btTransform and btRigidBody Relationship in ammo.js

This article explains the mathematical and structural relationship between btTransform and btRigidBody within the ammo.js physics engine. It covers how a rigid body uses transforms to represent its position and orientation in 3D space, the underlying matrix mathematics, and how to programmatically sync these states with 3D graphics libraries.

The Mathematical Representation

At its core, a btRigidBody represents a physical object with mass, velocity, and physical forces. However, to exist in a 3D simulation, it must have a spatial state. This state is defined by a btTransform.

Mathematically, a btTransform represents an affine transformation consisting of a translation vector and a rotation matrix. It maps local coordinates of the rigid body (\(X_{local}\)) to world coordinates (\(X_{world}\)):

\[X_{world} = R \cdot X_{local} + T\]

Where: * \(R\) is a \(3 \times 3\) rotation matrix (represented inside btTransform as a btMatrix3x3 or set via a btQuaternion). * \(T\) is a \(3\)-element translation vector (represented as a btVector3).

In homogeneous coordinates, this relationship is expressed as a single \(4 \times 4\) matrix multiplication:

\[\begin{bmatrix} X_{world} \\ 1 \end{bmatrix} = \begin{bmatrix} R & T \\ 0 & 1 \end{bmatrix} \begin{bmatrix} X_{local} \\ 1 \end{bmatrix}\]

The btRigidBody stores this matrix as its “Center of Mass” transform.

How btRigidBody Uses btTransform

In ammo.js, btRigidBody does not store position and rotation as separate, loose variables. Instead, it encapsulates them within a btTransform instance.

  1. Center of Mass: The btTransform of a btRigidBody specifically defines the position and orientation of the body’s center of mass, not necessarily its local origin (though they are often the same).
  2. State Updates: Every time the physics simulation steps (world.stepSimulation()), ammo.js calculates new linear and angular velocities, applies forces, and updates the btTransform of the btRigidBody accordingly.

Accessing and Modifying the Transform

To get or set the spatial state of a btRigidBody in ammo.js, you interact directly with its transform.

Reading the Transform

To retrieve the current position and rotation of a rigid body:

// Create a transform object to hold the output
let transform = new Ammo.btTransform();
rigidBody.getMotionState().getWorldTransform(transform);

// Extract translation (position)
let origin = transform.getOrigin();
let x = origin.x();
let y = origin.y();
let z = origin.z();

// Extract rotation (quaternion)
let rotation = transform.getRotation();
let qx = rotation.x();
let qy = rotation.y();
let qz = rotation.z();
let qw = rotation.w();

Writing the Transform

To manually override the position and orientation of a rigid body, you must construct a new btTransform and apply it:

let transform = new Ammo.btTransform();

// Set translation
let position = new Ammo.btVector3(0, 10, 0);
transform.setOrigin(position);

// Set rotation using a quaternion
let quaternion = new Ammo.btQuaternion(0, 0, 0, 1);
transform.setRotation(quaternion);

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

The Role of Motion States

While you can access the transform directly from the btRigidBody using getWorldTransform(), the mathematically correct and performant way to sync physics with rendering is through a btMotionState.

The motion state acts as a buffer. It allows ammo.js to interpolate the btTransform between physics steps, ensuring smooth visual updates even if the physics simulation runs at a different frame rate than the rendering loop.