All articles
14 min read

Generative Design: How to Create Algorithm-Driven Art and Architecture With Code

generative designcomputational designalgorithmic designparametric designcreative codingjavascripttutorial

Generative design is the practice of defining rules and letting algorithms create the output. Instead of drawing a shape by hand, you describe the logic behind it — growth patterns, physical forces, mathematical relationships — and the computer generates forms you never could have imagined. It's used everywhere: creative coding art, architectural facades, 3D-printed jewelry, furniture, industrial parts, and even urban planning.

This guide covers the core techniques of generative design with working JavaScript code you can run in your browser. No CAD software required — just a canvas and an algorithm.

What is generative design?

Traditional design is direct: you decide what goes where. Generative design is indirect: you define parameters, constraints, and objectives, then let a system explore the solution space. The designer becomes a curator — selecting the best outputs from hundreds or thousands of algorithmically generated variations.

The field spans multiple disciplines:

  • Generative art — algorithms that produce visual, sonic, or kinetic artwork (the focus of this tutorial)
  • Parametric architecture — buildings and structures where every dimension is driven by parameters (Zaha Hadid Architects, NBBJ, Bjarke Ingels Group)
  • Computational product design — topology-optimized parts, lattice structures, biomimetic forms (Autodesk Fusion, nTopology)
  • Algorithmic typography — fonts and lettering generated by rules and interpolation

What unites them is the core idea: define the system, not the artifact.

1. L-systems: growing organic structures

L-systems (Lindenmayer systems) are string-rewriting grammars that model biological growth. A set of rules is applied iteratively to an axiom string, and the result is interpreted as drawing instructions. They're behind some of the most iconic fractal plant images in computer graphics.

const canvas = document.createElement('canvas');
canvas.width = canvas.height = 600;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');

// L-system rules
const axiom = 'F';
const rules = { F: 'FF+[+F-F-F]-[-F+F+F]' };
const angle = 25 * Math.PI / 180;
const iterations = 4;

// Generate string
let str = axiom;
for (let i = 0; i < iterations; i++) {
  str = str.split('').map(c => rules[c] || c).join('');
}

// Draw
ctx.translate(300, 580);
ctx.strokeStyle = '#2d5a27';
ctx.lineWidth = 1;
const stack = [];
const len = 4;

for (const c of str) {
  if (c === 'F') {
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(0, -len);
    ctx.stroke();
    ctx.translate(0, -len);
  } else if (c === '+') {
    ctx.rotate(angle);
  } else if (c === '-') {
    ctx.rotate(-angle);
  } else if (c === '[') {
    ctx.save();
    stack.push(null);
  } else if (c === ']') {
    ctx.restore();
    stack.pop();
  }
}

Changing just the rules string or angle produces wildly different organisms — bushes, ferns, seaweed, coral, lightning. This is the essence of generative design: small parameter changes yield vast formal variety.

2. Space-filling curves: efficient beauty

Space-filling curves are continuous paths that visit every point in a 2D region. They appear in chip design (minimizing wire lengths), CNC toolpaths, and art. The Hilbert curve is the most famous.

const canvas = document.createElement('canvas');
canvas.width = canvas.height = 512;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');

function hilbert(order) {
  const n = 1 << order;
  const points = [];
  for (let i = 0; i < n * n; i++) {
    let rx, ry, s, d = i, x = 0, y = 0;
    for (s = 1; s < n; s *= 2) {
      rx = 1 & (d / 2);
      ry = 1 & (d ^ rx);
      if (ry === 0) {
        if (rx === 1) { x = s - 1 - x; y = s - 1 - y; }
        [x, y] = [y, x];
      }
      x += s * rx;
      y += s * ry;
      d = Math.floor(d / 4);
    }
    points.push([x, y]);
  }
  return points;
}

const order = 6;
const n = 1 << order;
const pts = hilbert(order);
const scale = canvas.width / n;

ctx.beginPath();
pts.forEach(([x, y], i) => {
  const px = x * scale + scale / 2;
  const py = y * scale + scale / 2;
  i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
});
ctx.strokeStyle = '#4a90d9';
ctx.lineWidth = 1.5;
ctx.stroke();

Space-filling curves demonstrate a core generative design principle: functional requirements can produce aesthetic forms. The curve isn't designed to be beautiful — it's designed to be efficient — but beauty emerges from the mathematical structure.

3. Reaction-diffusion: nature's pattern engine

Reaction-diffusion systems model how chemicals interact and diffuse through a medium. Alan Turing proposed them in 1952 to explain biological patterns — and they're remarkably accurate. Zebra stripes, leopard spots, coral growth, fingerprints: all reaction-diffusion.

const canvas = document.createElement('canvas');
const W = 200, H = 200;
canvas.width = W; canvas.height = H;
canvas.style.width = '400px'; canvas.style.height = '400px';
canvas.style.imageRendering = 'pixelated';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');

const dA = 1.0, dB = 0.5, feed = 0.055, kill = 0.062;
let a = Array.from({length: W * H}, () => 1);
let b = Array.from({length: W * H}, () => 0);

// Seed center
for (let y = H/2-10; y < H/2+10; y++)
  for (let x = W/2-10; x < W/2+10; x++)
    b[y * W + x] = 1;

function step() {
  const na = new Float64Array(W * H);
  const nb = new Float64Array(W * H);
  for (let y = 1; y < H-1; y++) {
    for (let x = 1; x < W-1; x++) {
      const i = y * W + x;
      const lapA = a[i-1] + a[i+1] + a[i-W] + a[i+W] - 4*a[i];
      const lapB = b[i-1] + b[i+1] + b[i-W] + b[i+W] - 4*b[i];
      const abb = a[i] * b[i] * b[i];
      na[i] = a[i] + dA * lapA - abb + feed * (1 - a[i]);
      nb[i] = b[i] + dB * lapB + abb - (kill + feed) * b[i];
    }
  }
  a = na; b = nb;
}

const img = ctx.createImageData(W, H);
function draw() {
  for (let s = 0; s < 10; s++) step();
  for (let i = 0; i < W * H; i++) {
    const v = Math.floor((1 - b[i]) * 255);
    img.data[i*4] = v; img.data[i*4+1] = v;
    img.data[i*4+2] = Math.floor(v * 0.8 + 50);
    img.data[i*4+3] = 255;
  }
  ctx.putImageData(img, 0, 0);
  requestAnimationFrame(draw);
}
draw();

By tweaking feed and kill parameters, you get entirely different patterns: spots, stripes, waves, spirals, mazes. Designers use reaction-diffusion for surface textures, architectural panels, and 3D-printed lattices.

4. Voronoi structures: optimal division of space

Voronoi diagrams divide a plane into regions based on proximity to seed points. They appear naturally in soap bubbles, giraffe skin, dried mud, and cell structures. In generative design, they're used for structural optimization — each cell edge represents a load path.

const canvas = document.createElement('canvas');
canvas.width = canvas.height = 512;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');

// Generate random seed points
const seeds = Array.from({length: 40}, () => ({
  x: Math.random() * 512,
  y: Math.random() * 512,
  color: 'hsl(' + Math.random()*360 + ', 60%, 70%)'
}));

// Brute-force Voronoi (pixel-by-pixel)
const img = ctx.createImageData(512, 512);
for (let y = 0; y < 512; y++) {
  for (let x = 0; x < 512; x++) {
    let minD = Infinity, closest = 0;
    for (let i = 0; i < seeds.length; i++) {
      const d = (x - seeds[i].x) ** 2 + (y - seeds[i].y) ** 2;
      if (d < minD) { minD = d; closest = i; }
    }
    // Parse HSL to draw — simplified: use index for color
    const hue = (closest * 360 / seeds.length) % 360;
    const [r, g, b] = hslToRgb(hue / 360, 0.6, 0.7);
    const idx = (y * 512 + x) * 4;
    img.data[idx] = r; img.data[idx+1] = g;
    img.data[idx+2] = b; img.data[idx+3] = 255;
  }
}
ctx.putImageData(img, 0, 0);

// Draw edges (distance to second-nearest)
for (let y = 0; y < 512; y++) {
  for (let x = 0; x < 512; x++) {
    let d1 = Infinity, d2 = Infinity;
    for (const s of seeds) {
      const d = Math.hypot(x - s.x, y - s.y);
      if (d < d1) { d2 = d1; d1 = d; }
      else if (d < d2) d2 = d;
    }
    if (d2 - d1 < 2) {
      const idx = (y * 512 + x) * 4;
      img.data[idx] = img.data[idx+1] = img.data[idx+2] = 30;
    }
  }
}
ctx.putImageData(img, 0, 0);

function hslToRgb(h, s, l) {
  const a = s * Math.min(l, 1 - l);
  const f = n => {
    const k = (n + h * 12) % 12;
    return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
  };
  return [f(0)*255|0, f(8)*255|0, f(4)*255|0];
}

Architects like Toyo Ito and Frei Otto have used Voronoi patterns in building facades and structural systems. The geometric elegance isn't decorative — it's structural.

5. Evolutionary algorithms: design by natural selection

Evolutionary algorithms are the most "generative" of all generative design techniques. You define a fitness function (what makes a design "good"), then breed a population of random designs through selection, crossover, and mutation — mimicking biological evolution.

const canvas = document.createElement('canvas');
canvas.width = 400; canvas.height = 400;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');

// Evolve a bridge truss: nodes connected by beams
// Fitness = minimize weight while supporting a load
const POP = 50, NODES = 8, GENS = 200;

function randomDesign() {
  return Array.from({length: NODES}, () => ({
    x: Math.random() * 400,
    y: Math.random() * 200 + 100,
    connections: Array.from({length: NODES}, () => Math.random() > 0.6)
  }));
}

function fitness(design) {
  // Simplified: prefer designs that span horizontally
  // with fewer connections (lighter) and nodes near top/bottom (structural)
  let span = 0, beams = 0;
  for (const n of design) span = Math.max(span, n.x);
  for (const n of design)
    for (const c of n.connections) if (c) beams++;
  const verticalSpread = design.reduce((s, n) =>
    s + Math.abs(n.y - 200), 0);
  return span * verticalSpread / (beams + 1);
}

function mutate(d) {
  return d.map(n => ({
    x: n.x + (Math.random() - 0.5) * 20,
    y: n.y + (Math.random() - 0.5) * 20,
    connections: n.connections.map(c =>
      Math.random() < 0.05 ? !c : c)
  }));
}

let pop = Array.from({length: POP}, randomDesign);
for (let g = 0; g < GENS; g++) {
  pop.sort((a, b) => fitness(b) - fitness(a));
  pop = pop.slice(0, POP / 2);
  while (pop.length < POP) pop.push(mutate(pop[Math.random()*pop.length|0]));
}

// Draw best design
const best = pop.sort((a, b) => fitness(b) - fitness(a))[0];
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, 400, 400);
ctx.strokeStyle = '#e94560';
ctx.lineWidth = 2;
for (let i = 0; i < best.length; i++) {
  for (let j = i + 1; j < best.length; j++) {
    if (best[i].connections[j]) {
      ctx.beginPath();
      ctx.moveTo(best[i].x, best[i].y);
      ctx.lineTo(best[j].x, best[j].y);
      ctx.stroke();
    }
  }
}
best.forEach(n => {
  ctx.fillStyle = '#eee';
  ctx.beginPath();
  ctx.arc(n.x, n.y, 4, 0, Math.PI * 2);
  ctx.fill();
});

Autodesk's Dreamcatcher and Fusion 360 generative design use this approach at industrial scale — generating thousands of structural designs optimized for weight, stress, manufacturing constraints, and material usage simultaneously.

6. Phyllotaxis: nature's arrangement algorithm

Phyllotaxis is how plants arrange leaves, seeds, and petals. The golden angle (137.5°) produces the optimal packing — no two elements overlap, and the pattern creates mesmerizing spirals. It's the basis for sunflower heads, pinecones, and succulent rosettes.

const canvas = document.createElement('canvas');
canvas.width = canvas.height = 512;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');

const golden = Math.PI * (3 - Math.sqrt(5)); // golden angle
const n = 800;
const cx = 256, cy = 256;

ctx.fillStyle = '#0a0a1a';
ctx.fillRect(0, 0, 512, 512);

for (let i = 0; i < n; i++) {
  const r = Math.sqrt(i) * 8;
  const theta = i * golden;
  const x = cx + r * Math.cos(theta);
  const y = cy + r * Math.sin(theta);
  const size = 2 + (i / n) * 4;
  const hue = (i * 0.5) % 360;

  ctx.fillStyle = 'hsl(' + hue + ', 70%, 55%)';
  ctx.beginPath();
  ctx.arc(x, y, size, 0, Math.PI * 2);
  ctx.fill();
}

Change the angle by just 0.1° and the pattern collapses into boring spokes. This sensitivity is what makes phyllotaxis so powerful in generative design — the golden angle is the one value that avoids all rational approximations, producing maximum variety from a single parameter.

7. Parametric surfaces: when math becomes architecture

Parametric surfaces use mathematical equations to define 3D forms. In architecture, tools like Grasshopper (Rhino) and Houdini generate building skins, roof structures, and interior partitions from equations. Here's a 2D slice — a parametric curve that could be a facade profile:

const canvas = document.createElement('canvas');
canvas.width = 600; canvas.height = 400;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');

ctx.fillStyle = '#f5f0e8';
ctx.fillRect(0, 0, 600, 400);

// Parametric ribbon — like an architectural facade section
const aParam = 3, bParam = 5;
let t = 0;

function draw() {
  ctx.fillStyle = 'rgba(245,240,232,0.03)';
  ctx.fillRect(0, 0, 600, 400);

  for (let i = 0; i < 500; i++) {
    const angle = (i / 500) * Math.PI * 2;
    const r1 = 120 + 40 * Math.sin(aParam * angle + t);
    const r2 = 120 + 40 * Math.cos(bParam * angle + t * 0.7);
    const x = 300 + r1 * Math.cos(angle);
    const y = 200 + r2 * Math.sin(angle);
    const alpha = 0.3 + 0.7 * (i / 500);

    ctx.fillStyle = 'rgba(50, 50, 80, ' + alpha + ')';
    ctx.beginPath();
    ctx.arc(x, y, 1.5, 0, Math.PI * 2);
    ctx.fill();
  }

  t += 0.01;
  requestAnimationFrame(draw);
}
draw();

By changing aParam and bParam, the curve morphs between organic and angular forms. Multiply this across a facade, varying parameters by elevation and orientation, and you get the kind of undulating surfaces seen in buildings by Zaha Hadid and UNStudio.

8. Agent-based design: emergent complexity

Agent-based systems model individual actors (ants, pedestrians, vehicles, particles) following simple local rules. Complex global patterns emerge from their interactions — a phenomenon called emergence. It's used in urban planning (pedestrian flow), logistics, and art.

const canvas = document.createElement('canvas');
canvas.width = canvas.height = 512;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');

// Ant colony: agents deposit pheromones, follow trails
const W = 256, H = 256;
const pheromone = new Float32Array(W * H);
const agents = Array.from({length: 500}, () => ({
  x: W/2 + (Math.random()-0.5)*20,
  y: H/2 + (Math.random()-0.5)*20,
  angle: Math.random() * Math.PI * 2
}));

ctx.fillStyle = '#000';
ctx.fillRect(0, 0, 512, 512);

function step() {
  // Diffuse and decay pheromones
  const next = new Float32Array(W * H);
  for (let y = 1; y < H-1; y++) {
    for (let x = 1; x < W-1; x++) {
      const i = y * W + x;
      next[i] = (pheromone[i-1] + pheromone[i+1] +
        pheromone[i-W] + pheromone[i+W]) * 0.24 * 0.99;
    }
  }

  // Move agents
  for (const a of agents) {
    // Sense ahead-left, ahead, ahead-right
    const sense = (da) => {
      const sx = Math.round(a.x + Math.cos(a.angle + da) * 5);
      const sy = Math.round(a.y + Math.sin(a.angle + da) * 5);
      if (sx >= 0 && sx < W && sy >= 0 && sy < H)
        return pheromone[sy * W + sx];
      return 0;
    };
    const left = sense(-0.5), fwd = sense(0), right = sense(0.5);
    if (fwd >= left && fwd >= right) { /* go straight */ }
    else if (left > right) a.angle -= 0.3;
    else a.angle += 0.3;
    a.angle += (Math.random() - 0.5) * 0.2;

    a.x += Math.cos(a.angle) * 1.5;
    a.y += Math.sin(a.angle) * 1.5;
    if (a.x < 0) a.x += W; if (a.x >= W) a.x -= W;
    if (a.y < 0) a.y += H; if (a.y >= H) a.y -= H;

    // Deposit pheromone
    const i = Math.round(a.y) * W + Math.round(a.x);
    if (i >= 0 && i < W * H) next[i] = Math.min(next[i] + 1, 5);
  }
  pheromone.set(next);
}

const img = ctx.createImageData(W, H);
function draw() {
  for (let s = 0; s < 3; s++) step();
  for (let i = 0; i < W * H; i++) {
    const v = Math.min(pheromone[i] * 60, 255);
    img.data[i*4] = v * 0.3;
    img.data[i*4+1] = v * 0.8;
    img.data[i*4+2] = v;
    img.data[i*4+3] = 255;
  }
  ctx.putImageData(img, 0, 0);
  requestAnimationFrame(draw);
}
draw();

Ant colony algorithms famously find shortest-path solutions competitive with human engineers. In generative design, they create networks, textures, and spatial organizations that feel organic because they are organic — modeled on biology rather than imposed by geometry.

Generative design tools and software

Beyond coding from scratch, several professional tools offer generative design capabilities:

  • Grasshopper (Rhino) — visual programming for parametric architecture and product design
  • Autodesk Fusion 360 — generative design for manufacturing: define loads, constraints, and manufacturing methods; the system generates optimized geometry
  • Houdini — procedural 3D for VFX, architecture visualization, and generative art
  • Processing / p5.js — code-based creative tools ideal for drawing with code and rapid prototyping
  • TouchDesigner — node-based tool for real-time generative visuals and installations
  • nTopology — implicit modeling for lattice structures and topology optimization
  • Cables.gl — browser-based visual patching for WebGL generative work
  • Plain JavaScript + Canvas/WebGL — as shown throughout this tutorial and on Lumitree's micro-worlds

The philosophy of generative design

What makes generative design different from traditional design isn't the technology — it's the mindset. You're designing a system, not a thing. The system might produce one beautiful result or a million. Your job is to tune the parameters, define the constraints, and curate the output.

This is exactly how Lumitree works: visitors provide seeds (ideas), algorithms generate micro-worlds, and the tree grows into forms no single designer could have planned. Each branch is a unique generative design artifact — an artwork created under constraints, small enough to fit in 50KB but rich enough to surprise.

The techniques in this guide — L-systems, space-filling curves, reaction-diffusion, Voronoi structures, evolutionary algorithms, phyllotaxis, parametric surfaces, and agent-based systems — are building blocks. Combine them. Use Perlin noise to vary L-system parameters. Feed Voronoi cells into an evolutionary optimizer. Layer reaction-diffusion on a parametric surface. The design space is infinite, and you've just drawn the first line.

Related articles