PL. 120 · CURVES / PARAMETRIC / TORUS WINDING
Torus Knot
classical knot theory · torus knots tabulated by P. G. Tait, 1876-1877 · genus formula: Herbert Seifert, 1935
OPEN THE LIVE PLATE ▸DEFINITION
x(φ) = (R + r·cos(qφ))·cos(pφ) y(φ) = (R + r·cos(qφ))·sin(pφ) z(φ) = r·sin(qφ) φ ∈ [0, 2π); gcd(p,q) = 1 ⇒ knot, else a link of gcd(p,q) components genus = (p−1)(q−1)/2 (Seifert, 1935)
NOTES
A curve traced on the surface of a torus, winding p times around the axis and q times around the tube, closing after phi runs once from 0 to 2 pi because both windings are whole numbers. When p and q share no common factor the curve never crosses itself and is a genuine knot -- the simplest nontrivial case, p = 2 and q = 3, is the trefoil, the first entry in every knot table since the nineteenth century. When p and q do share a common factor d, the same construction cannot close into one strand; it splits into d separate interlocking loops instead, each one the reduced (p/d, q/d) torus knot, the d strands running parallel on the same torus and evenly spaced around the tube -- drawn here rather than hidden, since a slider landing on a shared factor is not a failure of the mathematics, only a different honest object. Herbert Seifert proved in 1935 that every (p, q) torus knot bounds an orientable spanning surface of genus (p-1)(q-1)/2, built from the pattern the standard diagram traces out in Seifert circles -- one number that shows every torus knot past the trefoil is a genuinely different knot from its neighbours, not merely a different picture of the same one.
PROVENANCE
- Origin
- The (p, q) torus knot is classical: a curve wound p times around a torus axis and q times around its tube is the natural curve the surface already carries, drawn in ornamental knotwork long before formal topology existed. It enters the mathematical literature as part of the nineteenth-century tabulation of knots -- P. G. Tait, "On Knots", Transactions of the Royal Society of Edinburgh 28, 1876-1877, catalogues the (2, 2n+1) family (trefoil, cinquefoil, septafoil...) among the first knots ever listed. No single paper introduces the parametric family as such, so none is credited as its origin
- Standing
- Public domain -- a parametric curve and an elementary embedding fact (coprime windings do not self-intersect). No patent is possible on either
- Genus, verified
- Herbert Seifert proved in 1935 that a (p, q) torus knot bounds an orientable spanning surface of genus (p-1)(q-1)/2, the fact this plate states in its equation panel. H. Seifert, "Über das Geschlecht von Knoten", Mathematische Annalen 110, 1935, 571-592. Verified on Crossref against title, author, year and venue (DOI 10.1007/BF01448044) -- the one claim on this plate with a source recent enough to carry one; the curve itself predates any paper that could be cited for it
- Coprimality by construction
- p is drawn only from powers of two (2, 4, 8) and q only from odd numbers (3, 5, 7, 9, 11, 13, 15) on regenerate, so gcd(p, q) = 1 is not a probability but a proof: a power of two and an odd number share no factor, in every one of the 21 reachable combinations, checked exhaustively. The sliders still reach every integer in their declared range -- the same arrangement rule30 uses -- so a manually dialled common factor, p = q = 4 for instance, is findable on purpose and renders the link the mathematics actually produces rather than a blank or malformed curve
- Constants
- p and q are the two winding numbers and go through CHOICES rather than LOCKED, since every reachable integer pair is a genuine object, knot or link, and nothing in the declared range is dead. ratio (r over R) is a look dial with no dead zone either: any value short of 1 keeps the tube inside the torus, so it stays free across its whole span
- Source
- doi:10.1007/BF01448044
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. 120 · TORUS KNOT — classical knot theory · torus knots tabulated by P. G. Tait, 1876-1877 · genus formula: Herbert Seifert, 1935
// x(φ) = (R + r·cos(qφ))·cos(pφ)
// y(φ) = (R + r·cos(qφ))·sin(pφ)
// z(φ) = r·sin(qφ)
// φ ∈ [0, 2π); gcd(p,q) = 1 ⇒ knot, else a link of gcd(p,q) components
// genus = (p−1)(q−1)/2 (Seifert, 1935)
// 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=torusknot
float p_p = 2 + chf('p_tweak'); // p — axis winds · live 2 .. 12
float p_q = 3 + chf('q_tweak'); // q — tube winds · live 2 .. 15
float p_ratio = 0.35 + chf('ratio_tweak'); // r/R — tube ratio · live 0.2 .. 0.5
// 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)));
}
// waived: view — the azimuth the finished figure is viewed from, and Houdini has a camera of its own
// The (p_p, p_q) torus knot -- or, when they share a factor, the torus
// link that decomposes into gcd(p_p, p_q) disjoint copies of the reduced
// (p_p/d, p_q/d) torus knot, running parallel on the same torus and
// evenly spaced in TUBE phase by 2*pi/p -- the spacing a meridian count
// forces (the link crosses a meridian disc p times; d strands at p/d
// crossings each need tube intercepts 2*pi/p apart). An axis rotation of
// 2*pi/d instead lands copies on one another whenever gcd(q/d, d) > 1;
// p = 4, q = 8 collapses four strands to two, measured, not eyeballed.
// Carrying the true decomposition into the port is what keeps a
// non-coprime pair an honest multi-strand object here too, rather than a
// single knot silently retraced on top of itself. Nothing is waived: all
// three published constants (p_p, p_q, p_ratio) reach the body directly.
//
// Unlike lissajous3d, no PHASE-derived constant is fixed into this body:
// on the page PHASE only ever offsets the tumble animation and the comet
// position, both rendering concerns a static Houdini cook has no clock
// for -- it never touches the shape of the curve, so there is nothing of
// it for the port to carry.
float TAU = 6.28318530718;
// The Euclidean algorithm needs true integer division, unlike the plain
// frequency multiplies a Lissajous-style body only ever needs as floats.
int forma_p = int(p_p);
int forma_q = int(p_q);
int forma_a = forma_p;
int forma_b = forma_q;
while (forma_b != 0){
int forma_rem = forma_a % forma_b;
forma_a = forma_b;
forma_b = forma_rem;
}
int forma_d = forma_a;
int forma_pr = forma_p / forma_d;
int forma_qr = forma_q / forma_d;
int forma_n = 1500;
for (int j = 0; j < forma_d; j++){
float forma_off = TAU * float(j) / float(forma_p);
int prim = addprim(0, "polyline");
for (int i = 0; i <= forma_n; i++){
float u = float(i) / float(forma_n) * TAU;
float amp = 1.0 + p_ratio * cos(forma_qr * u + forma_off);
float ang = forma_pr * u;
float x = amp * cos(ang);
float y = amp * sin(ang);
float z = p_ratio * sin(forma_qr * u + forma_off);
// z-up: the torus axis maps onto the y axis in Houdini (up), the
// same mapping every z-up curve in this corpus uses (see
// catenoid.vex, lissajous3d.vex)
int pt = addpoint(0, set(x, z, y));
float uu = float(i) / float(forma_n);
setpointattrib(0, "Cd", pt, forma_ramp(0.8 + 0.3 * uu));
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. 120 · TORUS KNOT — classical knot theory · torus knots tabulated by P. G. Tait, 1876-1877 · genus formula: Herbert Seifert, 1935
// x(φ) = (R + r·cos(qφ))·cos(pφ)
// y(φ) = (R + r·cos(qφ))·sin(pφ)
// z(φ) = r·sin(qφ)
// φ ∈ [0, 2π); gcd(p,q) = 1 ⇒ knot, else a link of gcd(p,q) components
// genus = (p−1)(q−1)/2 (Seifert, 1935)
// 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 1 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=torusknot
// 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_p = 2 + forma_tweak("p_tweak"); // p — axis winds · live 2 .. 12
var p_q = 3 + forma_tweak("q_tweak"); // q — tube winds · live 2 .. 15
var p_ratio = 0.35 + forma_tweak("ratio_tweak"); // r/R — tube ratio · live 0.2 .. 0.5
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.43004692415706813; // 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 (p, q) torus knot on a torus of major radius 1 and tube radius r/R: p
// windings round the axis, q round the tube. When p and q share a factor D the
// figure is a torus LINK of D parallel strands of the reduced (p/D, q/D) knot,
// spaced 2π/p apart in tube phase — the counting argument on the plate. One
// strand per part: duplicate the group D times (D = 1 at the published (2, 3)
// — a knot — so a bare paste is already the whole figure). The view is
// the page's: the axis plane spins at 0.05 rad/s from this plate's own phase,
// with a fixed pitch mixing 0.32 of the depth into the screen vertical.
// parts: 1
var TAU = 6.283185307179586;
var P = Math.round(p_p), Q = Math.round(p_q);
function forma_gcd(x, y){ return y ? forma_gcd(y, x % y) : x; }
var D = forma_gcd(P, Q), Pr = P / D, Qr = Q / D;
var R = 1, r = R * p_ratio;
var FORMA_DEG = Math.PI / 180;
var rot = forma_t * 0.05 + forma_phase * TAU + p_view * FORMA_DEG, co = Math.cos(rot), si = Math.sin(rot);
var VH = 0.92, VD = 0.32;
var s = Math.min(forma_W * 0.42 / (R + r), forma_H * 0.42 / (r * VH + (R + r) * VD));
var cx = forma_W / 2, cy = forma_H / 2;
var N = Math.max(240, Math.round(1800 / D));
var j = ((forma_part % D) + D) % D, off = TAU * j / P;
var pts = [];
for (var i = 0; i <= N; i++){
var u = i / N * TAU;
var A = R + r * Math.cos(Qr * u + off), ang = Pr * u;
var x0 = A * Math.cos(ang), y0 = A * Math.sin(ang), z0 = r * Math.sin(Qr * u + off);
var X = x0 * co - y0 * si, dp = x0 * si + y0 * co;
pts.push(forma_pt(cx + X * s, cy - (z0 * VH + dp * VD) * s));
}
createPath(pts, [], [], true);