serializeScene function
Serializes the live Node graph rooted at root into a new
SceneDocument.
root is treated as the synthesized realization root (as returned by
realizeScene): its handedness is read back into the stage, and its
children become the document's root nodes. Nodes realized from a document
keep their identity-tag ids, so a load/edit/save cycle is rename-proof and
diff-friendly; untagged (hand-built) nodes mint fresh ids. Skins, the
root's parsed animations, and lazy prefab placeholders serialize too; a
loaded lazy subtree serializes as its placeholder (the streamed content
belongs to the referenced prefab, not this document). Components are
serialized through registry (the built-ins by default); a component no
codec claims is skipped with a debug warning.
Implementation
SceneDocument serializeScene(Node root, {FsceneComponentRegistry? registry}) {
final reg = registry ?? defaultComponentRegistry();
final document = SceneDocument();
document.stage.handedness = root.localTransform.determinant() < 0
? Handedness.right
: Handedness.left;
final context = SerializeContext(document);
// First pass: assign every node its id, reusing realize-time identity tags
// where present (skipping duplicates from app-side clones). Newly assigned
// ids are tagged back onto the nodes so follow-up passes (`serializeViews`
// referencing camera nodes) and future saves see stable ids.
final ids = <Node, LocalId>{};
final used = <LocalId>{};
void assign(Node node) {
final tagged = nodeFsceneId(node);
final id = tagged != null && used.add(tagged) ? tagged : document.newId();
ids[node] = id;
if (id != tagged) {
used.add(id);
tagNodeId(node, id);
}
for (final child in node.children) {
assign(child);
}
}
for (final child in root.children) {
assign(child);
}
for (final child in root.children) {
final spec = _serializeNode(child, document, reg, context, ids);
document.addNode(spec, root: true);
}
_serializeAnimations(root, document, ids);
return document;
}