Synchronize Rotating Bodies in ammo.js with btGearConstraint

In 3D physics simulations, coupling the rotational movement of two distinct objects is a common requirement for mechanical systems like gears, clocks, and machinery. This article explains how to use the btGearConstraint in ammo.js—the JavaScript port of the Bullet physics engine—to synchronize the rotation of two rigid bodies. We will cover what this constraint is, how it mathematically links rotation, and how to implement it directly in your JavaScript code.

What is a btGearConstraint?

The btGearConstraint is a specialized joint in ammo.js designed to couple the rotational degrees of freedom of two rigid bodies around their respective local axes.

In a real-world physical simulation, rendering highly detailed gear teeth and relying on collision detection to rotate gears is computationally expensive, unstable, and prone to jittering. The btGearConstraint solves this by bypassing collision detection entirely. Instead, it mathematically enforces a constant transmission ratio (gear ratio) between the two bodies, ensuring that when Body A rotates, Body B rotates proportionally.

Key Parameters of the Constraint

To create a btGearConstraint, you must define five primary parameters:

  1. Rigid Body A (rbA): The first physics body (e.g., the driving gear).
  2. Rigid Body B (rbB): The second physics body (e.g., the driven gear).
  3. Axis in A (axisInA): A btVector3 representing the local axis of rotation for the first body.
  4. Axis in B (axisInB): A btVector3 representing the local axis of rotation for the second body.
  5. Ratio (ratio): A floating-point number defining the gear ratio. If the ratio is 2.0, Body B will rotate twice as fast as Body A. A negative ratio (e.g., -2.0) reverses the direction of rotation, which is typical for external interlocking gears.

Implementing btGearConstraint in ammo.js

Because the gear constraint only links the rotational speeds, the two rigid bodies must be kept in place by other means. Usually, you must first pin each body in space using a btHingeConstraint or a btGeneric6DofConstraint so they do not drift apart.

Here is the step-by-step code implementation to set up and apply the btGearConstraint:

// Assuming Ammo is initialized and your physicsWorld, bodyA, and bodyB exist.

// 1. Define the local axes of rotation for both bodies
// In this example, both bodies rotate around their local Z-axes
const axisA = new Ammo.btVector3(0, 0, 1);
const axisB = new Ammo.btVector3(0, 0, 1);

// 2. Define the gear ratio
// A negative value ensures that when Gear A spins clockwise, Gear B spins counter-clockwise
const gearRatio = -1.5; 

// 3. Instantiate the btGearConstraint
const gearConstraint = new Ammo.btGearConstraint(
    bodyA,      // First rigid body
    bodyB,      // Second rigid body
    axisA,      // Local axis of rotation for bodyA
    axisB,      // Local axis of rotation for bodyB
    gearRatio   // Transmission ratio
);

// 4. Add the constraint to your ammo.js physics world
// The second argument 'true' disables collisions between the linked bodies
physicsWorld.addConstraint(gearConstraint, true);

// 5. Clean up temporary vectors to prevent memory leaks in WebAssembly
Ammo.destroy(axisA);
Ammo.destroy(axisB);

Best Practices and Tips