Remove Rigid Body during Ammo.js Collision Callback
Removing a rigid body directly inside an ammo.js collision callback will often crash your physics simulation because the WebAssembly engine is still iterating through its internal collision pipeline. To safely remove a rigid body, you must defer the removal process until after the physics world step has fully completed. This article explains why direct removal causes crashes and demonstrates the standard deferred queue pattern to safely delete physics bodies and clean up memory.
Why Direct Removal Crashes Ammo.js
Ammo.js is a direct port of the C++ Bullet Physics library. When a collision callback is triggered, the physics engine is mid-tick, actively reading and writing to its internal arrays of active rigid bodies and manifolds.
If you call dynamicsWorld.removeRigidBody(body) inside
this callback, you modify the active array while the engine is iterating
over it. This leads to null-pointer exceptions, memory corruption, and
immediate crashes within the WebAssembly heap.
The Solution: Deferred Removal Queue
The safest and most efficient way to handle this is to mark the
bodies for deletion during the collision callback, and then physically
remove and destroy them immediately after the
dynamicsWorld.stepSimulation() call has finished.
Step 1: Create a Removal Queue
Initialize an empty array to act as your cleanup queue.
const bodiesToRemove = [];Step 2: Push Bodies to the Queue During Collision
When your collision detection logic identifies a body that needs to be destroyed (for example, a projectile hitting a target), push that body into your removal queue instead of deleting it immediately.
function handleCollision(bodyA, bodyB) {
// Determine which body to destroy, e.g., bodyA
if (!bodiesToRemove.includes(bodyA)) {
bodiesToRemove.push(bodyA);
}
}Step 3: Clean Up After the Physics Step
In your main animation or update loop, step the physics simulation first. Once the step is complete, iterate through your queue to safely remove the bodies from the world and free up their memory.
function updatePhysics(deltaTime) {
// 1. Step the simulation
dynamicsWorld.stepSimulation(deltaTime, 10);
// 2. Process the deferred removal queue
while (bodiesToRemove.length > 0) {
const body = bodiesToRemove.pop();
// Remove the collision shape and motion state to prevent leaks
const motionState = body.getMotionState();
if (motionState) {
Ammo.destroy(motionState);
}
// Remove the body from the physics world
dynamicsWorld.removeRigidBody(body);
// Safely destroy the C++ memory allocated for the body
Ammo.destroy(body);
}
}Essential Memory Cleanup
Because ammo.js uses WebAssembly, JavaScript’s automatic garbage
collection will not free the memory allocated for C++ objects. When
removing a rigid body, you must explicitly call
Ammo.destroy() on both the rigid body and its motion state
to prevent severe memory leaks in your application.