L-system

L-시스템

A grammar that repeatedly rewrites symbols to grow branching, plant-like structures. Devised by biologist Aristid Lindenmayer in 1968 to model plant growth.

Also known as: Lindenmayer systemTurtle graphics fractal
···
js
const c = document.createElement('canvas');
document.body.appendChild(c);
c.style.width = '100%'; c.style.height = '100%';
const ctx = c.getContext('2d');
const cs = getComputedStyle(document.documentElement);
const ACCENT3 = cs.getPropertyValue('--accent-3').trim() || '#18c29c';
let w, h;
function resize() {
  const dpr = Math.min(devicePixelRatio || 1, 2);
  w = innerWidth; h = innerHeight;
  c.width = w * dpr; c.height = h * dpr;
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
addEventListener('resize', resize);
resize();

function expand(axiom, rules, gens) {
  let s = axiom;
  for (let g = 0; g < gens; g++) { let next = ''; for (const ch of s) next += rules[ch] ?? ch; s = next; }
  return s;
}
const RULES = { X: 'F+[[X]-X]-F[-FX]+X', F: 'FF' };
const STR = expand('X', RULES, 4);
const ANGLE = (25 * Math.PI) / 180, STEP = 6.2;

function buildSegments() {
  let x = 0, y = 0, ang = -Math.PI / 2, stack = [];
  const segs = [];
  let minX = 0, maxX = 0, minY = 0, maxY = 0;
  for (const ch of STR) {
    if (ch === 'F') {
      const nx = x + Math.cos(ang) * STEP, ny = y + Math.sin(ang) * STEP;
      segs.push([x, y, nx, ny]);
      x = nx; y = ny;
      minX = Math.min(minX, x); maxX = Math.max(maxX, x); minY = Math.min(minY, y); maxY = Math.max(maxY, y);
    } else if (ch === '+') ang += ANGLE;
    else if (ch === '-') ang -= ANGLE;
    else if (ch === '[') stack.push([x, y, ang]);
    else if (ch === ']') [x, y, ang] = stack.pop();
  }
  return { segs, bounds: [minX, maxX, minY, maxY] };
}
const { segs, bounds } = buildSegments();
const treeW = bounds[1] - bounds[0], treeH = bounds[3] - bounds[2];

function draw(frac) {
  const dpr = Math.min(devicePixelRatio || 1, 2);
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  ctx.clearRect(0, 0, w, h);
  const scale = Math.min(w / treeW, h / treeH) * 0.82;
  ctx.save();
  ctx.translate(w / 2 - ((bounds[0] + bounds[1]) / 2) * scale, h * 0.94 - bounds[3] * scale);
  ctx.scale(scale, scale);
  ctx.strokeStyle = ACCENT3; ctx.lineWidth = 1.4 / scale; ctx.lineCap = 'round';
  const count = Math.floor(segs.length * frac);
  for (let i = 0; i < count; i++) { const s = segs[i]; ctx.beginPath(); ctx.moveTo(s[0], s[1]); ctx.lineTo(s[2], s[3]); ctx.stroke(); }
  ctx.restore();
}
let start = performance.now();
const GROW_MS = 1100, HOLD_MS = 2200;
(function loop(t) {
  const elapsed = (t - start) % (GROW_MS + HOLD_MS);
  draw(Math.min(1, elapsed / GROW_MS));
  requestAnimationFrame(loop);
})(performance.now());

Start from a single symbol, "X", and repeatedly apply a rule like "wherever you see X, replace it with F+[[X]-X]-F[-FX]+X" — the string grows exponentially with each generation. Read that string as turtle graphics and it becomes a drawing: F draws a line forward, + and - turn left or right, and [ / ] push and pop the current position and heading, so a branch can split off and the turtle can return to the trunk.

That one rule alone produces self-similar, fractal structures that read as ferns, trees and shrubs. Purely geometric fractals like the Koch snowflake come from the exact same mechanism (F → F+F--F+F).

This demo expands the string four generations ahead, then animates the turtle tracing that path. It's a mainstay of procedural vegetation, branch-pattern backgrounds and fractal-art posters.

When to use

Use it to procedurally generate trees and plants, or for organic branch patterns in a background. Changing the angle and rule string alone can make it read as an entirely different species.