Deserialize Ammo.js Rigid Body State

Recreating the exact state of a physics simulation in ammo.js requires a structured approach to saving and loading rigid body properties. This article outlines the optimal strategy for deserializing saved ammo.js rigid body states, focusing on restoring transforms, velocities, and activation states while maintaining high performance and avoiding WebAssembly memory leaks.

The Optimal Deserialization Strategy

Because ammo.js is a WebAssembly/Emscripten port of the C++ Bullet Physics engine, you cannot simply parse a JSON object directly into a live rigid body. Instead, the optimal strategy is to modify the properties of an existing rigid body in-place using raw numerical data. This avoids the overhead of destroying and recreating collision shapes and rigid body instances.

To successfully deserialize a rigid body, you must restore four critical components: 1. World Transform (Position and Rotation) 2. Linear Velocity 3. Angular Velocity 4. Activation State


Step-by-Step Implementation

1. Extract the Saved Data

Your serialized data payload should ideally be a flat array or a lightweight JSON object containing only the essential floating-point values:

{
  "position": [0, 5, -2],
  "rotation": [0, 0, 0, 1],
  "linearVelocity": [0, -1, 0],
  "angularVelocity": [0.1, 0, 0]
}

2. Update the World Transform

To prevent visual jitter and ensure the physics engine registers the teleportation, you must update both the rigid body’s world transform and its motion state.

// Temporal ammo.js helper objects (allocate once and reuse to prevent memory leaks)
const tempTransform = new Ammo.btTransform();
const tempVector3 = new Ammo.btVector3();
const tempQuaternion = new Ammo.btQuaternion();

function deserializeRigidBody(rigidBody, savedState) {
    // 1. Set position
    tempVector3.setValue(savedState.position[0], savedState.position[1], savedState.position[2]);
    tempTransform.setOrigin(tempVector3);

    // 2. Set rotation
    tempQuaternion.setValue(savedState.rotation[0], savedState.rotation[1], savedState.rotation[2], savedState.rotation[3]);
    tempTransform.setRotation(tempQuaternion);

    // 3. Apply transform to the rigid body
    rigidBody.setWorldTransform(tempTransform);

    // 4. Update the motion state (crucial for visual rendering sync)
    const motionState = rigidBody.getMotionState();
    if (motionState) {
        motionState.setWorldTransform(tempTransform);
    }
    
    // Continue to velocities...
}

3. Restore Velocities

If you do not restore velocities, the object will instantly freeze in place upon deserialization.

    // 5. Restore Linear Velocity
    tempVector3.setValue(savedState.linearVelocity[0], savedState.linearVelocity[1], savedState.linearVelocity[2]);
    rigidBody.setLinearVelocity(tempVector3);

    // 6. Restore Angular Velocity
    tempVector3.setValue(savedState.angularVelocity[0], savedState.angularVelocity[1], savedState.angularVelocity[2]);
    rigidBody.setAngularVelocity(tempVector3);

4. Force Activation

Bullet Physics puts idle rigid bodies to sleep to save CPU cycles. When you deserialize a state, you must manually activate the body so the physics engine resumes calculating its forces immediately.

    // 7. Clear forces and force activation
    rigidBody.clearForces();
    rigidBody.activate(true); 

Performance and Memory Best Practices