· 5 分钟阅读 · 1959 字

物体斜向滑动时摩擦变强:两个切向钳制不能代替摩擦圆盘

同一块地面上,物体沿接触切线轴滑动和沿对角线滑动,为什么可能受到不同强度的摩擦?一个容易通过单轴测试的实现,是分别把两个切向冲量钳制到 [-μJn, μJn]。两个分量各自没有越界,合成向量却能达到上限的 √2 倍。切线基只是描述接触面的坐标工具,这种实现却把坐标轴方向写进了材质行为。

木质滑块、圆形与方形边界的接触摩擦概念图

本文的独立 C# 接触探针固定相对切向速度 v=(2,2) m/s、有效逆质量和 K=1 kg⁻¹、摩擦系数 μ=0.5,以及已经由法向求解阶段给出的压缩冲量 Jn=2 N·s。于是切向冲量上限 R=μJn=1 N·s。逐轴钳制给出 (-1,-1),长度 1.414214;圆盘投影给出 (-0.707107,-0.707107),长度约 1。两者都减速,但前者超出了当前摩擦模型允许的合力预算。

约束的是合成冲量长度

这里采用各向同性库仑摩擦约束 |Jt|≤μJn,其中 Jt 是接触平面中的二维冲量。它是三维摩擦锥在固定 Jn 下的圆盘截面。若分别约束 Jt.x 与 Jt.y,可行域就变成外接正方形,四个角均在圆盘之外。错误并非钳制范围选小或选大,而是两个分量本来共享一份长度预算。

先暂时忽略转动惯量,只保留接触点相对切向速度与标量有效逆质量。消除滑动所需的未约束增量为 ΔJ=-v/K。从零累计量开始,本例候选冲量是 (-2,-2);其长度大于 1,应沿原方向缩回半径 1。修正后相对速度为 (1.292893,1.292893) m/s,方向保持不变且仍在滑动。若候选冲量本来就在圆盘内,直接接受即可,不应把所有非零冲量都归一化到边缘,否则很小的滑动也会被过量修正。

核心实现位于 .tmp/daily-labs/2026-10-30-1930/FrictionDisk.cs。Solve 返回新的累计切向冲量,Project 只负责可行域投影,WrongBox 保留为失败回归:

using System.Numerics;

public static class FrictionDisk
{
    // 仅处理各向同性、两轴有效逆质量相同的接触切片。
    public static Vector2 Project(Vector2 candidate, float radius)
    {
        if (!float.IsFinite(candidate.X) || !float.IsFinite(candidate.Y) ||
            !float.IsFinite(radius) || radius < 0f)
            throw new ArgumentOutOfRangeException(nameof(candidate));
        double length = Math.Sqrt((double)candidate.X * candidate.X +
                                  (double)candidate.Y * candidate.Y);
        return length <= radius ? candidate : candidate * (float)(radius / length);
    }

    public static Vector2 Solve(Vector2 velocity, float inverseMassSum,
        float friction, float normalImpulse, Vector2 accumulated)
    {
        if (!float.IsFinite(inverseMassSum) || inverseMassSum <= 0f ||
            !float.IsFinite(friction) || friction < 0f ||
            !float.IsFinite(normalImpulse) || normalImpulse < 0f)
            throw new ArgumentOutOfRangeException(nameof(inverseMassSum));
        float radius = friction * normalImpulse;
        Vector2 candidate = accumulated - velocity / inverseMassSum;
        return Project(candidate, radius);
    }

    public static Vector2 WrongBox(Vector2 candidate, float radius) =>
        Vector2.Clamp(candidate, new Vector2(-radius), new Vector2(radius));
}

长度使用 double 中间值计算,避免两个较大的有限 float 分量在平方时先溢出;最终冲量仍以 float 存储。负摩擦系数、负法向冲量、非有限输入和非正有效逆质量均通过异常拒绝。两个完全静态物体不应进入这个接触速度修正入口。μJn 乘积若溢出,投影入口也会拒绝,调用层应将其视为数据合同失败。

固定滑动输入下的圆盘与方形可行域,以及累计冲量提交关系

旋转切线基后,摩擦不应换一种材质

保持世界速度不变,把切线基旋转 45°,原来的 (2,2) 可以表示成 (2√2,0)。逐轴钳制现在只消耗一个分量,输出长度变成 1;再转换回原坐标,其方向与圆盘结果一致。此前长度 1.414214 的结果因此变成约 1,变化只来自坐标基的选择。圆盘投影则与平面旋转可交换:先旋转候选量再投影,等价于先投影再旋转。

这使旋转不变性成为比单个数值断言更有效的回归合同。探针 Program.cs 用固定种子 193030 生成 10,000 组候选冲量、圆盘半径与旋转角;候选分量位于 [-10,10),半径位于 [0.1,2.1)。每组分别比较“直接投影”和“旋转、投影、逆旋转”,圆盘的最大向量差为 3.3717478e-7 N·s,逐轴方形的最大差为 2.9680495 N·s。圆盘长度超限的最大 float 残差为 2.3841858e-7 N·s,回归按数值容差判断。

如果设计确实需要冰面沿纹理方向更滑,应显式定义材质方向与各向异性约束,再把它变换到接触基。直接使用切线坐标中的正方形,相当于让临时基决定摩擦方向;基生成器稍作调整,物体行为就会改变。碰撞检测、求解器和材质系统之间需要传递的,是法线、有效质量、摩擦参数及其方向合同,而不是一个含义不明的“两个轴最大值”。

迭代器必须投影累计量

顺序冲量求解还会暴露另一种错误:每一轮只钳制新增冲量,再把它叠到旧值上。固定旧累计量 (0.8,0)、本轮候选增量 (0.8,0.8)、上限 1,单独投影新增量后再相加,累计长度达到 1.6647435 N·s。每轮增量合法并不能保证累计结果合法。

当前实现先构造 candidate=accumulated-v/K,投影得到新累计量;调用者只把两者的差值施加到物体速度:

Vector2 oldImpulse = accumulated;
Vector2 nextImpulse = FrictionDisk.Solve(
    relativeVelocity, inverseMassSum, mu, normalImpulse, oldImpulse);
Vector2 deltaImpulse = nextImpulse - oldImpulse;
relativeVelocity += deltaImpulse * inverseMassSum;
accumulated = nextImpulse;

回归把已含旧冲量效果的速度固定为 (-0.8,-0.8):施加差值后,再求解一轮的累计量保持不变,误差小于 1e-6 N·s;错误重施加完整累计量后,下一轮冲量偏移超过 0.1 N·s。这让重复记账能够被速度状态回归直接捕获。

同一迭代步中的 relativeVelocity 必须已经包含旧累计量的速度效果;若使用跨帧热启动,也要先应用热启动冲量,再进行这一更新。把 nextImpulse 每轮完整重施加一次,会重复记账。法向累计冲量随后下降时,摩擦半径也会缩小,旧切向累计量需要重新投影;差值可以撤回先前施加的部分摩擦,这不等于制造一个新的摩擦预算。

当天执行:

dotnet build $WORK_DIR/FrictionDiskLab.csproj -c Release
dotnet run --project $WORK_DIR/FrictionDiskLab.csproj -c Release --no-build

Release 构建退出码 0,零警告、零错误,耗时 2.42 s;运行退出码 0,23 项检查通过、失败 0、跳过 0,探针内部耗时 47.2291 ms。检查覆盖对角超限、圆盘内部与边界、零法向载荷、零摩擦、累计量提交、非法输入及 10,000 组旋转回归。上述耗时只记录本次执行,不用于宣称求解性能。

这个切片证明了标量有效质量条件下的投影与累计合同,尚未接入完整刚体引擎。偏心接触包含转动惯量后,两切向有效质量一般形成矩阵;圆盘仍描述各向同性摩擦的可行域,但欧氏径向投影未必是该质量度量下的精确局部最优解。多接触耦合、静动摩擦切换和收敛策略需要另外验证。选择圆盘首先是保住材质预算与坐标不变性;是否需要块求解器,则由接触质量矩阵和收敛误差决定,不能靠继续调小两个独立钳制阈值解决。