找到两条2D线段的交点很容易; the formula is straight forward。但我担心找到两条3D线段的交点不是。
算法是什么,在C#中最好找到两个3D线段的交点?
我找到了C++ implementation here。但我不相信解决方案,因为它优先考虑某个平面(看看perp
在实现部分下实现的方式,它假定优先选择z plane
。任何通用算法都不能假设任何飞机方向或偏好。)
有更好的解决方案吗?
答案 0 :(得分:14)
大多数3D线条不相交。一种可靠的方法是找到两条3D线之间的最短线。如果最短的线的长度为零(或者距离小于您指定的任何公差),那么您就知道两条原始线相交。
查找the shortest line between two 3D lines, written by Paul Bourke的方法总结/解释如下:
在下面的内容中,一条线将由位于其上的两个点定义,a 由点P1和P2定义的线“a”上的点具有等式
类似地,点P4和P4定义的第二行“b”上的点 将写成Pa = P1 + mua (P2 - P1)
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。
我们的想法是利用向量代数,在此阶段之前使用dot
和cross
来解决问题:
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 种可能的结果:
线段是平行的,所以它们不相交,或者,
线段不平行,它们所在的无限长线确实相交,但相交点不在任一线段的边界内,或者,
两条线相交且交点在 a 线的边界内但不在 b 线的边界内,或者,
两条线相交且交点在 b 线的边界内,但不在 a 线的边界内,或者,
两条线相交,交点在两条线段的边界内。
当直线相交且交点在直线 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 的标量倍数替换它时,我发现它运行良好。
但无论如何,非常感谢鲍勃。他想出了困难的部分。