From a71f6b88f3fe6852b9e60e86de4f3a0b8ea8ccf2 Mon Sep 17 00:00:00 2001 From: Haixuan Xavier Tao Date: Mon, 27 Jul 2026 17:53:55 +0200 Subject: [PATCH] Apply the contact cfm_factor to normal rows only, not friction The PGS sweep multiplied every contact row by cfm_factor, including the friction tangents. Rapier applies the soft-constraint cfm only to the normal part (ContactConstraintTangentPart::solve takes no cfm_factor); softening the tangents multiplies the effective friction coefficient by cfm_factor. At an overdamped contact config (natural_frequency 30 Hz, damping_ratio 5, dt 1.25 ms) cfm_factor is ~0.02, so mu=1.0 colliders behaved like mu~0.03: statically loaded feet on a humanoid slid steadily and a passive stand toppled. Measured (G1 passive stand): tangential slide insensitive to contact stiffness before the fix, reduced ~15% by this change in isolation, and the effective mu returned to the collider value (verified via a per-row impulse dump: tangent impulses reach mu*N instead of ~0.02*N). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01B7NC7U2wDx2tSmT9mkeF9h --- .../dynamics/multibody/contact_constraints.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src_rbd_shaders/dynamics/multibody/contact_constraints.rs b/src_rbd_shaders/dynamics/multibody/contact_constraints.rs index 922015a..d9e8daf 100644 --- a/src_rbd_shaders/dynamics/multibody/contact_constraints.rs +++ b/src_rbd_shaders/dynamics/multibody/contact_constraints.rs @@ -784,10 +784,16 @@ pub fn gpu_mb_solve_contact_constraints( } let rhs_total = j_dot_v + cons.rhs; - // CFM-factor form (rapier's `*ContactConstraintNormalPart::generic_solve`): - // `new = cfm_factor · (impulse − r · Δvel)`. `cfm_factor < 1` provides the - // soft-constraint compliance that keeps resting contacts from jittering. - let raw_imp = cons.cfm_factor * (cons.impulse - cons.inv_lhs * rhs_total); + // CFM-factor form (rapier's `ContactConstraintNormalPart::generic_solve`): + // `new = cfm_factor · (impulse − r · Δvel)`. Normal rows ONLY — rapier's + // `TangentPart::solve` takes no cfm_factor; softening friction scales the + // effective μ by cfm_factor. + let unsoft_imp = cons.impulse - cons.inv_lhs * rhs_total; + let raw_imp = if cons.kind == MB_CONTACT_KIND_TANGENT { + unsoft_imp + } else { + cons.cfm_factor * unsoft_imp + }; // Normal: clamp to ≥ 0 (no separation impulse). Friction tangent: // clamp to `±μ · normal_impulse` — looks up the paired normal slot