Skip to the plate
FORMA PUBLIC DOMAIN GENERATIVE ATLAS / ED. 0.28
Plate 17, Heighway Dragon: a still of the l-system / paper fold plate as the atlas renders it, in the fractals accent.

PL. 17  ·  FRACTALS / L-SYSTEM / PAPER FOLD

Heighway Dragon

John Heighway, William Harter, Bruce Banks, 1966

OPEN THE LIVE PLATE ▸

DEFINITION

fold a strip in half n times, unfold to 90°
turn sequence: Lₙ₊₁ = Lₙ · L · reverse(swap(Lₙ))

NOTES

Fold a strip of paper in half repeatedly, then open every crease to a right angle. Three NASA physicists worked it out in 1966; Michael Crichton put it in the chapter headings of Jurassic Park. It tiles the plane with copies of itself and never crosses its own path.

PROVENANCE

Origin
Heighway, Harter and Banks at NASA, 1966
Standing
Public domain — a folding rule
Constants
Order above 16 exceeds what a plate this size can resolve

HOUDINI · VEX

The same published mathematics as a Detail Wrangle body. Paste it into a Wrangle with Run Over set to Detail; every constant is the published value plus a tweak channel, so Create Spare Parameters gives a slider that starts where the paper does.

// FORMA — PL. 17 · HEIGHWAY DRAGON — John Heighway, William Harter, Bruce Banks, 1966
//   fold a strip in half n times, unfold to 90°
//   turn sequence: Lₙ₊₁ = Lₙ · L · reverse(swap(Lₙ))
// Paste into a Detail Wrangle (Run Over: Detail), no inputs needed.
// Written from the published mathematics, not adapted from any code.
// Constants arrive at their published values. Press the node's Create
// Spare Parameters button and every tweak becomes a slider — starting
// at 0, the published figure, and moving in the constant's own units.
// https://forma-gen.com/#plate=dragon

float p_order = 13 + chf('order_tweak');      // folds · live 6 .. 17

// The plate's own colour: FORMA's FRACTALS accent as a cosine ramp,
// brightest near t = 0 and t = 1, near-black around t = 0.5.
vector forma_ramp(float t){
  return set(
    0.46 + 0.5 * cos(6.28318530718 * (t + 0)),
    0.1389 + 0.151 * cos(6.28318530718 * (t + 0.05)),
    0.1912 + 0.2078 * cos(6.28318530718 * (t + 0.1)));
}

// Fold a strip in half `order` times and open every crease to a right
// angle: the turn sequence Lₙ₊₁ = Lₙ · L · reverse(swap(Lₙ)), walked on the
// integer lattice in quarter-turns. The heading stays an integer 0–3 the
// whole way, so the curve cannot drift off its own lattice; coordinates are
// the lattice steps themselves — scale to taste.
int order = int(rint(p_order));

int turns[];
for (int g = 0; g < order; g++){
    // reverse-and-negate the sequence so far, then splice around a left fold
    int rev[];
    for (int j = len(turns) - 1; j >= 0; j--) push(rev, -turns[j]);
    push(turns, 1);
    foreach (int tn; rev) push(turns, tn);
}

// canvas headings, y down: 0 = +x, 1 = +y, 2 = -x, 3 = -y
int dxs[] = {1, 0, -1, 0};
int dys[] = {0, 1, 0, -1};
int x = 0, y = 0, dir = 0;
int n = len(turns) + 1;
int prim = addprim(0, "polyline");
int pt = addpoint(0, set(0.0, 0.0, 0.0));
setpointattrib(0, "Cd", pt, forma_ramp(0.8));
addvertex(0, prim, pt);
for (int i = 0; i < len(turns); i++){
    x += dxs[dir];  y += dys[dir];
    // canvas y runs down; negated so the dragon sits as the plate shows it
    pt = addpoint(0, set(float(x), -float(y), 0.0));
    // colour sweeps the bright lobe of the ramp along the fold
    float u = float(i + 1) / float(n - 1);
    setpointattrib(0, "Cd", pt, forma_ramp(0.8 + 0.3 * u));
    addvertex(0, prim, pt);
    dir = (dir + turns[i] + 4) % 4;
}

AFTER EFFECTS · EXPRESSION

The same published mathematics as a Shape Layer path expression. Paste it onto a Path property; every constant is the published value plus a Slider Control named _tweak, so a bare paste already draws the figure and each slider moves one constant in its own units. Trim Paths is the comet.

// FORMA — PL. 17 · HEIGHWAY DRAGON — John Heighway, William Harter, Bruce Banks, 1966
//   fold a strip in half n times, unfold to 90°
//   turn sequence: Lₙ₊₁ = Lₙ · L · reverse(swap(Lₙ))
// After Effects port — paste onto a Shape Layer's Path property
// (Contents › Shape › Path). Written from the published mathematics, not
// adapted from any code. Constants arrive at their published values; add a
// Slider Control (Effect › Expression Controls) named <k>_tweak and that
// constant moves in its own units, starting at 0 — the published figure.
// The plate's comet and its reveal are Trim Paths; the stroke colour is
// FORMA's FRACTALS accent, #FF4D6A. Animation runs on time.
// https://forma-gen.com/#plate=dragon

// A missing slider reads 0, so a bare paste already draws the figure.
function forma_tweak(n){ try { return effect(n)("Slider"); } catch (e){ return 0; } }
var p_order = 13 + forma_tweak("order_tweak");    // folds · live 6 .. 17

// The frame: the plate's W × H canvas is this comp, with the origin at the
// layer's anchor; canvas y already runs down, as After Effects' does.
var forma_W = thisComp.width, forma_H = thisComp.height, forma_t = time;
var forma_phase = 0.376493674935773;   // this plate's own fixed phase, as the page has it
function forma_pt(x, y){ return [x - forma_W / 2, y - forma_H / 2]; }

// Heighway's dragon by paper-folding: each fold appends a 1 and the reverse
// of the previous turns negated, then the turns are walked on the unit grid.
// 2^folds segments — 8,192 at the published 13, 131,072 at the top of the
// range, which After Effects carries but not quickly. Scaled to 0.88 of the
// frame, centred. The page draws it in over ~18 s: Trim Paths, End 0 → 100%.
var turns = [];
for (var f = 0; f < Math.round(p_order); f++){
  var rev = [];
  for (var k = turns.length - 1; k >= 0; k--) rev.push(-turns[k]);
  turns = turns.concat([1], rev);
}
var x = 0, y = 0, dir = 0, raw = [[0, 0]];
var minx = 0, maxx = 0, miny = 0, maxy = 0;
for (var i = 0; i < turns.length; i++){
  x += Math.round(Math.cos(dir * Math.PI / 2));
  y += Math.round(Math.sin(dir * Math.PI / 2));
  raw.push([x, y]);
  if (x < minx) minx = x; if (x > maxx) maxx = x;
  if (y < miny) miny = y; if (y > maxy) maxy = y;
  dir = (dir + turns[i] + 4) % 4;
}
var s = Math.min(forma_W * 0.88 / (maxx - minx + 1), forma_H * 0.88 / (maxy - miny + 1));
var ox = (forma_W - (maxx - minx) * s) / 2 - minx * s;
var oy = (forma_H - (maxy - miny) * s) / 2 - miny * s;
var pts = [];
for (var j = 0; j < raw.length; j++) pts.push(forma_pt(ox + raw[j][0] * s, oy + raw[j][1] * s));
createPath(pts, [], [], false);