如何检测两条线段相交的位置?[关闭]


518

如何确定两条线在x,y点处是否相交?


将矩形的边缘视为单独的线而不是完整的多边形可能会有所帮助。
Ryan Graham

主持人说明:有关此主题是否为主题的讨论属于Meta Stack Overflow,有关此内容的更多评论将被删除。
马丁·彼得斯

Answers:


659

解决此问题的一种好方法是使用矢量叉积。将二维矢量叉积v  ×  w定义v x  w y  -  v y  w x

假设两个线段分别从pp  +  r和从qq  +  s。那么,第一行上的任何点都可以表示为p  +  t  r(对于标量参数  t),第二行上的任何点都可以表示为q  +  u  s(对于标量参数  u)。

Two line segments intersecting

如果我们能够找到tu,那么两条线相交:

p + t  r = q + u  s

Formulae for the point of intersection

s与两边交叉,得到

p + t  r)× s =(q + u  s)× s

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

t  (r × s)=(q - p)× s

因此,求解t

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

同样,我们可以解决u

p + t  r)× r =(q + u  s)× r

u  (s × r)=(pq)× r

u =(pq)× r /(s × r

为了减少计算步骤,可以方便地将其重写如下(记住s  ×  r = −  r  ×  s):

u =(qp)× r /(r × s

现在有四种情况:

  1. 如果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 1t 0 ]而不是[ t 0t 1 ]。

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

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

  4. 否则,两条线段不平行但不相交。

值得一提的是:此方法是Ronald Goldman在“ Graphics Gems ”第304页上发表的文章“三空间中的两条线的交点”中3D线交点算法的二维专业化。在三个维度上,通常的情况是线是倾斜的(既不平行也不相交),在这种情况下,该方法给出了两条线的最接近点。


5
@myrkos:否。第一行段是“从p到p + r”,因此当用参数项将其表示为“ p + tr”时,该段对应于0≤t≤1。
加雷斯·里斯

7
Gareth,我觉得我一定很想念某些东西,但是如何将一个向量除以一个向量呢?您对tu的解以结尾/ (r × s),但是(r × s)是向量,对吗?一个向量(0, 0, rx * sy - ry * sx)。并且左侧类似地是平行于z轴的向量。那么...我是否将z分量除以其他z分量?t的公式实际上是|(q − p) × s| / |(r × s)|吗?
LarsH

7
@LarsH:请参见第一段。
加雷斯·里斯

35
对于那些感兴趣的人,这是一个简单的C#实现,采用PointF起点和终点的直线坐标,似乎可行
Matt

24
我在@Matt之后放了一个JavaScript实现。我对Tekito指出的错误进行了更正。
pgkelley,

230

FWIW,以下函数(在C中)均检测线相交并确定相交点。它基于Andre LeMothe的“ Windows游戏编程大师技巧 ”中的算法。它与其他答案中的某些算法(例如Gareth的)没有什么不同。然后,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

那使我困惑了几个小时。:(


9
函数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);
cortijon 2012年

5
if(s> = 0 && s <= 1 && t> = 0 && t <= 1){//检测到碰撞var intX = p0_x +(t * s1_x); var intY = p0_y +(t * s1_y); 返回[intX,intY]; }返回null;//没有碰撞}
cortijon 2012年

13
好的算法,但是fyi不能处理行列式为0的情况。(上面的-s2_x * s1_y + s1_x * s2_y)。如果为0(或接近0),则线是平行或共线的。如果是共线的,则交点可能是另一个线段。
2013年

16
可以避免两次除法运算以提高速度(除法运算的成本高于乘法运算);如果线相交,则需要一格;如果线不相交,则需要零。先要计算分母和早期停止,如果它是零(可能添加代码以检测共线性。)接着,代替计算st直接,测试两个分子和分母之间的关系。仅当确认线相交时,您才真正需要计算的值t(而不是s)。
Qwertie

18
我对此处发布的所有算法进行了性能测试,而该算法至少是其他算法的两倍。感谢您的发布!
lajos

63

问题减少到这个问题:从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数字是关键。如果h介于0和之间1,则线相交,否则不相交。如果F*P为零,则当然无法进行计算,但是在这种情况下,线是平行的,因此仅在明显的情况下相交。

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

更多乐趣:

如果h正好 01线在端点触摸。您可以认为这不是一个“交叉口”。

具体来说,h您需要乘多少行的长度才能准确触摸另一条线。

因此,如果为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

29
这个算法很好。但是Dan指出@@ stackoverflow.com / questions / 563198 /… 和Elemental @ stackoverflow.com / questions /563198/…上有一个漏洞,如果您更新答案以供将来参考,那将是很不错的。谢谢。
钱兹

2
这个算法在数值上稳定吗?我尝试了类似的方法,结果发现在使用浮漂时会产生奇怪的结果。
milosz

3
这种算法似乎还有另一个问题。喂入点A = {1,0} B = {2,0} C = {0,0} D = {1,0},尽管这些线段清楚地触碰到了末端F P(还有E Q,与下面的用户一致)均为0,从而导致被0除以找到h和g。仍在为这个解决方案进行研究,但是我认为这个问题值得指出。
candrews

12
这个答案是完全错误的。尝试A = {0,0},B = {0,1},C = {0,2} D = {2,0}
Tim Cooper

6
A + E*g = C + F*h这两条线相交当且仅当该溶液到方程(假设它们是不平行的)具有两个,g并且h 0和1之间(输入或排他性的,这取决于是否在计数结束点接触)。
Daniel Fischer

46

我已经尝试实现上述Jason所描述的算法。不幸的是,在调试过程中使用数学方法时,我发现很多情况下它不起作用。

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

通过图形可以清楚地看出,0 <h <1标准仅表明截点存在于CD上,而没有告诉该截点是否位于AB上。为了确保有一个交叉点,您必须对变量g进行对称计算,并且拦截的要求是:0 <g <1 AND 0 <h <1


2
我一直在努力寻找为什么接受的答案对我不起作用的原因。非常感谢!
马特·布里奇斯

1
同样值得注意的是,边界条件在这种情况下有效(例如,对于h = 0或h = 1或g = 0或g = 1,线“只是”接触
元素

对于无法可视化结果的人员,我使用Javascript实现了此实现:jsfiddle.net/ferrybig/eokwL9mp
Ferrybig,

45

这是对加文答案的改进。marcp的解决方案也类似,但是都没有推迟分裂。

实际上,这实际上也是Gareth Rees的答案的实际应用,因为2D中叉积的等效物是perp-dot-product,这是此代码使用的三个乘积。切换到3D并使用叉积,最后对s和t进行插值,将导致3D中线之间的两个最接近的点。无论如何,二维解决方案:

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测试,而不是与零进行比较。极接近平行的线会产生稍微偏离的结果。这不是错误,而是浮点数学的局限性。


1
如果某些点的值为0,则失败,那不应该发生吗?
hfossli 2013年

1
我已更正了延迟分隔时引入的错误。当数字和单位均为负时,t可能为正。
iMalc

2
如果p0-p1是垂直的而p2-p3是水平的且两个线段交叉则不起作用。(已执行首次返回)
法比奥·达拉·利比拉

coolinear机箱有两个可能性:不重叠和重叠。第一个应该返回false,第二个应该返回true。在您的代码中,未经测试。它总是返回false,因为大多数答案在这里。遗憾的是,没有解决方案真正起作用。
AlexWien 2014年

3
您能启发我为什么所有这些方法都使用诸如这样的模糊变量名s32_y而不是描述变量名的东西point2YDifference吗?
Supuhstar

40

问题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中使用的相同逻辑。


15
需要明确的是,此答​​案中的问题B实际上是关于两条相交的线,而不是线段。我没有抱怨; 这是不正确的。只是不想让任何人被误导。
13年

1
没有“问题C”。并质疑d只反弹到问题A.
康拉德Viltersten

21

一旦被接受,答案是错误的(此后一直未被接受,万岁!)。它不能正确消除所有非交叉点。琐碎的看似可行,但可能会失败,特别是在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))点(0,1)/((0 ,-2)点(0,1))= 0

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

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

因此,显然这两个线段在CD上但不在AB线上的(0,0)相遇。那么怎么了?答案是0和1的值无效,只是有时会发生HAPPEN以正确预测端点交点。当一条线(而不是另一条线)的延伸满足线段时,该算法会预测线段的交点,但这是不正确的。我想象通过从AB vs CD开始测试,然后再以CD vs AB测试,可以消除此问题。只有两者都在0和1之间(包括0和1)时,才可以说它们相交。

如果必须预测终点,建议使用矢量叉积法。

-担


4
“已接受”的答案可以更改,因此您应该使用其他名称。(实际上,我认为自您发表评论以来,情况已经发生了变化)
Johannes Hoff

14

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

请记住,您需要让数字浮动或更改第8行以使用denom = float(...)
Jonno_FTW

11

找到两个线段的正确交点是一项不平凡的任务,其中包含很多边缘情况。这是一个使用Java记录良好,可运行且经过测试的解决方案。

本质上,当找到两个线段的交点时,可能会发生三件事:

  1. 线段不相交

  2. 有一个独特的交点

  3. 路口是另一个路段

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

代码来自我的github仓库

/**
 * 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<Pt> getCommonEndpoints(Pt p1, Pt p2, Pt p3, Pt p4) {

    List<Pt> 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<Pt> 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);
  }

它适用于我的地理坐标系!谢谢!但这是用于无限线相交,而我更多是在寻找有限线相交。
M. Usman Khan

8

只是想提一下,在数字食谱系列中可以找到很好的解释和明确的解决方案。我已经有了第三版,答案在第1117页的21.4节。Marina Gavrilova 可靠线截面相交测试可以在论文中找到另一种具有不同命名法的解决方案。在我看来,她的解决方案要简单一些。

我的实现如下:

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;
}

对于p1 =(0,0),p2 =(10,0),p3 =(9,0),p4 =(20,0)
不起作用

我猜这取决于您对“不起作用”的定义。Denom为0,因此它将返回false,这对我来说似乎正确,因为它们不相交。共线与相交不同。
marcp

8

上面有很多解决方案,但是我认为下面的解决方案非常简单易懂。

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

  1. 端点a和b在段CD的相对侧。
  2. 端点c和d在线段AB的相对侧。

更具体地说,当且仅当两个三元组a,c,d和b,c,d中的一个恰好是逆时针方向时,a和b才位于段CD的相对侧。

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 第2页


2
我认为您应该更具体一点:如何CCW定义测试?带有外部产品的标志?
ocramz

谢谢; 该伪代码允许在Scratch中非常简单地实现;看到这个项目:scratch.mit.edu/projects/129319027
Ruud

8

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/


此代码中的AGPointZero来自哪里?
seanicus 2014年

1
@seanicus更新了示例,改为使用CGPoint
hfossli 2014年

6

这对我来说很好。取自这里

 // 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;  
   }  
 }  

8
此代码存在几个问题。由于被零除,可能会引发异常。这很慢,因为它扎根了;有时会返回误报,因为它使用了软键。您可以做得更好!
加雷斯·里斯

可以作为解决方案,但是Jason给出的解决方案在计算上绝对更快,并且避免了此解决方案的许多问题
Elemental

6

我尝试了其中的一些答案,但是它们对我没用(对不起);经过更多的网上搜索后,我发现了这一点

对他的代码稍作修改,我现在有了此函数,它将返回交点,或者如果找不到交点,它将返回-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

6

加文(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;
            }
        }
    }
};

我不明白您怎么能理解像t = dx2 * dy3 - dx3 * dy2;... 这样的行是怎么回事
Supuhstar

@Supuhstar它与向量数学以及点积和叉积的定义有关。例如,您发布的代码表示跨产品操作。这是一种将一条线段投影到另一条线段上,以确定它在另一条线段上的位置的方法,该线段位于起始点在中间的某处或该行之后。所以t是归一化值。如果介于0和1之间,则两个线段相交。如果小于0或大于1,则不这样做。
Nolo

@Supuhstar另外请注意,为了使投影找到实际点,必须缩放结果。那就t/d来了
。– Nolo

1
我的意思是,您如何一眼就知道像这样的变量名发生了什么?为什么不喜欢crossProduct = (line1XDifference * line2YDifference) - (line2XDifference * line1YDifference)scaledResult = crossProduct / dotProduct
Supuhstar

1
@Supuhstar啊,我明白你的意思了。嗯,嗯,我想确实没有充分的理由要讲究超越效率的问题,但这本身并不是一个很好的理由,因为编译器可以很好地完成您提供给他们的所有代码并使之高效而不更改应计算的内容的可能性。在另一方面,姓名p1x, p1y等旨在通过他们的X和Y值来描述点,所以p1x是一个缩写point1x,同样d1x,在我心中是希腊字母的缩写deltaX,或者你可以说differenceInX。(更多)
Nolo

5

我认为对于这个问题有一个简单得多的解决方案。我今天想出了另一个想法,它似乎很好用(至少目前在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));
    }

}

3

此解决方案可能会有所帮助

public static float GetLineYIntesept(PointF p, float slope)
    {
        return p.Y - slope * p.X;
    }

    public static PointF FindIntersection(PointF line1Start, PointF line1End, PointF line2Start, PointF line2End)
    {

        float slope1 = (line1End.Y - line1Start.Y) / (line1End.X - line1Start.X);
        float slope2 = (line2End.Y - line2Start.Y) / (line2End.X - line2Start.X);

        float yinter1 = GetLineYIntesept(line1Start, slope1);
        float yinter2 = GetLineYIntesept(line2Start, slope2);

        if (slope1 == slope2 && yinter1 != yinter2)
            return PointF.Empty;

        float x = (yinter2 - yinter1) / (slope1 - slope2);

        float y = slope1 * x + yinter1;

        return new PointF(x, y);
    }

3

我将Kris的上述答案移植到了JavaScript中。在尝试了许多不同的答案之后,他提供了正确的观点。我以为我没有得到我所需要的要点而发疯。

function getLineLineCollision(p0, p1, p2, p3) {
    var s1, s2;
    s1 = {x: p1.x - p0.x, y: p1.y - p0.y};
    s2 = {x: p3.x - p2.x, y: p3.y - p2.y};

    var s10_x = p1.x - p0.x;
    var s10_y = p1.y - p0.y;
    var s32_x = p3.x - p2.x;
    var s32_y = p3.y - p2.y;

    var denom = s10_x * s32_y - s32_x * s10_y;

    if(denom == 0) {
        return false;
    }

    var denom_positive = denom > 0;

    var s02_x = p0.x - p2.x;
    var s02_y = p0.y - p2.y;

    var s_numer = s10_x * s02_y - s10_y * s02_x;

    if((s_numer < 0) == denom_positive) {
        return false;
    }

    var t_numer = s32_x * s02_y - s32_y * s02_x;

    if((t_numer < 0) == denom_positive) {
        return false;
    }

    if((s_numer > denom) == denom_positive || (t_numer > denom) == denom_positive) {
        return false;
    }

    var t = t_numer / denom;

    var p = {x: p0.x + (t * s10_x), y: p0.y + (t * s10_y)};
    return p;
}

2

我尝试了很多方法,然后决定编写自己的方法。所以这里是:

bool IsBetween (float x, float b1, float b2)
{
   return ( ((x >= (b1 - 0.1f)) && 
        (x <= (b2 + 0.1f))) || 
        ((x >= (b2 - 0.1f)) &&
        (x <= (b1 + 0.1f))));
}

bool IsSegmentsColliding(   POINTFLOAT lineA,
                POINTFLOAT lineB,
                POINTFLOAT line2A,
                POINTFLOAT line2B)
{
    float deltaX1 = lineB.x - lineA.x;
    float deltaX2 = line2B.x - line2A.x;
    float deltaY1 = lineB.y - lineA.y;
    float deltaY2 = line2B.y - line2A.y;

    if (abs(deltaX1) < 0.01f && 
        abs(deltaX2) < 0.01f) // Both are vertical lines
        return false;
    if (abs((deltaY1 / deltaX1) -
        (deltaY2 / deltaX2)) < 0.001f) // Two parallel line
        return false;

    float xCol = (  (   (deltaX1 * deltaX2) * 
                        (line2A.y - lineA.y)) - 
                    (line2A.x * deltaY2 * deltaX1) + 
                    (lineA.x * deltaY1 * deltaX2)) / 
                 ((deltaY1 * deltaX2) - (deltaY2 * deltaX1));
    float yCol = 0;
    if (deltaX1 < 0.01f) // L1 is a vertical line
        yCol = ((xCol * deltaY2) + 
                (line2A.y * deltaX2) - 
                (line2A.x * deltaY2)) / deltaX2;
    else // L1 is acceptable
        yCol = ((xCol * deltaY1) +
                (lineA.y * deltaX1) -
                (lineA.x * deltaY1)) / deltaX1;

    bool isCol =    IsBetween(xCol, lineA.x, lineB.x) &&
            IsBetween(yCol, lineA.y, lineB.y) &&
            IsBetween(xCol, line2A.x, line2B.x) &&
            IsBetween(yCol, line2A.y, line2B.y);
    return isCol;
}

基于以下两个公式:(我从线方程和其他公式简化了它们)

x的公式

y的公式


可以,但是尝试输入此坐标(如果它是共线/重叠的,则它将返回错误结果):PointA1 =(0,0)PointA2 =(0,2)和PointB1 =(0,1)PointB2 =(0,5)
dns

@dns好吧,这是因为代码对并行行返回false。我看到了问题,但是我仍然不知道该函数应该返回什么,因为有无数个答案。
Soroush Falahati

2

这基于Gareth Ree的答案。如果确实存在,也将返回线段的重叠。V用C ++编码,是一个简单的向量类。2D中两个向量的叉积返回单个标量。它由我的学校自动测试系统测试并通过。

//Required input point must be colinear with the line
bool on_segment(const V& p, const LineSegment& l)
{
    //If a point is on the line, the sum of the vectors formed by the point to the line endpoints must be equal
    V va = p - l.pa;
    V vb = p - l.pb;
    R ma = va.magnitude();
    R mb = vb.magnitude();
    R ml = (l.pb - l.pa).magnitude();
    R s = ma + mb;
    bool r = s <= ml + epsilon;
    return r;
}

//Compute using vector math
// Returns 0 points if the lines do not intersect or overlap
// Returns 1 point if the lines intersect
//  Returns 2 points if the lines overlap, contain the points where overlapping start starts and stop
std::vector<V> intersect(const LineSegment& la, const LineSegment& lb)
{
    std::vector<V> r;

    //http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
    V oa, ob, da, db; //Origin and direction vectors
    R sa, sb; //Scalar values
    oa = la.pa;
    da = la.pb - la.pa;
    ob = lb.pa;
    db = lb.pb - lb.pa;

    if (da.cross(db) == 0 && (ob - oa).cross(da) == 0) //If colinear
    {
        if (on_segment(lb.pa, la) && on_segment(lb.pb, la))
        {
            r.push_back(lb.pa);
            r.push_back(lb.pb);
            dprintf("colinear, overlapping\n");
            return r;
        }

        if (on_segment(la.pa, lb) && on_segment(la.pb, lb))
        {
            r.push_back(la.pa);
            r.push_back(la.pb);
            dprintf("colinear, overlapping\n");
            return r;
        }

        if (on_segment(la.pa, lb))
            r.push_back(la.pa);

        if (on_segment(la.pb, lb))
            r.push_back(la.pb);

        if (on_segment(lb.pa, la))
            r.push_back(lb.pa);

        if (on_segment(lb.pb, la))
            r.push_back(lb.pb);

        if (r.size() == 0)
            dprintf("colinear, non-overlapping\n");
        else
            dprintf("colinear, overlapping\n");

        return r;
    }

    if (da.cross(db) == 0 && (ob - oa).cross(da) != 0)
    {
        dprintf("parallel non-intersecting\n");
        return r;
    }

    //Math trick db cross db == 0, which is a single scalar in 2D.
    //Crossing both sides with vector db gives:
    sa = (ob - oa).cross(db) / da.cross(db);

    //Crossing both sides with vector da gives
    sb = (oa - ob).cross(da) / db.cross(da);

    if (0 <= sa && sa <= 1 && 0 <= sb && sb <= 1)
    {
        dprintf("intersecting\n");
        r.push_back(oa + da * sa);
        return r;
    }

    dprintf("non-intersecting, non-parallel, non-colinear, non-overlapping\n");
    return r;
}

2

这是C#中线段的基本实现,并带有相应的交点检测代码。它需要一个称为的2D向量/点结构Vector2f,尽管您可以将其替换为具有X / Y属性的任何其他类型。您也可以替换floatdouble是否适合您的需要更好。

该代码在我的.NET物理库Boing中使用

public struct LineSegment2f
{
    public Vector2f From { get; }
    public Vector2f To { get; }

    public LineSegment2f(Vector2f @from, Vector2f to)
    {
        From = @from;
        To = to;
    }

    public Vector2f Delta => new Vector2f(To.X - From.X, To.Y - From.Y);

    /// <summary>
    /// Attempt to intersect two line segments.
    /// </summary>
    /// <remarks>
    /// Even if the line segments do not intersect, <paramref name="t"/> and <paramref name="u"/> will be set.
    /// If the lines are parallel, <paramref name="t"/> and <paramref name="u"/> are set to <see cref="float.NaN"/>.
    /// </remarks>
    /// <param name="other">The line to attempt intersection of this line with.</param>
    /// <param name="intersectionPoint">The point of intersection if within the line segments, or empty..</param>
    /// <param name="t">The distance along this line at which intersection would occur, or NaN if lines are collinear/parallel.</param>
    /// <param name="u">The distance along the other line at which intersection would occur, or NaN if lines are collinear/parallel.</param>
    /// <returns><c>true</c> if the line segments intersect, otherwise <c>false</c>.</returns>
    public bool TryIntersect(LineSegment2f other, out Vector2f intersectionPoint, out float t, out float u)
    {
        var p = From;
        var q = other.From;
        var r = Delta;
        var s = other.Delta;

        // t = (q − p) × s / (r × s)
        // u = (q − p) × r / (r × s)

        var denom = Fake2DCross(r, s);

        if (denom == 0)
        {
            // lines are collinear or parallel
            t = float.NaN;
            u = float.NaN;
            intersectionPoint = default(Vector2f);
            return false;
        }

        var tNumer = Fake2DCross(q - p, s);
        var uNumer = Fake2DCross(q - p, r);

        t = tNumer / denom;
        u = uNumer / denom;

        if (t < 0 || t > 1 || u < 0 || u > 1)
        {
            // line segments do not intersect within their ranges
            intersectionPoint = default(Vector2f);
            return false;
        }

        intersectionPoint = p + r * t;
        return true;
    }

    private static float Fake2DCross(Vector2f a, Vector2f b)
    {
        return a.X * b.Y - a.Y * b.X;
    }
}

1

一个C ++程序,用于检查两个给定的线段是否相交

#include <iostream>
using namespace std;

struct Point
{
    int x;
    int y;
};

// Given three colinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
bool onSegment(Point p, Point q, Point r)
{
    if (q.x <= max(p.x, r.x) && q.x >= min(p.x, r.x) &&
        q.y <= max(p.y, r.y) && q.y >= min(p.y, r.y))
       return true;

    return false;
}

// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are colinear
// 1 --> Clockwise
// 2 --> Counterclockwise
int orientation(Point p, Point q, Point r)
{
    // See 10th slides from following link for derivation of the formula
    // http://www.dcs.gla.ac.uk/~pat/52233/slides/Geometry1x1.pdf
    int val = (q.y - p.y) * (r.x - q.x) -
              (q.x - p.x) * (r.y - q.y);

    if (val == 0) return 0;  // colinear

    return (val > 0)? 1: 2; // clock or counterclock wise
}

// The main function that returns true if line segment 'p1q1'
// and 'p2q2' intersect.
bool doIntersect(Point p1, Point q1, Point p2, Point q2)
{
    // Find the four orientations needed for general and
    // special cases
    int o1 = orientation(p1, q1, p2);
    int o2 = orientation(p1, q1, q2);
    int o3 = orientation(p2, q2, p1);
    int o4 = orientation(p2, q2, q1);

    // General case
    if (o1 != o2 && o3 != o4)
        return true;

    // Special Cases
    // p1, q1 and p2 are colinear and p2 lies on segment p1q1
    if (o1 == 0 && onSegment(p1, p2, q1)) return true;

    // p1, q1 and p2 are colinear and q2 lies on segment p1q1
    if (o2 == 0 && onSegment(p1, q2, q1)) return true;

    // p2, q2 and p1 are colinear and p1 lies on segment p2q2
    if (o3 == 0 && onSegment(p2, p1, q2)) return true;

     // p2, q2 and q1 are colinear and q1 lies on segment p2q2
    if (o4 == 0 && onSegment(p2, q1, q2)) return true;

    return false; // Doesn't fall in any of the above cases
}

// Driver program to test above functions
int main()
{
    struct Point p1 = {1, 1}, q1 = {10, 1};
    struct Point p2 = {1, 2}, q2 = {10, 2};

    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    p1 = {10, 0}, q1 = {0, 10};
    p2 = {0, 0}, q2 = {10, 10};
    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    p1 = {-5, -5}, q1 = {0, 0};
    p2 = {1, 1}, q2 = {10, 10};
    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    return 0;
}

1

基于@Gareth Rees答案,适用于Python版本:

import numpy as np

def np_perp( a ) :
    b = np.empty_like(a)
    b[0] = a[1]
    b[1] = -a[0]
    return b

def np_cross_product(a, b):
    return np.dot(a, np_perp(b))

def np_seg_intersect(a, b, considerCollinearOverlapAsIntersect = False):
    # /programming/563198/how-do-you-detect-where-two-line-segments-intersect/565282#565282
    # http://www.codeproject.com/Tips/862988/Find-the-intersection-point-of-two-line-segments
    r = a[1] - a[0]
    s = b[1] - b[0]
    v = b[0] - a[0]
    num = np_cross_product(v, r)
    denom = np_cross_product(r, s)
    # If r x s = 0 and (q - p) x r = 0, then the two lines are collinear.
    if np.isclose(denom, 0) and np.isclose(num, 0):
        # 1. If either  0 <= (q - p) * r <= r * r or 0 <= (p - q) * s <= * s
        # then the two lines are overlapping,
        if(considerCollinearOverlapAsIntersect):
            vDotR = np.dot(v, r)
            aDotS = np.dot(-v, s)
            if (0 <= vDotR  and vDotR <= np.dot(r,r)) or (0 <= aDotS  and aDotS <= np.dot(s,s)):
                return True
        # 2. If neither 0 <= (q - p) * r = r * r nor 0 <= (p - q) * s <= s * s
        # then the two lines are collinear but disjoint.
        # No need to implement this expression, as it follows from the expression above.
        return None
    if np.isclose(denom, 0) and not np.isclose(num, 0):
        # Parallel and non intersecting
        return None
    u = num / denom
    t = np_cross_product(v, s) / denom
    if u >= 0 and u <= 1 and t >= 0 and t <= 1:
        res = b[0] + (s*u)
        return res
    # Otherwise, the two line segments are not parallel but do not intersect.
    return None

0

如果矩形的每一边都是一条线段,而用户绘制的部分是一条线段,那么您只需要检查用户绘制的段是否与四个边线段相交即可。给定每个段的起点和终点,这应该是一个相当简单的练习。


3
请注意,这是对最初设计的问题的合理回答,但现在问题已被大量编辑,因此没有太大意义。
GS-向Monica道歉,2014年

0

根据t3chb0t的答案:

int intersezione_linee(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int& p_x, int& p_y)
{
   //L1: estremi (x1,y1)(x2,y2) L2: estremi (x3,y3)(x3,y3)
   int d;
   d = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4);
   if(!d)
       return 0;
   p_x = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4))/d;
   p_y = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4))/d;
   return 1;
}

int in_bounding_box(int x1, int y1, int x2, int y2, int p_x, int p_y)
{
    return p_x>=x1 && p_x<=x2 && p_y>=y1 && p_y<=y2;

}

int intersezione_segmenti(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int& p_x, int& p_y)
{
    if (!intersezione_linee(x1,y1,x2,y2,x3,y3,x4,y4,p_x,p_y))
        return 0;

    return in_bounding_box(x1,y1,x2,y2,p_x,p_y) && in_bounding_box(x3,y3,x4,y4,p_x,p_y);
}

0

我从《多视图几何》一书中阅读了这些算法

以下文字使用

'作为转置符号

*作为点积

x作为叉积,用作运算符时

1.行定义

点x_vec =(x,y)'位于ax + by + c = 0线上

我们将L =(a,b,c)'表示为点,将点表示为(x,y,1)'为齐次坐标

线方程可以写成

(x,y,1)(a,b,c)'= 0或x'* L = 0

2.线的交点

我们有两条线L1 =(a1,b1,c1)',L2 =(a2,b2,c2)'

假设x是一个点,一个向量,并且x = L1 x L2(L1叉积L2)。

请注意,x始终是2D点,如果您对(L1xL2)是三个元素的向量感到困惑,请阅读齐次坐标,而x是2D坐标。

根据三乘积,我们知道

由于L1,L2共面,因此L1 *(L1 x L2)= 0和L2 *(L1 x L2)= 0

我们用向量x代替(L1xL2),那么我们有L1 * x = 0,L2 * x = 0,这意味着x既位于L1上,又位于L2上,x是交点。

注意,这里的x是齐次坐标,如果x的最后一个元素为零,则表示L1和L2是平行的。


0

许多答案已将所有计算汇总为一个函数。如果您需要计算线斜率,y轴截距或x轴截距以在代码的其他地方使用,则将多余地进行这些计算。我已经分离出各自的功能,使用了明显的变量名,并注释了我的代码以使其易于理解。我需要知道线条是否在端点之外无限相交,所以在JavaScript中:

http://jsfiddle.net/skibulk/evmqq00u/

var point_a = {x:0, y:10},
    point_b = {x:12, y:12},
    point_c = {x:10, y:0},
    point_d = {x:0, y:0},
    slope_ab = slope(point_a, point_b),
    slope_bc = slope(point_b, point_c),
    slope_cd = slope(point_c, point_d),
    slope_da = slope(point_d, point_a),
    yint_ab = y_intercept(point_a, slope_ab),
    yint_bc = y_intercept(point_b, slope_bc),
    yint_cd = y_intercept(point_c, slope_cd),
    yint_da = y_intercept(point_d, slope_da),
    xint_ab = x_intercept(point_a, slope_ab, yint_ab),
    xint_bc = x_intercept(point_b, slope_bc, yint_bc),
    xint_cd = x_intercept(point_c, slope_cd, yint_cd),
    xint_da = x_intercept(point_d, slope_da, yint_da),
    point_aa = intersect(slope_da, yint_da, xint_da, slope_ab, yint_ab, xint_ab),
    point_bb = intersect(slope_ab, yint_ab, xint_ab, slope_bc, yint_bc, xint_bc),
    point_cc = intersect(slope_bc, yint_bc, xint_bc, slope_cd, yint_cd, xint_cd),
    point_dd = intersect(slope_cd, yint_cd, xint_cd, slope_da, yint_da, xint_da);

console.log(point_a, point_b, point_c, point_d);
console.log(slope_ab, slope_bc, slope_cd, slope_da);
console.log(yint_ab, yint_bc, yint_cd, yint_da);
console.log(xint_ab, xint_bc, xint_cd, xint_da);
console.log(point_aa, point_bb, point_cc, point_dd);

function slope(point_a, point_b) {
  var i = (point_b.y - point_a.y) / (point_b.x - point_a.x);
  if (i === -Infinity) return Infinity;
  if (i === -0) return 0;
  return i;
}

function y_intercept(point, slope) {
    // Horizontal Line
    if (slope == 0) return point.y;
  // Vertical Line
    if (slope == Infinity)
  {
    // THE Y-Axis
    if (point.x == 0) return Infinity;
    // No Intercept
    return null;
  }
  // Angled Line
  return point.y - (slope * point.x);
}

function x_intercept(point, slope, yint) {
    // Vertical Line
    if (slope == Infinity) return point.x;
  // Horizontal Line
    if (slope == 0)
  {
    // THE X-Axis
    if (point.y == 0) return Infinity;
    // No Intercept
    return null;
  }
  // Angled Line
  return -yint / slope;
}

// Intersection of two infinite lines
function intersect(slope_a, yint_a, xint_a, slope_b, yint_b, xint_b) {
  if (slope_a == slope_b)
  {
    // Equal Lines
    if (yint_a == yint_b && xint_a == xint_b) return Infinity;
    // Parallel Lines
    return null;
  }
  // First Line Vertical
    if (slope_a == Infinity)
  {
    return {
        x: xint_a,
      y: (slope_b * xint_a) + yint_b
    };
  }
  // Second Line Vertical
    if (slope_b == Infinity)
  {
    return {
        x: xint_b,
      y: (slope_a * xint_b) + yint_a
    };
  }
  // Not Equal, Not Parallel, Not Vertical
  var i = (yint_b - yint_a) / (slope_a - slope_b);
  return {
    x: i,
    y: (slope_a * i) + yint_a
  };
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.