ammo.js btQuaternion: Representing 3D Rotations

This article explores how ammo.js, the JavaScript port of the Bullet physics engine, natively represents 3D spatial rotations using the btQuaternion class. We will break down the mathematical structure of quaternions, demonstrate how to instantiate and manipulate them in ammo.js, and explain how they prevent common rotation issues like gimbal lock during physics simulations.

Understanding the Math Behind btQuaternion

In ammo.js, 3D rotations are represented using quaternions rather than Euler angles (pitch, yaw, and roll) or 3x3 rotation matrices. A quaternion is a four-dimensional vector expressed as:

\[q = (x, y, z, w)\]

Where: * \(x, y, z\) represent the vector of the rotation axis, scaled by the sine of half the rotation angle. * \(w\) represents the cosine of half the rotation angle.

By using four values to represent a rotation around an arbitrary axis, btQuaternion avoids the phenomenon of “gimbal lock”—a state where two of the three rotational axes align, losing a degree of freedom. Quaternions also require less memory than 3x3 matrices and are computationally efficient for spherical linear interpolation (SLERP).

Instantiating btQuaternion in ammo.js

To use quaternions in ammo.js, you must instantiate the class through the Ammo namespace. Here is the standard way to create an identity quaternion (representing no rotation):

// Initialize an identity quaternion (0, 0, 0, 1)
const quaternion = new Ammo.btQuaternion(0, 0, 0, 1);

You can also set the values of an existing quaternion using the setValue method:

quaternion.setValue(x, y, z, w);

Because ammo.js is a WebIDL/Emscripten port of C++, you access the individual components using getter methods rather than direct properties:

const x = quaternion.x();
const y = quaternion.y();
const z = quaternion.z();
const w = quaternion.w();

Creating Rotations from Euler Angles and Axis-Angles

Directly calculating the \(x, y, z, w\) values of a quaternion manually is highly impractical. The btQuaternion class provides helper methods to define rotations using more intuitive systems.

1. Using Euler Angles (Yaw, Pitch, Roll)

You can define a rotation using Euler angles using the setEulerZYX method. Note that ammo.js expects these angles to be in radians.

const yaw = Math.PI / 4;   // Rotation around Y-axis (45 degrees)
const pitch = 0;           // Rotation around X-axis
const roll = 0;            // Rotation around Z-axis

const quaternion = new Ammo.btQuaternion();
quaternion.setEulerZYX(yaw, pitch, roll);

2. Using Axis-Angle Representation

If you want to rotate an object by a specific angle around a custom direction vector, use the setRotation method:

const axis = new Ammo.btVector3(0, 1, 0); // Rotate around the Y-axis
const angle = Math.PI / 2;                // 90 degrees in radians

const quaternion = new Ammo.btQuaternion();
quaternion.setRotation(axis, angle);

Applying Quaternions to Physics Bodies

In ammo.js, the rotation of a rigid body is managed via its transform (btTransform). To apply a rotation to a physics object, you pass the btQuaternion to the transform before updating the body.

const transform = new Ammo.btTransform();
transform.setIdentity();

// Define rotation
const rotation = new Ammo.btQuaternion();
rotation.setEulerZYX(0, Math.PI / 4, 0);

// Apply rotation to transform
transform.setRotation(rotation);

// Apply transform to a rigid body's motion state
const motionState = new Ammo.btDefaultMotionState(transform);

To maintain physical accuracy, ensure your quaternions remain normalized (having a length of 1). If a quaternion becomes distorted due to floating-point drift over cumulative operations, you can re-normalize it using:

quaternion.normalize();