Cam and Follower Mechanism in Matter.js

This guide explains how to construct a functional cam and follower mechanism using the Matter.js 2D physics engine to convert continuous rotational motion into linear reciprocating motion. By combining rigid bodies, pivot constraints, vertical guides, and collision dynamics, you will learn how to model an eccentric cam, configure a spring-loaded follower rod, and maintain continuous mechanical contact during simulation.

Core Mechanical Components

A standard cam and follower system in a 2D physics environment requires three main components:

  1. The Cam: An asymmetrical or off-center rotating body mounted to a fixed pivot point.
  2. The Follower: A rigid body constrained to move along a single linear axis (typically vertical).
  3. The Preload Force: A spring constraint or gravity that ensures the follower stays in continuous physical contact with the cam's profile as it rotates.

Step-by-Step Implementation

1. Create the Cam Body and Pivot

The cam can be modeled using an ellipse, an egg-shaped polygon, or an eccentric circle. To create an eccentric circle, create a circular body and pin it at an off-center position using a fixed constraint.

const { Engine, Render, Runner, Bodies, Composite, Constraint, Body, Events } = Matter;

const engine = Engine.create();
const world = engine.world;

// Create an eccentric cam (circular body pinned off-center)
const camRadius = 60;
const camCenter = { x: 400, y: 300 };
const camOffset = 25; // Offset from geometric center to rotation axis

const cam = Bodies.circle(camCenter.x, camCenter.y, camRadius, {
    collisionFilter: { group: -1 }, // Prevent unwanted collisions if needed
    friction: 0,
    restitution: 0
});

// Anchor constraint acting as the axle
const axle = Constraint.create({
    pointA: { x: camCenter.x, y: camCenter.y + camOffset },
    bodyB: cam,
    pointB: { x: 0, y: camOffset },
    stiffness: 1,
    length: 0
});

2. Drive the Cam Rotation

To maintain consistent rotational motion without fighting natural friction, explicitly drive the cam's rotation using the beforeUpdate engine event.

const rotationSpeed = 0.05; // Radians per frame

Events.on(engine, 'beforeUpdate', () => {
    Body.setAngle(cam, cam.angle + rotationSpeed);
    Body.setAngularVelocity(cam, rotationSpeed);
});

3. Create the Follower

The follower consists of a rod positioned directly above the cam. To ensure purely vertical movement, frame the follower using static guide rails or use constraints to eliminate horizontal drift and rotation.

// Follower rod
const follower = Bodies.rectangle(camCenter.x, 150, 20, 180, {
    inertia: Infinity, // Prevents the follower from rotating
    friction: 0,
    frictionAir: 0.01,
    restitution: 0
});

// Guide rails to constrain linear vertical motion
const leftGuide = Bodies.rectangle(camCenter.x - 15, 150, 10, 200, {
    isStatic: true,
    friction: 0
});

const rightGuide = Bodies.rectangle(camCenter.x + 15, 150, 10, 200, {
    isStatic: true,
    friction: 0
});

4. Ensure Surface Contact (Preload)

If gravity is not strong enough to return the follower quickly at higher rotation speeds, apply an elastic preload constraint (a spring) to pull the follower downward against the cam surface.

const spring = Constraint.create({
    pointA: { x: camCenter.x, y: 300 },
    bodyB: follower,
    pointB: { x: 0, y: 80 },
    stiffness: 0.05,
    damping: 0.01,
    length: 80
});

5. Assembly and Execution

Add all defined bodies and constraints to the world composite and initialize the renderer and runner.

Composite.add(world, [cam, axle, follower, leftGuide, rightGuide, spring]);

const render = Render.create({
    element: document.body,
    engine: engine,
    options: {
        width: 800,
        height: 600,
        wireframes: false
    }
});

Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);

Tuning Performance