Skip to the plate
FORMA PUBLIC DOMAIN GENERATIVE ATLAS / ED. 0.28
Plate 42, Koch Snowflake: a still of the rewriting / edge substitution plate as the atlas renders it, in the fractals accent.

PL. 42  ·  FRACTALS / REWRITING / EDGE SUBSTITUTION

Koch Snowflake

Helge von Koch, 1904

OPEN THE LIVE PLATE ▸

DEFINITION

each side → four sides, each ⅓ the length
perimeter: L → (4/3)ⁿ·L
dimension: log 4 / log 3 ≈ 1.2619

NOTES

Von Koch built it to show a curve with no tangent anywhere could come from elementary geometry, not only from the analytic monsters of Weierstrass. Every rewriting multiplies the perimeter by 4/3 while the area converges to 8/5 of the starting triangle — an infinitely long fence around a finite lawn, and the standard first example of a fractal.

PROVENANCE

Origin
H. von Koch, "Sur une courbe continue sans tangente, obtenue par une construction géométrique élémentaire", Arkiv för Matematik 1, 1904
Standing
Public domain — early 20th-century mathematics
Constants
Depth n draws 3·4ⁿ segments; past 5 the detail is finer than the plate

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. 42 · KOCH SNOWFLAKE — Helge von Koch, 1904
//   each side → four sides, each ⅓ the length
//   perimeter: L → (4/3)ⁿ·L
//   dimension: log 4 / log 3 ≈ 1.2619
// 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=koch

float p_depth = 4 + chf('depth_tweak');       // iterations · live 1 .. 5
float p_peak  = 60 + chf('peak_tweak');       // peak angle (°) · live 20 .. 85

// 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)));
}

// Every rewriting turns each side into four, each a third the length, with
// the peak rotated off the segment — 60° is Koch's snowflake, the rest of
// the range the Cesàro family. Clockwise winding with a negative rotation
// keeps every peak on the outside, exactly as the plate builds it.
// Coordinates stay in the starting triangle's own unit frame.
int depth = int(rint(p_depth));

float pk = -p_peak * 3.14159265359 / 180.0;
float co = cos(pk), si = sin(pk);
float xs[] = {0.5, 1.0, 0.0, 0.5};
float ys[] = {0.0, 0.866, 0.866, 0.0};
for (int it = 0; it < depth; it++){
    float nx[], ny[];
    push(nx, xs[0]);  push(ny, ys[0]);
    for (int i = 0; i < len(xs) - 1; i++){
        float ax = xs[i], ay = ys[i], bx = xs[i + 1], by = ys[i + 1];
        float dx = (bx - ax) / 3.0, dy = (by - ay) / 3.0;
        float p1x = ax + dx, p1y = ay + dy;
        push(nx, p1x);                    push(ny, p1y);
        push(nx, p1x + dx * co - dy * si);  push(ny, p1y + dx * si + dy * co);
        push(nx, ax + dx * 2.0);          push(ny, ay + dy * 2.0);
        push(nx, bx);                     push(ny, by);
    }
    xs = nx;  ys = ny;
}

int prim = addprim(0, "polyline");
int n = len(xs);
for (int i = 0; i < n; i++){
    // canvas y runs down; negated so the flake sits as the plate shows it
    int pt = addpoint(0, set(xs[i], -ys[i], 0.0));
    // colour sweeps the bright lobe of the ramp once around the loop
    float u = float(i) / float(n - 1);
    setpointattrib(0, "Cd", pt, forma_ramp(0.8 + 0.3 * u));
    addvertex(0, prim, pt);
}

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. 42 · KOCH SNOWFLAKE — Helge von Koch, 1904
//   each side → four sides, each ⅓ the length
//   perimeter: L → (4/3)ⁿ·L
//   dimension: log 4 / log 3 ≈ 1.2619
// 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=koch

// 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_depth = 4 + forma_tweak("depth_tweak");     // iterations · live 1 .. 5
var p_peak  = 60 + forma_tweak("peak_tweak");     // peak angle (°) · live 20 .. 85

// 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.6362532314378768;   // 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]; }

// The Koch snowflake: start from a triangle, and on every segment replace the
// middle third with two sides of a bump at the peak angle — 60° is von Koch's,
// the slider makes it a family. Closed, scaled to 0.86 of the frame.
var pts0 = [[0.5, 0], [1, 0.866], [0, 0.866], [0.5, 0]];
var pk = -p_peak * Math.PI / 180, co = Math.cos(pk), si = Math.sin(pk);
for (var it = 0; it < Math.round(p_depth); it++){
  var next = [pts0[0]];
  for (var i = 0; i < pts0.length - 1; i++){
    var a = pts0[i], b = pts0[i + 1];
    var dx = (b[0] - a[0]) / 3, dy = (b[1] - a[1]) / 3;
    var p1 = [a[0] + dx, a[1] + dy];
    var p2 = [p1[0] + dx * co - dy * si, p1[1] + dx * si + dy * co];
    next.push(p1, p2, [a[0] + dx * 2, a[1] + dy * 2], b);
  }
  pts0 = next;
}
var minx = 1e9, maxx = -1e9, miny = 1e9, maxy = -1e9;
for (var j = 0; j < pts0.length; j++){
  var q = pts0[j];
  if (q[0] < minx) minx = q[0]; if (q[0] > maxx) maxx = q[0];
  if (q[1] < miny) miny = q[1]; if (q[1] > maxy) maxy = q[1];
}
var s = Math.min(forma_W * 0.86 / (maxx - minx), forma_H * 0.86 / (maxy - miny));
var ox = (forma_W - (maxx - minx) * s) / 2 - minx * s;
var oy = (forma_H - (maxy - miny) * s) / 2 - miny * s;
var pts = [];
for (var m = 0; m < pts0.length - 1; m++) pts.push(forma_pt(ox + pts0[m][0] * s, oy + pts0[m][1] * s));
createPath(pts, [], [], true);