SAT算法仅适用于凸多边形,对不规则多边形(凹、自相交、带孔或多连通)完全不可靠,因其数学前提要求两凸集在所有边法向量轴上投影重叠才等价于相交;凹多边形导致凸包胀大、法向量不完备、投影断裂,易漏判真实相交情形。

不规则多边形(即凹的、自相交的、带孔的或多连通的)不能直接用 SAT 判断碰撞——强行套用会漏判,比如“U形包住小矩形”却返回 false。必须退回到边-边检测 + 点包含检测的组合策略,且每一步都有浮点和拓扑陷阱。
为什么 SAT 对不规则多边形完全不可靠
SAT 的数学前提只对凸集成立:两个凸多边形在所有边法向量轴上投影重叠 ⇔ 它们相交。一旦多边形有凹角,它的凸包会“胀大”,导致真实相交却被某条轴“误分”。典型失效场景:
- “U”形多边形开口朝内,另一小矩形横穿开口——视觉明显相交,但 SAT 只扫外轮廓法向量,错过内部穿透
- 顶点顺序混乱(顺/逆时针混用)会让
glm::vec2(-e.y, e.x)生成方向相反的法向量,投影区间翻转 - 自相交多边形(如蝴蝶结形)根本无定义“边法向量语义”,
SAT逻辑直接崩坏
正确做法:边-边相交检测必须做,且要规范
这是判断“是否发生几何相交”的第一道硬门槛。不能只靠“有没有顶点在对方内部”,那是包含关系,不是相交。
- 用叉积 + 跨立实验实现
segment_intersect(),别写斜率公式(会除零、不处理垂直线) - 先做快速排斥:若一条线段的
max.x 等,直接跳过 - 严格区分“端点重合”和“内部相交”:
crossProduct(A,C,B) * crossProduct(A,B,D) 允许端点接触;若业务要求“仅穿透才算”,得改成 <code> - 遍历 A 的每条边与 B 的每条边,任一返回
true就可立即判定为碰撞
无边相交时,必须补点包含检测(但不能只靠它)
边不交 ≠ 不相交。例如小三角形完全落在大U形内部,此时需检查是否一方被另一方**严格包含**。
立即学习“C++免费学习笔记(深入)”;
- 用 winding number 算法判断点是否在多边形内部,别用射线法(对退化、自交多边形鲁棒性差)
- 浮点比较必须加容差:
abs(winding) > 1e-6f才算“严格内部”,不能判== 0 - 要双向检查:A 的所有顶点是否都在 B 内?B 的所有顶点是否都在 A 内?只要一方全在内、另一方至少一个顶点在外,就属于“包含型相交”
- 注意:若双方顶点互在对方内部(即完全重合或嵌套),也属于相交/重叠
工程落地前必须加的三道防护
真实数据里,建模误差、导入精度丢失、坐标缩放都会让算法当场崩溃。
- AABB 预筛不能省:
!aabb_intersect(aabb_a, aabb_b)就直接返回false,避免后续 O(n×m) 开销 - 顶点去重与退化边过滤:计算边向量
e = p[i+1] - p[i]后,必须检查glm::length(e) > 1e-6f,否则glm::normalize()产出 NaN,污染整条链 - 输入合规性检查:用
boost::polygon::is_valid()或手写边-边自交检测,提前拒绝自交多边形;用area()符号确认顶点顺序一致(全逆时针或全顺时针)
最易被忽略的是:winding number 计算中,共线点的处理方式直接影响“边界是否算内部”;而实际项目里,几乎没人手动验证顶点顺序是否真的全一致——结果就是测试用例全过,上线后某个导出模型突然不触发碰撞。


















