← Learn

The Scene Document Model: Document Structure for 3D Elements

Software 4 Senses LLC · Last updated

Most 3D engines give a scene a graph but not a document: things to draw, addressed by renderer handles, mutated imperatively — with no bubbling events, no stable authored addresses, and no save format that is the single source of truth. The Scene Document Model takes the opposite stance — it gives a pure 3D scene graph the structure of a document, so that solids, lights, and groups can be addressed, listened to, animated by name, saved, and reloaded the way the elements of a web page can. This page is the definition of the Scene Document Model, the model a 3D Spatial Web Interface is built on: 3D elements given document behavior.

What is the Scene Document Model?

Scene Document Model — an architecture in which the elements of a 3D scene — solids, lights, groups, cameras — carry the structure and behavior of a 2D document: a parent/child element tree, events that bubble along it, stable addresses, per-element state, and serialization that round-trips, so a 3D scene can be authored, queried, saved, and reasoned about the way a web page can.

None of these elements is HTML: every document behavior — the tree, the events, the addressing, the save format — is deliberately rebuilt over the engine's own records. The payoff is that decades of the 2D web's structural wisdom (nesting, bubbling, ids, components, hide-versus-remove) applies to geometry and light.

An element tree, not a display list

The model's substrate is a parent/child tree of addressable records, not a flat list of draw calls. A group is a container in the same sense a <div> is: it parents its children, its subtree moves and serializes as a unit, and a live group's subtree can be captured whole into a reusable definition. A message bubbled from a record climbs its parent chain toward the scene root, which is what makes the tree structural rather than cosmetic — hierarchy determines who hears what, not just what moves with what.

The tree stays honest through a strict mechanism-versus-content rule. A renderer needs internal structures the author never made — the reference implementation, for example, re-parents top-level records into bounding-volume-hierarchy cull nodes to prune ray marching. Those nodes are mechanism, excluded by identity from the document: saved scenes never contain them, queries never report them, and picking never selects them; authored structure is restored whenever the document is read or written. Membership is a judgment the engine must enforce — the document is what the author made, never what the renderer built to go fast.

The DOM concept map

Each load-bearing concept of the 2D document model has a specific equivalent in the Scene Document Model. The table below is the map; the rows are elaborated after it.

2D document conceptScene Document Model equivalent
ElementScene record — a solid, light, group, or camera display, each with an id that stays fixed while the scene runs.
Parent/child nestingGroups — container records whose subtree moves, captures, and serializes as a unit, and through which events bubble.
id / human-readable classId / authored label — a label saves with the scene, and the reference implementation's lookups (its query interface and the host page's port) accept it in place of an object's id, though a state-machine binding matches only an exact id or '@self'; it need not be unique, and a duplicate produces a which-one question, never a guess.
CSS transitionNamed animation clip playing from a 'current' sentinel frame that resolves from live state at play time — a glide from wherever the element is now.
Event listenerState-machine bus binding — a { channel, action, when, transition } subscription that fires a transition carrying the message payload, with '{{payload.key}}' templating to lift values into actions.
Reusable componentComposite — parameterized structure, serialized as its name-plus-params pair — and archetype: shape + clips + state machine, so the same shape can carry different semantics.
Document saveThe scene document — reconstitutable definitions, kept deliberately separate from reported live state.
display:nonePark — present but deliberately not participating: the record keeps its id, hierarchy position, and state, but is not drawn, not lit, and not clickable. Transient by ruling; never serialized.

Three rows deserve emphasis. Clips make the model's mutation convention analog-first: a state change is authored as a two-frame clip from 'current' to the target and played, so every change is continuous motion rather than a teleport — the 3D equivalent of never setting a property a CSS transition should have eased. The browser computes the easing (a Web Animations API timeline interpolating registered CSS custom properties) and the engine writes the sampled values into GPU records of every kind (see the animation bridge). Archetypes make state per-instance: every instance gets its own state machine sitting at its initial state, unfired, so two instances are never in lockstep unless explicitly choreographed. And park is precisely not a soft delete: it answers the same need display:none answers in 2D — still in the document, temporarily out of the experience — and because it is transient by design, a saved scene never records it and a loaded scene comes back fully participating.

Events that bubble through 3D

The model's event system rebuilds DOM event semantics over the record tree — it does not use DOM events, and no DOM object is involved in delivery. In the reference implementation, a synchronous scene bus carries frozen envelopes of the shape { channel, action, payload, senderId, targetId, seq, timeStamp }. A bubbling delivery walks parent pointers from the target record toward the scene root with stopPropagation semantics and a cycle guard; a bubble that reaches the root unstopped is offered only to an explicit whitelist of registered root actions, never to open method dispatch. The inverse traversal exists too: broadcastSubtree delivers root-down through a subtree. Delivery is fault-isolated per handler — a subscriber that throws is converted into a message on a reserved error channel rather than killing the loop, so one broken listener never silences the rest of the scene.

Inside the scene the browser has no element to hit-test, so picking is a CPU ray cast — exact against JavaScript twins of the distance fields for a handful of basic shapes, and against a conservative bounding box for the rest — and the result is published on the bus's ui channel to that channel's subscribers, like any other published message, rather than bubbled (see event propagation in 3D).

Layout machines for 3D volumes

Rigs are the model's layout containers: arrangement machines that position slabs — 3D content-bearing volumes — the way flex and grid containers position boxes. A rig owns persistent cells; slabs occupy them. The cells outlive their occupants: a cell is minted once, holds one occupant, and remains when the occupant moves elsewhere, so occupancy is a first-class, queryable fact of the document rather than an emergent property of positions. Re-arrangement is document-shaped too — solve the target layout, then glide every element from its live pose, interruptibly.

A rig's elements are volumes CSS cannot see — yet CSS can still be the solver: the reference implementation lays out a measurement twin with the author's ordinary CSS grid — an offscreen container of empty stand-in boxes, each carrying the declared size of the element it stands for — measures the resulting boxes, and converts the measured rectangles into world-space poses (see how CSS grid becomes 3D layout). The twin is neither the platform's Shadow DOM nor a hidden state store — it exists only to be measured. State for 3D elements lives with the elements themselves: in the records, their clips, and their machines, whose definitions the scene document saves — never in any hidden DOM.

A document that saves

A scene document round-trips because it stores definitions, not live state. Serialization is split on purpose into two views: the document carries the reconstitutable shapes — clip keyframes with 'current' preserved as a keyword, state machines as the plain JSON they were authored in, composites as their name-plus-params pair — while live state (clip progress, a machine's current state, message history) is reported separately for reading and is never written into the document. Reloading a scene therefore replays construction rather than restoring a memory image, which is what makes the saved form portable, diffable, and safe to edit by hand.

The same discipline keeps documents small: derived geometry never serializes. Where a scene has expanded a definition into many records — glyphs laid out from a text node, a mesh expanded into its triangles — the document stores a single reference node naming what to rebuild instead of what was built: a twelve-letter word is twelve glyph records under one group in the scene and one node in the document. And consistent with definitions-not-state, transient conditions stay out entirely — a parked element is never saved as parked; a loaded scene wakes with everything present and participating. The scene document is the single serialized truth for every 3D element.

Reference implementation

The Software 4 Senses 3D Spatial Web Studio implements the Scene Document Model as the stateful tier of its WebGPU engine. Every scene element — primitive, group, light, camera display — is an addressable record that can carry named clips and a declarative JSON state machine; a synchronous scene bus provides targeted delivery, parent-pointer bubbling with a root-action whitelist, and subtree broadcast; rigs place slabs through a CSS-measured twin; and scene documents serialize definitions — clips, machines, composites, reference nodes — separately from the live state the engine reports at runtime. The pages linked below cover the individual mechanisms in depth.

Related reading