Step 1 of 5 · Reading · ~3 min
Read
Collision Response
Coulomb Friction
When two rigid bodies collide, the collision-response solver resolves two directions of contact separately: the normal direction (preventing interpenetration — the bodies push apart) and the tangent direction (friction — resisting relative sliding along the contact surface). This lesson is about correctly bounding the tangential impulse using Coulomb's friction model, the same model used in essentially every real-time physics engine (Box2D, Bullet, PhysX).
Why friction needs a limit
If you solved friction the same way you solve the normal constraint — "apply whatever impulse is needed to bring relative tangential velocity to zero" — you'd get infinite friction: two boxes barely touching would stick together as if welded, no matter how lightly they're pressed together. That's obviously wrong. Real friction can only resist sliding up to a limit that's proportional to how hard the surfaces are pressed together.
Coulomb's model formalizes this with a single coefficient, mu (μ): the maximum tangential (friction) force/impulse is μ times the normal force/impulse:
max_friction = mu * normal_impulse
A high-friction material (rubber on asphalt, μ ≈ 0.8–1.0) resists sliding hard. A low-friction material (ice, μ ≈ 0.05–0.1) barely resists it at all. A normal impulse of zero (bodies barely touching, or separating) means zero friction is possible — you can't have friction without some normal force holding the surfaces together.
Clamping to the friction cone
The solver first computes what tangential impulse would be needed to fully stop relative sliding at the contact (call it jt_desired — this is what the tangential velocity-constraint solve produces before any limiting). Then that value gets clamped into the range allowed by the current normal impulse:
If jt_desired is within [-cap, cap], friction is strong enough to fully arrest sliding — the contact behaves like static friction, "sticking." If jt_desired exceeds the cap in either direction, friction can only partially resist the motion — the contact slides, and you apply exactly cap (in the direction opposing the slide) rather than the full desired impulse. This clamp is what separates static friction (enough grip to stop sliding) from kinetic friction (sliding continues, but resisted) without needing two separate code paths — the same formula covers both regimes.
Why this is solved per-contact, iteratively
In a full engine, jn itself isn't known up front — it's the output of the normal-impulse solve, which typically runs as an iterative (sequential impulses) solver alongside the friction solve, contact by contact, sweep after sweep, because multiple contacts interact (e.g., a box resting on two contact points needs both points solved together to stay balanced). This exercise isolates just the clamping step so you can nail the math before wiring it into that iterative loop.
Things to watch
muis a pair property in real engines: each material carries its own μ, and the pair's effective value is combined from the two. Box2D, Bullet and PhysX all default to the geometric mean,mu = sqrt(mu_a * mu_b), which has the useful property that a single frictionless surface (μ = 0) makes the whole contact frictionless no matter what it touches. Here you are handed the already-combined value directly.- The sign of
jt_desiredmatters — friction opposes whichever direction relative sliding is happening in, so simply clamping (not taking an absolute value first) preserves the correct direction. jnshould never be negative (a normal impulse only ever pushes bodies apart, never pulls them together), so the capmu * jnis always non-negative, keeping the clamp interval well-formed.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…