
本文详解如何修正基于sat的圆-多边形碰撞检测算法,重点解决因投影重叠判断逻辑缺陷导致的垂直方向误触发问题,并提供可直接集成的专业级实现。
本文详解如何修正基于sat的圆-多边形碰撞检测算法,重点解决因投影重叠判断逻辑缺陷导致的垂直方向误触发问题,并提供可直接集成的专业级实现。
在游戏开发(如高尔夫模拟)中,精确的圆与凸多边形碰撞检测至关重要。分离轴定理(SAT)是处理此类问题的经典方法:若存在一条轴,使得两物体在其上的投影不重叠,则二者无碰撞;反之,若所有候选轴上投影均重叠,则发生碰撞。
然而,原始代码中的 check_overlap 函数存在根本性逻辑错误:它仅在所有 x 坐标“完全相等”(即极端竖直对齐)时才检查 y 方向重叠,其余情况仅判断 x 方向——这违背了二维投影重叠的基本要求:必须同时在 x 和 y 两个正交分量上均存在重叠,才能判定该轴上投影相交。由于浮点坐标的连续性,x1 == x2 == x3 == x4 几乎永不成立,导致算法退化为单轴(x)粗略检测,从而在垂直布局下产生大量误报。
正确做法是对每条分离轴(即多边形每条边的法向量),将圆心投影到该轴上,并计算“圆在该轴上的投影区间”——即以圆心投影值为中心、半径为长度的线段。多边形则投影为其顶点在该轴上的标量值集合,取最小/最大值构成投影区间。随后,只需判断这两个一维区间是否重叠。
以下是修正后的完整核心逻辑(含关键注释):
import math
def get_edge_normal(A, B):
"""返回边AB的单位法向量(指向多边形外部,SAT所需)"""
dx, dy = B[0] - A[0], B[1] - A[1]
# 顺时针旋转90°得外法向(或根据顶点顺序调整符号)
nx, ny = -dy, dx
length = math.sqrt(nx**2 + ny**2)
if length == 0:
return [0.0, 0.0]
return [nx / length, ny / length]
def project_point_on_axis(point, axis):
"""计算点在单位轴向量上的标量投影值"""
return point[0] * axis[0] + point[1] * axis[1]
def circle_projection_interval(center, radius, axis):
"""返回圆在指定单位轴上的投影区间 [min_proj, max_proj]"""
center_proj = project_point_on_axis(center, axis)
return [center_proj - radius, center_proj + radius]
def polygon_projection_interval(vertices, axis):
"""返回凸多边形在指定单位轴上的投影区间 [min_proj, max_proj]"""
projections = [project_point_on_axis(v, axis) for v in vertices]
return [min(projections), max(projections)]
def intervals_overlap(interval1, interval2):
"""判断两个一维闭区间是否重叠"""
return interval1[0] <p><strong>关键注意事项:</strong> </p>
- ✅ 凸多边形前提:SAT 仅对凸多边形严格有效。若需支持凹多边形,请先三角剖分或改用其他算法(如GJK)。
- ✅ 法向量方向一致性:确保所有法向量统一朝外(或朝内),否则投影区间计算可能出错。可通过顶点顺序(顺/逆时针)验证。
- ✅ 数值鲁棒性:实际项目中建议添加小量
EPS = 1e-6防止浮点误差导致的边界误判(例如intervals_overlap中改为interval1[0] )。 - ⚠️ 性能优化:对高频调用场景(如每帧检测),可预计算并缓存多边形的轴向量,避免重复归一化。
通过上述重构,算法不再依赖坐标轴对齐假设,真正实现了二维平面内任意朝向、任意尺度下的精确圆-多边形碰撞判定,彻底解决了原代码在垂直方向的系统性误报问题。










