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:
- Rigid Body A (
rbA): The first physics body (e.g., the driving gear). - Rigid Body B (
rbB): The second physics body (e.g., the driven gear). - Axis in A (
axisInA): AbtVector3representing the local axis of rotation for the first body. - Axis in B (
axisInB): AbtVector3representing the local axis of rotation for the second body. - Ratio (
ratio): A floating-point number defining the gear ratio. If the ratio is2.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
- Use Support Constraints: Always anchor your gears
using hinges. The
btGearConstraintdoes not prevent the bodies from flying away from each other; it only forces their rotation to match. - Disable Collision Between Gears: Pass
trueas the second argument tophysicsWorld.addConstraint()to disable collisions between the two gear meshes. This prevents the physical geometry of the gears from colliding and fighting against the constraint. - Managing Memory: Since ammo.js is a
WebAssembly/asm.js port of C++, objects created with the
newkeyword (likebtVector3or the constraint itself) must be manually destroyed usingAmmo.destroy(object)when they are no longer needed to prevent memory leaks.