求解两个三维线段交点的算法

时间:2010-02-23 07:49:34

标签: c# math

找到两条2D线段的交点很容易; the formula is straight forward。但我担心找到两条3D线段的交点不是。

算法是什么,在C#中最好找到两个3D线段的交点?

我找到了C++ implementation here。但我不相信解决方案,因为它优先考虑某个平面(看看perp在实现部分下实现的方式,它假定优先选择z plane。任何通用算法都不能假设任何飞机方向或偏好。)

有更好的解决方案吗?

8 个答案:

答案 0 :(得分:14)

大多数3D线条不相交。一种可靠的方法是找到两条3D线之间的最短线。如果最短的线的长度为零(或者距离小于您指定的任何公差),那么您就知道两条原始线相交。

enter image description here

查找the shortest line between two 3D lines, written by Paul Bourke的方法总结/解释如下:

  

在下面的内容中,一条线将由位于其上的两个点定义,a   由点P1和P2定义的线“a”上的点具有等式

Pa = P1 + mua (P2 - P1)
     类似地,点P4和P4定义的第二行“b”上的点   将写成

Pb = P3 + mub (P4 - P3)
     

mua和mub的值范围从负无穷大到正无穷大。   P1 P2和P3 P4之间的线段具有相应的μ   介于0和1之间。

     

有两种方法可以找到最短的线段   行“a”和“b”。

接近一个方法:

  

首先是记下线的长度   段加入两条线然后找到最小值。那是,   最小化以下

|| Pb - Pa ||^2
     

用线的方程代替

|| P1 - P3 + mua (P2 - P1) - mub (P4 - P3) ||^2
     

然后可以在(x,y,z)分量中扩展上述内容。

     

有条件要求至少满足,衍生品有   对mua和mub的尊重必须为零。 ......以上功能只有   一个最小值,没有其他最小值或最大值。那么这两个方程就可以了   为mua和mub解决,找到的实际交叉点   将mu的值代入线的原始方程。

方法二:

  

另一种方法,但给出完全相同的方程式   意识到两条线之间的最短线段   垂直于两条线。这允许我们写两个   点积的方程为

(Pa - Pb) dot (P2 - P1) = 0
(Pa - Pb) dot (P4 - P3) = 0
     

根据线的等式扩展这些

( P1 - P3 + mua (P2 - P1) - mub (P4 - P3) ) dot (P2 - P1) = 0
( P1 - P3 + mua (P2 - P1) - mub (P4 - P3) ) dot (P4 - P3) = 0
     

根据坐标(x,y,z)扩展这些......   结果如下

d1321 + mua d2121 - mub d4321 = 0
d1343 + mua d4321 - mub d4343 = 0
     

,其中

dmnop = (xm - xn)(xo - xp) + (ym - yn)(yo - yp) + (zm - zn)(zo - zp)
     

请注意dmnop = dopmn

     

最后,解决mua问题

mua = ( d1343 d4321 - d1321 d4343 ) / ( d2121 d4343 - d4321 d4321 )
     

和back-substitute给出了mub

mub = ( d1343 + mua d4321 ) / d4343

找到此方法on Paul Bourke's website,这是一种出色的几何资源。该网站已经重组,因此向下滚动以查找主题。

答案 1 :(得分:4)

// This code in C++ works for me in 2d and 3d

// assume Coord has members x(), y() and z() and supports arithmetic operations
// that is Coord u + Coord v = u.x() + v.x(), u.y() + v.y(), u.z() + v.z()

inline Point
dot(const Coord& u, const Coord& v) 
{
return u.x() * v.x() + u.y() * v.y() + u.z() * v.z();   
}

inline Point
norm2( const Coord& v )
{
return v.x() * v.x() + v.y() * v.y() + v.z() * v.z();
}

inline Point
norm( const Coord& v ) 
{
return sqrt(norm2(v));
}

inline
Coord
cross( const Coord& b, const Coord& c) // cross product
{
return Coord(b.y() * c.z() - c.y() * b.z(), b.z() * c.x() - c.z() * b.x(), b.x() *  c.y() - c.x() * b.y());
}

bool 
intersection(const Line& a, const Line& b, Coord& ip)
// http://mathworld.wolfram.com/Line-LineIntersection.html
// in 3d; will also work in 2d if z components are 0
{
Coord da = a.second - a.first; 
Coord db = b.second - b.first;
    Coord dc = b.first - a.first;

if (dot(dc, cross(da,db)) != 0.0) // lines are not coplanar
    return false;

Point s = dot(cross(dc,db),cross(da,db)) / norm2(cross(da,db));
if (s >= 0.0 && s <= 1.0)
{
    ip = a.first + da * Coord(s,s,s);
    return true;
}

return false;
}

答案 2 :(得分:3)

我找到了一个解决方案:it's here

我们的想法是利用向量代数,在此阶段之前使用dotcross来解决问题:

a (V1 X V2) = (P2 - P1) X V2

并计算a

请注意,此实现不需要任何平面或轴作为参考。

答案 3 :(得分:2)

我尝试了@Bill answer,实际上每次都不起作用,我可以解释一下。基于link in his code.例如,我们有两个线段 AB CD

  

A =(2,1,5),B =(1,2,5)和C =(2,1,3)和D =(2,1,2)

当你试图得到交叉点时,它可能会告诉你它是A点(不正确)或没有交叉点(正确)。根据您放置这些细分的顺序。

x = A +(B-A)s
x = C +(D-C)t

Bill解决了 s 但从未解决 t 。由于您希望交叉点位于两个线段上,因此 s t 必须来自区间&lt; 0,1&gt; 。在我的例子中实际发生的是,只有 s ,如果来自该区间, t 是-2。 A 位于由 C D 定义的行上,但不在行 CD 上。

var s = Vector3.Dot(Vector3.Cross(dc, db), Vector3.Cross(da, db)) / Norm2(Vector3.Cross(da, db));

var t = Vector3.Dot(Vector3.Cross(dc, da), Vector3.Cross(da, db)) / Norm2(Vector3.Cross(da, db));

其中da是B-A,db是D-C,dc是C-A,我只保留了Bill提供的名字。

然后正如我所说,你必须检查 s t 是否来自&lt; 0,1&gt; ,你可以计算出结果。基于上面的公式。

if ((s >= 0 && s <= 1) && (k >= 0 && k <= 1))
{
   Vector3 res = new Vector3(this.A.x + da.x * s, this.A.y + da.y * s, this.A.z + da.z * s);
}

比尔答案的另一个问题是当两条线共线并且有一个以上的交叉点时。会有零除。你想避免这种情况。

答案 4 :(得分:1)

  

但我不敢找到两个3D线段的交点,

我认为是。您可以使用与2d(或任何其他维度)完全相同的方式找到交点。唯一的区别是,得到的线性方程组更可能没有解(意味着线不相交)。

你可以手动解决一般方程式,只需使用你的解决方案,或者用编程方式解决它,例如Gaussian elemination

答案 5 :(得分:1)

您提到的原始来源仅适用于二维情况。 perp的实现很好。 x和y的使用只是变量,并不表示对特定平面的偏好。

在3d情况下,同一站点具有此功能: “ http://geomalgorithms.com/a07-_distance.html

貌似Eberly撰写了回复: “ https://www.geometrictools.com/Documentation/DistanceLine3Line3.pdf

在这里放置这些内容是因为google指向几何算法和这篇文章。

答案 6 :(得分:0)

我找到了答案!

在上面的答案中,我找到了以下等式:

Eq#1:var s = Vector3.Dot(Vector3.Cross(dc, db), Vector3.Cross(da, db)) / Norm2(Vector3.Cross(da, db));

Eq#2:var t = Vector3.Dot(Vector3.Cross(dc, da), Vector3.Cross(da, db)) / Norm2(Vector3.Cross(da, db));

然后我修改了#3rd方程:

Eq#3:

if ((s >= 0 && s <= 1) && (k >= 0 && k <= 1))
{
   Vector3 res = new Vector3(this.A.x + da.x * s, this.A.y + da.y * s, this.A.z + da.z * s);
}

在保持等式1和等式2相同的同时,我创建了以下等式:

MyEq#1:Vector3f p0 = da.mul(s).add(A<vector>); MyEq#2:Vector3f p1 = db.mul(t).add(C<vector>);

然后我对创建另外三个方程进行了大胆的猜测:

MyEq#3:Vector3f p0z = projUV(da, p0).add(A<vector>); MyEq#4:Vector3f p1z = projUV(db, p1).add(C<vector>);

最后,减去projUV(1,2)的两个量值,可以得出0到0.001f之间的误差范围,以找出两条线是否相交。

MyEq#5:var m = p0z.magnitude() - p1z.magnitude();

现在,我介意您,这是用Java完成的。此说明尚未准备好Java约定。只需根据上面的公式进行操作即可。 (提示:请不要转换到世界空间,以使UV方程的两个投影都恰好落在您想要的位置。)

这些等式在我的程序中在视觉上都是正确的。

答案 7 :(得分:0)

除了 Bobs 的回答:

我在测试中发现,intersection() 函数解决了原来问题的一半,这是一种找到两条 3D 线 线段的交点的算法。

假设这些线是共面的,这个问题有 5 种可能的结果:

  1. 线段是平行的,所以它们不相交,或者,

  2. 线段不平行,它们所在的无限长线确实相交,但相交点不在任一线段的边界内,或者,

  3. 两条线相交且交点在 a 线的边界内但不在 b 线的边界内,或者,

  4. 两条线相交且交点在 b 线的边界内,但不在 a 线的边界内,或者,

  5. 两条线相交,交点在两条线段的边界内。

当直线相交且交点在直线 a 的边界内时,Bob 的intersection() 函数返回true,但如果直线相交且交点仅在直线b 的边界内,则返回false。

>

但是,如果您调用 intersect() 两次,首先是 a 行,然后是 b 行,然后第二次是 b 行和 a 行(交换了第一个和第二个参数),那么如果两个调用都返回 true,则相交包含在两者中线段(案例 5)。如果两个调用都返回 false,则两个线段都不包含相交(情况 2)。如果只有一个调用返回 true,则作为该调用的第一个参数传递的段包含交点(情况 3 或 4)。

此外,如果调用 norm2(cross(da,db)) 的返回值等于 0.0,则线段是平行的(情况 1)。

我在测试中注意到的另一件事是,经常使用这种代码实现的固定精度浮点数,dot(dc, cross(da,db)) 返回 0.0 可能非常不寻常,因此返回false 当情况并非如此时可能不是您想要的。您可能希望引入一个阈值,低于该阈值代码将继续执行而不是返回 false。这表明线段在 3D 中倾斜,但根据您的应用,您可能希望允许少量倾斜。

我注意到的最后一件事是 Bill 代码中的这条语句:

ip = a.first + da * Coord(s,s,s);

那个 da * Coord(s,s,s) 看起来是一个向量乘以向量。当我用 da * s 的标量倍数替换它时,我发现它运行良好。

但无论如何,非常感谢鲍勃。他想出了困难的部分。

相关问题