@weasel-js/core 1.10.0 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +168 -0
- package/README.md +33 -0
- package/dist/affordances/composeAffordanceLayer.js +21 -21
- package/dist/affordances/composeAffordanceLayer.js.map +1 -1
- package/dist/affordances/hitAffordanceRegions.js +48 -32
- package/dist/affordances/hitAffordanceRegions.js.map +1 -1
- package/dist/affordances/pathAnchors.js +50 -28
- package/dist/affordances/pathAnchors.js.map +1 -1
- package/dist/affordances/rotateCursor.js +12 -0
- package/dist/affordances/rotateCursor.js.map +1 -0
- package/dist/affordances/rotationHandle.js +40 -43
- package/dist/affordances/rotationHandle.js.map +1 -1
- package/dist/animation/rig/rigNode.js +9 -3
- package/dist/animation/rig/rigNode.js.map +1 -1
- package/dist/animation/rig/rigSeat.js +46 -13
- package/dist/animation/rig/rigSeat.js.map +1 -1
- package/dist/animation/rig/skinBinding.js +22 -14
- package/dist/animation/rig/skinBinding.js.map +1 -1
- package/dist/barrel/canvas.js +29 -28
- package/dist/barrel/interactions.js +33 -32
- package/dist/barrel/paths.js +11 -10
- package/dist/barrel/routing.js +49 -48
- package/dist/barrel/scene.js +10 -10
- package/dist/barrel/viewport.js +24 -21
- package/dist/canvas/Canvas.js +6 -17
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/CanvasView.js +34 -37
- package/dist/canvas/CanvasView.js.map +1 -1
- package/dist/canvas/CursorCoordsHud.js +3 -3
- package/dist/canvas/CursorCoordsHud.js.map +1 -1
- package/dist/canvas/DrawCanvas.js +3 -3
- package/dist/canvas/DrawCanvas.js.map +1 -1
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +24 -23
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js.map +1 -1
- package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js +40 -49
- package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js +297 -259
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
- package/dist/canvas/SceneCanvas/features.js +3 -24
- package/dist/canvas/SceneCanvas/features.js.map +1 -1
- package/dist/canvas/SceneCanvas/insertPreviewCommands.js +97 -0
- package/dist/canvas/SceneCanvas/insertPreviewCommands.js.map +1 -0
- package/dist/canvas/SceneCanvas/poseGeometry.js.map +1 -1
- package/dist/canvas/SceneCanvas/props.js.map +1 -1
- package/dist/canvas/SceneCanvas/scenePointQuery.js +50 -0
- package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -0
- package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js +52 -0
- package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js.map +1 -0
- package/dist/canvas/SceneCanvas/useActionTunings.js +17 -0
- package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -0
- package/dist/canvas/SceneCanvas/useCanvasCamera.js +37 -29
- package/dist/canvas/SceneCanvas/useCanvasCamera.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasDispatcher.js +11 -11
- package/dist/canvas/SceneCanvas/useCanvasDispatcher.js.map +1 -1
- package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js +45 -109
- package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +49 -41
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +64 -96
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas/worldPointAt.js +3 -4
- package/dist/canvas/SceneCanvas/worldPointAt.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +77 -80
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SceneViewCanvas.js +3 -3
- package/dist/canvas/SceneViewCanvas.js.map +1 -1
- package/dist/canvas/SurfaceCanvas.js +40 -0
- package/dist/canvas/SurfaceCanvas.js.map +1 -0
- package/dist/canvas/adapterDerivedPath.js +13 -13
- package/dist/canvas/adapterDerivedPath.js.map +1 -1
- package/dist/canvas/adapterPickSource.js +34 -0
- package/dist/canvas/adapterPickSource.js.map +1 -0
- package/dist/canvas/affordanceAt.js +1 -1
- package/dist/canvas/affordanceAt.js.map +1 -1
- package/dist/canvas/buildSceneLayer.js.map +1 -1
- package/dist/canvas/buildSceneTree.js +56 -34
- package/dist/canvas/buildSceneTree.js.map +1 -1
- package/dist/canvas/copiesAbove.js +27 -0
- package/dist/canvas/copiesAbove.js.map +1 -0
- package/dist/canvas/defaultDrawOne.js +24 -23
- package/dist/canvas/defaultDrawOne.js.map +1 -1
- package/dist/canvas/deps/editAnchors.js +45 -50
- package/dist/canvas/deps/editAnchors.js.map +1 -1
- package/dist/canvas/deps/hitTestArea.js +30 -15
- package/dist/canvas/deps/hitTestArea.js.map +1 -1
- package/dist/canvas/deps/insert.js +134 -100
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/layout.js +9 -15
- package/dist/canvas/deps/layout.js.map +1 -1
- package/dist/canvas/derivedPath.js +53 -29
- package/dist/canvas/derivedPath.js.map +1 -1
- package/dist/canvas/insertPreviewExtent.js +33 -31
- package/dist/canvas/insertPreviewExtent.js.map +1 -1
- package/dist/canvas/overChildren.js +29 -0
- package/dist/canvas/overChildren.js.map +1 -0
- package/dist/canvas/paintedCursorLayer.js.map +1 -1
- package/dist/canvas/pickReach.js +23 -0
- package/dist/canvas/pickReach.js.map +1 -0
- package/dist/canvas/pickWalk.js +51 -80
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/sceneAdapter.js +14 -35
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/sceneSlotWiring.js +11 -10
- package/dist/canvas/sceneSlotWiring.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +38 -44
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/canvas/useCanvasView.js +42 -44
- package/dist/canvas/useCanvasView.js.map +1 -1
- package/dist/canvas/useSceneAdapter.js +22 -0
- package/dist/canvas/useSceneAdapter.js.map +1 -0
- package/dist/canvas/useSurfaceInput.js +13 -16
- package/dist/canvas/useSurfaceInput.js.map +1 -1
- package/dist/canvas/viewRegistry.js +31 -23
- package/dist/canvas/viewRegistry.js.map +1 -1
- package/dist/canvas/wrapNodeOutput.js +7 -2
- package/dist/canvas/wrapNodeOutput.js.map +1 -1
- package/dist/clipboard.d.ts +1 -1
- package/dist/{clone-2a5hVbBd.d.ts → clone-C0eADtS0.d.ts} +13 -13
- package/dist/clone.d.ts +2 -2
- package/dist/core/geometry/poseDescriptor.js +49 -23
- package/dist/core/geometry/poseDescriptor.js.map +1 -1
- package/dist/core/geometry/regionHit.js +2 -2
- package/dist/core/geometry/regionHit.js.map +1 -1
- package/dist/core/geometry/targetFrame.js.map +1 -1
- package/dist/core/layers/children.js +1 -3
- package/dist/core/layers/children.js.map +1 -1
- package/dist/core/ops/create.js +2 -2
- package/dist/core/ops/create.js.map +1 -1
- package/dist/core/ops/delete.js +19 -23
- package/dist/core/ops/delete.js.map +1 -1
- package/dist/core/ops/reorder/index.js +3 -7
- package/dist/core/ops/reorder/index.js.map +1 -1
- package/dist/core/ops/reorder/setChildOrder.js +2 -4
- package/dist/core/ops/reorder/setChildOrder.js.map +1 -1
- package/dist/core/ops/reparent.js +0 -1
- package/dist/core/ops/reparent.js.map +1 -1
- package/dist/core/ops/slot.js +3 -6
- package/dist/core/ops/slot.js.map +1 -1
- package/dist/core/pages/fitViewToPage.js +15 -0
- package/dist/core/pages/fitViewToPage.js.map +1 -0
- package/dist/core/pages/pages.js +65 -0
- package/dist/core/pages/pages.js.map +1 -0
- package/dist/core/pages/pagesLayer.js +27 -0
- package/dist/core/pages/pagesLayer.js.map +1 -0
- package/dist/core/paint/meshKindLazy.js +2 -2
- package/dist/core/paint/meshKindLazy.js.map +1 -1
- package/dist/core/scene/NodeProperties.js.map +1 -1
- package/dist/core/scene/composeFrame.js.map +1 -1
- package/dist/core/scene/composePose.js +58 -20
- package/dist/core/scene/composePose.js.map +1 -1
- package/dist/core/scene/effectivePose.js +5 -1
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/historyFacade.js +19 -13
- package/dist/core/scene/historyFacade.js.map +1 -1
- package/dist/core/scene/nodeFnFields.js +7 -0
- package/dist/core/scene/nodeFnFields.js.map +1 -1
- package/dist/core/scene/outerOps.js +2 -1
- package/dist/core/scene/outerOps.js.map +1 -1
- package/dist/core/scene/reconcileSpecs.js +1 -1
- package/dist/core/scene/reconcileSpecs.js.map +1 -1
- package/dist/core/scene/sceneRecording.js +2 -1
- package/dist/core/scene/sceneRecording.js.map +1 -1
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/viewport/screenSlop.js.map +1 -1
- package/dist/core/viewport/useDecayLoop.js +28 -30
- package/dist/core/viewport/useDecayLoop.js.map +1 -1
- package/dist/core/viewport/useNodeOverlayFrame.js.map +1 -1
- package/dist/core/viewport/useViewAnimation.js +70 -69
- package/dist/core/viewport/useViewAnimation.js.map +1 -1
- package/dist/core/viewport/view.js +8 -5
- package/dist/core/viewport/view.js.map +1 -1
- package/dist/core/viewport/zoomAt.js +11 -2
- package/dist/core/viewport/zoomAt.js.map +1 -1
- package/dist/{cornerHandles-BEshc4z3.d.ts → cornerHandles-BSKbyuvQ.d.ts} +1 -1
- package/dist/features/paths/editablePath.js +24 -8
- package/dist/features/paths/editablePath.js.map +1 -1
- package/dist/features/paths/index.js +4 -4
- package/dist/features/paths/shapeGenerator.js +89 -0
- package/dist/features/paths/shapeGenerator.js.map +1 -0
- package/dist/features/paths/usePathEdit.js +13 -13
- package/dist/features/poseRun/usePoseRun.js +25 -25
- package/dist/features/poseRun/usePoseRun.js.map +1 -1
- package/dist/features/text/useSceneTextEdit.js +82 -64
- package/dist/features/text/useSceneTextEdit.js.map +1 -1
- package/dist/features/viewports/viewResolver.js +28 -24
- package/dist/features/viewports/viewResolver.js.map +1 -1
- package/dist/icons/SliceIcon.js +24 -0
- package/dist/icons/SliceIcon.js.map +1 -0
- package/dist/index.d.ts +956 -581
- package/dist/index.js +260 -251
- package/dist/interactions/actions/align/align.js +9 -9
- package/dist/interactions/actions/align/align.js.map +1 -1
- package/dist/interactions/actions/booleans/booleans.js +18 -18
- package/dist/interactions/actions/booleans/booleans.js.map +1 -1
- package/dist/interactions/actions/booleans/operands.js.map +1 -1
- package/dist/interactions/actions/clone/action.js +35 -30
- package/dist/interactions/actions/clone/action.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js +56 -59
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorHandles.js +42 -0
- package/dist/interactions/actions/defaults/anchorHandles.js.map +1 -0
- package/dist/interactions/actions/defaults/anchorJoin.js +92 -0
- package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -0
- package/dist/interactions/actions/defaults/bendSegment.js +46 -0
- package/dist/interactions/actions/defaults/bendSegment.js.map +1 -0
- package/dist/interactions/actions/defaults/copySubtree.js +1 -1
- package/dist/interactions/actions/defaults/copySubtree.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +32 -17
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/enterPathEdit.js +19 -7
- package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/icons/anchorIcons.js +75 -0
- package/dist/interactions/actions/defaults/icons/anchorIcons.js.map +1 -0
- package/dist/interactions/actions/defaults/icons/editIcons.js +35 -8
- package/dist/interactions/actions/defaults/icons/editIcons.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +16 -31
- package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js +28 -23
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/defaults/pinchZoom.js +29 -29
- package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
- package/dist/interactions/actions/defaults/segmentUnder.js +22 -0
- package/dist/interactions/actions/defaults/segmentUnder.js.map +1 -0
- package/dist/interactions/actions/defaults/viewportZoom.js +82 -79
- package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
- package/dist/interactions/actions/distribute/distribute.js +8 -8
- package/dist/interactions/actions/distribute/distribute.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/action.js +58 -55
- package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/preview.js +26 -0
- package/dist/interactions/actions/edit-anchors/preview.js.map +1 -0
- package/dist/interactions/actions/edit-anchors/symmetry.js +11 -0
- package/dist/interactions/actions/edit-anchors/symmetry.js.map +1 -0
- package/dist/interactions/actions/flip/action.js.map +1 -1
- package/dist/interactions/actions/geometryProjection.js.map +1 -1
- package/dist/interactions/actions/insert/action.js +37 -30
- package/dist/interactions/actions/insert/action.js.map +1 -1
- package/dist/interactions/actions/insert/frame.js +9 -2
- package/dist/interactions/actions/insert/frame.js.map +1 -1
- package/dist/interactions/actions/insert/repositionAction.js +31 -0
- package/dist/interactions/actions/insert/repositionAction.js.map +1 -0
- package/dist/interactions/actions/lasso-select/action.js +32 -32
- package/dist/interactions/actions/move/action.js +29 -25
- package/dist/interactions/actions/move/action.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/snapToContainer.js +12 -6
- package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
- package/dist/interactions/actions/move/capture.js +32 -23
- package/dist/interactions/actions/move/capture.js.map +1 -1
- package/dist/interactions/actions/move/frame.js +2 -14
- package/dist/interactions/actions/move/frame.js.map +1 -1
- package/dist/interactions/actions/move/session.js +1 -1
- package/dist/interactions/actions/move/session.js.map +1 -1
- package/dist/interactions/actions/move/translate.js.map +1 -1
- package/dist/interactions/actions/outlines/createOutlines.js.map +1 -1
- package/dist/interactions/actions/outlines/replaceWithPaths.js +13 -13
- package/dist/interactions/actions/outlines/replaceWithPaths.js.map +1 -1
- package/dist/interactions/actions/outlines/strokeRegion.js.map +1 -1
- package/dist/interactions/actions/pathCommands/cutJoin.js +102 -0
- package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -0
- package/dist/interactions/actions/pathCommands/livePathEdit.js +64 -0
- package/dist/interactions/actions/pathCommands/livePathEdit.js.map +1 -0
- package/dist/interactions/actions/pathCommands/pathNodes.js +99 -0
- package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -0
- package/dist/interactions/actions/pathCommands/simplifyOffset.js +42 -0
- package/dist/interactions/actions/pathCommands/simplifyOffset.js.map +1 -0
- package/dist/interactions/actions/resize/action.js +31 -20
- package/dist/interactions/actions/resize/action.js.map +1 -1
- package/dist/interactions/actions/resize/commit.js.map +1 -1
- package/dist/interactions/actions/resize/frame.js +29 -15
- package/dist/interactions/actions/resize/frame.js.map +1 -1
- package/dist/interactions/actions/resize/replay.js +47 -0
- package/dist/interactions/actions/resize/replay.js.map +1 -0
- package/dist/interactions/actions/resize/session.js +49 -35
- package/dist/interactions/actions/resize/session.js.map +1 -1
- package/dist/interactions/actions/resolveOverlays.js +2 -1
- package/dist/interactions/actions/resolveOverlays.js.map +1 -1
- package/dist/interactions/actions/rotate/action.js +40 -25
- package/dist/interactions/actions/rotate/action.js.map +1 -1
- package/dist/interactions/actions/rotate/frame.js +1 -1
- package/dist/interactions/actions/rotate/frame.js.map +1 -1
- package/dist/interactions/actions/rotate/replay.js +22 -0
- package/dist/interactions/actions/rotate/replay.js.map +1 -0
- package/dist/interactions/actions/rotate/session.js +42 -32
- package/dist/interactions/actions/rotate/session.js.map +1 -1
- package/dist/interactions/actions/transform-again/action.js +63 -0
- package/dist/interactions/actions/transform-again/action.js.map +1 -0
- package/dist/interactions/actions/transform-again/memory.js +35 -0
- package/dist/interactions/actions/transform-again/memory.js.map +1 -0
- package/dist/interactions/actions/useStandardActions.js +101 -83
- package/dist/interactions/actions/useStandardActions.js.map +1 -1
- package/dist/move.d.ts +4 -4
- package/dist/{originProjection-PPT88jZv.d.ts → originProjection-CoNVpITV.d.ts} +1 -1
- package/dist/resize.d.ts +3 -3
- package/dist/style.css +1 -1
- package/dist/tools/builtin/ellipse/useEllipseTool.js +16 -15
- package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
- package/dist/tools/builtin/line/useLineTool.js +16 -15
- package/dist/tools/builtin/line/useLineTool.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +111 -111
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/pickTopMostHit.js +11 -14
- package/dist/tools/builtin/pickTopMostHit.js.map +1 -1
- package/dist/tools/builtin/polygon/usePolygonTool.js +17 -15
- package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
- package/dist/tools/builtin/rect/useRectTool.js +15 -14
- package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
- package/dist/tools/builtin/select/adapterPointQuery.js +22 -18
- package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
- package/dist/tools/builtin/select/selectionContributions.js +1 -1
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js +2 -1
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/slice/useSliceTool.js +42 -40
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/tools/builtin/star/useStarTool.js +18 -16
- package/dist/tools/builtin/star/useStarTool.js.map +1 -1
- package/dist/tools/builtin/text/useTextTool.js +2 -2
- package/dist/tools/builtin/text/useTextTool.js.map +1 -1
- package/dist/{types-CIKDwlsP.d.ts → types-DBbbCy0i.d.ts} +22 -15
- package/dist/version.js +1 -1
- package/package.json +22 -22
- package/dist/canvas/surface.module.js +0 -6
- package/dist/canvas/surface.module.js.map +0 -1
- package/dist/canvas/surfaceBox.js +0 -16
- package/dist/canvas/surfaceBox.js.map +0 -1
- package/dist/layout/arrivals.js +0 -33
- package/dist/layout/arrivals.js.map +0 -1
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { createNodeMemoScope as e } from "../core/scene/nodeMemo.js";
|
|
2
2
|
import { resolveDerivedPath as t } from "../core/scene/derivedPath.js";
|
|
3
3
|
//#region src/canvas/adapterDerivedPath.ts
|
|
4
|
-
var n = /* @__PURE__ */ new WeakMap()
|
|
5
|
-
function
|
|
6
|
-
let
|
|
7
|
-
|
|
8
|
-
let
|
|
9
|
-
let t =
|
|
4
|
+
var n = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
function r(r) {
|
|
6
|
+
let i = n.get(r);
|
|
7
|
+
i === void 0 && (i = e(), n.set(r, i));
|
|
8
|
+
let a = i, o = (e) => typeof r.getWorldPose == "function" ? r.getWorldPose(e) : r.getPose(e), s = (e) => r.getChildren(e), c = (e) => {
|
|
9
|
+
let t = r.getNode(e);
|
|
10
10
|
if (t != null) return {
|
|
11
11
|
node: t,
|
|
12
|
-
pose:
|
|
12
|
+
pose: o(e),
|
|
13
13
|
get path() {
|
|
14
|
-
return
|
|
14
|
+
return l(e);
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
-
},
|
|
18
|
-
let n =
|
|
19
|
-
return !n || n.dependsOn === void 0 || n.derivePath === void 0 ? null : t(n,
|
|
17
|
+
}, l = (e) => {
|
|
18
|
+
let n = r.getNode(e);
|
|
19
|
+
return !n || n.dependsOn === void 0 || n.derivePath === void 0 ? null : t(n, c, s, a);
|
|
20
20
|
};
|
|
21
|
-
return
|
|
21
|
+
return l;
|
|
22
22
|
}
|
|
23
23
|
//#endregion
|
|
24
|
-
export {
|
|
24
|
+
export { r as adapterDerivedPathOf };
|
|
25
25
|
|
|
26
26
|
//# sourceMappingURL=adapterDerivedPath.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapterDerivedPath.js","names":["scopes","
|
|
1
|
+
{"version":3,"file":"adapterDerivedPath.js","names":["scopes","adapterDerivedPathOf","scope","adapter","createNodeMemoScope","slot","poseOf","id","childrenOf","depOf","node","pathOf","resolveDerivedPath"],"sources":["../../src/canvas/adapterDerivedPath.ts"],"sourcesContent":["/**\n * A derived node's path, resolved through an adapter with no scene in scope.\n *\n * A derivation reads its dependencies' nodes and where they are drawn, and\n * every adapter already answers both by id. Nothing pushes an invalidation\n * here, so freshness is the memo's own re-check of what the adapter answers.\n */\nimport type { Path } from '@weasel-js/geom';\nimport { resolveDerivedPath, type PathDerivingNode } from 'core/scene/derivedPath';\nimport { createNodeMemoScope, type NodeMemoScope } from 'core/scene/nodeMemo';\nimport type { DerivedDep, Node, NodeId } from 'core/scene/types';\n\n/** What deriving reads of an adapter. */\nexport interface DerivingAdapter<TPose> {\n getNode(id: string): unknown;\n getPose(id: string): TPose;\n /** Where the node is drawn, when the adapter composes poses. */\n getWorldPose?(id: string): TPose;\n getChildren(parentId: string | null): readonly string[];\n}\n\nconst scopes = new WeakMap<object, NodeMemoScope>();\n\n/**\n * The path the node `id` computes from its dependencies, or `null` when it\n * derives none. Memoized against `adapter`, so every reader of one adapter\n * shares a route.\n */\nexport function adapterDerivedPathOf<TPose>(\n adapter: DerivingAdapter<TPose>,\n): (id: string) => Path | null {\n let scope = scopes.get(adapter);\n if (scope === undefined) {\n scope = createNodeMemoScope();\n scopes.set(adapter, scope);\n }\n const slot = scope;\n const poseOf = (id: string): TPose =>\n (typeof adapter.getWorldPose === 'function' ? adapter.getWorldPose(id) : adapter.getPose(id));\n const childrenOf = (id: NodeId): readonly NodeId[] =>\n adapter.getChildren(id) as readonly NodeId[];\n const depOf = (id: NodeId): DerivedDep<TPose> | undefined => {\n const node = adapter.getNode(id);\n if (node === undefined || node === null) return undefined;\n return {\n node: node as Node<unknown, string, TPose>,\n pose: poseOf(id),\n get path(): Path | null { return pathOf(id); },\n };\n };\n const pathOf = (id: string): Path | null => {\n const node = adapter.getNode(id) as PathDerivingNode<TPose> | undefined | null;\n if (!node || node.dependsOn === undefined || node.derivePath === undefined) return null;\n return resolveDerivedPath(node, depOf, childrenOf, slot);\n };\n return pathOf;\n}\n"],"mappings":";;;AAqBA,IAAMA,oBAAS,IAAI,QAA+B;AAOlD,SAAgBC,EACd,GAC6B;CAC7B,IAAIC,IAAQF,EAAO,IAAIG,CAAO;CAC9B,AAAID,MAAU,KAAA,MACZ,IAAQE,EAAoB,GAC5BJ,EAAO,IAAIG,GAASD,CAAK;CAE3B,IAAMG,IAAOH,GACPI,KAAU,MACb,OAAOH,EAAQ,gBAAiB,aAAaA,EAAQ,aAAaI,CAAE,IAAIJ,EAAQ,QAAQI,CAAE,GACvFC,KAAc,MAClBL,EAAQ,YAAYI,CAAE,GAClBE,KAAS,MAA8C;EAC3D,IAAMC,IAAOP,EAAQ,QAAQI,CAAE;EAC/B,IAAIG,KAA+B,MACnC,OAAO;GACC;GACN,MAAMJ,EAAOC,CAAE;GACf,IAAI,OAAoB;IAAE,OAAOI,EAAOJ,CAAE;GAAG;EAC/C;CACF,GACMI,KAAU,MAA4B;EAC1C,IAAMD,IAAOP,EAAQ,QAAQI,CAAE;EAE/B,OADI,CAACG,KAAQA,EAAK,cAAc,KAAA,KAAaA,EAAK,eAAe,KAAA,IAAkB,OAC5EE,EAAmBF,GAAMD,GAAOD,GAAYH,CAAI;CACzD;CACA,OAAOM;AACT"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { adapterDerivedPathOf as e } from "./adapterDerivedPath.js";
|
|
2
|
+
//#region src/canvas/adapterPickSource.ts
|
|
3
|
+
function t(t, n = {}) {
|
|
4
|
+
let r = /* @__PURE__ */ new Map(), i = (e) => typeof t.getWorldPose == "function" ? t.getWorldPose(e) : t.getPose(e), a = e(t), o = (e) => ({
|
|
5
|
+
...t.getNode(e),
|
|
6
|
+
id: e,
|
|
7
|
+
pose: i(e)
|
|
8
|
+
});
|
|
9
|
+
return {
|
|
10
|
+
order() {
|
|
11
|
+
let e = [], n = /* @__PURE__ */ new Map(), i = (r, a) => {
|
|
12
|
+
for (let s of t.getChildren(r)) {
|
|
13
|
+
let t = o(s);
|
|
14
|
+
a && n.set(s, a), e.push(t), i(s, t);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
return i(null, null), r = n, e;
|
|
18
|
+
},
|
|
19
|
+
...n.alphaOf ? { alphaOf: n.alphaOf } : {},
|
|
20
|
+
...n.layerIsPainted ? { layerIsPainted: n.layerIsPainted } : {},
|
|
21
|
+
...n.planeOf ? { planeOf: n.planeOf } : {},
|
|
22
|
+
poseOf: (e) => i(e.id),
|
|
23
|
+
derivedPathOf: (e) => a(e.id),
|
|
24
|
+
parentsOf(e) {
|
|
25
|
+
let t = [], n = r.get(e.id), i = /* @__PURE__ */ new Set();
|
|
26
|
+
for (; n && !i.has(n.id);) i.add(n.id), t.push(n), n = r.get(n.id);
|
|
27
|
+
return t;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { t as adapterPickSource };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=adapterPickSource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapterPickSource.js","names":["adapterPickSource","parents","worldPose","adapter","id","derived","adapterDerivedPathOf","nodeOf","out","next","visit","childId","parentId","node","parent","gates","chain","cur","seen"],"sources":["../../src/canvas/adapterPickSource.ts"],"sourcesContent":["import { adapterDerivedPathOf } from './adapterDerivedPath';\nimport type { AdapterPickGates, PickCandidate, PickSource } from './pickWalk';\n\n/** The adapter surface the walk can reach without a `Scene`. */\ninterface PickAdapter<TPose> {\n /** Composed world pose, when the adapter has a composition strategy.\n * Falls back to `getPose`, which is the same value under the\n * absolute-pose model. */\n getWorldPose?(id: string): TPose;\n getPose(id: string): TPose;\n getNode(id: string): unknown;\n getChildren(parentId: string | null): readonly string[];\n}\n\n/**\n * A source over a bare `SelectAdapter`, for consumers with no `Scene`.\n *\n * The adapter's tree is walked depth-first, parents before children — the\n * same back-to-front order `renderOrderNodes` produces for a single layer.\n *\n * `gates` come from whichever view is picking, never from the adapter.\n */\nexport function adapterPickSource<TPose>(\n adapter: PickAdapter<TPose>,\n gates: AdapterPickGates = {},\n): PickSource<TPose> {\n // Rebuilt per walk: the tree may have changed between queries, and a walk\n // is the only thing that reads it.\n let parents = new Map<string, PickCandidate<TPose>>();\n\n // The adapter composes if it has a strategy; `getPose` alone is the node's\n // own pose, which is not where a framed child is drawn.\n const worldPose = (id: string): TPose =>\n (typeof adapter.getWorldPose === 'function' ? adapter.getWorldPose(id) : adapter.getPose(id));\n\n const derived = adapterDerivedPathOf(adapter);\n const nodeOf = (id: string): PickCandidate<TPose> => {\n const raw = adapter.getNode(id) as Partial<PickCandidate<TPose>>;\n return { ...raw, id, pose: worldPose(id) } as PickCandidate<TPose>;\n };\n\n return {\n order() {\n const out: PickCandidate<TPose>[] = [];\n const next = new Map<string, PickCandidate<TPose>>();\n const visit = (parentId: string | null, parent: PickCandidate<TPose> | null): void => {\n for (const childId of adapter.getChildren(parentId)) {\n const node = nodeOf(childId);\n if (parent) next.set(childId, parent);\n out.push(node);\n visit(childId, node);\n }\n };\n visit(null, null);\n parents = next;\n return out;\n },\n ...(gates.alphaOf ? { alphaOf: gates.alphaOf } : {}),\n ...(gates.layerIsPainted ? { layerIsPainted: gates.layerIsPainted } : {}),\n ...(gates.planeOf ? { planeOf: gates.planeOf } : {}),\n poseOf: (node) => worldPose(node.id),\n derivedPathOf: (node: PickCandidate<TPose>) => derived(node.id),\n parentsOf(node) {\n const chain: PickCandidate<TPose>[] = [];\n let cur = parents.get(node.id);\n const seen = new Set<string>();\n while (cur && !seen.has(cur.id)) {\n seen.add(cur.id);\n chain.push(cur);\n cur = parents.get(cur.id);\n }\n return chain;\n },\n };\n}\n"],"mappings":";;AAsBA,SAAgBA,EACd,GACA,IAA0B,CAAC,GACR;CAGnB,IAAIC,oBAAU,IAAI,IAAkC,GAI9CC,KAAa,MAChB,OAAOC,EAAQ,gBAAiB,aAAaA,EAAQ,aAAaC,CAAE,IAAID,EAAQ,QAAQC,CAAE,GAEvFC,IAAUC,EAAqBH,CAAO,GACtCI,KAAU,OAEP;EAAE,GADGJ,EAAQ,QAAQC,CAChB;EAAK;EAAI,MAAMF,EAAUE,CAAE;CAAE;CAG3C,OAAO;EACL,QAAQ;GACN,IAAMI,IAA8B,CAAC,GAC/BC,oBAAO,IAAI,IAAkC,GAC7CC,KAAS,GAAyB,MAA8C;IACpF,KAAK,IAAMC,KAAWR,EAAQ,YAAYS,CAAQ,GAAG;KACnD,IAAMC,IAAON,EAAOI,CAAO;KAG3B,AAFIG,KAAQL,EAAK,IAAIE,GAASG,CAAM,GACpCN,EAAI,KAAKK,CAAI,GACbH,EAAMC,GAASE,CAAI;IACrB;GACF;GAGA,OAFAH,EAAM,MAAM,IAAI,GAChB,IAAUD,GACHD;EACT;EACA,GAAIO,EAAM,UAAU,EAAE,SAASA,EAAM,QAAQ,IAAI,CAAC;EAClD,GAAIA,EAAM,iBAAiB,EAAE,gBAAgBA,EAAM,eAAe,IAAI,CAAC;EACvE,GAAIA,EAAM,UAAU,EAAE,SAASA,EAAM,QAAQ,IAAI,CAAC;EAClD,SAAS,MAASb,EAAUW,EAAK,EAAE;EACnC,gBAAgB,MAA+BR,EAAQQ,EAAK,EAAE;EAC9D,UAAU,GAAM;GACd,IAAMG,IAAgC,CAAC,GACnCC,IAAMhB,EAAQ,IAAIY,EAAK,EAAE,GACvBK,oBAAO,IAAI,IAAY;GAC7B,OAAOD,KAAO,CAACC,EAAK,IAAID,EAAI,EAAE,IAG5B,AAFAC,EAAK,IAAID,EAAI,EAAE,GACfD,EAAM,KAAKC,CAAG,GACd,IAAMhB,EAAQ,IAAIgB,EAAI,EAAE;GAE1B,OAAOD;EACT;CACF;AACF"}
|
|
@@ -44,7 +44,7 @@ function c(e) {
|
|
|
44
44
|
...t.targetId === void 0 ? {} : { targetIds: [t.targetId] },
|
|
45
45
|
...t.anchor === void 0 ? {} : { anchor: t.anchor },
|
|
46
46
|
...t.fixedPoint === void 0 ? {} : { fixedPoint: t.fixedPoint },
|
|
47
|
-
...e.
|
|
47
|
+
...e.cursor === void 0 ? {} : { cursor: e.cursor },
|
|
48
48
|
...e.binding.initialScratch === void 0 ? {} : { payload: e.binding.initialScratch }
|
|
49
49
|
};
|
|
50
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"affordanceAt.js","names":["chromeAffordances","sizes","targetSizesPx","opts","createRotationAffordance","rotateBandPx","rotationBadge","createCornerResizeAffordance","getAnchorState","createPathAnchorAffordances","anchorHitRadius","anchorStateFrom","dep","getRegistry","toCamera","id","buildAffordanceAt","affordances","wx","wy","hit","hitAffordanceRegions","getChromeState","getView","getIsVisible","toAffordanceHit","scratch","buildClassifyTarget","hitId","pickBest","getSelection","kindOfNode","layerAffordance","layerId","claim"],"sources":["../../src/canvas/affordanceAt.ts"],"sourcesContent":["/**\n * The dispatcher-side affordance classifier: answers \"what piece of selection\n * chrome is under this world point?\" by assembling the kit's affordances and\n * running the one shared walk (`hitAffordanceRegions`).\n */\n\nimport type { BodyClassification } from '@weasel-js/gestures';\nimport type { ChromeState } from 'core/selection/chromeState';\nimport type { EditAnchorsDep } from 'interactions/actions/depSchema';\nimport type { View } from 'core/viewport/view';\nimport type { AffordanceHit } from '@weasel-js/routing';\nimport type { Affordance, CommonAffordanceScratch, LayerHit } from 'affordances/types';\nimport { hitAffordanceRegions, type AffordanceRegionHit } from 'affordances/hitAffordanceRegions';\nimport { createCornerResizeAffordance } from 'affordances/cornerResize';\nimport { createRotationAffordance } from 'affordances/rotationHandle';\nimport { createPathAnchorAffordances, type AnchorState } from 'affordances/pathAnchors';\nimport { targetSizesPx } from 'core/device/targets';\n\nexport type { AnchorState };\n\n/** What decides the kit's chrome affordances — their sizes and which ones\n * exist. Shared by the hit-test and the slops overlay, which draws them. */\nexport interface ChromeAffordanceOptions {\n /** Pointer-size multiplier from the live `DeviceProfile`. Every default\n * below is resolved through {@link targetSizesPx} at this scale, so the\n * grab zone tracks the painted chrome on a coarse pointer. Default 1. */\n targetScale?: number;\n /** Corner-handle hit radius in screen px. Overrides the device-scaled default. */\n handleHitRadius?: number;\n /** Anchor / control hit radius in screen px. Overrides the device-scaled default. */\n anchorHitRadius?: number;\n /** Minimum rotate-band thickness outside the selection AABB, in screen px.\n * Overrides the device-scaled default. */\n rotateBandPx?: number;\n /** The rotate badge the selection overlay paints (`rotationBadgeOf`), so\n * it can be grabbed where it is drawn. Omitted or null, only the rotate\n * ring is hit-tested. */\n rotationBadge?: { distancePx: number; sizePx: number } | null;\n /** Anchor-editing state. When omitted, anchors aren't hit-tested. */\n getAnchorState?: () => AnchorState | null;\n}\n\nexport interface BuildAffordanceAtOptions extends ChromeAffordanceOptions {\n /** Live ChromeState at call time — current selection plus effective bounds,\n * including in-flight move/resize ghost poses. */\n getChromeState: () => ChromeState;\n /** Live view. Needed because region hit radii and the rotate band are\n * declared in screen pixels and resolved against the current scale. */\n getView: () => View;\n /** The affordances to walk, when the caller built them itself to share\n * them (`chromeAffordances`). Otherwise built from these options. */\n affordances?: readonly Affordance[];\n /** Chrome-caps resolver. Keeps the hit-test and the renderer agreeing on\n * which chrome is live (resize handles gated by\n * `'selection.resize-handles'`, rotation by `'selection.rotation-handle'`,\n * anchors by `'path-edit.anchors'`). Omitting defaults to always-visible. */\n getIsVisible?: () => (id: string) => boolean;\n}\n\n/**\n * The kit's selection chrome as affordances, bottom → top: rotate ring (and\n * badge), corner handles, then anchors and their controls — a corner handle\n * beats the rotate band it sits inside, and a control handle beats everything.\n */\nexport function chromeAffordances(opts: ChromeAffordanceOptions = {}): Affordance[] {\n const sizes = targetSizesPx(opts.targetScale);\n const {\n handleHitRadius = sizes.handle,\n anchorHitRadius = sizes.anchor,\n rotateBandPx = sizes.rotationDistance,\n getAnchorState,\n rotationBadge,\n } = opts;\n return [\n createRotationAffordance({\n bandPx: rotateBandPx,\n paint: null,\n ...(rotationBadge\n ? { handle: { distancePx: rotationBadge.distancePx, hitRadiusPx: rotationBadge.sizePx } }\n : {}),\n }),\n createCornerResizeAffordance({ handleHitRadius }),\n ...(getAnchorState\n ? createPathAnchorAffordances(getAnchorState, { hitRadius: anchorHitRadius })\n : []),\n ];\n}\n\n/**\n * Read anchor-editing state off a dep registry, for\n * {@link ChromeAffordanceOptions.getAnchorState}. Returns `null` while no\n * `editAnchors` dep is registered, so anchors are not hit-tested.\n */\nexport function anchorStateFrom(\n getRegistry: () => { get(name: 'editAnchors'): EditAnchorsDep | undefined } | null,\n /** The editable path in the world the hit-test is asked in. A node on a\n * parallax plane stores its path in the plane's world; the surface carries\n * it into the asking camera's. Default: unchanged. */\n toCamera?: (id: string, path: unknown) => unknown,\n): () => AnchorState | null {\n return () => {\n const dep = getRegistry()?.get('editAnchors');\n if (!dep) return null;\n return {\n editingId: dep.editingId ?? null,\n // The hit-test reads world-coord anchor positions, so route through\n // `getEditablePath`: both pose-as-polygon and `data.path` consumers\n // resolve correctly.\n getPose: (id) => (toCamera ? toCamera(id, dep.getEditablePath(id)) : dep.getEditablePath(id)),\n };\n };\n}\n\n/**\n * Build the `affordanceAt` thunk for `GestureDispatcherMounter`. The point is\n * **world-space**; callers convert from client coords first. Priority follows\n * the order of {@link chromeAffordances}, topmost first.\n */\nexport function buildAffordanceAt(\n opts: BuildAffordanceAtOptions,\n): (worldPoint: { x: number; y: number }) => AffordanceHit | null {\n const { getChromeState, getView, getIsVisible } = opts;\n const affordances = opts.affordances ?? chromeAffordances(opts);\n\n return function affordanceAt({ x: wx, y: wy }) {\n const hit = hitAffordanceRegions(\n affordances,\n wx,\n wy,\n getChromeState(),\n getView(),\n getIsVisible?.(),\n );\n return hit ? toAffordanceHit(hit) : null;\n };\n}\n\n/**\n * Region hit → `AffordanceHit`. The scratch fields named by\n * {@link CommonAffordanceScratch} are lifted onto the hit because the actions\n * that consume them read them there; the whole scratch rides along as `payload`.\n */\nfunction toAffordanceHit(hit: AffordanceRegionHit): AffordanceHit {\n const scratch = (hit.binding.initialScratch ?? {}) as CommonAffordanceScratch;\n return {\n kind: hit.region.hitKind ?? `${hit.affordanceId}:${hit.regionId}`,\n owner: hit.affordanceId,\n strength: 'shared',\n ...(scratch.targetId !== undefined ? { targetIds: [scratch.targetId] } : {}),\n ...(scratch.anchor !== undefined ? { anchor: scratch.anchor } : {}),\n ...(scratch.fixedPoint !== undefined ? { fixedPoint: scratch.fixedPoint } : {}),\n ...(hit.region.cursor !== undefined ? { cursor: hit.region.cursor } : {}),\n ...(hit.binding.initialScratch !== undefined ? { payload: hit.binding.initialScratch } : {}),\n } as AffordanceHit;\n}\n\n/**\n * Classify a world-space point against the current selection: `'empty'`,\n * `'selected-body'`, or `'unselected-body'`.\n *\n * @param kindOfNode - Resolver from a hit node id to its semantic kind.\n * Omitting it leaves `kind` undefined, which makes every `kind:` TargetSpec\n * form no-match.\n */\nexport function buildClassifyTarget(\n getSelection: () => readonly string[],\n pickBest: (wx: number, wy: number) => string | null,\n kindOfNode?: (id: string) => string | undefined,\n): (worldPoint: { x: number; y: number }) => BodyClassification {\n return function classifyTarget({ x: wx, y: wy }) {\n const hitId = pickBest(wx, wy);\n if (!hitId) return { body: 'empty' };\n const sel = getSelection();\n return {\n body: sel.includes(hitId) ? 'selected-body' : 'unselected-body',\n kind: kindOfNode?.(hitId),\n };\n };\n}\n\n/** A registered layer's claim on a point, as the affordance the dispatcher\n * routes: its kind names the layer, and it carries what the layer's hit-test\n * resolved. */\nexport function layerAffordance(layerId: string, claim: LayerHit): AffordanceHit {\n return {\n kind: `layer:${layerId}`,\n owner: layerId,\n strength: claim.strength ?? 'shared',\n ...(claim.claimedKinds !== undefined ? { claimedKinds: claim.claimedKinds } : {}),\n ...(claim.cursor !== undefined ? { cursor: claim.cursor } : {}),\n ...(claim.initialScratch !== undefined ? { payload: claim.initialScratch } : {}),\n };\n}\n"],"mappings":";;;;;;AAgEA,SAAgBA,EAAkB,IAAgC,CAAC,GAAiB;CAClF,IAAMC,IAAQC,EAAcC,EAAK,WAAW,GACtC,EACJ,qBAAkBF,EAAM,QACxB,qBAAkBA,EAAM,QACxB,kBAAeA,EAAM,kBACrB,mBACA,qBACEE;CACJ,OAAO;EACLC,EAAyB;GACvB,QAAQC;GACR,OAAO;GACP,GAAIC,IACA,EAAE,QAAQ;IAAE,YAAYA,EAAc;IAAY,aAAaA,EAAc;GAAO,EAAE,IACtF,CAAC;EACP,CAAC;EACDC,EAA6B,EAAE,mBAAgB,CAAC;EAChD,GAAIC,IACAC,EAA4BD,GAAgB,EAAE,WAAWE,EAAgB,CAAC,IAC1E,CAAC;CACP;AACF;AAOA,SAAgBC,EACd,GAIA,GAC0B;CAC1B,aAAa;EACX,IAAMC,IAAMC,EAAY,CAAC,EAAE,IAAI,aAAa;EAE5C,OADKD,IACE;GACL,WAAWA,EAAI,aAAa;GAI5B,UAAU,MAAQE,IAAWA,EAASC,GAAIH,EAAI,gBAAgBG,CAAE,CAAC,IAAIH,EAAI,gBAAgBG,CAAE;EAC7F,IAPiB;CAQnB;AACF;AAOA,SAAgBC,EACd,GACgE;CAChE,IAAM,EAAE,mBAAgB,YAAS,oBAAiBb,GAC5Cc,IAAcd,EAAK,eAAeH,EAAkBG,CAAI;CAE9D,OAAO,SAAsB,EAAE,GAAGe,GAAI,GAAGC,KAAM;EAC7C,IAAMC,IAAMC,EACVJ,GACAC,GACAC,GACAG,EAAe,GACfC,EAAQ,GACRC,IAAe,CACjB;EACA,OAAOJ,IAAMK,EAAgBL,CAAG,IAAI;CACtC;AACF;AAOA,SAASK,EAAgB,GAAyC;CAChE,IAAMC,IAAWN,EAAI,QAAQ,kBAAkB,CAAC;CAChD,OAAO;EACL,MAAMA,EAAI,OAAO,WAAW,GAAGA,EAAI,aAAa,GAAGA,EAAI;EACvD,OAAOA,EAAI;EACX,UAAU;EACV,GAAIM,EAAQ,aAAa,KAAA,IAAgD,CAAC,IAArC,EAAE,WAAW,CAACA,EAAQ,QAAQ,EAAE;EACrE,GAAIA,EAAQ,WAAW,KAAA,IAAyC,CAAC,IAA9B,EAAE,QAAQA,EAAQ,OAAO;EAC5D,GAAIA,EAAQ,eAAe,KAAA,IAAiD,CAAC,IAAtC,EAAE,YAAYA,EAAQ,WAAW;EACxE,GAAIN,EAAI,OAAO,WAAW,KAAA,IAA4C,CAAC,IAAjC,EAAE,QAAQA,EAAI,OAAO,OAAO;EAClE,GAAIA,EAAI,QAAQ,mBAAmB,KAAA,IAAsD,CAAC,IAA3C,EAAE,SAASA,EAAI,QAAQ,eAAe;CACvF;AACF;AAUA,SAAgBO,EACd,GACA,GACA,GAC8D;CAC9D,OAAO,SAAwB,EAAE,GAAGT,GAAI,GAAGC,KAAM;EAC/C,IAAMS,IAAQC,EAASX,GAAIC,CAAE;EAG7B,OAFKS,IAEE;GACL,MAFUE,EAEJ,CAAA,CAAI,SAASF,CAAK,IAAI,kBAAkB;GAC9C,MAAMG,IAAaH,CAAK;EAC1B,IALmB,EAAE,MAAM,QAAQ;CAMrC;AACF;AAKA,SAAgBI,EAAgB,GAAiB,GAAgC;CAC/E,OAAO;EACL,MAAM,SAASC;EACf,OAAOA;EACP,UAAUC,EAAM,YAAY;EAC5B,GAAIA,EAAM,iBAAiB,KAAA,IAAmD,CAAC,IAAxC,EAAE,cAAcA,EAAM,aAAa;EAC1E,GAAIA,EAAM,WAAW,KAAA,IAAuC,CAAC,IAA5B,EAAE,QAAQA,EAAM,OAAO;EACxD,GAAIA,EAAM,mBAAmB,KAAA,IAAgD,CAAC,IAArC,EAAE,SAASA,EAAM,eAAe;CAC3E;AACF"}
|
|
1
|
+
{"version":3,"file":"affordanceAt.js","names":["chromeAffordances","sizes","targetSizesPx","opts","createRotationAffordance","rotateBandPx","rotationBadge","createCornerResizeAffordance","getAnchorState","createPathAnchorAffordances","anchorHitRadius","anchorStateFrom","dep","getRegistry","toCamera","id","buildAffordanceAt","affordances","wx","wy","hit","hitAffordanceRegions","getChromeState","getView","getIsVisible","toAffordanceHit","scratch","buildClassifyTarget","hitId","pickBest","getSelection","kindOfNode","layerAffordance","layerId","claim"],"sources":["../../src/canvas/affordanceAt.ts"],"sourcesContent":["/**\n * The dispatcher-side affordance classifier: answers \"what piece of selection\n * chrome is under this world point?\" by assembling the kit's affordances and\n * running the one shared walk (`hitAffordanceRegions`).\n */\n\nimport type { BodyClassification } from '@weasel-js/gestures';\nimport type { ChromeState } from 'core/selection/chromeState';\nimport type { EditAnchorsDep } from 'interactions/actions/depSchema';\nimport type { View } from 'core/viewport/view';\nimport type { AffordanceHit } from '@weasel-js/routing';\nimport type { Affordance, CommonAffordanceScratch, LayerHit } from 'affordances/types';\nimport { hitAffordanceRegions, type AffordanceRegionHit } from 'affordances/hitAffordanceRegions';\nimport { createCornerResizeAffordance } from 'affordances/cornerResize';\nimport { createRotationAffordance } from 'affordances/rotationHandle';\nimport { createPathAnchorAffordances, type AnchorState } from 'affordances/pathAnchors';\nimport { targetSizesPx } from 'core/device/targets';\n\nexport type { AnchorState };\n\n/** What decides the kit's chrome affordances — their sizes and which ones\n * exist. Shared by the hit-test and the slops overlay, which draws them. */\nexport interface ChromeAffordanceOptions {\n /** Pointer-size multiplier from the live `DeviceProfile`. Every default\n * below is resolved through {@link targetSizesPx} at this scale, so the\n * grab zone tracks the painted chrome on a coarse pointer. Default 1. */\n targetScale?: number;\n /** Corner-handle hit radius in screen px. Overrides the device-scaled default. */\n handleHitRadius?: number;\n /** Anchor / control hit radius in screen px. Overrides the device-scaled default. */\n anchorHitRadius?: number;\n /** Minimum rotate-band thickness outside the selection AABB, in screen px.\n * Overrides the device-scaled default. */\n rotateBandPx?: number;\n /** The rotate badge the selection overlay paints (`rotationBadgeOf`), so\n * it can be grabbed where it is drawn. Omitted or null, only the rotate\n * ring is hit-tested. */\n rotationBadge?: { distancePx: number; sizePx: number } | null;\n /** Anchor-editing state. When omitted, anchors aren't hit-tested. */\n getAnchorState?: () => AnchorState | null;\n}\n\nexport interface BuildAffordanceAtOptions extends ChromeAffordanceOptions {\n /** Live ChromeState at call time — current selection plus effective bounds,\n * including in-flight move/resize ghost poses. */\n getChromeState: () => ChromeState;\n /** Live view. Needed because region hit radii and the rotate band are\n * declared in screen pixels and resolved against the current scale. */\n getView: () => View;\n /** The affordances to walk, when the caller built them itself to share\n * them (`chromeAffordances`). Otherwise built from these options. */\n affordances?: readonly Affordance[];\n /** Chrome-caps resolver. Keeps the hit-test and the renderer agreeing on\n * which chrome is live (resize handles gated by\n * `'selection.resize-handles'`, rotation by `'selection.rotation-handle'`,\n * anchors by `'path-edit.anchors'`). Omitting defaults to always-visible. */\n getIsVisible?: () => (id: string) => boolean;\n}\n\n/**\n * The kit's selection chrome as affordances, bottom → top: rotate ring (and\n * badge), corner handles, then anchors and their controls — a corner handle\n * beats the rotate band it sits inside, and a control handle beats everything.\n */\nexport function chromeAffordances(opts: ChromeAffordanceOptions = {}): Affordance[] {\n const sizes = targetSizesPx(opts.targetScale);\n const {\n handleHitRadius = sizes.handle,\n anchorHitRadius = sizes.anchor,\n rotateBandPx = sizes.rotationDistance,\n getAnchorState,\n rotationBadge,\n } = opts;\n return [\n createRotationAffordance({\n bandPx: rotateBandPx,\n paint: null,\n ...(rotationBadge\n ? { handle: { distancePx: rotationBadge.distancePx, hitRadiusPx: rotationBadge.sizePx } }\n : {}),\n }),\n createCornerResizeAffordance({ handleHitRadius }),\n ...(getAnchorState\n ? createPathAnchorAffordances(getAnchorState, { hitRadius: anchorHitRadius })\n : []),\n ];\n}\n\n/**\n * Read anchor-editing state off a dep registry, for\n * {@link ChromeAffordanceOptions.getAnchorState}. Returns `null` while no\n * `editAnchors` dep is registered, so anchors are not hit-tested.\n */\nexport function anchorStateFrom(\n getRegistry: () => { get(name: 'editAnchors'): EditAnchorsDep | undefined } | null,\n /** The editable path in the world the hit-test is asked in. A node on a\n * parallax plane stores its path in the plane's world; the surface carries\n * it into the asking camera's. Default: unchanged. */\n toCamera?: (id: string, path: unknown) => unknown,\n): () => AnchorState | null {\n return () => {\n const dep = getRegistry()?.get('editAnchors');\n if (!dep) return null;\n return {\n editingId: dep.editingId ?? null,\n // The hit-test reads world-coord anchor positions, so route through\n // `getEditablePath`: both pose-as-polygon and `data.path` consumers\n // resolve correctly.\n getPose: (id) => (toCamera ? toCamera(id, dep.getEditablePath(id)) : dep.getEditablePath(id)),\n };\n };\n}\n\n/**\n * Build the `affordanceAt` thunk for `GestureDispatcherMounter`. The point is\n * **world-space**; callers convert from client coords first. Priority follows\n * the order of {@link chromeAffordances}, topmost first.\n */\nexport function buildAffordanceAt(\n opts: BuildAffordanceAtOptions,\n): (worldPoint: { x: number; y: number }) => AffordanceHit | null {\n const { getChromeState, getView, getIsVisible } = opts;\n const affordances = opts.affordances ?? chromeAffordances(opts);\n\n return function affordanceAt({ x: wx, y: wy }) {\n const hit = hitAffordanceRegions(\n affordances,\n wx,\n wy,\n getChromeState(),\n getView(),\n getIsVisible?.(),\n );\n return hit ? toAffordanceHit(hit) : null;\n };\n}\n\n/**\n * Region hit → `AffordanceHit`. The scratch fields named by\n * {@link CommonAffordanceScratch} are lifted onto the hit because the actions\n * that consume them read them there; the whole scratch rides along as `payload`.\n */\nfunction toAffordanceHit(hit: AffordanceRegionHit): AffordanceHit {\n const scratch = (hit.binding.initialScratch ?? {}) as CommonAffordanceScratch;\n return {\n kind: hit.region.hitKind ?? `${hit.affordanceId}:${hit.regionId}`,\n owner: hit.affordanceId,\n strength: 'shared',\n ...(scratch.targetId !== undefined ? { targetIds: [scratch.targetId] } : {}),\n ...(scratch.anchor !== undefined ? { anchor: scratch.anchor } : {}),\n ...(scratch.fixedPoint !== undefined ? { fixedPoint: scratch.fixedPoint } : {}),\n ...(hit.cursor !== undefined ? { cursor: hit.cursor } : {}),\n ...(hit.binding.initialScratch !== undefined ? { payload: hit.binding.initialScratch } : {}),\n } as AffordanceHit;\n}\n\n/**\n * Classify a world-space point against the current selection: `'empty'`,\n * `'selected-body'`, or `'unselected-body'`.\n *\n * @param kindOfNode - Resolver from a hit node id to its semantic kind.\n * Omitting it leaves `kind` undefined, which makes every `kind:` TargetSpec\n * form no-match.\n */\nexport function buildClassifyTarget(\n getSelection: () => readonly string[],\n pickBest: (wx: number, wy: number) => string | null,\n kindOfNode?: (id: string) => string | undefined,\n): (worldPoint: { x: number; y: number }) => BodyClassification {\n return function classifyTarget({ x: wx, y: wy }) {\n const hitId = pickBest(wx, wy);\n if (!hitId) return { body: 'empty' };\n const sel = getSelection();\n return {\n body: sel.includes(hitId) ? 'selected-body' : 'unselected-body',\n kind: kindOfNode?.(hitId),\n };\n };\n}\n\n/** A registered layer's claim on a point, as the affordance the dispatcher\n * routes: its kind names the layer, and it carries what the layer's hit-test\n * resolved. */\nexport function layerAffordance(layerId: string, claim: LayerHit): AffordanceHit {\n return {\n kind: `layer:${layerId}`,\n owner: layerId,\n strength: claim.strength ?? 'shared',\n ...(claim.claimedKinds !== undefined ? { claimedKinds: claim.claimedKinds } : {}),\n ...(claim.cursor !== undefined ? { cursor: claim.cursor } : {}),\n ...(claim.initialScratch !== undefined ? { payload: claim.initialScratch } : {}),\n };\n}\n"],"mappings":";;;;;;AAgEA,SAAgBA,EAAkB,IAAgC,CAAC,GAAiB;CAClF,IAAMC,IAAQC,EAAcC,EAAK,WAAW,GACtC,EACJ,qBAAkBF,EAAM,QACxB,qBAAkBA,EAAM,QACxB,kBAAeA,EAAM,kBACrB,mBACA,qBACEE;CACJ,OAAO;EACLC,EAAyB;GACvB,QAAQC;GACR,OAAO;GACP,GAAIC,IACA,EAAE,QAAQ;IAAE,YAAYA,EAAc;IAAY,aAAaA,EAAc;GAAO,EAAE,IACtF,CAAC;EACP,CAAC;EACDC,EAA6B,EAAE,mBAAgB,CAAC;EAChD,GAAIC,IACAC,EAA4BD,GAAgB,EAAE,WAAWE,EAAgB,CAAC,IAC1E,CAAC;CACP;AACF;AAOA,SAAgBC,EACd,GAIA,GAC0B;CAC1B,aAAa;EACX,IAAMC,IAAMC,EAAY,CAAC,EAAE,IAAI,aAAa;EAE5C,OADKD,IACE;GACL,WAAWA,EAAI,aAAa;GAI5B,UAAU,MAAQE,IAAWA,EAASC,GAAIH,EAAI,gBAAgBG,CAAE,CAAC,IAAIH,EAAI,gBAAgBG,CAAE;EAC7F,IAPiB;CAQnB;AACF;AAOA,SAAgBC,EACd,GACgE;CAChE,IAAM,EAAE,mBAAgB,YAAS,oBAAiBb,GAC5Cc,IAAcd,EAAK,eAAeH,EAAkBG,CAAI;CAE9D,OAAO,SAAsB,EAAE,GAAGe,GAAI,GAAGC,KAAM;EAC7C,IAAMC,IAAMC,EACVJ,GACAC,GACAC,GACAG,EAAe,GACfC,EAAQ,GACRC,IAAe,CACjB;EACA,OAAOJ,IAAMK,EAAgBL,CAAG,IAAI;CACtC;AACF;AAOA,SAASK,EAAgB,GAAyC;CAChE,IAAMC,IAAWN,EAAI,QAAQ,kBAAkB,CAAC;CAChD,OAAO;EACL,MAAMA,EAAI,OAAO,WAAW,GAAGA,EAAI,aAAa,GAAGA,EAAI;EACvD,OAAOA,EAAI;EACX,UAAU;EACV,GAAIM,EAAQ,aAAa,KAAA,IAAgD,CAAC,IAArC,EAAE,WAAW,CAACA,EAAQ,QAAQ,EAAE;EACrE,GAAIA,EAAQ,WAAW,KAAA,IAAyC,CAAC,IAA9B,EAAE,QAAQA,EAAQ,OAAO;EAC5D,GAAIA,EAAQ,eAAe,KAAA,IAAiD,CAAC,IAAtC,EAAE,YAAYA,EAAQ,WAAW;EACxE,GAAIN,EAAI,WAAW,KAAA,IAAqC,CAAC,IAA1B,EAAE,QAAQA,EAAI,OAAO;EACpD,GAAIA,EAAI,QAAQ,mBAAmB,KAAA,IAAsD,CAAC,IAA3C,EAAE,SAASA,EAAI,QAAQ,eAAe;CACvF;AACF;AAUA,SAAgBO,EACd,GACA,GACA,GAC8D;CAC9D,OAAO,SAAwB,EAAE,GAAGT,GAAI,GAAGC,KAAM;EAC/C,IAAMS,IAAQC,EAASX,GAAIC,CAAE;EAG7B,OAFKS,IAEE;GACL,MAFUE,EAEJ,CAAA,CAAI,SAASF,CAAK,IAAI,kBAAkB;GAC9C,MAAMG,IAAaH,CAAK;EAC1B,IALmB,EAAE,MAAM,QAAQ;CAMrC;AACF;AAKA,SAAgBI,EAAgB,GAAiB,GAAgC;CAC/E,OAAO;EACL,MAAM,SAASC;EACf,OAAOA;EACP,UAAUC,EAAM,YAAY;EAC5B,GAAIA,EAAM,iBAAiB,KAAA,IAAmD,CAAC,IAAxC,EAAE,cAAcA,EAAM,aAAa;EAC1E,GAAIA,EAAM,WAAW,KAAA,IAAuC,CAAC,IAA5B,EAAE,QAAQA,EAAM,OAAO;EACxD,GAAIA,EAAM,mBAAmB,KAAA,IAAgD,CAAC,IAArC,EAAE,SAASA,EAAM,eAAe;CAC3E;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildSceneLayer.js","names":["withAdapterDerivedPaths","drawOne","cfg","adapter","pathOf","adapterDerivedPathOf","id","obj","toPose","pose","view","ctx","buildSceneLayer","postProcess","finish","cullDrawCommands","cmds","viewToMat3","dims","paintBounds","culledIn","paintMissesView","slot","debugSink","debugSinkFrom","data","hidden","hideIds","a","filteredDrawOne","v","b","boundsOfFn","ox","oy","wrapNodeOutput","hierarchicalAdapter","tree","buildSceneTree","derivedPathOf","objects","children","culled","wrapped","cmd","buildSceneLayers","sceneLayerIds","l","sceneLayerKey"],"sources":["../../src/canvas/buildSceneLayer.ts"],"sourcesContent":["/**\n * The scene slot: an adapter's tree as render layers, one per scene layer.\n */\nimport type { Path } from '@weasel-js/geom';\nimport type { Dims, RenderLayer } from 'core/layers/render';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { View } from 'core/viewport/view';\nimport { cullDrawCommands, viewToMat3, type DrawCommand } from '@weasel-js/renderer';\nimport { adapterDerivedPathOf } from './adapterDerivedPath';\nimport { buildSceneTree, type HierarchicalAdapter } from './buildSceneTree';\nimport { debugSinkFrom } from './drawEnvelope';\nimport type { NodePaintCtx } from './NodeShape';\nimport { paintMissesView, type PaintBoundsFn } from './paintCull';\nimport { sceneLayerKey } from './sceneLayerPaint';\nimport { wrapNodeOutput } from './wrapNodeOutput';\n\n/** Scene slot config — describes how to draw one object with its effective pose. */\nexport interface SceneSlotConfig<TNode extends { id: string }, TPose> {\n /** Paint these, in this order, in place of the adapter's tree: one flat run with no\n * scene layers, no container clips, and no pose composition. */\n objects?: TNode[];\n /** Project an object to its committed pose. Defaults to `adapter.getPose(obj.id)`. */\n toPose?: (obj: TNode) => TPose;\n /** Draw a single object as a `DrawCommand` tree. `ctx` carries a derived\n * node's path: `<SceneCanvas>` resolves it from the scene, and\n * `buildSceneLayer` called without one from its adapter. */\n drawOne: (obj: TNode, pose: TPose, view: View, ctx?: NodePaintCtx) => DrawCommand[];\n /** The path a container derives from its dependencies' poses, for the clip\n * it imposes on its subtree — `drawOne`'s `ctx` carries the derived path a\n * node *paints*, this one the clip it *imposes*. A scene-aware caller\n * supplies it; left out, both are resolved off the adapter. */\n derivedPathOf?: (obj: TNode, pose: TPose) => Path | null;\n /** Default ghost alpha for the move-overlay slot. Default 0.85. */\n ghostAlpha?: number;\n /** Per-id alpha multiplier. A node's emitted `DrawCommand[]` is wrapped in a\n * `{ kind: 'group', alpha }` when the returned value is not 1. */\n alphaFor?: (id: string) => number;\n /**\n * Post-processor for the scene slot's emitted commands: called with the final\n * world-space `DrawCommand[]` each time a scene canvas layer draws (after per-node\n * rotation wrapping and `alphaFor`), once per layer when the slot is split into\n * `scene:<layerId>` layers. Its return value replaces the array handed to the renderer.\n *\n * Commands are world-space — `drawLayers` applies the view transform afterward. Clip\n * nesting is capped at 7 levels; a wrapping group with `clip` consumes one. The\n * move-overlay ghost (drag preview) is not post-processed.\n */\n postProcess?: (cmds: DrawCommand[], view: View, dims: Dims) => DrawCommand[];\n /**\n * Skip what cannot reach the view. A node `paintBounds` places outside the\n * `dims` rectangle under `view` is not painted at all; then, after\n * `postProcess`, `cullDrawCommands` drops any remaining command that cannot\n * reach it — see there for what it can and cannot bound. So `postProcess`\n * sees an empty group where a culled node would have painted.\n *\n * Off by default because it makes the layer's world-space output depend on\n * the view: anything that draws this layer once and shows the result under\n * another view — a cache keyed without `view`, a consumer re-wrapping the\n * commands — would show the first view's cull.\n */\n cull?: boolean;\n /**\n * Where `drawOne`'s output for a node can reach, in the frame it paints in\n * — before the pose rotation the slot wraps it in. `null` for a node it\n * cannot bound cheaply. Read only under `cull`, which skips `drawOne` for a\n * node whose box misses the view, so it must enclose everything `drawOne`\n * paints: a box too small makes a node vanish before it leaves the screen.\n *\n * `<SceneCanvas>` supplies `defaultPaintBounds` while `drawOne` is\n * `defaultDrawOne`. Omit it with any other `drawOne` and culling only\n * trims commands after painting.\n */\n paintBounds?: PaintBoundsFn<TNode, TPose>;\n}\n\n/**\n * `cfg`'s `drawOne` and clip lookup, resolving derived paths off the adapter\n * when nothing scene-aware filled `derivedPathOf` in.\n */\nfunction withAdapterDerivedPaths<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n toPose: (obj: TNode) => TPose,\n): Pick<SceneSlotConfig<TNode, TPose>, 'drawOne' | 'derivedPathOf'> {\n const drawOne = cfg.drawOne;\n if (cfg.derivedPathOf !== undefined || !drawOne || !adapter) return cfg;\n const pathOf = adapterDerivedPathOf<TPose>(cfg.toPose === undefined ? adapter : {\n getNode: (id) => adapter.getNode(id),\n getPose: (id) => {\n const obj = adapter.getNode(id);\n return obj ? toPose(obj) : adapter.getPose(id);\n },\n getChildren: (id) => adapter.getChildren(id),\n });\n return {\n derivedPathOf: (obj) => pathOf(obj.id),\n drawOne: (obj, pose, view, ctx) => ((obj as { dependsOn?: unknown }).dependsOn === undefined\n ? drawOne(obj, pose, view, ctx)\n : drawOne(obj, pose, view, { ...ctx, derivedPath: pathOf(obj.id) })),\n };\n}\n\n/**\n * Build the scene layer, which draws committed poses. `hideIds()` skips the committed paint\n * of ids the active tool is currently ghosting so the source doesn't show through. With a\n * debug sink on its draw envelope, it records each node's bounds and origin there.\n */\nexport function buildSceneLayer<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n /** When set, the returned layer carries this `id` and paints ONLY the named\n * scene layer (the rest are walked for ancestor clips). Omit for the single\n * bundled `scene` layer. */\n slot?: { id: string; forLayer: string },\n): RenderLayer<unknown> {\n const toPose =\n cfg.toPose ??\n ((obj: TNode) => (adapter ? adapter.getPose(obj.id) : (obj as unknown as TPose)));\n const { drawOne, derivedPathOf } = withAdapterDerivedPaths(cfg, adapter, toPose);\n const postProcess = cfg.postProcess;\n const finish = cfg.cull\n ? (cmds: DrawCommand[], view: View, dims: Dims): DrawCommand[] =>\n cullDrawCommands(cmds, viewToMat3(view), { x: 0, y: 0, width: dims.width, height: dims.height })\n : (cmds: DrawCommand[]): DrawCommand[] => cmds;\n const paintBounds = cfg.cull ? cfg.paintBounds : undefined;\n const culledIn = (view: View, dims: Dims) => paintBounds\n ? paintMissesView(paintBounds, view, { x: 0, y: 0, width: dims.width, height: dims.height })\n : undefined;\n return {\n id: slot?.id ?? 'scene',\n label: slot ? `Scene: ${slot.forLayer}` : 'Scene',\n draw: (data, view, dims) => {\n const debugSink = debugSinkFrom(data);\n const hidden = hideIds();\n const a = adapter;\n if (a && cfg.objects === undefined && drawOne) {\n const filteredDrawOne = (obj: TNode, pose: TPose, v: View): DrawCommand[] => {\n if (hidden && hidden.has(obj.id)) return [];\n const cmds = drawOne(obj, pose, v);\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, b);\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n return wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n };\n // Honor cfg.toPose on the hierarchical path by shimming getPose on the\n // adapter so buildSceneTree routes through it instead of the raw adapter.\n const hierarchicalAdapter = cfg.toPose\n ? {\n ...a,\n getPose: (id: string) => {\n const obj = a.getNode(id);\n return obj ? toPose(obj) : a.getPose(id);\n },\n }\n : a;\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n const tree = buildSceneTree(\n hierarchicalAdapter as unknown as Parameters<typeof buildSceneTree>[0],\n filteredDrawOne as unknown as Parameters<typeof buildSceneTree>[1],\n view,\n slot?.forLayer,\n derivedPathOf as Parameters<typeof buildSceneTree>[4],\n culledIn(view, dims) as Parameters<typeof buildSceneTree>[5],\n );\n return finish(postProcess ? postProcess(tree, view, dims) : tree, view, dims);\n }\n // `cfg.objects`: the caller's own list, which has no scene layers to split by.\n const objects = cfg.objects ?? [];\n const children: DrawCommand[] = [];\n const culled = culledIn(view, dims);\n for (const obj of objects) {\n if (hidden && hidden.has(obj.id)) continue;\n const pose: TPose = toPose(obj);\n if (drawOne && !culled?.(obj, pose)) {\n const cmds = drawOne(obj, pose, view);\n const wrapped = wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n for (const cmd of wrapped) children.push(cmd);\n }\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, { x: b.x, y: b.y, width: b.width, height: b.height });\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n }\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n return finish(postProcess ? postProcess(children, view, dims) : children, view, dims);\n },\n };\n}\n\n/**\n * The scene slot as one canvas layer **per scene layer** — keyed `scene:<layerId>` — so\n * consumers can interleave custom layers *between* scene layers via the standard slot\n * anchoring (`before: 'scene:plantings'`). A single `scene` layer when `cfg.objects` replaces\n * the tree, or the adapter declares no layers.\n */\nexport function buildSceneLayers<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n): Array<{ key: string; layer: RenderLayer<unknown> }> {\n const sceneLayerIds =\n cfg.objects === undefined && adapter ? adapter.getLayers().map((l) => l.id) : null;\n if (!sceneLayerIds || sceneLayerIds.length === 0) {\n return [{ key: 'scene', layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds) }];\n }\n return sceneLayerIds.map((id) => ({\n key: sceneLayerKey(id),\n layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds, {\n id: sceneLayerKey(id),\n forLayer: id,\n }),\n }));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"buildSceneLayer.js","names":["withAdapterDerivedPaths","drawOne","cfg","adapter","pathOf","adapterDerivedPathOf","id","obj","toPose","pose","view","ctx","buildSceneLayer","postProcess","finish","cullDrawCommands","cmds","viewToMat3","dims","paintBounds","culledIn","paintMissesView","slot","debugSink","debugSinkFrom","data","hidden","hideIds","a","filteredDrawOne","v","b","boundsOfFn","ox","oy","wrapNodeOutput","hierarchicalAdapter","tree","buildSceneTree","derivedPathOf","objects","children","culled","wrapped","cmd","buildSceneLayers","sceneLayerIds","l","sceneLayerKey"],"sources":["../../src/canvas/buildSceneLayer.ts"],"sourcesContent":["/**\n * The scene slot: an adapter's tree as render layers, one per scene layer.\n */\nimport type { Path } from '@weasel-js/geom';\nimport type { Dims, RenderLayer } from 'core/layers/render';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { View } from 'core/viewport/view';\nimport { cullDrawCommands, viewToMat3, type DrawCommand } from '@weasel-js/renderer';\nimport { adapterDerivedPathOf } from './adapterDerivedPath';\nimport { buildSceneTree, type HierarchicalAdapter } from './buildSceneTree';\nimport { debugSinkFrom } from './drawEnvelope';\nimport type { NodePaintCtx } from './NodeShape';\nimport { paintMissesView, type PaintBoundsFn } from './paintCull';\nimport { sceneLayerKey } from './sceneLayerPaint';\nimport { wrapNodeOutput } from './wrapNodeOutput';\n\n/** Scene slot config — describes how to draw one object with its effective pose. */\nexport interface SceneSlotConfig<TNode extends { id: string }, TPose> {\n /** Paint these, in this order, in place of the adapter's tree: one flat run with no\n * scene layers, no container clips, and no pose composition. */\n objects?: TNode[];\n /** The pose an object is painted at. Defaults to `adapter.getPose(obj.id)`. `<SceneCanvas>`\n * hands it each node as the scene shows it, its `pose` an override's or a derived one,\n * and carries an override's `path` into the box it answers; return a pose placed from\n * `obj.pose`, or a drag in flight and a rig's motion do not show in this view. */\n toPose?: (obj: TNode) => TPose;\n /** Draw a single object as a `DrawCommand` tree. `ctx` carries a derived\n * node's path: `<SceneCanvas>` resolves it from the scene, and\n * `buildSceneLayer` called without one from its adapter. */\n drawOne: (obj: TNode, pose: TPose, view: View, ctx?: NodePaintCtx) => DrawCommand[];\n /** The path a container derives from its dependencies' poses, for the clip\n * it imposes on its subtree — `drawOne`'s `ctx` carries the derived path a\n * node *paints*, this one the clip it *imposes*. A scene-aware caller\n * supplies it; left out, both are resolved off the adapter. */\n derivedPathOf?: (obj: TNode, pose: TPose) => Path | null;\n /** Default ghost alpha for the move-overlay slot. Default 0.85. */\n ghostAlpha?: number;\n /** Per-id alpha multiplier. A node's emitted `DrawCommand[]` is wrapped in a\n * `{ kind: 'group', alpha }` when the returned value is not 1. */\n alphaFor?: (id: string) => number;\n /**\n * Post-processor for the scene slot's emitted commands: called with the final\n * world-space `DrawCommand[]` each time a scene canvas layer draws (after per-node\n * rotation wrapping and `alphaFor`), once per layer when the slot is split into\n * `scene:<layerId>` layers. Its return value replaces the array handed to the renderer.\n *\n * Commands are world-space — `drawLayers` applies the view transform afterward. Clip\n * nesting is capped at 7 levels; a wrapping group with `clip` consumes one. The\n * move-overlay ghost (drag preview) is not post-processed.\n */\n postProcess?: (cmds: DrawCommand[], view: View, dims: Dims) => DrawCommand[];\n /**\n * Skip what cannot reach the view. A node `paintBounds` places outside the\n * `dims` rectangle under `view` is not painted at all; then, after\n * `postProcess`, `cullDrawCommands` drops any remaining command that cannot\n * reach it — see there for what it can and cannot bound. So `postProcess`\n * sees an empty group where a culled node would have painted.\n *\n * Off by default because it makes the layer's world-space output depend on\n * the view: anything that draws this layer once and shows the result under\n * another view — a cache keyed without `view`, a consumer re-wrapping the\n * commands — would show the first view's cull.\n */\n cull?: boolean;\n /**\n * Where `drawOne`'s output for a node can reach, in the frame it paints in\n * — before the pose rotation the slot wraps it in. `null` for a node it\n * cannot bound cheaply. Read only under `cull`, which skips `drawOne` for a\n * node whose box misses the view, so it must enclose everything `drawOne`\n * paints: a box too small makes a node vanish before it leaves the screen.\n *\n * `<SceneCanvas>` supplies `defaultPaintBounds` while `drawOne` is\n * `defaultDrawOne`. Omit it with any other `drawOne` and culling only\n * trims commands after painting.\n */\n paintBounds?: PaintBoundsFn<TNode, TPose>;\n}\n\n/**\n * `cfg`'s `drawOne` and clip lookup, resolving derived paths off the adapter\n * when nothing scene-aware filled `derivedPathOf` in.\n */\nfunction withAdapterDerivedPaths<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n toPose: (obj: TNode) => TPose,\n): Pick<SceneSlotConfig<TNode, TPose>, 'drawOne' | 'derivedPathOf'> {\n const drawOne = cfg.drawOne;\n if (cfg.derivedPathOf !== undefined || !drawOne || !adapter) return cfg;\n const pathOf = adapterDerivedPathOf<TPose>(cfg.toPose === undefined ? adapter : {\n getNode: (id) => adapter.getNode(id),\n getPose: (id) => {\n const obj = adapter.getNode(id);\n return obj ? toPose(obj) : adapter.getPose(id);\n },\n getChildren: (id) => adapter.getChildren(id),\n });\n return {\n derivedPathOf: (obj) => pathOf(obj.id),\n drawOne: (obj, pose, view, ctx) => ((obj as { dependsOn?: unknown }).dependsOn === undefined\n ? drawOne(obj, pose, view, ctx)\n : drawOne(obj, pose, view, { ...ctx, derivedPath: pathOf(obj.id) })),\n };\n}\n\n/**\n * Build the scene layer, which draws committed poses. `hideIds()` skips the committed paint\n * of ids the active tool is currently ghosting so the source doesn't show through. With a\n * debug sink on its draw envelope, it records each node's bounds and origin there.\n */\nexport function buildSceneLayer<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n /** When set, the returned layer carries this `id` and paints ONLY the named\n * scene layer (the rest are walked for ancestor clips). Omit for the single\n * bundled `scene` layer. */\n slot?: { id: string; forLayer: string },\n): RenderLayer<unknown> {\n const toPose =\n cfg.toPose ??\n ((obj: TNode) => (adapter ? adapter.getPose(obj.id) : (obj as unknown as TPose)));\n const { drawOne, derivedPathOf } = withAdapterDerivedPaths(cfg, adapter, toPose);\n const postProcess = cfg.postProcess;\n const finish = cfg.cull\n ? (cmds: DrawCommand[], view: View, dims: Dims): DrawCommand[] =>\n cullDrawCommands(cmds, viewToMat3(view), { x: 0, y: 0, width: dims.width, height: dims.height })\n : (cmds: DrawCommand[]): DrawCommand[] => cmds;\n const paintBounds = cfg.cull ? cfg.paintBounds : undefined;\n const culledIn = (view: View, dims: Dims) => paintBounds\n ? paintMissesView(paintBounds, view, { x: 0, y: 0, width: dims.width, height: dims.height })\n : undefined;\n return {\n id: slot?.id ?? 'scene',\n label: slot ? `Scene: ${slot.forLayer}` : 'Scene',\n draw: (data, view, dims) => {\n const debugSink = debugSinkFrom(data);\n const hidden = hideIds();\n const a = adapter;\n if (a && cfg.objects === undefined && drawOne) {\n const filteredDrawOne = (obj: TNode, pose: TPose, v: View): DrawCommand[] => {\n if (hidden && hidden.has(obj.id)) return [];\n const cmds = drawOne(obj, pose, v);\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, b);\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n return wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n };\n // Honor cfg.toPose on the hierarchical path by shimming getPose on the\n // adapter so buildSceneTree routes through it instead of the raw adapter.\n const hierarchicalAdapter = cfg.toPose\n ? {\n ...a,\n getPose: (id: string) => {\n const obj = a.getNode(id);\n return obj ? toPose(obj) : a.getPose(id);\n },\n }\n : a;\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n const tree = buildSceneTree(\n hierarchicalAdapter as unknown as Parameters<typeof buildSceneTree>[0],\n filteredDrawOne as unknown as Parameters<typeof buildSceneTree>[1],\n view,\n slot?.forLayer,\n derivedPathOf as Parameters<typeof buildSceneTree>[4],\n culledIn(view, dims) as Parameters<typeof buildSceneTree>[5],\n );\n return finish(postProcess ? postProcess(tree, view, dims) : tree, view, dims);\n }\n // `cfg.objects`: the caller's own list, which has no scene layers to split by.\n const objects = cfg.objects ?? [];\n const children: DrawCommand[] = [];\n const culled = culledIn(view, dims);\n for (const obj of objects) {\n if (hidden && hidden.has(obj.id)) continue;\n const pose: TPose = toPose(obj);\n if (drawOne && !culled?.(obj, pose)) {\n const cmds = drawOne(obj, pose, view);\n const wrapped = wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n for (const cmd of wrapped) children.push(cmd);\n }\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, { x: b.x, y: b.y, width: b.width, height: b.height });\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n }\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n return finish(postProcess ? postProcess(children, view, dims) : children, view, dims);\n },\n };\n}\n\n/**\n * The scene slot as one canvas layer **per scene layer** — keyed `scene:<layerId>` — so\n * consumers can interleave custom layers *between* scene layers via the standard slot\n * anchoring (`before: 'scene:plantings'`). A single `scene` layer when `cfg.objects` replaces\n * the tree, or the adapter declares no layers.\n */\nexport function buildSceneLayers<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n): Array<{ key: string; layer: RenderLayer<unknown> }> {\n const sceneLayerIds =\n cfg.objects === undefined && adapter ? adapter.getLayers().map((l) => l.id) : null;\n if (!sceneLayerIds || sceneLayerIds.length === 0) {\n return [{ key: 'scene', layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds) }];\n }\n return sceneLayerIds.map((id) => ({\n key: sceneLayerKey(id),\n layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds, {\n id: sceneLayerKey(id),\n forLayer: id,\n }),\n }));\n}\n"],"mappings":"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"}
|
|
@@ -1,51 +1,73 @@
|
|
|
1
1
|
import { deriveParallaxView as e, planeMap as t } from "../core/viewport/parallax.js";
|
|
2
2
|
import { definesFrame as n } from "../core/scene/effectivePose.js";
|
|
3
3
|
import { clipCarrier as r, planeTransform as i } from "./planeClips.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { splitOverChildren as a } from "./overChildren.js";
|
|
5
|
+
import { findShapeSilhouette as o } from "./NodeShape.js";
|
|
6
|
+
import { scenePlaneOf as s } from "./pickWalk.js";
|
|
7
|
+
import { mat3 as c } from "@weasel-js/geom";
|
|
6
8
|
//#region src/canvas/buildSceneTree.ts
|
|
7
|
-
function
|
|
9
|
+
function l(e, t, n, r) {
|
|
8
10
|
let i = {
|
|
9
11
|
kind: "group",
|
|
10
12
|
children: e
|
|
11
13
|
};
|
|
12
|
-
for (let e = t.length - 1; e >= 0; e--)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
for (let e = t.length - 1; e >= 0; e--) {
|
|
15
|
+
let a = t[e];
|
|
16
|
+
i = "copy" in a ? {
|
|
17
|
+
kind: "group",
|
|
18
|
+
children: [i],
|
|
19
|
+
transform: c.fromAffine(a.copy)
|
|
20
|
+
} : {
|
|
21
|
+
kind: "group",
|
|
22
|
+
children: [i],
|
|
23
|
+
clip: r(a.path, a.layer, n)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
17
26
|
return i;
|
|
18
27
|
}
|
|
19
|
-
function
|
|
20
|
-
let
|
|
21
|
-
for (let e of
|
|
22
|
-
let
|
|
23
|
-
for (let t of
|
|
24
|
-
let
|
|
25
|
-
function
|
|
26
|
-
let
|
|
27
|
-
if (!
|
|
28
|
-
let
|
|
29
|
-
if (
|
|
28
|
+
function u(c, u, d, f, p, m) {
|
|
29
|
+
let h = c.getLayers(), g = /* @__PURE__ */ new Map();
|
|
30
|
+
for (let e of h) e.visible && g.set(e.id, []);
|
|
31
|
+
let _ = null;
|
|
32
|
+
for (let t of h) t.parallax && (_ ??= /* @__PURE__ */ new Map()).set(t.id, e(d, t.parallax));
|
|
33
|
+
let v = c.composePose?.bind(c), y = r(_ ? s(h, d) : null);
|
|
34
|
+
function b(e, t, r, i) {
|
|
35
|
+
let s = c.getNode(e);
|
|
36
|
+
if (!s) return;
|
|
37
|
+
let h = c.getPose(e), x = v && r !== null ? v(r, h) : h, S = f === void 0 || s.layer === f ? g.get(s.layer) : void 0, C = _?.get(s.layer) ?? d, { under: w, over: T } = a(S && (i || !m?.(s, x, C)) ? u(s, x, C) : []), E = t, D = null, O = s;
|
|
38
|
+
if (O.kind === "container") {
|
|
30
39
|
let e;
|
|
31
|
-
e = typeof
|
|
40
|
+
e = typeof O.clipFromPose == "function" ? O.clipFromPose(x) : o(s, x, { derivedPath: p?.(s, x) }), e && (D = {
|
|
32
41
|
path: e,
|
|
33
|
-
layer:
|
|
34
|
-
}]);
|
|
42
|
+
layer: s.layer
|
|
43
|
+
}, E = [...t, D]);
|
|
44
|
+
}
|
|
45
|
+
S && S.push(E.length > 0 ? l(w, E, s.layer, y) : {
|
|
46
|
+
kind: "group",
|
|
47
|
+
children: w
|
|
48
|
+
});
|
|
49
|
+
let k = n(s) ? x : r, A = c.getChildren(e);
|
|
50
|
+
for (let e of A) b(e, E, k, i);
|
|
51
|
+
let j = typeof O.copiesFromPose == "function" ? O.copiesFromPose(x, s) : null;
|
|
52
|
+
for (let e of j ?? []) {
|
|
53
|
+
let n = [
|
|
54
|
+
...t,
|
|
55
|
+
{ copy: e },
|
|
56
|
+
...D ? [D] : []
|
|
57
|
+
];
|
|
58
|
+
for (let e of A) b(e, n, k, !0);
|
|
35
59
|
}
|
|
36
|
-
|
|
60
|
+
S && T.length > 0 && S.push(E.length > 0 ? l(T, E, s.layer, y) : {
|
|
37
61
|
kind: "group",
|
|
38
|
-
children:
|
|
62
|
+
children: T
|
|
39
63
|
});
|
|
40
|
-
let T = n(i) ? m : r;
|
|
41
|
-
for (let t of c.getChildren(e)) y(t, C, T);
|
|
42
64
|
}
|
|
43
|
-
for (let e of c.getChildren(null))
|
|
44
|
-
let
|
|
45
|
-
for (let e of
|
|
46
|
-
if (!e.visible ||
|
|
47
|
-
let n =
|
|
48
|
-
|
|
65
|
+
for (let e of c.getChildren(null)) b(e, [], null, !1);
|
|
66
|
+
let x = [];
|
|
67
|
+
for (let e of h) {
|
|
68
|
+
if (!e.visible || f !== void 0 && e.id !== f) continue;
|
|
69
|
+
let n = g.get(e.id) ?? [], r = e.parallax ? i(t(d, e.parallax), null) : void 0;
|
|
70
|
+
x.push(r ? {
|
|
49
71
|
kind: "group",
|
|
50
72
|
transform: r,
|
|
51
73
|
children: n
|
|
@@ -54,9 +76,9 @@ function c(c, l, u, d, f, p) {
|
|
|
54
76
|
children: n
|
|
55
77
|
});
|
|
56
78
|
}
|
|
57
|
-
return
|
|
79
|
+
return x;
|
|
58
80
|
}
|
|
59
81
|
//#endregion
|
|
60
|
-
export {
|
|
82
|
+
export { u as buildSceneTree };
|
|
61
83
|
|
|
62
84
|
//# sourceMappingURL=buildSceneTree.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildSceneTree.js","names":["wrapInClips","node","cmds","i","clips","carry","toLayer","buildSceneTree","layers","adapter","buckets","l","planeViews","deriveParallaxView","view","compose","clipCarrier","scenePlaneOf","visit","id","local","pose","parentFrame","bucket","forLayer","v","self","culled","drawOne","ownClips","ancestorClips","maybeContainer","clip","findShapeSilhouette","derivedPathOf","childFrame","definesFrame","cid","rootId","out","layer","children","transform","planeTransform","planeMap"],"sources":["../../src/canvas/buildSceneTree.ts"],"sourcesContent":["import type { DrawCommand, GroupDrawCommand } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport { findShapeSilhouette } from './NodeShape';\nimport type { Node } from 'core/scene/types';\nimport type { Path } from '@weasel-js/geom';\nimport { definesFrame } from 'core/scene/effectivePose';\nimport { deriveParallaxView, planeMap, type ParallaxOpts } from 'core/viewport/parallax';\nimport { scenePlaneOf } from './pickWalk';\nimport { clipCarrier, planeTransform } from './planeClips';\n\n/** The scene-tree reading surface `buildSceneTree` walks. A `SceneCanvasAdapter` is one. */\nexport interface HierarchicalAdapter<TNode, TPose> {\n getLayers(): readonly { id: string; visible: boolean; parallax?: ParallaxOpts }[];\n getNode(id: string): TNode | undefined;\n getChildren(parentId: string | null): readonly string[];\n getPose(id: string): TPose;\n /** Folds a child's local pose into its parent's frame. Absent means the\n * scene stores absolute poses and a parent contributes no transform —\n * see `PoseComposition`. */\n composePose?(parent: TPose, child: TPose): TPose;\n}\n\n/** A clip on the chain, with the layer whose world it is drawn in. */\ninterface ChainClip { path: Path; layer: string }\n\n/** Wrap `cmds` in one nested group per clip so the renderer intersects them.\n * Outermost group carries the ancestor-most clip; innermost holds `cmds`. */\nfunction wrapInClips(\n cmds: DrawCommand[],\n clips: readonly ChainClip[],\n toLayer: string,\n carry: ReturnType<typeof clipCarrier>,\n): DrawCommand {\n let node: GroupDrawCommand = { kind: 'group', children: cmds };\n for (let i = clips.length - 1; i >= 0; i--) {\n node = { kind: 'group', children: [node], clip: carry(clips[i].path, clips[i].layer, toLayer) };\n }\n return node;\n}\n\n/**\n * Walk the adapter's scene tree once and emit each node's own paint into the bucket for\n * **that node's own `layer`** — not its parent's. So a child whose `layer` differs from its\n * parent draws in its own layer's pass while staying a tree child for hit-testing / move /\n * reparent. Output is one group per visible layer, in adapter (`getLayers`) order.\n *\n * `drawOne` returns world-coord commands. Because a node may be drawn outside its parent's\n * group, the tree nesting cannot carry geometry as renderer state; the walk folds each node's\n * pose into its parent's frame on the way down and hands the painter a **world** pose. Clips\n * are accumulated the same way — each in its container's plane, carried into the plane of the\n * node it clips. With no `composePose` on the adapter the fold is the identity.\n *\n * A layer carrying `parallax` is a plane: its painters and its cull see the view derived for\n * it, and its group carries the transform from its world to the camera's, so the caller's\n * single camera wrap still lands it right.\n */\nexport function buildSceneTree<\n TNode extends { id: string; layer: string },\n TPose,\n>(\n adapter: HierarchicalAdapter<TNode, TPose>,\n drawOne: (obj: TNode, pose: TPose, view: View) => DrawCommand[],\n view: View,\n /** When set, only nodes on this layer paint (others are still walked for\n * ancestor clips) and only this layer's group is returned. */\n forLayer?: string,\n /** The path a container computes from its dependencies' poses: the scene's\n * lookup, or `adapterDerivedPathOf` for an adapter with no scene. Left out,\n * a derived container contributes no clip. */\n derivedPathOf?: (node: TNode, pose: TPose) => Path | null,\n /** True for a node whose paint cannot be seen — see `paintMissesView`. Its\n * painter is not called; its clip and its children are walked as usual. */\n culled?: (node: TNode, pose: TPose, view: View) => boolean,\n): DrawCommand[] {\n const layers = adapter.getLayers();\n // A hidden layer gets no bucket, and a node with no bucket is not painted.\n const buckets = new Map<string, DrawCommand[]>();\n for (const l of layers) if (l.visible) buckets.set(l.id, []);\n let planeViews: Map<string, View> | null = null;\n for (const l of layers) {\n if (l.parallax) (planeViews ??= new Map()).set(l.id, deriveParallaxView(view, l.parallax));\n }\n\n const compose = adapter.composePose?.bind(adapter);\n const carry = clipCarrier(planeViews ? scenePlaneOf(layers, view) : null);\n\n function visit(\n id: string,\n ancestorClips: readonly ChainClip[],\n parentFrame: TPose | null,\n ): void {\n const node = adapter.getNode(id);\n if (!node) return;\n const local = adapter.getPose(id);\n // Fold once, on the way down: O(1) per node where walking the parent chain\n // per node would be O(depth).\n const pose = compose && parentFrame !== null ? compose(parentFrame, local) : local;\n // Skip the (potentially expensive) painter for nodes we won't emit; their\n // clip still extends the chain for descendants below.\n const bucket = forLayer === undefined || node.layer === forLayer\n ? buckets.get(node.layer) : undefined;\n const v = planeViews?.get(node.layer) ?? view;\n const self = bucket && !culled?.(node, pose, v) ? drawOne(node, pose, v) : [];\n\n // Extend the clip chain with this node's own clip when it is a container.\n let ownClips = ancestorClips;\n const maybeContainer = node as { kind?: string; clipFromPose?: (pose: TPose) => unknown };\n if (maybeContainer.kind === 'container') {\n let clip: unknown;\n if (typeof maybeContainer.clipFromPose === 'function') {\n // Explicit per-node clip wins — no fallback to the painter silhouette.\n clip = maybeContainer.clipFromPose(pose);\n } else {\n // No explicit clip → the painter silhouette for the container's shape kind.\n clip = findShapeSilhouette(\n node as unknown as Node<unknown, string, TPose>,\n pose,\n { derivedPath: derivedPathOf?.(node, pose) },\n );\n }\n if (clip) ownClips = [...ancestorClips, { path: clip as Path, layer: node.layer }];\n }\n\n // Every node emits a wrapper group even with empty paint, keeping the per-node tree\n // shape stable.\n if (bucket) {\n bucket.push(ownClips.length > 0 ? wrapInClips(self, ownClips, node.layer, carry) : { kind: 'group', children: self });\n }\n\n // An envelope's children share its own parent's frame.\n const childFrame = definesFrame(node as never) ? pose : parentFrame;\n for (const cid of adapter.getChildren(id)) visit(cid, ownClips, childFrame);\n }\n\n for (const rootId of adapter.getChildren(null)) visit(rootId, [], null);\n\n const out: DrawCommand[] = [];\n for (const layer of layers) {\n if (!layer.visible) continue;\n if (forLayer !== undefined && layer.id !== forLayer) continue;\n const children = buckets.get(layer.id) ?? [];\n const transform = layer.parallax ? planeTransform(planeMap(view, layer.parallax), null) : undefined;\n out.push(transform ? { kind: 'group', transform, children } : { kind: 'group', children });\n }\n return out;\n}\n"],"mappings":";;;;;;AA2BA,SAASA,EACP,GACA,GACA,GACA,GACa;CACb,IAAIC,IAAyB;EAAE,MAAM;EAAS,UAAUC;CAAK;CAC7D,KAAK,IAAIC,IAAIC,EAAM,SAAS,GAAGD,KAAK,GAAG,KACrC,IAAO;EAAE,MAAM;EAAS,UAAU,CAACF,CAAI;EAAG,MAAMI,EAAMD,EAAMD,EAAE,CAAC,MAAMC,EAAMD,EAAE,CAAC,OAAOG,CAAO;CAAE;CAEhG,OAAOL;AACT;AAkBA,SAAgBM,EAId,GACA,GACA,GAGA,GAIA,GAGA,GACe;CACf,IAAMC,IAASC,EAAQ,UAAU,GAE3BC,oBAAU,IAAI,IAA2B;CAC/C,KAAK,IAAMC,KAAKH,GAAQ,AAAIG,EAAE,WAASD,EAAQ,IAAIC,EAAE,IAAI,CAAC,CAAC;CAC3D,IAAIC,IAAuC;CAC3C,KAAK,IAAMD,KAAKH,GACd,AAAIG,EAAE,aAAW,sBAAe,IAAI,IAAI,EAAA,CAAG,IAAIA,EAAE,IAAIE,EAAmBC,GAAMH,EAAE,QAAQ,CAAC;CAG3F,IAAMI,IAAUN,EAAQ,aAAa,KAAKA,CAAO,GAC3CJ,IAAQW,EAAYJ,IAAaK,EAAaT,GAAQM,CAAI,IAAI,IAAI;CAExE,SAASI,EACP,GACA,GACA,GACM;EACN,IAAMjB,IAAOQ,EAAQ,QAAQU,CAAE;EAC/B,IAAI,CAAClB,GAAM;EACX,IAAMmB,IAAQX,EAAQ,QAAQU,CAAE,GAG1BE,IAAON,KAAWO,MAAgB,OAAOP,EAAQO,GAAaF,CAAK,IAAIA,GAGvEG,IAASC,MAAa,KAAA,KAAavB,EAAK,UAAUuB,IACpDd,EAAQ,IAAIT,EAAK,KAAK,IAAI,KAAA,GACxBwB,IAAIb,GAAY,IAAIX,EAAK,KAAK,KAAKa,GACnCY,IAAOH,KAAU,CAACI,IAAS1B,GAAMoB,GAAMI,CAAC,IAAIG,EAAQ3B,GAAMoB,GAAMI,CAAC,IAAI,CAAC,GAGxEI,IAAWC,GACTC,IAAiB9B;EACvB,IAAI8B,EAAe,SAAS,aAAa;GACvC,IAAIC;GAYJ,AAXA,AAKEA,IALE,OAAOD,EAAe,gBAAiB,aAElCA,EAAe,aAAaV,CAAI,IAGhCY,EACLhC,GACAoB,GACA,EAAE,aAAaa,IAAgBjC,GAAMoB,CAAI,EAAE,CAC7C,GAEEW,MAAM,IAAW,CAAC,GAAGF,GAAe;IAAE,MAAME;IAAc,OAAO/B,EAAK;GAAM,CAAC;EACnF;EAIA,AAAIsB,KACFA,EAAO,KAAKM,EAAS,SAAS,IAAI7B,EAAY0B,GAAMG,GAAU5B,EAAK,OAAOI,CAAK,IAAI;GAAE,MAAM;GAAS,UAAUqB;EAAK,CAAC;EAItH,IAAMS,IAAaC,EAAanC,CAAa,IAAIoB,IAAOC;EACxD,KAAK,IAAMe,KAAO5B,EAAQ,YAAYU,CAAE,GAAG,EAAMkB,GAAKR,GAAUM,CAAU;CAC5E;CAEA,KAAK,IAAMG,KAAU7B,EAAQ,YAAY,IAAI,GAAG,EAAM6B,GAAQ,CAAC,GAAG,IAAI;CAEtE,IAAMC,IAAqB,CAAC;CAC5B,KAAK,IAAMC,KAAShC,GAAQ;EAE1B,IADI,CAACgC,EAAM,WACPhB,MAAa,KAAA,KAAagB,EAAM,OAAOhB,GAAU;EACrD,IAAMiB,IAAW/B,EAAQ,IAAI8B,EAAM,EAAE,KAAK,CAAC,GACrCE,IAAYF,EAAM,WAAWG,EAAeC,EAAS9B,GAAM0B,EAAM,QAAQ,GAAG,IAAI,IAAI,KAAA;EAC1F,EAAI,KAAKE,IAAY;GAAE,MAAM;GAAS;GAAW;EAAS,IAAI;GAAE,MAAM;GAAS;EAAS,CAAC;CAC3F;CACA,OAAOH;AACT"}
|
|
1
|
+
{"version":3,"file":"buildSceneTree.js","names":["wrapInChain","node","cmds","i","chain","e","mat3","carry","toLayer","buildSceneTree","layers","adapter","buckets","l","planeViews","deriveParallaxView","view","compose","clipCarrier","scenePlaneOf","visit","id","local","pose","parentFrame","bucket","forLayer","v","self","splitOverChildren","copied","culled","drawOne","ownClips","ancestorClips","ownClip","maybeContainer","clip","findShapeSilhouette","derivedPathOf","childFrame","definesFrame","children","cid","copies","copy","over","rootId","out","layer","transform","planeTransform","planeMap"],"sources":["../../src/canvas/buildSceneTree.ts"],"sourcesContent":["import type { DrawCommand, GroupDrawCommand } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport { findShapeSilhouette } from './NodeShape';\nimport type { Node } from 'core/scene/types';\nimport { mat3, type Affine, type Path } from '@weasel-js/geom';\nimport { definesFrame } from 'core/scene/effectivePose';\nimport { deriveParallaxView, planeMap, type ParallaxOpts } from 'core/viewport/parallax';\nimport { scenePlaneOf } from './pickWalk';\nimport { clipCarrier, planeTransform } from './planeClips';\nimport { splitOverChildren } from './overChildren';\n\n/** The scene-tree reading surface `buildSceneTree` walks. A `SceneCanvasAdapter` is one. */\nexport interface HierarchicalAdapter<TNode, TPose> {\n getLayers(): readonly { id: string; visible: boolean; parallax?: ParallaxOpts }[];\n getNode(id: string): TNode | undefined;\n getChildren(parentId: string | null): readonly string[];\n getPose(id: string): TPose;\n /** Folds a child's local pose into its parent's frame. Absent means the\n * scene stores absolute poses and a parent contributes no transform —\n * see `PoseComposition`. */\n composePose?(parent: TPose, child: TPose): TPose;\n}\n\n/** A clip on the chain, with the layer whose world it is drawn in, or the\n * transform of a copy a container draws — see `ContainerNode.copiesFromPose`. */\ntype ChainEntry = { path: Path; layer: string } | { copy: Affine };\n\n/** Wrap `cmds` in one nested group per chain entry, so the renderer intersects\n * the clips and applies each copy's transform to everything inside it.\n * Outermost group carries the ancestor-most entry; innermost holds `cmds`. */\nfunction wrapInChain(\n cmds: DrawCommand[],\n chain: readonly ChainEntry[],\n toLayer: string,\n carry: ReturnType<typeof clipCarrier>,\n): DrawCommand {\n let node: GroupDrawCommand = { kind: 'group', children: cmds };\n for (let i = chain.length - 1; i >= 0; i--) {\n const e = chain[i];\n node = 'copy' in e\n ? { kind: 'group', children: [node], transform: mat3.fromAffine(e.copy) }\n : { kind: 'group', children: [node], clip: carry(e.path, e.layer, toLayer) };\n }\n return node;\n}\n\n/**\n * Walk the adapter's scene tree once and emit each node's own paint into the bucket for\n * **that node's own `layer`** — not its parent's. So a child whose `layer` differs from its\n * parent draws in its own layer's pass while staying a tree child for hit-testing / move /\n * reparent. Output is one group per visible layer, in adapter (`getLayers`) order.\n *\n * A node paints before its children. What its `drawOne` wraps in `overChildren` paints after\n * them instead, in the node's own layer.\n *\n * `drawOne` returns world-coord commands. Because a node may be drawn outside its parent's\n * group, the tree nesting cannot carry geometry as renderer state; the walk folds each node's\n * pose into its parent's frame on the way down and hands the painter a **world** pose. Clips\n * are accumulated the same way — each in its container's plane, carried into the plane of the\n * node it clips. With no `composePose` on the adapter the fold is the identity.\n *\n * A layer carrying `parallax` is a plane: its painters and its cull see the view derived for\n * it, and its group carries the transform from its world to the camera's, so the caller's\n * single camera wrap still lands it right.\n */\nexport function buildSceneTree<\n TNode extends { id: string; layer: string },\n TPose,\n>(\n adapter: HierarchicalAdapter<TNode, TPose>,\n drawOne: (obj: TNode, pose: TPose, view: View) => DrawCommand[],\n view: View,\n /** When set, only nodes on this layer paint (others are still walked for\n * ancestor clips) and only this layer's group is returned. */\n forLayer?: string,\n /** The path a container computes from its dependencies' poses: the scene's\n * lookup, or `adapterDerivedPathOf` for an adapter with no scene. Left out,\n * a derived container contributes no clip. */\n derivedPathOf?: (node: TNode, pose: TPose) => Path | null,\n /** True for a node whose paint cannot be seen — see `paintMissesView`. Its\n * painter is not called; its clip and its children are walked as usual. */\n culled?: (node: TNode, pose: TPose, view: View) => boolean,\n): DrawCommand[] {\n const layers = adapter.getLayers();\n // A hidden layer gets no bucket, and a node with no bucket is not painted.\n const buckets = new Map<string, DrawCommand[]>();\n for (const l of layers) if (l.visible) buckets.set(l.id, []);\n let planeViews: Map<string, View> | null = null;\n for (const l of layers) {\n if (l.parallax) (planeViews ??= new Map()).set(l.id, deriveParallaxView(view, l.parallax));\n }\n\n const compose = adapter.composePose?.bind(adapter);\n const carry = clipCarrier(planeViews ? scenePlaneOf(layers, view) : null);\n\n function visit(\n id: string,\n ancestorClips: readonly ChainEntry[],\n parentFrame: TPose | null,\n /** Under a copy: the pose is not where it paints, so the cull cannot judge it. */\n copied: boolean,\n ): void {\n const node = adapter.getNode(id);\n if (!node) return;\n const local = adapter.getPose(id);\n // Fold once, on the way down: O(1) per node where walking the parent chain\n // per node would be O(depth).\n const pose = compose && parentFrame !== null ? compose(parentFrame, local) : local;\n // Skip the (potentially expensive) painter for nodes we won't emit; their\n // clip still extends the chain for descendants below.\n const bucket = forLayer === undefined || node.layer === forLayer\n ? buckets.get(node.layer) : undefined;\n const v = planeViews?.get(node.layer) ?? view;\n const { under: self, over } = splitOverChildren(\n bucket && (copied || !culled?.(node, pose, v)) ? drawOne(node, pose, v) : [],\n );\n\n // Extend the clip chain with this node's own clip when it is a container.\n let ownClips = ancestorClips;\n let ownClip: ChainEntry | null = null;\n const maybeContainer = node as {\n kind?: string;\n clipFromPose?: (pose: TPose) => unknown;\n copiesFromPose?: (pose: TPose, node: unknown) => readonly Affine[] | null;\n };\n if (maybeContainer.kind === 'container') {\n let clip: unknown;\n if (typeof maybeContainer.clipFromPose === 'function') {\n // Explicit per-node clip wins — no fallback to the painter silhouette.\n clip = maybeContainer.clipFromPose(pose);\n } else {\n // No explicit clip → the painter silhouette for the container's shape kind.\n clip = findShapeSilhouette(\n node as unknown as Node<unknown, string, TPose>,\n pose,\n { derivedPath: derivedPathOf?.(node, pose) },\n );\n }\n if (clip) {\n ownClip = { path: clip as Path, layer: node.layer };\n ownClips = [...ancestorClips, ownClip];\n }\n }\n\n // Every node emits a wrapper group even with empty paint, keeping the per-node tree\n // shape stable.\n if (bucket) {\n bucket.push(ownClips.length > 0 ? wrapInChain(self, ownClips, node.layer, carry) : { kind: 'group', children: self });\n }\n\n // An envelope's children share its own parent's frame.\n const childFrame = definesFrame(node as never) ? pose : parentFrame;\n const children = adapter.getChildren(id);\n for (const cid of children) visit(cid, ownClips, childFrame, copied);\n\n // A copy is a copy of the clipped content, so its transform encloses the\n // container's own clip and sits inside every clip above it.\n const copies = typeof maybeContainer.copiesFromPose === 'function'\n ? maybeContainer.copiesFromPose(pose, node) : null;\n for (const copy of copies ?? []) {\n const chain: ChainEntry[] = [...ancestorClips, { copy }, ...(ownClip ? [ownClip] : [])];\n for (const cid of children) visit(cid, chain, childFrame, true);\n }\n\n if (bucket && over.length > 0) {\n bucket.push(ownClips.length > 0 ? wrapInChain(over, ownClips, node.layer, carry) : { kind: 'group', children: over });\n }\n }\n\n for (const rootId of adapter.getChildren(null)) visit(rootId, [], null, false);\n\n const out: DrawCommand[] = [];\n for (const layer of layers) {\n if (!layer.visible) continue;\n if (forLayer !== undefined && layer.id !== forLayer) continue;\n const children = buckets.get(layer.id) ?? [];\n const transform = layer.parallax ? planeTransform(planeMap(view, layer.parallax), null) : undefined;\n out.push(transform ? { kind: 'group', transform, children } : { kind: 'group', children });\n }\n return out;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { multiply as e } from "@weasel-js/geom";
|
|
2
|
+
//#region src/canvas/copiesAbove.ts
|
|
3
|
+
function t(t, n, r) {
|
|
4
|
+
if (n === null || t.get(n) === void 0) return [];
|
|
5
|
+
let i = [[
|
|
6
|
+
1,
|
|
7
|
+
0,
|
|
8
|
+
0,
|
|
9
|
+
1,
|
|
10
|
+
0,
|
|
11
|
+
0
|
|
12
|
+
]];
|
|
13
|
+
for (let a of [n, ...t.ancestorsOf(n)].reverse()) {
|
|
14
|
+
let n = t.get(a);
|
|
15
|
+
if (n?.kind !== "container" || !n.copiesFromPose) continue;
|
|
16
|
+
let o = n.copiesFromPose(r(n), n) ?? [];
|
|
17
|
+
o.length !== 0 && (i = i.flatMap((t) => [t, ...o.map((n) => e(t, n))]));
|
|
18
|
+
}
|
|
19
|
+
return i.slice(1);
|
|
20
|
+
}
|
|
21
|
+
function n(e, n, r) {
|
|
22
|
+
return t(e, e.get(n)?.parent ?? null, r);
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { n as copiesAbove, t as copiesWithin };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=copiesAbove.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copiesAbove.js","names":["copiesWithin","container","scene","all","aid","a","copies","poseOf","m","multiply","c","copiesAbove","id"],"sources":["../../src/canvas/copiesAbove.ts"],"sourcesContent":["import { multiply, type Affine } from '@weasel-js/geom';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\n\n/**\n * Every transform content placed in `container` is drawn under besides as\n * authored: the `copiesFromPose` of the container and of each container above\n * it, composed outermost first, the identity left out. Empty for the top of\n * the tree, or when nothing there draws copies. `poseOf` is the pose a\n * container is painted at, which is what its copies are computed from.\n */\nexport function copiesWithin<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n container: NodeId | null,\n poseOf: (node: Node<TData, TLayer, TPose>) => TPose,\n): Affine[] {\n if (container === null || scene.get(container) === undefined) return [];\n let all: Affine[] = [[1, 0, 0, 1, 0, 0]];\n for (const aid of [container, ...scene.ancestorsOf(container)].reverse()) {\n const a = scene.get(aid);\n if (a?.kind !== 'container' || !a.copiesFromPose) continue;\n const copies = a.copiesFromPose(poseOf(a), a) ?? [];\n if (copies.length === 0) continue;\n // A copy at an outer level carries everything inside it, inner copies included.\n all = all.flatMap((m) => [m, ...copies.map((c) => multiply(m, c))]);\n }\n return all.slice(1);\n}\n\n/** Every transform the ancestors of `id` draw it under; see {@link copiesWithin}. */\nexport function copiesAbove<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n id: NodeId,\n poseOf: (node: Node<TData, TLayer, TPose>) => TPose,\n): Affine[] {\n return copiesWithin(scene, scene.get(id)?.parent ?? null, poseOf);\n}\n"],"mappings":";;AAUA,SAAgBA,EACd,GACA,GACA,GACU;CACV,IAAIC,MAAc,QAAQC,EAAM,IAAID,CAAS,MAAM,KAAA,GAAW,OAAO,CAAC;CACtE,IAAIE,IAAgB,CAAC;EAAC;EAAG;EAAG;EAAG;EAAG;EAAG;CAAC,CAAC;CACvC,KAAK,IAAMC,KAAO,CAACH,GAAW,GAAGC,EAAM,YAAYD,CAAS,CAAC,CAAC,CAAC,QAAQ,GAAG;EACxE,IAAMI,IAAIH,EAAM,IAAIE,CAAG;EACvB,IAAIC,GAAG,SAAS,eAAe,CAACA,EAAE,gBAAgB;EAClD,IAAMC,IAASD,EAAE,eAAeE,EAAOF,CAAC,GAAGA,CAAC,KAAK,CAAC;EAClD,AAAIC,EAAO,WAAW,MAEtB,IAAMH,EAAI,SAAS,MAAM,CAACK,GAAG,GAAGF,EAAO,KAAK,MAAMG,EAASD,GAAGE,CAAC,CAAC,CAAC,CAAC;CACpE;CACA,OAAOP,EAAI,MAAM,CAAC;AACpB;AAGA,SAAgBQ,EACd,GACA,GACA,GACU;CACV,OAAOX,EAAaE,GAAOA,EAAM,IAAIU,CAAE,CAAC,EAAE,UAAU,MAAML,CAAM;AAClE"}
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { overChildren as e } from "./overChildren.js";
|
|
2
|
+
import { findNodeShape as t, findShapeBounds as n } from "./NodeShape.js";
|
|
3
|
+
import { textCommandFromRuns as r } from "@weasel-js/renderer";
|
|
3
4
|
//#region src/canvas/defaultDrawOne.ts
|
|
4
|
-
function
|
|
5
|
+
function i(e) {
|
|
5
6
|
let t = typeof window < "u" && window.devicePixelRatio || 1;
|
|
6
7
|
return Math.max(Math.abs(e.scale.x), Math.abs(e.scale.y)) * t;
|
|
7
8
|
}
|
|
8
|
-
var
|
|
9
|
-
function
|
|
10
|
-
let t =
|
|
11
|
-
return (e !==
|
|
9
|
+
var a, o = {};
|
|
10
|
+
function s(e) {
|
|
11
|
+
let t = i(e);
|
|
12
|
+
return (e !== a || o.pixelScale !== t) && (a = e, o = { pixelScale: t }), o;
|
|
12
13
|
}
|
|
13
|
-
function
|
|
14
|
-
let
|
|
15
|
-
...
|
|
16
|
-
pixelScale:
|
|
17
|
-
} : o
|
|
18
|
-
if (
|
|
19
|
-
let
|
|
20
|
-
|
|
21
|
-
text: d.label,
|
|
14
|
+
function c(n, a, o, c) {
|
|
15
|
+
let l = t(n), u = !o || c?.pixelScale !== void 0 ? c : c ? {
|
|
16
|
+
...c,
|
|
17
|
+
pixelScale: i(o)
|
|
18
|
+
} : s(o), d = l ? l.paint(n, a, u) : [], f = n.data;
|
|
19
|
+
if (f?.label && f.text == null) {
|
|
20
|
+
let t = a, i = r(t.x + 6, t.y + 14, [{
|
|
21
|
+
text: f.label,
|
|
22
22
|
fill: { paint: {
|
|
23
23
|
kind: "solid",
|
|
24
24
|
color: "rgba(0,0,0,0.7)"
|
|
@@ -26,23 +26,24 @@ function s(t, i, a, s) {
|
|
|
26
26
|
}], {
|
|
27
27
|
fontFamily: "sans-serif",
|
|
28
28
|
fontSize: 11
|
|
29
|
-
})
|
|
29
|
+
});
|
|
30
|
+
return [...d, n.kind === "container" ? e([i]) : i];
|
|
30
31
|
}
|
|
31
|
-
return
|
|
32
|
+
return d;
|
|
32
33
|
}
|
|
33
|
-
function
|
|
34
|
+
function l(e, t, r) {
|
|
34
35
|
let i = e.data;
|
|
35
36
|
if (i?.label && i.text == null) return null;
|
|
36
37
|
let a = Math.abs(r.scale.x), o = Math.abs(r.scale.y);
|
|
37
|
-
return
|
|
38
|
+
return n(e, t, {
|
|
38
39
|
scale: Math.sqrt(a * o),
|
|
39
40
|
leastScale: Math.min(a, o)
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
|
-
function
|
|
43
|
-
return e ===
|
|
43
|
+
function u(e) {
|
|
44
|
+
return e === c ? l : void 0;
|
|
44
45
|
}
|
|
45
46
|
//#endregion
|
|
46
|
-
export {
|
|
47
|
+
export { c as defaultDrawOne, l as defaultPaintBounds, u as paintBoundsFor };
|
|
47
48
|
|
|
48
49
|
//# sourceMappingURL=defaultDrawOne.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaultDrawOne.js","names":["pixelScaleOf","dpr","view","lastView","lastViewCtx","viewOnlyCtx","pixelScale","defaultDrawOne","painter","findNodeShape","node","paintCtx","ctx","primary","pose","data","p","textCommandFromRuns","defaultPaintBounds","sx","sy","findShapeBounds","paintBoundsFor","drawOne"],"sources":["../../src/canvas/defaultDrawOne.ts"],"sourcesContent":["/**\n * The kit's default per-node draw function, kept apart from `SceneCanvas.tsx` so non-React\n * callers (headless rasterization via `renderSceneToPixels`) can use it without importing the\n * React component tree.\n *\n * To teach the kit about a new kind of shape, register a painter (`registerNodeShape`) — do not\n * override `drawOne`. Override `drawOne` only for cross-cutting decoration.\n */\nimport type { Node } from 'core/scene/types';\nimport type { View } from 'core/viewport/view';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { textCommandFromRuns } from '@weasel-js/renderer';\nimport { findNodeShape, findShapeBounds, type NodePaintCtx } from './NodeShape';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\n\nfunction pixelScaleOf(view: View): number {\n const dpr = (typeof window !== 'undefined' && window.devicePixelRatio) || 1;\n return Math.max(Math.abs(view.scale.x), Math.abs(view.scale.y)) * dpr;\n}\n\n// Shared across a frame's nodes, which all arrive with the same view.\nlet lastView: View | undefined;\nlet lastViewCtx: NodePaintCtx = {};\nfunction viewOnlyCtx(view: View): NodePaintCtx {\n const pixelScale = pixelScaleOf(view);\n if (view !== lastView || lastViewCtx.pixelScale !== pixelScale) {\n lastView = view;\n lastViewCtx = { pixelScale };\n }\n return lastViewCtx;\n}\n\n/** Paint one node the way the kit does by default: dispatch to the first\n * registered node-shape painter that matches, then draw its `data.label` on\n * top when it has one. Consumers replace or wrap this via `drawOne`. */\nexport function defaultDrawOne<TData, TLayer extends string, TPose>(\n node: Node<TData, TLayer, TPose>,\n pose: TPose,\n view?: View,\n ctx?: NodePaintCtx,\n): DrawCommand[] {\n const painter = findNodeShape(node);\n const paintCtx = !view || ctx?.pixelScale !== undefined\n ? ctx\n : ctx ? { ...ctx, pixelScale: pixelScaleOf(view) } : viewOnlyCtx(view);\n const primary = painter ? painter.paint(node, pose, paintCtx) : [];\n\n // Label overlay — skipped for text nodes (their content is the label).\n const data = node.data as { label?: string; text?: string } | null;\n if (data?.label && data.text == null) {\n const p = pose as unknown as { x: number; y: number };\n // Copy rather than `push`. A painter's return value is not ours to grow:\n // one that memoizes its command list — which is the point of caching\n // `paint` — would gain another label command on every frame, forever.\n
|
|
1
|
+
{"version":3,"file":"defaultDrawOne.js","names":["pixelScaleOf","dpr","view","lastView","lastViewCtx","viewOnlyCtx","pixelScale","defaultDrawOne","painter","findNodeShape","node","paintCtx","ctx","primary","pose","data","p","label","textCommandFromRuns","overChildren","defaultPaintBounds","sx","sy","findShapeBounds","paintBoundsFor","drawOne"],"sources":["../../src/canvas/defaultDrawOne.ts"],"sourcesContent":["/**\n * The kit's default per-node draw function, kept apart from `SceneCanvas.tsx` so non-React\n * callers (headless rasterization via `renderSceneToPixels`) can use it without importing the\n * React component tree.\n *\n * To teach the kit about a new kind of shape, register a painter (`registerNodeShape`) — do not\n * override `drawOne`. Override `drawOne` only for cross-cutting decoration.\n */\nimport type { Node } from 'core/scene/types';\nimport type { View } from 'core/viewport/view';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { textCommandFromRuns } from '@weasel-js/renderer';\nimport { findNodeShape, findShapeBounds, type NodePaintCtx } from './NodeShape';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { overChildren } from './overChildren';\n\nfunction pixelScaleOf(view: View): number {\n const dpr = (typeof window !== 'undefined' && window.devicePixelRatio) || 1;\n return Math.max(Math.abs(view.scale.x), Math.abs(view.scale.y)) * dpr;\n}\n\n// Shared across a frame's nodes, which all arrive with the same view.\nlet lastView: View | undefined;\nlet lastViewCtx: NodePaintCtx = {};\nfunction viewOnlyCtx(view: View): NodePaintCtx {\n const pixelScale = pixelScaleOf(view);\n if (view !== lastView || lastViewCtx.pixelScale !== pixelScale) {\n lastView = view;\n lastViewCtx = { pixelScale };\n }\n return lastViewCtx;\n}\n\n/** Paint one node the way the kit does by default: dispatch to the first\n * registered node-shape painter that matches, then draw its `data.label` on\n * top when it has one: on top of a container's children too. Consumers replace or wrap this via `drawOne`. */\nexport function defaultDrawOne<TData, TLayer extends string, TPose>(\n node: Node<TData, TLayer, TPose>,\n pose: TPose,\n view?: View,\n ctx?: NodePaintCtx,\n): DrawCommand[] {\n const painter = findNodeShape(node);\n const paintCtx = !view || ctx?.pixelScale !== undefined\n ? ctx\n : ctx ? { ...ctx, pixelScale: pixelScaleOf(view) } : viewOnlyCtx(view);\n const primary = painter ? painter.paint(node, pose, paintCtx) : [];\n\n // Label overlay — skipped for text nodes (their content is the label).\n const data = node.data as { label?: string; text?: string } | null;\n if (data?.label && data.text == null) {\n const p = pose as unknown as { x: number; y: number };\n // Copy rather than `push`. A painter's return value is not ours to grow:\n // one that memoizes its command list — which is the point of caching\n // `paint` — would gain another label command on every frame, forever.\n const label = textCommandFromRuns(\n p.x + 6,\n p.y + 14,\n [{ text: data.label, fill: { paint: { kind: 'solid', color: 'rgba(0,0,0,0.7)' } } }],\n { fontFamily: 'sans-serif', fontSize: 11 },\n );\n // A container's children would cover a label drawn with the container.\n return [...primary, node.kind === 'container' ? overChildren([label]) : label];\n }\n\n // Rotation and per-node alpha are applied OUTSIDE this function, by `wrapNodeOutput` in\n // the scene walks and the preview-ghost layer, so that consumer-supplied painters get\n // them too.\n return primary;\n}\n\n/**\n * The box {@link defaultDrawOne}'s output for `node` fits in — the matched\n * painter's `bounds` — or `null` when it has none, or when a `data.label`\n * overlay adds text nothing bounds cheaply. The scene slot's `cull` reads it\n * to skip painting nodes the view cannot see.\n */\nexport function defaultPaintBounds<TData, TLayer extends string, TPose>(\n node: Node<TData, TLayer, TPose>,\n pose: TPose,\n view: View,\n): Bounds | null {\n const data = node.data as { label?: string; text?: string } | null;\n if (data?.label && data.text == null) return null;\n const sx = Math.abs(view.scale.x), sy = Math.abs(view.scale.y);\n return findShapeBounds(node, pose, { scale: Math.sqrt(sx * sy), leastScale: Math.min(sx, sy) });\n}\n\n/**\n * `defaultPaintBounds` when `drawOne` is `defaultDrawOne`, and nothing\n * otherwise: a painter's box says nothing about what a replacement\n * `drawOne` paints.\n */\nexport function paintBoundsFor<TData, TLayer extends string, TPose>(\n drawOne: unknown,\n): typeof defaultPaintBounds<TData, TLayer, TPose> | undefined {\n return drawOne === defaultDrawOne ? defaultPaintBounds : undefined;\n}\n"],"mappings":";;;;AAgBA,SAASA,EAAa,GAAoB;CACxC,IAAMC,IAAO,OAAO,SAAW,OAAe,OAAO,oBAAqB;CAC1E,OAAO,KAAK,IAAI,KAAK,IAAIC,EAAK,MAAM,CAAC,GAAG,KAAK,IAAIA,EAAK,MAAM,CAAC,CAAC,IAAID;AACpE;AAGA,IAAIE,GACAC,IAA4B,CAAC;AACjC,SAASC,EAAY,GAA0B;CAC7C,IAAMC,IAAaN,EAAaE,CAAI;CAKpC,QAJIA,MAASC,KAAYC,EAAY,eAAeE,OAClD,IAAWJ,GACX,IAAc,EAAE,cAAW,IAEtBE;AACT;AAKA,SAAgBG,EACd,GACA,GACA,GACA,GACe;CACf,IAAMC,IAAUC,EAAcC,CAAI,GAC5BC,IAAW,CAACT,KAAQU,GAAK,eAAe,KAAA,IAC1CA,IACAA,IAAM;EAAE,GAAGA;EAAK,YAAYZ,EAAaE,CAAI;CAAE,IAAIG,EAAYH,CAAI,GACjEW,IAAUL,IAAUA,EAAQ,MAAME,GAAMI,GAAMH,CAAQ,IAAI,CAAC,GAG3DI,IAAOL,EAAK;CAClB,IAAIK,GAAM,SAASA,EAAK,QAAQ,MAAM;EACpC,IAAMC,IAAIF,GAIJG,IAAQC,EACZF,EAAE,IAAI,GACNA,EAAE,IAAI,IACN,CAAC;GAAE,MAAMD,EAAK;GAAO,MAAM,EAAE,OAAO;IAAE,MAAM;IAAS,OAAO;GAAkB,EAAE;EAAE,CAAC,GACnF;GAAE,YAAY;GAAc,UAAU;EAAG,CAC3C;EAEA,OAAO,CAAC,GAAGF,GAASH,EAAK,SAAS,cAAcS,EAAa,CAACF,CAAK,CAAC,IAAIA,CAAK;CAC/E;CAKA,OAAOJ;AACT;AAQA,SAAgBO,EACd,GACA,GACA,GACe;CACf,IAAML,IAAOL,EAAK;CAClB,IAAIK,GAAM,SAASA,EAAK,QAAQ,MAAM,OAAO;CAC7C,IAAMM,IAAK,KAAK,IAAInB,EAAK,MAAM,CAAC,GAAGoB,IAAK,KAAK,IAAIpB,EAAK,MAAM,CAAC;CAC7D,OAAOqB,EAAgBb,GAAMI,GAAM;EAAE,OAAO,KAAK,KAAKO,IAAKC,CAAE;EAAG,YAAY,KAAK,IAAID,GAAIC,CAAE;CAAE,CAAC;AAChG;AAOA,SAAgBE,EACd,GAC6D;CAC7D,OAAOC,MAAYlB,IAAiBa,IAAqB,KAAA;AAC3D"}
|