Constraints, Contacts and Friction

Drop a wooden crate onto a floor in a game and it just… sits there. Boring? It shouldn't be. Nothing in the physics engine knows what a floor is. Gravity is still tugging the crate downward at every single frame, trying to shove it straight through the ground. The reason it rests, flat and calm, is that the engine is doing hidden, frantic bookkeeping — computing exactly the right pushes, thousands of times a second, so that the crate is stopped precisely at the surface, tilts back if you lean it, and doesn't slide off a gentle slope. That bookkeeping is the world of constraints, contacts and friction.

This is the machinery that holds a ragdoll's limbs onto its joints, keeps a stack of boxes from sinking into each other, lets a car's wheels grip a road, and stops a resting object from jittering itself to death. It's the quiet other half of rigid-body simulation — the first half being impulse-based response for things that bang together. Here we tackle the harder, subtler problem: things that must stay together, or stay apart, frame after frame.

A constraint is a rule about allowed motion

A constraint is a restriction on how bodies are permitted to move. We write it as a function of the positions that must stay zero:

C(x) = 0.

For a hinge that pins two bodies at a shared point, C might be "the gap between the two pin points"; keeping C = 0 keeps them pinned. But solving physics at the position level is awkward — positions are what we're trying to compute. The trick that runs almost every engine is to work one derivative down, at the velocity level. Differentiate C(x) = 0 with respect to time (chain rule):

\dot C = \frac{\partial C}{\partial x}\,\dot x = J\,v = 0.

Here v = \dot x is the stacked velocity of the bodies and J = \partial C / \partial x is the Jacobian of the constraint — a matrix whose rows say which combinations of velocity the constraint forbids. The equation Jv = 0 reads: "the velocity is not allowed to have any component that would change C." The solver's whole job is to find constraint forces (or impulses) that push v back onto the allowed set whenever the naked, unconstrained motion would violate it.

Two flavours: equality (joints) and inequality (contacts)

The single most important distinction is whether the constraint is an equality or an inequality.

A ball-and-socket joint fixes 3 translational DOFs (the two anchor points must coincide) but leaves all rotation free — a shoulder. A hinge additionally locks 2 rotational DOFs, leaving a single spin axis — a door or an elbow. A slider (prismatic joint) leaves exactly one translational DOF — a drawer. Each is just a different choice of which rows go into J.

Contacts are harder precisely because of the one-sidedness. At the velocity level a resting contact demands the normal velocity stay non-negative, J_n v \ge 0 (the surfaces may separate but not approach), and the normal impulse \lambda_n \ge 0 may only push. And they are linked: if the bodies are separating, the push must be zero; if there is a push, the bodies must be exactly touching. That "one or the other is zero" coupling is a complementarity condition — the source of all the difficulty, which we'll meet again shortly.

The contact manifold: why one point tips and four hold flat

Consider the humble worked example: a box resting on the ground. When the box's flat bottom face meets the flat floor, the true contact is an area, not a point. But a solver works with a finite set of points, so the collision system reduces that face-to-face touch to a small contact manifold — typically the 4 corners of the overlapping rectangle, each a contact point with its own outward normal n and its own non-penetration constraint.

Why four? Imagine the engine kept only one contact point, say under the box's centre. That single upward push balances gravity vertically — but it applies no torque to resist tipping. The tiniest sideways nudge, or even floating-point noise, gives the box a rotation that the lone contact cannot oppose, and it teeters and rolls like a plank balanced on a pencil. With four contacts spread to the corners, a tip lifts one side: the contacts on the far side press harder (their non-penetration constraints fight the downward corner), the net of the four pushes produces a restoring torque, and the box settles flat. A stable rest needs a manifold wide enough to generate the righting torque — points, plural, spread across the contact area.

Coulomb friction: the friction cone

Non-penetration handles the normal direction. Friction handles the tangential one — the sliding along the surface. The classical model is Coulomb friction: the tangential friction force is bounded by a multiple of the normal force,

\lVert f_t \rVert \;\le\; \mu\, f_n,

where \mu is the coefficient of friction. Geometrically this carves out a friction cone: the total contact force (f_n, f_t) must lie inside a cone whose half-angle is \arctan\mu around the normal. Two regimes live inside this rule:

The circular friction cone couples the two tangent directions in a way that's expensive to solve exactly, so real-time engines usually linearise it into a friction pyramid — a box (or a polygonal prism) that approximates the cone with a few flat faces. That turns the friction limit into simple per-axis clamps |f_{t}| \le \mu f_n the solver can enforce with a projection, at the cost of making friction very slightly direction-dependent (a hair stronger along the pyramid's diagonals).

Solving many constraints at once: the LCP and sequential impulses

A stack of boxes on a floor might have dozens of contacts, each with a non-penetration inequality and two friction limits, all coupled — pushing one contact changes the load on its neighbours. Written out, the normal-contact problem has exactly the complementarity structure we noticed earlier and forms a Linear Complementarity Problem (LCP):

J v \ge 0, \qquad \lambda \ge 0, \qquad (Jv)^\top \lambda = 0.

Read the three pieces together: the surfaces may only separate (Jv \ge 0), the contact impulse may only push (\lambda \ge 0), and for each contact at least one of the two is zero (either it's separating with no force, or in contact with a force). Add friction and it becomes a mixed LCP (equalities for joints, inequalities for contacts, box bounds for friction).

Solving a big LCP exactly every frame is too slow for real time. The workhorse instead is an iterative method: Projected Gauss–Seidel, known in the game-physics world as sequential impulses. Walk through the constraints one at a time; for each, compute the impulse that would satisfy that constraint in isolation, apply it immediately so later constraints see the updated velocities, and project the result back into its legal range — clamp the normal impulse to \lambda_n \ge 0, and clamp the friction impulse into [-\mu\lambda_n,\, \mu\lambda_n]. Sweep the whole list repeatedly; each pass nudges every constraint closer to satisfied, and after a handful of iterations the whole coupled system has converged to a consistent set of impulses.

Two housekeeping tricks: stabilisation and warm-starting

Working purely at the velocity level has a flaw: it only stops things from getting worse. If a box has already sunk a little into the floor (from a big timestep, or an accumulated error), keeping Jv = 0 just freezes it there — the penetration never heals, and joints slowly drift apart. The fix is Baumgarte stabilisation: feed a small fraction of the position error back into the velocity target, so instead of aiming for Jv = 0 the solver aims for

J v = -\frac{\beta}{\Delta t}\, C(x),

a gentle bias velocity (with 0 < \beta \le 1) that pushes the residual penetration back out over the next few frames. Modern engines often use a related "split impulse" or soft-constraint variant to avoid feeding that correction energy into the real velocity, but the idea is the same: nudge positions back to C = 0 without letting the drift build.

The second trick is warm-starting. A resting stack barely changes from one frame to the next, so the impulses it needed last frame are an excellent first guess for this frame. Storing each contact's accumulated impulse and reapplying it before iterating means the solver starts already close to the answer — a stack that would take dozens of iterations to settle from scratch holds rock-steady with only a few, because it remembers how it was standing.

A joint constraint is enforced by an iterative solver that runs a fixed, small number of passes per frame — it converges towards C = 0 but essentially never reaches it exactly. So a heavily loaded joint (a ragdoll hanging by one wrist, a long chain of links) will always show a tiny bit of stretch: the constraint is a spring that's very stiff but not infinitely so. More iterations tighten it; so does splitting a long chain's solve or using a direct solver for the joints. This is also why physics engines expose an "iteration count" or "solver quality" knob — it's literally how many Gauss–Seidel sweeps you're willing to pay for. Turn it down and chains go rubbery; turn it up and the CPU groans.

Three closely-related traps sink beginner rigid-body code:

1. One impulse pass is not enough. Because the contacts are coupled, a single sweep over them under-solves the stack — the bottom box gets pushed up, but by the time you've processed it the boxes above have already been resolved against stale velocities. The visible result is a stack that jitters, sinks, or slowly collapses. You must iterate the projected Gauss–Seidel sweep several times per frame (and warm-start) for a tall stack to stand still.

2. Too few contact points let a resting object wobble. Reduce a flat face-face rest to a single point and the object has no torque resisting tip — it teeters. A resting object needs a contact manifold of enough points, spread across the contact area, to be stable.

3. Friction is an inequality — clamp it. The single most common friction bug is treating it like an equality and applying whatever tangential impulse "kills" the sliding velocity. That can apply more force than the surface can supply, gluing objects to walls or letting them climb slopes they should slide down. You must clamp the tangential impulse to \pm\mu\lambda_n — the friction cone is a limit, not a target.