🚀 顶点-面碰撞检测之诗:用牛顿法追寻命运的交点

"一颗顶点,一张面,在四维时空里逐渐靠近,是否会碰撞?还是终将错过?"


🌌 引子:CCD 是什么?

连续碰撞检测(Continuous Collision Detection,简称 CCD) ,不是《漫威宇宙》的新组织,而是一个避免"穿模"现象的高级物理机制。

  • 普通的碰撞检测通常每帧检测一次位置,这叫 离散碰撞检测(DCD) ,容易错过高速运动中的碰撞。
  • CCD 则细致到每一帧之间的连续路径,确保即便对象高速飞驰,也不会偷偷"穿过"墙壁,毁了游戏体验。

今天我们聊的是 CCD 中的经典问题:

在一个时间区间 [t0, t1] 内,一个移动的点 是否会撞上一张移动的三角面


🧩 问题建模

想象你是一个顶点粒子,正在动感单车般穿越三维空间。与此同时,远处有一张缓缓移动的三角形大网,像三只冷酷无情的手指等待你落入陷阱。

我们需要求的是:有没有一个时刻 t(0 到 1 之间),你刚好撞在这张三角网格上?


📚 线性插值(Lerp)建模运动轨迹

我们假设顶点 P(t) 和三角形三个顶点 A(t), B(t), C(t) 都是线性运动的:

javascript 复制代码
function lerp(v0, v1, t) {
  return v0.map((v, i) => v + (v1[i] - v) * t);
}

在任意时间 t

  • P(t) = lerp(P0, P1, t)
  • A(t), B(t), C(t) 同理

🎯 顶点-面碰撞的数学条件(非公式解释)

顶点会撞上三角形面,等价于:

  1. 点 P(t) 落在 三角形 A(t), B(t), C(t) 所在平面上。
  2. P(t) 也位于该三角形内部(使用重心坐标检测)。

因此我们等价转化为:

求解一个函数 f(t),它表示 P(t) 和三角形面在时间 t 是否共面,当 f(t) = 0 时,即发生碰撞。


🔍 解方程:用牛顿法抓住那一瞬的命运

什么是牛顿法?

牛顿法是数值分析界的"福尔摩斯",专门用来寻找某个函数为零的点。给定一个初始猜测 t0,不断更新:

scss 复制代码
t_next = t - f(t) / f'(t)

只要选得好,几轮就能收敛!


✂️ + 区间裁剪(Interval Clipping)

为了避免牛顿法跳出 [0,1] 区间,我们在每轮迭代中做如下处理:

  • 如果 t 超出了 [t0, t1],我们就把 t 强行剪回来。
  • 并在迭代过程中维护一个收缩区间,像套娃一样越来越小,直到逼近根。

🧪 JavaScript 实现

ini 复制代码
function dot(a, b) {
  return a.reduce((sum, ai, i) => sum + ai * b[i], 0);
}

function cross(a, b) {
  return [
    a[1]*b[2] - a[2]*b[1],
    a[2]*b[0] - a[0]*b[2],
    a[0]*b[1] - a[1]*b[0]
  ];
}

function subtract(a, b) {
  return a.map((v, i) => v - b[i]);
}

function scalarTriple(a, b, c) {
  return dot(a, cross(b, c));
}

// f(t): 点与面之间的有符号体积(应为0)
function f(t, P0, P1, A0, A1, B0, B1, C0, C1) {
  const P = lerp(P0, P1, t);
  const A = lerp(A0, A1, t);
  const B = lerp(B0, B1, t);
  const C = lerp(C0, C1, t);
  return scalarTriple(subtract(P, A), subtract(B, A), subtract(C, A));
}

function df(t, eps, ...args) {
  return (f(t + eps, ...args) - f(t - eps, ...args)) / (2 * eps);
}

function findRoot(P0, P1, A0, A1, B0, B1, C0, C1, maxIter = 20, eps = 1e-6) {
  let t0 = 0.0;
  let t1 = 1.0;
  let t = 0.5;

  for (let i = 0; i < maxIter; i++) {
    const ft = f(t, P0, P1, A0, A1, B0, B1, C0, C1);
    const dft = df(t, 1e-5, P0, P1, A0, A1, B0, B1, C0, C1);

    if (Math.abs(ft) < eps) return t;

    if (dft === 0) {
      // 牛顿法无法推进,改为二分
      t = (t0 + t1) / 2;
    } else {
      const t_next = t - ft / dft;
      if (t_next < t0 || t_next > t1) {
        t = (t0 + t1) / 2;
      } else {
        t = t_next;
      }
    }

    // 更新搜索区间
    if (f(t, P0, P1, A0, A1, B0, B1, C0, C1) > 0) {
      t1 = t;
    } else {
      t0 = t;
    }
  }

  return null; // 未找到碰撞点
}

🧠 思维总结

  • CCD 就像是在"时间"这条轴上进行几何运算。
  • 用线性插值来表示时间参数化运动。
  • 牛顿法 + 区间裁剪,就像戴着安全带开快车,快速又稳当。
  • f(t) 是你和命运的距离,f'(t) 是你的速度方向,组合起来,找到你是否真的"撞墙"。

🏁 彩蛋:什么是"穿模"的哲学本质?

"穿模"其实是一个程序世界里的因果悖论:你本应该撞上去,但因为时间间隔太大,你直接"跳过"了那一帧。CCD,就是在帮你重新定义命运,让每一次物理接触都无所遁形。


✨ 下一步

  • 加入重心坐标检测,判断点是否落在三角形内(非仅共面)。
  • 支持面-面、边-边 CCD 检测。
  • 使用双精度 + 自适应精度(Shewchuk)避免数值不稳定。

如果你觉得这是一段严肃的数值计算,不妨换个角度想:

"在数字世界里,每一个点的移动,都是一场寻找归属感的旅程。"


如需更复杂版本(含边界检测、鲁棒数值包),欢迎继续追问!

是否需要我也把 重心坐标检测 部分和完整 CCD 判定逻辑封装成一个模块

相关推荐
JianZhen✓9 分钟前
现在在本地开发了一些代码A,又有了新需求要紧急开发代码B需要只上线代码B的代码,如何更好的处理这种情况
前端
郝学胜-神的一滴28 分钟前
Cesium绘制线:从基础到高级技巧
前端·javascript·程序人生·线性代数·算法·矩阵·图形渲染
蒙奇D索大1 小时前
【计算机网络】408计算机网络高分指南:物理层编码与调制技术精讲
java·前端·学习·计算机网络
无盐海1 小时前
CSRF漏洞攻击(跨站请求伪造攻击)
前端·csrf
慧一居士1 小时前
CSS3 全部功能点介绍,使用场景,对应功能点完整使用示例
前端
烛阴1 小时前
深入Lua包(Package)与依赖管理
前端·lua
IT_陈寒2 小时前
5个Vue3性能优化技巧,让你的应用提速50% 🚀(附实测对比)
前端·人工智能·后端
god002 小时前
chromium项目中添加源文件(BUILD.gn项目中添加源文件)
java·服务器·前端
快乐非自愿2 小时前
Vue 缓存之坑,变量赋值方式和响应式数据
前端·vue.js·缓存
Github掘金计划2 小时前
别再用 “臃肿监控” 了!这款轻量监控神器开源 3 月狂揽 1.3k Star!
前端·监控