geometry - 你怎么检测中两条直线段相交?

  显示原文与译文双语对照的内容

我怎么确定是否两条线相交,如果他们这样做,在x,y点?

时间:

这个问题有一个很好的方法,使用向量交叉产品。 定义 2 -dimensional向量叉积 v × wvx英镑yy英镑x ( 这是 3 -dimensional交叉产品的大小) 。

假设两个线段从 pp + R + 。 然后任意点在第一行可表示的是 p + t R ( 一个标量参数 ) 和任意点在第二行 + u 年代 ( 对于一个标量参数 u ) 。

Two line segments intersecting

两条线相交,如果我们能找到 t u 这样:

p + t R = + u

Formulae for the point of intersection

交叉双方.

( p + t R年代 = ( + u ) ×

由于年代×年代 = 0,这意味着

因此,解决 t :

同样的道理,我们可以解决的问题:

( p + t RR = ( + u ) × R

现在有四种情况:

  1. 在这种情况下,表达第二段的端点( + )的第一行段( p + t R ) 方程:

    0

    10 + · R/( R · R )

    如果之间的间隔 0 and 1 相交于间隔 [0, 1 ],线段是共线和重叠的;否则它们是共线和不相交的。

    注意,如果和 R 点相反的方向,然后年代 · R <0所以被检查的时间间隔是[ t 1 ,, 0 ] 而不是[ 0 ,, 1 ] 。

  2. 如果 R ×年代 ≠ 0和 0 ≤ t ≤ 1和 0 ≤ u ≤ 1,两个线段满足点 p + t R = + u 。

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

信贷:这个方法是 2 -dimensional专业化 3d线交点的算法从文章"three-space中两行的交集"罗纳德•高盛发表在图形宝石 304页。 在三个维度中,通常的情况是直线是倾斜的,在这种情况下,方法给出了两条直线的最近接近点。

FWIW,以下函数( 在C 中) 都检测直线交点并确定交点。 它基于 Andre lemothe的一个算法。 它与算法中其他一些问题的答案不同( 例如。 然后,'s ) 。LeMothe使用 Cramer ( 不询问我) 规则来求解方程本身。

我可以证明它在微弱的小行星克隆,而且似乎正确处理其他答案元素中描述的边界情况,丹和 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

这让我困惑小时 。 : (

问题减少到了这个问题: 从A 到B 和从C 到D 相交? 然后你可以问它四次( 矩形的四条边和每一个边之间) 。

这是一个向量数学来做它。 我假设从A 到B的线条是问题线,从C 到D的直线是矩形线之一。 我的符号是 Ax 是"x-coordinate的",Cy 是"y-coordinate的。"和" *"意味着 dot-product,所以 比如 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 位于 01 之间,则这些行相交,否则它们不相交。 如果 F*P 为零,当然你不能进行计算,但是在这种情况下,直线是平行的,因此只在明显的情况下相交。

交点的精确点是 C + F*h

的乐趣:

如果 h 完全 0 或者 1 end-point线接触。 你可以认为这是一个"交集",或者你认为合适。

具体来说,h 是为了准确地触摸另一条线而增加的长度。

因此,如果 h<0,它表示矩形线是从A 到b")的给定行( 打开方式"方向"正在",如果是 h>1,矩形线是给定行的"在前面"。

派生:

A 和C 是指向行首的向量;E 和F 是构成行的两端的向量。

对于平面中任意两条non-parallel线,必须正好有一对标量 gh,这样公式才能保持:


A + E*g = C + F*h

为什么因为两个non-parallel线必须相交,这意味着你可以在两个行之间进行缩放,互相触碰。

( 乍看起来就像一个含有两个未知数的单一方程) ! 但不是当你考虑到这是一个 2d矢量方程,这意味着这是一对方程 xy )。

我们必须删除其中一个变量。 一个简单的方法是使 E 术语为零。 为此,使用一个向量的dot-product两边的,使用一个将指向零的向量。 我在上面调用了 P,并做了明显的转换。

你现在拥有:


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

我试图实现上面描述的算法,所以优雅杰森;不幸的是虽然数学在调试工作时我发现很多情况下,它不工作。

例如考虑点 A(10,10) B(20,20) C(10,1) D(1,10) 给出 h= 。5,但是通过检查,这些段在彼此附近是 no-where 。

图形这清楚地表明 0 <h <1标准只有表明拦截点会躺在cd如果存在但告诉这个点是否位于ab没有之一。 为了确保有一个交叉点,你必须对变量g 进行对称计算,并且拦截的要求是: 0 <g <1和 0 <<1

下面是对gavin的改进。 marcp的解决方案也类似,但既不延迟除法。

这实际上是一个实际应用加雷斯·里斯的回答,因为相当于 cross-product 2 D perp-dot-product,就是这个代码使用三种。 切换到 3 cross-product并使用,在结尾处插值,结果在 3的行之间的两个最近的点。 无论如何,2解决方案:


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

基本上它推迟除法到最后一刻,并移动大部分测试,直到完成某些计算,从而添加 early-outs 。 最后,它还避免了在平行线平行时出现的零大小写。

你可能还要考虑使用一个epsilon测试而不是对零进行比较。 非常接近平行的线条可以产生稍微偏离的结果。 这不是 Bug,它是浮点数学的限制。

这里接受的答案不正确( 它已经被接受了,所以万岁) 。 它没有正确地清除所有 non-intersections 。 很简单,它似乎会工作,但可能会失败,尤其是在 0和 1被认为是有效的情况下。

请考虑以下情况:

( 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 )

所以,显然这两个线段在( 0,0 ) 见面,行cd上,但不是在ab。 所以发生了什么? 答案是 0和 1的值是无效的,只有在正确地预测端点交集时才有效。 当一个行( 但不是另一个)的扩展符合线段时,该算法预测线段的交集,但这是不正确的。 我想通过从 AB vs 光盘开始测试,然后用 CD vs AB测试,这个问题会被消除。 只有当两者介于 0和 1之间时,它们才能被认为相交。

如果必须预测 end-points,我建议使用向量交叉产品方法。

-Dan

问 C: 如何检测两条线段是否相交?

我搜索了同样的主题,我对答案不满意。 所以我写了一篇文章,解释了非常详细的如何检查两条线段是否与图像相交。 有完整的( 并测试) Java-code 。

下面是裁剪到最重要部分的文章:

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

enter image description here

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

enter image description here

如果两个边界框都有一个交集,你可以移动线段,使一个点位于( 0 |0 ) 。 现在,你可以通过一个。 现在用同样的方式移动线段b,检查线段b的新点是否在线段的不同侧面。 如果是这种情况,请按相反的方式检查。 如果也是这样,线段相交。 如果没有,它们就不会相交。

问题 A: 两条线段相交的位置?

你知道两条线段和b 相交。 如果你不知道,用我给你的工具查一下。

现在你可以通过一些案例,得到 7级数学( 参见代码和交互式示例 )的解决方案。

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

让我们说说你的观点 A = (x1, y1) ,点 B = (x2, y2) , C = (x_3, y_3) , D = (x_4, y_4) 你的第一行是 definied ( 使用!= ),第二个是 CD ( 使用!= ) 。


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 如果它们相交于所有。

每行和b 线由两条直线定义。 你基本上可以应用同样的逻辑,在问题中。

C 和 objective-c

基于 Gareth Rees answer回答


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/ 上是公开

Processing.js 有一个演示,示例代码

这对我来说工作正常。 从获取的


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

...