Ammo.js Constraint Solver and Cyclic Dependencies
This article explores how ammo.js, a JavaScript port of the Bullet physics engine, manages cyclic dependencies within complex, heavily constrained structures. We examine the core mechanics of its iterative Sequential Impulse (SI) solver, how it resolves conflicting joint demands without falling into infinite loops, and the optimization techniques used to maintain simulation stability in highly articulated systems.
The Sequential Impulse Solver
At the heart of ammo.js lies a Projected Gauss-Seidel (PGS) solver, implemented as a Sequential Impulse (SI) solver. Unlike direct global solvers that attempt to solve the entire system of constraints simultaneously using complex matrix inversions, the SI solver operates locally and iteratively.
In a system with cyclic dependencies—such as a closed loop of joints (e.g., a ragdoll holding its own feet or a closed mechanical linkage)—direct solvers can become computationally expensive or mathematically unstable due to over-determined or conflicting constraints. The SI solver bypasses this by visiting each constraint individually, calculating the impulse required to satisfy that single constraint, applying it immediately to the connected rigid bodies, and then moving to the next constraint.
Resolving Cyclic Loops via Iterative Relaxation
Because the solver updates the velocities of rigid bodies immediately after processing each constraint, the effects of an impulse applied at one joint instantly propagate to neighboring joints.
When cyclic dependencies are present: 1. Local Resolution: The solver resolves Constraint A, which slightly perturbs the state of Constraint B. 2. Propagation: When the solver evaluates Constraint B, it accounts for the new velocities introduced by Constraint A. 3. Feedback Loop: By the time the solver completes a full pass and returns to Constraint A, the feedback from the entire cycle has propagated back.
This process is repeated over multiple iterations per physics step
(defined by the numIterations parameter). Rather than
finding an exact mathematical solution to the entire loop at once, the
solver “relaxes” the system toward a stable state. With enough
iterations, the velocities of the bodies in the cyclic structure
converge to a state that satisfies all constraints simultaneously.
Key Stabilization Mechanisms
To prevent cyclic dependencies from causing infinite jitter, stretching, or physical explosions, ammo.js employs several mathematical stabilization techniques:
1. Constraint Force Mixing (CFM)
In highly constrained cyclic structures, it is common to have redundant or mathematically impossible configurations (e.g., three rigid bars connected in a triangle where the joint limits conflict). CFM adds a small, positive value to the diagonal of the constraint matrix. This effectively softens the constraints, transforming hard, unyielding joints into slightly elastic ones. CFM ensures the system remains mathematically solvable, preventing division-by-zero errors and allowing conflicting cycles to resolve without exploding.
2. Error Reduction Parameter (ERP)
ERP dictates how much of the positional joint error (displacement) is corrected in each physics step. Because cyclic structures easily accumulate positional drift, ERP works alongside CFM to slowly pull drifted bodies back into alignment over several frames, rather than trying to correct the entire error in a single frame, which would introduce massive, destabilizing forces into the cycle.
3. Warm Starting
At the beginning of each physics step, ammo.js does not start solving from scratch. Instead, it “warm starts” the solver by applying a fraction of the impulses calculated in the previous frame. For cyclic structures, which tend to maintain similar stress patterns from frame to frame, warm starting provides an excellent initial guess. This significantly accelerates convergence and keeps cyclic structures stable even with a lower iteration count.
Performance and Limitations
While the iterative nature of ammo.js allows it to handle cyclic dependencies robustly without crashing, heavily constrained structures can still suffer from “softness” or stretching if the solver cannot converge quickly enough.
If a cyclic system appears loose or unstable, stability can be
improved by: * Increasing Solver Iterations: Giving the
SI solver more passes to propagate impulses through the cycles. *
Substepping: Reducing the physics time step
(fixedTimeStep) to limit the amount of error that
accumulates between solver runs. * Reducing Mass
Ratios: Avoiding large differences in mass between connected
bodies within the cycle, as extreme mass ratios slow down impulse
propagation and hinder convergence.