The language layer
Everything so far has been static text. manic also has a small computation layer that runs before the animation — variables, arithmetic, loops, and macros. It lets one rule draw a hundred shapes.
These are resolved at build time. By the time the animation plays, they’ve expanded into plain calls — so they cost nothing at render.
Variables — let
let r = 120;
let gap = r * 2 + 40;
circle(a, (cx - gap, cy), r);
circle(b, (cx + gap, cy), r);
Arithmetic is what you’d expect: + - * / ^, parentheses, and functions like
sin, cos, sqrt. Constants pi, tau, e are built in, as are the canvas
vars w, h, cx, cy.
Put a
*between names. A number can hug a name (2r,3(x+1)), but two names glued together read as one word — writer*x,i*dx,pi*t, neverrx/idx/pit. This is the most common slip in loops and formulas.
Loops — for
for i in 0..5 {
dot(p{i}, (200 + i*180, cy), 8); // p0 … p4
}
p{i} is id interpolation — {expr} glued to a name makes each entity
unique. Use i in the body to compute positions, sizes, hues…
// one for-loop paints a full rainbow ring.
title("One loop"); canvas("16:9");
text(t, (cx, 90), "one loop, 24 dots, every hue"); color(t, cyan); size(t, 28); hidden(t);
let n = 24;
for i in 0..n {
dot(p{i}, (cx + 300*cos(tau*i/n), cy + 300*sin(tau*i/n)), 14);
hue(p{i}, 360*i/n); // colour by angle -> a rainbow
hidden(p{i});
}
show(t, 0.5);
stagger(0.05) { for i in 0..n { show(p{i}); } }
wait(1.4);
▶ See it play:
Conditionals — if
let n = 5;
if n > 3 {
circle(big, (cx, cy), 120);
}
Macros — def
A def is a reusable rule. Its parameters are numbers; build ids inside with
interpolation. It can even call itself (recursion) — that’s how the fractal tree
in the gallery is one page of code:
def branch(k, x, y, ang, len, depth) {
if depth > 0 && len > 2 {
let x2 = x + len*cos(ang);
let y2 = y - len*sin(ang);
line(seg{k}, (x, y), (x2, y2));
branch(2*k, x2, y2, ang + 0.4, len*0.72, depth - 1);
branch(2*k + 1, x2, y2, ang - 0.4, len*0.72, depth - 1);
}
}
branch(1, cx, h - 40, 1.5708, 150, 12);
Curve L-systems — one rule, one path
For deterministic textbook curves, use lsystem instead of manually recursing
into thousands of separate lines:
lsystem(carpet, (cx,cy), 620,
"F+F+F+F",
"F=FF+F+F+F+FF",
"angle=90 heading=0 iterations=4");
gradient(carpet, cyan, magenta, gold);
stroke(carpet, 2);
untraced(carpet);
draw(carpet, 3, smooth);
The six arguments are:
- id, centre, and fitted square size;
- the starting string (axiom);
- semicolon-separated rewrite rules such as
"X=...;Y=..."; - one short options string.
In the generated program, + turns left by angle, - turns right, |
turns around, and every symbol listed by draw=F moves forward while drawing.
heading sets the first direction, iterations controls detail, and padding
keeps the result away from its fitted edge.
Use closed=true fill=true when the grammar describes a closed boundary; Manic
triangulates it into one concave region. Keep fill=false for line curves. The
constructor deliberately does not interpret branch brackets: use a recursive
def or tree3 for branching plants. This separation keeps curve stories
simple and makes invalid input easy to repair.
Practical tips
- Increase
iterationsone step at a time: rewrite systems grow exponentially. - Keep the curve as one entity, then use ordinary
draw,gradient,move,become,step, and camera verbs. - For a lesson, show generations 0 → 1 → 2 before revealing the detailed curve.
- See
lsystem-asymptote-curves.manicfor four canonical grammars andcreator-lsystem-fractal-curve.manicfor a complete creator Short.
Repeated motifs — one design, many placements
Use repeat when the visual idea is “this motif forms a field,” not “write a
loop that manually copies every part.”
polygon(tile, (cx-12,cy+10), (cx+12,cy+10), (cx,cy-14));
repeat(field, tile, "layout=hex rings=4 spacing=42 rotate=30 scale=0.8");
untraced(field);
draw(field, 2, smooth);
The motif can be one entity or a tag containing several entities. Choose:
layout=hex rings=N spacing=S;layout=grid rows=R cols=C spacing=S(or separategapx/gapy);layout=radial count=N radius=R face=same|out.
rotate turns the arrangement and scale sizes each instance. Every output is
tagged with the destination id; stable instances are {id}.i0, {id}.i1, …
The output of one repeat can itself be the motif of another, which is how
nested textbook tilings stay short. Use normal color, gradient, mask,
untraced, draw, and stagger afterward.
See asymptote-tiling-reference.manic for hex/grid/radial/nested coverage and
creator-one-tile-pattern-story.manic for the creator treatment.
Reductions
Fold a range into one number with sum, prod, min, max:
let n = 6;
let total = sum(i in 1..n : i); // 1 + 2 + … + (n-1)
That’s the whole language. The rest is kits — bundles of higher-level figures → Kits.