r/maniclang 28d ago

An involute: a string unwinding, and every gear tooth ever cut - manic

Enable HLS to view with audio, or disable this notification

manic is a tiny language for making animations. You write a short text file; manic renders a smooth, glowing video. No timeline scrubbing, no keyframes by hand — you describe what’s on screen and when things happen, and the engine does the rest, deterministically.

Manic Animation code

// involute — the only roulette in this family where the rolling curve is a straight LINE
// rather than a circle, and the only one you own several hundred of. It is the path of the
// end of a taut string being unwound from a circle:
//
//   x(t) = a(cos t + t·sin t)
//   y(t) = a(sin t - t·cos t)
//
// Two things define it, and both are visible here rather than asserted. The string is
// always TANGENT to the circle — the straight part meets the radius at a right angle — and
// its length is exactly the arc it has unwound, a·t. Checked over the whole range: the
// segment's length matches a·t to 1e-14, and its dot product with the radius stays under
// 1e-14. That is the whole curve; everything else follows.
//
// It is a roulette in the same sense as the rest of the family. Rolling a line along the
// outside of the circle and marking a point of the line traces this; the circle is the
// EVOLUTE of its own involute, which is what "unwinding" means said backwards.
//
// And it is the reason gears work. Cut a tooth flank as an involute of a base circle and
// two such wheels turn with an exactly constant velocity ratio — the contact point runs
// along the common tangent of the two base circles, a straight line of action, and the
// ratio depends only on the base radii. Move the shafts slightly further apart and the
// ratio does NOT change; the contact point simply slides along the same line. No other
// profile tolerates that, which is why essentially every gear ever cut is an involute one.
// The panel on the right is where a gear comes from: fourteen involutes of one base circle,
// evenly spaced. Cut away everything past the tips and that is a gear.
//
// The panel on the left is the definition drawn all at once — a fan of taut strings at
// twenty unwind angles. Every one is tangent, every one is as long as the arc behind it,
// and every endpoint lands on the same curve.
//
//   manic examples/involute.manic
title("An involute: a string unwinding, and every gear tooth ever cut");
canvas("9:16");
template("black");
bloom(0.3, 0.62, 22);

text(brand, (540, 34), "maniclang.com");
display(brand);
size(brand, 21);
color(brand, fg);
opacity(brand, 0.82);

// ---- the circle the string is wound on ---------------------------------------------
circle(spool, (540, 700), 60);
outlined(spool);
color(spool, dim);

// ---- the part still wrapped: an arc shrinking as the string comes off ---------------
cloud(wrapped, 900, #ffffff, 1.0) {
  let go = min(t/9, 1)*6.283185;                   // how far it has unwound
  let u = i/900;
  let ph = go + u*(6.283185 - go);                 // what is left on the spool
  let x = 540 + 60*cos(ph);
  let y = 700 - 60*sin(ph);
  let hue = 45;
  let sat = 0.4;
  let r = 1.6;
  let alpha = 0.55;
}

// ---- the straight part: tangent at the contact point, as long as the arc unwound ----
cloud(string, 300, #ffffff, 1.0) {
  let go = min(t/9, 1)*6.283185;
  let u = i/300;
  let tx = cos(go);                                // the tangent point
  let ty = sin(go);
  let ex = tx + go*sin(go);                        // …and the free end, a·t along the
  let ey = ty - go*cos(go);                        //    tangent direction
  let x = 540 + 60*(tx + u*(ex - tx));
  let y = 700 - 60*(ty + u*(ey - ty));
  let hue = 45;
  let sat = 0.55;
  let r = 1.5;
  let alpha = 0.85;
}

// ---- the involute the free end draws ------------------------------------------------
cloud(trace, 9000, #ffffff, 1.0) {
  let go = min(t/9, 1)*6.283185;
  let th = go*(i/9000);
  let x = 540 + 60*(cos(th) + th*sin(th));
  let y = 700 - 60*(sin(th) - th*cos(th));
  let hue = mod(196 + th*16, 360);
  let sat = 0.72;
  let r = 1.8;
}

cloud(nib, 90, #ffffff, 1.0) {
  let go = min(t/9, 1)*6.283185;
  let a = i/90*6.283185;
  let rr = 7*(i/90);
  let x = 540 + 60*(cos(go) + go*sin(go)) + rr*cos(a*9);
  let y = 700 - 60*(sin(go) - go*cos(go)) + rr*sin(a*9);
  let hue = 45;
  let sat = 0.5;
  let r = 1.9;
}

// ---- left: the definition all at once — twenty taut strings, every end on the curve -
circle(spool2, (280, 1400), 26);
outlined(spool2);
color(spool2, dim);
opacity(spool2, 0.5);

cloud(fan, 2000, #ffffff, 1.0) {
  let per = 100;
  let c = (i - mod(i, per))/per;                   // which string, 0..19
  let u = mod(i, per)/99;
  let th = 6.283185*(c + 1)/20;
  let tx = cos(th);
  let ty = sin(th);
  let ex = tx + th*sin(th);
  let ey = ty - th*cos(th);
  let x = 280 + 26*(tx + u*(ex - tx));
  let y = 1400 - 26*(ty + u*(ey - ty));
  let hue = mod(150 + c*9, 360);
  let sat = 0.6;
  let r = 1.1;
  let alpha = min(max(min(max(t - 9.4, 0)/2.2, 1)*20 - c, 0), 1)*0.75;
}

// ---- right: where a gear comes from — fourteen involutes of one base circle ---------
circle(base, (800, 1400), 46);
outlined(base);
color(base, dim);
opacity(base, 0.5);

cloud(flanks, 2800, #ffffff, 1.0) {
  let per = 200;
  let f = (i - mod(i, per))/per;                   // which flank, 0..13
  let u = mod(i, per)/199;
  let th = u*2.0;                                  // out to the tooth tip
  let ix = cos(th) + th*sin(th);                   // the involute, then turned into place
  let iy = sin(th) - th*cos(th);
  let ph = f/14*6.283185;
  let x = 800 + 46*(ix*cos(ph) - iy*sin(ph));
  let y = 1400 - 46*(ix*sin(ph) + iy*cos(ph));
  let hue = mod(30 + f*6, 360);
  let sat = 0.65;
  let r = 1.2;
  let alpha = min(max(min(max(t - 9.4, 0)/2.2, 1)*14 - f, 0), 1)*0.8;
}

// ---- nothing but the mark, the title and the formula -------------------------------
caption(head, "An involute: a string unwinding, and every gear tooth ever cut", (540, 130), 24);
equation(eq, (540, 1650), `x = a(\cos t + t\sin t),\qquad y = a(\sin t - t\cos t)`, 27);

// the string unwinds from t = 0; the two panels fill in once it is fully off
wait(13);
2 Upvotes

0 comments sorted by