Language Reference
VMD Primitive DSL
The complete reference for Video Markdown — every primitive, command, and pattern. Write your first scene in minutes.
Video Markdown (VMD) — Created by Sohan Dananjaya
What is VMD?
Video Markdown is a language and an animated document type. You write it the way you think — describe what you want to see, when it appears, how it moves — and it renders into a fully animated, frame- accurate presentation. The file is the animation. One plain text file is a complete, living, moving document.
What VMD gives you:
Speed from idea to animation. There is no setup, no asset pipeline, no tool to learn for months. You write what you imagine and it exists. A concept that would take weeks in a production pipeline can be a finished animated scene by the end of the afternoon. VMD is built for the gap between an idea and making others see it.
Full control, always yours. Every frame, every timing, every color, every motion is declared by you in the file. Nothing is generated for you without your knowledge. You can adjust anything in seconds — change a duration, shift a color, add a physics event — because the entire animation is readable text you wrote.
Version controlled by nature. A VMD file is a text file. git diff works on it. Branching
works. Collaboration works. The full history of every change you made to an animation lives in
your repository exactly as it does for code.
Renders anywhere, no GPU required. The same .vmd file produces identical output on a
laptop, a server, a CI pipeline, a Docker container, a cloud deploy. No render farm. No locked
environment. The renderer is lightweight and runs wherever you run it.
The full range in one language. A clean labeled diagram. A 3D spinning wormhole with a parametric surface formula. A multi-phase cinematic with atmospheric color overlays, scanline arrivals, shockwave rings, drifting particles, and a data packet flowing through an architecture diagram. An avatar character walking across the scene while talking and smiling. All of it is VMD. All of it is the same syntax. You do not switch tools, learn a new system, or change how you think — you just write more.
Declarative by design. VMD is not procedural. You do not write a render loop, construct
objects, or call drawing functions. You declare what exists and what it does — and it exists and
does it. A sphere that spins is sphere[r:1 spin3d:(0,30,0)]. A wormhole is one surface3d line
with a formula. A character walking across the scene is one gesture("walk") chain. The language
is as close to thought as code gets.
Unlimited visual effects potential. Layered color overlays that shift atmosphere mid-scene. Particle systems that rise, drift, orbit, or bloom. Shockwave rings that expand from an event. Aura pulses around a focal element. Ken Burns motion on a full-bleed image. Cinematic veil and crown. Parametric rings drawing themselves outward. Color floods that flash and retreat. Every effect composes freely with every other — stack as many layers as the scene demands. There is no ceiling on visual complexity, and each layer is one element in the file.
AI-native. VMD is text with a logical, learnable grammar. An AI can read it, write it, modify it, and generate complete advanced scenes from a description — more accurately and controllably than any video generation model, because VMD is a language with rules, not a probabilistic output. You stay in control; the AI writes what you direct.
Unlimited visual potential. 11,000+ pre-drawn animated objects(icon libraries), full parametric math surfaces, a cinematic layering system, physics effects, 3D illustrations, avatar characters with gestures and expressions, charts, diagrams, image cinematics — all composable in a single file. If you can describe an effect, VMD has a path to make it exist.
VMD is the right tool when the work is educational, presentational, or visual — and when the answer to "how long will this take?" should be hours, not months.
Part 0 — How to Think in VMD
0.1 The Mental Shift
Let me start with a question that sounds too simple: what is an animation, really? Strip everything away and it's just a set of statements — this thing exists here, it moves there, it takes this long. That's all. And if that's all it is, then here's an interesting thought: why shouldn't you be able to just... write those statements down, and have them play? VMD is what happens when you take that thought seriously.
You want a circle that fades in at half a second, drifts over to the right, and turns into a square. So you write that down. And that's what renders. There's no timeline panel because the timing is written in the sentence. There's no canvas to click on because the positions are written in the sentence. The file isn't a project that points at an animation somewhere — the file IS the animation, all of it, every frame, sitting right there in plain text.
Now, why does this matter? Because of distance. In every other tool, there's a long road between the picture in your head and the picture on the screen — menus, panels, clicks, adjustments. In VMD the road is one sentence long. You think it, you write it, it exists. Once you feel that, everything else in this manual is just learning more words.
0.2 The Only Three Things
Here's a secret that will save you a lot of trouble: this whole language is three ideas. Three! Everything else is vocabulary, and vocabulary you can look up whenever you need it. But these three you have to actually understand.
First: phases. A film is chapters of time, played one after another. Each chapter — each
phase — has a duration. Add up all the durations and you must get exactly total_duration.
That's the one hard rule in the whole language, and it's not bureaucracy — it's bookkeeping.
Time has to add up. Inside each phase, the clock starts over at zero. Simple.
Second: elements. The things inside a phase. A circle, a word, an icon, a sphere, a little cartoon character. Each one has a name and one line describing what it is.
Third: animations. What the things do. And here's the beautiful part — what a thing does is written right on the same line as what it is, chained together with arrows:
title: type: primitive shape: text[text:"Energy" fontSize:40 fill:#4fc3f7/0.0 at:(0,1)] -> fadeIn(0.8s, 0.5s) -> moveTo((0,2.5), 1.2s, ease.out, 1.5s)
Read it out loud: a piece of text, invisible to start, fades in over 0.8 seconds beginning at half a second, then floats upward starting at a second and a half. You just read an animation. Not a description of one — the actual animation, the thing itself. Everything about this element is on these three lines: what it is, where it starts, what it does, and when.
That's the whole system. Honestly. The rest is details.
0.3 Reading Space
The canvas is 16 units wide and 12 units tall, and the middle is (0, 0). Y points up — the
way it does in mathematics, and the way it should.
So the top-left corner is (-8, 6) and the bottom-right is (8, -6), and after a day of this
you'll know a handful of addresses by heart the way you know your own street:
(0, 5)— up in the top bar, where scene labels live(0, 0)— dead center, where the important thing goes(0, -3.2)— down low, where the quiet captions whisperx: ±9 or ±10— and now, here's the interesting one: off the canvas entirely
Why would you put something outside the picture? Because a stage has wings! An actor doesn't
materialize in the middle of the stage — she walks on, and the walking-on tells you something.
Spawn a circle at x:-10, send it to x:0, and it enters, from the left, like it came from
somewhere. Suddenly your little 16-by-12 rectangle implies a whole world beyond its edges.
So don't think of the canvas as a piece of paper. Think of it as the lit part of a stage. Your job is deciding what's in the light, what's waiting in the dark, and where the audience's eye goes next.
0.4 The Animation Chain
Now I have to warn you about something, because everybody — everybody — learns this wrong the first time, and then has one confusing afternoon, and then never gets it wrong again. Let's skip you past the confusing afternoon.
Animations chain with ->, and every delay is measured from the start of the phase. Not
from when the previous step finished. From zero. Always from zero.
shape: circle[r:1 fill:#4fc3f7/0.0 at:(0,0)] -> fadeIn(0.6s, 0s) -> moveTo((3,0), 1.5s, ease.out, 1s) -> fadeOut(0.5s, 3s)
The moveTo starts at t=1. Not "one second after the fade finishes." Not "one second after
the fade starts, sort of." At t=1 on the phase clock, period. The fadeOut fires at t=3 on
that same clock.
And once you stop fighting it, you realize this is the right rule. Every number in the file is an absolute time. You can look at any delay, anywhere, and know exactly when it happens — without adding up a chain of durations in your head like some poor accountant. The schedule reads like a train timetable, not like a recipe where step four depends on how long step three took.
One more thing, and this one bites everyone too: every element is visible at t=0 unless you
say otherwise. The renderer doesn't know you were planning a dramatic entrance. If you
write a fadeIn but the element starts at full opacity, it just sits there, fully visible,
and then rudely fades in again. So seed things invisible:
fill:#hex/0.0 → filled shapes start transparentr:0.0 → circles can start at zero size
And for stroke-only shapes — rings, outlines, auras — the rule is stricter and a little funny:
give them no fill property at all. Not fill:#hex/0.0. Nothing. An invisible fill on a
stroke-only shape quietly breaks the morphing machinery, and it breaks it silently, which is
the worst way for anything to break.
0.5 Morph — The Universal Transformer
If VMD has one genuinely powerful idea, it's this one, so let's take our time.
Morph says: here is a thing in state A. Here is the same thing in state B. Become B.
type: morphshape: circle[r:0.5 fill:#4fc3f7/0.0 at:(0,0)] ~> circle[r:2.5 fill:#4fc3f7/0.90 at:(0,0)] (1.2s, ease.out, 0.5s) ~> circle[r:1.0 fill:#c77aff/0.70 at:(3,1)] (0.8s, ease.spring, 2s)
Notice the operator is ~>, and I want you to read it as "become," not "animate to." That's
not poetry — it's precision. Between state A and state B, the renderer interpolates every
property at once: size, color, opacity, position, all of them sliding together, every frame
computed for you. You supply the destinations. The language supplies the journey.
And here's what makes it universal: anything numeric can morph. Position, radius, color, opacity, stroke width, font size, blur, tint, crop. Even the shape's identity — a circle can become a square can become a star, smoothly, through all the in-between shapes you'd never bother to draw. Morph a function graph and the curve changes shape. Morph an image and the photograph blurs or tints or crops itself. Morph a 3D surface and the geometry warps from one mathematical object into another.
Multi-step morphs are just more ~> steps, and — same rule as before — every step's delay is
on the phase clock, absolute from zero.
There's one idiom you'll use constantly, the hold step, and it looks silly the first time you see it:
A[invisible] ~> A[invisible] (2s, linear, 0s) ~> B[visible] (0.8s, ease.out, 2s)
Become... yourself? For two seconds? Yes! It's a way of waiting. The element holds its state until t=2, and then transforms. You've written silence into the music. Remember this trick — it's how you control when things happen without changing what they are.
0.6 Easing — The Feeling of Motion
Here's an experiment. Take the same object, same start point, same end point, same duration — and change only the easing curve. You get completely different meanings. Not different prettiness. Different meanings.
| Easing | What it says |
|---|---|
ease.out | Confidence. The thing arrives and settles where it belongs. |
ease.in | Inevitability. The thing gathers itself and leaves — it knew it was leaving. |
ease.inOut | Organic travel. No hurry. A ship crossing water. |
linear | Machinery. Constant rate because physics says so. Data, orbits, time itself. |
ease.spring | Alive! Something reacted. It overshot a little because it cares. |
ease.elastic | Something's wrong. The system overshoots and wobbles — unstable. |
bounce | Contact. It hit the floor and the floor pushed back. |
Why does this work? Because your eye is a physicist. It's been watching real objects fall and settle and collide for your entire life, and it knows — without you knowing it knows — that heavy things don't stop suddenly, that machines don't hesitate, that living things overshoot. When motion on screen obeys those rules, you believe it. When it doesn't, you feel that something is fake, even if you can't say what.
So don't pick easing by what looks nice. Ask what the motion means, and pick the curve that
means it. A data packet is linear — it's machinery. A character getting surprising news is
ease.spring — it's alive. A planet settling into orbit is ease.inOut — it's enormous and
has nowhere urgent to be. The easing is grammar, not decoration.
0.7 Layering — How Scenes Are Built
A scene is a stack of transparent sheets, painted from the back forward. In VMD the rule is almost embarrassingly simple: elements render in file order. Whatever you write later paints on top of whatever you wrote earlier. That's the whole z-axis.
The stack, back to front:
Background color → Image → Atmosphere wash → Panels → Frame/HUD →Ghost text → World icons → Ambient motion → Drama content →Active events → Cinematic crown
The deep layers establish a world — the photograph, the colored atmosphere over it. The middle layers carve out a stage — dark panels where text can live safely. And the top layers make the argument. When a scene looks composed instead of assembled, this stack is why. When it looks like clip-art floating on a background, somebody skipped the middle.
And now the most important timing rule in the language — I'd put it in a frame on the wall. The causality rule. When something happens at time T — a shockwave expands, a packet arrives, a ring pulses — the text that talks about it appears at T plus 0.3 seconds. Always after. Never before, never simultaneous.
Why 0.3? Because that's roughly how long a human eye takes to catch an event and be ready to read about it. The event proves the claim; the text confirms what the viewer just saw with their own eyes. Run it in that order and the scene feels true. Run it backwards — announce first, show second — and the scene feels like a salesman. The viewer won't know why they don't believe you. But they won't.
0.8 Effects from Primitives
Now here's something that sounds like a weakness and is actually the best part. VMD has no particle system. No shockwave button. No effects menu at all.
What it has is: morph, opacity, position, size, and staggered timing. And it turns out that's like saying a piano only has 88 keys.
Watch. A shockwave: a stroke-only circle that grows from r:0.3 to r:8.0 while its
stroke fades to nothing. That's it — one morph. Now make three of them, staggered 0.4 seconds
apart, and you have the unmistakable signature of an energy release. You've seen it in a
hundred films. It's two lines.
Particles: tiny circles, r:0.04 to 0.08, each one rising from below the frame to above
it over a couple of seconds, fading as it goes. Make eight, scatter their positions and delays
so no two match — a field of drifting lights. There is no particle system. There are eight
small circles and your good taste.
A data packet: one small bright circle moving linear from node A to node B. Want a trail?
A line whose endpoint morphs along behind it at the same speed.
A flash of atmosphere: one full-canvas rectangle, morphing transparent → 22% blue → transparent, in under a second.
You see the pattern. Decide what change you want the eye to see. Find which properties produce that change. Write the two states. Done. You're not choosing from somebody's effects menu — you're composing from principles, and principles combine without limit. Nobody can list all the effects VMD has, for the same reason nobody can list all the sentences English has.
0.9 3D Thinking
For 3D, a phase stops being a flat canvas and becomes a real perspective scene. X to the right, Y up, and negative Z going into the picture, away from you.
Two things must exist in every 3D phase, no exceptions: a camera and a light. And this isn't the renderer being fussy — it's geometry being honest. A 3D world without a camera is a world seen from nowhere. A world without light is a world you can't see at all. The 2D canvas quietly gave you both for free; 3D makes you say where you're standing and where the sun is.
The camera: at is where you stand, lookAt is what you face — almost always (0,0,0) —
and fov is how wide your vision is. Here's a lovely fact from photography: a narrow fov
around 40 compresses the scene and makes it dramatic, like a telephoto lens; a wide fov
around 70 stretches the space open. Same scene, different lens, different feeling. And
orbit:(0,40,0) makes the camera circle the scene at 40 degrees per second, all by itself —
the world's cheapest camera crew.
The light: its direction is the way the light travels — so (-0.4,-0.7,0.4) streams
downward, meaning the source is up high, like a sun. intensity is how strong the direct
light is; ambient is the floor under the shadows — how dark the darkest corner gets. Low
ambient is film noir. High ambient is a well-lit classroom. Choose accordingly.
emissive:true makes a shape glow with its own fill color, ignoring the light entirely —
stars, energy cores, neon. Things that emit instead of receive.
Two traps, both silent, both worth knowing in advance: in 3D, opacity is its own property —
opacity:0.5 — and the slash syntax fill:#hex/0.5 does nothing, quietly. And fadeIn
doesn't exist in 3D; you fade with a morph.
And then — parametric surfaces, which are my favorite thing in the language. surface3d
takes three formulas, one per coordinate, swept over two parameters u and v. The formula IS
the shape. Write the torus equation, get a torus. Write the wormhole equation, get a wormhole.
A Möbius strip is one line of text. And because formulas are just properties, you can morph
between two surfaces and watch one mathematical object melt into another. If you remember
any single fact about VMD's 3D, remember this one: any smooth surface you can describe with
mathematics, you can have, in one line.
(Its little sibling surface plots a height field, z = f(x,y), over a flat grid —
mathematical terrain. It lies flat in the XZ plane, so stand the camera low and diagonal,
or you'll be staring at your beautiful landscape edge-on.)
0.10 The VMD Creative Approach
When you actually sit down to make something, here's the order of operations. Not rules — just the way that works.
-
See it first. Before you type anything, close your eyes and look at the final frame. Know which things move and which hold still. Know when the big moment lands. If you can't see it yet, you're not ready to write it — and that's fine, seeing is most of the work.
-
Build back to front. Background first, foreground last, because file order is paint order. Set the stage before you send on the actors.
-
Seed everything invisible. Anything that shouldn't exist at t=0 starts at zero opacity or zero size. Nothing appears uninvited. Every entrance is a decision you made.
-
Obey causality. Show the event; confirm it in words 0.3 seconds later. The eye first, then the mind. Proof, then caption. Every single time.
-
Choose easing by meaning. Ask what the motion is saying, then pick the curve that says it. The eye is a physicist; don't lie to it.
-
End with the crown. Every scene builds toward one moment, and one color —
#ffd54f— exists only for that moment. Spend it early and it's worthless. Save it, and the whole scene has been secretly pointing at it all along.
That's the grammar. Everything after this page is vocabulary — shapes, properties, commands, the words of the language. Vocabulary is easy; you look it up when you need it. But grammar is how you think, and now you have it. The rest will click into place like it was always obvious — which, you'll notice, is the feeling VMD is designed around.
Part I — The Language
1. The Mental Model
Three concepts. Understand these and everything else is detail.
Phases are named time chapters. A VMD file is a sequence of phases that play back to back.
Each phase has a duration. The sum of all phase durations must equal total_duration exactly.
Elements are objects inside a phase — shapes, text, icons, images, avatars, graphs. Each element has a name, a type, and a shape line that describes what it is and what it does.
Animations are what elements do over time. They are written as a chain directly on the shape
line, using ->. There is no separate timeline — the timing lives right on the element.
phases: intro: duration: 6 elements: title: type: primitive shape: text[text:"Hello" fontSize:40 fill:#ffffff at:(0,1)] -> fadeIn(0.8s, 0.5s)
That chain reads: "a text element that fades in over 0.8 seconds, starting at 0.5 seconds into the phase." The entire animation for that element is on one line.
2. File Anatomy
Every VMD file follows this structure:
concept: "Title of this animation"total_duration: 12 background: "#0f0f0f" avatars: hero: figure[name:"hero"]{...} phases: phase_name: duration: 6 description: "What happens here." elements: element_name: type: primitive shape: shape[...] -> command(...)
Rules that must never be broken:
total_durationmust equal the exact sum of all phase durations. A discrepancy is a hard error — the file will not render.avatars:comes beforephases:if used.- The
shape:line is always one line. Any line break breaks the parser silently. type:isprimitivefor shapes and graphs,morphfor morphing elements,figurefor avatar characters.
3. The Canvas
The 2D canvas is 16 units wide and 12 units tall. Center is (0, 0). Y is up.
(-8, 6) ────────────────── (8, 6) ← top edge │ │ │ (0, 0) │ ← center │ │(-8,-6) ────────────────── (8,-6) ← bottom edge
Named positions you will use constantly:
| Location | Coordinates |
|---|---|
| Canvas center | (0, 0) |
| Top bar | (0, 5.0) |
| Bottom bar | (0, -4.6) |
| Whisper / caption | (0, -3.2) |
| Off-canvas spawn | x: ±9.0–10.0 |
3D scenes have their own world space — covered in Part IX.
4. The Property Syntax
Every shape in VMD uses bracket notation:
shape[property:value property:value ...]
- No commas between properties
- Numbers have no quotes:
fontSize:36,r:0.5 - Colors use hex:
fill:#4fc3f7 - Opacity is written with
/after the hex:fill:#4fc3f7/0.8 - Positions and vectors use parentheses:
at:(0, 1.5),uRange:(0, 6.28) - Strings use double quotes:
text:"Hello world"
Part II — 2D Shapes
5. Primitives
circle[r:N fill:#hex/op stroke:#hex/op strokeWidth:N arc:N arcStart:N opacity:N at:(x,y)]ellipse[w:N h:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]rectangle[w:N h:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]square[w:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]line[from:(x,y) to:(x,y) stroke:#hex/op strokeWidth:N at:(x,y)]triangle[w:N h:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]star[points:N outerRadius:N innerRadius:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]regularPolygon[sides:N r:N fill:#hex/op stroke:#hex/op strokeWidth:N at:(x,y)]
All shapes share: fill, stroke, strokeWidth, at. The at property is the shape's center.
circlesupportsarc(degrees to sweep, 0–360) andarcStart(starting angle in degrees, 0 = 12 o'clock, increases clockwise). A full circle is the default. A partial circle draws a filled wedge — the foundation of pie and donut charts.arc:144 arcStart:0draws a 40% wedge starting at the top. BotharcandarcStartare fully morphable. In 3D,torushas the samearcproperty for partial torus segments.triangletakesw(width) andh(height). Usew:N h:Nfor explicit dimensions.squareis a uniform rectangle — usewfor both dimensions.rectangleis used when width and height differ.startakespoints(number of points),outerRadius(tip to center), andinnerRadius(inner notch to center). A 5-point star:star[points:5 outerRadius:1.2 innerRadius:0.5].regularPolygondraws any n-sided polygon bysidesand circumradiusr.sides:3is a triangle,sides:6is a hexagon.
All of these morph freely — morph a circle to a square to a star and the shape transitions
smoothly through each intermediate state.
Stroke-only shapes — rings, auras, arcs, outlines — must have no fill property at all. Not
fill:#hex/0.0 — no fill property. A fill:#hex/0.0 on a stroke-only shape causes morph
interpolation to break silently.
6. Text
text[text:"Label" fontSize:N fill:#hex/op fontWeight:bold at:(x,y)]
Text cannot wrap automatically. At each font size there is a maximum safe character count before the text overflows the canvas:
| fontSize | 64 | 52 | 46 | 40 | 36 | 30 | 22 | 16 | 13 |
|---|---|---|---|---|---|---|---|---|---|
| max chars | 8 | 10 | 12 | 14 | 18 | 22 | 30 | 44 | 54 |
For multi-line text, use \n in the string: text:"Line one\nLine two". Lines are spaced at
1.2× the font size and centered vertically on the at point. \\ produces a literal backslash
(needed for LaTeX-style notation).
Alternatively, use multiple text elements at different y positions for more control.
7. Function Graphs and Parametric Curves
functionGraph plots y = f(x) over an x range:
functionGraph[fn:"sin(x)" xStart:-6 xEnd:6 segments:80 stroke:#4fc3f7 strokeWidth:2 at:(0,0)]
parametric plots (x(t), y(t)) — arcs, circles, pie segments, spirals:
parametric[paramX:"2*cos(t)" paramY:"2*sin(t)" tStart:0 tEnd:6.28 segments:60 stroke:#4fc3f7 at:(0,0)]
Both can be used as type: morph. Morphing xEnd or tEnd from 0 to their final value draws
the curve progressively. Morphing fn or paramX/paramY mutates the curve's shape mid-scene.
Morphing stroke opacity and strokeWidth works the same as on any other shape.
curve2d plots a curve that has no f(t). Chaotic attractors like Lorenz and Rössler don't
exist as a formula you can evaluate at a point in time — they only exist as the result of
numerically integrating a system of derivatives, step by step, where each point depends on the
one before it. functionGraph and parametric can't express that; curve2d can:
curve2d[dxFn:"expr(x,y,z,t)" dyFn:"expr" dzFn:"expr" seed:(x,y,z) steps:8000 dt:0.01 skip:0 project:"xz" stroke:#4fc3f7 strokeWidth:1 colorMap:"turbo" colorMode:"t"]
You write the system's derivatives (dx/dt, dy/dt, dz/dt) — not positions. The engine
integrates them with fixed-step RK4 starting from seed, then draws the resulting path as a
line. project picks which two of the three integrated axes become the 2D plane (default
"xz"). Just like surface3d, any numeric property that isn't reserved becomes a formula
variable — this is how constants like a, b, c, sigma, rho, beta get into the
equations.
There's no camera to compensate with. Being a 2D primitive, curve2d can't zoom out for a
large-magnitude system the way a 3D camera could. pointScale (default 1) rescales the raw
integrated coordinates before they hit the canvas, and autoCenter:true recenters on the
trajectory's own bounding box instead of assuming it sits near (0,0). Lorenz's z-axis spans
roughly 0–50 in raw units — without both of these it renders far off-screen.
Tracing it in over time — use -> draw(duration, delay) instead of fadeIn. It reveals the
path point-by-point from the seed outward, so it looks like it's being plotted rather than
fading in as a solid blob:
curve2d[...] -> draw(3.0s, 0.5s)
Ready-to-use systems:
Rössler: dxFn:"-y-z" dyFn:"x+a*y" dzFn:"b+z*(x-c)" a:0.2 b:0.2 c:5.7 seed:(0.1,0,0)Lorenz: dxFn:"sigma*(y-x)" dyFn:"x*(rho-z)-y" dzFn:"x*y-beta*z" sigma:10 rho:28 beta:2.667 seed:(1,1,1)
No morph between two different curve2d trajectories — there's no cheap way to interpolate
between two independently-integrated point sets. Opacity and fade animations work normally.
8. Icons — A Pre-Built Factory of Animated Objects
Icons are not decorations. They are a library of over 11,000 pre-drawn objects — people, animals, machines, tools, buildings, scientific symbols, brand logos, UI elements, nature, space, biology, technology — ready to place, color, scale, and animate with zero design work. You do not draw a rocket, a molecule, a server, a person waving, a fire, a circuit board — you call one by name and animate it exactly like any other VMD element.
icon[icon:"mdi:fire" size:2.0 color:#4fc3f7/0.85 at:(0,0)]
Three libraries:
mdi:— 7,600+ filled objects. The primary library. People, animals, objects, systems, nature, science, technology. If you need a concrete thing drawn, it is almost certainly here.lucide:— 1,700+ outline-style icons. Architecture diagrams, data flow, technical concepts.logos:— brand logos and product marks.
The format is always "prefix:name" — both parts required. Wrong format produces a silent
placeholder with no error.
Icons animate like anything else. Chain the full animation system onto any icon:
rocket: type: primitive shape: icon[icon:"mdi:rocket" size:3.0 color:#ffffff/0.9 at:(-6,-4)] -> fadeIn(0.5s, 0s) -> moveTo((0,2), 2.0s, ease.out, 0.5s) -> rotate(360deg, 3s, linear, 2.5s) atom: type: morph shape: icon[icon:"mdi:atom" size:1.2 color:#4fc3f7/0.0 at:(0,0)] ~> icon[icon:"mdi:atom" size:4.0 color:#4fc3f7/0.9 at:(0,0)] (1.0s, ease.spring, 1s) ~> icon[icon:"mdi:atom" size:3.0 color:#4fc3f7/0.5 at:(0,0)] (0.6s, ease.out, 2s)
Size, color, opacity, position all morph smoothly. The icon name itself swaps at 50% of the morph step — use this to switch which object you are showing mid-animation:
state_icon: type: morph shape: icon[icon:"mdi:check-circle" size:2.5 color:#6bffb8/0.9 at:(0,0)] ~> icon[icon:"mdi:alert-circle" size:2.5 color:#ff6b9d/0.9 at:(0,0)] (0.4s, ease.spring, 3s)
What this means in practice: the difference between spending hours drawing a custom asset and writing one line of VMD. A server farm, a DNA strand, a human heart, a satellite, a factory floor, a neural network node — call it, size it, color it with the semantic palette, animate it into the scene. The library is the asset pipeline.
Size guide for composition:
| Role | Size |
|---|---|
| Lead subject (hero element) | 2.5–4.0 |
| Supporting element | 1.4–1.8 |
| Background world-building | 0.8–1.2 |
| Ambient atmosphere | 0.5–0.9 |
Images:
image[src:"images/photo.jpg" w:16.5 h:12.5 fit:cover opacity:0.9 at:(0,0)]
Images support visual effects, all of which are morphable: blur (0–5), grayscale (0–1),
tint:#hex, radius (rounded corners), crop:(x,y,w,h), opacity.
Full-bleed: w:16.5 h:12.5 at:(0,0). Portrait inset: w:7.5 h:11.0 at:(-3,0).
9. Groups
group{at:(2, 1)} [ circle[r:0.5 fill:#4fc3f7 at:(0,0)], text[text:"Node" fontSize:13 at:(0,-1)] ]
Groups offset all children relative to the group's at position. The entire group expression
must be on one line.
Part III — Styling
10. The Color System
Colors are hex values. Opacity is written inline with /:
fill:#4fc3f7 → solidfill:#4fc3f7/0.6 → 60% opacitystroke:#ffffff/0.35 → 35% opacity stroke
Semantic palette — these colors carry meaning. Use them for what they mean:
| Color | Hex | Meaning |
|---|---|---|
| Active / alive | #4fc3f7 | something working, flowing, on |
| Broken / failed | #ff6b9d | error, stress, overload |
| Resolved / correct | #6bffb8 | success, completion, answer |
| Deep system / AI | #c77aff | unknown, deep, hidden layer |
| Neutral structure | #ffffff | labels, scaffolding |
| Cinematic crown | #ffd54f | the key moment — use once, at the end |
#ffd54f must appear nowhere in a scene before the cinematic moment at the end. One early use
destroys the effect completely.
Opacity by role:
| Role | Range |
|---|---|
| Ambient / world texture | /0.06–0.20 |
| Background structure | /0.15–0.35 |
| Resting / seeded | /0.25–0.45 |
| Active / visible | /0.55–0.85 |
| Cinematic / hero | /0.90–1.00 |
11. Layering
Elements render in file order — later elements paint on top of earlier ones. There is no explicit z-index. Order your elements in the phase from background to foreground.
The conceptual layer stack from back to front:
Background color → Image → Color overlay → Panels → Frame/HUD →Ghost text → World icons → Ambient physics → Drama content →Active physics → Cinematic crown
Part IV — Animation
12. The Animation Chain
Animations are chained with -> directly on the shape line:
shape: circle[r:1 fill:#4fc3f7 at:(0,0)] -> fadeIn(0.6s, 0s) -> moveTo((3,0), 1.5s, ease.out, 1s)
All delays are absolute from the start of the phase, not relative to the previous step. In the
example above, moveTo starts at t=1s from phase start — not 1 second after fadeIn ends.
Every element is visible at t=0 unless you explicitly make it invisible. If an element has
a fadeIn but no spawn gate (see Chapter 15), it appears at full opacity at t=0 and then fades
in again. Always seed elements invisible before animating them in.
13. Commands
Shape commands:
| Command | Signature | Effect |
|---|---|---|
fadeIn | (duration, delay) | fades from invisible to full |
fadeOut | (duration, delay) | fades to invisible |
moveTo | ((x,y), duration, easing, delay, path) | translates to position — path optional |
rotate | (angle, duration, easing, delay) | 360deg = full rotation |
scale | (factor, duration, easing, delay) | uniform scale |
moveTo has a fifth, optional argument: path. Without it, an object moves in a perfectly
straight line — which is often wrong. A thrown ball arcs. A bouncing ball loses height each hit.
A released pendulum swings through an arc. A spring settles with a wobble. path:"expr(t)" lets
you write that motion directly as a formula instead of settling for a straight line every time.
t runs from 0 to 1 across the move (already eased by whatever easing you gave). The formula
describes a perpendicular offset off the straight line to the destination — not a new
coordinate system. moveTo still owns the actual start and end point, so the object always lands
exactly on (x,y), as long as the formula itself resolves to 0 at t=1. That's the one rule to
keep in mind: whatever curve you write, make sure it fades back to zero by the end.
shape: icon[icon:"mdi:rocket-launch" size:0.8 color:#4fc3f7 at:(-6,0)] -> fadeIn(0.3s, 0s) -> moveTo((6,0), 2.5s, linear, 0.3s, path:"8*t*(1-t)")
These aren't named presets — they're real equations, the same math and physics any general-purpose model or textbook already knows:
| Formula | Motion |
|---|---|
"H*t*(1-t)" | A true parabola — the actual shape of a thrown object under gravity |
"H*abs(sin(t*N*PI))*(1-t)" | Decaying bounces — N = bounce count, height shrinks toward the destination |
"H*sin(t*N*PI)*exp(-t*K)" | A damped oscillation — the real spring/pendulum-with-friction equation |
Only Math.* functions and the constants PI/TAU are available inside the formula — no access
to other elements or scene state.
One subtlety worth knowing: the perpendicular direction depends on which way the line is
actually pointing. A back-and-forth move (swing right, then swing back left) needs the formula's
sign flipped on the return leg, or the bulge will land on the wrong side. See
scenes/movepath_test.vmd for a working pendulum built exactly this way.
Physics commands — standalone, no easing argument:
| Command | Signature | Effect |
|---|---|---|
spring | (stiffness:N damping:N mass:N velocity:N) | spring-physics motion |
bounce | (stiffness:N damping:N mass:N velocity:N) | bounce-physics motion |
elastic | (stiffness:N damping:N mass:N velocity:N) | elastic-physics motion |
Text commands:
| Command | Signature | Effect |
|---|---|---|
typewriter | (duration, delay) | types left to right |
wordReveal | (stagger, delay) | each word fades in, staggered |
focusPull | (duration, delay) | dramatic arrival with soft scale |
14. Easing
| Easing | When to use |
|---|---|
ease.out | arrivals, settling, things coming to rest |
ease.in | departures, collapse, inevitable motion |
ease.inOut | travel across the canvas, Ken Burns, organic motion |
linear | data flow, orbits, mechanical motion, hold steps |
ease.spring | activation pop, character reactions, UI-like snap |
ease.elastic | surprise, unstable state |
bounce | physical landing |
15. The Morph System
Morphing transitions an element from one state to another. Use type: morph and the ~> operator:
type: morphshape: circle[r:0.5 fill:#4fc3f7/0.0 at:(0,0)] ~> circle[r:2.0 fill:#4fc3f7/0.9 at:(0,0)] (1.2s, ease.out, 0.5s)
What morphs smoothly: at, r, w, h, fill, stroke, opacity, strokeWidth,
fontSize, blur, grayscale, tint, crop.
What snaps at 50% of the step: icon name, image src, fontWeight.
Multi-step morph: chain multiple ~> steps. Each delay is absolute from phase start.
shape: A[...] ~> B[...] (1s, ease.out, 0s) ~> C[...] (0.8s, ease.out, 2.5s)
B arrives at t=1s. C starts at t=2.5s. Not 2.5s after B — 2.5s from phase start.
Hold step — stay at a state for a while before changing:
shape: A[invisible] ~> A[invisible] (2s, linear, 0s) ~> B[visible] (0.8s, ease.out, 2s)
The spawn gate — make an element invisible at t=0 so it can be animated in:
Stroke-only shapes (rings, auras):
circle[r:0.0 stroke:#4fc3f7/0.0 strokeWidth:0.0 at:(0,0)]
Filled shapes:
circle[r:0.0 fill:#4fc3f7/0.0 at:(0,0)]
The distinction matters: a fill:#hex/0.0 on a stroke-only shape breaks the morph silently.
Stroke-only shapes must have no fill property at all.
Part V — Scene Architecture
16. The Phase System
Phases are the chapters of a VMD file. They play back to back. Time resets to t=0 at the start
of each phase.
total_duration: 18 phases: opening: duration: 8 elements: ... main: duration: 7 elements: ... close: duration: 3 elements: ...
Element names are local to a phase. The same name in two phases refers to two independent elements. Split into a new phase when you need: a different background, a completely different layout, a meaningful chapter break. For animation sequences on a shared canvas, stay in one phase.
17. The Layer Depth Stack
VMD scenes are built in layers from back to front. Think of it as 10 planes. Knowing which plane each kind of element belongs to gives you the grammar of a well-composed scene:
| Plane | Role | What lives here |
|---|---|---|
| 0 | Canvas | background: color |
| 1 | Image world | Full-bleed photo, Ken Burns motion |
| 2 | Atmosphere | Full-canvas color overlay rectangle |
| 3 | Panels | Dark content-protection rectangles |
| 4 | Frame | HUD corners, dividers, parametric rings, scan lines |
| 5 | Ghost text | Large barely-visible concept word |
| 6 | World icons | Edge icons at low opacity, present from t=0 |
| 7 | Ambient physics | Drifting particles, background orbits |
| 8 | Drama | Text, charts, diagrams — the argument |
| 9 | Active physics | Shockwaves, data packets, causality events |
| 10 | Cinematic crown | Amber word, fourth-wall flood |
18. Scene Timing
Every scene follows a structure in time:
| Window | When | Rule |
|---|---|---|
| Initialization | 0–2s | World elements only. No content, no drama. |
| Settlement | 2–5s | Ken Burns starts. Overlay appears. World icons arrive. |
| Drama | 5s to end−3s | Text beats. Physics events. Minimum 3s between beats. |
| Hold | last 3s | Nothing new starts. Let the last beat breathe. |
The causality rule: physics fires at time T. Confirming text fires at T+0.3s. This is not optional — it is the grammar of VMD. The physics proves the claim; the text confirms what you just saw. Never the other way around.
Part VI — Cinematic Techniques
19. The Overlay System
A full-canvas rectangle w:16.5 h:12.5 with a tinted color wash. One per scene. The overlay
color is used nowhere else — it is the scene's atmospheric tone.
Common overlay colors and what they communicate:
| Color | Hex | Peak opacity | Tone |
|---|---|---|---|
| Crimson | #8b1a1a | /0.22 | power, dominance |
| Amber | #92400e | /0.30 | warmth, creative peak |
| Violet | #2e1065 | /0.27 | depth, legacy |
| Indigo | #0d0d2b | /0.22 | cosmic scale |
| Dark base | #000000 | /0.45 | pure cinema, content protection |
Standard behavior: hold transparent → rise to peak ease.inOut 3–4s → retreat to /0.12–0.18.
Never retreat to zero. The overlay remains as a permanent tint.
20. The Panel System
Panels are dark rectangles that carve out protected space for content — text and stats sit inside them, readable against any background image.
Common panel types:
| Panel | at | w | h | opacity |
|---|---|---|---|---|
| Right | (5.0, 0) | 6.5 | 12 | /0.82 |
| Left | (-5.0, 0) | 6.5 | 12 | /0.82 |
| Bottom bar | (0, -4.5) | 16 | 6 | /0.84 |
| Center card | (x, y) | 5.0 | 3.0 | /0.76 |
Panels emerge: opacity 0→target in 1.4s ease.out. A split line appears at +0.3s. Content
arrives at +0.3–0.6s after the line.
Optional border: stroke:#4fc3f7/0.22 strokeWidth:0.5
21. Frame and HUD
Four corner brackets establish a cinematic frame — each bracket is one horizontal + one vertical
short line. Length 0.7–1.0, stroke:#4fc3f7/0.50 strokeWidth:1.2. Stagger: TL at T, TR at
T+0.2s, BL at T+0.3s, BR at T+0.4s.
Parametric rings add depth around a focal point:
- Inner aura:
r:2.0 stroke:#4fc3f7/0.30 strokeWidth:1.2 - Outer aura:
r:3.5 stroke:#4fc3f7/0.14 strokeWidth:0.7
Scan line: rectangle w:16 h:0.09 fill:#4fc3f7/0.48 sweeping from y:6.5 to y:-6.5 over
1.8s linear.
22. Ghost Text and World Icons
Ghost text: a concept word at fontSize:64–80 fill:#ffffff/0.15 fontWeight:bold placed at
center, arriving with fadeIn 2s delay:0s. It is texture — it is not the argument. The argument
lives on top of it in the drama layer.
World icons: 4–8 icons at the canvas edges at size:0.5–1.1 and color:/0.30–0.42, present
from the first frame. One or two rotate slowly rotate(360deg, 9s, linear, 0s). They set the
thematic world of the scene without competing with content.
23. Scene Transitions
Black cut — hard chapter break. Place at the START of the new scene:
cut: type: morph shape: rectangle[w:16.5 h:12.5 fill:#000000/1.0 at:(0,0)] ~> rectangle[w:16.5 h:12.5 fill:#000000/0.0 at:(0,0)] (0.7s, ease.out, 0s)
Fade to black — contemplative ending. Place in the current scene's last element:
shape: rectangle[w:16.5 h:12.5 fill:#000000/0.0 at:(0,0)] ~> rectangle[w:16.5 h:12.5 fill:#000000/0.0 at:(0,0)] (13s, linear, 0s) ~> rectangle[w:16.5 h:12.5 fill:#000000/0.90 at:(0,0)] (2.5s, ease.inOut, 13s)
Cinematic hold — nothing new starts in the last 3 seconds. Mandatory in every scene.
24. The Cinematic Crown
The crown is the final moment. Every scene ends with one.
The amber word:
- A dim veil:
rectangle fill:#000000/0→/0.38in0.6s ease.out - The word:
text fontSize:40–64 fill:#ffd54f/0→/0.95atT+0.3s - After holding 2.5–3.5s, the word shrinks to
fontSize:13 fill:#ffd54f/0.28and moves to a bottom corner — carrying forward as a whisper into subsequent scenes
#ffd54f must not appear anywhere in the scene before this moment. The amber word's power comes
entirely from contrast. One early use destroys it for the entire film.
The fourth-wall break — once per film only. The flood and the text land simultaneously (not at T+0.3s). The simultaneity IS the break. Requires three or more prior scenes of consistent grammar before it earns the effect.
Part VII — Effects and Physics
25. Building Visual Effects
VMD has no built-in particle system, no shockwave primitive, no pre-made effect library. What it has is morph, stagger, opacity, position, size — and those compose into anything you can imagine. Every visual effect in VMD is built from the same primitives you already know.
The core logic of any effect:
- A morph that changes size, position, or opacity over time creates motion and presence
- Staggered delays on multiple identical elements create rhythm and density
- Easing controls whether the effect feels explosive, organic, mechanical, or gentle
- Layering several morphs on the same event point builds complexity from simple parts
Drifting particles — staggered small circles moving and fading:
p1: type: morph shape: circle[r:0.06 fill:#4fc3f7/0.0 at:(-2,-1)] ~> circle[r:0.06 fill:#4fc3f7/0.65 at:(-2,5)] (2.5s, ease.out, 0s) ~> circle[r:0.06 fill:#4fc3f7/0.0 at:(-2,5.5)] (0.5s, ease.out, 2.5s)p2: type: morph shape: circle[r:0.05 fill:#4fc3f7/0.0 at:(1,-1)] ~> circle[r:0.05 fill:#4fc3f7/0.55 at:(1,5)] (2.8s, ease.out, 0.4s) ~> circle[r:0.05 fill:#4fc3f7/0.0 at:(1,5.5)] (0.5s, ease.out, 3.2s)
Eight of these with varied x positions, delays, and sizes — you have a particle field. The effect is entirely yours: direction, speed, density, color, fade timing.
An expanding ring — a circle that grows and disappears:
ring: type: morph shape: circle[r:0.3 stroke:#4fc3f7/0.55 strokeWidth:2.2 at:(0,0)] ~> circle[r:8.0 stroke:#4fc3f7/0.0 strokeWidth:0.6 at:(0,0)] (3.0s, ease.out, 0s)
Three of these staggered 0.4s apart creates the visual signature of an energy release. The
stroke-only rule applies: no fill property, or the morph breaks silently.
A full-canvas color flash — atmosphere in a moment:
flood: type: morph shape: rectangle[w:16.5 h:12.5 fill:#4fc3f7/0.0 at:(0,0)] ~> rectangle[w:16.5 h:12.5 fill:#4fc3f7/0.22 at:(0,0)] (0.6s, ease.out, 0s) ~> rectangle[w:16.5 h:12.5 fill:#4fc3f7/0.0 at:(0,0)] (2.0s, ease.inOut, 0.6s)
A moving object with a trail:
packet: type: morph shape: circle[r:0.10 fill:#4fc3f7/0.9 at:(-4,0)] ~> circle[r:0.10 fill:#4fc3f7/0.9 at:(4,0)] (1.2s, linear, 0s)trail: type: morph shape: line[from:(-4,0) to:(-4,0) stroke:#4fc3f7/0.4 strokeWidth:1 at:(0,0)] ~> line[from:(-4,0) to:(4,0) stroke:#4fc3f7/0.4 strokeWidth:1 at:(0,0)] (1.2s, linear, 0s)
The point is not that these effects exist — it is that you create them. Any visual behaviour you can describe as a change in position, size, color, or opacity over time can be built this way. Stack morphs, stagger delays, combine primitives, layer elements. The effect space is unlimited.
29. Charts and Diagrams
VMD has no built-in chart primitive. Charts are composed from 2D shapes in a fixed build order:
- Panel background (
rectangle, dark card) - Grid H lines (
line,stroke:/0.12 strokeWidth:0.4) - Grid V lines
- Axes (
line,stroke:/0.55 strokeWidth:1.2) - Axis arrows (
triangle size:0.12) - Tick labels (
text fontSize:9) - Data (see chart type below)
- Tip dot (
circle r:0.12 fill:/1.0) - Tip aura (
circle stroke:/0.40) - Value label (
text fontSize:13 fontWeight:bold)
Chart types from primitives:
| Chart | Data element |
|---|---|
| Line graph | functionGraph |
| Bar chart | rectangle per bar, morph h:0→value |
| Scatter | circle per point, staggered fadeIn |
| Pie / donut (2D) | circle[arc:N arcStart:N] per segment, morph arc:0→N |
| Pie / donut (3D) | torus[arc:N rotate3d:(0,-offset,0)] per segment |
| Area | rectangle fill behind functionGraph |
| Venn | two overlapping parametric at /0.20 |
| Gantt | horizontal rectangle per task |
Pie and donut charts deserve a direct example since the technique is specific. Each segment
is one circle with arc (how many degrees it spans) and arcStart (where it begins). Segments
are staggered in and each morphs from arc:0 to its final angle. A donut is the same with a
background-colored circle placed on top to cut the center hole:
seg_a: type: morph shape: circle[r:2.0 arc:0 arcStart:0 fill:#4fc3f7 opacity:0 at:(0,0)] ~> circle[r:2.0 arc:144 arcStart:0 fill:#4fc3f7 opacity:1 at:(0,0)] (1.5s, ease.out, 0.5s)seg_b: type: morph shape: circle[r:2.0 arc:0 arcStart:144 fill:#c77aff opacity:0 at:(0,0)] ~> circle[r:2.0 arc:108 arcStart:144 fill:#c77aff opacity:1 at:(0,0)] (1.2s, ease.out, 2.2s)hole: type: primitive shape: circle[r:0.72 fill:#0f0f0f at:(0,0)]
The same chart in 3D uses torus[arc:N] segments, each rotated to its start angle with
rotate3d:(0,-offset,0) and given spin3d:(0,8,0) for a slow unified rotation.
Diagram types are built the same way: node-edge graphs from circle+line, flow diagrams
from rectangle+line+triangle, timelines from a horizontal line + circle nodes.
Animation order for charts: axes first, then grid, then data draws, then the tip dot springs in
(ease.spring 0.4s), then value label fades. Stagger multiple bars or segments 0.2–0.4s.
Part VIII — Image Techniques
30. Ken Burns and Arrivals
Ken Burns — slow scale and drift over a still image — is how images become cinematic:
bg: type: morph shape: image[src:"images/photo.jpg" w:16.5 h:12.5 at:(0,0) opacity:0.9] ~> image[src:"images/photo.jpg" w:19.0 h:14.4 at:(0.8,-0.4) opacity:0.9] (18s, ease.inOut, 0s)
Always hold at the start state for 3–7s linear before beginning the Ken Burns motion. Counter-
drift: if the image drifts right, bring content in from the left. The opposition creates energy.
Image arrival types:
| Arrival | Technique |
|---|---|
| Iris birth | seed tiny circle → image expands from center, ease.in |
| Scanline boot | scan rectangle sweeps top to bottom; image desaturated → full color at sweep end |
| Blur-to-sharp | blur:4 w:22 → blur:0 w:16.5, ease.inOut 3s |
| Curtain reveal | crop:(-8,-6,0.1,12) → crop:(-8,-6,16,12), ease.inOut 4s |
| Opacity fade | opacity:0 → opacity:0.90, ease.out 2s |
Part IX — 3D Scenes
31. Thinking in 3D
A 3D phase renders a full perspective scene instead of a flat canvas. The coordinate system is Y-up: X is right, Y is up, negative Z is into the scene.
Two elements are required in every 3D phase: a camera and a light. Missing either produces
a broken render with no error message.
Shape sizes in 3D are in world units, not canvas units. What is visible depends entirely on the
camera distance. At a camera distance of 8 units with fov:50, one world unit is approximately
40px on a 600px canvas. A sphere with r:0.1 is 8px — barely visible. Match shape sizes to how
far the camera is from the scene.
32. Camera and Light
camera[at:(0,3,-8) lookAt:(0,0,0) fov:50 fog:0 fogColor:#000000 orbit:(rx,ry,rz)]light[direction:(-1,-1,-1) intensity:0.9 ambient:0.2]
Camera: at is where the camera sits. lookAt is what it faces. fov is field of view in
degrees — 40 is telephoto (compressed, dramatic), 70 is wide (spacious, distorted edges). fog
adds atmospheric depth haze.
orbit:(rx,ry,rz) rotates the camera position around lookAt at degrees/second per axis. Y-axis
orbit (ry) sweeps horizontally; X-axis orbit (rx) arcs vertically. Example:
orbit:(0,40,0) spins the camera 40°/s around the scene. Both axes can run simultaneously.
Light: direction is the direction the light travels (e.g. (-0.4,-0.7,0.4) = downward and
forward). The renderer flips it internally to compute the "toward light" vector. intensity is the
main brightness (0–1). ambient is the floor — how bright the darkest shadow is. specular (0–1)
adds a glossy sheen; pair with shininess (higher = tighter highlight).
Common light presets:
- Sunlight:
direction:(-0.6,-0.8,0.5) intensity:0.95 ambient:0.25 - Dramatic:
direction:(0,-1,0.3) intensity:0.4 ambient:0.05 - Glossy metal:
direction:(-0.6,-0.8,0.5) intensity:0.95 ambient:0.25 specular:0.6 shininess:64
33. 3D Shapes
All 3D shapes share these universal properties:
fill:#hex opacity:N at:(x,y,z)rotate3d:(rx,ry,rz) spin3d:(rx,ry,rz)scaleX:N scaleY:N scaleZ:Nemissive:true repeat:N offset:(dx,dy,dz)
opacity is always a separate property in 3D. Writing fill:#hex/0.5 in a 3D shape has no
effect — the opacity is silently ignored. Always write opacity:0.5 separately.
spin3d values are degrees per second. They accumulate over time: spin3d:(0,30,0) rotates 30°
per second around Y. spin3d resets to rotate3d + 0° at the start of each phase.
The 3D shape library:
sphere[r:1 segments:24 stacks:16 fill:#4fc3f7]cube[size:1 fill:#f472b6] # or w:N h:N d:N explicitcylinder[r:0.5 h:2 fill:#a3e635] # runs along Y; rotate3d:(90,0,0) for Zcone[r:0.5 h:2 fill:#ffd54f] # tip at +Y, base at -Ytorus[size:2 r:0.3 arc:360 fill:#4fc3f7] # arc < 360 = partial ringhemisphere[r:1 flip:false fill:#f472b6] # flip:true = bowlprism[sides:6 r:1 h:2 fill:#a78bfa] # sides:3=triangle, 6=hexagonplane[w:6 h:4 subdivisions:8 fill:#1e293b] # flat XZ quad, +Y normalline3d[from:(0,0,0) to:(3,0,0) r:0.05 fill:#64748b] # tube between points
Rotation quick reference:
| Want | Use |
|---|---|
| Cylinder along Z | rotate3d:(90,0,0) |
| Cylinder along X | rotate3d:(0,0,90) |
| Torus in XY plane | rotate3d:(90,0,0) |
| Ring tilted N° forward | rotate3d:(N,0,0) |
| Two opposite beams | rotate3d:(35,0,0) and rotate3d:(35,180,0) with same spin3d |
34. Emissive Materials and Layered Glow
emissive:true makes a shape glow at its full fill color, ignoring the light. Use it for
anything that should appear to emit light: stars, energy cores, neon lines, data pulses.
Critical: emissive works on fill color. A dark fill like #040e1c glows black — invisible
on a dark background. Any hex channel must be above #30 for the glow to be visible.
Layered glow — stack concentric shapes with decreasing radius and increasing opacity:
glow_outer: sphere[r:0.90 fill:#0a3a6a opacity:0.30 emissive:true at:(0,0,0)]glow_mid: sphere[r:0.50 fill:#0d5a90 opacity:0.55 emissive:true at:(0,0,0)]glow_inner: sphere[r:0.25 fill:#1890c8 opacity:0.80 emissive:true at:(0,0,0)]core: sphere[r:0.12 fill:#4fc3f7 opacity:1.0 emissive:true at:(0,0,0)]
This technique works on any geometry — spheres for stars, surface3d cones for beams.
Transparent shapes in 3D render in a second pass after all opaque geometry. Stack multiple transparent shells freely — they are z-tested but not z-written.
35. Rotation and Repetition
rotate3d:(rx,ry,rz) applies fixed Euler angles in degrees, Rx → Ry → Rz order.
spin3d:(rx,ry,rz) adds animated rotation at degrees-per-second over time.
They stack: final rotation = [rx + spinX×t, ry + spinY×t, rz + spinZ×t].
Group spin — spinning a composed object (multiple shapes) as one rigid body: give every part
the same spin3d, using only Y-offset in at. Any Y-offset rotates identically around the Y axis.
body: sphere[r:1 at:(0,0,0) spin3d:(0,22,0) fill:#e2e8f0]wings: cube[w:5 h:0.1 d:1.2 at:(0,-0.05,0) spin3d:(0,22,0) fill:#cbd5e1]fin: cube[w:0.07 h:0.6 d:1 at:(0,0.27,0) spin3d:(0,22,0) fill:#cbd5e1]
Repeat clones a shape N times, each shifted by offset:
sphere[r:0.3 fill:#34d399 at:(0,0,0) repeat:5 offset:(1.4,0,0)]# creates 5 spheres at x=0, 1.4, 2.8, 4.2, 5.6
36. Parametric Surfaces
surface3d generates a triangle mesh from three math expressions over a 2D parameter space.
It is the most powerful primitive in VMD — any smooth 3D surface can be expressed this way.
surface3d[xFn:"expr(u,v)" yFn:"expr(u,v)" zFn:"expr(u,v)" uRange:[start,end] vRange:[start,end] segments:32 stacks:32 fill:#4fc3f7 opacity:1 colorMap:"viridis" colorMode:"height" wireframe:false lineWidth:1]
u and v are the two parameters swept across their ranges. Every point is (x(u,v), y(u,v), z(u,v)). Expressions have access to all Math.* methods, plus PI and TAU.
Any numeric property that is not a reserved name becomes a formula variable:
surface3d[xFn:"(R+r*cos(v))*cos(u)" yFn:"r*sin(v)" zFn:"(R+r*cos(v))*sin(u)" R:2.0 r:0.4 ...]
Reserved (cannot be formula variables): opacity, segments, stacks, scaleX, scaleY,
scaleZ, repeat, rotate3d, spin3d, at, uRange, vRange.
Color maps: viridis, plasma, heat, turbo, coolwarm, magma, ocean
Color modes: flat (uniform fill), u (gradient by u), v (gradient by v), height (by Y)
Wireframe mode: wireframe:true draws the mesh as edges instead of filled triangles.
This bypasses back-face culling — the reason wireframe is essential for one-sided surfaces like
the Möbius strip or Klein bottle. One side always faces away from the camera; wireframe shows both.
Double-sided solid: render two copies of the same surface, second with uRange reversed:
side_a: surface3d[... uRange:(0,6.2832) fill:#c0c0c0]side_b: surface3d[... uRange:(6.2832,0) fill:#c0c0c0]
side_a catches front-faces, side_b catches back-faces. Together they cover the full surface.
Ready-to-use shapes:
Torus: xFn:"(R+r*cos(v))*cos(u)" yFn:"r*sin(v)" zFn:"(R+r*cos(v))*sin(u)" R:2 r:0.4 vRange:[0,6.2832]Möbius: xFn:"(1+v*0.5*cos(u*0.5))*cos(u)" yFn:"v*0.5*sin(u*0.5)" zFn:"(1+v*0.5*cos(u*0.5))*sin(u)" vRange:[-1,1]Helix: xFn:"(1+0.15*cos(v))*cos(u)" yFn:"u*0.2+0.15*sin(v)" zFn:"(1+0.15*cos(v))*sin(u)" uRange:[0,18.85] vRange:[0,6.2832]Saddle: xFn:"u" yFn:"u*u-v*v" zFn:"v" uRange:[-2,2] vRange:[-2,2] colorMap:"coolwarm" colorMode:"height"Wormhole: xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.8 vRange:[-4.5,4.5]
Height map — simpler entry point for mathematical landscapes:
surface[fn:"sin(x)*cos(y)" xRange:[-3,3] yRange:[-3,3] resolution:32 colorMap:"viridis" colorMode:"height"]
xRange and yRange use array brackets [start,end], not parentheses.
colorMode options: height (default), normal, radial, custom (with colorExpr:"expr").
Camera orientation for surface: the surface lies flat in the XZ plane — xRange maps
to world X, yRange maps to world Z, and fn output is the world Y height. A camera directly
overhead sees it nearly edge-on. Always use a low diagonal camera:
camera[at:(4,3,-5) lookAt:(0,0,0) fov:65]
Fading in a 3D shape — fadeIn does not work on 3D. Use morph:
type: morphshape: sphere[r:0.0 fill:#4fc3f7 opacity:0.0 at:(0,0,0)] ~> sphere[r:0.8 fill:#4fc3f7 opacity:1.0 at:(0,0,0)] (1.0s, ease.out, 0.5s)
If the shape has spin3d, set it in both the from and to states.
Part X — Avatars
37. The Character System
A VMD avatar is a fully rigged 2D cartoon character defined in plain text, inline in the same file that uses it. No external asset, no design tool.
avatars: hero: figure[name:"hero"]{root[shape:none] chest[...] neck[...] head[...] ...all 19 points...} phases: scene: duration: 5 elements: hero: type: figure shape: figure[use:"hero" at:(0,-0.45) scale:85] -> gesture("walk", 5s, 0s) -> moveTo((3,-0.45), 5s, 0s)
avatars: comes before phases:. The inline figure[name:"..."]{} definition works everywhere
— CLI, website preview, Docker, production. Only gesture and moveTo are valid on a figure
element. fadeIn, rotate, scale are silent no-ops.
Built-in characters — narrator and luna are pre-defined and do not need an entry in
avatars:. Use them directly: figure[use:"narrator" at:(0,-0.45) scale:85].
38. The 19 Anchor Points
Every figure must define exactly these 19 named anchor points — missing any one is a hard error:
root, chest, neck, head,eye_l, eye_r, mouth,shoulder_l, shoulder_r, elbow_l, elbow_r, hand_l, hand_r,hip_l, hip_r, knee_l, knee_r, foot_l, foot_r
The skeleton is a forward-kinematics chain. Rotating a joint orients all segments distal to it.
To swing the whole arm: rotate shoulder_r. To additionally bend the forearm on top of that:
rotate elbow_r. Rotating elbow_r does not re-orient the upper arm — only what comes after it.
Reference proportions (head r:0.34 is the anchor):
| Part | Key value |
|---|---|
chest | width:0.6 widthEnd:0.5 |
neck | length:0.22 |
head | r:0.34 length:0.20 |
eye_l/r | w:0.10 h:0.075 |
shoulder_l/r circle | r:0.14 — match chest fill |
elbow_l/r capsule | width:0.20 widthEnd:0.17 |
hand_l/r capsule | width:0.14 widthEnd:0.11 |
knee_l/r capsule | width:0.26 widthEnd:0.22 |
foot_l/r capsule | width:0.20 widthEnd:0.24 — wider end = shoe sole |
hip_l/r circle | r:0.14 — match chest fill |
The shoulder_l/r and hip_l/r joint circles must match the chest fill color — they are the
torso surface at those joints. Color transitions (shirt to skin, trouser to shoe) happen at
hand_l/r and foot_l/r.
39. Shape Kinds and Head Shading
Only five shape kinds exist for anchor points and extras:
| Shape | Notes |
|---|---|
none | Only for root |
circle | Safe on any point; rotation-invariant |
ellipse | Accepts angle (radians) for a static authored tilt |
capsule | Chain-points only. On extras it silently renders nothing. |
path | points:[[-0.3,0.1],[0.1,0.4],...] in local offsets; smoothing:0.55 |
Head shading adds depth. The shadeFill property enables it:
head[shape:circle r:0.34 fill:#e3a878 shadeFill:#5a2f17 shadeOpacity:0.22 shadeDx:0.12 shadeDy:-0.14 shadeScale:0.75 length:0.20]
shadeDx:0.12 shadeDy:-0.14 = upper-left light source. shadeFill should be two shades darker
than fill. Works on circle, ellipse, path.
40. Extras
Extras are decorations attached to any anchor point:
extra[attachTo:"head" shape:ellipse w:0.10 h:0.026 fill:#2b211c angle:0.08 offset:(-0.15,0.10) role:"eyebrow_l"]
- Pupils:
attachTo:"eye_l"+shape:circle— gaze tracking activates automatically by attachment point alone. No role tag needed. - Eyebrows:
attachTo:"head"+role:"eyebrow_l"orrole:"eyebrow_r". Only these two role values exist. The role makes the eyebrow react to expression gestures. - Hair:
pathonhead. Keep fringe points aty > ~0.08in head-local space — lower than that visually covers the eyes. - Fingers: 2–3 small
ellipseextras onhand_landhand_r. A bare capsule hand reads as a mitten. capsuleon extras renders nothing. Usecircle,ellipse, orpathonly.
Paint order: all 19 anchor point shapes paint first, then extras in listed order. Extras are always on top — there is no "behind the body" option.
41. Gestures and Expressions
Chain gestures with -> the same way as any animation:
shape: figure[use:"hero" at:(0,-0.45) scale:85] -> gesture("walk", 5s, 0s) -> gesture("talk", 5s, 0s) -> moveTo((3,-0.45), 5s, 0s)
Gestures that run in the same time window merge per-channel. talk drives mouth_open. walk
drives legs and arms. Neither overwrites the other. A character can walk, talk, and smile at once.
Gesture library:
| Gesture | What it does |
|---|---|
idle | Breathing loop — tiny chest + head oscillation. Run continuously under other gestures. |
walk | Alternating legs + counter-arm swing. Pair with moveTo for actual movement. |
wave | Right arm raises and waves twice, lowers. |
talk | Uneven mouth open/close chatter. |
explain | Both forearms lift symmetrically toward chest — presenter gesture. |
point-left | Left arm extends toward left with genuine elbow bend. |
point-right | Mirror of point-left. |
point-up | Right arm raises above head. |
look-left | Pupils shift left, return. |
look-right | Pupils shift right, return. |
look-up | Pupils shift up, return. |
head-tilt | Side-to-side head arc. |
shrug | Both arms out and up — "I don't know." |
happy | Smile, eyebrows up, eyes wide. |
sad | Frown, eyebrows in, eyes narrower. |
surprised | Eyes wide, brows high, mouth opens — snaps in fast. |
angry | Brows down sharply, eyes narrow, frown. |
flinch | Startled body snap + arms defensive + surprised expression — one call. |
laugh | Chest shake + rapid mouth + happy expression — one call. |
Expression gestures (happy, sad, surprised, angry) compose freely with everything else.
42. Multiple Characters
Define multiple named avatars in the avatars: block. Reference each independently in phases with
separate type: figure elements. Each character's animations and gestures are fully independent:
avatars: teacher: figure[name:"teacher"]{...} student: figure[name:"student"]{...} phases: dialogue: duration: 8 elements: teacher: type: figure shape: figure[use:"teacher" at:(-2.5,-0.45) scale:85] -> gesture("explain", 5s, 0s) -> gesture("talk", 5s, 0s) student: type: figure shape: figure[use:"student" at:(2.5,-0.45) scale:85] -> gesture("idle", 8s, 0s) -> gesture("look-left", 2s, 1s)
Appendices
A. The Renderer Laws
These are non-negotiable rules. Violating any of them produces no error — just silent wrong output.
shape:is always one line. Any line break = parser failure.total_duration= exact sum of all phase durations. Any discrepancy = hard error, file will not render.- All delays are absolute from phase start, not relative to the previous step.
- Every element is visible at
t=0unless explicitly spawned invisible. - Stroke-only shapes (rings, auras): no
fillproperty at all.fill:#hex/0.0= silent morph break. iconformat is always"prefix:name". Wrong format = silent placeholder.groupexpression must be on one line.- 3D:
opacityis always a separate property.fill:#hex/0.5= silent ignore. - 3D:
fadeInis a no-op. Usetype: morphwithopacity:0.0 → 1.0. - 3D:
spin3dresets torotate3d + 0°at the start of each phase. - 3D: every phase needs both
cameraandlight. - Avatars: all 19 anchor points required. Missing any = silent render failure.
- Avatars:
capsuleon extras = silent nothing. Usecircle,ellipse, orpath. - Avatars:
fadeIn,rotate,scaleon a figure = silent no-ops. #ffd54fappears nowhere before the cinematic crown moment.
B. Common Mistakes
| Symptom | Cause | Fix |
|---|---|---|
Element visible before its fadeIn | No spawn gate | Start with r:0.0 fill:#hex/0.0 |
| Morph ring breaks at start | fill:#hex/0.0 on stroke-only | Remove fill property entirely |
| Wrong output, no error | total_duration mismatch | Add up all phase durations |
| 3D shape invisible | Dark fill + emissive:true | Brighten fill — one channel above #30 |
| 3D opacity ignored | Using fill:#hex/N syntax | Add opacity:N as separate property |
| Avatar missing | Anchor point omitted | All 19 points required — hard error if any missing |
| Icon shows placeholder | Wrong format | Use "prefix:name" — both parts |
| Amber word has no impact | #ffd54f used earlier in scene | Remove all early uses |
| Möbius strip half-invisible | Back-face culling | Add wireframe:true or reversed uRange copy |
| 3D shape stops spinning at phase boundary | spin3d reset | Set rotate3d in next phase to match end angle |
C. Easing Quick Reference
| Easing | Feel |
|---|---|
ease.out | Lands softly — the default for arrivals |
ease.in | Builds then releases — departures, inevitable motion |
ease.inOut | Smooth start and end — travel, Ken Burns |
linear | Mechanical, constant — orbits, data flow, hold steps |
ease.spring | Overshoots and settles — UI pop, character snap |
ease.elastic | Multiple overshoots — surprise, instability |
bounce | Hits the floor and bounces — physical landing |
D. Icon Library
11,000+ pre-drawn objects — animate any of them with fadeIn, moveTo, rotate, scale, morph,
or any other command. No drawing required.
| Prefix | Count | Style | What's inside |
|---|---|---|---|
mdi: | ~7,400 | Filled | People, animals, machines, tools, science, space, biology, buildings, technology, nature, IoT, medical — the primary library |
lucide: | ~1,745 | Outline | Architecture diagrams, data flow, UI elements, technical concepts — strict 24×24 grid design |
logos: | ~1,861 | Color | Brand marks and product logos — full color, not monotone |
Size guide: 2.5–4.0 hero subject, 1.4–1.8 supporting, 0.8–1.2 world background, 0.5–0.9 ambient.
Format always "prefix:name". Both parts required — wrong format = silent placeholder.
E. Surface3d Shape Library
Sphere: xFn:"r*sin(v)*cos(u)" yFn:"r*cos(v)" zFn:"r*sin(v)*sin(u)" r:1 uRange:[0,6.2832] vRange:[0,3.1416]Torus: xFn:"(R+r*cos(v))*cos(u)" yFn:"r*sin(v)" zFn:"(R+r*cos(v))*sin(u)" R:2 r:0.4 uRange:[0,6.2832] vRange:[0,6.2832]Möbius: xFn:"(1+v*0.5*cos(u*0.5))*cos(u)" yFn:"v*0.5*sin(u*0.5)" zFn:"(1+v*0.5*cos(u*0.5))*sin(u)" uRange:[0,6.2832] vRange:[-1,1]Helix: xFn:"(1+0.15*cos(v))*cos(u)" yFn:"u*0.2+0.15*sin(v)" zFn:"(1+0.15*cos(v))*sin(u)" uRange:[0,18.85] vRange:[0,6.2832]Saddle: xFn:"u" yFn:"u*u-v*v" zFn:"v" uRange:[-2,2] vRange:[-2,2] colorMap:"coolwarm" colorMode:"height"Wormhole: xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.8 uRange:[0,6.2832] vRange:[-4.5,4.5]Trefoil: xFn:"(2+cos(1.5*u))*cos(u)*0.5+0.2*cos(v)" yFn:"sin(1.5*u)*0.5+0.2*sin(v)" zFn:"(2+cos(1.5*u))*sin(u)*0.5" uRange:[0,12.566] vRange:[0,6.2832]Ellipsoid: xFn:"a*sin(v)*cos(u)" yFn:"b*cos(v)" zFn:"c*sin(v)*sin(u)" a:1.5 b:0.8 c:1.5 uRange:[0,6.2832] vRange:[0,3.1416]Cone beam: xFn:"r*(1-v)*cos(u)" yFn:"h*v-h*0.5" zFn:"r*(1-v)*sin(u)" r:0.4 h:5 uRange:[0,6.2832] vRange:[0,1]
All accept fill, opacity, emissive, spin3d, rotate3d, at, wireframe.
Part XI — Complete Examples
Three complete, runnable VMD files. Each one is designed to be read as a whole — not as a reference, but as a working scene you can copy, run, and learn from. The code is the teaching.
Important: In these examples,
shape:definitions are split across multiple lines for readability. In a real.vmdfile, everyshape:and its full chain must be one continuous line. VMD has no rule against long lines — only against broken ones.
Example 1 — The Morph Chain
What it shows: type: morph at its simplest. One element, six states, one unbroken chain.
Every shape transition is smooth. No code, no loop — just states and the language filling the
space between them.
Notice: all delays are absolute from phase start. Each ~> step fires at a clock time, not
"after the previous step ends." Read the delays: 0s, 1.5s, 3s, 4.5s, 6s. That's the schedule.
concept: "The Morph Chain"total_duration: 10background: "#0a0e1a" phases: chain: duration: 10 elements: label: type: primitive shape: text[text:"type: morph" fontSize:14 fill:#58a6ff/0.0 at:(0,4.5)] -> fadeIn(0.5s, 0.3s) shape: type: morph shape: circle[r:1.0 fill:#ff4444 at:(0,0)] ~> square[w:2.0 fill:#ff8800 at:(0,0)] (1.5s, ease.inOut) ~> triangle[w:2.5 h:2.5 fill:#ffff00 at:(0,0)] (1.5s, ease.inOut, 1.5s) ~> star[points:5 outerRadius:1.2 innerRadius:0.6 fill:#44ff88 at:(0,0)] (1.5s, ease.inOut, 3.0s) ~> regularPolygon[sides:6 r:1.2 fill:#4488ff at:(0,0)] (1.5s, ease.inOut, 4.5s) ~> circle[r:1.0 fill:#cc44ff at:(0,0)] (1.5s, ease.inOut, 6.0s)
Example 2 — The Cinematic
What it shows: how a real presentational scene is structured from first element to crown. One file, 18 seconds, three phases. Background image with continuous ken burns across all phases. Vintage color overlay. A designed avatar character that walks in, points, talks, then celebrates. Three large icons arriving staggered. A function graph that morphs mid-scene. A three-ring shockwave crown at the end.
Notice: the bg image morph in each phase picks up exactly where the previous phase left off — w:18.0→19.8, then 19.8→21.6, then 21.6→23.4. That is a single 18-second zoom written in three 6-second segments. The vintage overlay is just a semi-transparent rectangle, persistent across all phases. The crown fires at t=0 of the last phase, not t=0 of the whole scene — because every delay is absolute within its phase clock.
concept: "The Laws That Move Everything"total_duration: 18background: "#0d0a07" avatars: newton: figure[name:"newton"]{root[shape:none] chest[shape:capsule width:0.62 widthEnd:0.52 fill:#5c2a3a] neck[shape:capsule width:0.16 fill:#e8cfa8 length:0.22] head[shape:circle r:0.34 fill:#e8cfa8 shadeFill:#7a4a2e shadeOpacity:0.20 shadeDx:0.12 shadeDy:-0.14 shadeScale:0.75 length:0.20] eye_l[shape:ellipse w:0.10 h:0.075 fill:#f7f3ea offset:(-0.15,0.08)] eye_r[shape:ellipse w:0.10 h:0.075 fill:#f7f3ea offset:(0.15,0.08)] mouth[shape:ellipse w:0.12 h:0.026 fill:#9a5f55 offset:(0,-0.16)] shoulder_l[shape:circle r:0.135 fill:#5c2a3a offset:(-0.20,-0.05)] shoulder_r[shape:circle r:0.135 fill:#5c2a3a offset:(0.20,-0.05)] elbow_l[shape:capsule width:0.19 widthEnd:0.16 fill:#5c2a3a] elbow_r[shape:capsule width:0.19 widthEnd:0.16 fill:#5c2a3a] hand_l[shape:capsule width:0.135 widthEnd:0.105 fill:#e8cfa8] hand_r[shape:capsule width:0.135 widthEnd:0.105 fill:#e8cfa8] hip_l[shape:circle r:0.135 fill:#5c2a3a offset:(-0.20,-0.04)] hip_r[shape:circle r:0.135 fill:#5c2a3a offset:(0.20,-0.04)] knee_l[shape:capsule width:0.25 widthEnd:0.21 fill:#2b2420] knee_r[shape:capsule width:0.25 widthEnd:0.21 fill:#2b2420] foot_l[shape:capsule width:0.19 widthEnd:0.23 fill:#1a1410] foot_r[shape:capsule width:0.19 widthEnd:0.23 fill:#1a1410] extra[attachTo:"eye_l" shape:circle r:0.032 fill:#211a16] extra[attachTo:"eye_r" shape:circle r:0.032 fill:#211a16] extra[attachTo:"head" shape:path fill:#dcd6c8 smoothing:0.55 points:[[-0.34,-0.60],[-0.50,-0.20],[-0.46,0.10],[-0.52,0.30],[-0.34,0.42],[-0.40,0.50],[-0.16,0.50],[0,0.46],[0.16,0.50],[0.40,0.50],[0.34,0.42],[0.52,0.30],[0.46,0.10],[0.50,-0.20],[0.34,-0.60],[0.18,-0.35],[0.16,0.27],[0,0.23],[-0.16,0.27],[-0.18,-0.35]]] extra[attachTo:"head" shape:ellipse w:0.10 h:0.026 fill:#cfcabe angle:0.08 offset:(-0.15,0.085) role:"eyebrow_l"] extra[attachTo:"head" shape:ellipse w:0.10 h:0.026 fill:#cfcabe angle:-0.08 offset:(0.15,0.085) role:"eyebrow_r"] extra[attachTo:"hand_l" shape:ellipse w:0.045 h:0.075 fill:#e8cfa8 angle:0.3 offset:(0.04,-0.06)] extra[attachTo:"hand_l" shape:ellipse w:0.045 h:0.095 fill:#e8cfa8 angle:0.0 offset:(-0.02,-0.105)] extra[attachTo:"hand_l" shape:ellipse w:0.045 h:0.075 fill:#e8cfa8 angle:-0.3 offset:(-0.08,-0.075)] extra[attachTo:"hand_r" shape:ellipse w:0.045 h:0.075 fill:#e8cfa8 angle:-0.3 offset:(-0.04,-0.06)] extra[attachTo:"hand_r" shape:ellipse w:0.045 h:0.095 fill:#e8cfa8 angle:0.0 offset:(0.02,-0.105)] extra[attachTo:"hand_r" shape:ellipse w:0.045 h:0.075 fill:#e8cfa8 angle:0.3 offset:(0.08,-0.075)]} phases: arrival: duration: 6 elements: bg: type: morph shape: image[src:"image_1.jpg" w:18.0 h:13.0 fit:cover opacity:0.70 at:(0,0)] ~> image[src:"image_1.jpg" w:19.8 h:14.3 fit:cover opacity:0.70 at:(0.20,0.12)] (6s, ease.inOut, 0s) vintage: type: morph shape: rectangle[w:18 h:13 fill:#1e0c02/0.0 at:(0,0)] ~> rectangle[w:18 h:13 fill:#1e0c02/0.42 at:(0,0)] (2.5s, ease.out, 0s) title: type: primitive shape: text[text:"The Laws That Move Everything" fontSize:30 fill:#f5e6c8/0.0 at:(0,4.3)] -> fadeIn(0.8s, 0.5s) subtitle: type: primitive shape: text[text:"Physics. Motion. VMD." fontSize:15 fill:#b8956a/0.0 at:(0,3.4)] -> fadeIn(0.6s, 1.4s) newton: type: figure shape: figure[use:"newton" at:(-5.5,-1.8) scale:82] -> gesture("walk", 2.0s, 0.3s) -> gesture("explain", 3.7s, 2.3s) shockwave: type: morph shape: circle[r:0.15 stroke:#f5e6c8 strokeWidth:3.5 opacity:0.9 at:(-5.5,0.8)] ~> circle[r:4.5 stroke:#f5e6c8 strokeWidth:0.5 opacity:0.0 at:(-5.5,0.8)] (1.4s, ease.out, 1.2s) content: duration: 6 elements: bg: type: morph shape: image[src:"image_1.jpg" w:19.8 h:14.3 fit:cover opacity:0.70 at:(0.20,0.12)] ~> image[src:"image_1.jpg" w:21.6 h:15.6 fit:cover opacity:0.70 at:(0.40,0.24)] (6s, ease.inOut, 0s) vintage: type: primitive shape: rectangle[w:18 h:13 fill:#1e0c02/0.42 at:(0,0)] newton: type: figure shape: figure[use:"newton" at:(-5.5,-1.8) scale:82] -> gesture("point-right", 3.0s, 0s) -> gesture("talk", 3.0s, 3.0s) icon_apple: type: primitive shape: icon[icon:"mdi:food-apple" size:2.4 color:#d64545/0.0 at:(1.2,0.2)] -> fadeIn(0.5s, 0.3s) -> moveTo((1.2,0.9), 0.8s, ease.out, 0.3s) icon_atom: type: primitive shape: icon[icon:"mdi:atom" size:2.4 color:#4fc3f7/0.0 at:(3.8,0.2)] -> fadeIn(0.5s, 0.9s) -> moveTo((3.8,0.9), 0.8s, ease.out, 0.9s) icon_bolt: type: primitive shape: icon[icon:"mdi:lightning-bolt" size:2.4 color:#ffd54f/0.0 at:(6.4,0.2)] -> fadeIn(0.5s, 1.5s) -> moveTo((6.4,0.9), 0.8s, ease.out, 1.5s) graph: type: morph shape: functionGraph[fn:"sin(x)" xStart:-3 xEnd:3 segments:120 stroke:#81c784 strokeWidth:2.5 opacity:0.0 at:(3.8,-1.8)] ~> functionGraph[fn:"sin(x)" xStart:-3 xEnd:3 segments:120 stroke:#81c784 strokeWidth:2.5 opacity:1.0 at:(3.8,-1.8)] (0.8s, ease.out, 2.0s) ~> functionGraph[fn:"cos(x)" xStart:-3 xEnd:3 segments:120 stroke:#4fc3f7 strokeWidth:2.5 opacity:1.0 at:(3.8,-1.8)] (1.5s, ease.inOut, 3.5s) graph_label: type: morph shape: text[text:"f(x) = sin(x)" fontSize:11 fill:#81c784/0.0 at:(3.8,-2.9)] ~> text[text:"f(x) = sin(x)" fontSize:11 fill:#81c784/0.85 at:(3.8,-2.9)] (0.8s, ease.out, 2.0s) ~> text[text:"f(x) = cos(x)" fontSize:11 fill:#4fc3f7/0.85 at:(3.8,-2.9)] (1.5s, ease.inOut, 3.5s) crown: duration: 6 elements: bg: type: morph shape: image[src:"image_1.jpg" w:21.6 h:15.6 fit:cover opacity:0.70 at:(0.40,0.24)] ~> image[src:"image_1.jpg" w:23.4 h:16.9 fit:cover opacity:0.70 at:(0.60,0.36)] (6s, ease.inOut, 0s) vintage: type: primitive shape: rectangle[w:18 h:13 fill:#1e0c02/0.42 at:(0,0)] newton: type: figure shape: figure[use:"newton" at:(-5.5,-1.8) scale:82] -> gesture("happy", 3.0s, 0s) -> gesture("wave", 3.0s, 3.0s) burst: type: morph shape: circle[r:0.4 fill:#ffd54f/0.9 at:(0,0)] ~> circle[r:6.5 fill:#ffd54f/0.0 at:(0,0)] (1.8s, ease.out, 0s) ring1: type: morph shape: circle[r:0.2 stroke:#ffd54f strokeWidth:3.5 opacity:1.0 at:(0,0)] ~> circle[r:6.0 stroke:#ffd54f strokeWidth:0.5 opacity:0.0 at:(0,0)] (1.6s, ease.out, 0.2s) ring2: type: morph shape: circle[r:0.2 stroke:#f5e6c8 strokeWidth:2.8 opacity:0.85 at:(0,0)] ~> circle[r:6.0 stroke:#f5e6c8 strokeWidth:0.5 opacity:0.0 at:(0,0)] (1.6s, ease.out, 0.6s) ring3: type: morph shape: circle[r:0.2 stroke:#4fc3f7 strokeWidth:2.2 opacity:0.70 at:(0,0)] ~> circle[r:6.0 stroke:#4fc3f7 strokeWidth:0.5 opacity:0.0 at:(0,0)] (1.6s, ease.out, 1.0s) crown_text: type: primitive shape: text[text:"Every motion has a law.\nEvery law has a shape." fontSize:24 fill:#ffd54f/0.0 at:(0,-0.2)] -> fadeIn(0.7s, 1.8s) crown_sub: type: primitive shape: text[text:"This is VMD." fontSize:15 fill:#f5e6c8/0.0 at:(0,-1.8)] -> fadeIn(0.6s, 2.8s)
Example 3 — The Wormhole
What it shows: 3D in VMD from first principles. No flat canvas — a full perspective scene with camera, light, and parametric geometry. Three concentric wormhole surfaces are the same Ellis formula at different throat radii, layered with different opacities to produce depth. Two flat spacetime sheets complete the physics image. Two camera angles, two phases.
Notice: emissive:true on the glow spheres means they ignore the light entirely — they are
sources, not receivers. wireframe:true on the surfaces with a low lineWidth produces the mesh
look without filling the faces. spin3d:(5,0,0) on every surface rotates them all at the same
rate around the same axis — they move as one object. The second phase simply moves the camera
inside the throat and slows the spin, and the whole scene changes character with those two edits.
concept: "Wormhole — Einstein-Rosen Bridge"total_duration: 10 phases: bridge_view: duration: 5 elements: camera: type: primitive shape: camera[at:(0,3.5,-7) lookAt:(0,0,0) fov:55] light: type: primitive shape: light[direction:(-0.4,-0.7,0.4) intensity:1.05 ambient:0.28] wh_outer: type: primitive shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.8 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#0b1240 opacity:0.32 wireframe:true lineWidth:0.62 spin3d:(5,0,0) at:(0,0,0)] wh_mid: type: primitive shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.5 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#0f4fd4 opacity:0.62 wireframe:true lineWidth:0.88 spin3d:(5,0,0) at:(0,0,0)] wh_core: type: primitive shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.28 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#1ea8ff opacity:0.92 wireframe:true lineWidth:1.08 spin3d:(5,0,0) at:(0,0,0)] upper_sheet: type: primitive shape: surface3d[xFn:"u" yFn:"4.6" zFn:"v" uRange:[-6,6] vRange:[-6,6] segments:18 stacks:18 fill:#0a2a60 opacity:0.36 wireframe:true lineWidth:0.55 spin3d:(5,0,0) at:(0,0,0)] lower_sheet: type: primitive shape: surface3d[xFn:"u" yFn:"-4.6" zFn:"v" uRange:[-6,6] vRange:[-6,6] segments:18 stacks:18 fill:#0a2a60 opacity:0.36 wireframe:true lineWidth:0.55 spin3d:(5,0,0) at:(0,0,0)] glow_hot: type: primitive shape: sphere[r:0.07 fill:#ffffff opacity:1.0 emissive:true at:(0,0,0)] glow_warm: type: primitive shape: sphere[r:0.24 fill:#80d8ff opacity:0.50 emissive:true at:(0,0,0)] glow_cool: type: primitive shape: sphere[r:0.58 fill:#1060b0 opacity:0.18 emissive:true at:(0,0,0)] tunnel_view: duration: 5 elements: camera: type: primitive shape: camera[at:(0.5,-6.5,0.5) lookAt:(0,0,0) fov:62] light: type: primitive shape: light[direction:(-0.4,0.8,0.3) intensity:1.05 ambient:0.28] wh_outer: type: primitive shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.8 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#0b1240 opacity:0.32 wireframe:true lineWidth:0.62 spin3d:(2,0,2) at:(0,0,0)] wh_mid: type: primitive shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.5 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#0f4fd4 opacity:0.62 wireframe:true lineWidth:0.88 spin3d:(2,0,2) at:(0,0,0)] wh_core: type: primitive shape: surface3d[xFn:"sqrt(b*b+v*v)*cos(u)" yFn:"v" zFn:"sqrt(b*b+v*v)*sin(u)" b:0.28 uRange:[0,6.2832] vRange:[-4.5,4.5] segments:36 stacks:30 fill:#1ea8ff opacity:0.92 wireframe:true lineWidth:1.08 spin3d:(2,0,2) at:(0,0,0)] glow_hot: type: primitive shape: sphere[r:0.07 fill:#ffffff opacity:1.0 emissive:true at:(0,0,0)] glow_warm: type: primitive shape: sphere[r:0.24 fill:#80d8ff opacity:0.50 emissive:true at:(0,0,0)] glow_cool: type: primitive shape: sphere[r:0.58 fill:#1060b0 opacity:0.18 emissive:true at:(0,0,0)]Video Markdown (VMD) — Created by Sohan Dananjaya
Explore and start writing →