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 3 — cube3, sphere3, move3,
orbit3. That’s how you tell the two worlds apart.
Generated and derived 3D geometry
These constructors cover large scientific structures without expanding the scene into thousands of named objects:
implicit3(shell,"x*x+y*y-z*z",(-2,2),(-2,2),(-2,2),1,24);
collection3data(nodes,(0,0,0),"0 0 0; 1 0 0; 1 1 1; 0 1 0");
links3data(edges,nodes,"0 1; 1 2; 2 3; 3 0");
lsystem3(tree,(0,0,0),0.18,25,5,"F","F=F[+F][-F][^F]");
implicit3samples a bounded scalar field and extracts one isosurface.collection3dataandlinks3dataretain explicit coordinates and arbitrary edges as two batches—useful for Cayley diagrams, dependency graphs, and imported scientific datasets.lsystem3is a 3D turtle grammar:Fdraws,fmoves,+/-yaw,^/&pitch,</>roll, and brackets branch.
To show the three shadows of a moving curve, derive them from the source:
curve3(path,"2*cos(t)","2*sin(t)","t/3",(0,12.56));
projectpath3(xyShadow,path,xy);
projectpath3(xzShadow,path,xz);
projectpath3(yzShadow,path,yz);
par {
rotate3(path,(20,50,10),2,smooth);
orbit3(55,20,18,2,smooth);
}
The projected paths resample the source after its current transform, so the shadows cannot drift away from the geometry they explain.
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:
| word | creator 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.
view3andturn3both 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 choosefront,side,top, orisometric. A margin around1.15–1.35is useful for landscape; start around1.45–1.7for 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.
view3uses 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.travel3reads the route’s current transform every frame. A route may rotate or move in the sameparblock 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:
Scientific frames: 2D, textbook 3D, and spatial 3D
Use the smallest truthful dimension. A genuinely planar geometry problem should
stay in Manic’s native 2D shapes. When the relationship is spatial but the
result belongs on a calm textbook page, use a real 3D frame3 with an
orthographic camera and mode=textbook. When depth becomes part of the lesson,
restyle that same frame and orbit it:
camera3((9,-11,8), (0,0,0), 12, orthographic);
frame3(world, (0,0,0), (8,8,6),
"x=-4..4 y=-4..4 z=-3..3 planes=xy:min,xz:min,yz:min major=2 minor=1 mode=textbook");
// Later: same coordinates, geometry, labels, and identity.
par {
present3(world, spatial, 0.7, smooth);
orbit3(52, 28, 12, 2.4, smooth);
}
The options are compact and composable:
| need | option |
|---|---|
| data bounds | x=-2..4 y=0..8 z=0.1..100 |
| one or more walls | planes=xy:min,xz:min,yz:min |
| a top or central plane | planes=xy:max,xz:origin |
| an exact section | planes=xy:-0.5 |
| two coloured parallel sections | planes=xy:-0.5@cyan,xy:1.5@magenta |
| shared intervals | major=1 minor=0.5 |
| independent intervals | xmajor=0.5 ymajor=1 zmajor=2 |
| logarithmic data | zscale=log (the corresponding range must be positive) |
| page or spatial styling | mode=textbook or mode=spatial |
Every generated role is addressable through tags: world.axes, world.axis.x,
world.grids, world.grid.xy, world.grid.major, world.grid.minor,
world.ticks, and world.labels. This makes ordinary show, fade, pulse,
and recolor useful without adding graph-specific animation commands.
grid3 and axes3 remain the fastest defaults for a simple positive stage.
Choose frame3 when bounds, log scale, multiple walls, repeated sections, or
textbook/spatial continuity matter.
▶ Solve the 3 × 4 × 12 box in three honest views:
▶ Pure 3D: shortest distance from a point to a plane:
▶ Six Asymptote-inspired grid policies:
The 3D cast
| shape | write | draws |
|---|---|---|
| cube3 | cube3(box, (0,0,1), (2,2,2)); | a box (width, depth, height) |
| sphere3 | sphere3(ball, (0,0,1), 0.9); | a ball of that radius |
| point3 | point3(p, (1,1,1)); | a small marker in space |
| line3 / arrow3 | arrow3(v, (0,0,0), (0,0,2)); | a segment / a vector |
| grid3 | grid3(floor, (0,0,0), 5, 1); | a ground grid to sit things on |
| axes3 | axes3(ax, (0,0,0), 3); | labelled x, y, z arrows |
| frame3 | frame3(world,(0,0,0),(8,8,6),"planes=xy:min,xz:min mode=textbook"); | bounded scientific axes plus selected grid planes |
| randomwalk3 | randomwalk3(walk,(0,0,0),10000,21,"mode=turtle angle=60 color=turn shade=depth"); | one deterministic 3D path with a local/world direction model |
| hilbert3 | hilbert3(curve,(0,0,0),6,4); | one exact space-filling path through all cells of a 3D lattice |
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), (18, -14)); // optional screen offset clears the marker
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.
Deterministic 3D random walks
randomwalk3 keeps tens of thousands of decisions in one path, so draw-on,
camera fitting, direct seeking, and export remain predictable:
camera3((10,-12,8),(0,0,0),38,orthographic);
randomwalk3(walk,(0,0,0),12000,21,
"mode=turtle angle=60 distribution=gaussian color=turn shade=depth scale=0.12");
untraced(walk);
par {
draw(walk,2.4,smooth);
view3(walk,"fit",0.7,smooth,1.2);
}
mode=axischooses one of ±x, ±y, ±z on every step.mode=turtleadvances along a local heading, then turns that orientation frame; useangle=90or60for the classic textbook constructions.distribution=uniform|gaussianchanges how the six choices are sampled.color=direction(orturn) paints segments by the selected choice.shade=depthadds a camera-aware far/near cue without changing the path.scaleis the world-space length of every step.seedmakes comparison and rerendering exact. The bounded maximum is 50,000 steps.
See asymptote-randomwalk3-reference.manic for the recurring Asymptote models
and creator-randomwalk3-diffusion.manic for a 100 → 1,000 → 10,000-step
explanation.
A line that fills a cube
hilbert3 generates one exact 3D Hilbert path as a single seekable entity.
Order 1 visits 8 cells with 7 segments; every refinement replaces each visit
with eight smaller visits, so order 5 has 32,767 segments:
camera3((13,-15,11),(0,0,0),42);
hilbert3(curve,(0,0,0),6,1);
hilbert3(next,(0,0,0),6,2); hidden(next);
untraced(curve);
draw(curve,1.5,smooth);
become3(curve,next,1.3,smooth);
The default arc-length gradient stays continuous through become3; choose
"color=single" when an ordinary color(curve, cyan) should paint the whole
path. Orders are deliberately bounded to 1–5 so direct seeking and production
rendering remain predictable. See
How one line fills a cube for the full
7 → 32,767-segment story with a continuous camera and CTA.
Large evolving 3D stories
Four small foundations cover dependency clouds, articulated mechanisms, time-varying fields, and addressable model parts without exposing frame callbacks.
| Intent | Manic words |
|---|---|
| many stable points | collection3 |
| relationships among them | links3, child3 |
| one dependent articulated chain | chain3, trail3 |
| a changing 3D field and its motion | vectorfield3, advect3 |
| a camera that follows one member | followshot3 |
| named parts inside one OBJ | assembly3 |
| a small offline sound beat | cue |
Here is the reusable field pattern:
camera3((8,-10,6), (0,0,0), 42);
collection3(seeds, (-2,0,0), 48, 1.2, 21, 0.045);
vectorfield3(flow, (0,0,0), 4,
"-y + 0.35*sin(2*pi*p)",
"x + 0.25*cos(2*pi*p)",
"0.25*sin(x+y+2*pi*p)", 5);
child3(hero, seeds, 0, 0.08);
trail3(history, seeds, 0, 0.025);
par {
advect3(seeds, flow, 5, 0.45);
followshot3(hero);
}
followshot3(none);
p is normalized absolute time from 0 to 1. Manic precompiles the RK4 paths,
then samples those paths, the field arrows, the child proxy, the trail, and the
camera target from the same timeline time. Scrubbing backward or jumping
straight to the middle is therefore repeatable.
Use collection3 for a fixed-count repeated 3D cast. It renders as a batch,
while child3 exposes only the member that needs a label or camera. links3
can create chain, nearest, or all relationships without hundreds of
handwritten link3 calls:
collection3(cloud, (0,0,0), 80, 3, 42);
links3(neighborhood, cloud, nearest, 2);
drift3(cloud, 4, 0.6);
For a dependent mechanism, every chain3 endpoint starts where its predecessor
ended. The history is taken from the real compiled endpoint route:
collection3(arm, (0,0,0), 4, 0, 7, 0.07);
chain3(arm, "1.8 1.35 1.0", "1.0 -1.8 2.4", 6);
links3(bones, arm, chain);
ring3(firstOrbit, arm, 0, 72);
ring3(secondOrbit, arm, 1, 72);
trail3(tipHistory, arm, 3, 0.035);
ring3 does not approximate a separately animated circle. Its centre is the
previous endpoint and its radius is the current distance to the chosen child,
so it remains correct while the chain rotates, travels, seeks, or rewinds.
Turn that same member history into a truthful screen-space plot with
historyplot. Choose x, y, or z; Manic uses the complete compiled range
for stable scaling and reveals only the history reached so far:
historyplot(wave, arm, 3, y, (540,1250), (820,320));
Use historyplot3 when the trace belongs inside the 3-D world rather than the
screen overlay. Its origin is a world coordinate and its size is measured in
world units, so depth stacking, focus shots, orbiting, and follow cameras all
remain coherent:
historyplot3(wave3, arm, 3, y, (-3,-4,2), (8,2.5));
view3(wave3, "front", 1, smooth, 1.2);
Both forms are derived from the same compiled child motion. Use historyplot
for a dashboard or fixed teaching panel; use historyplot3 for layered
Fourier constructions, spatial signal galleries, and camera-led explanations.
For a procedural tree, tree3 keeps authoring and rendering bounded. Each
generation is one addressable edge batch, while leaves are one collection:
tree3(tree, (0,0,0), 2.1, 27, 0.72, 10, 42);
for i in 0..10 { untraced(tree.d{i}); }
stagger(0.12) { for i in 0..10 { draw(tree.d{i},0.5,smooth); } }
view3(tree, "isometric", 1.0, smooth, 1.35);
This is the right tradeoff for creator files: a meaningful generated recipe, normal layers/tags and camera verbs, but no recursive user callback.
For grouped assets, OBJ group names become safe part ids:
assembly3(console, "asset:models/manic-console.obj", (0,0,0), 1.4);
text(screenLabel, (0,0), "signal");
label3(screenLabel, console.screen, 0.28);
cue(chime);
This is deliberately bounded: stable-count collections, formula fields, named OBJ groups, camera-facing labels, and four local cues. GLB node/material hierarchies, arbitrary SFX files, shader callbacks, count-changing children, and true occluding 3D glyph meshes remain future layers.
▶ Living dependency cloud:
▶ Dependent chain and truthful history:
▶ Fourier chain to live derived waveform:
▶ Five depth-layered Fourier families (DefinedMotion animation2.gif test):
▶ Odd harmonics to square-wave partial sum:
▶ Batched fractal-tree growth:
▶ Time-varying field and follow shot:
▶ Addressable assembly, notation, cue, and particle punctuation:
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.
| Story | Dimensional journey | Teaching idea |
|---|---|---|
| The trapped light beam | 1D → 2D → 3D | Distance grows from 5 to 13 to 85 through nested right triangles. |
| How space learned to grow | point → line → plane → room | A line sweeps into a surface; the surface lifts into volume. |
| Length, area, volume | 1D → 2D → 3D | Why measured units become cm, cm², and cm³. |
| A point gets an address | x → (x,y) → (x,y,z) | Each new axis adds one coordinate to the same point. |
| The revolving semicircle | diameter → curve → sphere | A solid can be generated from a lower-dimensional rule. |
| Statistical dimensions | list → scatter → cloud | More variables require a richer coordinate world. |
| Dimension reduction | 3D → 2D → 1D | A sphere reveals a section, then the section reveals a diameter. |
| Watermelon sections | whole → 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. Writepi*t, neverpit(manic readspitas one unknown word). Same forv*v, notvv.
Make the same generated object change
Use an ordinary parameter, add p to the generated formula, and connect it
with bind. Manic resamples the same object instead of replacing it:
parameter(shape, (640,620), 0, 0, 1, "shape", 2);
surface3(world, "0.22*(x*x+y*y)", (-3,3), (-3,3), 34);
bind(shape, world, formula, "0.22*x*x + 0.22*(1-2*p)*y*y");
to(shape, value, 1, 3, smooth);
The same pattern works for every stable formula family:
bind(shape, helix, formula,
"(1+p)*cos(t)", "(1+p)*sin(t)", "0.3*t");
bind(shape, torus, formula,
"(3+(0.2+p)*cos(v))*cos(u)",
"(3+(0.2+p)*cos(v))*sin(u)",
"(0.2+p)*sin(v)");
This is a useful creator distinction:
- use
bindwhen one curve/surface remains the subject and its mathematical rule changes continuously; - use
morph3when one authored shape becomes a different authored shape.
The bound object keeps its id, resolution, colour, material, transform, and
timeline identity. Manic checks the declared parameter range at build time and
rejects sampled non-finite formulas before recording. Topology and item count
stay fixed, so this is not a per-vertex scripting API. Do not combine morph3
and a generated-family binding on the same target. gradient3,
tangentplane3, and volume3 stay attached to that source and resample as it
deforms, so the measurement and the surface cannot drift apart.
▶ General three-family reference:
▶ Creator Short — solve the bowl-to-saddle transition:
Design tip: author the constructor formula to match the parameter’s initial value. Then the first reveal and the live journey share the same exact shape, with no opening jump.
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
papertemplate 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 profiler(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=smoothfor spheres/organic surfaces and leave boxes flat. - A little
meshclarifies topology;1is intentionally strong. depthandshadoware subtle readability controls from0to1, not a replacement for authored lighting.checkerandstripesare 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");
assembly3(console, "asset:models/manic-console.obj", (3,0,0), 1.2);
show(console.screen, 0.4);
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 URI | What it is | Good for |
|---|---|---|
asset:models/manic-pyramid.obj | Small generic pyramid OBJ | Learning model3, a beacon, marker, monument, or placeholder model |
asset:models/manic-console.obj | Grouped console OBJ (base, screen, key) | Learning assembly3, part callouts, staged reveals, and technical product stories |
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 pointrotate3— turn it (degrees around x, y, z)grow3— stretch a line or arrow’s tip to a new pointorbit3— swing the camera (angle around, angle up, distance)roll3— bank the camera around its viewing direction; it can run besideorbit3inpar, including through stable overhead/underside viewslook3— 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… |
|---|---|
hue | color with a palette name |
stroke | thick |
move / rotate / spin | move3 / rotate3 |
cam / zoom | camera3 / orbit3 |
morph | morph3 |
Now see it all in motion in the 3D scenes gallery.