Skip to content

Reference

Core

Scenes, cameras, nodes, shapes, text, animation, orbit controls and mounting.

import { ... } from 'cseen';

The angles and distance of a perspective camera, the inverse of orbitPosition.

function anglesOf(position: V3, target: V3): { yaw: number; pitch: number; distance: number; }
ParameterType
positionV3
targetV3

Returns { yaw: number; pitch: number; distance: number; }

A circular arc as cubic segments, none longer than a quarter turn. Angles are in radians with x = cos and y = sin, and y points down like in SVG, so a positive sweep turns clockwise on screen.

function arcSegs(cx: number, cy: number, r: number, a0: number, a1: number): Seg[]
ParameterType
cxnumber
cynumber
rnumber
a0number
a1number

Returns Seg[]

Example

import { Construct, Scene, arcSegs, segsD } from 'cseen';
const s = new Scene({ view: 'front', size: [320, 320], padding: 6 });
const c = new Construct({ box: [0, 0, 200, 200], size: [3, 3] });
c.add({ d: segsD(arcSegs(100, 100, 70, -Math.PI / 2, Math.PI / 3)), width: 4 });
s.add(c);
Run it in the playground →

A line with an arrowhead at the end. The same options as line, with arrow: 'end' already set.

function arrow(a: Point, b: Point, o?: PathOpts): Path
ParameterType
aPoint
bPoint
o?PathOpts

Returns Path

Options: PathOpts

Example

import { Scene, arrow, ground } from 'cseen';
const s = new Scene();
s.add(ground(), arrow([-3, 0, 0], [3, 0, 2], { role: 'accent' }));
Run it in the playground →

The coordinate frame: three arrows along x, y and z with labels. This is how a reader learns which way is which. Put it near the origin of a figure.

function axes(o?: NodeOpts & { length?: number; labels?: [string, string, string] | false; role?: string; }): Group
ParameterType
o?NodeOpts & { length?: number; labels?: [string, string, string] | false; role?: string; }

Returns Group

Example

import { Scene, axes, ground } from 'cseen';
const s = new Scene();
s.add(ground(), axes({ at: [-3, -2, 0], length: 2 }));
Run it in the playground →

A box standing on the point at, the centre of its base. size is a number for a cube or [x, y, z].

function box(o?: BoxOpts): Box
ParameterType
o?BoxOpts

Returns Box

Options: BoxOpts

Example

import { Scene, box, ground } from 'cseen';
const s = new Scene();
s.add(ground());
s.add(box({ at: [-2, 0, 0], size: 1.2 })); // a cube
s.add(box({ at: [1, 0, 0], size: [2, 1, 0.6], role: 'accent' })); // width, depth, height
Run it in the playground →

A pinhole camera: its apex, image plane and the rays through the corners, aimed at a target. The glyph’s own origin is the lens, so cam.anchor([0, 0, 0]) is where rays should start.

function cameraGlyph(pos: V3, target: V3, o?: { dist?: number; w?: number; h?: number; hot?: boolean; role?: string; }): Group
ParameterType
posV3
targetV3
o?{ dist?: number; w?: number; h?: number; hot?: boolean; role?: string; }

Returns Group

Example

import { Scene, box, cameraGlyph, ground } from 'cseen';
const s = new Scene();
s.add(ground(), box({ at: [2, 3, 0], size: 1.4, role: 'accent' }));
s.add(cameraGlyph([-3, -3, 1.4], [2, 3, 0.8]));
Run it in the playground →

A circle or arc in a coordinate plane, centred on at. Angles are in degrees.

function circle(at: V3, r: number, o?: PathOpts & { plane?: 'xy' | 'xz' | 'yz'; from?: number; to?: number; }): Path
ParameterType
atV3
rnumber
o?PathOpts & { plane?: 'xy' | 'xz' | 'yz'; from?: number; to?: number; }

Returns Path

Example

import { Scene, circle, ground } from 'cseen';
const s = new Scene();
s.add(ground(), circle([0, 0, 0], 1.5, { plane: 'xy', role: 'accent' }));
Run it in the playground →

A full circle as a path, starting at the top and going clockwise, so it draws on like a compass.

function circleD(cx: number, cy: number, r: number): string
ParameterType
cxnumber
cynumber
rnumber

Returns string

Example

import { Construct, Scene, circleD } from 'cseen';
const s = new Scene({ view: 'front', size: [320, 320], padding: 6 });
const c = new Construct({ box: [0, 0, 200, 200], size: [3, 3] });
c.add({ d: circleD(100, 100, 80), width: 3 });
s.add(c);
Run it in the playground →

Multiplies a zoom by a factor and keeps it inside the limits.

function clampZoom(zoom: number, factor: number, range?: [number, number]): number
ParameterType
zoomnumber
factornumber
range?[number, number]

Returns number

Code on any plane, coloured, optionally numbered. Animate it with typeOn.

function code(src: string | string[], o?: Omit<TextBlockOpts, 'text' | 'code'>): TextBlock
ParameterType
srcstring | string[]
o?Omit<TextBlockOpts, 'text' | 'code'>

Returns TextBlock

Example

import { Scene, Timeline, code, typeOn } from 'cseen';
const s = new Scene({ view: 'front' });
const src = s.add(code(['let total = 0;', 'for (const n of list) {', ' total += n;', '}'], { at: [-3, 0, -1], size: [6, 2.4], fontSize: 0.5 }));
const tl = new Timeline();
tl.play(typeOn(src, 3));
tl.lockFit(s);
Run it in the playground →

a colour role (ink, faint, accent, grid, s1 to s8) or any CSS colour, as a CSS colour

function colourOf(c: string): string
ParameterType
cstring

Returns string

A curve through the points of a function t => [x, y, z], sampled samples times between t0 and t1.

function curve(fn: (t: number): V3, o?: ConstructorParameters<typeof Curve>[1]): Curve
ParameterType
fn(t: number) => V3
o?(PathOpts & { range?: [number, number]; samples?: number; })

Returns Curve

Example

import { Scene, curve, ground } from 'cseen';
const s = new Scene();
s.add(ground(), curve((t) => [-3 + 6 * t, 0, 1 + Math.sin(t * Math.PI * 2)], { role: 'accent' }));
Run it in the playground →

A measured distance: a line with end ticks and a label, offset to one side so it does not cover the thing measured.

function dimension(a: V3, b: V3, text: string, o?: { offset?: V3; role?: string; }): Group
ParameterType
aV3
bV3
textstring
o?{ offset?: V3; role?: string; }

Returns Group

Example

import { Scene, dimension, ground } from 'cseen';
const s = new Scene();
s.add(ground(), dimension([-3, 0, 0], [3, 0, 0], '6 units'));
Run it in the playground →

A dot at a point. r is its radius in pixels, so it stays the same size however the camera zooms.

function dot(at: V3, o?: ConstructorParameters<typeof Dot>[0]): Dot
ParameterType
atV3
o?(NodeOpts & StyleOpts & { r?: number; })

Returns Dot

Example

import { Scene, dot, ground } from 'cseen';
const s = new Scene();
s.add(ground(), dot([0, 0, 1], { role: 'accent' }));
Run it in the playground →

New angles after a drag of (dx, dy) pixels, in degrees. Dragging right turns the scene so its near side moves right; dragging down tips the top towards the viewer. Pitch is clamped.

function dragToAngles(a: { yaw: number; pitch: number; }, dx: number, dy: number, o?: { sensitivity?: number; limits?: OrbitLimits; }): { yaw: number; pitch: number; }
ParameterType
a{ yaw: number; pitch: number; }
dxnumber
dynumber
o?{ sensitivity?: number; limits?: OrbitLimits; }

Returns { yaw: number; pitch: number; }

Draws a line, curve or outline on from its start to its end.

function draw(n: Node, duration?: number): Anim
ParameterType
nNode
duration?number

Returns Anim

Example

import { Scene, Timeline, arrow, draw, ground } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const a = s.add(arrow([-3, 0, 0], [3, 0, 2], { role: 'accent' }));
const tl = new Timeline();
tl.play(draw(a, 1.2));
tl.lockFit(s);
Run it in the playground →

Fades a node in from invisible to its own opacity.

function fadeIn(n: Node, duration?: number): Anim
ParameterType
nNode
duration?number

Returns Anim

Example

import { Scene, Timeline, box, fadeIn, ground } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [0, 0, 0], size: 1.5, role: 'accent' }));
const tl = new Timeline();
tl.play(fadeIn(b, 1));
tl.lockFit(s);
Run it in the playground →

Fades a node out to invisible.

function fadeOut(n: Node, duration?: number): Anim
ParameterType
nNode
duration?number

Returns Anim

Example

import { Scene, Timeline, box, fadeOut, ground, wait } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [0, 0, 0], size: 1.5, role: 'accent' }));
const tl = new Timeline();
tl.play(wait(0.5));
tl.play(fadeOut(b, 1));
tl.lockFit(s);
Run it in the playground →

A square lattice lying on the floor (z = 0). With a parallel camera it is drawn as one repeating pattern, so it is exact and cheap.

function ground(o?: ConstructorParameters<typeof Ground>[0]): Ground
ParameterType
o?(NodeOpts & { step?: number; extent?: number; })

Returns Ground

Example

import { Scene, ground } from 'cseen';
const s = new Scene();
s.add(ground());
Run it in the playground →

Groups nodes so they move, turn and scale together. Children keep their own positions relative to the group.

function group(o?: NodeOpts, ...children: Node[]): Group
ParameterType
o?NodeOpts
childrenNode[]

Returns Group

Options: NodeOpts

Example

import { Scene, box, ground, group } from 'cseen';
const s = new Scene();
s.add(ground());
const g = s.add(group({ rot: [0, 0, 25] })); // turn the whole group at once
g.add(box({ at: [-1, 0, 0], size: 1 }), box({ at: [1, 0, 0], size: 1, role: 'accent' }));
Run it in the playground →

A height field z = f(x, y) over a rectangle.

function heightField(f: (x: number, y: number): number, o?: SurfaceOpts & { x?: [number, number]; y?: [number, number]; }): Surface
ParameterType
f(x: number, y: number) => number
o?SurfaceOpts & { x?: [number, number]; y?: [number, number]; }

Returns Surface

Example

import { Scene, ground, heightField } from 'cseen';
const s = new Scene();
s.add(ground());
s.add(heightField((x, y) => 0.6 * Math.sin(x) * Math.cos(y) + 0.7, { x: [-3, 3], y: [-2, 2], res: [24, 24], role: 'accent' }));
Run it in the playground →

Text that stays upright and readable, attached to a point in the scene. It always faces the viewer.

function label(text: string, at: V3, o?: TextOpts): Label
ParameterType
textstring
atV3
o?TextOpts

Returns Label

Options: TextOpts

Example

import { Scene, box, ground, label } from 'cseen';
const s = new Scene();
s.add(ground(), box({ at: [0, 0, 0], size: 1.2 }));
s.add(label('a box', [0, 0, 1.8], { anchor: 'middle' }));
Run it in the playground →

Splits text into rows that fit cols characters. The source is the lines joined with newlines, and rows point into it, so typing progress (a count of source characters) means the same thing before and after wrapping. word breaks at spaces (a long word is split), char breaks anywhere, none never breaks.

function layoutRows(lines: string[], cols: number, wrap: 'word' | 'char' | 'none'): Row[]
ParameterType
linesstring[]
colsnumber
wrap'word' | 'char' | 'none'

Returns Row[]

A straight line through two or more points.

function line(a: Point, b: Point, o?: PathOpts): Path
ParameterType
aPoint
bPoint
o?PathOpts

Returns Path

Options: PathOpts

Example

import { Scene, ground, line } from 'cseen';
const s = new Scene();
s.add(ground(), line([-3, 0, 0], [3, 0, 2]));
Run it in the playground →

See it in the gallery.

A straight line as a path.

function lineD(a: Pt, b: Pt): string
ParameterType
aPt
bPt

Returns string

Example

import { Construct, Scene, lineD } from 'cseen';
const s = new Scene({ view: 'front', size: [320, 200], padding: 6 });
const c = new Construct({ box: [0, 0, 200, 100], size: [3, 1.5] });
c.add({ d: lineD([10, 50], [190, 50]), width: 3 });
s.add(c);
Run it in the playground →

A straight segment between two points.

function lineSegs(a: Pt, b: Pt): Seg[]
ParameterType
aPt
bPt

Returns Seg[]

Example

import { Construct, Scene, lineSegs, segsD } from 'cseen';
const s = new Scene({ view: 'front', size: [320, 200], padding: 6 });
const c = new Construct({ box: [0, 0, 200, 100], size: [3, 1.5] });
const up = lineSegs([20, 80], [100, 20]), down = lineSegs([100, 20], [180, 80]);
c.add({ d: segsD(up.concat(down)), width: 4 });
s.add(c);
Run it in the playground →

A curved arrow between two objects or points.

function link(a: Point, b: Point, o?: LinkOpts): Link
ParameterType
aPoint
bPoint
o?LinkOpts

Returns Link

Options: LinkOpts

Example

import { Scene, box, ground, link } from 'cseen';
const s = new Scene();
s.add(ground());
const a = s.add(box({ at: [-3, 0, 0], size: 1 }));
const b = s.add(box({ at: [3, 0, 0], size: 1, role: 'accent' }));
s.add(link(a.anchor([0, 0, 1]), b.anchor([0, 0, 1]), { bend: 0.3, flow: 1.5, role: 'accent' }));
Run it in the playground →

A measuring bracket between two points: a line with a short tick across each end, for showing a length.

function measureD(a: Pt, b: Pt, tick?: number): string
ParameterType
aPt
bPt
tick?number

Returns string

Example

import { Construct, Scene, measureD } from 'cseen';
const s = new Scene({ view: 'front', size: [320, 200], padding: 6 });
const c = new Construct({ box: [0, 0, 200, 100], size: [3, 1.5] });
c.add({ d: measureD([30, 50], [170, 50]), width: 2 });
s.add(c);
Run it in the playground →

Puts a scene in the page and plays it. The SVG is built once; each frame only the pieces that changed are replaced, and the drawing pauses when it is off screen.

function mount(target: Element, scene: Scene, o?: MountOpts): Player
ParameterType
targetElement
sceneScene
o?MountOpts

Returns Player

Options: MountOpts

Example

import { Scene, box, ground, mount } from 'cseen';
import { document } from 'cseen/devices';
const s = new Scene({ view: 'iso' });
s.add(ground(), box({ at: [0, 0, 0], size: 1.5, role: 'accent' }));
const player = mount(document.querySelector('#figure')!, s);
// player.invalidate() redraws after you change the scene yourself

Moves a node by an offset from where it is when the animation starts.

function moveBy(n: Node, by: V3, duration?: number, ease?: EaseName): Anim
ParameterType
nNode
byV3
duration?number
ease?'back' | 'linear' | 'in' | 'out' | 'inOut' | 'smooth'

Returns Anim

Example

import { Scene, Timeline, box, ground, moveBy } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [-2, 0, 0], size: 1.2, role: 'accent' }));
const tl = new Timeline();
tl.play(moveBy(b, [4, 0, 0], 1.5));
tl.lockFit(s);
Run it in the playground →

Moves a node to a point.

function moveTo(n: Node, pos: V3, duration?: number, ease?: EaseName): Anim
ParameterType
nNode
posV3
duration?number
ease?'back' | 'linear' | 'in' | 'out' | 'inOut' | 'smooth'

Returns Anim

Example

import { Scene, Timeline, box, ground, moveTo } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [-3, 0, 0], size: 1.2, role: 'accent' }));
const tl = new Timeline();
tl.play(moveTo(b, [3, 0, 0], 1.5));
tl.lockFit(s);
Run it in the playground →

Places an SVG document inside a rectangle of another SVG, scaled to fill it (like object-fit: cover).

function nestSvg(source: string, x: number, y: number, w: number, h: number): string
ParameterType
sourcestring
xnumber
ynumber
wnumber
hnumber

Returns string

Turns the camera of a scene with the pointer, wheel, touch and keyboard. The scene needs fit: 'sphere' (or a locked fit) so the object keeps its size while it turns.

const player = mount(el, scene, { timeline }); orbit(el, scene, { onChange: () => player.invalidate() });

If a timeline animates the camera, the timeline wins while it plays.

function orbit(target: Element, scene: Scene, o?: OrbitOpts): Orbit
ParameterType
targetElement
sceneScene
o?OrbitOpts

Returns Orbit

Options: OrbitOpts

Example

import { Scene, box, ground, mount, orbit } from 'cseen';
import { document } from 'cseen/devices';
const s = new Scene({ fit: 'sphere' });
s.add(ground(), box({ at: [0, 0, 0], size: 1.5, role: 'accent' }));
const el = document.querySelector('#figure')!;
const player = mount(el, s);
orbit(el.querySelector('svg')!, s, { onChange: () => player.invalidate() }); // drag, wheel, pinch and arrow keys

Where a perspective camera sits when it looks at target from distance along the given angles (same angles as the parallel camera).

function orbitPosition(target: V3, yaw: number, pitch: number, distance: number): V3
ParameterType
targetV3
yawnumber
pitchnumber
distancenumber

Returns V3

Plays the animations at the same time.

function par(...anims: Anim[]): Anim
ParameterType
animsAnim[]

Returns Anim

Example

import { Scene, Timeline, box, ground, moveBy, par, rotateTo } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [-2, 0, 0], size: 1.2, role: 'accent' }));
const tl = new Timeline();
tl.play(par(moveBy(b, [4, 0, 0], 1.5), rotateTo(b, [0, 0, 90], 1.5))); // both at once
tl.lockFit(s);
Run it in the playground →

Reads an SVG document into a size and inner markup that plane can place. Scripts, event handlers and external references are removed and ids are made unique.

function parseSvg(source: string, o?: { recolor?: Record<string, string>; }): ParsedSvg
ParameterType
sourcestring
o?{ recolor?: Record<string, string>; }

Returns ParsedSvg

A line through any number of points, optionally closed. The general form of line and polygon.

function path(pts: Point[], o?: PathOpts): Path
ParameterType
ptsPoint[]
o?PathOpts

Returns Path

Options: PathOpts

Example

import { Scene, ground, path } from 'cseen';
const s = new Scene();
s.add(ground(), path([[-3, 0, 0], [-1, 0, 2], [1, 0, 0], [3, 0, 2]]));
Run it in the playground →

A line being drawn by a pen: pen(points), then tween draw from 0 to 1.

function pen(rail: Point[] | ((t: number): V3), o?: PenOpts): Pen
ParameterType
railPoint[] | ((t: number) => V3)
o?PenOpts

Returns Pen

Options: PenOpts

Example

import { Scene, Timeline, pen, tween } from 'cseen';
const s = new Scene({ view: 'front' });
const stroke = s.add(pen((t) => [-3 + 6 * t, 0, 0.9 * Math.sin(t * 2 * Math.PI)], { role: 'accent', width: 2.2 }));
const tl = new Timeline();
tl.play(tween(stroke, { draw: 1 }, { duration: 2, from: { draw: 0 } }));
Run it in the playground →

Lays an SVG (a logo, a chart, a screenshot) on any plane of the scene. Exact with a parallel camera, an approximation with perspective.

function plane(o: PlaneOpts): Plane
ParameterType
oPlaneOpts

Returns Plane

Options: PlaneOpts

Example

import { Scene, plane } from 'cseen';
const s = new Scene({ view: 'front' });
const svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 60"><rect width="100" height="60" fill="none" stroke="currentColor"/><circle cx="50" cy="30" r="18" fill="currentColor"/></svg>';
s.add(plane({ svg, size: [4, 2.4], at: [0, 0, 0] }));
Run it in the playground →

See it in the gallery.

The point at a distance and an angle (degrees, 0 to the right, 90 up) from a centre.

function polar(c: Pt, r: number, deg: number): Pt
ParameterType
cPt
rnumber
degnumber

Returns Pt

Example

import { Construct, Scene, lineD, polar } from 'cseen';
const s = new Scene({ view: 'front', size: [320, 320], padding: 6 });
const c = new Construct({ box: [0, 0, 200, 200], size: [3, 3] });
for (let k = 0; k < 8; k++) c.add({ d: lineD([100, 100], polar([100, 100], 80, k * 45)), width: 3 });
s.add(c);
Run it in the playground →

A flat filled polygon through three or more points.

function polygon(pts: Point[], o?: FaceOpts): Face
ParameterType
ptsPoint[]
o?FaceOpts

Returns Face

Options: FaceOpts

Example

import { Scene, ground, polygon } from 'cseen';
const s = new Scene();
s.add(ground(), polygon([[-2, 0, 0], [2, 0, 0], [0, 0, 2.5]], { role: 'accent' }));
Run it in the playground →

A dot that rides along a link or a line.

function pulse(rail: Rail, o?: PulseOpts): Pulse
ParameterType
railRail
o?PulseOpts

Returns Pulse

Options: PulseOpts

Example

import { Scene, Timeline, box, ground, link, pulse, tween } from 'cseen';
const s = new Scene();
s.add(ground());
const a = s.add(box({ at: [-3, 0, 0], size: 1 }));
const b = s.add(box({ at: [3, 0, 0], size: 1, role: 'accent' }));
const l = s.add(link(a.anchor([0, 0, 1]), b.anchor([0, 0, 1]), { bend: 0.3 }));
const ping = s.add(pulse(l, { trail: 4 }));
const tl = new Timeline();
tl.play(tween(ping, { along: 1 }, { duration: 1.6, from: { along: 0 }, ease: 'linear' }));
Run it in the playground →

A flat rectangle standing in a coordinate plane, as a filled face.

function rect(at: V3, size: [number, number], o?: FaceOpts & { plane?: 'xy' | 'xz' | 'yz'; }): Face
ParameterType
atV3
size[number, number]
o?FaceOpts & { plane?: 'xy' | 'xz' | 'yz'; }

Returns Face

Example

import { Scene, ground, rect } from 'cseen';
const s = new Scene();
s.add(ground(), rect([0, 0, 0], [3, 2], { plane: 'xy' }));
Run it in the playground →

The static markup of a scene at one moment: render on the server, then mount in the browser to bring it to life.

function renderStill(scene: Scene, timeline?: Timeline, t?: number): string
ParameterType
sceneScene
timeline?Timeline
t?number

Returns string

Example

import { Scene, box, ground, renderStill } from 'cseen';
const s = new Scene({ view: 'iso' });
s.add(ground(), box({ at: [0, 0, 0], size: 1.5, role: 'accent' }));
const svg = renderStill(s); // a standalone <svg> string: on the server, or anywhere without a browser

Plays an animation backwards: every tween runs from its end value to its start value, last one first. Each tween needs a literal to and a from, so there is something to return to. Put it after the animation to take a construction apart again, or to make a loop that ends where it began.

function reverse(a: Anim): Anim
ParameterType
aAnim

Returns Anim

Example

import { Scene, Timeline, box, ground, reverse, tween } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [-3, 0, 0], size: 1.2, role: 'accent' }));
const tl = new Timeline();
const there = tween(b, { pos: [3, 0, 0] }, { duration: 1.5, from: { pos: [-3, 0, 0] } });
tl.play(there);
tl.play(reverse(there));
tl.lockFit(s);
Run it in the playground →

A small deterministic random generator, so generated scenes are identical on server and client.

function rng(seed: number): (): number
ParameterType
seednumber

Returns () => number

Turns segments about a point. The angle is in radians, clockwise on screen.

function rotateSegs(segs: Seg[], c: Pt, ang: number): Seg[]
ParameterType
segsSeg[]
cPt
angnumber

Returns Seg[]

Example

import { Construct, Scene, lineSegs, rotateSegs, segsD } from 'cseen';
const s = new Scene({ view: 'front', size: [320, 320], padding: 6 });
const c = new Construct({ box: [0, 0, 200, 200], size: [3, 3] });
const leaf = lineSegs([100, 100], [100, 20]);
for (let k = 0; k < 6; k++) c.add({ d: segsD(rotateSegs(leaf, [100, 100], (k * Math.PI) / 3)), width: 4 });
s.add(c);
Run it in the playground →

Rotates a node to Euler angles in degrees about x, y and z.

function rotateTo(n: Node, rot: V3, duration?: number): Anim
ParameterType
nNode
rotV3
duration?number

Returns Anim

Example

import { Scene, Timeline, box, ground, rotateTo } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [0, 0, 0], size: [2, 1, 1], role: 'accent' }));
const tl = new Timeline();
tl.play(rotateTo(b, [0, 0, 90], 1.5));
tl.lockFit(s);
Run it in the playground →

Scales a node to a factor.

function scaleTo(n: Node, scale: number, duration?: number): Anim
ParameterType
nNode
scalenumber
duration?number

Returns Anim

Example

import { Scene, Timeline, box, ground, scaleTo } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [0, 0, 0], size: 1, role: 'accent' }));
const tl = new Timeline();
tl.play(scaleTo(b, 2, 1.2));
tl.lockFit(s);
Run it in the playground →

Writes segments as an SVG path string. A new subpath starts wherever a segment does not begin where the last one ended, and close ends with Z.

function segsD(segs: Seg[], close?: boolean, digits?: number): string
ParameterType
segsSeg[]
close?boolean
digits?number

Returns string

Example

import { Construct, Scene, arcSegs, segsD } from 'cseen';
const s = new Scene({ view: 'front', size: [320, 200], padding: 6 });
const c = new Construct({ box: [0, 0, 200, 100], size: [3, 1.5] });
const lens = arcSegs(100, 50, 60, -0.6, 0.6).concat(arcSegs(100, 50, 60, Math.PI - 0.6, Math.PI + 0.6));
c.add({ d: segsD(lens, true), paint: 'accent', width: 3, props: { fill: 0.3 } });
s.add(c);
Run it in the playground →

Plays the animations one after another.

function seq(...anims: Anim[]): Anim
ParameterType
animsAnim[]

Returns Anim

Example

import { Scene, Timeline, box, ground, moveBy, seq } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [-2, 0, 0], size: 1.2, role: 'accent' }));
const tl = new Timeline();
tl.play(seq(moveBy(b, [4, 0, 0], 1), moveBy(b, [0, 3, 0], 1))); // one, then the other
tl.lockFit(s);
Run it in the playground →

The bounding sphere of the drawable nodes, used by fit: 'sphere'. Its centre is the middle of the box around the points, so it does not change with the camera angle.

function sphereOf(prims: Prim[]): Sphere | null
ParameterType
primsPrim[]

Returns Sphere \| null

Starts each animation a little after the previous one.

function stagger(gap: number, ...anims: Anim[]): Anim
ParameterType
gapnumber
animsAnim[]

Returns Anim

Example

import { Scene, Timeline, box, fadeIn, ground, stagger } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const boxes = [-3, -1, 1, 3].map((x) => s.add(box({ at: [x, 0, 0], size: 1 })));
const tl = new Timeline();
tl.play(stagger(0.25, ...boxes.map((b) => fadeIn(b, 0.6)))); // each starts a little after the last
tl.lockFit(s);
Run it in the playground →

A parametric surface (u, v) => [x, y, z] drawn as a mesh of shaded quads.

function surface(fn: (u: number, v: number): V3, o?: SurfaceOpts): Surface
ParameterType
fn(u: number, v: number) => V3
o?SurfaceOpts

Returns Surface

Options: SurfaceOpts

Example

import { Scene, ground, surface } from 'cseen';
const s = new Scene();
s.add(ground());
// half a cylinder: (u, v) in, a point out
s.add(surface((u, v) => [-2 + 4 * u, 1.3 * Math.cos(Math.PI * v), 1.3 * Math.sin(Math.PI * v)], { res: [8, 20] }));
Run it in the playground →

Splits a line of code into coloured pieces. Deliberately small: it suits snippets in illustrations, not an editor.

function tokenize(line: string): Token[]
ParameterType
linestring

Returns Token[]

Animate any properties of a node or camera: tween(cube, { opacity: 0.2, pos: [1, 0, 0] }).

function tween(target: Animatable, to: Record<string, To>, o?: TweenOpts): Anim
ParameterType
targetAnimatable
toRecord<string, To>
o?TweenOpts

Returns Anim

Options: TweenOpts

Example

import { Scene, Timeline, box, ground, tween } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [-2, 0, 0], size: 1.2, role: 'accent' }));
const tl = new Timeline();
tl.play(tween(b, { pos: [2, 0, 0], rot: [0, 0, 90] }, { duration: 1.6, ease: 'inOut' }));
tl.lockFit(s);
Run it in the playground →

Types text or code onto a plane letter by letter.

function typeOn(n: Node, duration?: number): Anim
ParameterType
nNode
duration?number

Returns Anim

Example

import { Scene, Timeline, typeOn, write } from 'cseen';
const s = new Scene({ view: 'front' });
const t = s.add(write('Typed, letter by letter.', { at: [-3, 0, 0], size: [6, 1], fontSize: 0.5 }));
const tl = new Timeline();
tl.play(typeOn(t, 2));
tl.lockFit(s);
Run it in the playground →

Does nothing for a while. Use it to leave a gap between two animations.

function wait(duration: number): Anim
ParameterType
durationnumber

Returns Anim

Example

import { Scene, Timeline, box, ground, moveBy, wait } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [0, 0, 0], size: 1.2, role: 'accent' }));
const tl = new Timeline();
tl.play(wait(1)); // nothing happens for a second
tl.play(moveBy(b, [2, 0, 0], 1));
tl.lockFit(s);
Run it in the playground →

Text on any plane: write('hello', { at, size, u, v }). Put it on a floor, a wall, or a screen.

function write(text: string | string[], o?: Omit<TextBlockOpts, 'text'>): TextBlock
ParameterType
textstring | string[]
o?Omit<TextBlockOpts, 'text'>

Returns TextBlock

Example

import { Scene, ground, write } from 'cseen';
const s = new Scene();
s.add(ground());
s.add(write(['Hello,', 'floor.'], { at: [-2, -1, 0], size: [4, 1.6], fontSize: 0.7, u: [1, 0, 0], v: [0, 1, 0] }));
Run it in the playground →

A box standing on the point at: the centre of its base.

new Box(o?: BoxOpts): Box

Options: BoxOpts

Extends Node.

MemberDescription
o: BoxOpts
pt(name: 'top' | 'bottom' | 'center' | 'front' | 'back' | 'left' | 'right'): (): V3named points on the box, in world space, that follow it when it moves
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

World axes: x to the right, y into the scene, z up. Every scene shares them, so a figure never has to explain where “up” is.

project returns [screenX, screenY (down), depth]. Larger depth means farther away.

new Camera(kind: 'ortho' | 'persp', props: Record<string, number | number[]>): Camera

Example

import { Camera, Scene, box, ground } from 'cseen';
const s = new Scene({
view: Camera.perspective({ position: [5, -9, 5], target: [0, 0, 1], fov: 35 }),
});
s.add(ground(), box({ at: [0, 0, 0], size: 1.5, role: 'accent' }));
Run it in the playground →

Extends Animatable.

MemberDescription
kind: 'ortho' | 'persp'
axonometric(view?: View): Camera (static)A parallel (axonometric) camera. This is the look of technical illustration.
perspective(o: { position: V3; target?: V3; fov?: number; }): Camera (static)A pinhole camera with real perspective.
zoom: numberMagnification applied after the picture is framed: 1 is the framed size, 2 is twice as large. Animatable.
scaleAt(depth: number): numberScreen size of one world unit at a given depth, relative to the framing (1 for a parallel camera).
viewDir(): V3Unit vector of the direction the camera looks along (towards increasing depth), for the parallel camera.
project(p: V3): V3World point to [screenX, screenY (down), depth]. Perspective divides by depth, clamped so a point behind the camera cannot flip or blow up.
eyeSide(planePoint: V3, normal: V3): numberWhich side of a plane the viewer is on: positive when the eye is on the side the normal points to, negative on the other side, near zero when the plane is seen edge-on.
visible(p: V3): booleanTrue when a point is in front of the camera (always true for a parallel camera).

A drawing built from parts that each draw on, fill, turn, move and fade by themselves. It is the tool for line constructions: guides, compass circles and outlines that trace, copy and turn into a finished shape. Parts paint in the order they were added.

new Construct(o?: ConstructOpts): Construct

Options: ConstructOpts

Example

import { Construct, Scene, Timeline, arcSegs, circleD, segsD, tween } from 'cseen';
const s = new Scene({ view: 'front', size: [360, 360], padding: 6 });
const c = s.add(new Construct({ box: [0, 0, 200, 200], size: [3.4, 3.4] }));
const ring = c.add({ d: circleD(100, 100, 70), stroke: 'faint', width: 1.5, props: { draw: 0 } });
const leaf = c.add({ d: segsD(arcSegs(160, 100, 80, 2.5, 3.78).concat(arcSegs(40, 100, 80, -0.64, 0.64)), true), paint: 'accent', width: 3, props: { draw: 0 } });
const tl = new Timeline();
tl.play(tween(ring, { draw: 1 }, { duration: 1.2 }));
tl.play(tween(leaf, { draw: 1, fill: 0.4 }, { duration: 1.2 }));
tl.play(tween(leaf, { spin: 90 }, { duration: 1.2 }));
Run it in the playground →

Extends SvgPlane.

MemberDescription
o: ConstructOpts
parts: Part[]
add(o: PartOpts): Partadds a shape on top of the others and returns it, ready to animate
at(x: number, y: number): V3Where a point of the drawing sits in the scene, in the plane’s own space (world units from its corner), so lines, dots and labels can be tied to the drawing: a drop line from a corner of a shape to where it lifted.
draw(W: number, H: number): stringall parts, in order, inside one group that maps the box onto the plane (centred, uniform scale)

A line through the samples of a function, drawn as one SVG path.

new Curve(fn: (t: number)

Extends Node.

MemberDescription
fn: (t: number) => V3
o: PathOpts & { range?: [number, number]; samples?: number; }
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

A dot of fixed size on the screen at a point of the scene.

new Dot(o?: NodeOpts & StyleOpts & { r?: number; }): Dot

Extends Node.

MemberDescription
o: StyleOpts & { r?: number; }
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

One flat polygon.

new Face(pts: Point[], o?: FaceOpts): Face

Options: FaceOpts

Extends Node.

MemberDescription
pts: Point[]
o: FaceOpts
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

The ground lattice. With a parallel camera it fills the whole picture.

new Ground(o?: NodeOpts & { step?: number; extent?: number; }): Ground

Extends Node.

MemberDescription
o: { step?: number; extent?: number; }
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

A node that holds other nodes and moves, turns and scales them together.

new Group(o?: NodeOpts): Group

Options: NodeOpts

Extends Node.

MemberDescription
children: Node[]
add<T extends Node>(node: T): Tadds nodes to the group. Adding one node returns it, so you can keep a handle: const b = g.add(box()).
add<T extends Node>(node: T): Tadds nodes to the group. Adding one node returns it, so you can keep a handle: const b = g.add(box()).
add<T extends Node>(node: T): Tadds nodes to the group. Adding one node returns it, so you can keep a handle: const b = g.add(box()).
remove(n: Node): voidtakes a node out of the group. Does nothing for a node that is not in this group.
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

Text that stays upright and readable, attached to a point in the scene.

new Label(text: string, o?: TextOpts): Label

Options: TextOpts

Extends Node.

MemberDescription
text: string
o: TextOpts
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

A curved arrow between two things. The ends can be objects or anchors, so the link follows them when they move. flow: true makes dashes travel along it, and .at(t) gives the point at t (0 to 1) for a pulse to ride.

new Link(a: Point, b: Point, o?: LinkOpts): Link

Options: LinkOpts

Extends Node.

MemberDescription
a: Point
b: Point
o: LinkOpts
at(t: number): V3the point at t along the link, in world space
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

The base of everything in a scene. A node has a position, rotation, scale and opacity, and animatable properties.

new Node(o?: NodeOpts): Node

Options: NodeOpts

Extends Animatable.

MemberDescription
id: string
name?: string | undefined
parent?: Group | undefined
layer: number
world(p: V3): V3point in this node’s own space to world space
toLocal(p: V3): V3a point in world space as a point in this node’s own space: the inverse of world
worldDir(d: V3): V3a direction in this node’s own space as a direction in world space (rotation and scale, no translation)
anchor(local: V3): (): V3a point that stays attached to this node as it moves, rotates and scales
opacityEff: numberopacity of this node times that of every group above it
layerEff: numberdraw layer of this node plus that of every group above it
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

One drawn shape of a construction. Everything that changes is in props, so a timeline can animate it: draw (how much of the outline is drawn, 0 to 1), fill (the fill’s opacity), line (the outline’s opacity), spin (degrees about about), scale, dx, dy and op (opacity of the whole part). Any property you add through props is also animatable and is passed to d when d is a function.

new Part(o: PartOpts): Part

Options: PartOpts

Extends Animatable.

MemberDescription
o: PartOpts

A line through points, drawn as one SVG path.

new Path(pts: Point[], o?: PathOpts): Path

Options: PathOpts

Extends Node.

MemberDescription
pts: Point[]
o: PathOpts
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

A line being drawn by a pen. draw (0 to 1) is how much has been drawn: the trail follows the rail and a pen rides at its tip. The rail is a list of points, or a function from 0..1 to a point. tween(p, { draw: 1 }, { duration: 2 }) writes it out.

new Pen(rail: Point[] | ((t: number)

Options: PenOpts

Extends Node.

MemberDescription
rail: Point[] | ((t: number) => V3)
o: PenOpts
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

Any SVG laid on a plane in 3D. With a parallel camera the placement is exact. With perspective it is a close approximation, built from small pieces (an SVG can only be sheared, not foreshortened).

new Plane(o: PlaneOpts): Plane

Options: PlaneOpts

Extends Node.

MemberDescription
parsed: ParsedSvg
o: PlaneOpts
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

A dot that rides along a link, a polyline or a function. Animate along from 0 to 1: tween(p, { along: 1 }, { duration: 2, from: { along: 0 } }). It is hidden outside 0 to 1.

new Pulse(rail: Rail, o?: PulseOpts): Pulse

Options: PulseOpts

Extends Node.

MemberDescription
rail: Rail
o: PulseOpts
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

A scene is a tree of nodes, a camera, and the settings to draw them. It draws whatever state its nodes are in right now. A timeline changes that state over time.

new Scene(o?: SceneOpts): Scene

Options: SceneOpts

Example

import { Scene, box, ground } from 'cseen';
const s = new Scene({ view: 'iso', title: 'A box on the ground' });
s.add(ground(), box({ at: [0, 0, 0], size: 1.5, role: 'accent' }));
Run it in the playground →
MemberDescription
root: Groupthe top of the tree of nodes
camera: Camera
size: [number, number]drawing size in viewBox units
padding: numberspace around the drawing in viewBox units
region?: Region | undefinedthe part of the picture the scene may use
light: V3direction towards the light, in world space
title?: string | undefinedaccessible name
desc?: string | undefinedlonger accessible description
fit: 'auto' | 'sphere' | Fit‘auto’ refits every frame. ‘sphere’ frames the bounding sphere. A locked fit keeps the picture steady while things move.
sphere?: Sphere | undefinedwith fit ‘sphere’: a sphere fixed ahead of time, so a moving scene keeps its framing (see Timeline.lockFit)
sketch?: { amp: number; seed: number; } | undefinedthe hand-drawn look, if any
split: booleanwhether faces that pierce each other are cut before sorting
add<T extends Node>(node: T): TAdds nodes to the scene. Adding one node returns it, so you can keep a reference to animate.
add<T extends Node>(node: T): TAdds nodes to the scene. Adding one node returns it, so you can keep a reference to animate.
add<T extends Node>(node: T): TAdds nodes to the scene. Adding one node returns it, so you can keep a reference to animate.
prims(): Prim[]Everything the nodes draw right now, flattened, in the order they were added.
screen(p: V3): [number, number]Where a point of the scene lands in the picture, in the scene’s drawing units, with the framing as it is now. Use it to line something up with the drawing, such as a grid on the page behind it.
frame(): FrameDraws the scene as it is now: the items back to front and the framing used. svg and mount build on it.
lockTo(b: Bounds): voidKeeps the picture still while things move. Pass the bounds of every moment you care about (see lockFit on a timeline) so nothing is ever cropped.
box(): { lo: V3; hi: V3; } | nullthe box around every point in world space, for framing by sphere
bounds(): Bounds | nullthe box around the drawing on screen, before fitting, or null when nothing is visible
svg(o?: { class?: string; }): stringThe scene as a complete, standalone SVG string. Safe to render on the server.

Several spheres drawn as one body: overlapping balls, like a cloud, are ordered together by depth.

new Spheres(list: [V3, number][], o?: NodeOpts & StyleOpts & { fill?: number; alpha?: number; res?: number; outline?: boolean; shade?: boolean; }): Spheres

Extends Node.

MemberDescription
list: [V3, number][]
o: StyleOpts & { fill?: number; alpha?: number; res?: number; outline?: boolean; shade?: boolean; }
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

A parametric surface (u, v) to a point, drawn as a mesh of shaded quads. A function z = f(x, y) is a special case.

new Surface(fn: (u: number, v: number)

Options: SurfaceOpts

Extends Node.

MemberDescription
fn: (u: number, v: number) => V3
o: SurfaceOpts
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

Anything drawn as SVG on a plane in the scene: charts, screen elements, maths figures. A subclass says how to draw itself from its current props, and gets the plane, fading and placement for free.

new SvgPlane(o: SvgPlaneOpts, fallback: [number, number]): SvgPlane

Options: SvgPlaneOpts

Extends Node.

MemberDescription
size: [number, number]
o: SvgPlaneOpts
draw(W: number, H: number): stringthe drawing, in svg units: W wide and H tall, origin at the top left
collect(out: Prim[]): voidappends what this node draws right now to out, in world coordinates. Draws nothing when fully transparent.

Text laid on a plane in the scene: a wall, the floor, a screen. It can be code, with colours. It wraps to the plane and shrinks to fit, so it never runs past the edge. type (0 to 1) is how much has been typed, so typeOn(block) animates it appearing letter by letter.

new TextBlock(o: TextBlockOpts): TextBlock

Options: TextBlockOpts

Extends Node.

MemberDescription
o: TextBlockOpts
lines: string[]
length: numbercharacters in the source, counting a newline after every line: the scale of type
collect(out: Prim[]): voiddraws the rows typed so far, one <text> per row with a <tspan> per run of one colour

A timeline is a list of tweens. seek(t) rewinds everything to its starting state and replays up to t, so the result depends only on t. Playing, scrubbing and rendering a still frame all work the same way.

new Timeline(): Timeline

Example

import { Scene, Timeline, box, fadeIn, ground, moveBy } from 'cseen';
const s = new Scene({ fit: 'sphere' });
s.add(ground());
const b = s.add(box({ at: [0, 0, 0], size: 1.5, role: 'accent' }));
const tl = new Timeline();
tl.play(fadeIn(b, 1));
tl.chapter('move').play(moveBy(b, [2, 0, 0], 1.2));
tl.lockFit(s);
Run it in the playground →
MemberDescription
tweens: Tween[]every tween, with start times on the timeline’s clock
chapters: { name: string; time: number; }[]named marks, in the order made, for step buttons and captions
play(a: Anim): thisappends an animation after everything so far
at(time: number, a: Anim): thisplaces an animation at an absolute time without moving the cursor
wait(d: number): thisleaves a gap: the next play starts d seconds later
chapter(name: string): thismarks the current time as a named step, for “next step” buttons and captions
duration: numberthe length in seconds: where the cursor is, so a trailing wait counts
seek(t: number): voidputs every animated object in the state it has at time t (seconds)
lockFit(scene: Scene, samples?: number): thisFixes the scene’s framing to fit every moment of this timeline, so the picture does not shift as things move. Call it once after building the timeline.

A point that may follow something else: a fixed position, or a function evaluated each frame.

type Anchor = V3 | (() => V3)

A piece of animation. It knows its length and its tweens (starting at 0), and can be combined with others.

OptionTypeDefaultDescription
durationnumberlength in seconds
tweensTween[]the tweens that make it up, with start times relative to its beginning

Options of box.

Extends FaceOpts.

OptionTypeDefaultDescription
size?BoxSize[width (x), depth (y), height (z)], or one number for a cube

Options of a construction.

Extends SvgPlaneOpts.

OptionTypeDefaultDescription
box?[number, number, number, number]the part of the drawing’s own coordinates to show: x, y, width, height. [0, 0, 1000, 1000] by default.

An easing function: maps progress 0 to 1 onto progress 0 to 1.

type Ease = (t: number) => number

The name of a built-in easing curve.

type EaseName = keyof typeof ease

Options of filled surfaces.

Extends NodeOpts, StyleOpts.

OptionTypeDefaultDescription
fill?numbercolour strength, 0 to 1. Low values are a tint, 1 is the full role colour.
alpha?numbersee-through amount. 1 is solid, 0.2 is a faint pane of glass.
shade?booleanlight the face from the scene’s light, darker or lighter by angle
outline?booleandraw the outline

Options of link.

Extends NodeOpts, __type.

OptionTypeDefaultDescription
bend?numberhow far the line bows away from the straight line, as a fraction of its length. 0 is straight. Default 0.25.
toward?V3the direction it bows toward. Default up (z).
samples?numberhow many points the curve is sampled at

What a mark can match: a word, a pattern, or a [start, end) range of the source text.

type MarkKey = string | RegExp | [number, number]

Words to colour in a text block: a map from the word to a role or CSS colour, or a list of [match, colour] pairs where the match is a string, a regular expression or a [start, end] range.

type Marks = Record<string, string> | Array<[MarkKey, string]>

Options of mount.

OptionTypeDefaultDescription
timeline?Timelinethe timeline to play
autoplay?booleanplay when mounted. Default true.
loop?boolean | { pause: number; }restart at the end. true, or { pause: seconds } to hold the last frame first. Default true.
speed?numberplayback speed, 1 by default
rest?numbertime to show for people who prefer reduced motion, and before play. Defaults to the end of the timeline.
onlyVisible?booleanonly animate while on screen. Default true.
maxFps?numberupper limit of redraws per second, 60 by default

Options every node takes.

OptionTypeDefaultDescription
at?V3position of the node’s origin in its parent
rot?V3Euler rotation in degrees
scale?numberuniform scale, 1 by default
opacity?numbertransparency of the whole node, 0 to 1. Animatable.
layer?numberdraw order between groups of things: lower first. Labels default to above geometry.
name?stringa name for finding the node later

Handle to a running orbit control.

OptionTypeDefaultDescription
reset() => voidback to the view the camera had when orbit was set up
destroy() => voidremoves the listeners and restores the element

How far the camera may turn and zoom. Pitch and yaw are in degrees.

OptionTypeDefaultDescription
pitch?[number, number]degrees. Default [-89, 90]: from just below the ground to straight down.
yaw?[number, number]degrees. Default: free.
zoom?[number, number]magnification. Default [0.4, 6].

Options of orbit.

OptionTypeDefaultDescription
onChange?(() => void)called after the camera changed. Pass () => player.invalidate() so the picture redraws.
zoom?booleanwheel and pinch zoom. Default true.
limits?OrbitLimitshow far the camera may turn and zoom
autoRotate?numberdegrees per second of slow turning until the person touches the figure. Default off. Ignored for reduced motion.
keyboard?booleanarrow keys turn, plus and minus zoom, 0 resets. Default true.
sensitivity?numberdegrees per pixel of drag. Default 0.4.
home?Record<string, number | number[]>the camera state that reset returns to (a camera.snapshot()). Default: the camera as it is when orbit starts.

Options of a part of a construction.

OptionTypeDefaultDescription
d?string | ((p: Record<string, Value>) => string)the outline as an SVG path, or a function of the part’s properties that makes it, so a shape can be reshaped by animating props. Leave it out for a text label.
text?stringmakes the part a small text label instead of an outline, drawn at at in the colour of stroke. Fade it with op.
at?[number, number]where the label sits, in the drawing’s coordinates. The text is centred on it unless anchor says otherwise.
size?numberthe label’s font size in svg units, 24 by default
anchor?'start' | 'end' | 'middle'which end of the label sits at at: start, middle (default) or end
stroke?stringcolour of the line: a role (ink, faint, accent, s1 to s8) or any CSS colour. ink by default.
paint?stringcolour of the fill: a role or any CSS colour. No fill by default.
width?numberline width in svg units of the drawing, 2 by default
about?[number, number]the point that spin and scale turn and grow about, the middle of the box by default
props?Record<string, Value>starting values of the part’s properties (draw, fill, line, spin, dx, dy, scale, op, or your own)

Options of lines and paths.

Extends NodeOpts, StyleOpts.

OptionTypeDefaultDescription
closed?booleanjoin the last point back to the first
arrow?'start' | 'end' | 'both'where to put arrowheads
dash?stringan SVG dash pattern such as '3 3'
flow?number | booleandashes travel along the line (in the direction it is drawn): true, or cycles per second. Runs in CSS, so it works in a static SVG too.
draw?numberhow much of the line is drawn, 0 to 1. Animate it for a draw-on effect.

Options of pen.

Extends NodeOpts.

OptionTypeDefaultDescription
role?stringcolour role (ink, faint, accent, s1 to s8) or a role you define in CSS
width?numberline width in svg units
lean?V3which way the pen leans from its tip. Default up and a little to the right.
length?numberlength of the pen in world units
keep?booleanleave the pen drawn once the line is finished (default: it lifts away)
samples?numberhow many points a function is sampled at

Options of plane.

Extends NodeOpts.

OptionTypeDefaultDescription
svgstringthe SVG to place: its source text
size[number, number]size of the plane in world units: [width along u, height along v]
u?V3direction of the SVG’s x axis, and of its (upward) y axis, in the node’s space. Defaults to the x and z axes: a wall facing you.
v?V3direction of the SVG’s upward y axis in the node’s space
recolor?Record<string, string>swap hard-coded colours in the SVG for CSS variables, e.g. { ‘#ffffff’: ‘var(—c-bg)’ }
border?string | falsea line around the plane
cells?numberpieces per side when the camera has perspective. More is smoother and slower.
alpha?numbersee-through amount of the SVG, 0 to 1

Controls a mounted scene: play, pause, seek by time or by chapter, and redraw after a change made by hand.

OptionTypeDefaultDescription
timenumberthe current time in seconds
durationnumberthe length of the timeline in seconds
play() => voidstarts or resumes playing
pause() => voidstops playing, keeping the current frame
seek(t: number) => voidjumps to a time in seconds
step(dir?: 1 | -1) => voidjumps to the start of the next (or previous) chapter
destroy() => voidstops and releases the scene
playingbooleanwhether it is playing
invalidate() => voidRedraws the scene as it is now, without moving the timeline. Call it after changing the camera or a node by hand (orbit controls do). Calls within one frame are merged, and maxFps still applies.

A place in the scene: a plain [x, y, z], an anchor of a node, or a node itself (its position).

type Point = Anchor | Node

What the renderer draws. Nodes turn themselves into these, in world coordinates.

OptionTypeDefaultDescription
keystringa stable name, so a redraw replaces only what changed
kind'path' | 'dot' | 'text' | 'plane' | 'ground'what to draw
ptsV3[]the points in world space
rolestringcolour role (ink, faint, accent, s1 to s8) or a role you define in CSS
opacitynumbertransparency, 0 to 1
layernumberdraw order between groups of things, lower first
closed?booleanjoin the last point back to the first
face?booleanfill the polygon as a lit surface
fill?numberhow strongly the role colour shows in a face, 0 to 1 (mixed into the background)
alpha?numbersee-through amount of a face, 1 is solid
mix?number0 to 1: blend the face colour towards the accent, for value-coloured surfaces
shade?booleanlight the face from the scene light
cull?booleanhide when the viewer is behind it. Needs a normal that points outwards.
normal?V3the outward direction of a face, used for shading, culling and depth order
width?numberline width in SVG units
draw?numberhow much of the line is drawn, 0 to 1
arrow?'start' | 'end' | 'both'where to put arrowheads
dash?stringan SVG dash pattern
flow?numberdashes travel along the line, this many cycles per second
r?numberradius in pixels, for a dot
text?stringthe characters, for text
anchor?'start' | 'end' | 'middle'which end of the text sits at the point
dx?numbernudge right in pixels
dy?numbernudge down in pixels
size?numberfont size in pixels
noFit?booleanleave out of the framing, so it never changes the size of the picture
group?stringpieces of one object (a mesh, a box) are ordered among themselves by depth, not by plane tests
plane?{ w: number; h: number; svg: ParsedSvg; cells: number; id: string; }an SVG to place on the plane through the first three points
ground?{ step?: number; extent?: number; }the lattice on the floor

A point in svg units.

type Pt = [number, number]

Options of pulse.

Extends NodeOpts.

OptionTypeDefaultDescription
r?numberradius of the dot in pixels
role?stringcolour role (ink, faint, accent, s1 to s8) or a role you define in CSS
trail?numberhow many fading copies follow the pulse
spacing?numberhow far apart the copies are, as a fraction of the path

One drawn row of text: a slice [start, end) of the source, and the source line it came from.

OptionTypeDefaultDescription
startnumberindex of the first character in the source
endnumberindex after the last character
linenumberthe source line the row belongs to
firstbooleanwhether it is the first row of its source line

Options of new Scene.

OptionTypeDefaultDescription
view?Camera | Viewa preset (‘iso’, ‘front’, ‘side’, ‘top’), explicit angles, or a ready-made Camera
size?[number, number]drawing size in viewBox units. Defaults to 320 by 200.
padding?numberspace around the drawing in viewBox units, 12 by default
region?Regionpart of the picture the scene may use, leaving room for insets
light?V3direction towards the light, in world space
title?stringaccessible name and description
desc?stringlonger accessible description of the figure
sketch?boolean | { roughness?: number; seed?: number; }a hand-drawn look: lines wobble a little and are drawn twice. true, or { roughness, seed }. Repeatable: the same scene always wobbles the same way.
fit?'auto' | 'sphere'‘auto’ (default) fits the drawing each frame. ‘sphere’ fits the bounding sphere, so the object keeps its size while the camera turns.
split?booleancut faces that pierce each other along the crossing, so they sort correctly. Off by default: it costs time on every frame, and most scenes have nothing that pierces.

One cubic Bezier segment: start, two control points, end. Lines are cubics with the controls on the ends.

type Seg = [Pt, Pt, Pt, Pt]

A sphere in world space: a centre c and a radius r.

OptionTypeDefaultDescription
cV3the centre
rnumberthe radius

Colour and line width, shared by lines, faces and text.

OptionTypeDefaultDescription
role?stringcolour role: ink, faint, accent, warm, cool, s1 to s8, or any name you define in CSS as .cn-r-name
width?numberline width in SVG units

Options of surface.

Extends FaceOpts.

OptionTypeDefaultDescription
u?[number, number]parameter ranges
v?[number, number]range of the second parameter, [min, max]
res?[number, number]number of cells along u and v
color?((p: V3, u: number, v: number) => number)returns 0 to 1 per point, blending the colour from the role towards the accent

Options shared by everything that is an SVG drawing placed on a plane: charts, screens and maths figures.

Extends NodeOpts.

OptionTypeDefaultDescription
size?[number, number]size in world units
u?V3directions of the drawing’s right and up. Default: a wall facing the viewer.
v?V3direction of the plane’s upward axis
cull?booleanhide the plane when it is seen from behind, so a face of a closed shape does not show through the faces in front. Off by default.

Options of write and code.

Extends NodeOpts.

OptionTypeDefaultDescription
textstring | string[]the text, or an array of lines
size?[number, number]size of the plane in world units. Text is laid out inside it from the top left.
u?V3direction of the text’s x axis and of its (upward) y axis. Defaults to a wall facing the viewer.
v?V3direction of the text’s upward y axis
fontSize?number | 'sm' | 'md' | 'lg'height of a line of text: world units, or ‘sm’ | ‘md’ | ‘lg’ relative to the box. Default fits the box.
lines?numberor: this many lines fill the box
code?booleancolour the pieces of code (keywords, strings, numbers, comments)
numbers?booleanshow line numbers
color?stringCSS colour of ordinary text. Defaults to the theme ink.
pad?numbermargin inside the plane, in world units
lineHeight?numberdistance between lines as a multiple of the font size
cursor?boolean | 'always'show a cursor while typing, or always
align?'start' | 'end' | 'middle'horizontal alignment inside the box
border?string | falsecolour role of a line drawn around the plane (e.g. ‘faint’), or false for none
wrap?'word' | 'char' | 'none'how to break long lines: at spaces (default), anywhere, or not at all
overflow?'shrink' | 'clip'when the text does not fit: make it smaller (default) or cut it off at the edge
mark?Markscolour chosen words: { deploy: ‘accent’ } or [[/\d+/, ‘s2’], [[0, 4], ‘faint’]]. A role name or any CSS colour.
charWidth?numberwidth of a character as a fraction of the font size. 0.6 suits the default monospace font.

Options of label.

Extends NodeOpts, StyleOpts.

OptionTypeDefaultDescription
anchor?'start' | 'end' | 'middle'which end of the text sits at the point
dx?numbernudge in final pixels (a text always faces the viewer)
dy?numbernudge down in pixels
size?numberfont size in pixels

A piece of a line of code with its kind, for colouring.

OptionTypeDefaultDescription
textstringthe characters of the piece
kindTokenKindwhat the piece is

What a token is: plain text, a keyword, a string, a number or a comment.

type TokenKind = 'plain' | 'keyword' | 'string' | 'number' | 'comment'

One change of one object’s properties over a span of time.

OptionTypeDefaultDescription
targetAnimatablethe animated object
toRecord<string, To>the values to reach, or functions of the current value
from?Record<string, Value>values set before the tween starts, so something can wait invisible until its moment
startnumberstart time in seconds, relative to its animation
durationnumberlength in seconds
easeEasethe easing function

Options of tween.

OptionTypeDefaultDescription
duration?numberlength in seconds, 1 by default
ease?'back' | Ease | 'linear' | 'in' | 'out' | 'inOut' | 'smooth'an easing name or function, inOut by default
from?Record<string, Value>values set before the tween starts
delay?numberseconds to wait before it starts

A point or direction [x, y, z]. x runs right, y runs into the scene, z runs up.

type V3 = [number, number, number]

A camera preset (iso, front, side, top) or explicit { yaw, pitch } angles in degrees.

type View = 'iso' | 'front' | 'side' | 'top' | { yaw: number; pitch: number }

The named easing curves: linear, in, out, inOut, smooth and back. Each maps time 0 to 1 onto progress 0 to 1.

const ease: { linear: (t: number) => number; in: (t: number) => number; out: (t: number) => number; inOut: (t: number) => number; smooth: (t: number) => number; back: (t: number) => number; }