Ammo.js Solver Iterations and Constraint Stability

This article explains how the solver iteration count directly influences the stability, behavior, and performance of physical constraints within an ammo.js physics world. You will learn the mechanics behind constraint solving, the consequences of low versus high iteration counts, and how to balance physics accuracy with real-time web performance.

Understanding the Constraint Solver in Ammo.js

Ammo.js, a direct WebAssembly port of the C++ Bullet Physics SDK, uses an iterative solver (specifically, a Projected Gauss-Seidel solver) to resolve constraints. Constraints include joints like hinges, sliders, springs, and point-to-point connections, as well as contact points during collisions.

Instead of solving all physical forces simultaneously using an expensive global matrix, ammo.js solves them sequentially. The solver loops through every constraint in the system multiple times per physics step, applying small impulse adjustments to satisfy the constraints. The “solver iteration count” is the exact number of times this loop runs during a single physics substep.

Direct Effects of Iteration Count on Constraints

1. Stiffness and Resistance to Stretching

In an ideal physics simulation, a joint holding two objects together is perfectly rigid. However, because ammo.js approximates this sequentially, constraints act like stiff springs. * Low Iteration Count: If the solver only runs a few times (e.g., 2 to 5 iterations), the calculations do not fully converge. Constraints will appear “soft” or “rubbery.” Under load, joints will stretch, sag, or drift apart. * High Iteration Count: Increasing the count (e.g., 15 to 30 iterations) allows the math to converge closer to the analytical solution. The joints become stiff, unyielding, and hold their configured limits and anchors with high precision.

2. Elimination of Physics Jitter and “Exploding” Joints

When multiple constraints are chained together—such as in a ragdoll, a rope, or a vehicle suspension system—the movement of one joint directly impacts the next. * The Feedback Loop: With too few iterations, correcting a constraint at the start of the chain introduces errors further down. On the next frame, correcting those errors introduces new errors back at the start. This feedback loop manifests as visual jitter, vibrating meshes, or catastrophic “explosions” where objects fly apart. * Achieving Equilibrium: Higher iterations allow these mutual forces to settle into an equilibrium during a single physics step, completely neutralizing jitter and stabilizing complex composite structures.

3. Handling High Mass Ratios

A classic stress test for any physics engine is connecting a very light object to a very heavy object (a high mass ratio). * At low iteration counts, the solver cannot successfully resolve the massive disparity in momentum. The light object will easily penetrate boundaries, stretch joints infinitely, or cause the heavy object to vibrate uncontrollably. * Elevating the solver iterations is mandatory to keep joints intact when heavy and light rigid bodies interact.

The Performance Trade-Off

While a high iteration count ensures rock-solid stability, it comes at a linear computational cost. Because ammo.js operates in single-threaded web environments (often running alongside heavy WebGL rendering), optimizing this value is critical:

To achieve stable constraints in ammo.js without sacrificing frame rate, use the following configuration rules:

  1. Set the Right Default: For standard games and simple scenes, a solver iteration count of 10 (often the default) is sufficient. For complex machinery, ropes, or ragdolls, increase this to 20 or 30.

  2. Configure via Code: You can adjust the iteration count directly on the physics world’s solver object:

    // Access the constraint solver and increase iterations
    const solver = dynamicsWorld.getConstraintSolver();
    solver.setNumIterations(20); 
  3. Decrease the Timestep Instead: If constraints are still unstable at 25 iterations, do not increase them further. Instead, increase the physics substeps or decrease the fixedTimeStep size in your dynamicsWorld.stepSimulation(timeElapsed, maxSubSteps, fixedTimeStep) loop. Running 10 iterations over two smaller time steps is often much more stable than running 20 iterations over one large time step.