· 5 分钟阅读 · 1888 字

两个动态物体碰撞:等大冲量不等于相同速度变化

质量 1 kg 的圆形物体以 4 m/s 撞向静止的 3 kg 物体。若碰撞回调给双方各做一次“沿法线反射速度”,轻物体变成 −4 m/s,重物体仍然不动,总动量从 +4 变成 −4 kg·m/s。画面已经分开,封闭系统却凭空得到了一次外部作用。

对于恢复系数为一的正碰,正确结果是轻物体 −2 m/s、重物体 +2 m/s。两者得到大小相同、方向相反的冲量,但轻物体改变了 6 m/s,重物体只改变 2 m/s。将反射函数从静态墙面搬到可推动物体时,缺失的正是另一方的有限质量与速度响应。

大小不同的圆形物件与反向箭头表达碰撞冲量的版画概念图,非运行截图

先约束相对速度,再分配双方变化

本文用独立 C# 工程验证二维、无摩擦、只有平动的瞬时单接触。位置以米计,速度以米每秒计,质量以千克计。接触法线 n 为单位向量,从 A 指向 B;相对法向速度定义为 g = dot(vB-vA,n),因此 g 小于零表示接近。这个方向约定与“墙面朝外”的反射接口不同,不能只替换公式而保留旧法线。

令双方逆质量分别为 wA、wB,恢复系数为 e。向 B 施加 +j n,同时向 A 施加 −j n,速度变为:

vA' = vA - j wA n
vB' = vB + j wB n
g'  = g + j (wA + wB)
要求 g' = -e g
因此 j = -(1+e)g / (wA+wB)

冲量单位是 kg·m/s,乘逆质量才得到速度变化。公式的分母不是双方质量之和,而是双方对同一冲量产生的速度响应之和。只在 A 上使用 B 的质量,或者双方各自求一次冲量,都不能保证使用同一接触约束。

固定案例取 n=(1,0),完整输入速度为 A=(4,2)、B=(0,−1)。当 e=1 时,j=6 kg·m/s,结果 A=(−2,2)、B=(2,−1)。无摩擦约束只改变法向分量,两者切向速度分别保持 2 与 −1。下图的箭头只编码 X 方向速度,不把圆形概念物件当作碰撞几何实测。

固定质量和速度下的冲量分配、动量对照与分离边界

一次求解交付双方速度

实现位于 .tmp/daily-labs/2026-10-26-2000/Impulse.cs,调用链为 Program → ContactImpulse.Solve → V.Dot。求解器返回完整的双方结果与冲量;调用方应在同一物理步内应用这对结果,不能让 A 的碰撞回调先改完速度,再让 B 用已经变更的输入重算同一接触。

public readonly record struct V(double X, double Y)
{
    public static V operator +(V a, V b) => new(a.X + b.X, a.Y + b.Y);
    public static V operator -(V a, V b) => new(a.X - b.X, a.Y - b.Y);
    public static V operator *(V a, double s) => new(a.X * s, a.Y * s);
    public double Dot(V b) => X * b.X + Y * b.Y;
    public bool Finite => double.IsFinite(X) && double.IsFinite(Y);
}

public static class ContactImpulse
{
    public static (V A, V B, double J) Solve(
        V a, V b, V normal, double inverseA, double inverseB, double restitution)
    {
        if (!a.Finite || !b.Finite || !normal.Finite ||
            Math.Abs(normal.Dot(normal) - 1) > 1e-10 ||
            !double.IsFinite(inverseA) || !double.IsFinite(inverseB) ||
            inverseA < 0 || inverseB < 0 ||
            !double.IsFinite(restitution) || restitution < 0 || restitution > 1)
            throw new ArgumentOutOfRangeException(nameof(normal));

        double sum = inverseA + inverseB;
        double speed = (b - a).Dot(normal);
        if (!double.IsFinite(sum) || !double.IsFinite(speed))
            throw new ArithmeticException();
        if (sum == 0 || speed >= 0)
            return (a, b, 0);

        double impulse = -(1 + restitution) * speed / sum;
        V nextA = a - normal * (impulse * inverseA);
        V nextB = b + normal * (impulse * inverseB);
        if (!double.IsFinite(impulse) || !nextA.Finite || !nextB.Finite)
            throw new ArithmeticException();
        return (nextA, nextB, impulse);
    }
}

逆质量为零表示求解器不修改该对象的速度,并不表示它的速度输入必须为零。这样可表示静止墙,也可表示由外部轨迹驱动的运动学表面;后者能与动态物体交换能量,不能再要求“双方有限质量动能守恒”。两边逆质量都为零时没有可响应自由度,直接返回原速度,避免除零。

g≥0 时同样返回原值。接触记录仍然存在,并不意味着双方还在接近;如果把已经分离的速度再代入恢复公式,会得到负冲量,把物体拉回去。这里的提前返回也覆盖切向擦过的零法向速度。法线长度、非负逆质量、有限输入与 e∈[0,1] 都在入口校验;中间量或结果溢出则显式失败,不把无穷速度提交给后续积分。

守恒检查要覆盖质量比与接触方向

Program.cs 中的 Check 与 Reject 覆盖固定案例、等质量速度交换、完全非弹性碰撞、静态 B、双方静态、分离、擦过,以及非法法线、负逆质量、非有限输入与超范围恢复系数。错误对照保留“双方独立反射”所得的 −4,与正确总动量 +4 直接比较。

额外 10,000 组使用种子 20261026,随机旋转法线,质量分别落在 [0.1,10) kg,接近速度落在 [0.01,20.01) m/s,恢复系数落在 [0,1)。每组同时验证二维动量、反弹后的相对法向速度,以及动能损失公式:ΔK=(1-e²)g²/[2(wA+wB)]。随机切向速度使测试不会只验证水平直线上的标量公式。

当天在 .NET 9 Release 下执行,$LAB_ROOT 表示本文探针目录:

$ cd $LAB_ROOT
$ dotnet build ImpulseLab.csproj -c Release --nologo
Build succeeded. 0 Warning(s), 0 Error(s)
Time Elapsed 00:00:09.47
exit code: 0

$ dotnet run --project ImpulseLab.csproj -c Release --no-build
passed=21 failed=0 skipped=0 samples=10000 elapsed_ms=78.3754
fixed_input: massA=1 massB=3 vA=(4,2) vB=(0,-1) e=1
output: j=6 vA=(-2,2) vB=(2,-1)
normal_momentum: before=4 after=4 wrong=-4
max_momentum_error=5.859285502108464e-14
max_restitution_error=2.4868995751603507e-14
max_energy_loss_error=1.0231815394945443e-12
exit code: 0

三类最大误差分别以 kg·m/s、m/s 和焦耳计;它们是双精度有限样本范围内的观测值,不能作为引擎单精度接入的容差直接复制。耗时记录只证明检查实际执行,没有测量物理引擎吞吐。e=0 的固定正碰另得到双方同为 1 m/s:相对法向运动消失,总动量仍为 4,法向动能从 8 J 降到 2 J。恢复系数控制能量损失,不控制冲量是否成对。

偏心接触会改变分母

当前接口适合只有平动自由度的圆盘、粒子或受约束游戏对象。一般刚体在偏离质心处受冲量还会旋转,此时接触点速度包含角速度贡献,有效逆质量也包含惯量与力臂项。只把输入换成接触点速度,却仍用 wA+wB 作分母,会低估系统对冲量的响应;必须一起扩展角动量更新和有效质量计算。

接触生成负责提供同一时刻的法线和状态,求解器负责同时改变双方速度,积分器再消费更新后的速度。穿透修正、持续接触的静止阈值、多点迭代与摩擦都不由这条单接触恢复公式承担;已有物理引擎处理接触时也不能再叠加一次本文冲量。轻重物体能否正确互推,验收对象应是成对状态变化及其守恒关系,单看反弹方向正确无法证明响应正确。