您的位置:首页 > 其它

判断与求解平面内两线段的交点的算法与实现

2017-01-03 23:42 555 查看
两条线段的位置关系可以大致归类为平行、相交和不平行且不相交,平行又可以分为重合,共线(不重合),部分重合,不平行且不相交的情况为线段所在直线相交,交点在线段外。

 
我们要判断线段的位置关系以及计算交点的位置关系,需要用到向量的相关知识,即定义两个向量p(x1, y1)和q(x2,
y2),向量积系数k=p×q=x1·y2-x2·y1,若k=0则p与q平行,若k>0,则p逆时针旋转到q(这里指旋转角小于180度的情况),若k<0,则p顺时针旋转到q(相关推导请参考游戏中两个常用的数学运算推导及算法推论 中关于向量积的推导和推论)。

 
如下图,现在有4个点A(x1, y1)、B(x2, y2)、C(x3,y3)、D(x4, y4),若线段AB和CD存在交点,则设其交点为E(x, y),向量AB记为p1(dx1,dy1),向量CD记为p2(dx2,
dy2),向量AC记为p3(dx3, dy3),如下图所示:



 
我们根据向量积系数为0来判断向量是否平行,即由如下算法:
float k = x1·y2-x2·y1;
if (k == 0) {
      //平行
} else {
      //进一步判断是否相交
}
这里对于平行中包含的共线、重合等特殊情况就不做具体讨论了,我们集中精力来考虑交点的求解算法。
我们先不考虑线段相交的情况,先来考虑直线的相交问题(要判断线段有无交点,可以先求解直线的交点,然后判断交点是否在线段内部)。

 
根据前面的定义我们能够得到下面的关系:
dx1=x2-x1
dy1=y2-y1
dx2=x4-x3
dy2=y4-y3
dx3=x3-x1
dy3=y3-y1

 
为了方便计算结果的表示,再定义下面的两个量:
t1=dx2·dy1-dx1·dy2
t2=dx1·dy3-dx3·dy1

 
设直线AB的斜率为k1,直线CD的斜率为k2,则有k1=(y-y1)/(x-x1)=(y2-y1)/(x2-x1)=dy1/dx1,k2=(y-y3)/(x-x3)=(y4-y3)/(x4-x3)=dy2/dx2,联立求解得:
x=(dx1·dx2·dy3-dx1·dy2·x3+dx2·dy1·x1)/(dy1·dx2-dy2·dx1)
=(dx1·dx2·dy3-dx1·dy2·x3+dx2·dy1·x3-dx2·dy1·x3+dx2·dy1·x1)/(dy1·dx2-dy2·dx1)
=x3+(dx1·dx2·dy3-dx2·dx3·dy1)/(dy1·dx2-dy2·dx1)
=x3+dx2·(dx1·dy3-dx3·dy1)/(dy1·dx2-dy2·dx1)
=x3+dx2·t2/t1
y=y3+dy2·(x-x3)/dx2=y3+dy2·t2/t1

 
因此,我们得到下面的求解算法(忽略具体的编程语言,只是算法的主体思路):
Struct Point {
      floatx;
      floaty;
}

 
float calculateVectorProduct(PointP1, Point P2, Point P3, Point P4) { 
      return(P2.x-P1.x) * (P4.y-P3.y) - (P2.y-P1.y) * (P4.x-P3.x);
}

 
PointcalculateIntersectionPoint(Point A, Point B, Point C, Point D) {

float t1 =calculateVectorProduct(C, D, A, B);
      floatt2 = calculateVectorProduct(A, B, A, C);

float x = C.x +(D.x-C.x) * t2 / t1;

float y = C.y +(D.y-C.y) * t2 / t1;

return newPoint(x, y);
}

 
在得到了交点之后,我们来做进一步的判断,来确定交点是否在线段上。要确定点是否在线段上其实比较简单,因为我们求得的点一定在直线上,所以不需要去计算点是否满足直线方程,只需要判断点的横纵坐标是否在线段两个端点的横纵坐标之间即可。

 
算法实现比较简单:
float min(float Num1, float Num2) {
      returnNum1 > Num2 ? Num2 : Num1;
}

 
float max(float Num1, float Num2) {
      returnNum1 > Num2 ? Num1 : Num2;
}

 
float isBetween(float Num, floatNum1, float Num2) {
      floatdeviation =0.1;      //   由于浮点数的精度问题,因此引入误差防止误判
      return(Num >= min(Num1, Num2)-deviation && Num <= max(Num1,Num2)+deviation;
}

 
bool isPointInSegment(Point P1,Point LineStart, Point LineEnd) {
      return(isBetween(P1.x, LineStart.x,LineEnd.x)  &&  isBetween(P1.y, LineStart.y,LineEnd.y));
}
当然这里isPointInSegment这个方法是基于P1在LineStart和LineEnd确定的直线上的,也就是基于P1是前面算法中求得的直线交点。要判断点是否在直线上,需要根据y=kx+t来求解出直线方程,然后验证点坐标是否满足直线方程即可。

 
算法如下:
bool checkIsPointOnLine(Point P1,Point LineStart, Point LineEnd) {
      if(isBetween(LineStart.x-LineEnd.x, 0, 0)) {
           returnisBetween(P1.x-LineStart.x, 0, 0);
      }else {
           floatk = (LineEnd.y-LineStart.y)/(LineEnd.x-LineStart.x);
           floatcalculatedY = k * (P1.x-LineEnd.x) + LineEnd.y;
           returnisBetween(P1.y-calculatedY, 0, 0);
      }
}

 
据此我们可以对任意给出的4个点做判断:
Point A = Point(1, 1);
Point B = Point(4, 3);
Point C = Point(2, 1);
Point D = Point(4, 5);
Point E =calculateIntersectionPoint(A, B, C, D);
bool isOnSegment = isPointInSegment(E, A, B) & isPointInSegment(E, C, D);

 
这个算法对于模拟游戏中需要求解线段之间的交点,射线之间的交点是非常有帮助的。如果有疑问,欢迎留言~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 交点