Diffusion-Limited Aggregation
Random walks
One simple rule: particles walk randomly until they touch the growing cluster, then stick. From this emerges lightning, frost, coral — the geometry of accretion. Click anywhere to seed new growth.
Click to add seed points. Structure grows organically from where particles attach.
The Random Walk
Each particle is a walker — a point that moves in random directions, one step at a time. The walk is unbiased: every angle is equally likely. Over time, this produces Brownian motion, the same process that drives diffusion in physical systems. Particles explore space without any goal or direction, simply drifting until they encounter the cluster.
const angle = Math.random() * Math.PI * 2;
w.x += Math.cos(angle) * stepSize;
w.y += Math.sin(angle) * stepSize;
Two pixels per step, in a random direction. That choice — small steps, random angles — is what produces the organic fractal structure. Larger steps would create coarser branches. Biased angles would preferentially grow in certain directions. The pure random walk is what gives DLA its characteristic self-similarity.
Cluster Formation
The simulation maintains two populations: the cluster of stuck particles and the walkers still in motion. When a walker touches the cluster, it stops moving and becomes part of the growing structure. The cluster starts from one or more seed points — click anywhere to add more — and accretes layer by layer as walkers collide and stick.
for (const c of cluster) {
const dx = w.x - c.x;
const dy = w.y - c.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < checkRadius) {
if (Math.random() < stickProbability) {
cluster.push({
x: w.x,
y: w.y,
depth: c.depth + 1
});
walkers.splice(i, 1);
}
break;
}
}
Each stuck particle stores its depth — how many attachment steps from the original seed. This isn't used for the physics, but it drives the color gradient. The first particles stuck to the seed have depth 1, their grandchildren have depth 2, and so on. The structure builds outward from its centers.
Collision Detection
Every frame, each walker checks its distance to every cluster point. If the distance falls below the check radius — 3 pixels — the walker is close enough to stick. This is O(n × m) in the worst case: n walkers checking m cluster points. For a sprawling cluster and many walkers, this can become expensive. But DLA is inherently local: walkers only collide with the cluster's periphery, and early frames are cheap.
const checkRadius = 3;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < checkRadius) {
// collision detected
}
The check radius is slightly larger than the particle drawing radius. This creates a small tolerance zone where walkers can "touch" without pixel-perfect precision. Too small a radius and walkers phase through the cluster. Too large and the structure becomes blocky. Three pixels balances visual fidelity with reasonable performance.
Stick Probability
Not every collision results in attachment. The stickProbability parameter controls whether a walker that touches the cluster actually joins it. At 100%, contact is guaranteed. At lower values, walkers might brush past many times before finally catching — this creates denser, more compact structures because particles penetrate deeper into the branches before stopping.
if (Math.random() < stickProbability) {
cluster.push({
x: w.x,
y: w.y,
depth: c.depth + 1
});
walkers.splice(i, 1);
}
The probability transforms the geometry. High stick probability creates lightning-like branches — walkers catch on the first protrusion they encounter, reinforcing the fractal fingers. Low probability produces coral-like density — particles wander into gaps before stopping, filling in the structure from the inside. Adjust the slider and watch the shapes change.
Spawn and Population
Walkers don't spawn everywhere — they appear at the canvas edges, introducing them from outside the cluster. This matters because DLA is sensitive to where particles originate. Edge spawning simulates diffusion from a surrounding medium. If particles spawned randomly across the canvas, they'd appear inside the cluster, breaking the growth dynamics.
const edge = Math.floor(Math.random() * 4);
let x, y;
switch (edge) {
case 0: x = Math.random() * canvas.width; y = 0; break; // top
case 1: x = canvas.width; y = Math.random() * canvas.height; break; // right
case 2: x = Math.random() * canvas.width; y = canvas.height; break; // bottom
case 3: x = 0; y = Math.random() * canvas.height; break; // left
}
The maxWalkers cap prevents unbounded population growth. If walkers accumulated without limit, the simulation would slow as the cluster grew dense. The spawn rate — walkers added per frame — controls the flow. Too few and the cluster grows slowly with sparse branches. Too many and the particle density obscures the structure, a gray cloud of walkers surrounding the growth.
Depth-Based Coloring
Each stuck particle carries a depth — its generational distance from a seed point. The visualization maps this depth to color: shallow particles near seeds are teal, deep particles on branch tips are terracotta. This isn't just aesthetic. The color gradient reveals the growth topology: dark centers surrounded by lighter growth, branching outward.
const maxDepth = Math.max(...cluster.map(c => c.depth), 1);
for (const c of cluster) {
const t = Math.min(c.depth / 50, 1);
// Gradient from teal to terracotta
const r = Math.round(45 + (184 - 45) * t);
const g = Math.round(106 + (92 - 106) * t);
const b = Math.round(93 + (56 - 93) * t);
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
ctx.beginPath();
ctx.arc(c.x, c.y, 1.5, 0, Math.PI * 2);
ctx.fill();
}
The depth divides by 50 — a normalization chosen empirically so that typical clusters show the full gradient. Too low and everything turns terracotta. Too high and it's all teal. The actual depth values are unbounded — a long-running simulation might accumulate depths in the hundreds — but the color formula caps at 1, saturating at terracotta for the outermost growth.
What This Reveals
Diffusion-limited aggregation demonstrates how one simple rule produces infinite complexity. Particles walk randomly. They stick on contact. The result is fractal: dendrites, lightning, frost patterns, mineral deposits — all from Brownian motion and collision. No blueprint. No plan. Just accretion.
The structure emerges from screening — the branching arms of the cluster catch incoming walkers before they can reach the interior. Growth concentrates on the tips, creating fingers that split and branch. The longer a cluster grows, the more pronounced its dendrites become. Click to add seeds in different locations and watch the structures compete for walkers, their growth zones shadowing each other.
What you're seeing is a geometry that arises from randomness constrained by contact. The random walk provides the motion. The cluster provides the boundary. The meeting point — the moment of attachment — is where chaos crystallizes into structure.