PL. 72 · FRACTALS / REWRITING / SYMMETRIC FOLD
Lévy C Curve
Ernesto Cesàro, 1906 · Georg Faber, 1910 · Paul Lévy, 1938
OPEN THE LIVE PLATE ▸DEFINITION
F → +F−−F+ (turn θ, 45° is the C) every segment folds outward each generation dimension → 2 as the folds close
NOTES
Fold every segment of a line into a right-angle tent, forever, and the C curve appears — Cesàro described it first and Faber analysed the same family, both for its differentiability; Lévy was the one who set out its self-similarity and the geometric construction, and the name went to him. It is the koch construction’s wilder sibling: koch’s bump keeps the curve simple, while the C’s symmetric fold makes it cross itself into a paved, furred braid. The angle walks the Cesàro–Faber family — de Rham curves, with the classical C at 45°.
PROVENANCE
- Origin
- E. Cesàro, "Fonctions continues sans dérivée", Archiv der Mathematik und Physik (3) 38 (1906), 57–63; G. Faber, 1910, on the same family; P. Lévy, "Les courbes planes ou gauches et les surfaces composées de parties semblables au tout", Journal de l’École Polytechnique, 1938, 227–247 and 249–291, which is where the self-similar construction and the name come from
- Standing
- Public domain — early 20th-century geometry
- Constants
- Generations are locked: each doubles the segment count. The fold angle is free — the whole range is alive
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. 72 · LÉVY C CURVE — Ernesto Cesàro, 1906 · Georg Faber, 1910 · Paul Lévy, 1938
// F → +F−−F+ (turn θ, 45° is the C)
// every segment folds outward each generation
// dimension → 2 as the folds close
// 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=levyc
float p_gens = 12 + chf('gens_tweak'); // generations · live 8 .. 13
float p_ang = 45 + chf('ang_tweak'); // θ — fold (deg) · live 30 .. 58
// 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)));
}
// F → +F−−F+ with turn θ: every segment folds outward into a symmetric
// tent, forever — the classical C at 45°, the wider Cesàro–Faber family
// either side. Tokens are integers (0 F, 1 +, 2 −); the heading accumulates
// exactly as the plate's own turtle does. Steps are unit length — scale to
// taste.
int forma_ruleF[] = {1, 0, 2, 2, 0, 1};
int gens = int(rint(p_gens));
int seq[] = {0};
for (int g = 0; g < gens; g++){
int next[];
foreach (int sym; seq){
if (sym == 0) foreach (int r; forma_ruleF) push(next, r);
else push(next, sym);
}
seq = next;
}
float turn = p_ang * 3.14159265359 / 180.0;
float ang = 0.0, x = 0.0, y = 0.0;
int steps = 0;
foreach (int sym; seq) if (sym == 0) steps++;
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);
int i = 0;
foreach (int sym; seq){
if (sym == 1){ ang += turn; continue; }
if (sym == 2){ ang -= turn; continue; }
x += cos(ang); y += sin(ang); i++;
// canvas y runs down; negated so the braid sits as the plate shows it
pt = addpoint(0, set(x, -y, 0.0));
// colour sweeps the bright lobe of the ramp along the path
setpointattrib(0, "Cd", pt, forma_ramp(0.8 + 0.3 * float(i) / float(steps)));
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
// FORMA — PL. 72 · LÉVY C CURVE — Ernesto Cesàro, 1906 · Georg Faber, 1910 · Paul Lévy, 1938
// F → +F−−F+ (turn θ, 45° is the C)
// every segment folds outward each generation
// dimension → 2 as the folds close
// 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=levyc
// 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_gens = 12 + forma_tweak("gens_tweak"); // generations · live 8 .. 13
var p_ang = 45 + forma_tweak("ang_tweak"); // θ — fold (deg) · live 30 .. 58
// 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.23880349309183657; // 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 Lévy C curve as an L-system: F → +F−−F+, turning by the slider's angle
// (45° is Lévy's; the slider makes it a family), one unit step per F. The
// generation count is 2^gens segments — 4,096 at the published 12. Fitted to
// 0.82 of the frame; the page grows it over 21 s — Trim Paths, End 0 → 100%.
var gens = Math.round(p_gens), turn = p_ang * Math.PI / 180;
var s = 'F';
for (var g = 0; g < gens; g++) s = s.replace(/F/g, '+F--F+');
var ang = 0, x = 0, y = 0;
var raw = [[0, 0]];
for (var i = 0; i < s.length; i++){
var ch = s.charAt(i);
if (ch === '+') ang += turn;
else if (ch === '-') ang -= turn;
else { x += Math.cos(ang); y += Math.sin(ang); raw.push([x, y]); }
}
var minx = 1e9, maxx = -1e9, miny = 1e9, maxy = -1e9;
for (var j = 0; j < raw.length; j++){
var q = raw[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 sc = Math.min(forma_W * 0.82 / ((maxx - minx) || 1), forma_H * 0.82 / ((maxy - miny) || 1));
var ox = (forma_W - (maxx - minx) * sc) / 2 - minx * sc;
var oy = (forma_H - (maxy - miny) * sc) / 2 - miny * sc;
var pts = [];
for (var m = 0; m < raw.length; m++) pts.push(forma_pt(ox + raw[m][0] * sc, oy + raw[m][1] * sc));
createPath(pts, [], [], false);