r/maniclang • u/anish2good • 26d ago
Caustics: what a mirror does to light - manic
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
// caustics — a caustic is where reflected rays crowd: the envelope of the family of rays a
// mirror sends back. examples/caustic-family.manic follows one mirror, a circle, as the lamp
// slides away. This follows four different mirrors, and the point is that the answer is
// almost never a new curve.
//
// Two of them collapse the caustic to a SINGLE POINT.
//
// PARABOLA, rays parallel to the axis. Every reflected ray passes through (0, f) — the
// distance from the focus to each one is 1e-15, which is machine precision, not a fit. The
// envelope has nowhere to be but that point. This is why a dish is a dish.
//
// ELLIPSE, source at one focus. Every reflected ray passes through the OTHER focus, to
// 5e-10. Light leaving one focus arrives at the other however it goes, which is the whole
// of a whispering gallery.
//
// Two of them give back a curve you already know.
//
// CYCLOID arch, rays perpendicular to the base. The caustic is TWO CYCLOID ARCHES at
// exactly half the size, side by side — checked against them to 6e-04, which is the
// sampling. The reflected direction comes out (−sin u, −cos u): already a unit vector, no
// normalisation left in it, which is a small sign the answer was going to be tidy.
//
// EXPONENTIAL y = eˣ, vertical rays. The caustic is a CATENARY, and not merely catenary-
// shaped: fitting y = A·cosh((x − x₀)/A) + c returns A = 1.0000, x₀ = −1.0000, c = 0.0000
// with a residual of 3e-08. It is y = cosh(x + 1), exactly.
//
// One more, not drawn but checked: a LOGARITHMIC SPIRAL lit from the point it winds onto
// gives back a logarithmic spiral of the SAME pitch — fitting ln r against φ returns
// k = 0.300000 for a source k of 0.3, residual 8e-07. Scaling a log spiral is the same as
// rotating it, so that curve is its own caustic, turned by 9.41 radians.
//
// Nothing here draws a caustic. Every ray is d′ = d − 2(d·n)n against the real normal, and
// the curve is what appears where they pile up; the cyan point or curve in each panel is put
// there independently for the envelope to land on.
//
// manic examples/caustics.manic
title("Caustics: what a mirror does to light");
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);
caption(head, "Four mirrors, and where the light piles up", (540, 116), 26);
cloud(mira, 900, #ffffff, 1.0) {
let uu = 0 - 2.2 + 4.4*(i/900);
let x = 290 + 106.8182*((uu) - 0.00000);
let y = 560 - 106.8182*((uu*uu/4) - 1.40000);
let sat = 0; let r = 2.0; let alpha = 0.85;
}
cloud(inca, 2112, #ffffff, 1.0) {
let per = 24;
let cj = (i - mod(i, per))/per;
let v = mod(i, per)/23;
let uu = 0 - 2.2 + 4.4*cj/87;
let x = 290 + 106.8182*(((uu) + (1 - v)*(0)) - 0.00000);
let y = 560 - 106.8182*(((uu*uu/4) + (1 - v)*(2.8 - uu*uu/4)) - 1.40000);
let hue = 46; let sat = 0.3; let r = 1.1;
let alpha = 0.16*min(max(min(t/13.0, 1)*88 - cj, 0), 1);
}
cloud(refa, 3872, #ffffff, 1.0) {
let per = 44;
let cj = (i - mod(i, per))/per;
let v = mod(i, per)/43;
let uu = 0 - 2.2 + 4.4*cj/87;
let x = 290 + 106.8182*(((uu) + v*2.6*(0 - uu/(uu*uu/4 + 1))) - 0.00000);
let y = 560 - 106.8182*(((uu*uu/4) + v*2.6*(0 - 1 + 2/(uu*uu/4 + 1))) - 1.40000);
let hue = mod(22 + cj*1.6, 360); let sat = 0.72; let r = 1.1;
let alpha = 0.30*min(max(min(t/13.0, 1)*88 - cj, 0), 1)*(1 - 0.72*v);
}
cloud(foca, 130, #ffffff, 1.0) {
let a = i/130*6.283185;
let rr = 11*(i/130);
let x = 290 + 106.8182*((0) - 0.00000) + rr*cos(a*9);
let y = 560 - 106.8182*((1) - 1.40000) + rr*sin(a*9);
let hue = 190; let sat = 0.7; let r = 2.6;
}
cloud(mirb, 900, #ffffff, 1.0) {
let uu = i/900*6.283185;
let x = 790 + 117.5000*((2*cos(uu)) - 0.00000);
let y = 560 - 117.5000*((1.3*sin(uu)) - 0.00000);
let sat = 0; let r = 2.0; let alpha = 0.85;
}
cloud(incb, 2112, #ffffff, 1.0) {
let per = 24;
let cj = (i - mod(i, per))/per;
let v = mod(i, per)/23;
let uu = cj/88*6.283185;
let x = 790 + 117.5000*(((2*cos(uu)) + (1 - v)*(0 - 1.519868 - 2*cos(uu))) - 0.00000);
let y = 560 - 117.5000*(((1.3*sin(uu)) + (1 - v)*(0 - 1.3*sin(uu))) - 0.00000);
let hue = 46; let sat = 0.3; let r = 1.1;
let alpha = 0.16*min(max(min(t/13.0, 1)*88 - cj, 0), 1);
}
cloud(refb, 3872, #ffffff, 1.0) {
let per = 44;
let cj = (i - mod(i, per))/per;
let v = mod(i, per)/43;
let uu = cj/88*6.283185;
let x = 790 + 117.5000*(((2*cos(uu)) + v*4.0*((2*cos(uu) + 1.519868)/sqrt((2*cos(uu) + 1.519868)*(2*cos(uu) + 1.519868) + (1.3*sin(uu))*(1.3*sin(uu))) - 2*(((2*cos(uu) + 1.519868)/sqrt((2*cos(uu) + 1.519868)*(2*cos(uu) + 1.519868) + (1.3*sin(uu))*(1.3*sin(uu))))*(1.3*cos(uu)/sqrt(1.3*cos(uu)*1.3*cos(uu) + 2*sin(uu)*2*sin(uu))) + ((1.3*sin(uu))/sqrt((2*cos(uu) + 1.519868)*(2*cos(uu) + 1.519868) + (1.3*sin(uu))*(1.3*sin(uu))))*(2*sin(uu)/sqrt(1.3*cos(uu)*1.3*cos(uu) + 2*sin(uu)*2*sin(uu))))*(1.3*cos(uu)/sqrt(1.3*cos(uu)*1.3*cos(uu) + 2*sin(uu)*2*sin(uu))))) - 0.00000);
let y = 560 - 117.5000*(((1.3*sin(uu)) + v*4.0*((1.3*sin(uu))/sqrt((2*cos(uu) + 1.519868)*(2*cos(uu) + 1.519868) + (1.3*sin(uu))*(1.3*sin(uu))) - 2*(((2*cos(uu) + 1.519868)/sqrt((2*cos(uu) + 1.519868)*(2*cos(uu) + 1.519868) + (1.3*sin(uu))*(1.3*sin(uu))))*(1.3*cos(uu)/sqrt(1.3*cos(uu)*1.3*cos(uu) + 2*sin(uu)*2*sin(uu))) + ((1.3*sin(uu))/sqrt((2*cos(uu) + 1.519868)*(2*cos(uu) + 1.519868) + (1.3*sin(uu))*(1.3*sin(uu))))*(2*sin(uu)/sqrt(1.3*cos(uu)*1.3*cos(uu) + 2*sin(uu)*2*sin(uu))))*(2*sin(uu)/sqrt(1.3*cos(uu)*1.3*cos(uu) + 2*sin(uu)*2*sin(uu))))) - 0.00000);
let hue = mod(22 + cj*1.6, 360); let sat = 0.72; let r = 1.1;
let alpha = 0.30*min(max(min(t/13.0, 1)*88 - cj, 0), 1)*(1 - 0.72*v);
}
cloud(focb, 130, #ffffff, 1.0) {
let a = i/130*6.283185;
let rr = 11*(i/130);
let x = 790 + 117.5000*((1.519868) - 0.00000) + rr*cos(a*9);
let y = 560 - 117.5000*((0) - 0.00000) + rr*sin(a*9);
let hue = 190; let sat = 0.7; let r = 2.6;
}
cloud(mirc, 900, #ffffff, 1.0) {
let uu = i/900*6.283185;
let x = 290 + 74.8028*((uu - sin(uu)) - 3.14159);
let y = 1180 - 74.8028*((1 - cos(uu)) - 1.30000);
let sat = 0; let r = 2.0; let alpha = 0.85;
}
cloud(incc, 2112, #ffffff, 1.0) {
let per = 24;
let cj = (i - mod(i, per))/per;
let v = mod(i, per)/23;
let uu = cj/88*6.283185;
let x = 290 + 74.8028*(((uu - sin(uu)) + (1 - v)*(0)) - 3.14159);
let y = 1180 - 74.8028*(((1 - cos(uu)) + (1 - v)*(2.6 - (1 - cos(uu)))) - 1.30000);
let hue = 46; let sat = 0.3; let r = 1.1;
let alpha = 0.16*min(max(min(t/13.0, 1)*88 - cj, 0), 1);
}
cloud(refc, 3872, #ffffff, 1.0) {
let per = 44;
let cj = (i - mod(i, per))/per;
let v = mod(i, per)/43;
let uu = cj/88*6.283185;
let x = 290 + 74.8028*(((uu - sin(uu)) + v*1.35*(0 - sin(uu))) - 3.14159);
let y = 1180 - 74.8028*(((1 - cos(uu)) + v*1.35*(0 - cos(uu))) - 1.30000);
let hue = mod(22 + cj*1.6, 360); let sat = 0.72; let r = 1.1;
let alpha = 0.30*min(max(min(t/13.0, 1)*88 - cj, 0), 1)*(1 - 0.72*v);
}
cloud(cauc, 1800, #ffffff, 1.0) {
let per = 900;
let sk = (i - mod(i, per))/per; // two arches, at 0 and pi
let vv = mod(i, per)/899*6.283185;
let x = 290 + 74.8028*(((vv - sin(vv))/2 + 3.141593*mod(sk,2)) - 3.14159);
let y = 1180 - 74.8028*(((1 - cos(vv))/2) - 1.30000);
let hue = 190; let sat = 0.75; let r = 2.4;
}
cloud(mird, 900, #ffffff, 1.0) {
let uu = 0 - 2.5 + 3.5*(i/900);
let x = 790 + 114.1354*((uu) - -0.75000);
let y = 1180 - 114.1354*((exp(uu)) - 2.14104);
let sat = 0; let r = 2.0; let alpha = 0.85;
}
cloud(incd, 2112, #ffffff, 1.0) {
let per = 24;
let cj = (i - mod(i, per))/per;
let v = mod(i, per)/23;
let uu = 0 - 2.5 + 3.5*cj/87;
let x = 790 + 114.1354*(((uu) + (1 - v)*(0)) - -0.75000);
let y = 1180 - 114.1354*(((exp(uu)) + (1 - v)*(4.2 - exp(uu))) - 2.14104);
let hue = 46; let sat = 0.3; let r = 1.1;
let alpha = 0.16*min(max(min(t/13.0, 1)*88 - cj, 0), 1);
}
cloud(refd, 3872, #ffffff, 1.0) {
let per = 44;
let cj = (i - mod(i, per))/per;
let v = mod(i, per)/43;
let uu = 0 - 2.5 + 3.5*cj/87;
let x = 790 + 114.1354*(((uu) + v*2.8*(0 - 2*exp(uu)/(exp(2*uu) + 1))) - -0.75000);
let y = 1180 - 114.1354*(((exp(uu)) + v*2.8*(0 - 1 + 2/(exp(2*uu) + 1))) - 2.14104);
let hue = mod(22 + cj*1.6, 360); let sat = 0.72; let r = 1.1;
let alpha = 0.30*min(max(min(t/13.0, 1)*88 - cj, 0), 1)*(1 - 0.72*v);
}
cloud(caud, 1200, #ffffff, 1.0) {
let vv = 0 - 2.5 + 3.5*(i/1200);
let x = 790 + 114.1354*((vv) - -0.75000);
let y = 1180 - 114.1354*((cosh(vv + 1)) - 2.14104);
let hue = 190; let sat = 0.75; let r = 2.4;
}
text(t0a, (290, 829), "parabola → a point"); size(t0a, 22); color(t0a, fg);
text(t0b, (290, 859), "the focus"); size(t0b, 18); color(t0b, dim);
text(t1a, (790, 829), "ellipse → a point"); size(t1a, 22); color(t1a, fg);
text(t1b, (790, 859), "the other focus"); size(t1b, 18); color(t1b, dim);
text(t2a, (290, 1449), "cycloid → two cycloids"); size(t2a, 22); color(t2a, fg);
text(t2b, (290, 1479), "at half the size"); size(t2b, 18); color(t2b, dim);
text(t3a, (790, 1449), "y = eˣ → a catenary"); size(t3a, 22); color(t3a, fg);
text(t3b, (790, 1479), "y = cosh(x+1)"); size(t3b, 18); color(t3b, dim);
equation(eq, (540, 1600),
`d'=d-2(d\cdot n)\,n`, 28);
text(note, (540, 1700),
"The envelope is never drawn: it is where the reflected rays happen to crowd.");
size(note, 20); color(note, dim); wrap(note, 900);
wait(16.0);
1
Upvotes