Convert Ammo.js Transform to Euler Angles
This article explains how to extract and convert an
ammo.js transform object into Euler angles (pitch, yaw, and
roll) represented in degrees. This process is essential for displaying
3D physics rotations in a standard 2D user interface or inspector panel.
We will walk through extracting the quaternion from the
btTransform and converting it using both pure JavaScript
mathematics and a Three.js helper method.
Extracting the Quaternion from btTransform
In ammo.js (the Emscripten port of the Bullet physics
engine), rotation is stored as a quaternion within the
btTransform object. To read this rotation, you must first
extract the quaternion components (\(x, y, z,
w\)).
// Assuming 'transform' is an instance of Ammo.btTransform
const quaternion = transform.getRotation();
const qx = quaternion.x();
const qy = quaternion.y();
const qz = quaternion.z();
const qw = quaternion.w();Pure JavaScript Mathematical Conversion
Once you have the quaternion components, you can use trigonometric formulas to convert them into Euler angles. The following function converts the quaternion values into Pitch, Yaw, and Roll in radians, and then converts those values into degrees for UI display.
function quaternionToEulerDegrees(qx, qy, qz, qw) {
// Roll (x-axis rotation)
const sinr_cosp = 2 * (qw * qx + qy * qz);
const cosr_cosp = 1 - 2 * (qx * qx + qy * qy);
const roll = Math.atan2(sinr_cosp, cosr_cosp);
// Pitch (y-axis rotation)
const sinp = 2 * (qw * qy - qz * qx);
let pitch;
if (Math.abs(sinp) >= 1) {
pitch = Math.sign(sinp) * (Math.PI / 2); // Use 90 degrees if out of range
} else {
pitch = Math.asin(sinp);
}
// Yaw (z-axis rotation)
const siny_cosp = 2 * (qw * qz + qx * qy);
const cosy_cosp = 1 - 2 * (qy * qy + qz * qz);
const yaw = Math.atan2(siny_cosp, cosy_cosp);
// Convert radians to degrees
const radToDeg = 180 / Math.PI;
return {
x: roll * radToDeg, // Pitch / Roll depending on your coordinate system
y: pitch * radToDeg,
z: yaw * radToDeg
};
}
// Usage:
const eulerAngles = quaternionToEulerDegrees(qx, qy, qz, qw);
console.log(`X: ${eulerAngles.x.toFixed(2)}°, Y: ${eulerAngles.y.toFixed(2)}°, Z: ${eulerAngles.z.toFixed(2)}°`);Conversion Using Three.js (Recommended for Three.js Projects)
If you are using ammo.js alongside Three.js, you can
leverage the built-in math utilities of Three.js to handle the
conversion. This avoids manual trigonometric calculations and accounts
for gimbal lock and rotation orders (such as XYZ, YXZ, etc.)
automatically.
// 1. Create reusable Three.js math objects to avoid garbage collection overhead
const threeQuat = new THREE.Quaternion();
const threeEuler = new THREE.Euler();
// 2. Copy values from Ammo quaternion to Three.js quaternion
threeQuat.set(qx, qy, qz, qw);
// 3. Set the Euler angles from the quaternion (default order is 'XYZ')
threeEuler.setFromQuaternion(threeQuat, 'XYZ');
// 4. Convert to degrees for your UI
const uiRotation = {
x: threeEuler.x * (180 / Math.PI),
y: threeEuler.y * (180 / Math.PI),
z: threeEuler.z * (180 / Math.PI)
};