Skip to the plate
FORMA PUBLIC DOMAIN GENERATIVE ATLAS / ED. 0.28
Plate 135, Brownian Bridge: a still of the stochastic / midpoint displacement plate as the atlas renders it, in the curves accent.

PL. 135  ·  CURVES / STOCHASTIC / MIDPOINT DISPLACEMENT

Brownian Bridge

Paul Lévy, 1948

OPEN THE LIVE PLATE ▸

DEFINITION

B(0) = B(1) = 0
B(mid) = ½(B(left)+B(right)) + Z·σ·Δ^H,  Z ~ N(0,1)
Δ = right − left,  H = ½ is the Lévy bridge (Lévy, 1948)
H ≠ ½ is the fractional generalisation

NOTES

Fractional Brownian motion, elsewhere in this atlas, sums octaves of a two-dimensional noise lattice; this is the same idea run in one dimension, and by the older, more literal method. Pin both ends at zero — the bridge condition — then set the midpoint from the average of its two neighbours plus a random offset, treat each half as its own smaller bridge, and repeat: Lévy described this in 1948, decades before noise summation became the standard route to a fake mountain range. Every level halves the interval and halves the added variance, so a jagged path emerges from nothing but local coin flips, never the global sum fbm computes. Takagi, also in this order, refines a curve by the same halving-of-scale logic, but with a fixed tent function standing in for the random step; here the step really is random, seeded so a given run repeats but no two seeds agree past the first level. Pushing H away from one half was never part of the 1948 construction — it is the later fractional generalisation the computer-graphics literature built on top of it, trading the true bridge for something smoother or rougher on demand.

PROVENANCE

Origin
P. Lévy, Processus stochastiques et mouvement brownien, Gauthier-Villars, Paris, 1948 — the interpolation-by-successive-bisection construction of Brownian motion, consolidating results Lévy had already published across several papers in the late 1930s
Standing
Public domain — pre-DOI mathematics
Rendering lineage
The computer-graphics use of midpoint displacement to synthesise terrain and clouds is A. Fournier, D. Fussell and L. Carpenter, "Computer rendering of stochastic models", Communications of the ACM 25(6), 1982, 371-384, doi:10.1145/358523.358553 — verified on Crossref against title, all three authors, venue, volume, issue and year. Fournier, Fussell and Carpenter applied the construction to graphics; they did not discover it, so this plate credits the mathematics to Lévy and the rendering technique to them separately rather than folding one citation into the other, and src above points at their paper rather than the pre-DOI 1948 book because that is the identifier that actually resolves
Constants
levels is a cost-and-shape dial on the pattern the Rauzy fractal plate set: every level only ever adds finer displacement on top of the already-drawn coarser path, because the random draws are consumed in a fixed order and extending the depth never rewrites what a shallower run already showed — so it is locked from REGENERATE the same way. hurst and amp are the real shape dials; both were swept across the whole declared box (500 random all-constants tuples, a harsher test than the plus-or-minus twelve percent REGENERATE itself applies) with no near-flat result anywhere in the box and no observed peak deviation past half the frame height — worst measured 49%. pace only paces the on-page reveal and never touches the geometry, so it is left free to jitter along with hurst and amp
Source
doi:10.1145/358523.358553

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. 135 · BROWNIAN BRIDGE — Paul Lévy, 1948
//   B(0) = B(1) = 0
//   B(mid) = ½(B(left)+B(right)) + Z·σ·Δ^H,  Z ~ N(0,1)
//   Δ = right − left,  H = ½ is the Lévy bridge (Lévy, 1948)
//   H ≠ ½ is the fractional generalisation
// 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=brownianbridge

float p_levels = 7 + chf('levels_tweak');       // levels (recursion depth) · live 6 .. 9
float p_hurst  = 0.5 + chf('hurst_tweak');      // H — Hurst exponent · live 0.2 .. 0.8
float p_amp    = 1 + chf('amp_tweak');          // amplitude · live 0.6 .. 1.4

// The plate's own colour: FORMA's CURVES 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.11 + 0.1196 * cos(6.28318530718 * (t + 0)),
    0.46 + 0.5 * cos(6.28318530718 * (t + 0.05)),
    0.2453 + 0.2667 * cos(6.28318530718 * (t + 0.1)));
}

// Levy's 1948 construction, cooked straight to the finished bridge: pin both
// ends at zero, then repeatedly split every known interval at its midpoint
// and displace it from the average of its two neighbours by a seeded
// Gaussian scaled to (interval length)^H — the same halving-of-variance
// rule the plate's own draw derives from Brownian scaling. Dividing every
// level's displacement by norm keeps hurst and amp independent, exactly as
// the draw function does: without it, a lower H would also inflate the
// total reach, confusing "rougher" with "bigger". Deterministic: every
// displacement is random(counted seed) on the plate's own seed, so a cook
// of the same constants is the same bridge every time, and REGENERATE
// changing the seed is the only thing that draws a different one. The
// page's frame-by-frame level reveal and its HILITE flash on the newest
// midpoints are the animation's own devices, not part of the mathematics,
// and are not ported — this cooks the finished construction at full depth.
// waived: pace — it paces the plate's level-by-level reveal on the page,
// and a cook has no clock to pace; every other constant reaches the body

function float forma_normsq(int levels; float hurst){
    // the geometric series sum(interval^(2H)) over every level, used to
    // keep the total displaced variance independent of hurst
    float s = 0.0;
    for (int lvl = 1; lvl <= levels; lvl++)
        s += pow(pow(2.0, float(-(lvl - 1))), 2.0 * hurst);
    return s;
}

int L = int(rint(p_levels));
int n = int(pow(2.0, float(L)) + 0.5);      // VEX has no shift operator
float norm = sqrt(forma_normsq(L, p_hurst));

float Y[];
resize(Y, n + 1);
Y[0] = 0.0;
Y[n] = 0.0;

int rc = 1948;                    // the plate's own seed, counted upward — Levy's book year

int step = n;
for (int lvl = 1; lvl <= L; lvl++){
    int half = step / 2;
    float llen = float(step) / float(n);
    float std = (p_amp / norm) * pow(llen, p_hurst);
    for (int k = half; k < n; k += step){
        float u1 = max(1e-6, random(rc));  rc++;
        float u2 = random(rc);  rc++;
        float z = sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2);
        Y[k] = (Y[k - half] + Y[k + half]) * 0.5 + z * std;
    }
    step = half;
}

int prim = addprim(0, "polyline");
for (int i = 0; i <= n; i++){
    float x = float(i) / float(n) * 2.0 - 1.0;
    // the graph is drawn y-up already; no flip needed
    int pt = addpoint(0, set(x, Y[i], 0.0));
    // colour sweeps the bright lobe of the ramp along the curve
    float u = float(i) / float(n);
    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. 135 · BROWNIAN BRIDGE — Paul Lévy, 1948
//   B(0) = B(1) = 0
//   B(mid) = ½(B(left)+B(right)) + Z·σ·Δ^H,  Z ~ N(0,1)
//   Δ = right − left,  H = ½ is the Lévy bridge (Lévy, 1948)
//   H ≠ ½ is the fractional generalisation
// 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 CURVES accent, #3DFF88. Animation runs on time.
// This plate draws 2 separate paths at its published constants:
// duplicate the group (Contents › Group) that many times and each copy draws
// its own part, read from its position in the layer. A Slider Control named
// "part" on the layer pins one instead.
// The figure is seeded like the page: a bare paste is the page's boot seed,
// and a Slider Control named seed_tweak moves to any other.
// https://forma-gen.com/#plate=brownianbridge

// 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_levels = 7 + forma_tweak("levels_tweak");     // levels (recursion depth) · live 6 .. 9
var p_hurst  = 0.5 + forma_tweak("hurst_tweak");    // H — Hurst exponent · live 0.2 .. 0.8
var p_amp    = 1 + forma_tweak("amp_tweak");        // amplitude · live 0.6 .. 1.4
var p_pace   = 1 + forma_tweak("pace_tweak");       // seconds per level · live 0.6 .. 2.2

// 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.2662929461803287;   // 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]; }
function forma_partIndex(){
  try { return Math.round(effect("part")("Slider")); } catch (e){}
  try { return thisProperty.propertyGroup(3).propertyIndex - 1; } catch (e){ return 0; }
}
var forma_part = forma_partIndex();
var forma_seed = (1 + Math.round(forma_tweak("seed_tweak"))) >>> 0;
var forma_imul = Math.imul || function (a, b){
  var ah = (a >>> 16) & 0xffff, al = a & 0xffff, bh = (b >>> 16) & 0xffff, bl = b & 0xffff;
  return (al * bl + (((ah * bl + al * bh) << 16) >>> 0)) | 0;
};
function forma_seeded(k){   // the page's seeded(k): mulberry32 on the atlas seed
  var a = (((forma_seed * 2654435761) >>> 0) ^ ((k * 40503) >>> 0)) >>> 0;
  return function (){
    a = a + 0x6D2B79F5 | 0;
    var t = forma_imul(a ^ a >>> 15, 1 | a);
    t = t + forma_imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}

// Lévy's Brownian bridge by midpoint displacement: pinned at both ends,
// every known interval is halved at its midpoint and the midpoint displaced
// from the mean of its neighbours by a Gaussian of standard deviation
// amp·(interval length)^H — Brownian scaling applied at every halving, H = ½
// the bridge itself, other H the fractional generalisation the graphics
// literature built on it. The level displacements are divided by the root of
// the geometric series Σ 2^(−2H(l−1)), so amp and hurst stay independent.
// Box–Muller on the page's own seeded(1948). Part 0 is the finished bridge
// at all 2^levels intervals; part 1 is the construction in progress — the
// curve at the level reached so far, one level per `pace` seconds from the
// start of the comp, then held — which is the reveal the page animates.
// parts: 2
var L = Math.round(p_levels), n = 1 << L;
var Y = new Array(n + 1);
for (var z = 0; z <= n; z++) Y[z] = 0;
var sumSq = 0;
for (var lv = 1; lv <= L; lv++) sumSq += Math.pow(2, -(lv - 1) * 2 * p_hurst);
var norm = Math.sqrt(sumSq);
var rnd = forma_seeded(1948);
function forma_gaussian(){
  var u1 = Math.max(1e-9, rnd()), u2 = rnd();
  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(6.283185307179586 * u2);
}
var step = n;
for (var lvl = 1; lvl <= L; lvl++){
  var half = step / 2, std = (p_amp / norm) * Math.pow(step / n, p_hurst);
  for (var k = half; k < n; k += step) Y[k] = (Y[k - half] + Y[k + half]) / 2 + forma_gaussian() * std;
  step = half;
}
var shown = forma_part === 1 ? Math.min(L, Math.floor(Math.max(0, forma_t) / Math.max(0.05, p_pace)) + 1) : L;
var stride = n >> shown;
var pts = [];
for (var i = 0; i <= n; i += stride) pts.push(forma_pt(forma_W * (0.05 + 0.9 * i / n), forma_H * 0.5 - Y[i] * forma_H * 0.15));
createPath(pts, [], [], false);