Convert Ammo.js Transform to WebGL Matrix
This article explains the standard approach for converting an
Ammo.btTransform object from the ammo.js physics library
into a flat 16-element float array compatible with 4x4 WebGL matrices.
We will cover the math behind the conversion, the standard extraction of
translation and rotation data, and how to structure the final
column-major array for your WebGL shaders.
Understanding the Matrix Structure
WebGL applications and shader programs expect 4x4 matrices to be
formatted as flat, 16-element, column-major arrays
(Float32Array).
A standard 3D transformation matrix containing rotation (\(R\)) and translation (\(T\)) is structured as follows:
\[ \begin{bmatrix} R_{00} & R_{10} & R_{20} & 0 \\ R_{01} & R_{11} & R_{21} & 0 \\ R_{02} & R_{12} & R_{22} & 0 \\ T_x & T_y & T_z & 1 \end{bmatrix} \]
In a flat WebGL-compatible array, the indices map directly to these columns:
[
R00, R01, R02, 0, // Column 1 (X basis)
R10, R11, R12, 0, // Column 2 (Y basis)
R20, R21, R22, 0, // Column 3 (Z basis)
Tx, Ty, Tz, 1 // Column 4 (Translation)
]Step-by-Step Conversion Process
The most reliable and memory-efficient way to convert an
Ammo.btTransform in JavaScript is to extract the
translation vector and the rotation quaternion, then manually construct
the 16-element array. This avoids allocating temporary Emscripten heap
memory.
1. Extract Position and Rotation from Ammo.js
First, retrieve the origin (position) and the rotation (quaternion)
from your active btTransform instance:
// Assumes 'transform' is an instance of Ammo.btTransform
const origin = transform.getOrigin();
const rotation = transform.getRotation();
const tx = origin.x();
const ty = origin.y();
const tz = origin.z();
const qx = rotation.x();
const qy = rotation.y();
const qz = rotation.z();
const qw = rotation.w();2. Math for Quaternion to 4x4 Matrix
To convert the quaternion \((qx, qy, qz, qw)\) and translation \((tx, ty, tz)\) into the 16-element array, use the standard mathematical formula for quaternion-to-rotation-matrix conversion:
function ammoTransformToWebGLArray(transform, outArray = new Float32Array(16)) {
const origin = transform.getOrigin();
const rotation = transform.getRotation();
const tx = origin.x();
const ty = origin.y();
const tz = origin.z();
const qx = rotation.x();
const qy = rotation.y();
const qz = rotation.z();
const qw = rotation.w();
// Pre-calculate products for performance
const x2 = qx + qx;
const y2 = qy + qy;
const z2 = qz + qz;
const xx = qx * x2;
const xy = qx * y2;
const xz = qx * z2;
const yy = qy * y2;
const yz = qy * z2;
const zz = qz * z2;
const wx = qw * x2;
const wy = qw * y2;
const wz = qw * z2;
// Column 1
outArray[0] = 1 - (yy + zz);
outArray[1] = xy + wz;
outArray[2] = xz - wy;
outArray[3] = 0;
// Column 2
outArray[4] = xy - wz;
outArray[5] = 1 - (xx + zz);
outArray[6] = yz + wx;
outArray[7] = 0;
// Column 3
outArray[8] = xz + wy;
outArray[9] = yz - wx;
outArray[10] = 1 - (xx + yy);
outArray[11] = 0;
// Column 4 (Translation)
outArray[12] = tx;
outArray[13] = ty;
outArray[14] = tz;
outArray[15] = 1;
return outArray;
}Alternative: Using WebGL Math Libraries
If you use standard WebGL helper libraries like glMatrix or Three.js, you can leverage their built-in composition methods instead of writing the raw math.
Using glMatrix
import { mat4, vec3, quat } from 'gl-matrix';
const outMatrix = mat4.create(); // Float32Array(16)
const position = vec3.fromValues(origin.x(), origin.y(), origin.z());
const rotationQuat = quat.fromValues(rotation.x(), rotation.y(), rotation.z(), rotation.w());
mat4.fromRotationTranslation(outMatrix, rotationQuat, position);Using Three.js
import { Matrix4, Vector3, Quaternion } from 'three';
const matrix = new Matrix4();
const position = new Vector3(origin.x(), origin.y(), origin.z());
const quaternion = new Quaternion(rotation.x(), rotation.y(), rotation.z(), rotation.w());
const scale = new Vector3(1, 1, 1);
matrix.compose(position, quaternion, scale);
// Access flat array via matrix.elements