How to Destroy btMotionState in Ammo.js

In WebGL and 3D physics development using ammo.js (the Emscripten port of Bullet Physics), managing memory is critical to prevent severe memory leaks. This article provides a direct, step-by-step guide on how to properly use Ammo.destroy() to clean up custom btMotionState implementations, ensuring that both WebAssembly heap memory and JavaScript references are correctly released.

The Importance of Manual Memory Management

Because ammo.js is compiled from C++ using Emscripten, JavaScript’s garbage collector cannot automatically free the underlying WebAssembly (Wasm) memory allocated for physics objects. If you create a custom btMotionState and simply lose its reference in JavaScript, the memory remains allocated in the Wasm heap, eventually leading to application crashes.

To prevent this, you must explicitly use Ammo.destroy(object) when you no longer need the motion state.

Step-by-Step Cleanup Process

To safely destroy a custom motion state, you must follow a strict order of operations. Destroying a motion state while it is still attached to an active rigid body will cause a segmentation fault (crash).

Step 1: Remove the Rigid Body from the Physics World

Before deleting any components, remove the parent rigid body from the physics world.

physicsWorld.removeRigidBody(rigidBody);

Step 2: Destroy the Rigid Body

You can now safely destroy the rigid body. If you passed the motion state to the rigid body’s construction helper (btRigidBodyConstructionInfo), the rigid body does not automatically claim ownership of the motion state’s memory. You must handle both separately.

Ammo.destroy(rigidBody);

Step 3: Destroy the Custom Motion State

Once the rigid body is destroyed or the motion state is decoupled, call Ammo.destroy() on your custom motion state instance.

Ammo.destroy(motionState);

Code Example: Creation and Proper Destruction

Here is a complete lifecycle example demonstrating how to implement, use, and clean up a custom motion state in ammo.js:

// 1. Define and instantiate the custom motion state
const CustomMotionState = Ammo.btMotionState.implement({
    getWorldTransform: function(worldTrans) {
        // Copy transform from your graphics object to physics world (if kinematic)
    },
    setWorldTransform: function(worldTrans) {
        // Copy transform from physics world to your graphics object
        const transform = Ammo.castObject(worldTrans, Ammo.btTransform);
        const origin = transform.getOrigin();
        const rotation = transform.getRotation();
        
        myGraphicsObject.position.set(origin.x(), origin.y(), origin.z());
        myGraphicsObject.quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w());
    }
});

const motionState = new CustomMotionState();

// 2. Assign to a rigid body
const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, colShape, localInertia);
const rigidBody = new Ammo.btRigidBody(rbInfo);
physicsWorld.addRigidBody(rigidBody);

// ... Simulation Loop ...

// 3. Proper Cleanup Sequence
physicsWorld.removeRigidBody(rigidBody);

Ammo.destroy(rigidBody);
Ammo.destroy(rbInfo);    // Do not forget construction info
Ammo.destroy(motionState); // Safely free the custom motion state
Ammo.destroy(colShape);   // Free the collision shape

Key Considerations