How Warm Starting Accelerates Solver Convergence Across Sub-Steps in Box3D
Warm starting accelerates solver convergence in Box3D by reapplying cached contact and joint impulses from the previous physics tick as the initial guess for each sub-step, reducing the number of Gauss-Seidel iterations needed to reach equilibrium.
Box3D is a 3D physics engine that solves rigid body constraints through iterative methods. The solver runs multiple sub-steps per simulation tick to resolve contacts and joint constraints accurately. Warm starting leverages the temporal coherence of physics simulations by persisting impulse data across these sub-steps, dramatically improving stability and performance.
The Physics Sub-Step Loop
Box3D processes each simulation tick through a sequence of solver stages defined in src/solver.c. The engine executes the following operations during every sub-step:
- Integrate velocities – Update linear and angular velocities from external forces.
- Warm-start – Apply impulses cached from the previous tick.
- Solve – Perform Gauss-Seidel iterations to resolve constraints.
- Integrate positions – Update body transforms based on solved velocities.
- Relax – Perform optional extra iterations without bias.
The warm-start stage occurs immediately after velocity integration and before the main solver phase. This ordering ensures the solver begins with a physically plausible state rather than zero impulses.
Impulse Caching and Reapplication
Warm starting relies on persistent storage of constraint impulses. When enabled, Box3D caches normal and friction impulses at the end of each tick, then reapplies them at the start of the next sub-step.
Caching Impulses from Previous Ticks
In src/contact_solver.c (line 38) and related joint implementations, the engine stores impulse magnitudes within constraint structures:
float warmStartScale = world->enableWarmStarting ? 1.0f : 0.0f;
/* ... */
cp->normalImpulse = warmStartScale * mp->normalImpulse;
constraint->frictionImpulse.x = warmStartScale * b3Dot(manifold->frictionImpulse, tangent1);
The warmStartScale variable acts as a toggle. When world->enableWarmStarting is true, impulses are preserved at full magnitude; when disabled, the scale becomes zero and impulses reset.
Reapplying Impulses at Sub-Step Start
At the beginning of each sub-step, the solver invokes warm-start functions defined in src/solver.c (line 265):
b3WarmStartJointsTask(block, context); // Applies joint warm-starts
b3WarmStartContacts_Convex(block, context);
b3WarmStartContacts_Mesh(block, context);
These functions distribute the cached impulses back into the velocity constraints, providing the iterative solver with a non-zero initial guess that closely approximates the final solution.
Why Warm Starting Accelerates Convergence
Warm starting improves solver performance through four key mechanisms:
Better Initial Guess – By starting from the previous tick's impulses, the solver avoids the slow convergence associated with zero-initialization. The initial state is already close to the equilibrium solution.
Momentum Continuity – Physical configurations change minimally between sub-steps. Impulses that resolved penetration or friction in the previous step remain largely valid, maintaining continuity in the constraint resolution.
Reduced Oscillation – Reapplying previous impulses dampens high-frequency error that would otherwise cause the solver to "chatter" between iterations, stabilizing the Gauss-Seidel process.
Sub-Step Accumulation – Because Box3D runs multiple sub-steps per tick (often 4-8), warm-started impulses compound their stabilizing effect through each successive sub-step, carrying valid solution data forward through the entire simulation frame.
Enabling Warm Starting in Box3D
The warm-starting feature is controlled through the world API exposed in src/physics_world.c. By default, the engine enables warm starting, but you can toggle it at runtime:
/* Create a world */
b3WorldId world = b3World_Create();
b3World_EnableWarmStarting(world, true); // Enable warm starting
/* Simulate with 8 sub-steps and 2 solver iterations per sub-step */
for (int i = 0; i < 100; ++i) {
b3World_Step(world, 1.0f/60.0f, 8, 2);
}
/* Compare against disabled warm starting */
b3World_EnableWarmStarting(world, false);
b3World_Step(world, 1.0f/60.0f, 8, 2);
/* Note increased jitter and higher effective iteration requirements */
When disabled, the solver must converge from zero impulses every sub-step, requiring more iterations to achieve the same stability and often producing visible jitter in stacked objects.
Key Implementation Files
The warm-starting system spans several core files:
src/solver.c– Defines the sub-step ordering and orchestrates warm-start tasks at line 265.src/contact_solver.c– Implements warm-starting for contact constraints; handles impulse caching at line 38.src/joint.c– Dispatches warm-start operations for all joint types.src/physics_world.c– Stores the globalenableWarmStartingflag and provides the public API.src/world_snapshot.c– Serializes the warm-starting state when capturing world snapshots.
Summary
- Warm starting reuses impulse data from previous physics ticks to initialize the solver state.
- The system scales cached impulses by the
enableWarmStartingflag insrc/physics_world.c. - Functions like
b3WarmStartContacts_Convexandb3WarmStartJointsTaskreapply impulses at the start of each sub-step. - This technique reduces Gauss-Seidel iteration counts by providing a physically plausible initial guess.
- Warm starting minimizes solver oscillation and maintains momentum continuity across sub-steps.
- Disabling warm starting forces zero-initialization, increasing CPU cost and visual jitter.
Frequently Asked Questions
What happens if I disable warm starting in Box3D?
When disabled via b3World_EnableWarmStarting(world, false), the solver initializes all contact and joint impulses to zero at the start of every sub-step. This forces the Gauss-Seidel solver to converge from scratch each time, typically requiring more iterations to reach stability and producing noticeable jitter in resting contacts.
How does warm starting affect the solver iteration count?
Warm starting effectively reduces the number of iterations required for acceptable convergence. While the b3World_Step parameter still controls the maximum iteration count, warm-started simulations often reach stable solutions with fewer actual iterations, saving CPU cycles while maintaining accuracy.
Is warm starting data preserved across world snapshots?
Yes. According to src/world_snapshot.c, the enableWarmStarting flag is serialized when capturing world state. However, the specific impulse values cached in constraints are typically recalculated on the next step after restoration, ensuring the solver begins with valid data.
Can warm starting cause instability in certain scenarios?
Warm starting assumes temporal coherence between frames. In simulations with sudden violent impacts or teleporting objects, the cached impulses may no longer represent valid constraint solutions, potentially requiring an extra iteration or two to correct. However, the solver's relaxation phase usually compensates for these discontinuities without manual intervention.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →