Ammo.js stepSimulation Sequence of Events
When developing 3D applications with ammo.js (the Emscripten port of
the Bullet Physics library), the stepSimulation method is
the core driver of the physics world. This article breaks down the exact
sequence of internal events and phases that occur during a single call
to stepSimulation, including time integration, collision
detection, constraint solving, and interpolation.
When you call
dynamicsWorld.stepSimulation(timeStep, maxSubSteps, fixedTimeStep),
the engine executes the following precise sequence of events:
1. Sub-step Calculation and Time Accumulation
Ammo.js does not simulate physics using variable frame rates, as this
would cause unstable and non-deterministic behavior. Instead, it uses a
fixed time step. * Accumulate Time: The engine adds the
passed timeStep (usually the time elapsed since the last
render frame) to an internal time accumulator. * Determine
Sub-steps: The engine calculates how many fixed steps of size
fixedTimeStep (default is 1/60th of a second) are required
to match the accumulated time. * Clamp Steps: If the
required number of steps exceeds maxSubSteps, the engine
clamps the simulation to prevent the “spiral of death” (where physics
lag slows down rendering, causing even more physics steps to be
queued).
2. The Internal Single-Step Loop
For each calculated sub-step, ammo.js executes a single, self-contained simulation step. If the accumulated time is too small for a single sub-step, this loop is skipped, and the engine jumps straight to the interpolation phase. Within each individual sub-step, the following sequence occurs:
- Apply External Forces: The engine applies gravity, wind, and any user-defined forces or torques to all active rigid bodies in the world.
- Predict Unconstrained Motion: Based on current velocities and applied forces, the engine predicts where each rigid body would be at the end of the sub-step if there were no collisions or constraints. This is known as predictive integration.
- Perform Broadphase Collision Detection: The engine performs a rapid, coarse check to find pairs of objects whose Axis-Aligned Bounding Boxes (AABB) overlap. This filters out pairs of objects that are too far apart to collide.
- Perform Narrowphase Collision Detection: For each overlapping pair identified by the broadphase, the engine runs precise geometric algorithms (like GJK or EPA) to determine if they actually touch. If they do, the engine generates contact points, penetration depths, and collision normals, creating “contact manifolds.”
- Create and Solve Constraints (The Constraint Solver): The engine gathers all contact points and user-defined constraints (such as hinges, sliders, and joints) and passes them to the constraint solver (typically a Sequential Impulse Solver). The solver calculates the exact impulses required to resolve penetrations, handle friction, and keep joints from breaking.
- Integrate Transforms (Update Positions): The engine applies the calculated impulses to the velocities of the rigid bodies. It then integrates these velocities to calculate the final, actual positions and rotations of the bodies for the current sub-step.
- Clear Forces: The engine resets the accumulated forces and torques on the rigid bodies, preparing them for the next step.
3. State Interpolation
Once the sub-step loop completes, there is almost always a small remainder of time left in the accumulator that was not enough to trigger another full fixed step.
To prevent visual stuttering, ammo.js calculates an interpolation factor between 0 and 1 based on this remaining time. It then interpolates the positions and orientations of the rigid bodies between the previous physics state and the current physics state. This interpolated transform is what is exposed to your graphics renderer, ensuring smooth motion even when the rendering frame rate does not align perfectly with the physics simulation rate.