GJK比直接边边检测更优,因其将碰撞检测转化为原点是否在Minkowski差集内的问题,时间复杂度远低于O(m×n),且能可靠处理完全嵌套等边界情况。

为什么GJK比直接边边检测更值得用
因为凸多边形边数一多,暴力检测每条边与另一多边形的相交或包含关系,时间复杂度会飙到 O(m×n),且难以处理“完全嵌套但无边相交”的情况(比如一个小正方形完全在大三角形内部)。GJK只依赖支持函数(support function)和单纯形迭代,把问题降维成原点是否在Minkowski差集内部,实际运行中常在3~5次迭代内收敛,对任意维度凸体都适用。
怎么写一个可靠的support函数
support函数是GJK的基石:给定方向向量 d,返回多边形中在该方向上最远的顶点。常见错误是没归一化输入方向就直接点积,导致结果受方向长度影响;或者顶点索引越界、浮点比较不加容差。
实操建议:
- 始终对输入方向
d调用normalize()(或手动除以sqrt(d.x*d.x + d.y*d.y)),再点积 - 遍历所有顶点时用
std::numeric_limits<double>::lowest()</double>初始化最大值,避免 -0.0 或 NaN 干扰 - 若多边形顶点按逆时针存储,可提前计算凸包(虽然输入已假设为凸),但不必额外排序
- 示例片段:
Vec2 support(const std::vector<Vec2>& poly, const Vec2& d) { Vec2 d_norm = normalize(d); int best_i = 0; double max_dp = dot(poly[0], d_norm); for (int i = 1; i < poly.size(); ++i) { double dp = dot(poly[i], d_norm); if (dp > max_dp + 1e-9) { // 加容差防浮点抖动 max_dp = dp; best_i = i; } } return poly[best_i]; }GJK主循环里最容易卡死的三个地方
GJK不是黑盒——它靠不断收缩单纯形(线段/三角形)逼近原点。一旦方向更新错误、退化判断缺失或原点恰好落在边上,就会无限循环或返回假阴性。
立即学习“C++免费学习笔记(深入)”;
关键检查点:
- 每次新支持点
s必须满足dot(s, d) < 0,否则说明原点已在当前Minkowski差集内部 → 直接返回“碰撞” - 单纯形含2个点(线段)时,若原点投影落在端点外,新搜索方向应垂直于该线段并指向原点;别直接用
s - a这类错误方向 - 三点构成的三角形中,必须用重心坐标或叉积符号判断原点落在哪个子区域(顶点附近 / 边附近 / 内部),不能只靠面积正负——二维下推荐用
cross(b-a, o-a)和cross(c-b, o-b)符号组合判断 - 务必设迭代上限(如30次),并检查连续两次单纯形顶点距离变化是否小于
1e-6,防止数值震荡
从GJK输出结果到最终碰撞判定的临界处理
GJK本身只回答“原点是否在Minkowski差集中”,即是否相交或接触。但工程中常需最小平移向量(MTD)或穿透深度——这时不能直接退出,而要调用EPA(Expanding Polytope Algorithm)或用GJK终止单纯形做投影估算。
实用取舍:
- 仅需布尔碰撞?GJK返回
true即可,但注意:若两多边形共边或共点,浮点误差可能导致dot(s,d) ≈ 0,建议将判定阈值设为1e-8而非0 - 需要分离轴?GJK终止单纯形的某条边法向就是候选分离轴,用该方向调用两次
support()可得两多边形在此方向的投影区间,间隙即为穿透深度 - 不要在GJK里尝试构造完整Minkowski差集顶点——计算量爆炸,也违背算法设计本意
真正麻烦的是退化情形:两多边形几乎平行的长边擦过、顶点精确落在对方边上。这时候支持函数的浮点不确定性会被放大,必须统一用
long double或自定义定点运算,且所有几何判断都要带绝对/相对双容差。 - 每次新支持点


















