圆周上的三维点,有中心、半径和法向量。

问题描述 投票:0回答:3

我的问题类似于 如何使一个点绕着一条线运行,3D 但那里的答案似乎并不能解决我的问题。 而我正在寻找的是一个通用的解决方案。

我试图用OpenGL ES(JavaAndroid)解决一个问题。

我有一个圆,它的中心是一个3D点,半径和一个3D向量,指定圆所在平面的法线。

我需要找到代表圆周上与 "旋转 "X轴(根据法线矢量旋转)成一定角度的3D点。

我已经有了一个在X轴上给定角度的3D点的实现。Circle 成员函数的类。pointAt,这在有限的情况下是可行的。 具体来说,在我目前的实现中,我假设圆位于X-Y平面上,并相应地返回一个点,然后,由于我知道圆实际上位于X-Z平面上,我只需将返回点中的Y和Z值互换一下就可以了。 然而,这并不是一个通用的解决方案,这也是我所要的。

当我尝试了在 如何使一个点绕着一条线运行,3D我得到的点离它们应该在的地方很远。

那么,如何,才能计算出这样一个圆周上的点呢?

编辑]我想我的解释还不够充分。 我的假设是,圆在X-Y平面上是 "正常 "的,Z方向的法向量为(0,0,1)-1。 如果在圆周上需要一个点,该点的定义是:。

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

其中 R 是半径。CxCyXY 圆心的坐标,以及 a 是通过圆的中心点并与X轴平行的向量的角度。

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

java android opengl-es 3d geometry
3个回答
6
投票

你需要的是一个新的坐标系来放置圆。就像任何普通的坐标系一样,我们希望基向量相互正交,并且每个基向量的长度为1。我会把基向量命名为 v1, v2v3,它们依次对应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))

0
投票

所以,在与@Timothy Shields合作的过程中,他在评论中说: 如何使一个点绕着一条线运行,3D 我得到了我的答案。 下面是我的成果圈课的节选,如果有人感兴趣的话。 该 normalized 在座 Vector 类简单地将向量的每一个分量除以向量长度,返回一个单位向量。 Circle, VectorPoint 都是我为我的应用程序创建的类。

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

}

0
投票

按照Reto的回答中的策略,使用3D图形的 toxilib库做了一个实现。 做了两个版本,getPointOnCircle使用的是未分叉的原始 toxilib,getPointOnCircleD使用的是分叉的 toxiLib版本,采用了双倍函数。

我同意评论中说的问题,问的不全面。 圆的起始位置没有指定(什么位置对应角度==0.0 ? 我想补充的是,圆的方向也没有指定(Clockwize还是CounterClockwise?)。为了符合北美数学和物理学的惯例,我将Reto的角度乘以-1,得到我想要的CCW方向的轴心,左手掌心朝向+X,手指朝向+Y,拇指朝向+Z。 在测试用例中,我记录了从代码中得到的方向。

我的用例是在代码中绘制圆柱体和螺旋体。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)));
  }  
}
© www.soinside.com 2019 - 2024. All rights reserved.