Build a Double Pendulum Chaos Simulation in Matter.js
This article provides a step-by-step guide to constructing an interactive double pendulum simulation using the Matter.js 2D physics engine to demonstrate mathematical chaos theory. You will learn how to initialize the physics world, configure coupled rigid bodies and rigid constraints, enable mouse interaction, and implement real-time trajectory tracing. By the end of this tutorial, you will have a browser-based simulation that visually proves how minor variations in initial conditions produce radically divergent, unpredictable outcomes.
Understanding the Physics and Chaos Theory
A simple pendulum exhibits regular, periodic motion. However, attaching a second pendulum to the bob of the first produces a double pendulum—one of the simplest mechanical systems capable of dynamic chaos.
While the system's equations of motion are entirely deterministic, they are non-linear and coupled. This creates extreme sensitivity to initial conditions (popularly known as the "butterfly effect"). A variation as minute as a fraction of a millimeter in the starting release angle will cause the secondary bob to trace entirely different pathways over time, providing an ideal visual demonstration of chaos theory.
Step 1: Initialize the Matter.js Environment
Begin by setting up an HTML canvas and importing Matter.js via a CDN.
Extract the necessary engine modules: Engine,
Render, Runner, Bodies,
Composite, Constraint, Mouse, and
MouseConstraint.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Double Pendulum Chaos</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<style>
body { margin: 0; overflow: hidden; background: #111; }
canvas { display: block; }
</style>
</head>
<body>
<script>
const { Engine, Render, Runner, Bodies, Composite, Constraint, Mouse, MouseConstraint, Events } = Matter;
const engine = Engine.create();
const world = engine.world;
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: window.innerWidth,
height: window.innerHeight,
wireframes: false,
background: '#111'
}
});
Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);Step 2: Assemble the Double Pendulum
The double pendulum requires an immovable anchor point, two circular bodies (bobs), and two rigid rod constraints.
- The Anchor: A static body positioned in the upper-middle of the screen.
- The First Arm and Bob: Connected between the static anchor and the first moving circle.
- The Second Arm and Bob: Connected between the first circle and the second circle.
Set stiffness: 1 on the constraints so the rods behave
like rigid bodies rather than elastic springs, and reduce
frictionAir to zero on the bobs to sustain the motion.
const centerX = window.innerWidth / 2;
const centerY = 150;
const rodLength = 140;
// Static Anchor
const anchor = Bodies.circle(centerX, centerY, 5, { isStatic: true, render: { visible: false } });
// Pendulum Bob 1
const bob1 = Bodies.circle(centerX + rodLength, centerY, 15, {
density: 0.005,
frictionAir: 0.0001,
render: { fillStyle: '#4ecdc4' }
});
// Pendulum Bob 2
const bob2 = Bodies.circle(centerX + rodLength * 2, centerY, 15, {
density: 0.005,
frictionAir: 0.0001,
render: { fillStyle: '#ff6b6b' }
});
// Rigid Arm 1
const arm1 = Constraint.create({
bodyA: anchor,
bodyB: bob1,
length: rodLength,
stiffness: 1,
render: { strokeStyle: '#fff', lineWidth: 3 }
});
// Rigid Arm 2
const arm2 = Constraint.create({
bodyA: bob1,
bodyB: bob2,
length: rodLength,
stiffness: 1,
render: { strokeStyle: '#fff', lineWidth: 3 }
});
Composite.add(world, [anchor, bob1, bob2, arm1, arm2]);Step 3: Enable Interactive Controls
To allow users to manually perturb the system and test different
initial conditions, add a MouseConstraint. Ensure that user
interactions update the engine correctly without breaking physical
consistency.
const mouse = Mouse.create(render.canvas);
const mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
constraint: {
stiffness: 0.2,
render: { visible: false }
}
});
Composite.add(world, mouseConstraint);
render.mouse = mouse;Step 4: Tracing the Motion Trail
Visualizing chaos requires recording the trajectory of the bottom bob
(bob2). By hooking into the Matter.js
afterRender event, you can draw a persistent path that
reveals the complex, non-repeating geometric patterns generated by the
system.
const trail = [];
const maxTrailLength = 500;
Events.on(render, 'afterRender', () => {
const context = render.context;
// Store position
trail.push({ x: bob2.position.x, y: bob2.position.y });
if (trail.length > maxTrailLength) {
trail.shift();
}
// Render path
if (trail.length > 1) {
context.beginPath();
context.moveTo(trail[0].x, trail[0].y);
for (let i = 1; i < trail.length; i++) {
context.lineTo(trail[i].x, trail[i].y);
}
context.strokeStyle = 'rgba(255, 107, 107, 0.4)';
context.lineWidth = 1.5;
context.stroke();
}
});
</script>
</body>
</html>Demonstrating Chaotic Divergence
To turn this simulation into a direct proof of chaos:
- Duplicate the system by adding a second identical pendulum set directly behind the first.
- Offset the second pendulum's starting position by a microscopic
amount (e.g.,
0.001pixels or radians). - Assign each secondary bob a distinct trail color.
Initially, both pendulums will swing in lockstep. Within seconds, the non-linear forces will amplify the microscopic difference, causing the arms to diverge into entirely independent, chaotic trajectories.