欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

判断两条线段是否相交以及求相交的交点坐标

程序员文章站 2022-04-01 13:35:23
...

最近在看recast&detour源码的时候有遇到许多数学上的算法问题,特此记录,以便以后查看。


方法一:

求线段AB  和 线段CD 有没有交点以及交点坐标。

1)先对AB和CD线段的aabb包围盒进行相交性检测,看是否 肯定不相交。

2)再用二维叉积进行进一步判断相交可能性。令:

\[\begin{gathered}
  a1 = cross2d(\overrightarrow {AB} ,\overrightarrow {AD} ) \hfill \\
  a2 = cross2d(\overrightarrow {AB} ,\overrightarrow {AC} ) \hfill \\
  a3 = cross2d(\overrightarrow {CD} ,\overrightarrow {CA} ) \hfill \\
  a4 = cross2d(\overrightarrow {CD} ,\overrightarrow {CB} ) \hfill \\ 
\end{gathered} \]

如果 a1 * a2 > 0 (说明 C 和 D点 在线段AB的同一侧):

         如果 a3 * a4 = 0 : 判断相交且交点是A或B,否则不相交。

如果 a1 * a2 < 0(说明 C 和 D点 在线段AB的异侧) :

        如果 a3 * a4 <=0: 判断相交(=0时交点是A或B),否则不相交。

如果 a1 * a2 = 0(说明 C 或 D点 在线段AB上):

        如果 a3 * a4 = 0 , 所有两线段有重合定义为不相交,否则就是相交。

    ps:a4 也等于 a3 + a2 - a1。可以通过面积或者叉积的分配规律证明

方法二

求解相交点的坐标,列方程求解,此方法同时计算除了交点坐标。

    http://dec3.jlu.edu.cn/webcourse/t000096/graphics/chapter5/01_1.html

问题:求线段A(x1,y1)B(x2,y2) 和 线段C(x3,y3)D(x4,y4)之间的交点。

使用参数方程表示线段AB 和 CD所在的直线方程:

\[\left\{ {\begin{array}{*{20}{c}}
  {x = {x_1} + \lambda ({x_2} - {x_1})} \\ 
  {y = {y_1} + \lambda ({y_2} - {y_1})} 
\end{array}} \right.\left\{ {\begin{array}{*{20}{c}}
  {x = {x_3} + \mu ({x_4} - {x_3})} \\ 
  {y = {y_3} + \mu ({y_4} - {y_3})} 
\end{array}} \right.\]

如果两个线段之间有交点,即联立方程求参数lamda 和 mu 

\[\begin{array}{*{20}{c}}
  {({x_2} - {x_1})\lambda  - ({x_4} - {x_3})\mu  = {x_3} - {x_1}} \\ 
  {({y_2} - {y_1})\lambda  - ({y_4} - {y_3})\mu  = {y_3} - {y_1}} 
\end{array}\]

列成行列式,用解向量法求解方程:

\[\left( {\begin{array}{*{20}{c}}
  {{x_2} - {x_1}}&{ - ({x_4} - {x_3})} \\ 
  {{y_2} - {y_1}}&{ - ({y_4} - {y_3})} 
\end{array}} \right)\left( {\begin{array}{*{20}{c}}
  \lambda  \\ 
  \mu  
\end{array}} \right) = \left( {\begin{array}{*{20}{c}}
  {{x_3} - {x_1}} \\ 
  {{y_3} - {y_1}} 
\end{array}} \right)\]

令向量:

u = (x2-x1, y2-y1) = AB向量

v = (x4-x3, y4-y3) = CD向量

w = (x3-x1, y3-y1) = AC向量


\[\det  = \left| {\begin{array}{*{20}{c}}
  {{x_2} - {x_1}}&{ - ({x_4} - {x_3})} \\ 
  {{y_2} - {y_1}}&{ - ({y_4} - {y_3})} 
\end{array}} \right| =  - ({u_x}{v_y} - {v_x}{u_y}) =  - cross(u,v)\]

最终求得参数 lamda 和 mu,把lamda mu带入参数方程即得交点


\[\left\{ {\begin{array}{*{20}{c}}
  {\lambda  = \left| {\begin{array}{*{20}{c}}
  {{x_3} - {x_1}}&{ - ({x_4} - {x_3})} \\ 
  {{y_3} - {y_1}}&{ - ({y_4} - {y_3})} 
\end{array}} \right|/\det  =  - cross(w,v)/\det  = cross(v,w)/\det } \\ 
  {\mu  = \left| {\begin{array}{*{20}{c}}
  {{x_2} - {x_1}}&{{x_3} - {x_1}} \\ 
  {{y_2} - {y_1}}&{{y_3} - {y_1}} 
\end{array}} \right|/\det  = cross(u,w)/\det } 
\end{array}} \right.\]

继续分析:

因为 lamda的值 =(交点到起点A)/AB,

而 lamda的值 = cross(v,w)/cross(v,u) = (CD叉积AC)/(CD叉积AB) =  (CA叉积CD)/(CD叉积AB) 。

所以

 1 > lamda > 0  说明 交点在 AB 上; 

lamda = 0 交点在A点;

lamda =1 交点在B点;

lamda >1 交点在AB延长线上;

lamda <0 交点在BA延长线上。

同理 mu 的值也能反应交点在CD线段上的各种情况。

所以可以通过 lamda 和 mu 的值,判断出交点的情况。只有两者都在(0,1)之间 也是真正的相交于线段之内。


源码

方法一判断是否相交

源码中调用 overlapSegSeg2d 函数之前已经排除了在端点在线段以及延长线上的可能,所以可以简单通过是否小于0判断是否相交。

static int overlapSegSeg2d(const float* a, const float* b, const float* c, const float* d)
{
	const float a1 = vcross2(a, b, d);
	const float a2 = vcross2(a, b, c);
	if (a1*a2 < 0.0f)
	{
		float a3 = vcross2(c, d, a);
		float a4 = a3 + a2 - a1;
		if (a3 * a4 < 0.0f)
			return 1;
	}
	return 0;
}

方法二计算交点坐标

ap 和 aq 即为AB点, bq和bq即为CD点,s和t即为lamda和mu

bool dtIntersectSegSeg2D(const float* ap, const float* aq,
						 const float* bp, const float* bq,
						 float& s, float& t)
{
	float u[3], v[3], w[3];
	dtVsub(u,aq,ap);
	dtVsub(v,bq,bp);
	dtVsub(w,ap,bp);
	float d = vperpXZ(u,v);
	if (fabsf(d) < 1e-6f) return false;
	s = vperpXZ(v,w) / d;
	t = vperpXZ(u,w) / d;
	return true;
}

进一步应用线段之间相交的交点所在位置

来判断线段与多边形之间的相交情况

// 应该是针对凸多边形
bool dtIntersectSegmentPoly2D(const float* p0, const float* p1,
							  const float* verts, int nverts,
							  float& tmin, float& tmax,
							  int& segMin, int& segMax)
{
	static const float EPS = 0.00000001f;
	
	tmin = 0;
	tmax = 1;
	segMin = -1;
	segMax = -1;
	
	float dir[3];
	dtVsub(dir, p1, p0);
	
	for (int i = 0, j = nverts-1; i < nverts; j=i++)
	{
		float edge[3], diff[3];
		dtVsub(edge, &verts[i*3], &verts[j*3]);
		dtVsub(diff, p0, &verts[j*3]);
		// 面积
		// n > 0 说明 p0 在 edgej->i 的 右边
		const float n = dtVperp2D(edge, diff);

		// d 约等于 0 ,说明 s 和 edge平行
		const float d = dtVperp2D(dir, edge);
		if (fabsf(d) < EPS)
		{
			// S is nearly parallel to this edge
			// n < 0 说明 p0 在 edgej->i 的 左边 , 那么肯定不相交了
			if (n < 0)
				return false;
			else
				continue;
		}

		// 面积之比 即 高之比 
		// t 表示的是 (s与edge的交点 到 s的起点)的线段长度/ s的长度
		const float t = n / d;

		// n > 0 说明 i 在 dir 的 左边
		if (d < 0)
		{	
			// segment S is entering across this edge
			// 更新的是 tmin 应该取最大值,因为是靠近,所以交点肯定是越远离起点越好 越远离起点 t值越大
			// 最大的tmin 应该是 edge 与 s的交点在edge上 而非是edge的延长线上。
			if (t > tmin)
			{
				tmin = t;
				segMin = j;
				// S enters after leaving polygon
				if (tmin > tmax)
					return false;
			}
		}
		else
		{
			// segment S is leaving across this edge
			// 更新的是 tmin 应该取最小值 因为是远离,所以交点肯定是越靠近起点越好 越靠近起点 t值越小
			// 最小的tmax 应该是 edge 与 s的交点在edge上 而非是edge的延长线上。
			// 如果 t 一直是 大于1 说明 终点在多边形之内 那么 segMax = -1 tmax = 1
			// 如果 segMax 不等于 -1 ,那么 tmax 肯定是小于1 
			if (t < tmax)
			{
				tmax = t;
				segMax = j;
				// S leaves before entering polygon
				if (tmax < tmin)
					return false;
			}
		}
	}
	
	return true;
}

参考

https://www.cnblogs.com/Duahanlang/archive/2013/05/11/3073434.html

https://blog.csdn.net/rickliuxiao/article/details/6259322



相关标签: 叉积