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