DEFINITION
r(θ) = cos(k·θ), k = n/d
NOTES
A sinusoid in polar coordinates. Grandi named these rose curves for the obvious reason. When k is an integer the bloom has k petals if k is odd and 2k if it is even — a parity quirk that comes from whether the negative half of the cosine retraces the positive half or lands opposite it.
PROVENANCE
- Origin
- Guido Grandi, Italian mathematician, c. 1723
- Standing
- Public domain — 18th-century mathematics
- Constants
- n/d rational gives a closed curve; large d gives dense rosettes
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. 02 · RHODONEA — Guido Grandi, c. 1723
// r(θ) = cos(k·θ), k = n/d
// 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=rose
float p_n = 5 + chf('n_tweak'); // n — numerator · live 1 .. 13
float p_d = 4 + chf('d_tweak'); // d — denominator · live 1 .. 13
// 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)));
}
// r = cos(k·θ) with k = n/d closes after d full turns of θ, tracing every
// petal exactly once — the same span the plate walks.
int forma_n = 4000;
int nn = int(p_n), dd = int(p_d);
float k = float(nn) / float(dd);
float turns = 6.28318530718 * float(dd);
int prim = addprim(0, "polyline");
for (int i = 0; i <= forma_n; i++){
float th = float(i) / float(forma_n) * turns;
float r = cos(k * th);
// canvas y runs down; negated so the curve sits as the plate shows it
int pt = addpoint(0, set(r * cos(th), -r * sin(th), 0.0));
// colour sweeps the bright lobe of the ramp along the curve
float u = float(i) / float(forma_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
// FORMA — PL. 02 · RHODONEA — Guido Grandi, c. 1723
// r(θ) = cos(k·θ), k = n/d
// 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.
// https://forma-gen.com/#plate=rose
// 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_n = 5 + forma_tweak("n_tweak"); // n — numerator · live 1 .. 13
var p_d = 4 + forma_tweak("d_tweak"); // d — denominator · live 1 .. 13
// 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.46361143887043; // 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]; }
// r = R·cos(kθ) with k = n/d, traced over d full turns so a rational k closes.
// The page's comet circles once every ~24 s (Trim Paths, End 0 → 100%).
var R = Math.min(forma_W, forma_H) * 0.45, k = p_n / p_d;
var turns = Math.PI * 2 * p_d, N = 4000;
var pts = [];
for (var i = 0; i <= N; i++){
var th = i / N * turns, r = R * Math.cos(k * th);
pts.push(forma_pt(forma_W / 2 + r * Math.cos(th), forma_H / 2 + r * Math.sin(th)));
}
createPath(pts, [], [], true);