Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Going 3D

So far every shape has lived on a flat page. manic has a second world: a real 3D space you look at through a camera. The idea is exactly the same — you name a cast of shapes and call them out in a script — but now the shapes have depth, and you can spin the camera around them.

One rule of thumb: everything 3D ends in 3cube3, sphere3, move3, orbit3. That’s how you tell the two worlds apart.

First, a camera

A 3D scene needs a camera — an eye to look through. You say where the eye sits and which point it looks at:

camera3((8, -10, 6), (0, 0, 1), 45);   // eye position, look-at point, zoom

Positions are (x, y, z), and here z is up (x and y are the ground). You add one camera, and you can swing it around later.

The creator-first five

For most explainers, start with the relationship you want rather than camera coordinates or intermediate positions. 3D V2 adds five composition words:

wordcreator intent
view3(subject, "isometric", 1, smooth, 1.3)frame one object or tagged group, with margin
travel3(subject, route, 2, smooth)move along a line3, arrow3, or curve3
attach3(label, subject, (0,0,1))position-only follow (compatible default); add rigid to inherit orientation; use none to release
become3(subject, blueprint, 1, smooth)keep the subject id while it adopts another shape and style
turn3(group, pivot, z, 90, 1, smooth)rigidly turn one object or tagged group around a pivot and axis

Here is the core pattern. The sensor and wings travel with the ship, release without snapping, then the wings deploy and the ship becomes its final design:

attach3(sensor, ship, (0,0,0.8));
attach3(leftWing, ship, (0,-1.2,0));
attach3(rightWing, ship, (0,1.2,0));
par { draw(route, 2.8, smooth); travel3(ship, route, 2.8, smooth); }

attach3(sensor, none);
attach3(leftWing, none);  attach3(rightWing, none);
par {
  turn3(wings, ship, z, 90, 1.1, smooth);
  become3(ship, finalBlueprint, 1.1, smooth);
}
view3(spacecraft, "isometric", 1.2, smooth, 1.4);

▶ Compact five-word reference:

▶ Vertical creator story:

▶ Production lab (relationships, contours, finishes, OBJ, and variable tube):

Design details that make 3D feel professional

  • Give the main subject one persistent id. Declare alternate forms as hidden blueprints and use become3; do not swap the whole scene.
  • Tag parts that should turn or frame together. view3 and turn3 both accept a tag, so one spatial rig remains one authoring concept.
  • Attach before the shared move; release immediately before a part gets its own motion. Release preserves the resolved world position.
  • Use view3(...,"fit") to preserve the current viewing direction, or choose front, side, top, or isometric. A margin around 1.15–1.35 is useful for landscape; start around 1.45–1.7 for vertical video with text above and a social footer below.
  • Put shot changes at idea boundaries, then let object motion carry the middle of the explanation. Constant camera motion makes depth harder to read.
  • view3 uses transformed group bounds and the active canvas aspect ratio, so the same intent works in landscape and portrait without a second 3D mode. In a creator/quiz scene it also fits the actual media rectangle, not the full canvas behind the heading and social footer.
  • travel3 reads the route’s current transform every frame. A route may rotate or move in the same par block without pre-baking dozens of coordinates.
  • Use attach3(part, body, offset, rigid) for a mechanical assembly. Its offset lives in the body’s local frame, it turns with the body, and release freezes both position and orientation exactly.

▶ See it play:

The 3D cast

shapewritedraws
cube3cube3(box, (0,0,1), (2,2,2));a box (width, depth, height)
sphere3sphere3(ball, (0,0,1), 0.9);a ball of that radius
point3point3(p, (1,1,1));a small marker in space
line3 / arrow3arrow3(v, (0,0,0), (0,0,2));a segment / a vector
grid3grid3(floor, (0,0,0), 5, 1);a ground grid to sit things on
axes3axes3(ax, (0,0,0), 3);labelled x, y, z arrows

Style and reveal them with words you already know — color, opacity, show, flash:

cube3(box, (0, 0, 1), (2, 2, 2));   color(box, cyan);
show(box, 0.6);

Labelling a point in space

To put words on a 3D point, make an ordinary 2D text and pin it there with pin3. As the camera moves, the label sticks to its point:

text(tag, (0, 0), "origin");
pin3(tag, (0, 0, 0));

pin3 deliberately stays the same screen size. When the label should feel as if it lives in the world, use label3 and give its desired world height:

text(pName, (0,0), "P");
label3(pName, pointP, 0.35);   // gets smaller as P moves away

Relationships, projections, and contours

These are live construction words, not snapshots:

point3(p, (2,1,3));
project3(shadow, p, "xy");
link3(drop, p, shadow, 0.06);

surface3(bowl, "x^2+y^2", (-2,2), (-2,2), 24);
contour3(levelOne, bowl, 1);

Move p and both shadow and drop recompute every frame. contour3 accepts a surface3 height field and extracts the requested z level. Use these words for geometry, vectors, optimization, fields, and engineering callouts—the engine owns the relationship while the author owns the story.

Textbook dimension-story series

These portrait stories use the same 3D engine for a textbook-friendly purpose: start with one familiar object, preserve its identity, and let the next dimension arrive through motion. Each gallery page contains the complete source and its video card.

StoryDimensional journeyTeaching idea
The trapped light beam1D → 2D → 3DDistance grows from 5 to 13 to 85 through nested right triangles.
How space learned to growpoint → line → plane → roomA line sweeps into a surface; the surface lifts into volume.
Length, area, volume1D → 2D → 3DWhy measured units become cm, cm², and cm³.
A point gets an addressx → (x,y) → (x,y,z)Each new axis adds one coordinate to the same point.
The revolving semicirclediameter → curve → sphereA solid can be generated from a lower-dimensional rule.
Statistical dimensionslist → scatter → cloudMore variables require a richer coordinate world.
Dimension reduction3D → 2D → 1DA sphere reveals a section, then the section reveals a diameter.
Watermelon sectionswhole → halves → ¼ + ¾Perpendicular great-circle cuts create meaningful pieces.

Creator pattern: keep the mathematical subject persistent, introduce one axis or section at a time, and hold the settled frame long enough to read the new formula. Use view3(...,"fit") at dimensional boundaries; use ordinary object motion inside each explanation.

Curves and surfaces

Draw a wire through space from three formulas of t (a helix, here), or a surface from a height formula z = f(x, y):

curve3(helix, "cos(t)", "sin(t)", "t*0.2", (0, 12));
surface3(wave, "sin(x)*cos(y)", (-3, 3), (-3, 3));

For shapes a plain height field can’t make — a torus, a Möbius strip — use param3, which takes three formulas of two parameters, u and v:

param3(torus, "(3 + cos(v))*cos(u)", "(3 + cos(v))*sin(u)", "sin(v)",
       (0, 6.28), (0, 6.28));

One formula rule: always put * between names. Write pi*t, never pit (manic reads pit as one unknown word). Same for v*v, not vv.

Textbook sections: halves, quarters, and the remainder

param3 is also the V2 route for an exact authored section of a curved solid. Bound one parameter to describe only the required half or quarter of the sphere, and add a second bounded surface for the exposed cut face. This keeps the lesson mathematical: the geometry is defined by the section, not hidden by a flat mask.

The watermelon example uses that pattern to compare horizontal and vertical great-circle cuts, then makes two perpendicular cuts and separates a quarter from its three-quarter remainder:

// A sphere quarter: longitude u spans 90°, latitude v spans the full height.
param3(quarter,
  "3*cos(v)*cos(u)", "3*cos(v)*sin(u)", "3*sin(v)",
  (0, pi/2), (-pi/2, pi/2));
finish3(quarter, "shading=smooth material=matte depth=0.25 shadow=0.18");

▶ Animated textbook reconstruction:

Open the complete, copyable source in the 3D scenes gallery.

Practical authoring tips:

  • Keep the outside shell and each exposed section face as separate tagged entities. They can be revealed, shifted, and recoloured as one teaching unit.
  • Use a light paper template and restrained face colours when the goal is a textbook diagram; depth should clarify the construction, not dominate it.
  • Frame each new construction with view3(tag,"fit",...) after the cast changes. The camera then follows the mathematical subject instead of fixed coordinates.
  • V2 supports exact authored sections through bounded param3. A generic verb that cuts any arbitrary solid and automatically creates the resulting pieces is intentionally deferred to V3.

Solids

Build filled, shaded solids:

  • prism3 / pyramid3 — n-sided prisms and cones (use many sides for a cylinder or a smooth cone).
  • revolve3 — spin a radius profile r(t) around the upright axis (vases, spheres, lathe shapes).
  • extrude3 — lift a flat 2D shape (even a boolean cut-out) straight up into a solid.
prism3(hex, (0, 0, 1), 6, 1, 2);
revolve3(vase, (3, 0, 1.5), "0.7 + 0.4*sin(t*2)", (0, 3));

Giving lines some body

A 3D line, arrow, or curve is a thin thread by default. thick turns it into a rounded tube (arrows grow a solid head):

arrow3(v, (0, 0, 0), (2, 2, 2));   thick(v, 0.04);

For a horn, vessel, nerve, pipe, or any path whose radius changes, use a normalized radius profile (t=0 start, t=1 end):

curve3(spine, "4*t-2", "sin(6*t)*0.2", "0", (0,1));
tube3(horn, spine, "0.06 + 0.28*t", 14);

One optional render finish

The default is still Manic’s restrained, template-aware diagram rendering. When an object needs a different surface treatment, finish3 keeps the choice in one bounded string:

finish3(globe, "shading=smooth material=metal depth=0.2 shadow=0.2");
finish3(terrain, "mesh=0.25 texture=checker scale=3");
finish3(shell, "material=glass shading=smooth");
  • Start with shading=smooth for spheres/organic surfaces and leave boxes flat.
  • A little mesh clarifies topology; 1 is intentionally strong.
  • depth and shadow are subtle readability controls from 0 to 1, not a replacement for authored lighting.
  • checker and stripes are procedural and deterministic; Manic does not load texture scripts or arbitrary shaders.

Controlled OBJ models

model3(mark, "asset:models/manic-pyramid.obj", (0,0,1), 1.4);
finish3(mark, "shading=smooth material=metal mesh=0.12");

model3 reads geometry only: OBJ vertices, polygon faces (triangulated), and lines. It ignores material/script features and enforces file/geometry limits. An asset: URI selects a file packaged with Manic, so it works from the CLI, Docker image, or production backend without a launch-directory assumption or extra flag. An ordinary path such as uploads/my-model.obj still works for a user-owned model, but the UI/backend must provision that file. Use built-in solids when they express the same idea—they remain the lightest option.

Available bundled 3D assets

Stable URIWhat it isGood for
asset:models/manic-pyramid.objSmall generic pyramid OBJLearning model3, a beacon, marker, monument, or placeholder model

Bundled names are intentionally few and predictable. See the full Bundled assets catalog and do not invent an asset: name that is not listed. To add one to Manic itself, place the geometry-only file under assets/models/, document its URI here and in assets/README.md, and add a checked example. The release, Docker, EC2, and playground pipelines copy the complete assets/ directory automatically, so future catalog entries need no per-file deployment rule.

Moving in 3D

Same rhythm as the 2D verbs, with the 3 on the end:

par {
  rotate3(box, (0, 0, 360), 4, linear);   // spin the box
  orbit3(70, 25, 12, 4, smooth);          // orbit the camera around it
  roll3(-20, 4, smooth);                   // bank around the view direction
}
  • move3 / shift3 — move to / by a point
  • rotate3 — turn it (degrees around x, y, z)
  • grow3 — stretch a line or arrow’s tip to a new point
  • orbit3 — swing the camera (angle around, angle up, distance)
  • roll3 — bank the camera around its viewing direction; it can run beside orbit3 in par, including through stable overhead/underside views
  • look3 — aim the camera at a new point

These remain the precise controls. Use them when exact coordinates or a specific orbit are part of the explanation; use the creator-first five for camera composition and relationship choreography.

Check the transitions, not only the last frame

manic check examples/three-d-v2-lab.manic --canvas portrait

The publishing audit samples camera transitions between named steps. It warns when projected 3D bounds leave the creator media rectangle, an orbit/zoom reads as a shock, the eye enters geometry, or a live spatial relationship has lost its source. It also checks the settled frame of every step. The most useful repair is usually semantic: tag the shot’s subject and call view3(tag,"fit") after the cast changes, or give a camera beat more duration.

Morphing one shape into another

morph3 sets a shape up to become another; then to(..., morph, ...) blends between them. It works for curves, surfaces, and solids — even a cube turning into a sphere:

cube3(a, (0, 0, 1), (2, 2, 2));
sphere3(b, (0, 0, 0), 1.2);   hidden(b);
morph3(a, b);
to(a, morph, 1, 2.5, smooth);   // a cube melts into a ball

Which words work in 3D?

3D shapes speak most of the same vocabulary — color, opacity, hidden, untraced, tag, and the verbs show, fade, draw, flash, pulse, scale. A handful of words are 2D-only and will politely refuse on a 3D shape (with a message that names the 3D replacement):

if you reach for…on a 3D shape, use…
huecolor with a palette name
strokethick
move / rotate / spinmove3 / rotate3
cam / zoomcamera3 / orbit3
morphmorph3

Now see it all in motion in the 3D scenes gallery.