最近在看recast&detour源码的时候有遇到许多数学上的算法问题,特此记录,以便以后查看。
方法一:
线段AB 线段CD
1)先对AB和CD线段的包围盒进行相交性检测,看是否 肯定不相交。
2)再用叉积进行进一步判断相交可能性。令:
a1 = (B点 - A点) x (D点 - A点) (叉积) a2 = (B - A) x (C - A) (叉积)
a3 = (D - C) x (A - C) (叉积) a4 = (D - C) x (B - C) (叉积)
如果 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