Create a Branching Domino Rally in Matter.js
This guide explains how to construct a dynamic domino rally simulation featuring branching chain reactions using Matter.js. You will learn how to configure the physics engine, calculate precise spacing to maintain momentum, assemble standard linear domino runs, and engineer reliable branching junctions where a single impact splits into two distinct paths.
1. Engine and World Initialization
Start by importing the required Matter.js modules and setting up the engine, renderer, and runner. A stable canvas width and height alongside default downward gravity provide the foundation for natural toppling.
const { Engine, Render, Runner, Bodies, Composite, Body, Vector } = Matter;
const engine = Engine.create();
const world = engine.world;
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: 1200,
height: 600,
wireframes: false
}
});
Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);2. Defining Domino Properties and Spacing
For dominoes to fall cleanly without sliding out of place or bouncing unnaturally, configure their physical properties with high friction, moderate density, and minimal restitution (bounciness).
- Width: 10 px
- Height: 60 px
- Spacing: 30 to 35 px (roughly half to two-thirds of the height to ensure each domino strikes the upper half of the next).
- Friction: 0.6 to 0.9 (prevents base slipping).
- Restitution: 0.1 (minimizes energy loss to elasticity).
function createDomino(x, y, angle = 0) {
return Bodies.rectangle(x, y, 10, 60, {
angle: angle,
friction: 0.8,
frictionStatic: 1.0,
restitution: 0.1,
density: 0.002,
render: { fillStyle: '#e74c3c' }
});
}3. Creating Linear Runs
A helper function simplifies building tracks across platforms. Dominoes are laid sequentially along the horizontal axis.
function createDominoLine(startX, startY, count, spacing = 32) {
const dominoes = [];
for (let i = 0; i < count; i++) {
dominoes.push(createDomino(startX + i * spacing, startY));
}
return dominoes;
}4. Engineering the Branching Mechanism
Because Matter.js operates in a 2D side-view environment with downward gravity, a single domino cannot split sideways in the Z-axis. Instead, branching is achieved mechanically at an intersection using vertical distribution:
- The Staggered Shelf Split: The main path terminates at an elevation. The final domino falls forward onto a dual-trigger mechanism or directly impacts an upper shelf line while its lower half or an intermediary falling weight triggers a lower track.
- The Rocker / Splitter Lever: The terminal domino strikes a centrally pivoted lever (seesaw). The downward motion of the lever's struck side trips a lower tier of dominoes, while an attached counterweight or linked arm trips an adjacent upper run.
- The Dual-Platform Fall: The incoming domino rests at the edge of a step. When it topples, it hits an intermediate angled block that simultaneously strikes a forward path and drops a payload onto a secondary path below.
Here is the implementation of a reliable multi-level branch where a primary track splits into an upper straight path and a lower descending path:
// Ground and platforms
const floor = Bodies.rectangle(600, 580, 1200, 40, { isStatic: true });
const mainPlatform = Bodies.rectangle(200, 300, 400, 20, { isStatic: true });
const upperPlatform = Bodies.rectangle(650, 300, 400, 20, { isStatic: true });
const lowerPlatform = Bodies.rectangle(650, 480, 400, 20, { isStatic: true });
Composite.add(world, [floor, mainPlatform, upperPlatform, lowerPlatform]);
// 1. Incoming main track
const mainTrack = createDominoLine(50, 260, 10, 32);
// 2. The Splitter Unit: A dynamic intermediary block at the edge
const splitter = Bodies.rectangle(370, 255, 20, 70, {
density: 0.004,
friction: 0.5,
render: { fillStyle: '#f1c40f' }
});
// A suspended ball acting as a transfer weight to the lower path
const lowerTrigger = Bodies.circle(385, 280, 15, {
density: 0.005,
restitution: 0.2,
render: { fillStyle: '#3498db' }
});
// 3. Branch Paths
const upperTrack = createDominoLine(450, 260, 8, 32);
const lowerTrack = createDominoLine(450, 440, 8, 32);
Composite.add(world, [...mainTrack, splitter, lowerTrigger, ...upperTrack, ...lowerTrack]);5. Triggering the Rally
To start the cascade, apply a forward angular velocity or a gentle horizontal force to the first domino in the main run.
// Apply force to the top of the first domino
const firstDomino = mainTrack[0];
Body.applyForce(firstDomino, { x: firstDomino.position.x, y: firstDomino.position.y - 25 }, { x: 0.02, y: 0 });6. Fine-Tuning for Reliability
- Constraint Stabilizers: If dominoes jitter due to
continuous solver settling, increase
engine.positionIterationsandengine.velocityIterationsfrom the default6to10or12. - Gap Distances: Keep the gap at the branch junction slightly narrower than regular gaps (around 20–25 px) to compensate for energy lost when pushing multiple targets.
- Mass Scaling: Ensure the splitter or junction
element has enough mass (via
density) to transfer momentum to both outputs without stopping dead on the first contact.