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 shapeKey Considerations
- Avoid Double Freeing: Ensure you only call
Ammo.destroy(motionState)once. Calling it multiple times on the same pointer will cause memory corruption. - Nullify References: After destroying the object,
set your JavaScript reference to
null(motionState = null) to allow the JavaScript garbage collector to clean up the wrapper object. - Decoupling: If you plan to reuse a rigid body with
a different motion state, use
rigidBody.setMotionState(newMotionState)before destroying the old one.