不能只靠一个公式算出碰撞后速度,必须拆解为法向和切向分别处理:法向用恢复系数e计算冲量jₙ实现反弹,切向根据静摩擦力上限μₛ|jₙ|裁剪理想冲量jₜᵢdₑₐₗ以判断是否滑动。

怎么用 C++ 实现带摩擦力和恢复系数的二维刚体碰撞响应
直接说结论:不能只靠一个公式算出碰撞后速度,必须拆解为法向(沿接触面法线)和切向(沿接触面切线)两个方向分别处理,且切向要判断是否发生滑动——这决定了静摩擦力还是动摩擦力起作用。
核心难点不在数学推导,而在物理逻辑分支:恢复系数 e 只约束法向相对速度的反弹比例;而切向速度变化由摩擦力冲量决定,该冲量上限受静摩擦力最大值限制(即 μ * |normal_impulse|),一旦超出就转为滑动,改用动摩擦系数 μ_k 计算。
- 常见错误是直接对总相对速度乘
e,或忽略切向摩擦力是否饱和 - 典型使用场景:2D 物理引擎(如 Box2D 简化版)、小球撞斜坡、带旋转的刚体碰撞(本文先不考虑角动量耦合)
- 若物体质量差异极大(如墙质量设为
INFINITY),需在公式中令对应质量趋于无穷以简化
法向速度更新:恢复系数 e 怎么正确代入
法向方向唯一决定反弹强弱。设两物体 A、B,碰撞前法向速度分量为 vA_n、vB_n(注意符号:朝向彼此为负),法向单位向量为 n,质量分别为 mA、mB。
关键不是“乘 e”,而是先算出使法向相对速度反转并衰减所需的冲量大小:j_n = -(1 + e) * (vB_n - vA_n) / (1/mA + 1/mB)。这个 j_n 就是法向冲量,再分别加到两物体速度上:vA_new += j_n * n / mA,vB_new -= j_n * n / mB。
-
e = 0时,j_n仅消除相对接近速度,无反弹 -
e > 1合理(能量输入,如爆炸式碰撞),但多数仿真中限制在[0, 1] - 若
vB_n - vA_n >= 0(已分离或静止),不应触发碰撞响应——漏掉这个判断会导致“自碰撞”抖动
切向速度更新:为什么不能直接用 μ 乘法向冲量
切向冲量 j_t 的目标是抑制相对滑动,但它有上限:静摩擦力能提供的最大冲量是 μ_s * |j_n|。所以必须先算出“理想静摩擦冲量”——即把切向相对速度减为零所需冲量:j_t_ideal = -(vB_t - vA_t) / (1/mA + 1/mB)。
然后裁剪:j_t = clamp(j_t_ideal, -μ_s * |j_n|, μ_s * |j_n|)。若 |j_t_ideal| ,说明静摩擦足以锁死切向运动(纯滚动/无滑动);否则取边界值,进入滑动状态,此时应改用动摩擦系数 μ_k(通常 μ_k )重新计算耗散——但注意:滑动时切向冲量大小固定为 μ_k * |j_n|,方向与相对滑动方向相反。
- 误用
μ_s处理所有情况,会导致本该滑动的接触点“粘住”,产生虚假旋转或卡顿 - 切向单位向量
t必须与n正交,且按右手系保持一致(例如n=(nx, ny),则t=(-ny, nx)) - 若启用旋转,切向冲量还会产生力矩,需额外更新角速度;本文仅平动,暂不涉及
inertia或angular_velocity
C++ 实操:一个最小可运行的碰撞响应函数片段
以下代码假设已知碰撞点、法向 n、两物体质量与速度,且不做旋转、不迭代求解(单次碰撞):
struct Vec2 { float x, y; };
Vec2 operator+(const Vec2& a, const Vec2& b) { return {a.x+b.x, a.y+b.y}; }
Vec2 operator*(const Vec2& v, float s) { return {v.x*s, v.y*s}; }
float dot(const Vec2& a, const Vec2& b) { return a.x*b.x + a.y*b.y; }
<p>void resolveCollision(Vec2& vA, Vec2& vB,
float mA, float mB,
const Vec2& n, float e,
float mu_s, float mu_k) {
// 法向投影
float vA_n = dot(vA, n), vB_n = dot(vB, n);
if (vB_n - vA_n >= 0) return; // 未接近,跳过</p><pre class="brush:php;toolbar:false;">float invMassSum = 1.0f/mA + 1.0f/mB;
float j_n = -(1.0f + e) * (vB_n - vA_n) / invMassSum;
// 切向向量(假设 2D,n 已归一化)
Vec2 t = {-n.y, n.x};
float vA_t = dot(vA, t), vB_t = dot(vB, t);
float j_t_ideal = -(vB_t - vA_t) / invMassSum;
float j_t_max = mu_s * fabsf(j_n);
float j_t;
if (fabsf(j_t_ideal) <p>}</p>
真正容易被忽略的是:这个函数只解决单次碰撞;真实引擎中,多个接触点、顺序依赖、穿透修正(position correction)和迭代求解(如 SI 或 Gauss-Seidel)才是稳定性的关键——但那些已超出“速度更新”本身,属于系统级问题。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











