Pause and Resume Ammo.js Physics

Controlling the execution of a physics engine is essential for implementing pause menus, managing game states, and optimizing performance in WebGL applications. This article provides a direct guide on how to globally pause and resume the ammo.js physics simulation, as well as how to selectively freeze and unfreeze individual rigid bodies within the simulation loop.

Method 1: Global Pause and Resume

The simplest way to pause the entire physics simulation is by controlling the step simulation call within your application’s animation frame loop.

ammo.js advances the physics world using the stepSimulation method. By introducing a boolean flag, you can conditionally skip this step.

let isPaused = false;

// Your main animation/render loop
function tick(deltaTime) {
    if (!isPaused) {
        // Step the physics world only if not paused
        // Max sub-steps = 10, fixed time step = 1/60
        dynamicsWorld.stepSimulation(deltaTime, 10, 1/60);
    }

    // Continue rendering your visual scene (e.g., Three.js)
    renderer.render(scene, camera);
    requestAnimationFrame(tick);
}

// Functions to toggle state
function pausePhysics() {
    isPaused = true;
}

function resumePhysics() {
    isPaused = false;
}

By bypassing stepSimulation, all physical bodies freeze in their current positions and velocities, preserving their states until the simulation resumes.


Method 2: Selective Pausing (Freezing Individual Bodies)

If you need the physics simulation to continue running while freezing only specific objects, you must manipulate those individual rigid bodies. In ammo.js, this is achieved by storing the body’s velocities and temporarily changing its activation state or kinematic flags.

Step 1: Freezing a Specific Rigid Body

To pause an individual body, you must store its current linear and angular velocities so they can be restored later, set its velocities to zero, and disable its simulation state.

// Object to store the velocities of paused bodies
const pausedBodiesState = new Map();

function pauseRigidBody(body) {
    // 1. Get and store current velocities
    const linearVelocity = body.getLinearVelocity();
    const angularVelocity = body.getAngularVelocity();

    pausedBodiesState.set(body, {
        linear: { x: linearVelocity.x(), y: linearVelocity.y(), z: linearVelocity.z() },
        angular: { x: angularVelocity.x(), y: angularVelocity.y(), z: angularVelocity.z() }
    });

    // 2. Zero out velocities to stop movement
    const zeroVector = new Ammo.btVector3(0, 0, 0);
    body.setLinearVelocity(zeroVector);
    body.setAngularVelocity(zeroVector);

    // 3. Force the body into a disabled simulation state
    // 5 represents the DISABLE_SIMULATION activation state
    body.setActivationState(5); 
    
    // Clean up temporary vector
    Ammo.destroy(zeroVector);
}

Step 2: Resuming a Specific Rigid Body

To resume the individual body, restore its saved velocities and reactivate it within the dynamics world.

function resumeRigidBody(body) {
    const savedState = pausedBodiesState.get(body);

    if (savedState) {
        // 1. Re-apply saved velocities
        const linearVelocity = new Ammo.btVector3(savedState.linear.x, savedState.linear.y, savedState.linear.z);
        const angularVelocity = new Ammo.btVector3(savedState.angular.x, savedState.angular.y, savedState.angular.z);

        body.setLinearVelocity(linearVelocity);
        body.setAngularVelocity(angularVelocity);

        // 2. Reactivate the body
        // 1 represents the ACTIVE_TAG activation state
        body.activate(true); 
        body.setActivationState(1);

        // Clean up temporary vectors and map entry
        Ammo.destroy(linearVelocity);
        Ammo.destroy(angularVelocity);
        pausedBodiesState.delete(body);
    }
}

Alternative: Kinematic Toggle

Another selective approach is to temporarily convert the rigid body into a kinematic body. Kinematic bodies are not affected by gravity or forces but can still be collided with by other dynamic objects.

// Freeze by making it kinematic
function makeKinematic(body) {
    body.setCollisionFlags(body.getCollisionFlags() | 2); // 2 is CF_KINEMATIC_OBJECT
    body.setActivationState(4); // 4 is DISABLE_DEACTIVATION
}

// Restore to dynamic
function makeDynamic(body) {
    body.setCollisionFlags(body.getCollisionFlags() & ~2); // Remove kinematic flag
    body.activate(true);
}