3d point on circumference of a circle with a cente

2020-02-11 18:02发布

问题:

My question is similar to How to Make a Point Orbit a Line, 3D but the answer there didn't seem to solve my problem. And what I am looking for is a general solution.

For the record I am trying to solve an issue in OpenGL ES (Java/Android).

I have a circle with a 3D point for its center, a radius, and a 3D vector specifying the normal to the plane the circle lies in.

I need to find the 3D point representing the point on the circumference at a given angle from the 'rotated' X-axis (rotated according to the normal vector).

I already have an implementation in a Circle class of a member function, pointAt, which works under limited circumstances. Specifically, in my current implementation I assume the circle lies in the X-Y plane and return a point accordingly and then, since I know the circle is actually lying in the X-Z plane I simply swap the Y and Z values in the returned point and it works. However, this is not a general solution and that is what I am going to need.

When I tried the algorithm given in How to Make a Point Orbit a Line, 3D, I got points pretty far removed from where they should have been.

So, how, can I calculate a point on the circumference of such a circle?

[Edit] I guess my explanation wasn't sufficient. My assumption is that a circle is 'normally' in the X-Y plane with a normal vector of (0, 0, 1) - 1 in the Z direction. If a point on the circumference is needed the point is defined by:

x = R*cos(a) + Cx
y = R*sin(a) + Cy

where R is the radius, Cx and Cy are the X and Y coordinates of the center of the circle, and a is the angle from a vector through the circle's center point and parallel with the X-axis.

Now, if the circle doesn't have a normal vector pointing along the Z-axis but, instead, is some arbitrary (x, y, z) vector, how do I find that same point?

回答1:

What you need is a new coordinate system to place the circle. As any common coordinate system, we'll want the base vectors to be orthogonal to each other, and have length 1 each. I'll name the base vectors v1, v2, and v3, which correspond to x, y, and z in order.

The new base vector that replaces z, which is v3 is given by the normal vector of the circle. If it's not normalized yet, you'll want to normalize it here:

     [ v3x ]
v3 = [ v3y ] = normalize(circleNormal)
     [ v3z ]

Next, we'll chose v1. This can be an arbitrary vector that is orthogonal to v3. Since we want it to take the place of the x-axis, we can choose it to have an y-component of 0:

               [ v3z ]
v1 = normalize([ 0   ])
               [ -v3x]

Note that the dot product of this vector with v3 is 0, which means that the two vectors are indeed orthogonal. The vector will be degenerate if the normal vector of the circle points exactly in the y-direction. I'll let you figure out how to handle that if it's a concern in your usage.

Now we just need the last vector, which can be calculated as the cross product of the other two:

v2 = v3 x v1

This will already be normalized since v1 and v3 were normalized, and are orthogonal.

With this new basis, points on the circle can now be calculated as:

p = centerPoint + R * (cos(a) * v1 + sin(a) * v2)

Putting the whole thing closer to code form (untested):

// Only needed if normal vector (nx, ny, nz) is not already normalized.
float s = 1.0f / (nx * nx + ny * ny + nz * nz);
float v3x = s * nx;
float v3y = s * ny;
float v3z = s * nz;

// Calculate v1.
s = 1.0f / (v3x * v3x + v3z * v3z);
float v1x = s * v3z;
float v1y = 0.0f;
float v1z = s * -v3x;

// Calculate v2 as cross product of v3 and v1.
// Since v1y is 0, it could be removed from the following calculations. Keeping it for consistency.
float v2x = v3y * v1z - v3z * v1y;
float v2y = v3z * v1x - v3x * v1z;
float v2z = v3x * v1y - v3y * v1x;

// For each circle point.
px = cx + r * (v1x * cos(a) + v2x * sin(a))
py = cy + r * (v1y * cos(a) + v2y * sin(a))
pz = cz + r * (v1z * cos(a) + v2z * sin(a))


回答2:

So, in working with @Timothy Shields in comments on How to Make a Point Orbit a Line, 3D I got my answer. Here is an excerpt from my resulting Circle class if anyone is interested. The normalized member on the Vector class simply divides each of the vector's components by the vector length to return a unit vector. Circle, Vector, and Point are all classes I have created for my app.

public class Circle {
    public final Point center;
    public final float radius;
    public final Vector normal;

    ....
    public Point pointAt(float angle) {
        float xv = (float) Math.cos(angle);
        float yv = (float) Math.sin(angle);

        Vector v = findV();
        Vector w = v.crossProduct(normal);

        // Return center + r * (V * cos(a) + W * sin(a))
        Vector r1 = v.scale(radius*xv);
        Vector r2 = w.scale(radius*yv);

        return new Point(center.x + r1.x + r2.x,
                         center.y + r1.y + r2.y,
                         center.z + r1.z + r2.z);
    }

    private Vector findV() {
        Vector vp = new Vector(0f, 0f, 0f);
        if (normal.x != 0 || normal.y != 0) {
            vp = new Vector(0f, 0f, 1f);
        } else if (normal.x != 0 || normal.z != 0) {
            vp = new Vector(0f, 1f, 0f);
        } else if (normal.y != 0 || normal.z != 0) {
            vp = new Vector(1f, 0f, 0f);
        } else {
            return null; // will cause an exception later.
        }

        Vector cp = normal.crossProduct(vp);
        return cp.normalized();
    }

}