How to Fix Jitter in Stacked Ammo.js Boxes

Stacked rigid bodies in Ammo.js (a JavaScript port of the Bullet physics engine) often exhibit a realistic but frustrating shaking or jittering effect. This article explains the underlying causes of this instability—such as constraint solver limitations, substepping mismatches, and collision margin issues—and provides actionable solutions to mitigate the jitter and achieve stable physics stacks.

What Causes Jitter in Stacked Ammo.js Boxes?

The jittering effect, often referred to as “physics creep” or “shaking,” is primarily caused by the iterative nature of the constraint solver.


How to Mitigate Stack Jitter

To achieve stable stacked boxes, you must configure the simulation parameters, step sizes, and rigid body properties correctly.

1. Configure Fixed Timesteps and Substepping

Never pass raw delta time from your render loop directly into the physics solver without proper substepping. The stepSimulation function requires a fixed time step and a maximum number of substeps.

// Example step simulation call
// stepSimulation(actualTimeElapsed, maxSubSteps, fixedTimeStep)
physicsWorld.stepSimulation(deltaTime, 10, 1 / 120);

Using a fixedTimeStep of 1/120 (120Hz) instead of 1/60 (60Hz) dramatically increases stack stability by calculating physics more frequently, reducing the distance boxes can penetrate between frames.

2. Increase Solver Iterations

You can increase the accuracy of the constraint solver by increasing the number of iterations. More iterations mean the solver has more passes to resolve the stack’s forces.

const solverInfo = physicsWorld.getSolverInfo();
solverInfo.set_m_numIterations(20); // Default is usually 10

Note: Increasing iterations improves stability but consumes more CPU.

3. Adjust Collision Margins

Ammo.js uses a small margin around shapes to improve collision detection performance. For box shapes (btBoxShape), the margin can cause rounded behavior at corners or unexpected gaps if misconfigured.

Set the margin explicitly on your box shapes, or rely on implicit margins if using modern versions of the engine:

const boxShape = new Ammo.btBoxShape(new Ammo.btVector3(halfWidth, halfHeight, halfDepth));
boxShape.setMargin(0.04); // Adjust this value based on your object scale

4. Configure Rigid Body Sleeping (Deactivation)

Allowing objects to fall asleep is the most effective way to stop jitter in resting stacks. Once an object’s linear and angular velocity falls below a threshold, the engine disables its physics calculations until another object collides with it.

Ensure sleeping is enabled on your stacked boxes:

// Prevent objects from staying active forever
body.setActivationState(1); // 1 = ACTIVE_TAG, which allows sleeping transitions

// Set custom thresholds for when the body should fall asleep
body.setSleepingThresholds(0.8, 1.0); // (linearVelocityThreshold, angularVelocityThreshold)

5. Tune ERP and CFM

The Error Reduction Parameter (ERP) and Constraint Force Mixing (CFM) control how aggressively the engine resolves penetrations. * ERP defines what fraction of joint/collision error is corrected in each step. * CFM defines how “soft” the constraints are.

Reducing ERP or slightly increasing CFM can soften the corrective forces, preventing the explosive over-corrections that cause stacks to jitter and collapse. These can be adjusted globally via the solver info:

const solverInfo = physicsWorld.getSolverInfo();
solverInfo.set_m_erp(0.2);  // Lower values make corrections gentler
solverInfo.set_m_globalCfm(0.00001); // Adds a tiny amount of elasticity/softness