空间中直线段和三角形的相交算法
最近在看recast&detour源码的时候有遇到许多数学上的算法问题,特此记录,以便以后查看。
分析
问题: 已经线段pq (p起点 q终点) ,三角形abc(a b c逆时针存储),判断pq与abc有无交点。
第一步:
判断三角形所在的面和线段所在的直线 是否平行 或者 是否是从三角形面的背面进入。如果是就提前退出,否则进入第二步。
(使用面的法向量 和 线段的向量 的 点积)
第二步:
判断线段和三角形所在的面的交点是否在线段上。
平面的方程可以表示为:
X*normal = d (1)
其中 X 为平面上的点,d为原点到平面的距离。
以p为起点的,沿着pq方向的射线上的点的坐标可以表示为:
M = p + t*pq = p - t*qp (2)
假设M点即为 平面与pq所在的直线的交点:(M点和a点均为在平面上的点,带入公式(1)中
\[(\overrightarrow {op} - t \cdot \overrightarrow {qp} ) \cdot \overrightarrow {normal} = \overrightarrow {oa} \cdot \overrightarrow {normal} \]
解得t为
\[t = \frac{{\overrightarrow {ap} \cdot \overrightarrow {normal} }}{{\overrightarrow {qp} \cdot \overrightarrow {normal} }}\]
判断t是否 (0,1)之间,如果是继续第三步,如果否返回false。
第三步:
判断是否交点在三角形内部。
三角形abc内的点M可以用三角形的重心坐标系表示 :
\[M = (1 - {\lambda _2} - {\lambda _3})a + {\lambda _2}b + {\lambda _3}c\]
其中如果lamda3 和 lamda2 都在(0,1)之间,那么表示M在abc的内部
改写成向量形式,且把M用公式(2)带入,得到:
\[\overrightarrow {op} - t\overrightarrow {qp} = (1 - {\lambda _2} - {\lambda _3})\overrightarrow {oa} + {\lambda _2}\overrightarrow {ob} + {\lambda _3}\overrightarrow {oc} \]
整理得:
\[{\lambda _2}\overrightarrow {ab} + {\lambda _3}\overrightarrow {ac} + t\overrightarrow {qp} = \overrightarrow {ap} \]
写成向量的形式:
\[\left( {\begin{array}{*{20}{c}}
{a{b_x}}&{a{c_x}}&{q{p_x}} \\
{a{b_y}}&{a{c_y}}&{q{p_y}} \\
{a{b_z}}&{a{c_z}}&{q{p_z}}
\end{array}} \right)\left( {\begin{array}{*{20}{c}}
{{\lambda _2}} \\
{{\lambda _3}} \\
t
\end{array}} \right) = \left( {\begin{array}{*{20}{c}}
{a{p_x}} \\
{a{p_y}} \\
{a{p_z}}
\end{array}} \right)\]
求解lamda2 lamda3:(应用tips中的克莱姆法则以及混合积的运算法则)
\[\begin{gathered}
{\lambda _2} = \frac{{(ap,ac,qp)}}{{(ab,ac,qp)}} = \frac{{ac \cdot (qp \times ap)}}{{qp \cdot (ab \times ac)}} \hfill \\
{\lambda _3} = \frac{{(ab,ap,qp)}}{{(ab,ac,qp)}} = \frac{{ab \cdot (ap \times qp)}}{{qp \cdot (ab \times ac)}} = \frac{{ - ab \cdot (qp \times ap)}}{{qp \cdot (ab \times ac)}} \hfill \\
\end{gathered} \]
源码
// 空间点 sp 起点 sq终点
// 三角形空间点 a b c
// 输出参数 t
static bool intersectSegmentTriangle(const float* sp, const float* sq,
const float* a, const float* b, const float* c,
float &t)
{
float v, w;
float ab[3], ac[3], qp[3], ap[3], norm[3], e[3];
rcVsub(ab, b, a);
rcVsub(ac, c, a);
// 终点->起点 的向量, 不是 起点->终点
rcVsub(qp, sp, sq);
// Compute triangle normal. Can be precalculated or cached if
// intersecting multiple segments against the same triangle
// 法线方向为三角形正面方向
rcVcross(norm, ab, ac);
// Compute denominator d. If d <= 0, segment is parallel to or points
// away from triangle, so exit early
// d = 0.0 说明 qp 和 norm 垂直,说明三角形和 qp 平行。
// d < 0.0 说明 qp 和 norm 是钝角 说明是从三角形的背面 进入和三角形相交的
float d = rcVdot(qp, norm);
if (d <= 0.0f) return false;
// Compute intersection t value of pq with plane of triangle. A ray
// intersects if 0 <= t. Segment intersects if 0 <= t <= 1. Delay
// dividing by d until intersection has been found to pierce triangle
rcVsub(ap, sp, a);
t = rcVdot(ap, norm);
if (t < 0.0f) return false;
if (t > d) return false; // For segment; exclude this code line for a ray test
// Compute barycentric coordinate components and test if within bounds
rcVcross(e, qp, ap);
v = rcVdot(ac, e);
if (v < 0.0f || v > d) return false;
w = -rcVdot(ab, e);
if (w < 0.0f || v + w > d) return false;
// Segment/ray intersects triangle. Perform delayed division
t /= d;
return true;
}
tips
克莱姆法则
一个线性方程组可以用矩阵(A为n*n的方阵)与向量(x,c均长度为n的列向量)的方程来表示:
\[Ax = c\]
如果 A是一个可逆矩阵,那么方程有解,为
\[{x_i} = \frac{{\det ({A_i})}}{{\det (A)}}\]
其中Ai是被列向量 c取代了 A的第i列的列向量后得到的矩阵。
混合积的运算法则
1.混合积和三维矩阵行列式的关系(其中 后一个等号 矩阵的转置矩阵的行列式等于这个矩阵的行列式。)
\[a \cdot (b \times c) = \left| {\begin{array}{*{20}{c}}
{{a_1}}&{{a_2}}&{{a_3}} \\
{{b_1}}&{{b_2}}&{{b_3}} \\
{{c_1}}&{{c_2}}&{{c_3}}
\end{array}} \right| = \left| {\begin{array}{*{20}{c}}
{{a_1}}&{{b_1}}&{{c_1}} \\
{{a_2}}&{{b_2}}&{{c_2}} \\
{{a_3}}&{{b_3}}&{{c_3}}
\end{array}} \right|\]
2.混合积的交换律
\[a \cdot (b \times c) = b \cdot (c \times a) = c \cdot (a \times b)\]
上一篇: Unity 碰撞器(Collider)与触发器(Trigger)
下一篇: 密码学:对称加密算法