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.
- Solver Iteration Limits: To calculate collisions, Ammo.js uses an iterative constraint solver. If the solver does not run enough iterations, it cannot fully resolve the forces propagating through a tall stack, leaving residual energy that manifests as shaking.
- Micro-Penetrations and Over-Correction: Physics engines allow boxes to slightly penetrate each other to detect collisions. The engine then applies a corrective force to push them apart. In a stack, this correction pushes the next box up, creating a feedback loop of over-corrections.
- Inconsistent Timesteps: Passing variable frame times (delta time) directly into the physics step function causes instability.
- Absence of Rigid Body Sleeping: If rigid bodies do not enter a “sleeping” (deactivated) state when their velocity is near zero, tiny numerical fluctuations will keep them perpetually in motion.
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 10Note: 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 scale4. 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