Diffusion Limited Aggregation
Diffusion-Limited Aggregation (DLA), first described by Witten and Sander in 1981, demonstrates how fractal structures can emerge from random motion. This implementation, adapted from the Complexity Explorables model "Particularly Stuck" (Janina Schöneberger & Dirk Brockmann), begins with a single stationary seed particle at the center. Free particles undergo noisy, spiraling trajectories; when one collides with the growing aggregate, it sticks permanently.
The resulting structure is a branching, tree-like form with characteristic fractal dimension. This self-similar branching arises because particles are more likely to collide with the tips of existing branches—outer regions "shadow" inner ones, limiting growth there. DLA captures the essential physics of many natural phenomena: electrodeposition, mineral dendrites, lightning paths, and the growth of certain biological structures like coral and neuronal arbors.
import { Agent, Environment, CanvasRenderer, utils } from "flocc"; import { Button, Panel, Slider } from "flocc-ui"; /* ----- PARAMETERS ----- */ const SPEED = 0.8; const NOISE = 0.8; const ATTRACTION = 0.05; const TWIST = 0.05; const TWISTMIX = 0.0; /* ---------------------- */ utils.seed(1); const [width, height] = [128, 128]; const scale = Math.min(window.innerWidth, window.innerHeight) / 128; const environment = new Environment({ width, height, torus: false }); const renderer = new CanvasRenderer(environment, { width: width * scale, height: height * scale, origin: { x: -width / 2, y: -height / 2 }, scale }); renderer.mount("#container"); environment.set("speed", SPEED); environment.set("noise", NOISE); environment.set("attraction", ATTRACTION); environment.set("twist", TWIST); const color = (agent) => { return agent.get("state") ? "#bbb" : "#000"; }; function addAgent() { const agent = new Agent({ x: utils.random(-width / 2, width / 2), y: utils.random(-height / 2, height / 2), polarity: utils.uniform(), state: true, size: scale / 2, color }); agent.addRule(tick); environment.addAgent(agent); } function tick(agent) { const { speed, noise, attraction, twist } = environment.getData(); let { x, y, polarity, state } = agent.getData(); if (!state) return; const P = polarity < TWISTMIX ? 1 : -1; const r = utils.distance(agent, { x: 0, y: 0 }); const dx = speed * ((-attraction * x + P * twist * y) / r) + noise * utils.random(-0.5, 0.5, true) * Math.sqrt(speed); const dy = speed * ((-attraction * y - P * twist * x) / r) + noise * utils.random(-0.5, 0.5, true) * Math.sqrt(speed); x += dx; y += dy; const statics = environment.memo(() => environment.getAgents().filter((a) => !a.get("state")) ); if (r - 1 < environment.get("radius")) { for (let i = 0; i < statics.length; i++) { if (utils.distance(agent, statics[i]) < 1) { state = false; if (r > environment.get("radius")) { environment.set("radius", r); } if (environment.getAgents().length < 2000) addAgent(); break; } } } return { x, y, state }; } function populate() { for (let i = 0; i < 300; i++) { addAgent(); } const agent = new Agent({ x: 0, y: 0, state: false, size: scale / 2, color }); agent.addRule(tick); environment.addAgent(agent); environment.set("radius", 0); } function UI() { new Panel(environment, [ new Slider({ name: "speed", min: 0.4, max: 5 }), new Slider({ name: "noise", min: 0, max: 2 }), new Slider({ name: "attraction", min: 0, max: 0.5 }), new Slider({ name: "twist", min: -0.25, max: 0.25 }), new Button({ label: "Reset Parameters", onClick() { environment.set("speed", SPEED); environment.set("noise", NOISE); environment.set("attraction", ATTRACTION); environment.set("twist", TWIST); } }), new Button({ label: "Reset Environment", onClick() { while (environment.getAgents().length > 0) { const agents = environment.getAgents(); environment.removeAgent(agents[agents.length - 1]); } populate(); } }) ]); } function setup() { populate(); UI(); } function run() { environment.tick(); requestAnimationFrame(run); } setup(); run();