3D线段与平面的交点

7
我正在尝试实现一种线段和平面相交测试,它将根据是否与平面相交返回true或false。如果线段与平面有交点,则函数还将返回线段与平面相交的接触点;如果没有交点,则该函数仍应返回线段作为光线的交点。我使用了Christer Ericson的《Real-time Collision Detection》中的信息和代码,但我认为我没有正确地实现它。
我使用的平面是从三角形的法线和顶点派生出来的。无论交点是否位于我用来派生平面的三角形上,我都希望找到在平面上的交点位置。
该函数的参数如下:
contact = the contact point on the plane, this is what i want calculated
ray = B - A, simply the line from A to B
rayOrigin = A, the origin of the line segement
normal = normal of the plane (normal of a triangle)
coord = a point on the plane (vertice of a triangle)

以下是我使用的代码:
bool linePlaneIntersection(Vector& contact, Vector ray, Vector rayOrigin, Vector normal, Vector coord) {

    // calculate plane
    float d = Dot(normal, coord);

    if (Dot(normal, ray)) {
        return false; // avoid divide by zero
    }

    // Compute the t value for the directed line ray intersecting the plane
    float t = (d - Dot(normal, rayOrigin)) / Dot(normal, ray);

    // scale the ray by t
    Vector newRay = ray * t;

    // calc contact point
    contact = rayOrigin + newRay;

    if (t >= 0.0f && t <= 1.0f) {
        return true; // line intersects plane
    }
    return false; // line does not
}

在我的测试中,它从未返回true... 有什么想法吗?

你最终解决了吗? - nkint
4个回答

13

我回答这个问题是因为在谷歌上搜索C++光线相交示例时,它排在首位 :)

代码总是返回false,因为你在这里进入了if语句:

if (Dot(normal, ray)) {
   return false; // avoid divide by zero
}

如果向量垂直,则点积为零,这是要避免的情况(无交集),而非零数字在C中为真。
因此解决方案是取反(!)或执行 Dot(...) == 0。
在所有其他情况下,都将存在交集。

关于交点计算:

平面上所有点X遵循以下方程:

  

Dot(N, X) = d

其中N为法线,通过将平面上已知点放入方程中可以找到d

float d = Dot(normal, coord);

在射线上,一条直线的所有点 s 都可以表示为一个点 p 和一个给出方向的向量 D

s = p + x*D

所以如果我们搜索 x s 在平面上的位置,我们有:

Dot(N, s) = d
Dot(N, p + x*D) = d

点积 a.btranspose(a)*b
transpose(N)Nt

Nt*(p + x*D) = d
Nt*p + Nt*D*x = d (x scalar)
x = (d - Nt*p) / (Nt*D)
x = (d - Dot(N, p)) / Dot(N, D)

这给了我们:

float x = (d - Dot(normal, rayOrigin)) / Dot(normal, ray);
现在我们可以通过将x放入直线方程中来得到交点

s = p + x*D

Vector intersection = rayOrigin + x*ray;

上述代码更新为:
bool linePlaneIntersection(Vector& contact, Vector ray, Vector rayOrigin, 
                           Vector normal, Vector coord) {
    // 获取 d 值
    float d = Dot(normal, coord);
if (Dot(normal, ray) == 0) { return false; // 没有交点,直线与平面平行 }
// 计算射线在平面上的 X 值 float x = (d - Dot(normal, rayOrigin)) / Dot(normal, ray);
// 输出交点 *contact = rayOrigin + normalize(ray)*x; // 确保你的射线向量已经被归一化了 return true; }

说明 1:
d 值是什么意思?
对于两个向量 ab,点积实际上返回一个向量在另一个向量上的正交投影的长度乘以这个向量的长度。
但如果 a 被标准化(长度为1),那么 Dot(a, b) 就是向量 b 在向量 a 上的投影长度。对于我们的平面而言,d 给出的是所有平面点到原点法向量方向上的距离(a 是法向量)。我们可以通过比较在法向量上的投影长度(点积)来确定一个点是否在平面上。

说明 2:
如何检查射线是否与三角形相交?(用于光线追踪)
为了测试一条射线是否进入由3个顶点构成的三角形,首先必须像这里展示的那样,获取与三角形构成的平面的交点。
下一步是查看此点是否位于三角形内。这可以通过使用重心坐标来实现,重心坐标将平面上的一个点表示为其中三个点的组合。请参见重心

我认为你不应该在最后将光线规范化。 - undefined

2
我可能会错,但是代码中有几个地方似乎非常可疑。首先,考虑这一行代码:
// calculate plane
float d = Dot(normal, coord);

在这里,你的值d对应于平面法线(一个向量)和空间中的一个点(平面上的一个点)的点积。这似乎是错误的。特别地,如果你有任何通过原点的平面并使用原点作为坐标点,你将会得出以下计算:

d = Dot(normal, (0, 0, 0)) = 0

立即返回false。我不确定你在这里想要做什么,但我相信这不是你的本意。

代码中另一个看起来可疑的地方是这一行:

// Compute the t value for the directed line ray intersecting the plane
float t = (d - Dot(normal, rayOrigin)) / Dot(normal, ray);

请注意,您正在计算飞机法向量(一个向量)和射线起点(空间中的一个点)之间的点积。这似乎很奇怪,因为这意味着根据射线在空间中的起始位置,您用于射线的缩放因子会发生变化。我建议您再次查看此代码,以确定这是否确实是您想要的。

希望能对您有所帮助!


我的错误应该是 if (Dot(normal, ray) == 0),就像平面垂直于光线,因此可能没有交点。至于 float t = (d - Dot(normal, rayOrigin)) / Dot(normal, ray); 这是书中给出的方程式。 - kbirk
射线使用的缩放因子根据射线起点进行更改,以补偿分母中相应的缩放,需要注意的是,线向量“ray”不是单位向量,而是整个线段。 - Keith
@Keith- 啊,我没听懂。现在明白了。 - templatetypedef
2
@user785259:纠正了那个错误,测试是否可以工作? - Beta
是的 - 它已经修正并可以工作。 - MCone

1

对我来说,这看起来很好。我已经独立检查了代数,对我来说这很好。

作为一个例子测试用例:

A = (0,0,1)
B = (0,0,-1)
coord = (0,0,0)
normal = (0,0,1)

这将会得到:

d = Dot( (0,0,1), (0,0,0)) = 0
Dot( (0,0,1), (0,0,-2)) = -2 // so trap for the line being in the plane passes.
t = (0 - Dot( (0,0,1), (0,0,1) ) / Dot( (0,0,1), (0,0,-2)) = ( 0 - 1) / -2 = 1/2
contact = (0,0,1) + 1/2 (0,0,-2) = (0,0,0) // as expected.

因此,在@templatetypedef的答案后进行了修改,我唯一能看到问题的地方是在实现其他操作之一时,无论是Dot()还是Vector运算符。


1

这个版本在OpenGL C#应用程序中对我有效。

bool GetLinePlaneIntersection(out vec3 contact, vec3 ray_origin, vec3 ray_end, vec3 normal, vec3 coord)
{
    contact = new vec3();
    vec3 ray = ray_end - ray_origin; 

    float d = glm.dot(normal, coord);
    if (glm.dot(normal, ray) == 0)
    {
        return false; 
    }
    
    float t = (d - glm.dot(normal, ray_origin)) / glm.dot(normal, ray);
    
    contact = ray_origin + ray * t; 
    return true;
}

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