Computer Graphics · Animation

Sequential Impulse and the Contact It Solves One Pair at a Time

How a rigid-body contact constraint is solved as a sequence of per-contact impulses, why warm starting makes the solver converge, and what happens when too many contacts share one body.

When a character’s foot meets the ground, a solver must produce the impulses that stop the foot penetrating and keep it from sliding, and it must do so for dozens of contacts between dozens of bodies within a fixed time budget. The method that dominates real-time rigid-body simulation is sequential impulse: treat each contact as a small constraint, solve it one at a time with an impulse, and repeat the sweep several times. The method is an approximation of a global solve, and its quality hinges on details that look incidental, chief among them warm starting.

A contact is a one-dimensional constraint with an inequality

A contact between two bodies can push them apart but cannot pull them together, and it can apply tangential friction up to a bound. The normal direction is fixed by the collision, usually the shared normal of the touching surfaces, and the solver’s job is to find the scalar impulse along that normal that separates the bodies without violating the non-penetration inequality, plus a friction impulse in the tangent plane bounded by the normal impulse times the friction coefficient.

The impulse is a change in momentum, and it is related to the contact velocity through the effective mass of the two bodies at the contact point. For two bodies with masses and inertias, the effective mass in the normal direction is a scalar computed from the contact offset and the inverse inertias; a large effective mass means the contact is hard to accelerate, a small one means a light contact. The impulse that brings the normal velocity to zero is the target, and the solver computes it as the relative normal velocity divided by the effective mass, with the inequality clamping the accumulated impulse to be non-negative so the solver never pulls.

/* One normal-impulse solve for a single contact, with accumulated clamping.
   inv_mass and inv_inertia are the inverse mass/inertia tensors of the two
   bodies; r is the contact offset from each body's centre; bias drives
   positional error correction; the accumulated impulse is warm-started. */
static void solve_contact_normal(body_t *A, body_t *B, contact_t *c, float dt) {
    vec3 rA = c->p - A->pos, rB = c->p - B->pos;
    vec3 rnA = cross(rA, c->n), rnB = cross(rB, c->n);
    float k = A->inv_mass + B->inv_mass
            + dot(c->n, cross(A->inv_inertia * rnA, rA)
                      + cross(B->inv_inertia * rnB, rB));
    if (k < 1e-9f) return;                      /* effectively infinite mass */
    float vn = dot(B->vel - A->vel, c->n)
             + dot(B->ang_vel, rnB) - dot(A->ang_vel, rnA);
    float bias = c->bias;                        /* positional error / dt */
    float lambda = -(vn + bias) / k;             /* impulse this iteration */
    float old = c->accum;                        /* warm-started total */
    c->accum = fmaxf(old + lambda, 0.0f);        /* clamp: no tension */
    lambda = c->accum - old;
    apply_impulse(A, B, c->n * lambda, rA, rB);  /* opposite directions */
}

The inequality is what makes the problem different from a spring. A spring always applies a force proportional to displacement in both directions; a contact applies an impulse only when the bodies are approaching, and clamps to zero when they separate. The clamp makes the problem a linear complementarity problem, and the sequential solver’s per-contact clamp is what turns a global solve into a sequence of cheap one-dimensional solves.

Solving all contacts at once is the exact problem

The contacts of a scene are coupled: resolving one contact changes the velocities that the next contact sees, so the correct solution is a joint solve over all contacts at once, a matrix whose size is the number of contacts times the number of velocity components. That matrix is large, and for a scene with a stack of boxes it is also dense in the contact degrees of freedom, because every contact in a stack transmits force through the bodies to every other. The direct solve is cubic in the contact count and far too expensive for real time.

Sequential impulse replaces the joint solve with Gauss-Seidel iteration: sweep the contacts in some order, solve each independently given the current velocities, and repeat. One sweep is a poor approximation of the joint solve because each contact is resolved without knowing what its neighbours will do, but several sweeps converge toward the exact solution, and for the contact counts of a character or a modest stack, a handful of sweeps is enough that the residual is below the threshold of visibility.

The ordering is the same reproducibility question as in position-based constraint projection, and it has the same answer: fix the order, or parallelize by colouring contacts that do not share a body. Two contacts on disjoint body pairs are independent and can be solved in any order; contacts sharing a body are coupled and must be serialized. A deterministic build fixes both.

Warm starting is what makes the iteration converge

In a static scene, the previous frame’s contact impulses are almost the right answer for this frame. Sequential impulse exploits this by storing the accumulated impulse per contact and starting each solve from that value instead of from zero. That is warm starting, and it is the difference between a solver that needs many sweeps to find the resting impulse and one that needs two or three, because a resting stack already carries most of its impulse from the previous frame and only needs a correction for the motion since then.

The mechanism is visible in the accumulation. The solver stores c->accum, the running total impulse, and each iteration adds a delta. Starting c->accum from the previous frame’s value means the first iteration is already near the solution, and the deltas are small corrections. Without warm starting, every frame starts from zero and the solver must rebuild the full resting impulse from scratch, which for a stack of many boxes means the stack visibly settles and shifts at the start of each frame.

Warm starting fails when the contact is new. A foot that just touched the ground has no previous impulse, so the solver starts from zero for that contact and spends the first few sweeps building the impulse, during which the foot sinks slightly before the accumulated impulse catches up. That transient is the reason new contacts jitter on landing and settle over a frame or two, and it is bounded by the solver’s sweep count rather than removed.

Positional error is corrected separately from velocity

The impulse that stops the relative velocity does not remove the penetration that accumulated because the bodies were allowed to overlap for a frame. The solver adds a bias term to the target velocity that drives the bodies apart at a rate proportional to the penetration divided by the time step, so the position correction is folded into the velocity solve as a bias. The bias is a heuristic: it has no physical force behind it, and it is the main knob for how aggressively the solver expels penetrating bodies.

A bias too small leaves bodies visibly interpenetrating; a bias too large adds energy to the system and makes resting contacts bounce. The standard formulation scales the bias by a factor between zero and one, often called the Baumgarte parameter after the technique, and choosing it is a stability decision of the same kind as the explicit integration bound: too aggressive a correction introduces oscillation, too weak leaves error.

The bias also breaks momentum conservation. Because the position correction is not a real force, it changes the total momentum of the system slightly each frame, and for a stack of bodies the cumulative effect is a slow drift. The magnitude is set by the bias and the penetration, so a solver that keeps penetration small and bias modest drifts slowly enough that the drift is not visible over a scene’s lifetime.

Friction turns one solve into two

The tangential friction impulse is solved after the normal impulse, using the normal impulse the normal solve just produced. The friction bound is the normal impulse times the friction coefficient, and the friction solve is a clamp of the tangential velocity to zero subject to that bound: if the tangential velocity is small, the impulse cancels it and the contact sticks; if large, the impulse reaches the bound and the contact slides. The coupling is one-way, normal then friction, because the friction bound depends on the normal impulse but not vice versa.

That one-way coupling is an approximation of the true Coulomb cone, which couples the two directions through a single convex constraint. A solver that does the friction inside the same iteration as the normal, updating the bound as the normal grows, is more accurate and more expensive; the sequential approximation, normal fully solved then friction fully solved, is the standard compromise and is stable enough for the contact speeds of characters and props.

The friction coefficient enters as a single scalar, but real contact friction depends on the pair of materials, the relative velocity, and the normal force, and a single coefficient is a deliberate simplification. The visible consequence is that a single coefficient set for a character’s feet makes both the walk and the idle behave, while a sliding object with the same coefficient slides or sticks in ways a two-coefficient model would not; the solver trades material fidelity for the ability to run hundreds of contacts in real time.

Stacks are the stress test

A stack of boxes concentrates all the method’s weaknesses. Every box contacts its neighbours, so the contact graph is highly connected and the Gauss-Seidel sweep has no parallelism. The impulses propagate from the top of the stack to the bottom at one contact per sweep, so a tall stack needs as many sweeps as there are boxes before the bottom box feels the top’s weight; with too few sweeps the bottom contacts are under-resolved and the stack sags. And warm starting is what keeps a settled stack still, so the moment a stack is disturbed, the solver must rebuild the impulses, and the disturbance travels down the stack one sweep at a time.

The symptom of too few sweeps in a stack is a stack that slowly sinks or jitters, and it is not a bug in the contact solve but a budget decision: the solver was given fewer sweeps than the stack’s height demanded. The fix is more sweeps, a smaller time step, or a solver that groups contacts, and each of those is a real cost traded against the visual.

The method’s place in the pipeline is at the end of the physics update, producing corrected velocities and positions for the rigid bodies, which the skinning stage then renders. A character whose feet are simulated with sequential impulse contacts and whose spine is animated by blend layers is the normal case, and the two solvers meet at the root, where the contact impulses and the animation pose are combined into the final transform. Getting that combination wrong, applying the animation and the physics in the wrong order, is the difference between a character that stands on the ground and one that hovers above it.

References

  1. Iterative Dynamics with Temporal Coherence — Catto, Game Developers Conference 2005
  2. Box2D: A 2D Physics Engine — Catto
  3. Physics for Game Developers — Bourg and Bywalec, O Reilly, 2nd edition 2013