How to Create a Geneva Drive in Matter.js
This guide explains how to design, assemble, and simulate a functioning Geneva drive mechanism using Matter.js. You will learn the principles of constructing the driving wheel and the slotted driven wheel using compound bodies, anchoring them with pivot constraints, and tuning collision parameters to achieve reliable intermittent rotary motion in a 2D physics environment.
Understanding the Mechanics
A Geneva drive (or Maltese cross) translates continuous rotational motion into intermittent rotary motion. It consists of two primary interacting components:
- The Drive Wheel: Rotates continuously and features a driving pin offset from its center, along with a raised circular blocking disc.
- The Driven Wheel: Features symmetrical slots (typically four to six) radiating from its center, alternating with concave locking cutouts that interface with the blocking disc to prevent rotation between index steps.
In Matter.js, rigid-body physics replaces ideal mathematical linkages. The mechanism relies purely on contact forces: the pin enters a slot, pushes the wheel forward by a fraction of a turn, and exits, while friction and constraints regulate the movement.
1. Environment Setup
Matter.js requires standard module initialization. Import the engine, runner, bodies, composite, and constraint classes:
const { Engine, Render, Runner, Bodies, Body, Composite, Constraint, Vertices } = Matter;
const engine = Engine.create();
const world = engine.world;
// Geneva drives require higher solver fidelity to avoid pin tunneling
engine.positionIterations = 12;
engine.velocityIterations = 8;2. Modeling the Driven Wheel (Geneva Cross)
Because standard Geneva wheels have concave geometry (slots), Matter.js cannot treat them as simple single-polygon shapes without concave decomposition or compound assemblies. The most stable approach is building a compound body using overlapping rectangles to form the slots.
For a classic 4-slot Geneva wheel:
- Create central spoke segments.
- Arrange four rectangular boundary walls around each slot to leave a recessed opening.
- Combine these sub-bodies into a single composite rigid body.
function createGenevaWheel(x, y, radius, slotWidth) {
const parts = [];
const numSlots = 4;
const spokeWidth = radius * 0.4;
const spokeLength = radius;
// Create central core
const core = Bodies.circle(x, y, radius * 0.3, { isSensor: true });
parts.push(core);
// Build arm walls to define slots
for (let i = 0; i < numSlots; i++) {
const angle = (i * Math.PI) / 2;
// Offset rectangular plates defining the slot walls
const wallOffset = slotWidth / 2 + spokeWidth / 2;
const armLeft = Bodies.rectangle(
x + Math.cos(angle - 0.2) * (radius * 0.6),
y + Math.sin(angle - 0.2) * (radius * 0.6),
spokeLength,
spokeWidth,
{ angle: angle }
);
parts.push(armLeft);
}
const drivenWheel = Body.create({
parts: parts,
friction: 0.05,
restitution: 0,
density: 0.005
});
return drivenWheel;
}3. Modeling the Drive Wheel and Pin
The drive wheel consists of a central rotating hub and an offset drive pin that enters the slots of the driven wheel.
function createDriveWheel(x, y, radius, pinDistance, pinRadius) {
// Base hub
const hub = Bodies.circle(x, y, radius, {
collisionFilter: { mask: 0 } // Prevent base body from interfering with slots
});
// Pin positioned on the perimeter
const pin = Bodies.circle(x + pinDistance, y, pinRadius, {
friction: 0.02,
restitution: 0,
density: 0.01
});
return Body.create({
parts: [hub, pin],
frictionAir: 0
});
}4. Anchoring and Constraining the Mechanism
Both wheels must rotate about fixed centers. Use
Constraint.create with zero length to create static
revolute joints.
const driveCenter = { x: 300, y: 400 };
const centerDistance = 141.4; // Calculated based on wheel radii for a 90-degree drive
const drivenCenter = { x: driveCenter.x + centerDistance, y: 400 };
const driveWheel = createDriveWheel(driveCenter.x, driveCenter.y, 80, 100, 8);
const drivenWheel = createGenevaWheel(drivenCenter.x, drivenCenter.y, 100, 18);
// Pivot constraints anchored to fixed world points
const drivePivot = Constraint.create({
pointA: driveCenter,
bodyB: driveWheel,
pointB: { x: 0, y: 0 },
stiffness: 1,
length: 0
});
const drivenPivot = Constraint.create({
pointA: drivenCenter,
bodyB: drivenWheel,
pointB: { x: 0, y: 0 },
stiffness: 1,
length: 0
});
Composite.add(world, [driveWheel, drivenWheel, drivePivot, drivenPivot]);5. Driving the Motion and Physics Tuning
To operate the Geneva mechanism, apply constant angular velocity to the driving wheel inside the engine update loop, or maintain continuous rotational velocity directly:
Events.on(engine, 'beforeUpdate', () => {
// Maintain a constant drive speed
Body.setAngularVelocity(driveWheel, 0.03);
});Critical Parameters for Stability
- Friction: Keep the friction coefficient low on both
the driving pin and the driven wheel parts
(
friction: 0.01 - 0.05). High friction causes the pin to jam inside the slot rather than sliding smoothly. - Slot Width Clearance: Ensure the slot width is
slightly larger than the pin diameter
(
slotWidth ≈ pinRadius * 2 + 2px) to accommodate numerical inaccuracies during collision resolution. - Sub-stepping: If the pin escapes the slot or
tunnels through geometry at higher speeds, decrease the engine step size
or increase
engine.positionIterations.