SAT算法
SAT 是处理凸多边形 (Convex Polygons) 和 凸多面体 (Convex Polyhedra) 之间碰撞检测的强大工具。其核心思想非常优雅:
如果能找到一条轴,使得两个凸体在该轴上的投影(一维区间)不发生重叠,那么这两个凸体就没有发生碰撞。反之,如果对于所有需要测试的候选轴,两个凸体的投影都发生重叠,那么它们必然发生了碰撞。
这条能将两个凸体投影分开的轴,就被称为分离轴 (Separating Axis)。
1. 候选轴 (Candidate Axes)
对于两个凸多面体 A 和 B,需要测试的候选轴是:
- 物体 A 的所有面的法线。
- 物体 B 的所有面的法线。
- 物体 A 的每一条边与物体 B 的每一条边的叉积 (Cross Product)。
2. 投影与测试
对于每一个候选轴 ,我们需要:
- 计算物体 A 的所有顶点在轴 上的投影,找到投影区间的最小值 和最大值 。
- 同样计算物体 B 在轴 上的投影区间 。
- 检查这两个区间是否重叠。如果不重叠(即 或 ),则我们找到了一个分离轴,可以立即判定两个物体没有碰撞,并终止算法。
如果测试完所有候选轴,它们的投影都发生重叠,那么物体就发生了碰撞。
3. 计算穿透深度和法线
在所有候选轴的投影中,那个重叠量最小的投影,其对应的轴就是最小穿透轴 (Axis of Minimum Penetration)。这个最小的重叠量就是穿透深度 (Penetration Depth),该轴的方向就是接触法线 (Contact Normal)。
代码示例 (2D SAT 伪代码):
bool checkSATCollision(const Polygon& A, const Polygon& B, ContactInfo& out_contact) {
float minOverlap = FLT_MAX;
Vector2 mtvAxis; // Minimum Translation Vector Axis
// 1. 获取所有候选轴 (对于2D凸多边形,就是边的法线)
std::vector<Vector2> axes = getCandidateAxes(A, B);
for (const auto& axis : axes) {
// 2. 计算在轴上的投影
Projection pA = A.project(axis);
Projection pB = B.project(axis);
// 3. 检查投影是否重叠
float overlap = pA.getOverlap(pB);
if (overlap <= 0) {
// 找到了分离轴,没有碰撞
return false;
}
// 4. 记录最小重叠量
if (overlap < minOverlap) {
minOverlap = overlap;
mtvAxis = axis;
}
}
// 如果所有轴都重叠,则发生碰撞
out_contact.penetrationDepth = minOverlap;
out_contact.normal = mtvAxis.normalized();
// ... (需要额外逻辑来确保法线方向正确,并计算接触点)
return true;
}
- 优点: 原理清晰,对于边和面数量不多的凸体非常高效。能同时得到碰撞结果、穿透深度和法线。
- 缺点: 随着顶点数的增加,候选轴的数量会急剧增长(特别是边-边叉积部分),导致性能下降。不适用于非凸体或曲面。