Brandon Cate · Software Engineer

Algorithmic trees in a wind field

The itch

I’ve wanted to play with algorithmic art for a long time. There’s something about it that stays in my head — a few small rules, some math, and the output feels alive in a way that procedural code rarely does. I never had a real reason to build anything in that space. A portfolio site was the excuse.

So the site is one page. Four trees sway in a wind field you can’t see directly, though you can see the shape of it in the particles drifting across the ground. Everything is procedural, deterministic per seed, and reroll-able from a refresh.

Layers

The L-system

An L-system is a tiny grammar. Which is a fancy word for a set of rules that rewrite symbols. Each rule has the shape “when you see X, replace it with this sequence.” You start with an axiom, a starting point, and a few rules:

const grammar = {
  axiom: 'F',
  rules: { F: 'F[+F][-F]' },
  iterations: 4,
};

Apply the rules iterations times and each F gets substituted in place. The string explodes:

F
F[+F][-F]
F[+F][-F][+F[+F][-F]][-F[+F][-F]]

Each letter and symbol in those strings is a drawing instruction. F means draw a line forward. So FFF draws three segments stacked end to end.

+ and - rotate. Each one tilts the next segment by a small fixed angle — say, 22.5° — to the right or left. So F+F draws a segment, rotates, then draws another going off at an angle. An elbow.

[ and ] are for branching. [ saves the current position and direction; ] returns to the last saved point. So F[+F][-F] draws a trunk, saves, branches right with a sub-segment, returns to the trunk top, then branches left with another. A Y:

To actually walk and render the string, you use a turtle — a cursor that starts at the bottom of the canvas pointing up and moves in response to each character:

function walk(string) {
  const stack = [];
  let pos = { x: 0, y: 0 }
  let angle = -Math.PI / 2;

  for (const ch of string) {
    if (ch === 'F') {
      const next = step(pos, angle);
      drawLine(pos, next);
      pos = next;
    } else if (ch === '+') angle += delta;
    else if (ch === '-') angle -= delta;
    else if (ch === '[') stack.push({ pos, angle });
    else if (ch === ']') ({ pos, angle } = stack.pop());
  }
}

That’s the classical setup. It produces a flat path — a tree-shape made of line segments. A static tree.

The wind field

The wind isn’t some kind of looping animation. It’s a function — wind(x, y, t) — that takes a position in space and a moment in time and returns a vector. Sample it anywhere, you get the local wind at that point.

It’s built out of a few small parts, each tuned by ear. A constant baseline so samples never come back as zero (without it, anything reading the field would look frozen between gusts). A spatial term so the vector varies across x and y at the same instant. A temporal term so the field changes over time. And a gust function — a slow oscillator that modulates the temporal one — so the wind has rhythm instead of buzzing at a constant frequency.

function wind(x, y, t) {
  const spatial  = sin(x * spatialFreqX + y * spatialFreqY);
  const temporal = sin(t * temporalFreq);
  const gust     = sin(t * gustFreq) ** 2 * gustAmp;
  return bias + amplitude * spatial * (temporal + gust);
}

The actual code has more — clamps, fades, a peak-alpha kick so gusts are visually emphasised — but the shape is the same. A field you can sample anywhere, at any moment.

Applying wind to the tree

So far we have a static tree and a wind field, but no link between them. A static tree is just a sequence of line segments. There’s nowhere to apply wind. Wind needs to be sampled at each joint, and the rotation it produces has to compose down through the children so a wobble near the trunk carries the whole branch with it.

To handle this my turtle, his name is now Jeff, doesn’t draw lines. It builds a graph:

function buildGraph(string) {
  const stack = [];
  let current = root
  let angle = -Math.PI / 2;

  for (const ch of string) {
    if (ch === 'F') {
      const next = addVertex({
        pos: step(current.pos, angle),
        parent: current,            // every vertex knows its parent joint
      });
      current = next;
    } else if (ch === '+') angle += delta;
    else if (ch === '-') angle -= delta;
    else if (ch === '[') stack.push({ vertex: current, angle });
    else if (ch === ']') ({ vertex: current, angle } = stack.pop());
  }
  return root;
}

The shape of the loop is identical to walk. The difference is one line — parent: current — which turns the output from a sequence of draws into a graph where every vertex knows what it hangs from.

The vertices in that graph are what we’ll treat as joints — pivot points the wind can act on. Each one carries a position, a parent, and a compliance value that controls how strongly it bends:

type Joint = {
  pos: { x: number; y: number };
  parent: Joint | null;
  compliance: number;  // close to 0 at the trunk, larger toward the tips
  swayAngle: number;   // updated each frame
};

Each frame, every joint samples the field at its position and rotates by compliance × wind. Its children inherit the rotation cumulatively as they’re re-posed, so the branches bend together but each joint contributes its own slight wobble.

// every frame, for every joint in every tree:
joint.swayAngle = wind(joint.pos.x, joint.pos.y, t) * joint.compliance;

Particles work the same way. They aren’t part of any tree, but they read the same field:

// every frame, for every particle:
const drift = wind(particle.x, particle.y, t);
particle.x += drift.x;
particle.y += drift.y;

What I like about this is that the trees and the particles don’t know about each other. They share no state. They just both read the same function. The coupling that makes them feel like one system is purely mathematical.

Close

The thing I didn’t expect when I started this is how much the shared math matters for the feel of it. I’d thought of the trees, the wind, and the particles as separate systems I’d compose at the end. They became one system the moment the same wind function was feeding both the trees and the particles and because of that the page stopped looking like layered animations and started looking like a place.

I want to do more of this. Reaction-diffusion, flow fields, agent-based growth — there’s a whole corner of algorithmic art I’ve only barely touched, and each one has the same “few small rules, output feels alive” shape that pulled me into L-systems in the first place. The portfolio was the excuse this time, hopefully I’ll find another one.

“In the beginning, the Universe was created. This has made a lot of people very angry and been widely regarded as a bad move.”