Building a Grandfather Clock Escapement in Matter.js
This guide explains how to construct an antique grandfather clock recoil escapement anchor mechanism using the Matter.js 2D physics engine. It details the step-by-step assembly of the primary mechanical components—the escape wheel, the pivoted anchor pallets, and the harmonic pendulum—along with the physical tuning required to convert continuous rotational torque into sustained, rhythmic oscillation.
Understanding the Core Components
An antique anchor escapement relies on three interconnected physical elements:
- The Escape Wheel: A steadily powered gear with saw-tooth profile teeth pointing in the direction of rotation.
- The Anchor and Pallets: A curved frame mounted on an arbor above the escape wheel, equipped with an entry pallet and an exit pallet that alternately engage the wheel's teeth.
- The Pendulum: A rigid body suspended beneath the anchor's pivot that supplies the restoring force through gravity, dictating the timekeeping period.
1. Initialize the Matter.js Environment
Configure an engine, world, and rendering canvas with standard downward gravity.
const { Engine, Render, Runner, Bodies, Composite, Constraint, Body, Events } = Matter;
const engine = Engine.create();
const world = engine.world;
world.gravity.y = 1; // Standard downward gravity
const render = Render.create({
element: document.body,
engine: engine,
options: { width: 800, height: 800, wireframes: false }
});
Render.run(render);
Runner.run(Runner.create(), engine);2. Construct the Escape Wheel
The escape wheel needs angled teeth to produce the impulse and recoil characteristic of traditional clockworks. Create a composite body consisting of a central circular hub and symmetrically spaced, angled teeth.
const wheelRadius = 80;
const toothCount = 12;
const wheelPosition = { x: 400, y: 350 };
const wheelParts = [Bodies.circle(wheelPosition.x, wheelPosition.y, wheelRadius - 10, { isSensor: true })];
for (let i = 0; i < toothCount; i++) {
const angle = (i / toothCount) * Math.PI * 2;
const toothLength = 25;
const toothWidth = 8;
const toothX = wheelPosition.x + Math.cos(angle) * wheelRadius;
const toothY = wheelPosition.y + Math.sin(angle) * wheelRadius;
// Angled rectangle representing a ratchet-style tooth
const tooth = Bodies.rectangle(toothX, toothY, toothLength, toothWidth, {
angle: angle + 0.4, // Slight forward tilt for ratchet action
friction: 0.05,
restitution: 0.0
});
wheelParts.push(tooth);
}
const escapeWheel = Body.create({
parts: wheelParts,
collisionFilter: { group: 1 }
});
// Anchor the wheel to a fixed pivot point
const wheelPivot = Constraint.create({
pointA: wheelPosition,
bodyB: escapeWheel,
length: 0,
stiffness: 1
});
Composite.add(world, [escapeWheel, wheelPivot]);3. Build the Anchor and Pallets
The anchor spans across the top of the escape wheel. It features an entry pallet that arrests a tooth as it swings in, and an exit pallet that arrests a tooth on the other side as the anchor swings back.
const anchorPivotPoint = { x: 400, y: 240 };
// Main crossbar
const crossbar = Bodies.rectangle(anchorPivotPoint.x, anchorPivotPoint.y + 10, 140, 10);
// Left pallet (entry pallet)
const entryPallet = Bodies.rectangle(anchorPivotPoint.x - 65, anchorPivotPoint.y + 45, 12, 55, {
angle: 0.35,
friction: 0.05,
restitution: 0.0
});
// Right pallet (exit pallet)
const exitPallet = Bodies.rectangle(anchorPivotPoint.x + 65, anchorPivotPoint.y + 45, 12, 55, {
angle: -0.35,
friction: 0.05,
restitution: 0.0
});
const anchor = Body.create({
parts: [crossbar, entryPallet, exitPallet],
collisionFilter: { group: 1 }
});
const anchorPivot = Constraint.create({
pointA: anchorPivotPoint,
bodyB: anchor,
pointB: { x: 0, y: -10 },
length: 0,
stiffness: 1
});
Composite.add(world, [anchor, anchorPivot]);4. Attach the Pendulum
In antique clocks, the pendulum arbor is rigidly fixed to the anchor pallet arbor. To model this efficiently, append the pendulum rod and heavy bob directly into the anchor's composite structure, or join it with a rigid constraint.
const rodLength = 220;
const pendulumRod = Bodies.rectangle(anchorPivotPoint.x, anchorPivotPoint.y + (rodLength / 2), 6, rodLength);
const pendulumBob = Bodies.circle(anchorPivotPoint.x, anchorPivotPoint.y + rodLength, 30, {
density: 0.05 // High density provides stable momentum
});
const pendulumAssembly = Body.create({
parts: [pendulumRod, pendulumBob],
collisionFilter: { group: -1 } // Prevent self-collision with the wheel
});
// Fix the pendulum to rotate locked with the anchor
const anchorPendulumLink = Constraint.create({
bodyA: anchor,
bodyB: pendulumAssembly,
pointA: { x: 0, y: 0 },
pointB: { x: 0, y: -rodLength / 2 },
stiffness: 1,
length: 0
});
Composite.add(world, [pendulumAssembly, anchorPendulumLink]);5. Apply the Driving Torque and Physics Tuning
A mechanical clock requires continuous driving torque (the descent of weights or a mainspring). Apply a constant torque to the escape wheel during each update cycle:
const drivingTorque = 0.8;
Events.on(engine, 'beforeUpdate', () => {
// Clockwise driving torque
escapeWheel.torque = drivingTorque;
});
// Give the pendulum an initial offset to start oscillation
Body.setAngle(pendulumAssembly, 0.15);
Body.setAngle(anchor, 0.15);Critical Tuning Parameters
- Restitution: Set restitution to
0.0on both the wheel teeth and pallet faces. High restitution induces elastic bounce that throws the anchor out of phase and causes the teeth to jam. - Friction: Keep friction between
0.01and0.05. High friction prevents the pallet faces from sliding over tooth tips during the dead and impulse phases. - Torque-to-Inertia Ratio: If the driving torque is too high, it will overpower the pendulum's gravitational restoring force, pushing the pallets out of the wheel path entirely. If the torque is too low, internal friction will stall the mechanism. The torque must be calibrated to just balance the energy lost through pallet collisions.