OBB + 分离轴定理(SAT)的碰撞检测之术
"世界本无碰撞,皆因空间不分。" ------ 图形学祖师爷·虚构版
🧠 引子:什么是 OBB?
我们都听说过 AABB(轴对齐包围盒),它就像你搬家打包用的纸箱------永远正对 X、Y、Z 轴。但 OBB(Oriented Bounding Box)不同,它像旋转过的收纳箱,可以适应物体的姿态,不拘泥于坐标轴的秩序。
OBB 的基本构成是:
- 一个中心点(Center)
- 三个正交单位向量(代表盒子的局部 X、Y、Z 轴方向)
- 每个方向上的"半长"(half-length)
所以,它不仅会转,还会摆,会扭,堪称三维空间中的"瑜伽高手"。
💥 问题:两个 OBB 怎么判断是否碰撞?
面对两个旋转中的物体,光靠目视显然不靠谱------你需要一位判官,它的名字叫:
🧪 分离轴定理(Separating Axis Theorem)
它告诉我们一句真理:
"若存在一条轴,使两个物体在该轴上的投影不重叠 ,则它们没有碰撞。"
换句话说,想知道两个盒子有没有撞上,只需要:
- 找出所有可能的分离轴
- 把两个盒子在这些轴上投影
- 看看有没有哪一条轴上的投影互不相交
如果有 → 安然无恙
如果没有 → 撞上了!
🔍 分离轴都有哪些?
对两个 OBB(记作 A 和 B),我们必须检验 15 条轴:
- A 的局部 3 轴:A₁, A₂, A₃
- B 的局部 3 轴:B₁, B₂, B₃
- A×B 的轴对组合叉积(9 条):A₁×B₁, A₁×B₂, ..., A₃×B₃
这些轴,足以穿透一切迷雾,找出可能的分离空间。
🧮 实现步骤(JavaScript)
以下是一个简化的伪代码逻辑(实战中建议搭配 gl-matrix
或 three.js
的向量类):
scss
function isOBBCollision(boxA, boxB) {
const axes = [];
// 1. 添加 A 和 B 的局部坐标轴
axes.push(...boxA.axes);
axes.push(...boxB.axes);
// 2. 添加各对轴的叉积
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
const cross = boxA.axes[i].clone().cross(boxB.axes[j]);
if (cross.lengthSq() > 1e-6) { // 排除零向量
axes.push(cross.normalize());
}
}
}
// 3. 检查所有分离轴
for (const axis of axes) {
if (!overlapOnAxis(boxA, boxB, axis)) {
return false; // 找到了分离轴,碰撞失败
}
}
return true; // 所有轴都有重叠,发生碰撞
}
关键的 overlapOnAxis
:
ini
function overlapOnAxis(boxA, boxB, axis) {
const projA = projectOBB(boxA, axis);
const projB = projectOBB(boxB, axis);
return projA.max >= projB.min && projB.max >= projA.min;
}
function projectOBB(box, axis) {
// 计算中心点在轴上的投影
const centerProj = box.center.dot(axis);
// 计算半径:把每个轴长度乘以在投影轴上的投影
let radius = 0;
for (let i = 0; i < 3; i++) {
const lengthOnAxis = Math.abs(box.axes[i].dot(axis)) * box.halfLengths[i];
radius += lengthOnAxis;
}
return {
min: centerProj - radius,
max: centerProj + radius
};
}
⚙️ OBB 的结构定义
arduino
const box = {
center: new Vector3(0, 0, 0),
axes: [ // 本地 x, y, z 三个单位向量
new Vector3(1, 0, 0),
new Vector3(0, 1, 0),
new Vector3(0, 0, 1),
],
halfLengths: [1, 2, 1] // 半长沿 x, y, z
};
你可以使用 THREE.Vector3
替代以上的向量类型,实际工程中建议封装成类。
🚀 性能优化建议
- 减少检测频率:动态物体之间使用"宽相碰撞"先过滤(如 Sphere 检查)。
- 分帧检测 :利用
requestIdleCallback
或 Web Worker 分担部分计算。 - 批量合并检测:适用于密集体积群组的整体处理。
🎭 彩蛋:用文学讲述碰撞
想象两个宇宙飞船,漂浮在三维太空中。它们不是矩形,不是球体,而是由工程师用 OBB 封装的三维实体。若它们交错而过,分离轴间一丝不交,那便是和平共处;若它们十五轴皆重,那便是命运的拥抱------一次壮烈的相撞。
🧩 总结
概念 | 说明 |
---|---|
OBB | 有向包围盒,可旋转的碰撞检测体 |
SAT | 判断碰撞是否发生的算法核心定理 |
分离轴数量 | OBB 对间共 15 条轴需判断 |
判断标准 | 任一轴无投影重叠 → 无碰撞 |
应用场景 | 精确三维碰撞检测、刚体模拟、AI导航 |
"图形学不仅是三角和像素,它是数字宇宙的哲学。"
------ 某无名开发者,在调试 SAT 崩溃后如此感慨
如何实现一个完整的三维 OBB-SAT 碰撞检测库、搭配 Three.js 动画演示?