3d点在圆的圆周与中心,半径和法线向量

时间:2014-12-30 23:20:32

标签: java android opengl-es 3d geometry

我的问题类似于How to Make a Point Orbit a Line, 3D,但那里的答案似乎并没有解决我的问题。我正在寻找的是一般解决方案。

为了记录,我试图解决OpenGL ES(Java / Android)中的问题。

我有一个圆圈,其中心有一个3D点,一个半径和一个3D矢量,用于指定圆所在平面的法线。

我需要找到一个3D点,它表示圆周上圆周上的点与旋转的'#39; X轴(根据法线向量旋转)。

我已经在成员函数Circle的{​​{1}}类中实现了一个实现,它在有限的情况下工作。具体来说,在我目前的实现中,我假设圆位于XY平面并相应地返回一个点然后,因为我知道圆实际上位于XZ平面中,我只是在返回的点中交换Y和Z值并且它起作用。但是,这不是一般解决方案,而是我将需要的。

当我尝试How to Make a Point Orbit a Line, 3D中给出的算法时,我得到的分数远远超出它们应该的位置。

那么,我怎么能计算出这样一个圆周上的一个点呢?

[编辑] 我想我的解释还不够。我的假设是,一个圆圈通常是'在X方向上具有法向矢量(0,0,1)-1的X-Y平面。如果需要圆周上的点,则该点由以下定义:

pointAt

其中x = R*cos(a) + Cx y = R*sin(a) + Cy 是半径,RCx是圆心的CyX坐标,{{1} }是从矢量到圆的中心点并与X轴平行的角度。

现在,如果圆圈没有沿Z轴指向的法线向量,而是某个任意(x,y,z)向量,我该如何找到相同的点?

3 个答案:

答案 0 :(得分:3)

您需要的是一个用于放置圆的新坐标系。作为任何公共坐标系,我们希望基矢量彼此正交,并且每个具有长度1。我将基本向量命名为v1v2v3,它们按顺序对应于x,y和z。

替换z的新基矢量,即v3由圆的法线矢量给出。如果它尚未标准化,您可以在此处对其进行标准化:

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

接下来,我们选择v1。这可以是与v3正交的任意向量。由于我们希望它取代x轴,我们可以选择它的y分量为0:

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

请注意,此向量与v3的点积为0,这意味着这两个向量确实是正交的。如果圆的法向矢量精确地指向y方向,则矢量将是简并的。如果您的使用中存在问题,我会告诉您如何处理。

现在我们只需要最后一个向量,它可以作为另外两个的叉积来计算:

v2 = v3 x v1

由于v1v3已归一化且正交,因此已经进行了规范化。

有了这个新基础,圆圈上的点现在可以计算为:

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

将整个事物更接近代码形式(未经测试):

// 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))

答案 1 :(得分:0)

所以,在How to Make a Point Orbit a Line, 3D的评论中与@Timothy Shields合作,我得到了答案。如果有人感兴趣的话,这是我产生的Circle课程的摘录。 normalized类上的Vector成员只是将每个向量的分量除以向量长度以返回单位向量。 CircleVectorPoint都是我为自己的应用创建的课程。

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();
    }

}

答案 2 :(得分:0)

根据Reto的回答,使用toxilib库对3D图形进行了实现。有两个版本,getPointOnCircle使用未分叉的原始toxilib,以及getPointOnCircleD使用分叉的toxiLib版本(使用双精度)。

我同意所提出的问题不完整的意见。没有指定圆的起始位置(与角度== 0.0?对应的位置)。我还要补充一点,也没有指定圆的方向(Clockwize或CounterClockwise?)。为了符合北美的数学和物理惯例,我将Reto的角度乘以-1,以获得对于左手手掌朝向+ X,手指朝向+ Y,拇指朝向+ Z的轴所需的CCW方向。在测试用例中,我记录了代码中的方向。

我的用例是在https://processing.org/环境中绘制圆柱和螺旋。为此,我提供了一种方法,该方法既可以返回圆上的点,也可以返回圆上的法线。

 /*  9 test cases seem necessary.
   *   1 from: zero length normal input   (Choose to not implement protection from this condition in default method)
   *   1 from: normal unaligned with any axis, 
   *   3 from: normal in any of the three axial planes, 
   *   3 from: normal along any of the three axies   
   *   1 from: 1.0 != normal.magnitude()  (Choose to not implement protection from this condition in default method)
   */  
   //VecD3D normal = new VecD3D();
   //normal = new VecD3D(1.0,1.0,1.0).normalize(); /* path 0, sets 0==angle near 0.7071,0.0000,0.7071  CCW from  -1,-1,-1  */
   //normal = new VecD3D(1.0,0.0,0.0);             /* path 0, sets 0==angle at -Z  CCW from -X */
   //normal = new VecD3D(0.0,1.0,0.0);             /* path 1, sets 0==angle at +X  CCW from -Y */
   //normal = new VecD3D(0.0,0.0,1.0);             /* path 0, sets 0==angle at +X  CCW from -Z */
   //normal = new VecD3D(1.0,1.0,0.0).normalize(); /* path 0, sets 0==angle at -Z  CCW from -1,-1, 0 */
   //normal = new VecD3D(0.0,1.0,1.0).normalize(); /* path 0, sets 0==angle at +X  CCW from  0,-1,-1 */
   //normal = new VecD3D(1.0,0.0,1.0).normalize(); /* path 0, sets 0==angle at +X  CCW from  1, 0, 1 */
   //normal = new VecD3D(100.,100.,100.);          /* path 0, sets 0==angle near 0.7071,0.0000,0.7071  CCW from  -1,-1,-1  */

/* based on  https://stackoverflow.com/questions/27714014/3d-point-on-circumference-of-a-circle-with-a-center-radius-and-normal-vector
 * This uses the extension of the toxiclibs.org 3D vector class extension fork providing doubles based vectors   https://github.com/TPMoyer/toxiclibs
 * This method does not check that the normal is normalized, and does not check that the normal is not the zero vector
 */
import toxi.geom.*;
VecD3D getPointOnCircleD(VecD3D v0, VecD3D normal,double angle,double radius){
  /* If you are not confident that the input normal will always have 
   *    1.0==normal.magnitude()
   * uncomment the last two lines of this comment block.   
   *
   * Two actions should be taken in order, 
   *   1'st if the input normal is the zero vector, insert a normal of your choice (I like up, because you should always know which way is up)
   *   2'nd normalize the vector
   * The need for the ordering is because 
   *     true == new VecD3D().normalize().isZeroVector();   // use .isZeroVector() instead of == compare to VecD3D.ZERO as the later fails  
   * The expected most likely source for a zero length normal is from an unmodified instance from a VecD3D default constructor 
   *     VecD3D normal = new VecD3D();
   *
   * if(normal.isZeroVector())normal=new VecD3D(0.,0.,1.);
   * normal=normal.normalize();
   */    
  if(normal.x != 0. || normal.z != 0.){
    VecD3D v1 = new VecD3D(normal.z,0.0,-normal.x).normalize();
    VecD3D v2 = normal.cross(v1);
    //log.info("getPointOnCircleD path 0");
    return (v0.add(v1.scale(Math.cos(-angle)).add(v2.scale(Math.sin(-angle))).scale(radius)));
  } else {
    VecD3D v1 = new VecD3D(normal.y,0.,-normal.x).normalize();
    VecD3D v2 = normal.cross(v1);
    //log.info("getPointOnCircleD path 1");
    return (v0.add(v1.scale(Math.cos(-angle)).add(v2.scale(Math.sin(-angle))).scale(radius)));
  }  
}
/* based on  https://stackoverflow.com/questions/27714014/3d-point-on-circumference-of-a-circle-with-a-center-radius-and-normal-vector
 * This uses the extension of the toxiclibs.org 3D vector class extension fork into using doubles   https://github.com/TPMoyer/toxiclibs
 */
VecD3D[] getPointAndNormalOnCircleD(VecD3D v0, VecD3D normal,double angle,double radius){
  /* If you are not confident that the input normal will always have 
   *    1.0==normal.magnitude()
   * uncomment the last two lines of this comment block.   
   *
   * Two actions should be taken in order, 
   *   1'st if the input normal is the zero vector, insert a normal of your choice (I like up, because you should always know which way is up)
   *   2'nd normalize the vector
   * The need for the ordering is because 
   *     true == new VecD3D().normalize().isZeroVector();   // use .isZeroVector() instead of == compare to VecD3D.ZERO as the later fails  
   * The expected most likely source for a zero length normal is from an unmodified instance from a VecD3D default constructor 
   *     VecD3D normal = new VecD3D();
   *
   * if(normal.isZeroVector())normal=new VecD3D(0.,0.,1.);
   * normal=normal.normalize();
   */  
  VecD3D[] out = new VecD3D[2];
  if(normal.x != 0. || normal.z != 0.){
    VecD3D v1 = new VecD3D(normal.z,0.0,-normal.x).normalize();
    VecD3D v2 = normal.cross(v1);
    out[1]=v1.scale(Math.cos(-angle)).add(v2.scale(Math.sin(-angle)));
    out[0]=v0.add(out[1].scale(radius));    
  } else {
    VecD3D v1 = new VecD3D(normal.y,0.,-normal.x).normalize();
    VecD3D v2 = normal.cross(v1);
    out[1]=v1.scale(Math.cos(-angle)).add(v2.scale(Math.sin(-angle)));
    out[0]=v0.add(out[1].scale(radius));
  }
  return out;
}
/* based on  https://stackoverflow.com/questions/27714014/3d-point-on-circumference-of-a-circle-with-a-center-radius-and-normal-vector
 * This uses the the toxiclibs.org 3D vector class   http://toxiclibs.org/
 */
Vec3D getPointOnCircle(Vec3D v0, Vec3D normal,float angle,float radius){
  /* If you are not confident that the input normal will always have 
   *    1.0==normal.magnitude()
   * uncomment the last two lines of this comment block.   
   *
   * Two actions should be taken in order, 
   *   1'st if the input normal is the zero vector, insert a normal of your choice (I like up, because you should always know which way is up)
   *   2'nd normalize the vector
   * The need for the ordering is because 
   *     true == new VecD3D().normalize().isZeroVector();   // use .isZeroVector() instead of == compare to VecD3D.ZERO as the later fails  
   * The expected most likely source for a zero length normal is from an unmodified instance from a VecD3D default constructor 
   *     VecD3D normal = new VecD3D();
   *
   * if(normal.isZeroVector())normal=new VecD3D(0.,0.,1.);
   * normal=normal.normalize();
   */    
  if(normal.x != 0. || normal.z != 0.){
    Vec3D v1 = new Vec3D(normal.z,0.0,-normal.x).normalize();
    Vec3D v2 = normal.cross(v1);
    return new Vec3D((v0.add(v1.scale((float)Math.cos(-angle)).add(v2.scale((float)Math.sin(-angle))).scale(radius))));
  } else {
    Vec3D v1 = new Vec3D(normal.y,0.,-normal.x).normalize();
    Vec3D v2 = normal.cross(v1);
    return (v0.add(v1.scale((float)Math.cos(-angle)).add(v2.scale((float)Math.sin(-angle))).scale(radius)));
  }  
}