How to Sync Three.js Meshes with Ammo.js Rigid Bodies
Synchronizing Three.js meshes with Ammo.js rigid bodies is essential for creating realistic 3D physics simulations in the browser. This article explains how to link your visual 3D models with their physical representations and update their positions and rotations inside the animation loop. You will learn how to set up the association between graphics and physics, retrieve motion states from Ammo.js, and apply those transformations directly to Three.js meshes on every frame.
The Core Concept of Synchronization
In a WebGL physics application, Three.js manages what the user sees (the graphics), while Ammo.js calculates where objects should be based on forces like gravity and collisions (the physics). To sync them, you must let Ammo.js calculate the movements first, extract the updated position and rotation data from the physical “rigid body,” and then apply that data to the corresponding Three.js “mesh.”
Step 1: Pair Your Meshes and Rigid Bodies
To update your meshes, you need a way to loop through them. The most common approach is to create an array that holds pairs of Three.js meshes and Ammo.js rigid bodies.
// Array to hold physics/graphics pairs
const syncList = [];
function createBox(width, height, depth, mass, position) {
// 1. Create Three.js Mesh (Graphics)
const geometry = new THREE.BoxGeometry(width, height, depth);
const material = new THREE.MeshPhongMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(position);
scene.add(mesh);
// 2. Create Ammo.js Rigid Body (Physics)
const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(position.x, position.y, position.z));
const motionState = new Ammo.btDefaultMotionState(transform);
const colShape = new Ammo.btBoxShape(new Ammo.btVector3(width * 0.5, height * 0.5, depth * 0.5));
const localInertia = new Ammo.btVector3(0, 0, 0);
if (mass > 0) colShape.calculateLocalInertia(mass, localInertia);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, colShape, localInertia);
const body = new Ammo.btRigidBody(rbInfo);
physicsWorld.addRigidBody(body);
// 3. Pair them together and add to the tracking list
syncList.push({
mesh: mesh,
rigidBody: body
});
}Step 2: Set Up Temporary Math Objects
Instantiating new objects inside your render loop causes garbage collection overhead, which leads to performance stuttering. To prevent this, define temporary Ammo.js variables outside your render loop to reuse them during updates.
// Create a reusable transform object once
const tempTransform = new Ammo.btTransform();Step 3: Update the Physics World and Sync Meshes
Inside your requestAnimationFrame render loop, you must advance the physics simulation and loop through your paired objects to copy the physical coordinates to the visual meshes.
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const deltaTime = clock.getDelta();
// Step the physics simulation
if (physicsWorld) {
physicsWorld.stepSimulation(deltaTime, 10);
}
// Synchronize graphics with physics
for (let i = 0; i < syncList.length; i++) {
const obj = syncList[i];
const mesh = obj.mesh;
const rigidBody = obj.rigidBody;
const motionState = rigidBody.getMotionState();
if (motionState) {
// Get the updated transform from the rigid body
motionState.getWorldTransform(tempTransform);
// Extract position
const origin = tempTransform.getOrigin();
mesh.position.set(origin.x(), origin.y(), origin.z());
// Extract rotation (Quaternion)
const rotation = tempTransform.getRotation();
mesh.quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w());
}
}
renderer.render(scene, camera);
}Important Considerations
- Static Bodies: Objects with a mass of
0(like floors or static walls) do not need to be synchronized inside the loop since they do not move. Omitting them from your synchronization loop saves CPU cycles. - Kinematic Bodies: If you want to move a physics object manually via Three.js (e.g., a moving platform), you must set its collision flag to kinematic, move the mesh, and write the transform from the mesh back into the rigid body’s motion state.