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.