PL. 123 · CURVES / LINK / BRUNNIAN TRIPLE
Borromean Rings
Traditional — the three-ring motif is attested in Buddhist art from the second century, in Viking imagery by the ninth and in Japanese heraldry by the twelfth, all centuries before it became the crest of the Borromeo family of Milan in the fifteenth century, the name this plate uses · non-circularity proved by Michael Freedman and Richard Skora, 1987
OPEN THE LIVE PLATE ▸DEFINITION
A(t) = (cos t, e·sin t, 0) B(t) = (0, cos t, e·sin t) C(t) = (e·sin t, 0, cos t) t ∈ [0, 2π), 0 < e < 1 (cyclic permutation x→y→z→x carries A→B→C)
NOTES
Three loops, no two of which are linked with each other, that cannot be pulled apart once all three are present — cut any one and the other two fall away trivially, because they were never caught on each other to begin with. That property has a name, Brunnian, after Hermann Brunn, who generalised it to any number of components in 1892; the three-ring case is centuries older than the name and older still than the Milanese family whose crest gave it the one it now carries. The picture every version of this symbol draws is three round circles, and that picture is wrong: Michael Freedman and Richard Skora proved in 1987 that no three circles in three-dimensional space can form the Borromean rings at all. Their argument runs through four-dimensional hyperbolic geometry rather than through the circles directly, and it settles a stronger claim, that no Brunnian link of this kind admits an exact-circle realisation, of which the Borromean case is the smallest instance. What does work is three congruent ellipses, one flattened into each of the three mutually perpendicular coordinate planes and related to each other by rotating which axis is which. Flattened by any amount at all, three such ellipses sit clear of one another everywhere except at the single degenerate point where flattening stops and they become circles again — which is exactly the configuration Freedman and Skora rule out. The plate keeps a wide safety margin either side of that limit, never rendering an eccentricity close enough to it for the drawing to misrepresent what is actually linked.
PROVENANCE
- Origin — the symbol
- Attested in Buddhist art from the second century, in Viking imagery by the ninth and in Japanese heraldry by the twelfth, converging independently on the same figure well before it had the name this plate uses. The name is the crest of the Borromeo family of Milan, adopted in the fifteenth century as a mark of three families bound as one
- Origin — the general property
- H. Brunn, "Über Verkettung", Sitzungsberichte der Bayerischen Akademie der Wissenschaften, München, 1892 — the paper that first studied links where every proper sub-link is trivial. Brunn did not name the class after himself; that credit is H. Debrunner, 1961. Pre-DOI, so nothing is cited that cannot be checked
- Non-circularity
- M. H. Freedman, R. Skora, "Strange actions of groups on spheres", Journal of Differential Geometry 25 (1987), 75–98. Verified on Crossref against title, both authors, year and venue (DOI 10.4310/jdg/1214440725, container Journal of Differential Geometry, volume 25, 1987); the page range 75-98 is corroborated by the published PDF at Project Euclid. Multiple independent secondary sources (an arXiv paper on Brunnian links, the Wikipedia and HandWiki Borromean-rings articles) attribute the circles-impossibility result specifically to this paper, arrived at there as a corollary of a broader hyperbolic-geometry argument about group actions on spheres, not stated as the main result — which is why the note above treats it as an application of that argument rather than as the primary subject. A 1991 elementary proof exists (Lindstrom and Zetterstrom, American Mathematical Monthly) but is not the source cited here, since Freedman and Skora is both earlier and the result several secondary sources credit first
- The ellipse construction
- Not from any external source — derived and checked directly against the definition below. Three ellipses A(t)=(cos t, e sin t, 0), B(t)=(0, cos t, e sin t), C(t)=(e sin t, 0, cos t) lie one to a coordinate plane; solving each pair for shared points shows they touch only when e=1, at (±1,0,0) and its two cyclic images, and are strictly disjoint for every e in (0,1) — checked by brute-force nearest-point search over each pair, 600 samples per ring, at fifteen eccentricities from 0.05 to 0.999, with the minimum found distance falling to zero only in the e→1 limit. Because the family never touches for e<1, its link type cannot change anywhere in that open interval — a continuous family of disjoint embeddings is always ambient isotopic to itself — so confirming the Borromean pattern at one representative e (the projected diagram at e=0.5 shows exactly six crossings, two between each pair of rings, alternating around each ring the way the standard minimal Borromean diagram does) stands for the whole interval, not just the value checked
- Constants
- e sets how flattened each ellipse is and is jitterable across its whole declared range: the minimum 3-D distance between any two rings, measured by the same brute-force search above at every 0.025 step from e=0.10 to e=0.925, rises from 0 near e=0 to a peak of 0.414 near e=0.54 and falls back toward 0 as e approaches 1 — never linear, because the closest-approach point migrates from an interior critical point to a fixed corner exactly at the golden-ratio conjugate 1/φ≈0.618, where the two branches meet. The declared range 0.30 to 0.75 keeps the measured minimum at or above 0.25 throughout — a quarter of the major axis, comfortably clear of the e=1 limit the citation above rules out and comfortably short of the flattened, hard-to-read shapes near e=0. size and speed are pure display constants — overall scale and tumble rate — with no bearing on which link is drawn, so both are jitterable over their whole range as well
- Source
- doi:10.4310/jdg/1214440725
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. 123 · BORROMEAN RINGS — Traditional — the three-ring motif is attested in Buddhist art from the second century, in Viking imagery by the ninth and in Japanese heraldry by the twelfth, all centuries before it became the crest of the Borromeo family of Milan in the fifteenth century, the name this plate uses · non-circularity proved by Michael Freedman and Richard Skora, 1987
// A(t) = (cos t, e·sin t, 0)
// B(t) = (0, cos t, e·sin t)
// C(t) = (e·sin t, 0, cos t)
// t ∈ [0, 2π), 0 < e < 1 (cyclic permutation x→y→z→x carries A→B→C)
// 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=borromean
float p_ecc = 0.5 + chf('ecc_tweak'); // e — ellipse axis ratio (b/a) · live 0.3 .. 0.75
// 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)));
}
// Three ellipses, one per coordinate plane, related by the cyclic
// permutation (x,y,z) -> (z,x,y): A in z=0, B in x=0, C in y=0. e<1 keeps
// the three pairwise disjoint in 3-D -- they only touch, at e=1, exactly
// where Freedman and Skora's theorem says a linked triple of round
// circles cannot exist (see borromean.js for the check).
//
// The page's screen-space over/under gap logic exists only to fake
// occlusion on a flat canvas: it searches every frame for where two
// projected curves pass close together and breaks the farther one's
// path there. A true 3-D curve in a Houdini viewport needs none of
// that -- the viewport's own depth test (or a render) hides the far
// strand at every crossing correctly, from any camera, with no search
// -- so it is not ported, the same call pappus.vex makes about its
// reveal and HILITE head (see its own note). This body emits the plain
// geometry, one closed polyline per ring.
// waived: speed, size, view — speed paces the plate's tumble and a cook has no
// clock; size is a canvas fit-scale, not a shape constant — the body
// draws at the construction's own natural unit radius (major axis 1),
// which any camera or transform in Houdini can rescale; view is the
// azimuth that tumble is steered to on the page, which is the same
// camera by another name
// n arrives as a parameter: a VEX function cannot read a snippet-scope
// variable (no closures), so the sample count lives one literal deep
// here rather than as a file-scope constant forma_ring could not see.
function void forma_ring(int axis, n; float e; vector col){
// axis 0 = A (z=0 plane), 1 = B (x=0 plane), 2 = C (y=0 plane)
int prim = addprim(0, "polyline");
for (int i = 0; i <= n; i++){
float u = float(i) / float(n) * 2.0 * PI;
float co = cos(u), si = sin(u);
float x, y, z;
if (axis == 0){ x = co; y = e * si; z = 0.0; }
else if (axis == 1){ x = 0.0; y = co; z = e * si; }
else { x = e * si; y = 0.0; z = co; }
// z-up: the curve's own z axis maps onto Houdini's y (up), the
// same mapping every z-up curve in this corpus uses (catenoid.vex)
int pt = addpoint(0, set(x, z, y));
setpointattrib(0, "Cd", pt, col);
addvertex(0, prim, pt);
}
}
float e = p_ecc;
int seg = 96;
forma_ring(0, seg, e, forma_ramp(0.85));
forma_ring(1, seg, e, forma_ramp(0.95));
forma_ring(2, seg, e, forma_ramp(1.06));
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. 123 · BORROMEAN RINGS — Traditional — the three-ring motif is attested in Buddhist art from the second century, in Viking imagery by the ninth and in Japanese heraldry by the twelfth, all centuries before it became the crest of the Borromeo family of Milan in the fifteenth century, the name this plate uses · non-circularity proved by Michael Freedman and Richard Skora, 1987
// A(t) = (cos t, e·sin t, 0)
// B(t) = (0, cos t, e·sin t)
// C(t) = (e·sin t, 0, cos t)
// t ∈ [0, 2π), 0 < e < 1 (cyclic permutation x→y→z→x carries A→B→C)
// 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 6 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.
// https://forma-gen.com/#plate=borromean
// 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_ecc = 0.5 + forma_tweak("ecc_tweak"); // e — ellipse axis ratio (b/a) · live 0.3 .. 0.75
var p_size = 0.74 + forma_tweak("size_tweak"); // ring size · live 0.55 .. 0.85
var p_speed = 0.05 + forma_tweak("speed_tweak"); // tumble speed · live 0.02 .. 0.14
var p_view = 0 + forma_tweak("view_tweak"); // view azimuth ° · live -180 .. 180
// 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.5387448573019356; // 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();
// The Borromean rings as three ellipses in the three coordinate planes,
// related by the cyclic permutation (x, y, z) → (z, x, y): A in z = 0, B in
// x = 0, C in y = 0, each with eccentricity `ecc`. No two are linked and the
// three cannot be separated. The page's camera — a fixed compound tilt of
// 32° about x then 27° about y, then a tumble at `speed` rad/s from this
// plate's phase — and its fit, exact since every point has |p| ≤ √2.
//
// The crossings are the picture: where two rings cross on screen, the one
// behind is broken, or three loops drawn over one another stop looking
// inseparable. The page finds them fresh every frame in screen space — for
// every sample on one ring the nearest on the other, a local minimum below
// a threshold tied to the scale is a crossing, the two best-separated are
// kept — and opens a gap of seven samples on the deeper ring. A path cannot
// have a gap, so each ring is two runs between its two under-crossings:
// six parts, part 2k and 2k+1 the two arcs of ring k.
// parts: 6
var TAU = 6.283185307179586, N = 96, e = p_ecc;
var A = [], B = [], C = [];
for (var i = 0; i < N; i++){
var u = i / N * TAU, co = Math.cos(u), si = Math.sin(u);
A.push([co, e * si, 0]); B.push([0, co, e * si]); C.push([e * si, 0, co]);
}
var PHI1 = 0.5585053606381855, PHI2 = 0.47123889803846897;
var c1 = Math.cos(PHI1), s1 = Math.sin(PHI1), c2 = Math.cos(PHI2), s2 = Math.sin(PHI2);
var FORMA_DEG = Math.PI / 180;
var theta = forma_t * p_speed + forma_phase * TAU + p_view * FORMA_DEG, cth = Math.cos(theta), sth = Math.sin(theta);
var scale = Math.min(forma_W, forma_H) * 0.5 * p_size / Math.SQRT2;
var cx = forma_W / 2, cy = forma_H / 2;
function forma_proj(pt){
var x = pt[0], y = pt[1], z = pt[2];
var ya = y * c1 - z * s1, za = y * s1 + z * c1;
var xa = x * c2 + za * s2, zb = -x * s2 + za * c2;
var x2 = xa * cth - ya * sth, y2 = xa * sth + ya * cth;
return [cx + x2 * scale, cy - zb * scale, y2];
}
var pA = [], pB = [], pC = [];
for (var j = 0; j < N; j++){ pA.push(forma_proj(A[j])); pB.push(forma_proj(B[j])); pC.push(forma_proj(C[j])); }
var visA = [], visB = [], visC = [];
for (var v = 0; v < N; v++){ visA.push(1); visB.push(1); visC.push(1); }
var gapHalf = 3, thresh2 = (scale * 0.5) * (scale * 0.5);
function forma_markGap(vis, i0){ for (var d = -gapHalf; d <= gapHalf; d++) vis[(i0 + d + N) % N] = 0; }
function forma_crossings(P, Q){
var hmin = [], jmin = [];
for (var a = 0; a < N; a++){
var best = Infinity, bj = 0, pa = P[a];
for (var b = 0; b < N; b++){
var dx = pa[0] - Q[b][0], dy = pa[1] - Q[b][1], d2 = dx * dx + dy * dy;
if (d2 < best){ best = d2; bj = b; }
}
hmin.push(best); jmin.push(bj);
}
var cand = [];
for (var k = 0; k < N; k++){
var prev = hmin[(k - 1 + N) % N], next = hmin[(k + 1) % N];
if (hmin[k] <= prev && hmin[k] <= next && hmin[k] < thresh2) cand.push([k, jmin[k], hmin[k]]);
}
cand.sort(function (u, w){ return u[2] - w[2]; });
var picked = [];
for (var m = 0; m < cand.length && picked.length < 2; m++){
var clear = true;
for (var q = 0; q < picked.length; q++){
var dd = Math.abs(picked[q][0] - cand[m][0]) % N;
if (Math.min(dd, N - dd) <= N / 6) clear = false;
}
if (clear) picked.push(cand[m]);
}
return picked;
}
function forma_resolve(P, visP, Q, visQ){
var cr = forma_crossings(P, Q);
for (var n = 0; n < cr.length; n++){
if (P[cr[n][0]][2] < Q[cr[n][1]][2]) forma_markGap(visP, cr[n][0]); else forma_markGap(visQ, cr[n][1]);
}
}
forma_resolve(pA, visA, pB, visB);
forma_resolve(pB, visB, pC, visC);
forma_resolve(pC, visC, pA, visA);
var rings = [[pA, visA], [pB, visB], [pC, visC]];
var ring = rings[Math.max(0, Math.min(2, Math.floor(forma_part / 2)))], run = forma_part % 2;
var P = ring[0], vis = ring[1];
// The visible runs of this ring, each a list of sample indices in order.
var start = -1;
for (var s = 0; s < N; s++) if (vis[s] && !vis[(s - 1 + N) % N]){ start = s; break; }
var runs = [];
if (start < 0) runs.push(null); // no gap at all: the whole ring
else {
var cur = null;
for (var w = 0; w < N; w++){
var idx = (start + w) % N;
if (!vis[idx]){ cur = null; continue; }
if (!cur){ cur = []; runs.push(cur); }
cur.push(idx);
}
}
var pick = runs[Math.min(run, runs.length - 1)];
var pts = [];
if (pick === null){
for (var z = 0; z < N; z++) pts.push(forma_pt(P[z][0], P[z][1]));
createPath(pts, [], [], true);
} else {
for (var y = 0; y < pick.length; y++) pts.push(forma_pt(P[pick[y]][0], P[pick[y]][1]));
createPath(pts, [], [], false);
}