computeScene function
Implementation
ComputedScene computeScene(CanvasSceneDocument doc, CoreServices services) {
final geom = NodeGeometry(services);
final drawList = <DrawItem>[];
final nodeById = <NodeId, Node>{};
final worldById = <NodeId, vm.Matrix4>{};
final inverseWorldById = <NodeId, vm.Matrix4>{};
final localBoundsById = <NodeId, Rect2D>{};
final visualBoundsWorldById = <NodeId, Rect2D>{};
final pathIRById = <NodeId, PathIR>{};
final imagePlacementById = <NodeId, ImagePlacement>{};
final iconTextById = <NodeId, IconTextPayload>{};
final iconPathIRById = <NodeId, PathIR>{};
// -----------------------------
// Helpers
// -----------------------------
vm.Matrix4 localMatrixFromKnownBounds(Node n) {
final xf = n.xf;
final b = localBoundsById[n.id];
final pivot = _pivotFromOrigin(xf.origin, b, xf.customPivotPx);
return matFromTRS(
position: xf.position,
rotationRad: xf.rotationRad,
scale: xf.scale,
pivotPx: pivot,
);
}
// -----------------------------
// PASS A: compute localBounds for ALL nodes (leaves + groups)
// - Leaves: measure/compile + cache
// - Groups: union(children bounds transformed into this node's local space)
// -----------------------------
Rect2D? computeLocalBounds(Node n, bool ancestorHidden) {
final hidden = ancestorHidden || n.hidden;
if (hidden) return null;
final leafBounds = geom.leafLocalBounds(
n,
pathIRById: pathIRById,
imagePlacementById: imagePlacementById,
iconTextById: iconTextById,
iconPathIRById: iconPathIRById,
);
if (leafBounds != null) {
localBoundsById[n.id] = leafBounds;
return leafBounds;
}
// Group/logo bounds: union child AABBs in *this node's local space*.
if (n is GroupNode) {
Rect2D? u;
final kids = nodesInPaintOrder(n.childrenOrEmpty);
for (final c in kids) {
final cb = computeLocalBounds(c, hidden);
if (cb == null) continue;
// Child local matrix uses child's own pivot rules, which rely on its bounds
// (already computed because this is post-order).
final childLocalMat = localMatrixFromKnownBounds(c);
final childAabbInParentLocal = aabbOfTransformedRect(cb, childLocalMat);
u = (u == null)
? childAabbInParentLocal
: Rect2DX.union(u, childAabbInParentLocal);
}
if (u != null) localBoundsById[n.id] = u;
return u;
}
return null;
}
// Run pass A for all roots
for (final n in nodesInPaintOrder(doc.children)) {
computeLocalBounds(n, false);
}
// -----------------------------
// PASS B: compute world matrices + drawList + visual world bounds
// -----------------------------
Rect2D? walk(
Node n,
vm.Matrix4 parentWorld,
List<NodeId> groupStack,
bool ancestorHidden,
) {
final hidden = ancestorHidden || n.hidden;
if (hidden) return null;
nodeById[n.id] = n;
final localMat = localMatrixFromKnownBounds(n);
final world = vm.Matrix4.copy(parentWorld)..multiply(localMat);
worldById[n.id] = world;
final inv = vm.Matrix4.copy(world)..invert();
inverseWorldById[n.id] = inv;
if (n is GroupNode) {
final kids = nodesInPaintOrder(n.childrenOrEmpty);
final nextStack = [...groupStack, n.id];
Rect2D? groupWorld;
for (final c in kids) {
final cb = walk(c, world, nextStack, hidden);
if (cb == null) continue;
groupWorld = (groupWorld == null) ? cb : Rect2DX.union(groupWorld, cb);
}
if (groupWorld != null) {
visualBoundsWorldById[n.id] = groupWorld;
}
return groupWorld;
}
// Leaf: add draw item
drawList.add(DrawItem(leafId: n.id, groupStack: groupStack));
final lb = localBoundsById[n.id];
if (lb != null) {
final aabb = aabbOfTransformedRect(lb, world);
visualBoundsWorldById[n.id] = aabb;
return aabb;
}
return null;
}
for (final n in nodesInPaintOrder(doc.children)) {
walk(n, vm.Matrix4.identity(), const <NodeId>[], false);
}
return ComputedScene(
drawList: drawList,
nodeById: nodeById,
worldById: worldById,
inverseWorldById: inverseWorldById,
localBoundsById: localBoundsById,
visualBoundsWorldById: visualBoundsWorldById,
pathIRById: pathIRById,
imagePlacementById: imagePlacementById,
iconTextById: iconTextById,
iconPathIRById: iconPathIRById,
);
}