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:

  1. The Escape Wheel: A steadily powered gear with saw-tooth profile teeth pointing in the direction of rotation.
  2. 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.
  3. 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