如何确定两条线是否相交,如果它们相交,在x,y点处是什么?
对于使用矢量交叉产品的这个问题,有一个很好的方法.将二维向量叉积v × w定义为v x w y - v y w x.
假设两个线段从p到p + r和从q到q + s.然后第一行上的任何点都可表示为p + t r(对于标量参数 t)和第二行上的任何点可表示为q + u s(对于标量参数 u).
如果我们能找到t和u,则两条线相交:
p + t r = q + u 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)
以同样的方式,我们可以为你解决:
(p + t r)× r =(q + u s)× r
u (s × r)=(p - q)× r
u =(p - q)× r /(s × r)
为了减少计算步骤的数量,可以方便地重写如下(记住s × r = - r × s):
u =(q - p)× r /(r × s)
现在有四种情况:
如果r × s = 0且(q - p)× r = 0,则两条线共线.
在这种情况下,根据第一个线段(p + t r)的等式表示第二个段(q和q + s)的端点:
t 0 =(q - p)· r /(r · r)
t 1 =(q + s - p)· r /(r · r)= t 0 + s · r /(r · r)
如果t 0和t 1之间的间隔与区间[0,1]相交,则线段共线并重叠; 否则它们是共线的和不相交的.
注意,如果s和r指向相反的方向,则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页.在三个方面,通常的情况是这些线是倾斜的(既不平行也不相交),在这种情况下,该方法给出了两条线最接近的点.
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
那让我困惑了几个小时.:(
问题减少到这个问题:从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
是完全 0
或1
线在终点处触摸.您可以认为这是一个"交叉点"或不适合您.
具体来说,h
你需要多少乘以线的长度才能与另一条线完全接触.
因此,If h<0
表示矩形线在给定线的"后面"("方向"为"从A到B"),如果h>1
矩形线在给定线的"前面".
推导:
A和C是指向线的起点的向量; E和F是形成线的A和C末端的向量.
对于平面中的任何两条非平行线,必须只有一对标量g
,h
并且这个等式成立:
A + E*g = C + F*h
为什么?因为两条非平行线必须相交,这意味着您可以将每条线都按比例缩放两条线并相互接触.
(首先,这看起来像是一个有两个未知数的方程! 但是当你认为这是一个二维矢量方程时,并不是这意味着这实际上是一对方程式x
和y
.)
我们必须消除其中一个变量.一个简单的方法是使E
术语为零.要做到这一点,使用一个用E点到零的向量取等式两边的点积.我P
在上面调用了这个向量,我做了E的明显变换.
你现在有:
A*P = C*P + F*P*h (A-C)*P = (F*P)*h ( (A-C)*P ) / (F*P) = h
我试图实现Jason上面描述的优雅算法; 不幸的是,虽然在调试中通过数学工作,我发现很多情况下它不起作用.
例如,考虑点A(10,10)B(20,20)C(10,1)D(1,10)给出h = .5但是通过检查可以清楚地看出这些段在每个附近都没有其他.
对此进行图形化可以清楚地表明,0
这是对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测试而不是与零进行比较.非常接近平行的线可以产生稍微偏离的结果.这不是一个错误,它是浮点数学的一个限制.
我搜索了相同的主题,我对答案不满意.所以我写了一篇文章,详细解释了如何检查两个线段是否与很多图像相交.有完整的(和测试的)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中使用的相同逻辑.
这里接受的答案是不正确的(从那以后一直不被接受,所以万岁!).它没有正确消除所有非交叉点.平凡地看起来它可能会起作用但它可能会失败,尤其是在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之间时才能说它们相交.
如果您必须预测终点,我建议使用矢量交叉乘积法.
-担
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
找到两个线段的正确交集是一个非常重要的任务,有很多边缘情况.这是一个用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);
}
只是想提一下,在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; }
根据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/
上面提供了大量的解决方案,但我认为以下解决方案非常简单易懂.
两个段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
这对我很有用.取自这里.
// 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; } }
我尝试了一些这些答案,但他们没有为我工作(抱歉的家伙); 经过一些网络搜索,我发现了这个.
通过对他的代码稍加修改,我现在有了这个函数,它将返回交叉点,或者如果没有找到交集,它将返回-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
似乎对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; } } } };
我认为这个问题有一个更简单的解决方案.我今天提出了另一个想法,似乎工作得很好(至少现在是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)); } }