Monocurl
Download

std.anim

anim.mcl

Reference for the symbols exported by anim.mcl.

Index

Rate Functions

Primitive Animations

Follower Animations

Indication Animations

Transfer Animations

Timing Operators

Rate Functions

function

Identity rate function: animation progress equals time progress.

Use this when a motion should have constant speed or when another helper already encodes easing.

let linear = |t| t
play rate{linear} Lerp(1, [&dot])
function

Default ease-in/ease-out rate for most animations.

A monotone fifth-order smoothstep-style curve, symmetric around the midpoint.

let smooth = |t| {
    let s = 1 - t
    return t * t * t * (10 * s * s + 5 * s * t + t * t)
}
play Lerp(1, [&dot], smooth)
function

Rate that starts slowly and accelerates.

let smooth_in = |t| 2 * smooth(0.5 * t)
play rate{smooth_in} Lerp(1, [&item])
function

Rate that starts quickly and settles slowly.

let smooth_out = |t| 2 * smooth(0.5 * (t + 1)) - 1
play rate{smooth_out} Lerp(1, [&item])
function

Fifth-order smoothstep rate.

let smoother = |t| t * t * t * (t * (t * 6 - 15) + 10)
function

Cosine ease-in/ease-out rate.

let cosine = |t| ...
function

Power ease-in rate; larger p makes the start slower and the end sharper.

let ease_in = |t, p = 3| ...
ppower, default 3
function

Power ease-out rate; larger p makes the start sharper and the end slower.

let ease_out = |t, p = 3| ...
ppower, default 3
function

Symmetric power ease-in/ease-out rate.

let ease_in_out = |t, p = 3| {
    if (t < 0.5) { return ease_in(2 * t, p) / 2 }
    return 1 - ease_in(2 * (1 - t), p) / 2
}
ppower, default 3
function

Exponential ease-out rate.

let exponential = |t| ...
function

Bounce-out rate with decaying rebounds near the end.

let bounce = |t| ...
function

Elastic-out rate that overshoots and rings into place.

Because it overshoots, it can produce values outside [0, 1]; use with geometry that tolerates extrapolation.

let elastic = |t| ...

Primitive Animations

importantconstructor

Reserve time on the current slide without changing any followers.

Wait advances the timeline but does not sync leaders to followers.

let Wait = |time = 1| ...
play Wait(0.5)
importantconstructor

Snap selected followers to their current leader values.

Passing [] asks Monocurl to snap every dirty leader. Pass explicit references in larger scenes to avoid syncing unrelated state.

let Set = |vars = []| ...
videodoc-anim-set-1.mcl
background = [0.035, 0.045, 0.060, 1]
mesh dot = fill{CYAN} Circle(0.2)
slide "set"
play Set([&dot])
play Wait(1)
dot = fill{ORANGE} Square(0.4)
play Set([&dot])
importantconstructor

Interpolate selected followers toward their leaders using value interpolation.

Lerp recursively interpolates normal values: equal values stay fixed, numbers and complex numbers blend linearly, same-length lists and same-key maps lerp elementwise, matching labeled function calls lerp only their labeled arguments and rerun the function, and matching operator chains lerp operands plus labeled operator arguments. Unlabeled call arguments must already match exactly. Use Trans when the visible mesh topology changes instead of merely the arguments of one live expression.

let Lerp = |time = 1, &vars = [], rate = smooth| PrimitiveAnim(time, &vars, nil, nil, rate)
varsoptional leader references; [] asks Monocurl to find dirty leaders
ratetiming function, default smooth
videodoc-anim-lerp-1.mcl
background = [0.035, 0.045, 0.060, 1]
mesh ball = center{center: 1.2l} fill{color: BLUE} Circle(radius: 0.35)
slide "labeled lerp"
play Set()
ball.center = 1.2r
ball.color = ORANGE
ball.radius = 0.6
play Lerp(1)
videodoc-anim-lerp-2.mcl
background = [0.035, 0.045, 0.060, 1]
mesh blue = center{center: 1.2l + 0.4u} fill{color: BLUE} Circle(radius: 0.25)
mesh gold = center{center: 1.2l + 0.4d} fill{color: YELLOW} Circle(radius: 0.25)
slide "selected leaders"
play Set()
blue.center = 1.2r + 0.4u
gold.center = 1.2r + 0.4d
play Lerp(1, [&blue])
play Set([&gold])
videodoc-anim-lerp-3.mcl
background = [0.035, 0.045, 0.060, 1]
mesh ball = center{center: 1.2l} fill{color: ORANGE} Circle(radius: 0.25)
slide "rate"
play Set()
ball.center = 1.2r
play rate{bounce} Lerp(1.2, [&ball])
constructor

Low-level primitive animation wrapper used by higher-level animations.

embed can normalize follower/leader values into interpolation endpoints plus state; lerp can then use that state at every frame. Most scene code should use Lerp, Trans, Write, Grow, Fade, or CameraLerp instead.

let PrimitiveAnim = |time = 1, &vars = [], embed = nil, lerp = nil, rate = smooth| ...
embedoptional function mapping follower and leader into interpolation endpoints
lerpoptional interpolation function
ratetiming function

Follower Animations

importantconstructor

Fade newly introduced content in and removed content out.

Fade compares the current follower with the destination leader. Meshes introduced by the leader fade in; meshes deleted from the leader fade out. delta adds an entrance/exit offset, so content can fade while sliding.

let Fade = |time = 1, &meshes = [], delta = [0, 0, 0], rate = smooth| ...
meshesoptional leader references
deltaoptional entrance/exit offset
videodoc-anim-fade-1.mcl
background = [0.035, 0.045, 0.060, 1]
mesh note = center{center: 0.8l} fill{color: CYAN} Circle(radius: 0.3)
slide "fade"
play Fade(0.6, [&note], 0.2d)
note = []
play Fade(0.6, [&note], 0.2d)
importantconstructor

Draw stroke and text contours progressively.

Write compares the current follower with the destination leader. Stroke/text contours introduced by the leader are traced in; contours deleted from the leader are traced out. It is most useful for strokes, Text, Tex, and Latex.

let Write = |time = 1, &meshes = [], rate = smooth| ...
meshesoptional leader references
videodoc-anim-write-1.mcl
background = [0.035, 0.045, 0.060, 1]
mesh title = Text("Monocurl", 0.8)
slide "write"
play Write(1, [&title])
title = []
play Write(0.7, [&title])
importantconstructor

Morph between different mesh geometry by building a point/contour/surface matching plan.

Trans is the general-purpose geometry morph. It may split, reorder, tessellate, and match mesh components so unrelated shapes can still transform, which is flexible but can occasionally pick a surprising correspondence. Use TagTrans when logical identities should control which pieces match.

let Trans = |time = 1, &meshes = [], path_arc = 0, rate = smooth, similar_topo_hint = 0| ...
meshesoptional leader references; [] asks Monocurl to find dirty mesh leaders
path_arc0 for straight motion, or a 3-vector whose direction chooses the arc plane and whose length is the arc angle in radians
ratetiming function
similar_topo_hintwhen truthy, first try to match start and end meshes vertex-by-vertex using the same triangle graph; useful when you authored matching topology and want control over the correspondence instead of the general matcher
videodoc-anim-trans-1.mcl
background = [0.035, 0.045, 0.060, 1]
mesh shape = fill{color: alpha{0.14} CYAN} stroke{color: CYAN} Triangle(1.2l, 1.2r, 1u)
slide "trans"
play Set()
shape = fill{color: alpha{0.14} ORANGE} stroke{color: ORANGE} Circle(radius: 1)
play Trans(1.2)
importantconstructor

Morph meshes by matching tagged subparts before matching geometry.

Use TagTrans when a scene has logical identities, such as terms in an equation or pieces in a proof. Tags decide which submesh is paired first; the underlying Trans matcher then handles geometry inside each pair and any remaining content.

let TagTrans = |time = 1, &meshes = [], path_arc = 0, rate = smooth, similar_topo_hint = 0, tag_map = nil| ...
meshesoptional leader references; [] asks Monocurl to find dirty mesh leaders
path_arcsame circular-arc control as Trans
similar_topo_hintwhen truthy, first try vertex-by-vertex interpolation for tagged pairs with the same triangle graph
tag_mapoptional function that remaps source tags before matching
videodoc-anim-tagtrans-1.mcl
background = [0.035, 0.045, 0.060, 1]
mesh pair = [
    tag{1} center{1.1l} fill{alpha{0.22} BLUE} stroke{BLUE} Circle(0.35),
    tag{2} center{1.1r} fill{alpha{0.22} ORANGE} stroke{ORANGE} Square(0.55)
]
slide "tag trans"
play Set()
pair = [
    tag{2} center{1.1l} fill{alpha{0.22} ORANGE} stroke{ORANGE} Square(0.55),
    tag{1} center{1.1r} fill{alpha{0.22} BLUE} stroke{BLUE} Circle(0.35)
]
play TagTrans(1.2, [&pair])
importantconstructor

Interpolate a camera leader with camera-specific motion.

Prefer CameraLerp over plain Lerp for camera motion. It linearly moves the camera position and forward direction, but spherical-interpolates the up vector, which avoids the roll/twist artifacts a structural value lerp can produce.

let CameraLerp = |&camera, time = 1, rate = smooth| ...
cameracamera leader reference, passed with &camera
camera = DEFAULT_CAMERA
play Set([&camera])
camera = Camera([2, 1, 5], ORIGIN, UP)
play CameraLerp(&camera, 1)
constructor

Reveal newly introduced mesh content by growing it outward from its center.

Grow compares the current follower with the destination leader. Meshes introduced by the leader start collapsed at their centroid and expand into place; meshes deleted from the leader shrink back to their centroid. Best for appearing objects; use Fade for opacity-only entrances.

let Grow = |time = 1, &meshes = [], rate = smooth| ...
meshesoptional leader references; [] asks Monocurl to infer dirty mesh leaders
videodoc-anim-grow-1.mcl
background = [0.035, 0.045, 0.060, 1]
mesh ring = stroke{CYAN, 3} Circle(1)
slide "grow"
play Grow(0.8, [&ring])
ring = []
play Grow(0.6, [&ring])
constructor

Morph line-based geometry by carrying local edge rotations through the path.

Bend is best for curves, polylines, and stroke-like shapes whose local direction should rotate smoothly. Unlike Trans, it does not run the full topology matcher; use Trans for unrelated filled shapes and Bend when the path structure itself is meaningful.

let Bend = |time = 1, &meshes = [], rate = smooth| ...
meshesoptional leader references
videodoc-anim-bend-1.mcl
background = [0.035, 0.045, 0.060, 1]
mesh path = stroke{color: CYAN, stroke_width: 4} Polyline([1.4l, 0.4l + 0.7u, 0.4r + 0.4d, 1.4r])
slide "bend"
play Set()
path = stroke{color: ORANGE, stroke_width: 4} Polyline([1.4l, 0.8l + 0.6d, 0.7r + 0.7u, 1.4r])
play Bend(1.2)
constructor

Bend morph that pairs tagged subparts before matching geometry.

Use this for tagged stroke/path content when each tagged piece should bend independently.

let TagBend = |time = 1, &meshes = [], rate = smooth| ...
play TagBend(1, [&paths])

Indication Animations

constructor

Temporarily change a mesh leader to a highlight color and then restore it.

Highlight is a progressor: it mutates the referenced leader to the highlighted value and then back to the original value.

let Highlight = |&value, color, time = 1, rate = smooth| anim ...
valuemesh leader reference
play Highlight(&equation, YELLOW)
constructor

Sweep a moving write-window over a mesh and then restore it.

Flash uses two phase functions: the leading edge and trailing edge of the visible window. By default the window opens, sweeps over the stroke/text contours, and closes.

let Flash = |&value, time = 1, lead = nil, trail = nil| ...
valuemesh leader reference
leadoptional leading edge rate
trailoptional trailing edge rate
play Flash(&equation, 0.8)

Transfer Animations

constructor

Move all mesh content from one leader into another and clear the source.

This is a state-changing helper, not a visual morph by itself; it snaps both leaders with Set.

let Transfer = |&from, &into| anim {
    into = [into, from]
    from = []
    play Set([&from, &into])
}
play Transfer(&old_group, &new_group)
constructor

Move only the tagged subset matching a filter into another leader.

The filter receives the full tag list. Non-matching content stays in from.

let TransferSubset = |&from, &into, filter = nil| anim ...
filtertag filter for content to move
play TransferSubset(&source, &sink, |tags| 2 in tags)
constructor

Copy the tagged subset matching a filter into another leader.

Matching content is appended to into; from is left unchanged.

let CopySubset = |&from, &into, filter = nil| anim ...
filtertag filter for content to copy
constructor

Copy all mesh content from one leader into another without clearing the source.

Snaps only the destination follower after appending the source content.

let Copy = |&from, &into| anim {
    into = [into, from]
    play Set([&into])
}
constructor

Move a tagged subset into an auxiliary target, morph it, then transfer it into another leader.

This is the workhorse for equation/proof animations: select tagged source pieces, morph them into matching destination geometry, then append them into into.

let TransSubsetTo = |&from, filter, target, &into, time = 1, tagged = 0, path_arc = 0, rate = smooth, similar_topo_hint = 0| anim {
    mesh aux = []
    play TransferSubset(&from, &aux, filter)

    aux = target
    if (tagged) {
        play TagTrans(time, &aux, path_arc, rate, similar_topo_hint)
    } else {
        play Trans(time, &aux, path_arc, rate, similar_topo_hint)
    }

    play Transfer(&aux, &into)
}
filtertag filter for content to move
targettarget mesh for the subset
timemorph duration
taggeduse TagTrans when truthy, Trans otherwise
path_arcarc control passed through to Trans/TagTrans
similar_topo_hintwhen truthy, first try vertex-by-vertex interpolation for subset pairs with the same triangle graph
play TransSubsetTo(&labels, |tags| 2 in tags, tag_filter{|tags| 2 in tags} formula, &formula, 1, 1)
constructor

Copy a tagged subset into an auxiliary target, morph it, then transfer it into another leader.

Same as TransSubsetTo, but leaves the source content in place.

let TransSubsetCopy = |&from, filter, target, &into, time = 1, tagged = 0, path_arc = 0, rate = smooth, similar_topo_hint = 0| anim {
    mesh aux = []
    play CopySubset(&from, &aux, filter)

    aux = target
    if (tagged) {
        play TagTrans(time, &aux, path_arc, rate, similar_topo_hint)
    } else {
        play Trans(time, &aux, path_arc, rate, similar_topo_hint)
    }

    play Transfer(&aux, &into)
}
filtertag filter for content to copy
targettarget mesh for the copied subset
timemorph duration
taggeduse TagTrans when truthy, Trans otherwise
path_arcarc control passed through to Trans/TagTrans
similar_topo_hintwhen truthy, first try vertex-by-vertex interpolation for subset pairs with the same triangle graph
constructor

Run a list of animations with staggered offsets.

Starts each animation after an offset distributed by unit_map, then waits for all branches to finish. Good for cascaded Grow, Write, or per-item progressors.

let LaggedMap = |anims, average_offset = 0.1, unit_map = smooth| ...
average_offsetaverage delay between neighboring starts
unit_maprate used to distribute offsets
play LaggedMap(map(dots, |&d| Grow(0.4, [&d])), 0.08)

Timing Operators

importantoperator

Replace the rate function on a primitive animation.

This affects primitive animations and wrappers that return primitive animations. It does not rewrite arbitrary anim { ... } blocks.

let rate = operator |target, rate| ...
play rate{bounce} Lerp(1, [&ball])
operator

Slow an animation by a multiplicative factor.

slow{2} doubles the target duration.

let slow = operator |target, factor = 2| ...
play slow{2} Write(1, [&path])
operator

Speed an animation up by a multiplicative factor.

fast{2} halves the target duration.

let fast = operator |target, factor = 2| ...
operator

Delay an animation block by playing Wait before it.

Useful inside parallel play [...] lists when one branch should start later.

let delay = operator |target, time = 1| [target, anim {
    play Wait(time)
    play target
}]
play [Write(1, [&a]), delay{0.2} Write(1, [&b])]