Skip to the plate
FORMA PUBLIC DOMAIN GENERATIVE ATLAS / ED. 0.28
Plate 108, Catenoid–Helicoid Family: a still of the surface / isometric bend plate as the atlas renders it, in the curves accent.

PL. 108  ·  CURVES / SURFACE / ISOMETRIC BEND

Catenoid–Helicoid Family

Leonhard Euler, 1744 · Jean Baptiste Meusnier, 1776 · the family: Ossian Bonnet, 1853

OPEN THE LIVE PLATE ▸

DEFINITION

x = cos θ·sinh u·sin v + sin θ·cosh u·cos v
y = −cos θ·sinh u·cos v + sin θ·cosh u·sin v
z = u·cos θ + v·sin θ

NOTES

The catenoid is the soap film between two rings, the first minimal surface anyone found after the plane; the helicoid is the spiral ramp, found by Meusnier thirty years later. Bonnet showed they are the same surface: one can be bent into the other without stretching, tearing or creasing, every intermediate shape still minimal, distances along the surface never changing. The plate draws the wireframe and runs the bend back and forth — the ends of the catenoid unroll into the edges of the ramp and roll back up. θ = 0 is the catenoid, θ = π/2 the helicoid, and everything between is a surface with no everyday name.

PROVENANCE

Origin
L. Euler, Methodus Inveniendi Lineas Curvas, 1744 (the catenoid); J. B. Meusnier, Mémoire sur la courbure des surfaces, presented 1776, published 1785 (the helicoid); O. Bonnet, 1853 (the isometric family)
Standing
Eighteenth- and nineteenth-century geometry; public domain. Pre-DOI, so nothing is cited that cannot be checked
Constants
Span is the u extent of the patch; the classic figures draw about one and a quarter units either side

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. 108 · CATENOID–HELICOID FAMILY — Leonhard Euler, 1744 · Jean Baptiste Meusnier, 1776 · the family: Ossian Bonnet, 1853
//   x = cos θ·sinh u·sin v + sin θ·cosh u·cos v
//   y = −cos θ·sinh u·cos v + sin θ·cosh u·sin v
//   z = u·cos θ + v·sin θ
// 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=catenoid

float p_span    = 1.2 + chf('span_tweak');        // u — patch half-span · live 0.8 .. 1.6
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 Bonnet family wireframe at a fixed bend angle. theta = PI/4 is the
// midpoint of the deformation — half catenoid, half helicoid, the surface
// with no everyday name — chosen because the two endpoints are the figures
// every textbook already draws.
// waived: speed, view — animation pacing; the bend runs on the page, a still
// cooks here, and view is where that pacing is steered to: a camera azimuth,
// which Houdini has its own
float forma_theta = PI / 4.0;
int   forma_nu    = int(rint(p_density));
int   forma_nv    = int(rint(p_density * 1.6));
int   forma_seg   = 36;

float ct = cos(forma_theta), st = sin(forma_theta);
float U = p_span;

// the rings: v-lines at constant u
for (int i = 0; i < forma_nu; i++){
    float u = -U + 2.0 * U * float(i) / float(forma_nu - 1);
    int prim = addprim(0, "polyline");
    for (int j = 0; j <= forma_seg; j++){
        float v = float(j) / float(forma_seg) * 2.0 * PI;
        float x = ct * sinh(u) * sin(v) + st * cosh(u) * cos(v);
        float y = -ct * sinh(u) * cos(v) + st * cosh(u) * sin(v);
        // v centred on PI, as the plate centres it, so the helicoid climbs
        // symmetrically about the origin
        float z = u * ct + (v - PI) * st;
        // z-up parametrisation onto Houdini's y-up
        int pt = addpoint(0, set(x, z, y));
        setpointattrib(0, "Cd", pt, forma_ramp(0.9));
        addvertex(0, prim, pt);
    }
}
// the meridians: u-lines at constant v
for (int j = 0; j < forma_nv; j++){
    float v = float(j) / float(forma_nv) * 2.0 * PI;
    int prim = addprim(0, "polyline");
    for (int i = 0; i <= 12; i++){
        float u = -U + 2.0 * U * float(i) / 12.0;
        float x = ct * sinh(u) * sin(v) + st * cosh(u) * cos(v);
        float y = -ct * sinh(u) * cos(v) + st * cosh(u) * sin(v);
        float z = u * ct + (v - PI) * st;
        int pt = addpoint(0, set(x, z, y));
        setpointattrib(0, "Cd", pt, forma_ramp(0.14));
        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. 108 · CATENOID–HELICOID FAMILY — Leonhard Euler, 1744 · Jean Baptiste Meusnier, 1776 · the family: Ossian Bonnet, 1853
//   x = cos θ·sinh u·sin v + sin θ·cosh u·cos v
//   y = −cos θ·sinh u·cos v + sin θ·cosh u·sin v
//   z = u·cos θ + v·sin θ
// 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=catenoid

// 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.2 + forma_tweak("span_tweak");      // u — patch half-span · live 0.8 .. 1.6
var p_density = 14 + forma_tweak("density_tweak");    // wireframe lines · live 8 .. 24
var p_speed   = 0.08 + forma_tweak("speed_tweak");    // bend 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.20984634035266936;   // 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 catenoid–helicoid isometry: the one-parameter family of minimal
// surfaces x = cos θ sinh u sin v + sin θ cosh u cos v (and its partners) that
// bends the catenoid (θ = 0) into the helicoid (θ = π/2) without stretching.
// θ breathes from this plate's own phase at the speed on the slider, the
// view turns at 0.07 rad/s with a fixed pitch mixing 0.34 of the depth into
// the screen vertical, and the fit divides by the worst-case radius over the
// whole bend so no θ leaves the frame — all as the page does. A wireframe is
// two families of curves on one surface, so two parts: part 0 is the rings
// (fixed u), chained one to the next along the v = 0 meridian, which is
// itself a curve of the surface; part 1 is the meridians (fixed v), chained
// alternately along the top and bottom rims. Every connector lies on the
// surface, so the stroke shows nothing the wireframe does not have. The
// page's depth cue — nearer arcs brighter — is the stroke's to carry.
// parts: 2
var TAU = 6.283185307179586;
// the page's pingpong(t·speed + PHASE, 1) adds the phase a second time inside cycle()
var u01 = ((forma_t * p_speed + 2 * forma_phase) % 1 + 1) % 1;
var th = (u01 < 0.5 ? u01 * 2 : 2 - u01 * 2) * Math.PI / 2;
var ct = Math.cos(th), st = Math.sin(th);
var NU = Math.round(p_density), NV = Math.round(p_density * 1.6);
var U = p_span, seg = 36;
var FORMA_DEG = Math.PI / 180;
var rot = forma_t * 0.07 + p_view * FORMA_DEG, cr = Math.cos(rot), sr = Math.sin(rot);
var cx = forma_W / 2, cy = forma_H / 2;
var sc = Math.min(forma_W, forma_H) / (2.05 * Math.sqrt(Math.cosh(U) * Math.cosh(U) + Math.PI * Math.PI));
function forma_surf(u, v){
  var x = ct * Math.sinh(u) * Math.sin(v) + st * Math.cosh(u) * Math.cos(v);
  var y = -ct * Math.sinh(u) * Math.cos(v) + st * Math.cosh(u) * Math.sin(v);
  var z = u * ct + (v - Math.PI) * st;
  var X = x * cr + z * sr, Z = -x * sr + z * cr;
  var Y = y * 0.94 + Z * 0.34;
  return forma_pt(cx + X * sc, cy - Y * sc);
}
var pts = [];
if (forma_part === 1){
  for (var j = 0; j < NV; j++){
    var v = j / NV * TAU, down = (j % 2 === 0);
    for (var i = 0; i <= 12; i++){
      var u = down ? U - 2 * U * i / 12 : -U + 2 * U * i / 12;
      pts.push(forma_surf(u, v));
    }
    if (j + 1 < NV){                                   // along the rim to the next meridian
      var rim = down ? -U : U, v2 = (j + 1) / NV * TAU;
      for (var q = 1; q < 4; q++) pts.push(forma_surf(rim, v + (v2 - v) * q / 4));
    }
  }
} else {
  for (var r = 0; r < NU; r++){
    var ur = -U + 2 * U * r / (NU - 1);
    for (var k = 0; k <= seg; k++) pts.push(forma_surf(ur, k / seg * TAU));
  }
}
createPath(pts, [], [], false);