Clean Up and Dispose Ammo.js Physics World
This article provides a step-by-step guide on how to safely and completely dispose of an Ammo.js physics world when transitioning between application scenes. Because Ammo.js is a WebAssembly (Wasm) or Emscripten port of the C++ Bullet Physics library, JavaScript’s automatic garbage collection does not apply to its allocated memory. To prevent severe memory leaks during scene changes, you must manually destroy all rigid bodies, collision shapes, constraints, and the physics world core components themselves.
Why Manual Disposal is Necessary in Ammo.js
Ammo.js operates on a pre-allocated heap within WebAssembly. When you
create objects using new Ammo.btRigidBody(...) or
new Ammo.btDiscreteDynamicsWorld(...), memory is allocated
on this C++ heap. Simply nullifying your JavaScript variables or
changing scenes will leave these objects allocated in the Wasm memory,
quickly leading to memory exhaustion. You must explicitly call
Ammo.destroy() on every allocated object in the correct
reverse order of creation.
Step 1: Remove and Destroy Rigid Bodies
Before destroying the physics world, you must remove all rigid bodies currently added to it. You also need to clean up their associated motion states and collision shapes if they are not being reused.
// Assuming you have an array keeping track of your rigid bodies
const rigidBodies = [...];
rigidBodies.forEach(body => {
// Remove the body from the physics world
physicsWorld.removeRigidBody(body);
// Destroy the motion state if one was used
const motionState = body.getMotionState();
if (motionState) {
Ammo.destroy(motionState);
}
// Destroy the collision shape (if not shared/reused elsewhere)
const shape = body.getCollisionShape();
if (shape) {
Ammo.destroy(shape);
}
// Destroy the rigid body itself
Ammo.destroy(body);
});
// Clear your reference array
rigidBodies.length = 0;Step 2: Remove and Destroy Constraints and Joints
If your scene utilizes physics constraints (such as hinges, sliders, or point-to-point joints), these must be removed from the world and destroyed.
const joints = [...];
joints.forEach(joint => {
physicsWorld.removeConstraint(joint);
Ammo.destroy(joint);
});
joints.length = 0;Step 3: Dispose of Core Physics World Components
Once all physical entities (bodies, shapes, and constraints) have been removed and destroyed, you can safely dismantle the physics world structure. You must destroy these components in the reverse order of how they were initialized to avoid dangling pointers.
// 1. Destroy the dynamics world
if (physicsWorld) {
Ammo.destroy(physicsWorld);
physicsWorld = null;
}
// 2. Destroy the constraint solver
if (solver) {
Ammo.destroy(solver);
solver = null;
}
// 3. Destroy the broadphase interface
if (broadphase) {
Ammo.destroy(broadphase);
broadphase = null;
}
// 4. Destroy the collision dispatcher
if (dispatcher) {
Ammo.destroy(dispatcher);
dispatcher = null;
}
// 5. Destroy the default collision configuration
if (collisionConfiguration) {
Ammo.destroy(collisionConfiguration);
collisionConfiguration = null;
}Best Practices for Scene Transitions
- Track Every Allocation: Keep dedicated arrays for rigid bodies, collision shapes, and constraints. This makes iterating through them during clean-up straightforward.
- Avoid Shape Duplication: If multiple objects share
the same box or sphere dimensions, use a single
btCollisionShapeinstance. Remember to destroy this shared shape only once at the end of the scene clean-up process. - Verify Memory Release: Use your browser’s developer tools to monitor the heap size when switching scenes. If the memory footprint continuously grows with each transition, review your arrays to ensure no Ammo.js objects were bypassed during the destruction loop.