Skip to the plate
FORMA PUBLIC DOMAIN GENERATIVE ATLAS / ED. 0.28
Plate 124, Enneper Surface: a still of the surface / weierstrass-enneper data plate as the atlas renders it, in the curves accent.

PL. 124  ·  CURVES / SURFACE / WEIERSTRASS-ENNEPER DATA

Enneper Surface

Alfred Enneper, 1864 · the representation: Karl Weierstrass, 1866

OPEN THE LIVE PLATE ▸

DEFINITION

x = u − u³/3 + uv²
y = −(v − v³/3 + u²v)
z = u² − v²
Weierstrass–Enneper data (f, g) = (1, z);  mean curvature H ≡ 0

NOTES

Feed almost any pair of holomorphic functions through the representation Weierstrass set out in 1866 and the surface that comes back has zero mean curvature everywhere — the local definition of a soap film with no pressure difference across it. Enneper had already published the simplest nontrivial case two years earlier, in 1864: take f = 1 and g = z itself, integrate, and the three real parts become the surface drawn here. Where the catenoid and the helicoid it bends into, plate 108, never cross themselves at any size, this one does. The map from the (u, v) plane to space stops being one-to-one once the sampled disk grows past radius root 3, and the reason is exact rather than approximate: setting x = 0 on the boundary circle of radius r and solving gives sin squared theta equal to (r squared minus 3) over four r squared, which has a real solution only once r reaches root 3. At r = root 3 itself the first two points to collide are (root 3, 0) and (minus root 3, 0) in the parameter plane, both landing on the single point (0, 0, 3) in space; past it a whole curve of further collisions opens up. The patch radius here is a dial, not a fixed choice — it defaults well inside root 3 so the surface reads as the clean saddle every textbook draws first, and it reaches past that radius on purpose, so the self-crossing is one slider push away rather than a boundary the plate quietly avoids.

PROVENANCE

Origin — the surface
A. Enneper, "Analytisch-geometrische Untersuchungen", Zeitschrift für Mathematik und Physik 9, 1864, 96-125
Origin — the representation
K. Weierstrass, "Untersuchungen über die Flächen, deren mittlere Krümmung überall gleich Null ist", read 25 June 1866, Monatsberichte der Königlich Preussischen Akademie der Wissenschaften zu Berlin, 1866, 612-625; reprinted in Mathematische Werke III, 1903, 39-52. Both men were working on minimal surfaces in the same years — Wikipedia dates it to 1863 for both — so the two-year gap between publications is not a priority claim, only the order the two papers actually appeared in
Standing
Both papers predate any DOI-issuing system by decades. Checked directly against Crossref (bibliographic search on each title, no partial match on either), so no src identifier is offered here rather than a guessed one. Nineteenth-century mathematics, public domain, no patent possible on either
The self-intersection, derived rather than asserted
Several secondary sources state that the Enneper surface stops being an embedding beyond some critical radius without giving the number, so rather than repeat an unsourced figure it is derived here directly from the parametrization above. On the boundary circle u squared plus v squared equal to r squared, z matches automatically between angle theta and pi minus theta (both give r squared cos 2 theta), and y matches too by the same pairing; only x can differ, and x at pi minus theta always equals minus x at theta. The pair collides exactly when x(theta) = 0 for a theta other than the trivial theta = pi/2, which solving 1 + r squared sin squared theta − (r squared over 3) cos squared theta = 0 turns into sin squared theta = (r squared − 3) / (4 r squared) — real only once r squared is at least 3. Checked numerically at r = 2: the predicted angle sin theta = 0.25 (theta about 14.5 degrees) gives (u, v) about (1.937, 0.5) and (−1.937, 0.5), and evaluating the surface at both lands x within 0.001 of zero at each, matching z = r squared cos 2 theta to three figures
Constants
Patch radius, wireframe line count and tumble rate all render across their whole declared range — nothing here goes dead or degenerate the way a chaotic map can, so nothing is locked from regenerate. span reaching to 2.4 is the self-intersection made findable on purpose, not a boundary being protected against

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. 124 · ENNEPER SURFACE — Alfred Enneper, 1864 · the representation: Karl Weierstrass, 1866
//   x = u − u³/3 + uv²
//   y = −(v − v³/3 + u²v)
//   z = u² − v²
//   Weierstrass–Enneper data (f, g) = (1, z);  mean curvature H ≡ 0
// 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=enneper

float p_span    = 1.4 + chf('span_tweak');        // patch radius r — max u, v extent · live 0.8 .. 2.4
float p_density = 14 + chf('density_tweak');      // wireframe lines · live 8 .. 24

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

// The Weierstrass-Enneper patch, disk radius r = p_span, as a wireframe of
// iso-r rings and iso-theta spokes. No view tumble is baked into the
// geometry: the page rotates a flat canvas projection because it has no
// real camera, but a Houdini viewport already orbits real 3-D points, so
// the rings and spokes below are the true, untumbled surface.
// waived: speed, view — the on-page tumble rate and the azimuth it is steered
// to are both a flat-canvas camera effect; a Houdini cook has a real 3-D
// camera and needs nothing baked for it
int forma_nu   = int(rint(p_density));            // rings: fixed r, theta sweeps
int forma_nv   = int(rint(p_density * 1.5));       // spokes: fixed theta, r sweeps
int forma_segr = 48;
int forma_segs = 12;

// rings: constant r, theta over 0..2pi, closed loops
for (int i = 0; i < forma_nu; i++){
    float r = p_span * float(i + 1) / float(forma_nu);
    int prim = addprim(0, "polyline");
    for (int j = 0; j <= forma_segr; j++){
        float th = float(j) / float(forma_segr) * 2.0 * PI;
        float u = r * cos(th), v = r * sin(th);
        float x = u - u * u * u / 3.0 + u * v * v;
        float y = -(v - v * v * v / 3.0 + u * u * v);
        float z = u * u - v * v;
        // z-up: the surface's own z (the saddle depth) maps onto Houdini's
        // y (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));
        setpointattrib(0, "Cd", pt, forma_ramp(0.92));
        addvertex(0, prim, pt);
    }
}
// spokes: constant theta, r over 0..p_span, open lines from the hub
for (int j = 0; j < forma_nv; j++){
    float th = float(j) / float(forma_nv) * 2.0 * PI;
    int prim = addprim(0, "polyline");
    for (int i = 0; i <= forma_segs; i++){
        float r = p_span * float(i) / float(forma_segs);
        float u = r * cos(th), v = r * sin(th);
        float x = u - u * u * u / 3.0 + u * v * v;
        float y = -(v - v * v * v / 3.0 + u * u * v);
        float z = u * u - v * v;
        int pt = addpoint(0, set(x, z, y));
        setpointattrib(0, "Cd", pt, forma_ramp(0.4));
        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. 124 · ENNEPER SURFACE — Alfred Enneper, 1864 · the representation: Karl Weierstrass, 1866
//   x = u − u³/3 + uv²
//   y = −(v − v³/3 + u²v)
//   z = u² − v²
//   Weierstrass–Enneper data (f, g) = (1, z);  mean curvature H ≡ 0
// 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.
// https://forma-gen.com/#plate=enneper

// 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_span    = 1.4 + forma_tweak("span_tweak");      // patch radius r — max u, v extent · live 0.8 .. 2.4
var p_density = 14 + forma_tweak("density_tweak");    // wireframe lines · live 8 .. 24
var p_speed   = 0.08 + forma_tweak("speed_tweak");    // tumble rate · live 0.02 .. 0.2
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.9713084360118955;   // 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();

// Enneper's minimal surface in its Weierstrass form, x = u − u³/3 + uv²,
// y = −(v − v³/3 + u²v), z = u² − v², over a disc of radius `span` in the
// (u, v) plane — past √3 the surface meets itself, which is the slider's own
// decision. The page's view: the surface turns at the speed on the slider
// from this plate's phase, with 0.34 of the depth mixed into the screen
// vertical; the patch radius breathes four per cent about the slider; the
// fit is measured on three rings near the rim, where the reach is largest.
// Two parts, as for the catenoid: part 0 the rings (fixed r), chained along
// the θ = 0 spoke, itself a curve of the surface; part 1 the spokes (fixed
// θ), each walked out from the centre and back — a spoke retraced lies on
// itself — so every connector is on the surface.
// parts: 2
var TAU = 6.283185307179586, cx = forma_W / 2, cy = forma_H / 2;
var Rmax = p_span * 1.05, reachXY = 0.001, reachV = 0.001;
for (var k = 0; k < 3; k++){
  var rr = Rmax * (0.7 + 0.15 * k);
  for (var ai = 0; ai < 96; ai++){
    var ta = ai / 96 * TAU, ua = rr * Math.cos(ta), va = rr * Math.sin(ta);
    var xa = ua - ua * ua * ua / 3 + ua * va * va;
    var ya = -(va - va * va * va / 3 + ua * ua * va);
    var za = ua * ua - va * va;
    var hor = Math.sqrt(xa * xa + ya * ya);
    if (hor > reachXY) reachXY = hor;
    var vert = Math.abs(za) * 0.94 + hor * 0.34;
    if (vert > reachV) reachV = vert;
  }
}
var sc = Math.min(forma_W * 0.40 / reachXY, forma_H * 0.40 / reachV);
var FORMA_DEG = Math.PI / 180;
var rot = forma_t * p_speed + forma_phase * TAU + p_view * FORMA_DEG, co = Math.cos(rot), si = Math.sin(rot);
var R = p_span * (1 + 0.04 * Math.sin(forma_t * 0.05 + forma_phase * TAU));
function forma_surf(r, th){
  var u = r * Math.cos(th), v = r * Math.sin(th);
  var x = u - u * u * u / 3 + u * v * v;
  var y = -(v - v * v * v / 3 + u * u * v);
  var z = u * u - v * v;
  var X = x * co + y * si, D = -x * si + y * co;
  var Y = z * 0.94 + D * 0.34;
  return forma_pt(cx + X * sc, cy - Y * sc);
}
var NU = Math.round(p_density), NV = Math.round(p_density * 1.5);
var segR = 48, segS = 12;
var pts = [];
if (forma_part === 1){
  pts.push(forma_surf(0, 0));
  for (var j = 0; j < NV; j++){
    var th = j / NV * TAU;
    for (var i = 1; i <= segS; i++) pts.push(forma_surf(R * i / segS, th));
    for (var m = segS - 1; m >= 0; m--) pts.push(forma_surf(R * m / segS, th));
  }
} else {
  for (var n = 0; n < NU; n++){
    var r = R * (n + 1) / NU;
    for (var q = 0; q <= segR; q++) pts.push(forma_surf(r, q / segR * TAU));
  }
}
createPath(pts, [], [], false);