如何在平面上获取三个不共线点?- C++

4
我正在尝试实现线面交叉算法。根据维基百科,我需要平面上三个非共线点来完成此操作。
因此,我尝试在C++中实现此算法。然而,有些地方肯定出了问题,因为选择任意的x和y坐标并使其适合于平面是没有意义的。如果平面是垂直于x轴的,则y=1的点将不在平面上。
我意识到这个问题已经在StackOverflow上发布了很多次,并且我看到了很多解决方案,其中平面由3个点定义。但是我只有一个法向量和一个位置。在解决我的非共线点查找器之前,我无法测试我的线面交叉算法。

目前的问题是,我正在除以normal.z,当normal.z为0时,这显然行不通。

我正在使用这个平面进行测试:Plane* p = new Plane(Color(), Vec3d(0.0,0.0,0.0), Vec3d(0.0,1.0,0.0)); //第二个参数:位置,第三个参数:法向量

当前的代码会得出错误的答案:

{0 , 0 , 0} // alright, this is the original
{12.8377 , 17.2728 , -inf} // obviously this is not a non-colinear point on the given plane

这是我的代码:

std::vector<Vec3d>* Plane::getThreeNonColinearPoints() {
    std::vector<Vec3d>* v = new std::vector<Vec3d>();

    v->push_back(Vec3d(position.x, position.y, position.z)); // original position can serve as one of the three non-colinear points.

    srandom(time(NULL));

    double rx, ry, rz, start;

    rx = Plane::fRand(10.0, 20.0);
    ry = Plane::fRand(10.0, 20.0);
    // Formula from here: http://en.wikipedia.org/wiki/Plane_(geometry)#Definition_with_a_point_and_a_normal_vector
    // nx(x-x0) + ny(y-y0) + nz(z-z0) = 0
    // |-----------------| <- this is "start"
    //I'll try to insert position as x0,y0,z0 and normal as nx,ny,nz, and solve the equation
    start = normal.x * (rx - position.x) + normal.y * (ry - position.y);
    // nz(z-z0) = -start
    start = -start;
    // (z-z0) = start/nz
    start /= normal.z; // division by zero
    // z = start+z0
    start += position.z;
    rz = start;

    v->push_back(Vec3d(rx, ry, rz));

    // TODO one more point

    return v;
}

我意识到我可能完全错误地尝试解决这个问题。如果是这样,请提供一个具体的实现链接。当我看到这么多的线-平面相交实现时,我相信一定存在这样的实现。
提前感谢。

实际上,您可以使用方程ax + by + cz == d来描述任何平面。任何具有b == 0.0的平面都将与y轴平行(正如您所描述的那样),因为在这种情况下,y的值无法影响不等式。 - comingstorm
3个回答

5

一个平面可以用多种方式定义。通常使用平面上的一点和法向量。要从三个点(P1, P2, P3)中获取法向量,请取三角形边的叉积。

P1 = {x1, y1, z1};
P2 = {x2, y2, z2};
P3 = {x3, y3, z3};

N = UNIT( CROSS( P2-P1, P3-P1 ) );
Plane P = { P1, N }

反过来,从一个点P1和法线N到三个点,你可以从任何方向G(而非沿法线N)开始,使得DOT(G,N)!=0。然后,在平面上的两个正交方向为:

//try G={0,0,1} or {0,1,0} or {1,0,0}
G = {0,0,1};
if( MAG(CROSS(G,N))<TINY ) { G = {0,1,0}; }
if( MAG(CROSS(G,N))<TINY ) { G = {1,0,0}; }
U = UNIT( CROSS(N, G) );  
V = CROSS(U,N);
P2 = P1 + U;
P3 = P1 + V;

一条直线由一个点和一个方向定义。通常情况下,两个点(Q1, Q2)定义了这条直线。
Q1 = {x1, y1, z1};
Q2 = {x2, y2, z2};
E = UNIT( Q2-Q1 );
Line L = { Q1, E }

线和平面的交点由直线上的点r=Q1+t*E与平面相交得到,满足DOT(r-P1,N)=0。通过求解沿着直线的标量距离t来解决这个问题。

t = DOT(P1-Q1,N)/DOT(E,N);

以及位置为

r = Q1+(t*E);

注意:函数DOT()返回两个向量的点积,CROSS()返回它们的叉积,UNIT()返回单位向量(其大小为1)。

DOT(P,Q) = P[0]*Q[0]+P[1]*Q[1]+P[2]*Q[2];
CROSS(P,Q) = { P[1]*Q[2]-P[2]*Q[1], P[2]*Q[0]-P[0]*Q[2], P[0]*Q[1]-P[1]*Q[0] };
UNIT(P) = {P[0]/sqrt(DOT(P,P)), P[1]/sqrt(DOT(P,P)), P[2]/sqrt(DOT(P,P))};
t*P =  { t*P[0], t*P[1], t*P[2] };
MAG(P) = sqrt(P[0]*P[0]+P[1]*P[1]+P[2]*P[2]);

我认为这是一个非常接地气和易于理解的答案。我明白代码清单2中的叉积有效地旋转了法向量,是吗?我还想知道,如果您的线平面交点算法中没有交点会发生什么。在清单4中分配“t”的代码会产生垃圾或除以零吗?谢谢。 - Janus Troelsen
@user309483 - 叉积可以找到两个向量的公共法线。它不一定是旋转。如果平面法线N垂直于线方向E,则它们的点积为零,t会除以零。 - John Alexiou
非常感谢,我现在已经在C++中实现了它并且运作正常。任何想要代码的人都可以给我发消息! - Janus Troelsen

2

您可能会发现一种易于实现的方法是查看飞机与坐标轴相交的位置。对于由方程aX + bY + cZ - d = 0给出的平面,将两个变量保持为0并解决第三个变量。因此,解决方案将是(假设abcd都是非零的):

(d/a, 0, 0)
(0, d/b, 0)
(0, 0, d/c)

当你在解决问题时,需要考虑一个或多个系数为0的情况,以避免出现退化或共线解。例如,如果恰好有一个系数为0(比如说a=0),那么你应该使用

(1, d/b, 0)
(0, d/b, 0)
(0, 0, d/c)

如果恰好有两个系数为0(比如a=0和b=0),您可以使用以下公式:
(1, 0, d/c)
(0, 1, d/c)
(0, 0, d/c)

如果d=0,平面与三个轴在原点相交,因此您可以使用以下公式:
(1, 0, -a/c)
(0, -c/b, 1)
(-b/a, 1, 0)

您需要为d和仅有另一个系数为0的情况,以及d和其他两个系数均为0的情况找到类似的案例。总共应该有16种情况,但有一些事情应该让它变得更加可管理。


1

其中N=(Nx,Ny,Nz)是法向量,你可以将点N(Ny,Nz,Nx)(Nz,Nx,Ny)投影到平面上:它们保证是不同的。

或者,如果PQ在平面上,对于任何t!=0,其中x是叉积,则P+t(Q-P)xN也在平面上。

或者,如果M!=N是任意向量,则K=MxNL=KxN与平面共线,平面上的任何点p都可以写成p=Origin+sK+tL,其中s,t为某些值。


你的第一种方法会失败的一个情况是法向量的三个分量都相同 (例如:N = (1, 1, 1))。 - andand
更加健壮的方法:如果|Nx|>|Ny|,则P = (-Nz, 0, Nx),否则P = (0, -Nz, Ny)。 - MBo
我不确定我理解这句话:“如果M!=N是一个任意的向量”。你说的“!=”是指“不等于”,对吗?布尔表达式的结果怎么可能是一个向量呢?谢谢你的耐心。我理解“K=MxN是共线”的意思,因为它们相等,所以你的意思是它们都是共线的。但是当它们“不相等”时,我应该如何解释呢? - Janus Troelsen
其中M是任意向量,满足M不等于N。 - spraff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接