我的问题类似于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
是半径,R
和Cx
是圆心的Cy
和X
坐标,{{1} }是从矢量到圆的中心点并与X轴平行的角度。
现在,如果圆圈没有沿Z轴指向的法线向量,而是某个任意(x,y,z)向量,我该如何找到相同的点?
答案 0 :(得分:3)
您需要的是一个用于放置圆的新坐标系。作为任何公共坐标系,我们希望基矢量彼此正交,并且每个具有长度1。我将基本向量命名为v1
,v2
和v3
,它们按顺序对应于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
由于v1
和v3
已归一化且正交,因此已经进行了规范化。
有了这个新基础,圆圈上的点现在可以计算为:
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
成员只是将每个向量的分量除以向量长度以返回单位向量。 Circle
,Vector
和Point
都是我为自己的应用创建的课程。
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)));
}
}