How to Use btTransform in Ammo.js

This article explains the fundamental role of btTransform in Ammo.js, the WebAssembly port of the Bullet physics engine. You will learn what a btTransform is, how it represents spatial data, and how to use it to set, update, and synchronize the positions and rotations of rigid bodies between your physics simulation and a 3D rendering library like Three.js.

What is a btTransform?

In Ammo.js, btTransform is a class used to represent the position (translation) and orientation (rotation) of an object in 3D space. Mathematically, it combines a 3D vector (for the position) and either a 3x3 matrix or a quaternion (for the rotation).

Instead of managing position and rotation as separate, disconnected variables, Ammo.js uses btTransform as a single, unified container. This ensures that spatial operations and physics calculations remain mathematically consistent.

How btTransform Manages Object Positions

To manage object positions effectively, you will interact with btTransform in two primary phases: initializing an object’s starting position and retrieving its updated position during the physics simulation loop.

1. Setting an Object’s Initial Position

When creating a rigid body, you must define its starting transform. This is typically done by instantiating a btTransform, setting it to the identity state (clearing any default translation/rotation), and then assigning a position and rotation.

// 1. Create the transform object
const transform = new Ammo.btTransform();
transform.setIdentity();

// 2. Set the position (translation) using a btVector3
const position = new Ammo.btVector3(0, 10, 0); // X, Y, Z coordinates
transform.setOrigin(position);

// 3. Set the rotation (orientation) using a btQuaternion
const rotation = new Ammo.btQuaternion(0, 0, 0, 1); // No rotation
transform.setRotation(rotation);

Once defined, this transform is passed to a btDefaultMotionState, which is then used to construct the final btRigidBody.

2. Reading Positions During the Simulation Loop

During the physics loop, gravity, collisions, and forces move your objects. To render these movements on the screen, you must extract the updated btTransform from the physics body and apply it to your visual 3D mesh.

To do this efficiently, Ammo.js utilizes a “motion state” to write the updated transform into a temporary btTransform object:

// Create a reusable transform helper outside the loop to prevent memory leaks
const tempTransform = new Ammo.btTransform();

function updatePhysics(deltaTime) {
    // Step the physics world
    physicsWorld.stepSimulation(deltaTime, 10);

    // Retrieve the updated transform from the rigid body's motion state
    const motionState = rigidBody.getMotionState();
    if (motionState) {
        motionState.getWorldTransform(tempTransform);

        // Extract the position
        const origin = tempTransform.getOrigin();
        const posX = origin.x();
        const posY = origin.y();
        const posZ = origin.z();

        // Extract the rotation
        const rotation = tempTransform.getRotation();
        const rotX = rotation.x();
        const rotY = rotation.y();
        const rotZ = rotation.z();
        const rotW = rotation.w();

        // Apply these coordinates to your 3D graphics library (e.g., Three.js mesh)
        threeMesh.position.set(posX, posY, posZ);
        threeMesh.quaternion.set(rotX, rotY, rotZ, rotW);
    }
}

Memory Management with btTransform

Because Ammo.js is compiled from C++ via Emscripten, JavaScript’s automatic garbage collection does not apply to Ammo objects. If you instantiate btTransform, btVector3, or btQuaternion using the new keyword, you must manually destroy them when they are no longer needed to prevent memory leaks:

Ammo.destroy(transform);
Ammo.destroy(position);
Ammo.destroy(rotation);