直接结论:若两个AABB处于同一世界坐标系下,只需逐轴比较x/y/z三对投影区间是否重叠,共6次区间交集判断。需避免顶点变换求凸包或调用OBB函数,注意初始化值勿用numeric_limits::max(),镜像时确保变换矩阵旋转部分已归一化,关键在坐标系一致性和包围盒初始化兜底。

如何用分离轴定理(SAT)判断两个世界坐标系下的AABB是否相交
直接结论:如果两个AABB都在同一世界坐标系下,只需逐轴比较投影区间是否重叠——这是最简、最稳、最快的方式。别被“三维”“坐标系变换”吓住,核心就是6个轴(x/y/z各一对)上的区间交集判断。
常见错误是试图把AABB顶点全部变换再求凸包、或调用通用OBB碰撞函数——既慢又容易因浮点误差漏判。真实项目里,只要AABB已处于同一坐标系(比如都经过模型→世界变换),AABB::intersects(const AABB& other) 就该是纯轴对齐的6次 max(minA, minB) 判断。
- 假设A的包围盒范围是
[minA.x, maxA.x]、[minA.y, maxA.y]、[minA.z, maxA.z],B同理 - 相交当且仅当:
minA.x = minB.x && minA.y = minB.y && minA.z = minB.z - 注意:用
和 <code>>=而非和 <code>>,否则刚好贴边(如maxA.x == minB.x)会被误判为不相交
当两个AABB处于不同局部坐标系时,必须先统一到同一坐标系再判断
没有“跨坐标系直接判AABB相交”的捷径。所谓“不同坐标系”,本质是各自有独立的 transform(4×4矩阵)。你不能拿A的 min/max 直接和B的比——它们单位、方向、原点全不同。
正确做法只有一种:把其中一个AABB变换到另一个的坐标系下(推荐把B变换到A的世界系),再按上一节方法判断。关键不是“怎么变”,而是“变什么”:
立即学习“C++免费学习笔记(深入)”;
- 不要只变换8个顶点再重新计算AABB——这会放大误差,且AABB可能变歪(实际变成OBB)
- 应使用AABB-to-AABB的保守变换:对每个轴,分别计算变换后8个顶点在该轴上的投影极值,即
newMin.x = min(dot(transformedVertex[i], x_axis)),其中x_axis是目标坐标系的x基向量(通常是变换矩阵的第0列) - 更高效的做法是:用变换矩阵的旋转部分(3×3子矩阵)和位移,推导出新AABB的
min/max——见transformAABB(const glm::mat4& m, const AABB& src)实现逻辑
为什么不用GJK或通用OBB碰撞检测
因为没必要。GJK适合任意凸体,OBB适合旋转后的盒体,但AABB是OBB的特例(旋转为零)。一旦你强行套用这些算法:
- 性能开销大:GJK迭代收敛、OBB需计算15条分离轴(3+3+3×3),而AABB只需6次标量比较
- 数值不稳定:GJK在面共面、边共线时易震荡;OBB的轴叉积计算引入额外浮点误差
- 接口复杂:要传顶点数组、法向量、支持函数——而AABB只有6个float
除非你的“AABB”其实根本没对齐(比如美术导出时带了非零旋转),那它就不是AABB,该用OBB检测,但此时名字就该叫 OBB 而非 AABB。
容易被忽略的浮点陷阱和边界情况
实操中最常栽跟头的地方不在逻辑,而在浮点表示和初始化:
- 未初始化的AABB(如
min = {1e30, 1e30, 1e30},max = {-1e30, -1e30, -1e30})若忘记调用reset()或encapsulate(point),会导致min.x > max.x,后续所有相交判断恒返回false - 用
std::numeric_limits<float>::max()</float>初始化时,注意它是正无穷大,参与运算可能产生nan;建议用1e30f这类有限大值 - 当物体缩放为负(如镜像)时,变换后的AABB仍可用上述方法,但需确保变换矩阵提取的旋转部分已归一化——否则基向量长度不为1,投影会失真
真正麻烦的从来不是“怎么写”,而是“谁负责保证坐标系一致”和“谁来兜底未初始化的包围盒”。这两个问题不解决,算法再漂亮也白搭。


















