How to Model Gear Ratios in Matter.js
This article explains how to simulate gear ratios between two rotating circular bodies in Matter.js. Because Matter.js does not feature an out-of-the-box mechanical gear constraint, developers must combine fixed revolute constraints with a runtime angular coupling mechanism. By fixing two circular bodies at their centers and programmatically locking their relative rotational speeds, you can achieve accurate, slip-free mechanical gear ratios without the instability of physical gear teeth.
Setting Up the Pinned Gears
To begin, create two circular bodies representing the gears and pin each body to a fixed point in the world using zero-length constraints. This allows them to rotate freely around their central axes while preventing linear translation.
const { Bodies, Constraint, Composite } = Matter;
// Define gear sizes
const radiusA = 40;
const radiusB = 80; // Gear ratio is 2:1
// Create the gear bodies
const gearA = Bodies.circle(200, 200, radiusA, { frictionAir: 0.01 });
const gearB = Bodies.circle(200 + radiusA + radiusB, 200, radiusB, { frictionAir: 0.01 });
// Pin Gear A to its center
const pivotA = Constraint.create({
pointA: { x: gearA.position.x, y: gearA.position.y },
bodyB: gearA,
pointB: { x: 0, y: 0 },
stiffness: 1,
length: 0
});
// Pin Gear B to its center
const pivotB = Constraint.create({
pointA: { x: gearB.position.x, y: gearB.position.y },
bodyB: gearB,
pointB: { x: 0, y: 0 },
stiffness: 1,
length: 0
});
Composite.add(engine.world, [gearA, gearB, pivotA, pivotB]);Enforcing the Gear Ratio
Standard distance constraints connect specific coordinates and will
wrap or bind after a single half-turn, making them unsuitable for
continuous rotation. The standard way to model a continuous gear
constraint in Matter.js is by binding the angular velocities and
relative angles using the engine's beforeUpdate event.
The gear ratio \(R\) is defined by the relationship between the radii:
\[R = \frac{r_A}{r_B}\]
For meshing external gears, the bodies must rotate in opposite directions:
\[\omega_B = -\omega_A \cdot \left(\frac{r_A}{r_B}\right)\]
Implementing the Custom Constraint Logic
Attach an event listener to the Matter.js engine to enforce the rotational coupling before each physics step.
const gearRatio = radiusA / radiusB;
Matter.Events.on(engine, 'beforeUpdate', () => {
// If one body is rotating, calculate the required angular velocity for the other
const expectedOmegaB = -gearA.angularVelocity * gearRatio;
// Smoothly apply correction to maintain the ratio
const angularDiff = expectedOmegaB - gearB.angularVelocity;
// Adjust angular velocity directly to prevent slipping
Matter.Body.setAngularVelocity(gearB, expectedOmegaB);
// Transfer opposing torques to ensure realistic resistance
const inertiaRatio = gearB.inertia / gearA.inertia;
if (Math.abs(angularDiff) > 0.0001) {
gearA.torque -= angularDiff * 0.1 * inertiaRatio;
}
});Synchronizing Angles
Over prolonged simulations, tiny floating-point drift can cause the visual teeth of meshed gears to slip out of phase. To prevent this, track an initial reference angle and enforce positional angle synchronization:
const initialAngleA = gearA.angle;
const initialAngleB = gearB.angle;
Matter.Events.on(engine, 'beforeUpdate', () => {
const deltaAngleA = gearA.angle - initialAngleA;
const targetAngleB = initialAngleB - (deltaAngleA * gearRatio);
// Correct angle error while preserving dynamics
Matter.Body.setAngle(gearB, targetAngleB);
Matter.Body.setAngularVelocity(gearB, -gearA.angularVelocity * gearRatio);
});Using this approach eliminates complex collision calculations between tiny teeth polygons while providing stable, realistic mechanical gear interactions.