当前位置:  开发笔记 > 编程语言 > 正文

如何检测两个线段相交的位置?

如何解决《如何检测两个线段相交的位置?》经验,为你挑选了16个好方法。

如何确定两条线是否相交,如果它们相交,在x,y点处是什么?



1> Gareth Rees..:

对于使用矢量交叉产品的这个问题,有一个很好的方法.将二维向量叉积v  ×  w定义v x  w y  -  v y  w x.

假设两个线段从pp  +  r和从qq  +  s.然后第一行上的任何点都可表示为p  +  t  r(对于标量参数  t)和第二行上的任何点可表示为q  +  u  s(对于标量参数  u).

两个线段相交

如果我们能找到tu,则两条线相交:

p + t  r = q + u  s

交点的公式

跨两侧小号,让

(p + t  rs =(q + u  ss

由于s  ×  s = 0,这意味着

t  (r × s)=(q - ps

因此,解决t:

t =(q - ps /(r × s)

以同样的方式,我们可以为解决:

(p + t  rr =(q + u  sr

u  (s × r)=(p - qr

u =(p - qr /(s × r)

为了减少计算步骤的数量,可以方便地重写如下(记住s  ×  r = -  r  ×  s):

u =(q - pr /(r × s)

现在有四种情况:

    如果r  ×  s  = 0且(q  -  p)×  r  = 0,则两条线共线.

    在这种情况下,根据第一个线段(p + t r)的等式表示第二个段(qq  +  s)的端点:

    t 0 =(q - p)·  r /(r  ·  r)

    t 1 =(q + s - p)·  r /(r  ·  r)= t 0 + s  ·  r /(r  ·  r)

    如果t 0t 1之间的间隔与区间[0,1]相交,则线段共线并重叠; 否则它们是共线的和不相交的.

    注意,如果sr指向相反的方向,则s  ·  r <0,因此要检查的间隔是[ t 1,t 0 ]而不是[ t 0,t 1 ].

    如果r  ×  s  = 0并且(q  -  p)×  r  ≠0,则两条线平行且不相交.

    如果- [R  ×  小号  ≠0和0≤   ≤1和0≤  ü  ≤1,两条线段在点满足p +  - [R = q + Ü  小号.

    否则,两个线段不平行但不相交.

信用:这种方法是3D线交叉算法的二维专业化,来自Ronald Goldman的文章"三维空间中的两条线的交点",发表于图形宝石,第304页.在三个方面,通常的情况是这些线是倾斜的(既不平行也不相交),在这种情况下,该方法给出了两条线最接近的点.


对于那些感兴趣的人,这里有一个简单的C#实现,为行提取PointF起点和终点坐标,这似乎正在起作用:http://ideone.com/PnPJgb
我在@Matt之后放了一个[JavaScript实现](https://github.com/pgkelley4/line-segments-intersect/blob/master/js/line-segments-intersect.js).我对Tekito指出的错误进行了更正.
加雷斯,我觉得我必须遗漏一些东西,但你如何用矢量划分(矢量)?你的*t*和*u*的解决方案以`/(r×s)`结尾,但`(r×s)`是一个向量,对吗?向量`(0,0,rx*sy - ry*sx)`.并且左侧类似地是平行于z轴的矢量.那么......我只是将z分量除以另一个z分量吗?t的公式实际上是`|(q - p)×s |/|(r×s)|`?
@LarsH:见第一段.
@myrkos:不.第一个线段"从p到p + r"运行,因此当它在参数项中表示为"p + tr"时,该段对应于0≤t≤1.类似地,对于另一个段.
@Mat:我认为你的C#有2个问题.一,对于共线性,你需要测试BOTH rxs和CmPxr = 0,所以后者应该嵌套在前者中.二,共线性重叠的测试看起来是错误的(没有提到D,并且有D位置是决定因素的例子)
我不明白案例1.这句话是什么意思?"根据第一线段的等式表示第二段(**q**和**q**+**s**)的端点(**p**+ t**r**)".`t0`和`t1`是第二段的端点吗?你是如何得出这些方程式的?...感谢您对这篇旧帖的回复.:)
当两条线共线,重叠或触摸并且|| qp ||时,这似乎对于特殊情况失败 大于|| r || 和|| s ||.例如p,p + r =(1,0),(5,0)和q,q + s =(15,0)和(5,0).但是当rs <0时,通过交换p和p + r可以很容易地解决这个问题.
这就是我见过的每个严肃的2D库中的方法.它依赖于同质空间中的点线对偶性,我仍然只有一点点的把握.
这基本上与我的技术相同,但我使用点积而不是交叉积.在这种情况下,我相信效率大致相同.
重新"这是三维交叉产品的大小" - 由于数量总是积极的,我认为它是更正确的说它是3D交叉产品的z组件.
@GarethRees感谢您的好帖子!我做了一个"简洁"[codeproject.com上的C#实现](http://www.codeproject.com/Tips/862988/Find-the-Intersection-Point-of-Two-Line-Segments),我在那里试着坚持你的算法和命名.但是,我认为@krjampani对他的边缘案例有一点意见.给定`r = p2-p; s = q2-q`,如果我要添加类似`if(r*s <0){Vector.Swap(p,p2); r = p2-p)}`我似乎发现了"非探索者"的交叉点,如果我不交换,我就不会这样.

2> Gavin..:

FWIW,以下功能(在C中)都检测线交叉并确定交点.它基于Andre LeMothe的" Windows游戏编程大师的诀窍 "中的算法.它与其他答案中的某些算法(例如Gareth's)没有什么不同.LeMothe然后使用Cramer的规则(不要问我)自己解决方程.

我可以证明它在我的微弱小行星克隆中起作用,并且似乎正确处理Elemental,Dan和Wodzu在其他答案中描述的边缘情况.它也可能比KingNestor发布的代码更快,因为它是所有的乘法和除法,没有平方根!

我想在那里有一些除以零的可能性,尽管在我的情况下这不是一个问题.很容易修改,以避免崩溃.

// Returns 1 if the lines intersect, otherwise 0. In addition, if the lines 
// intersect the intersection point may be stored in the floats i_x and i_y.
char get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y, 
    float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
    float s1_x, s1_y, s2_x, s2_y;
    s1_x = p1_x - p0_x;     s1_y = p1_y - p0_y;
    s2_x = p3_x - p2_x;     s2_y = p3_y - p2_y;

    float s, t;
    s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
    t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);

    if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
    {
        // Collision detected
        if (i_x != NULL)
            *i_x = p0_x + (t * s1_x);
        if (i_y != NULL)
            *i_y = p0_y + (t * s1_y);
        return 1;
    }

    return 0; // No collision
}

顺便说一句,我必须说,在LeMothe的书中,虽然他显然得到了正确的算法,但是他展示的具体例子插错了数字并且计算错误.例如:

(4*(4 - 1)+ 12*(7 - 1))/(17*4 + 12*10)

= 844/0.88

= 0.44

那让我困惑了几个小时.:(


我对这里发布的所有算法进行了性能测试,这个算法的速度至少是其他算法的两倍.谢谢发帖!
速度可以避免两个除法运算(除法成本超过乘法); 如果线条相交,则需要一个分割,如果它们不相交,则需要零.首先应该计算分母并提前停止,如果它为零(可能添加代码来检测共线性).接下来,不是直接计算`s`和`t`,而是测试两个分子和分母之间的关系.只有当线被确认相交时,你才真正需要计算`t`(但不是`s`)的值.
好的算法,但是它不能处理行列式为0的情况(上面的-s2_x*s1_y + s1_x*s2_y).如果它是0(或接近0),则线是平行的或共线的.如果它是共线的,则交叉点可以是另一个线段.
function getLineIntersection(p0_x,p0_y,p1_x,p1_y,p2_x,p2_y,p3_x,p3_y){var s1_x,s1_y,s2_x,s2_y; s1_x = p1_x - p0_x; s1_y = p1_y - p0_y; s2_x = p3_x - p2_x; s2_y = p3_y - p2_y; var s,t; s =(-s1_y*(p0_x-p2_x)+ s1_x*(p0_y-p2_y))/(-s2_x*s1_y + s1_x*s2_y); t =(s2_x*(p0_y-p2_y)-s2_y*(p0_x-p2_x))/(-s2_x*s1_y + s1_x*s2_y);
if(s> = 0 && s <= 1 && t> = 0 && t <= 1){//检测到碰撞var intX = p0_x +(t*s1_x); var intY = p0_y +(t*s1_y); return [intX,intY]; } return null; //没有碰撞}
Python:def lineIntersection(self,A,B,C,D):Bx_Ax = B [0] - A [0] By_Ay = B [1] - A [1] Dx_Cx = D [0] - C [0] Dy_Cy = D [1] - C [1]行列式=(-Dx_Cx*By_Ay + Bx_Ax*Dy_Cy)如果abs(行列式)<1e-20:返回无s =(-By_Ay*(A [0] - C [0] )+ Bx_Ax*(A [1] - C [1]))/行列式t =(Dx_Cx*(A [1] - C [1]) - Dy_Cy*(A [0] - C [0]))/如果s> = 0且s <= 1且t> = 0且t <= 1,则判定为:返回(A [0] +(t*Bx_Ax),A [1] +(t*By_Ay))返回无
计算行列式只需一次,存储它,并检查,如果它小于'EPSILON`,则向前返回0.如果不是,请使用已计算的行列式继续算法.对于大多数人来说,这应该处理并行/共线的情况.
很棒的答案!但废话.我花了15分钟将它移植到JavaScript只是为了发现有人已经做到了.d:

3> Jason Cohen..:

问题减少到这个问题:从A到B和从C到D的两条线是否相交?然后你可以问四次(在直线和矩形的四边之间).

这是执行此操作的矢量数学.我假设从A到B的线是有问题的线,从C到D的线是矩形线之一.我的符号是Ax"A的x坐标",Cy是"C的y坐标".并且" *"表示点积,例如A*B = Ax*Bx + Ay*By.

E = B-A = ( Bx-Ax, By-Ay )
F = D-C = ( Dx-Cx, Dy-Cy ) 
P = ( -Ey, Ex )
h = ( (A-C) * P ) / ( F * P )

这个h数字是关键.如果h0和之间1,则线相交,否则它们不相交.如果F*P为零,当然您无法进行计算,但在这种情况下,线条是平行的,因此仅在明显的情况下相交.

确切的交点是C + F*h.

更多乐趣:

如果h完全 01线在终点处触摸.您可以认为这是一个"交叉点"或不适合您.

具体来说,h你需要多少乘以线的长度才能与另一条线完全接触.

因此,If h<0表示矩形线在给定线的"后面"("方向"为"从A到B"),如果h>1矩形线在给定线的"前面".

推导:

A和C是指向线的起点的向量; E和F是形成线的A和C末端的向量.

对于平面中的任何两条非平行线,必须只有一对标量g,h并且这个等式成立:

A + E*g = C + F*h

为什么?因为两条非平行线必须相交,这意味着您可以将每条线都按比例缩放两条线并相互接触.

(首先,这看起来像是一个有两个未知数的方程! 但是当你认为这是一个二维矢量方程时,并不是这意味着这实际上是一对方程式xy.)

我们必须消除其中一个变量.一个简单的方法是使E术语为零.要做到这一点,使用一个用E点到零的向量取等式两边的点积.我P在上面调用了这个向量,我做了E的明显变换.

你现在有:

A*P = C*P + F*P*h
(A-C)*P = (F*P)*h
( (A-C)*P ) / (F*P) = h


这个算法很好.但Dan有一个漏洞,正如Dan @ http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/1201356#1201356&Elemental @ http所指出的那样://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/1201356#1201356如果您想更新您的答案以备将来参考,那将会很酷.谢谢.
这个答案完全错误.尝试A = {0,0},B = {0,1},C = {0,2} D = {2,0}
`A + E*g = C + F*h`两条线交叉**当且仅当**该方程的解(假设它们不是平行的)具有**两者,`g`和`h`**介于0和1之间(独立或独占,取决于您是否计算在终点触摸).
该算法似乎存在另一个问题.当它被馈送时,点A = {1,0} B = {2,0} C = {0,0} D = {1,0},尽管线段在末端明显接触,F*P(以及E*Q,与用户下面的修正符一致)都是0,因此除以0得到h和g.仍然致力于解决这个问题,但我认为这个问题值得指出.
这个算法在数值上是否稳定?我尝试了一个similliar aproach,结果在使用花车时给出了奇怪的结果.

4> Elemental..:

我试图实现Jason上面描述的优雅算法; 不幸的是,虽然在调试中通过数学工作,我发现很多情况下它不起作用.

例如,考虑点A(10,10)B(20,20)C(10,1)D(1,10)给出h = .5但是通过检查可以清楚地看出这些段在每个附近都没有其他.

对此进行图形化可以清楚地表明,0


我一直在拉着我的头发试图找出为什么接受的答案不适合我.非常感谢!

5> iMalc..:

这是对Gavin答案的改进.marcp的解决方案也类似,但都没有推迟该部门.

这实际上也是Gareth Rees答案的实际应用,因为2D中的交叉产品是perp-dot-product,这是该代码使用的三个.切换到3D并使用交叉积,在末尾插入s和t,得到3D中线之间的两个最接近的点.无论如何,2D解决方案:

int get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y, 
    float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
    float s02_x, s02_y, s10_x, s10_y, s32_x, s32_y, s_numer, t_numer, denom, t;
    s10_x = p1_x - p0_x;
    s10_y = p1_y - p0_y;
    s32_x = p3_x - p2_x;
    s32_y = p3_y - p2_y;

    denom = s10_x * s32_y - s32_x * s10_y;
    if (denom == 0)
        return 0; // Collinear
    bool denomPositive = denom > 0;

    s02_x = p0_x - p2_x;
    s02_y = p0_y - p2_y;
    s_numer = s10_x * s02_y - s10_y * s02_x;
    if ((s_numer < 0) == denomPositive)
        return 0; // No collision

    t_numer = s32_x * s02_y - s32_y * s02_x;
    if ((t_numer < 0) == denomPositive)
        return 0; // No collision

    if (((s_numer > denom) == denomPositive) || ((t_numer > denom) == denomPositive))
        return 0; // No collision
    // Collision detected
    t = t_numer / denom;
    if (i_x != NULL)
        *i_x = p0_x + (t * s10_x);
    if (i_y != NULL)
        *i_y = p0_y + (t * s10_y);

    return 1;
}

基本上它将分组推迟到最后一刻,并将大部分测试移动到某些计算完成之前,从而增加了早期.最后,它还避免了在线平行时发生的零除法.

您也可以考虑使用epsilon测试而不是与零进行比较.非常接近平行的线可以产生稍微偏离的结果.这不是一个错误,它是浮点数学的一个限制.


你能告诉我为什么所有这些都使用像s32_y`这样模糊的变量名而不是描述它是什么样的`point2YDifference`?
如果p0-p1是垂直的并且p2-p3是水平的并且两个段交叉,则不起作用.(第一次返回执行)

6> Martin Thoma..:
问题C:如何检测两个线段是否相交?

我搜索了相同的主题,我对答案不满意.所以我写了一篇文章,详细解释了如何检查两个线段是否与很多图像相交.有完整的(和测试的)Java代码.

这篇文章是裁剪到最重要的部分:

检查线段a是否与线段b相交的算法如下所示:

在此输入图像描述

什么是边界框?这是两个线段的两个边界框:

在此输入图像描述

如果两个边界框都有一个交点,则移动线段a使得一个点位于(0 | 0).现在你有一条线穿过a定义的原点.现在以相同的方式移动线段b并检查线段b的新点是否在线a的不同侧.如果是这种情况,请反过来检查.如果是这种情况,则线段相交.如果没有,它们就不相交了.

问题A:两个线段在哪里相交?

你知道两个线段a和b相交.如果您不知道,请使用我在"问题C"中给您的工具进行检查.

现在,您可以查看一些案例并获得7年级数学解决方案(请参阅代码和交互示例).

问题B:如何检测两条线是否相交?

比方说,你的观点A = (x1, y1),点B = (x2, y2),C = (x_3, y_3),D = (x_4, y_4).你的第一行由AB(A!= B)定义,第二行由CD定义(C!= D).

function doLinesIntersect(AB, CD) {
    if (x1 == x2) {
        return !(x3 == x4 && x1 != x3);
    } else if (x3 == x4) {
        return true;
    } else {
        // Both lines are not parallel to the y-axis
        m1 = (y1-y2)/(x1-x2);
        m2 = (y3-y4)/(x3-x4);
        return m1 != m2;
    }
}
问题D:两条线在哪里相交?

如果它们相交则检查问题B.

线a和b由每条线的两个点定义.您基本上可以应用问题A中使用的相同逻辑.


需要明确的是,这个答案中的问题B确实是两条相交的线,而不是线段.我没有抱怨; 这不是不正确的.只是不希望任何人被误导.

7> Dan..:

这里接受的答案是不正确的(从那以后一直不被接受,所以万岁!).它没有正确消除所有非交叉点.平凡地看起来它可能会起作用但它可能会失败,尤其是在0和1被认为对h有效的情况下.

考虑以下情况:

(4,1) - (5,1)和(0,0) - (0,2)处的线

这些是垂直线,显然不重叠.

A =(4,1)
B =(5,1)
C =(0,0)
D =(0,2)
E =(5,1) - (4,1)=( - 1,0)
F = (0,2) - (0,0)=(0,-2)
P =(0,1)
h =((4,1) - (0,0))dot(0,1)/((0 ,-2)dot(0,1))= 0

根据上面的答案,这两个线段在端点处相交(值为0和1).该端点将是:

(0,0)+(0,-2)*0 =(0,0)

因此,显然两个线段在(0,0)处相遇,这是在线CD上,但不在线AB上.出了什么问题?答案是0和1的值无效,只有HAPPEN才能正确预测端点交叉.当一条线(但不是另一条线)的延伸线满足线段时,算法会预测线段的交叉点,但这不正确.我想通过测试从AB对CD开始,然后再用CD对AB进行测试,这个问题就会被消除.只有当两者都介于0和1之间时才能说它们相交.

如果您必须预测终点,我建议使用矢量交叉乘积法.

-担


"已接受"的答案可能会发生变化,因此您应该将其称为其他内容.(事实上​​,我认为自你的评论以来它已经改变了)

8> Kris..:

Python版iMalc的答案:

def find_intersection( p0, p1, p2, p3 ) :

    s10_x = p1[0] - p0[0]
    s10_y = p1[1] - p0[1]
    s32_x = p3[0] - p2[0]
    s32_y = p3[1] - p2[1]

    denom = s10_x * s32_y - s32_x * s10_y

    if denom == 0 : return None # collinear

    denom_is_positive = denom > 0

    s02_x = p0[0] - p2[0]
    s02_y = p0[1] - p2[1]

    s_numer = s10_x * s02_y - s10_y * s02_x

    if (s_numer < 0) == denom_is_positive : return None # no collision

    t_numer = s32_x * s02_y - s32_y * s02_x

    if (t_numer < 0) == denom_is_positive : return None # no collision

    if (s_numer > denom) == denom_is_positive or (t_numer > denom) == denom_is_positive : return None # no collision


    # collision detected

    t = t_numer / denom

    intersection_point = [ p0[0] + (t * s10_x), p0[1] + (t * s10_y) ]


    return intersection_point



9> will.fiset..:

找到两个线段的正确交集是一个非常重要的任务,有很多边缘情况.这是一个用Java编写的经过充分记录,工作和测试的解决方案.

实质上,当找到两个线段的交集时,有三件事情可能发生:

    段不相交

    有一个独特的交叉点

    十字路口是另一个部分

注意:在代码中,我假设x1 = x2和y1 = y2的线段(x1,y1),(x2,y2)是有效的线段.从数学上讲,线段由不同的点组成,但我允许段在此实现中是完整性的点.

代码取自我的github repo

/**
 * This snippet finds the intersection of two line segments.
 * The intersection may either be empty, a single point or the
 * intersection is a subsegment there's an overlap.
 */

import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.lang.Math.min;

import java.util.ArrayList;
import java.util.List;

public class LineSegmentLineSegmentIntersection {

  // Small epsilon used for double value comparison.
  private static final double EPS = 1e-5;

  // 2D Point class.
  public static class Pt {
    double x, y;
    public Pt(double x, double y) {
      this.x = x; 
      this.y = y;
    }
    public boolean equals(Pt pt) {
      return abs(x - pt.x) < EPS && abs(y - pt.y) < EPS;
    }
  }

  // Finds the orientation of point 'c' relative to the line segment (a, b)
  // Returns  0 if all three points are collinear.
  // Returns -1 if 'c' is clockwise to segment (a, b), i.e right of line formed by the segment.
  // Returns +1 if 'c' is counter clockwise to segment (a, b), i.e left of line
  // formed by the segment.
  public static int orientation(Pt a, Pt b, Pt c) {
    double value = (b.y - a.y) * (c.x - b.x) - 
                   (b.x - a.x) * (c.y - b.y);
    if (abs(value) < EPS) return 0;
    return (value > 0) ? -1 : +1;
  }

  // Tests whether point 'c' is on the line segment (a, b).
  // Ensure first that point c is collinear to segment (a, b) and
  // then check whether c is within the rectangle formed by (a, b)
  public static boolean pointOnLine(Pt a, Pt b, Pt c) {
    return orientation(a, b, c) == 0 && 
           min(a.x, b.x) <= c.x && c.x <= max(a.x, b.x) && 
           min(a.y, b.y) <= c.y && c.y <= max(a.y, b.y);
  }

  // Determines whether two segments intersect.
  public static boolean segmentsIntersect(Pt p1, Pt p2, Pt p3, Pt p4) {

    // Get the orientation of points p3 and p4 in relation
    // to the line segment (p1, p2)
    int o1 = orientation(p1, p2, p3);
    int o2 = orientation(p1, p2, p4);
    int o3 = orientation(p3, p4, p1);
    int o4 = orientation(p3, p4, p2);

    // If the points p1, p2 are on opposite sides of the infinite
    // line formed by (p3, p4) and conversly p3, p4 are on opposite
    // sides of the infinite line formed by (p1, p2) then there is
    // an intersection.
    if (o1 != o2 && o3 != o4) return true;

    // Collinear special cases (perhaps these if checks can be simplified?)
    if (o1 == 0 && pointOnLine(p1, p2, p3)) return true;
    if (o2 == 0 && pointOnLine(p1, p2, p4)) return true;
    if (o3 == 0 && pointOnLine(p3, p4, p1)) return true;
    if (o4 == 0 && pointOnLine(p3, p4, p2)) return true;

    return false;
  }

  public static List getCommonEndpoints(Pt p1, Pt p2, Pt p3, Pt p4) {

    List points = new ArrayList<>();

    if (p1.equals(p3)) {
      points.add(p1);
      if (p2.equals(p4)) points.add(p2);

    } else if (p1.equals(p4)) {
      points.add(p1);
      if (p2.equals(p3)) points.add(p2);

    } else if (p2.equals(p3)) {
      points.add(p2);
      if (p1.equals(p4)) points.add(p1);

    } else if (p2.equals(p4)) {
      points.add(p2);
      if (p1.equals(p3)) points.add(p1);
    }

    return points;
  }

  // Finds the intersection point(s) of two line segments. Unlike regular line 
  // segments, segments which are points (x1 = x2 and y1 = y2) are allowed.
  public static Pt[] lineSegmentLineSegmentIntersection(Pt p1, Pt p2, Pt p3, Pt p4) {

    // No intersection.
    if (!segmentsIntersect(p1, p2, p3, p4)) return new Pt[]{};

    // Both segments are a single point.
    if (p1.equals(p2) && p2.equals(p3) && p3.equals(p4))
      return new Pt[]{p1};

    List endpoints = getCommonEndpoints(p1, p2, p3, p4);
    int n = endpoints.size();

    // One of the line segments is an intersecting single point.
    // NOTE: checking only n == 1 is insufficient to return early
    // because the solution might be a sub segment.
    boolean singleton = p1.equals(p2) || p3.equals(p4);
    if (n == 1 && singleton) return new Pt[]{endpoints.get(0)};

    // Segments are equal.
    if (n == 2) return new Pt[]{endpoints.get(0), endpoints.get(1)};

    boolean collinearSegments = (orientation(p1, p2, p3) == 0) && 
                                (orientation(p1, p2, p4) == 0);

    // The intersection will be a sub-segment of the two
    // segments since they overlap each other.
    if (collinearSegments) {

      // Segment #2 is enclosed in segment #1
      if (pointOnLine(p1, p2, p3) && pointOnLine(p1, p2, p4))
        return new Pt[]{p3, p4};

      // Segment #1 is enclosed in segment #2
      if (pointOnLine(p3, p4, p1) && pointOnLine(p3, p4, p2))
        return new Pt[]{p1, p2};

      // The subsegment is part of segment #1 and part of segment #2.
      // Find the middle points which correspond to this segment.
      Pt midPoint1 = pointOnLine(p1, p2, p3) ? p3 : p4;
      Pt midPoint2 = pointOnLine(p3, p4, p1) ? p1 : p2;

      // There is actually only one middle point!
      if (midPoint1.equals(midPoint2)) return new Pt[]{midPoint1};

      return new Pt[]{midPoint1, midPoint2};
    }

    /* Beyond this point there is a unique intersection point. */

    // Segment #1 is a vertical line.
    if (abs(p1.x - p2.x) < EPS) {
      double m = (p4.y - p3.y) / (p4.x - p3.x);
      double b = p3.y - m * p3.x;
      return new Pt[]{new Pt(p1.x, m * p1.x + b)};
    }

    // Segment #2 is a vertical line.
    if (abs(p3.x - p4.x) < EPS) {
      double m = (p2.y - p1.y) / (p2.x - p1.x);
      double b = p1.y - m * p1.x;
      return new Pt[]{new Pt(p3.x, m * p3.x + b)};
    }

    double m1 = (p2.y - p1.y) / (p2.x - p1.x);
    double m2 = (p4.y - p3.y) / (p4.x - p3.x);
    double b1 = p1.y - m1 * p1.x;
    double b2 = p3.y - m2 * p3.x;
    double x = (b2 - b1) / (m1 - m2);
    double y = (m1 * b2 - m2 * b1) / (m1 - m2);

    return new Pt[]{new Pt(x, y)};
  }

}

这是一个简单的用法示例:

  public static void main(String[] args) {

    // Segment #1 is (p1, p2), segment #2 is (p3, p4)
    Pt p1, p2, p3, p4;

    p1 = new Pt(-2, 4); p2 = new Pt(3, 3);
    p3 = new Pt(0, 0);  p4 = new Pt(2, 4);
    Pt[] points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point = points[0];

    // Prints: (1.636, 3.273)
    System.out.printf("(%.3f, %.3f)\n", point.x, point.y);

    p1 = new Pt(-10, 0); p2 = new Pt(+10, 0);
    p3 = new Pt(-5, 0);  p4 = new Pt(+5, 0);
    points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point1 = points[0], point2 = points[1];

    // Prints: (-5.000, 0.000) (5.000, 0.000)
    System.out.printf("(%.3f, %.3f) (%.3f, %.3f)\n", point1.x, point1.y, point2.x, point2.y);
  }



10> marcp..:

只是想提一下,在Numeric Recipes系列中可以找到一个好的解释和明确的解决方案.我已经获得了第3版,答案在第1117页,第21.4节.另一种具有不同命名法的解决方案可以在Marina Gavrilova Reliable Line Section Intersection Testing的论文中找到.在我看来,她的解决方案有点简单.

我的实现如下:

bool NuGeometry::IsBetween(const double& x0, const double& x, const double& x1){
   return (x >= x0) && (x <= x1);
}

bool NuGeometry::FindIntersection(const double& x0, const double& y0, 
     const double& x1, const double& y1,
     const double& a0, const double& b0, 
     const double& a1, const double& b1, 
     double& xy, double& ab) {
   // four endpoints are x0, y0 & x1,y1 & a0,b0 & a1,b1
   // returned values xy and ab are the fractional distance along xy and ab
   // and are only defined when the result is true

   bool partial = false;
   double denom = (b0 - b1) * (x0 - x1) - (y0 - y1) * (a0 - a1);
   if (denom == 0) {
      xy = -1;
      ab = -1;
   } else {
      xy = (a0 * (y1 - b1) + a1 * (b0 - y1) + x1 * (b1 - b0)) / denom;
      partial = NuGeometry::IsBetween(0, xy, 1);
      if (partial) {
         // no point calculating this unless xy is between 0 & 1
         ab = (y1 * (x0 - a1) + b1 * (x1 - x0) + y0 * (a1 - x1)) / denom; 
      }
   }
   if ( partial && NuGeometry::IsBetween(0, ab, 1)) {
      ab = 1-ab;
      xy = 1-xy;
      return true;
   }  else return false;
}



11> hfossli..:

C和Objective-C

根据Gareth Rees的回答

const AGKLine AGKLineZero = (AGKLine){(CGPoint){0.0, 0.0}, (CGPoint){0.0, 0.0}};

AGKLine AGKLineMake(CGPoint start, CGPoint end)
{
    return (AGKLine){start, end};
}

double AGKLineLength(AGKLine l)
{
    return CGPointLengthBetween_AGK(l.start, l.end);
}

BOOL AGKLineIntersection(AGKLine l1, AGKLine l2, CGPoint *out_pointOfIntersection)
{
    // http://stackoverflow.com/a/565282/202451

    CGPoint p = l1.start;
    CGPoint q = l2.start;
    CGPoint r = CGPointSubtract_AGK(l1.end, l1.start);
    CGPoint s = CGPointSubtract_AGK(l2.end, l2.start);

    double s_r_crossProduct = CGPointCrossProductZComponent_AGK(r, s);
    double t = CGPointCrossProductZComponent_AGK(CGPointSubtract_AGK(q, p), s) / s_r_crossProduct;
    double u = CGPointCrossProductZComponent_AGK(CGPointSubtract_AGK(q, p), r) / s_r_crossProduct;

    if(t < 0 || t > 1.0 || u < 0 || u > 1.0)
    {
        if(out_pointOfIntersection != NULL)
        {
            *out_pointOfIntersection = CGPointZero;
        }
        return NO;
    }
    else
    {
        if(out_pointOfIntersection != NULL)
        {
            CGPoint i = CGPointAdd_AGK(p, CGPointMultiply_AGK(r, t));
            *out_pointOfIntersection = i;
        }
        return YES;
    }
}

CGFloat CGPointCrossProductZComponent_AGK(CGPoint v1, CGPoint v2)
{
    return v1.x * v2.y - v1.y * v2.x;
}

CGPoint CGPointSubtract_AGK(CGPoint p1, CGPoint p2)
{
    return (CGPoint){p1.x - p2.x, p1.y - p2.y};
}

CGPoint CGPointAdd_AGK(CGPoint p1, CGPoint p2)
{
    return (CGPoint){p1.x + p2.x, p1.y + p2.y};
}

CGFloat CGPointCrossProductZComponent_AGK(CGPoint v1, CGPoint v2)
{
    return v1.x * v2.y - v1.y * v2.x;
}

CGPoint CGPointMultiply_AGK(CGPoint p1, CGFloat factor)
{
    return (CGPoint){p1.x * factor, p1.y * factor};
}

许多函数和结构都是私有的,但你应该很容易知道发生了什么.这是公开的回购https://github.com/hfossli/AGGeometryKit/



12> zstring..:

上面提供了大量的解决方案,但我认为以下解决方案非常简单易懂.

两个段Vector AB和Vector CD相交,当且仅当

    端点a和b位于段CD的相对侧.

    端点c和d位于区段AB的相对侧.

更具体地说,a和b位于区段CD的相对侧,当且仅当两个三元组a,c,d和b,c,d中的一个是逆时针顺序时.

Intersect(a, b, c, d)
 if CCW(a, c, d) == CCW(b, c, d)
    return false;
 else if CCW(a, b, c) == CCW(a, b, d)
    return false;
 else
    return true;

这里CCW表示逆时针方向,它根据点的方向返回true/false.

资料来源:http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf Page 2


我认为你应该更具体一点:如何定义`CCW`测试?随着外部产品的标志?

13> KingNestor..:

这对我很有用.取自这里.

 // calculates intersection and checks for parallel lines.  
 // also checks that the intersection point is actually on  
 // the line segment p1-p2  
 Point findIntersection(Point p1,Point p2,  
   Point p3,Point p4) {  
   float xD1,yD1,xD2,yD2,xD3,yD3;  
   float dot,deg,len1,len2;  
   float segmentLen1,segmentLen2;  
   float ua,ub,div;  

   // calculate differences  
   xD1=p2.x-p1.x;  
   xD2=p4.x-p3.x;  
   yD1=p2.y-p1.y;  
   yD2=p4.y-p3.y;  
   xD3=p1.x-p3.x;  
   yD3=p1.y-p3.y;    

   // calculate the lengths of the two lines  
   len1=sqrt(xD1*xD1+yD1*yD1);  
   len2=sqrt(xD2*xD2+yD2*yD2);  

   // calculate angle between the two lines.  
   dot=(xD1*xD2+yD1*yD2); // dot product  
   deg=dot/(len1*len2);  

   // if abs(angle)==1 then the lines are parallell,  
   // so no intersection is possible  
   if(abs(deg)==1) return null;  

   // find intersection Pt between two lines  
   Point pt=new Point(0,0);  
   div=yD2*xD1-xD2*yD1;  
   ua=(xD2*yD3-yD2*xD3)/div;  
   ub=(xD1*yD3-yD1*xD3)/div;  
   pt.x=p1.x+ua*xD1;  
   pt.y=p1.y+ua*yD1;  

   // calculate the combined length of the two segments  
   // between Pt-p1 and Pt-p2  
   xD1=pt.x-p1.x;  
   xD2=pt.x-p2.x;  
   yD1=pt.y-p1.y;  
   yD2=pt.y-p2.y;  
   segmentLen1=sqrt(xD1*xD1+yD1*yD1)+sqrt(xD2*xD2+yD2*yD2);  

   // calculate the combined length of the two segments  
   // between Pt-p3 and Pt-p4  
   xD1=pt.x-p3.x;  
   xD2=pt.x-p4.x;  
   yD1=pt.y-p3.y;  
   yD2=pt.y-p4.y;  
   segmentLen2=sqrt(xD1*xD1+yD1*yD1)+sqrt(xD2*xD2+yD2*yD2);  

   // if the lengths of both sets of segments are the same as  
   // the lenghts of the two lines the point is actually  
   // on the line segment.  

   // if the point isn’t on the line, return null  
   if(abs(len1-segmentLen1)>0.01 || abs(len2-segmentLen2)>0.01)  
     return null;  

   // return the valid intersection  
   return pt;  
 }  

 class Point{  
   float x,y;  
   Point(float x, float y){  
     this.x = x;  
     this.y = y;  
   }  

   void set(float x, float y){  
     this.x = x;  
     this.y = y;  
   }  
 }  


这段代码有几个问题.由于除零,它可以引发异常; 它很慢,因为它需要平方根; 它有时会返回误报,因为它使用了一个软糖因子.你可以比这更好!

14> 小智..:

我尝试了一些这些答案,但他们没有为我工作(抱歉的家伙); 经过一些网络搜索,我发现了这个.

通过对他的代码稍加修改,我现在有了这个函数,它将返回交叉点,或者如果没有找到交集,它将返回-1,-1.

    Public Function intercetion(ByVal ax As Integer, ByVal ay As Integer, ByVal bx As Integer, ByVal by As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal dx As Integer, ByVal dy As Integer) As Point
    '//  Determines the intersection point of the line segment defined by points A and B
    '//  with the line segment defined by points C and D.
    '//
    '//  Returns YES if the intersection point was found, and stores that point in X,Y.
    '//  Returns NO if there is no determinable intersection point, in which case X,Y will
    '//  be unmodified.

    Dim distAB, theCos, theSin, newX, ABpos As Double

    '//  Fail if either line segment is zero-length.
    If ax = bx And ay = by Or cx = dx And cy = dy Then Return New Point(-1, -1)

    '//  Fail if the segments share an end-point.
    If ax = cx And ay = cy Or bx = cx And by = cy Or ax = dx And ay = dy Or bx = dx And by = dy Then Return New Point(-1, -1)

    '//  (1) Translate the system so that point A is on the origin.
    bx -= ax
    by -= ay
    cx -= ax
    cy -= ay
    dx -= ax
    dy -= ay

    '//  Discover the length of segment A-B.
    distAB = Math.Sqrt(bx * bx + by * by)

    '//  (2) Rotate the system so that point B is on the positive X axis.
    theCos = bx / distAB
    theSin = by / distAB
    newX = cx * theCos + cy * theSin
    cy = cy * theCos - cx * theSin
    cx = newX
    newX = dx * theCos + dy * theSin
    dy = dy * theCos - dx * theSin
    dx = newX

    '//  Fail if segment C-D doesn't cross line A-B.
    If cy < 0 And dy < 0 Or cy >= 0 And dy >= 0 Then Return New Point(-1, -1)

    '//  (3) Discover the position of the intersection point along line A-B.
    ABpos = dx + (cx - dx) * dy / (dy - cy)

    '//  Fail if segment C-D crosses line A-B outside of segment A-B.
    If ABpos < 0 Or ABpos > distAB Then Return New Point(-1, -1)

    '//  (4) Apply the discovered position to line A-B in the original coordinate system.
    '*X=Ax+ABpos*theCos
    '*Y=Ay+ABpos*theSin

    '//  Success.
    Return New Point(ax + ABpos * theCos, ay + ABpos * theSin)
End Function



15> Nolo..:

似乎对Gavin的回答感兴趣,其中cortijon在评论中提出了javascript版本,而iMalc提供的版本的计算量略少.有些人指出了各种代码提案的缺点,其他人则评论了一些代码提案的效率.

iMalc通过Gavin的答案提供的算法是我目前在javascript项目中使用的算法,我只是想提供一个清理版本,如果它可以帮助任何人.

// Some variables for reuse, others may do this differently
var p0x, p1x, p2x, p3x, ix,
    p0y, p1y, p2y, p3y, iy,
    collisionDetected;

// do stuff, call other functions, set endpoints...

// note: for my purpose I use |t| < |d| as opposed to
// |t| <= |d| which is equivalent to 0 <= t < 1 rather than
// 0 <= t <= 1 as in Gavin's answer - results may vary

var lineSegmentIntersection = function(){
    var d, dx1, dx2, dx3, dy1, dy2, dy3, s, t;

    dx1 = p1x - p0x;      dy1 = p1y - p0y;
    dx2 = p3x - p2x;      dy2 = p3y - p2y;
    dx3 = p0x - p2x;      dy3 = p0y - p2y;

    collisionDetected = 0;

    d = dx1 * dy2 - dx2 * dy1;

    if(d !== 0){
        s = dx1 * dy3 - dx3 * dy1;
        if((s <= 0 && d < 0 && s >= d) || (s >= 0 && d > 0 && s <= d)){
            t = dx2 * dy3 - dx3 * dy2;
            if((t <= 0 && d < 0 && t > d) || (t >= 0 && d > 0 && t < d)){
                t = t / d;
                collisionDetected = 1;
                ix = p0x + t * dx1;
                iy = p0y + t * dy1;
            }
        }
    }
};



16> t3chb0t..:

我认为这个问题有一个更简单的解决方案.我今天提出了另一个想法,似乎工作得很好(至少现在是2D).您所要做的就是计算两条线之间的交点,然后检查计算的交点是否在两个线段的边界框内.如果是,则线段相交.而已.

编辑:

这是我计算交集的方式(我不知道在哪里找到这个代码片段)

Point3D

来自

System.Windows.Media.Media3D

public static Point3D? Intersection(Point3D start1, Point3D end1, Point3D start2, Point3D end2) {

        double a1 = end1.Y - start1.Y;
        double b1 = start1.X - end1.X;
        double c1 = a1 * start1.X + b1 * start1.Y;

        double a2 = end2.Y - start2.Y;
        double b2 = start2.X - end2.X;
        double c2 = a2 * start2.X + b2 * start2.Y;

        double det = a1 * b2 - a2 * b1;
        if (det == 0) { // lines are parallel
            return null;
        }

        double x = (b2 * c1 - b1 * c2) / det;
        double y = (a1 * c2 - a2 * c1) / det;

        return new Point3D(x, y, 0.0);
    }

这是我的(为了答案而简化)BoundingBox类:

public class BoundingBox {
    private Point3D min = new Point3D();
    private Point3D max = new Point3D();

    public BoundingBox(Point3D point) {
        min = point;
        max = point;
    }

    public Point3D Min {
        get { return min; }
        set { min = value; }
    }

    public Point3D Max {
        get { return max; }
        set { max = value; }
    }

    public bool Contains(BoundingBox box) {
        bool contains =
            min.X <= box.min.X && max.X >= box.max.X &&
            min.Y <= box.min.Y && max.Y >= box.max.Y &&
            min.Z <= box.min.Z && max.Z >= box.max.Z;
        return contains;
    }

    public bool Contains(Point3D point) {
        return Contains(new BoundingBox(point));
    }

}

推荐阅读
落单鸟人
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有