camera-framing
camera-framing
Camera-framing primitives — every parameter of `<frame>`, one scene each.
Read the rig's notes
`<frame>` is a CAMERA POSE TRACK. It lives under `<camera><properties>` and
holds `<keyframes><kf …/>`; each kf is resolved fresh EVERY FRAME and the
two bracketing kfs are lerped. There are exactly two kinds of kf:
COORDINATE kf kf(time= position="x y z" look_at="x y z" fov= …)
a literal pose, the same thing a hand-authored camera does.
FRAMED kf kf(time= node="<node-id>" fill= …)
"put the target on screen at N% of the viewport" — the pose
is SOLVED from the node's LIVE world bounds, so it tracks a
target that is moving, growing, or still laying itself out.
Both kinds live in one track and lerp into each other, which is the whole
point: fly from a hand-picked establishing pose straight into a solved
close-up without ever typing a coordinate for the close-up.
THE PARAMETERS (packages/engine/lib/frame-shot-target.ts):
node the node id to frame. Its presence is what makes a kf "framed".
framing "all" (also scene / stage / world) frames the WHOLE SCENE instead
of one node — the union of everything it shows, solved live, so
no id is named and nothing is missed. Haze, lights, camera layers
and off-screen ui stages are not content and stay out of it; the
fit clears the scene's depth, so the front of the box is on
screen at 100 too. Scene 09 is its catalogue.
fill 1 to 1000, default 80 — percent of the viewport the target fills.
Distance is solved so the LARGER of (height, width·aspect) fits:
100 is the exact fit, below it pads, above it keeps closing in on
the centre of the target (140 = "inside the composition").
fov the lens the shot is solved FOR. Same fill, different fov = same
on-screen size, completely different perspective. Defaults to the
camera's live fov.
padding distance multiplier applied after the fill solve, default 1.
padding=1.5 = "the same shot, 50% farther back".
offset "x y z" added to the LOOK-AT point, so the target slides off
centre by the negated offset while the camera translates with it.
side which way the camera sits off the target's face:
auto (default) the target's outward normal, flipped if it
faces away from the usual overview (0 1.2 10.5) — and
NEVER flipped for a live device screen, whose readable
side is already known
front the target's own +normal, whatever it is aimed at
back the mirror of it
world fixed world +Z — the target's rotation is ignored
entirely (what camera-facing text reels want, so a
flapping target cannot swing the camera around)
bounds what gets measured:
(unset) ONE mesh — the node's live display surface if it has
one, else its largest-area mesh
union the union of the whole subtree — for g / row / column /
isle / grid containers
visible only the members currently on screen, each box scaled
by its live opacity, so the shot GROWS continuously as
a group fades in instead of jumping per entrance.
(word-cloud nodes default to this.)
mesh pick one mesh inside the node by name. Every mesh is renamed to
its authoring node id at mount, so this takes an id.
mesh_match exact (default) or contains — substring match on that name.
up "x y z" overrides the derived camera up (which follows a rolled
target, rolling the horizon with it).
zoom applied on top of the solved pose — punch in without moving.
focus / aperture / max_blur the DOF trio also rides these kfs; the depth
of field reel (promos/primitives/camera-dof/) is its catalogue.
AND ON THE TRACK ITSELF:
frame(smooth=0.5) makes the camera CHASE the track with an exponential lag
of that many seconds instead of riding it exactly. Raw segment
lerping is velocity-discontinuous — the camera dead-stops at
every keyframe — and it twitches whenever a live-solved target
changes size. The damped chase keeps velocity continuous through
both. Scene 08 is nothing but hard steps, made fluid by it.
AUTHORING TRAP worth knowing before you copy anything here: a `<g>` in this
format is an IDENTITY container. Its own position / rotation are folded into
an SVG child context, NOT applied as a 3D transform, so a `<g>` carrying
rotation="0 -38 0" silently does nothing to its children. Every element below
therefore carries its own absolute position and rotation, and the only two
`<g>`s left in the reel (s05-cluster, s06-board) exist purely as FRAMING
TARGETS for bounds=union and bounds=visible. Likewise a `<rect>` with no
`position` is anchored SVG-style at its top-left, so its centre lands at
(w/2, h/2) — every rect here states its position explicitly.
Scene map — the tight beats always carry their own label ON the framed card,
because a
camera dives in.
01 node · fill 35 to 60 to 95 on one card
02 side front · back · world (auto is the default everywhere else)
03 padding · offset the same fill, pushed back and pushed off-centre
04 fov 18 · 42 · 80 at ONE fill, down a depth corridor
05 bounds visible (live build-up) · unset (one mesh) · union
06 mesh · mesh_match exact id · substring · then the whole board
07 up · zoom a rolled card: derived up vs world up, then zoom
08 smooth a step track with no easing at all, damped to fluid
09 framing="all" the whole scene as the target: 55 · 100 · 140
Preview: /packages/runtime/index.html?project=promos/primitives/camera-framing/camera-framing.promo- Topic
- camera
- Scenes
- 9
- Kind
- reference rig
//
Camera-framing primitives — every parameter of `<frame>`, one scene each.
`<frame>` is a CAMERA POSE TRACK. It lives under `<camera><properties>` and
holds `<keyframes><kf …/>`; each kf is resolved fresh EVERY FRAME and the
two bracketing kfs are lerped. There are exactly two kinds of kf:
COORDINATE kf kf(time= position="x y z" look_at="x y z" fov= …)
a literal pose, the same thing a hand-authored camera does.
FRAMED kf kf(time= node="<node-id>" fill= …)
"put the target on screen at N% of the viewport" — the pose
is SOLVED from the node's LIVE world bounds, so it tracks a
target that is moving, growing, or still laying itself out.
Both kinds live in one track and lerp into each other, which is the whole
point: fly from a hand-picked establishing pose straight into a solved
close-up without ever typing a coordinate for the close-up.
THE PARAMETERS (packages/engine/lib/frame-shot-target.ts):
node the node id to frame. Its presence is what makes a kf "framed".
framing "all" (also scene / stage / world) frames the WHOLE SCENE instead
of one node — the union of everything it shows, solved live, so
no id is named and nothing is missed. Haze, lights, camera layers
and off-screen ui stages are not content and stay out of it; the
fit clears the scene's depth, so the front of the box is on
screen at 100 too. Scene 09 is its catalogue.
fill 1 to 1000, default 80 — percent of the viewport the target fills.
Distance is solved so the LARGER of (height, width·aspect) fits:
100 is the exact fit, below it pads, above it keeps closing in on
the centre of the target (140 = "inside the composition").
fov the lens the shot is solved FOR. Same fill, different fov = same
on-screen size, completely different perspective. Defaults to the
camera's live fov.
padding distance multiplier applied after the fill solve, default 1.
padding=1.5 = "the same shot, 50% farther back".
offset "x y z" added to the LOOK-AT point, so the target slides off
centre by the negated offset while the camera translates with it.
side which way the camera sits off the target's face:
auto (default) the target's outward normal, flipped if it
faces away from the usual overview (0 1.2 10.5) — and
NEVER flipped for a live device screen, whose readable
side is already known
front the target's own +normal, whatever it is aimed at
back the mirror of it
world fixed world +Z — the target's rotation is ignored
entirely (what camera-facing text reels want, so a
flapping target cannot swing the camera around)
bounds what gets measured:
(unset) ONE mesh — the node's live display surface if it has
one, else its largest-area mesh
union the union of the whole subtree — for g / row / column /
isle / grid containers
visible only the members currently on screen, each box scaled
by its live opacity, so the shot GROWS continuously as
a group fades in instead of jumping per entrance.
(word-cloud nodes default to this.)
mesh pick one mesh inside the node by name. Every mesh is renamed to
its authoring node id at mount, so this takes an id.
mesh_match exact (default) or contains — substring match on that name.
up "x y z" overrides the derived camera up (which follows a roll