🌌 计算机图形学奇谈

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 条轴

  1. A 的局部 3 轴:A₁, A₂, A₃
  2. B 的局部 3 轴:B₁, B₂, B₃
  3. A×B 的轴对组合叉积(9 条):A₁×B₁, A₁×B₂, ..., A₃×B₃

这些轴,足以穿透一切迷雾,找出可能的分离空间。


🧮 实现步骤(JavaScript)

以下是一个简化的伪代码逻辑(实战中建议搭配 gl-matrixthree.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 动画演示?

相关推荐
fs哆哆11 分钟前
在VB.net中,函数:列数字转字母
java·服务器·前端·javascript·.net
Hilaku1 小时前
别再手写i18n了!深入浏览器原生Intl对象(数字、日期、复数处理)
前端·javascript·代码规范
每天吃饭的羊1 小时前
强制缓存与协商缓存
前端
缘来小哥1 小时前
Nodejs的多版本管理,不仅仅只是nvm的使用
前端·node.js
陈随易1 小时前
Vite和pnpm都在用的tinyglobby文件匹配库
前端·后端·程序员
LeeAt1 小时前
还在为移动端项目组件发愁?快来试试React Vant吧!
前端·web components
鹏程十八少1 小时前
4. Android 用户狂赞的UI特效!揭秘折叠卡片+流光动画的终极实现方案
前端
心在飞扬2 小时前
AI开发应用 02-nodejs快速入门
javascript
Cache技术分享2 小时前
141. Java 泛型 - Java 泛型方法的类型擦除
前端·后端
YGY_Webgis糕手之路2 小时前
OpenLayers 综合案例-基础图层控制
前端·gis