computeScene function

ComputedScene computeScene(
  1. CanvasSceneDocument doc,
  2. CoreServices services
)

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,
  );
}