How to Map Ammo.js Quaternion to Three.js
This article explains how to efficiently synchronize rotation data between the Ammo.js physics engine and the Three.js 3D library. You will learn the best practices for copying quaternion data from Ammo.js to Three.js, focusing on performance optimization to prevent garbage collection spikes in your animation loops.
When integrating Ammo.js with Three.js, you must constantly update the visual representation (Three.js mesh) with the physical state (Ammo.js rigid body). Rotation in both libraries is represented by quaternions, but because Ammo.js is a WebAssembly/C++ wrapper (compiled via Emscripten) and Three.js is native JavaScript, their data structures differ.
In Three.js, quaternion components are accessed as direct properties
(x, y, z, w). In
Ammo.js, they must be accessed via getter methods (x(),
y(), z(), w()).
The Efficient Mapping Pattern
To map the quaternion efficiently, avoid instantiating new objects inside your animation loop. Creating new objects every frame triggers JavaScript’s garbage collector, causing noticeable frame drops. Instead, reuse existing instances.
Here is the most efficient way to perform the update:
// 1. Instantiate temporary variables once (outside the render loop)
const tempTransform = new Ammo.btTransform();
// 2. Inside your physics update/render loop:
function updatePhysicsBilling(mesh, rigidBody) {
const motionState = rigidBody.getMotionState();
if (motionState) {
// Retrieve the transform from Ammo.js
motionState.getWorldTransform(tempTransform);
// Get the Ammo.js quaternion
const ammoQuat = tempTransform.getRotation();
// Direct, allocation-free mapping to Three.js mesh quaternion
mesh.quaternion.set(
ammoQuat.x(),
ammoQuat.y(),
ammoQuat.z(),
ammoQuat.w()
);
// Update the mesh position similarly
const ammoPos = tempTransform.getOrigin();
mesh.position.set(
ammoPos.x(),
ammoPos.y(),
ammoPos.z()
);
}
}Key Performance Practices
- Zero Allocations: Notice that
tempTransformis instantiated outside the function. The loop only modifies existing memory addresses instead of creating newAmmo.btTransformobjects. - Direct Assignment: By passing the individual
x(),y(),z(), andw()values directly tomesh.quaternion.set(), you bypass the need to create any intermediate JavaScript objects or arrays. - Avoid WebAssembly Heap Overhead: Do not try to read
directly from the WebAssembly memory heap unless you are doing bulk
copies of thousands of objects. For standard rigid body updates, the
set()method using the Emscripten getters is highly optimized and sufficient.