关闭

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

标签: 算法交点
464人阅读 评论(0) 收藏 举报
分类:

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

 

我们要判断线段的位置关系以及计算交点的位置关系,需要用到向量的相关知识,即定义两个向量p(x1, y1)和q(x2, y2),向量积系数k=p×q=x1·y2-x2·y1,若k=0则pq平行,若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);

 

这个算法对于模拟游戏中需要求解线段之间的交点,射线之间的交点是非常有帮助的。如果有疑问,欢迎留言~

86
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场