@pascal-app/editor 0.8.0 → 0.9.2
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/package.json +15 -13
- package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
- package/src/components/editor/bake-exporter.tsx +47 -0
- package/src/components/editor/custom-camera-controls.tsx +795 -28
- package/src/components/editor/editor-layout-mobile.tsx +8 -3
- package/src/components/editor/editor-layout-v2.tsx +76 -51
- package/src/components/editor/export-manager.tsx +69 -32
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
- package/src/components/editor/first-person/build-collider-world.ts +228 -83
- package/src/components/editor/first-person-controls.tsx +943 -27
- package/src/components/editor/floating-action-menu.tsx +676 -208
- package/src/components/editor/floating-building-action-menu.tsx +2 -2
- package/src/components/editor/floorplan-background-selection.test.ts +58 -0
- package/src/components/editor/floorplan-background-selection.ts +3 -2
- package/src/components/editor/floorplan-panel.tsx +6916 -12137
- package/src/components/editor/grid.tsx +197 -36
- package/src/components/editor/group-actions.ts +457 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +420 -0
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +396 -0
- package/src/components/editor/group-transform-shared.ts +489 -0
- package/src/components/editor/handles/handle-arrow.tsx +581 -0
- package/src/components/editor/handles/use-handle-drag.ts +246 -0
- package/src/components/editor/index.tsx +412 -162
- package/src/components/editor/measurement-pill.tsx +115 -0
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +1646 -0
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +70 -0
- package/src/components/editor/quick-measurement-hud.tsx +28 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +1106 -483
- package/src/components/editor/site-edge-labels.tsx +45 -21
- package/src/components/editor/slab-hole-highlights.tsx +474 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +140 -41
- package/src/components/editor/use-floorplan-background-placement.ts +190 -35
- package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
- package/src/components/editor/use-floorplan-scene-data.ts +19 -7
- package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
- package/src/components/editor/wall-measurement-label.tsx +14 -19
- package/src/components/editor/wall-move-side-handles.tsx +870 -0
- package/src/components/editor/wall-opening-highlights.tsx +181 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
- package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/editor-2d/svg-paths.ts +10 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
- package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
- package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/stair/stair-edit-system.tsx +1 -1
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-defaults.ts +8 -0
- package/src/components/tools/elevator/elevator-tool.tsx +375 -0
- package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
- package/src/components/tools/fence/fence-drafting.ts +141 -27
- package/src/components/tools/item/move-tool.tsx +45 -105
- package/src/components/tools/item/placement-math.test.ts +22 -0
- package/src/components/tools/item/placement-math.ts +59 -7
- package/src/components/tools/item/placement-strategies.ts +432 -16
- package/src/components/tools/item/placement-types.ts +22 -1
- package/src/components/tools/item/use-draft-node.ts +51 -2
- package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
- package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
- package/src/components/tools/roof/roof-tool.tsx +350 -28
- package/src/components/tools/select/box-select-state.ts +88 -0
- package/src/components/tools/select/box-select-tool.tsx +442 -520
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +570 -0
- package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
- package/src/components/tools/select/select-candidates.test.ts +109 -0
- package/src/components/tools/select/select-candidates.ts +85 -0
- package/src/components/tools/shared/affordance-dispatch.ts +30 -0
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
- package/src/components/tools/shared/floor-stack-preview.ts +25 -0
- package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
- package/src/components/tools/shared/placement-box-geometry.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +305 -0
- package/src/components/tools/shared/polygon-editor.tsx +866 -175
- package/src/components/tools/shared/segment-angle.ts +56 -0
- package/src/components/tools/site/site-boundary-editor.tsx +520 -16
- package/src/components/tools/site/site-flag-model.ts +18 -0
- package/src/components/tools/stair/stair-defaults.ts +2 -1
- package/src/components/tools/stair/stair-tool.tsx +342 -37
- package/src/components/tools/tool-manager.tsx +239 -62
- package/src/components/tools/wall/wall-drafting.test.ts +330 -0
- package/src/components/tools/wall/wall-drafting.ts +248 -171
- package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +86 -82
- package/src/components/ui/action-menu/action-button.tsx +21 -1
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +40 -170
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +3 -117
- package/src/components/ui/action-menu/measurement-control.tsx +188 -0
- package/src/components/ui/action-menu/structure-tools.tsx +27 -93
- package/src/components/ui/action-menu/view-toggles.tsx +279 -90
- package/src/components/ui/command-palette/editor-commands.tsx +20 -15
- package/src/components/ui/command-palette/index.tsx +5 -3
- package/src/components/ui/controls/material-paint-panel.tsx +152 -0
- package/src/components/ui/controls/material-picker.tsx +87 -177
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +142 -46
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/floating-level-selector.tsx +47 -10
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +235 -23
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
- package/src/components/ui/level-duplicate-dialog.tsx +2 -1
- package/src/components/ui/panels/node-display.ts +17 -16
- package/src/components/ui/panels/panel-manager.tsx +55 -58
- package/src/components/ui/panels/panel-wrapper.tsx +238 -12
- package/src/components/ui/panels/parametric-inspector.tsx +482 -0
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/scene-loader.tsx +1 -3
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -0
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
- package/src/components/ui/sidebar/tab-bar.tsx +75 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer-overlay.tsx +253 -61
- package/src/components/viewer-zone-system.tsx +7 -2
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +64 -10
- package/src/hooks/use-ceiling-events.ts +176 -0
- package/src/hooks/use-drag-action.ts +127 -0
- package/src/hooks/use-keyboard.ts +493 -95
- package/src/hooks/use-selection.ts +64 -0
- package/src/index.tsx +451 -6
- package/src/lib/active-placement-surface.ts +35 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/constants.ts +6 -2
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +192 -0
- package/src/lib/direct-manipulation.ts +109 -0
- package/src/lib/editor-api.ts +50 -0
- package/src/lib/elevator-support.ts +96 -0
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +117 -0
- package/src/lib/floorplan/floorplan-export.tsx +364 -0
- package/src/lib/floorplan/geometry.ts +53 -0
- package/src/lib/floorplan/index.ts +10 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/floorplan/selection-tool.ts +40 -0
- package/src/lib/floorplan/stairs.ts +12 -1
- package/src/lib/fresh-planar-placement.test.ts +339 -0
- package/src/lib/fresh-planar-placement.ts +130 -0
- package/src/lib/glb-export.test.ts +441 -0
- package/src/lib/glb-export.ts +874 -0
- package/src/lib/history.ts +11 -1
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +51 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +211 -0
- package/src/lib/level-duplication.ts +54 -42
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +227 -48
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +183 -0
- package/src/lib/measurements.ts +199 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/placement-metadata.ts +29 -0
- package/src/lib/planar-cursor-placement.test.ts +100 -0
- package/src/lib/planar-cursor-placement.ts +42 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +7 -7
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +196 -0
- package/src/lib/scene-clipboard.ts +332 -0
- package/src/lib/scene.ts +40 -8
- package/src/lib/selection-routing.test.ts +298 -0
- package/src/lib/selection-routing.ts +175 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +69 -10
- package/src/lib/sfx-player.test.ts +124 -0
- package/src/lib/sfx-player.ts +148 -55
- package/src/lib/site-boundary.ts +1 -0
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +138 -0
- package/src/lib/snapping-mode.ts +179 -0
- package/src/lib/stair-duplication.ts +4 -4
- package/src/lib/stair-levels.test.ts +146 -0
- package/src/lib/stair-levels.ts +177 -0
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/world-grid-snap.ts +231 -0
- package/src/lib/zone-content.ts +130 -0
- package/src/store/use-alignment-guides.ts +21 -0
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-editor.tsx +767 -114
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +21 -0
- package/src/store/use-floorplan-draft-preview.ts +101 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-interaction-scope.test.ts +186 -0
- package/src/store/use-interaction-scope.ts +132 -0
- package/src/store/use-measurement-draft.test.ts +530 -0
- package/src/store/use-measurement-draft.ts +543 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-placement-preview.ts +39 -0
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +43 -0
- package/src/store/use-wall-move-ghosts.ts +36 -0
- package/src/store/use-wall-snap-indicator.ts +34 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
- package/src/components/tools/building/move-building-tool.tsx +0 -157
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
- package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
- package/src/components/tools/column/column-tool.tsx +0 -97
- package/src/components/tools/column/move-column-tool.tsx +0 -105
- package/src/components/tools/door/door-math.ts +0 -110
- package/src/components/tools/door/door-tool.tsx +0 -324
- package/src/components/tools/door/move-door-tool.tsx +0 -412
- package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
- package/src/components/tools/fence/fence-tool.tsx +0 -346
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
- package/src/components/tools/fence/move-fence-tool.tsx +0 -302
- package/src/components/tools/item/item-tool.tsx +0 -26
- package/src/components/tools/roof/move-roof-tool.tsx +0 -364
- package/src/components/tools/slab/move-slab-tool.tsx +0 -182
- package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
- package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
- package/src/components/tools/slab/slab-tool.tsx +0 -322
- package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
- package/src/components/tools/spawn/spawn-tool.tsx +0 -130
- package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
- package/src/components/tools/wall/move-wall-tool.tsx +0 -358
- package/src/components/tools/wall/wall-tool.tsx +0 -332
- package/src/components/tools/window/move-window-tool.tsx +0 -447
- package/src/components/tools/window/window-math.ts +0 -117
- package/src/components/tools/window/window-tool.tsx +0 -332
- package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
- package/src/components/ui/helpers/slab-helper.tsx +0 -20
- package/src/components/ui/helpers/wall-helper.tsx +0 -20
- package/src/components/ui/panels/ceiling-panel.tsx +0 -249
- package/src/components/ui/panels/column-panel.tsx +0 -759
- package/src/components/ui/panels/door-panel.tsx +0 -1299
- package/src/components/ui/panels/fence-panel.tsx +0 -222
- package/src/components/ui/panels/item-panel.tsx +0 -306
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
- package/src/components/ui/panels/roof-panel.tsx +0 -280
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
- package/src/components/ui/panels/slab-panel.tsx +0 -250
- package/src/components/ui/panels/spawn-panel.tsx +0 -161
- package/src/components/ui/panels/stair-panel.tsx +0 -577
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
- package/src/components/ui/panels/wall-panel.tsx +0 -273
- package/src/components/ui/panels/window-panel.tsx +0 -968
- package/src/contexts/presets-context.tsx +0 -121
- package/src/hooks/use-contextual-tools.ts +0 -61
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type Cursor, emitter } from '@pascal-app/core'
|
|
4
|
+
import type { ThreeEvent } from '@react-three/fiber'
|
|
5
|
+
import { type ReactNode, useEffect, useMemo, useRef } from 'react'
|
|
6
|
+
import {
|
|
7
|
+
BoxGeometry,
|
|
8
|
+
type BufferGeometry,
|
|
9
|
+
CircleGeometry,
|
|
10
|
+
Color,
|
|
11
|
+
CylinderGeometry,
|
|
12
|
+
DoubleSide,
|
|
13
|
+
ExtrudeGeometry,
|
|
14
|
+
type Group,
|
|
15
|
+
type Intersection,
|
|
16
|
+
Mesh,
|
|
17
|
+
type Raycaster,
|
|
18
|
+
Shape,
|
|
19
|
+
TorusGeometry,
|
|
20
|
+
} from 'three'
|
|
21
|
+
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
|
22
|
+
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
23
|
+
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
24
|
+
import useEditor from '../../../store/use-editor'
|
|
25
|
+
|
|
26
|
+
// While a press-drag move is in flight (`placementDragMode`), the move tool
|
|
27
|
+
// owns the pointer and the handle rig rides the moving node — so a handle hit
|
|
28
|
+
// area would sit under the cursor and starve the tool's surface raycast
|
|
29
|
+
// (`wall:move` for openings, `grid:move` for free movers), freezing the drag.
|
|
30
|
+
// Make every handle hit area inert for the duration; the indicator mesh still
|
|
31
|
+
// renders (it's already NO_RAYCAST + depthTest off) so the grip stays visible.
|
|
32
|
+
function hitAreaRaycast(this: Mesh, raycaster: Raycaster, intersects: Intersection[]): void {
|
|
33
|
+
if (useEditor.getState().placementDragMode) return
|
|
34
|
+
Mesh.prototype.raycast.call(this, raycaster, intersects)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const ARROW_SCALE = 0.65
|
|
38
|
+
export const ARROW_COLOR = '#8381ed'
|
|
39
|
+
export const ARROW_HOVER_COLOR = '#a5b4fc'
|
|
40
|
+
export const NO_RAYCAST = () => null
|
|
41
|
+
export const HIT_AREA_MARGIN = 0.035
|
|
42
|
+
|
|
43
|
+
const HIT_AREA_RENDER_ORDER = 1011
|
|
44
|
+
const HIT_AREA_THICKNESS = 0.08
|
|
45
|
+
const CHEVRON_MIN_X = -0.2
|
|
46
|
+
const CHEVRON_MAX_X = 0.22
|
|
47
|
+
const CHEVRON_HALF_WIDTH = 0.12
|
|
48
|
+
const CHEVRON_NOTCH_X = -0.04
|
|
49
|
+
const CHEVRON_SHAFT_HALF_WIDTH = 0.035
|
|
50
|
+
const CHEVRON_DEPTH = 0.08
|
|
51
|
+
const CHEVRON_BEVEL_THICKNESS = 0.035
|
|
52
|
+
const CHEVRON_BEVEL_SIZE = 0.03
|
|
53
|
+
const CHEVRON_BEVEL_SEGMENTS = 10
|
|
54
|
+
// Slimmer extrude profile matching the legacy wall side handles
|
|
55
|
+
// (`wall-move-side-handles.tsx`) — opt-in via the `thin` prop so the chunkier
|
|
56
|
+
// default is preserved for every other handle that uses the shared chevron.
|
|
57
|
+
const CHEVRON_THIN_DEPTH = 0.045
|
|
58
|
+
const CHEVRON_THIN_BEVEL_THICKNESS = 0.018
|
|
59
|
+
const CHEVRON_THIN_BEVEL_SIZE = 0.02
|
|
60
|
+
const CHEVRON_THIN_BEVEL_SEGMENTS = 8
|
|
61
|
+
const MOVE_CROSS_HALF_LENGTH = 0.36
|
|
62
|
+
const MOVE_CROSS_SHAFT_HALF_WIDTH = 0.03
|
|
63
|
+
const MOVE_CROSS_HEAD_HALF_WIDTH = 0.12
|
|
64
|
+
const MOVE_CROSS_HEAD_INSET = 0.2
|
|
65
|
+
const MOVE_CROSS_DEPTH = 0.06
|
|
66
|
+
const MOVE_CROSS_BEVEL_THICKNESS = 0.018
|
|
67
|
+
const MOVE_CROSS_BEVEL_SIZE = 0.012
|
|
68
|
+
const MOVE_CROSS_BEVEL_SEGMENTS = 6
|
|
69
|
+
const ROTATE_HANDLE_RADIUS = 0.2
|
|
70
|
+
const ROTATE_HANDLE_HALF_SWEEP = Math.PI / 3
|
|
71
|
+
const ROTATE_RIBBON_HALF_WIDTH = 0.02
|
|
72
|
+
const ROTATE_HEAD_HALF_WIDTH = 0.045
|
|
73
|
+
const TRACKER_CUBE_SIZE = 0.16
|
|
74
|
+
export const CORNER_HEX_RADIUS = 0.11
|
|
75
|
+
|
|
76
|
+
export type HandleArrowShape = 'chevron' | 'cross' | 'curved-arrow' | 'tracker' | 'corner-picker'
|
|
77
|
+
export type HandleArrowInputShape = HandleArrowShape | 'arrow' | 'move-cross'
|
|
78
|
+
|
|
79
|
+
export type HandleArrowPlacement = {
|
|
80
|
+
position: readonly [number, number, number]
|
|
81
|
+
rotation?: readonly [number, number, number]
|
|
82
|
+
baseScale: number
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type PointerHandler = (event: ThreeEvent<PointerEvent>) => void
|
|
86
|
+
|
|
87
|
+
export type HandleArrowProps = {
|
|
88
|
+
shape: HandleArrowInputShape
|
|
89
|
+
placement: HandleArrowPlacement
|
|
90
|
+
hover: boolean
|
|
91
|
+
cursor: Cursor
|
|
92
|
+
onHoverChange: (hovered: boolean) => void
|
|
93
|
+
onPointerDown: PointerHandler
|
|
94
|
+
activeCursor?: Cursor
|
|
95
|
+
children?: ReactNode
|
|
96
|
+
hoverScale?: number
|
|
97
|
+
indicatorRotation?: readonly [number, number, number]
|
|
98
|
+
onPointerEnter?: PointerHandler
|
|
99
|
+
onPointerLeave?: PointerHandler
|
|
100
|
+
// Extrude the slimmer wall-handle chevron profile (chevron shape only).
|
|
101
|
+
thin?: boolean
|
|
102
|
+
// Render the corner-picker disc as a smooth circle instead of a hexagon.
|
|
103
|
+
round?: boolean
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizeHandleArrowShape(shape: HandleArrowInputShape, cursor: Cursor): HandleArrowShape {
|
|
107
|
+
if (shape === 'arrow') return 'chevron'
|
|
108
|
+
if (shape === 'move-cross') return 'cross'
|
|
109
|
+
if (shape === 'chevron' && cursor === 'move') return 'cross'
|
|
110
|
+
return shape
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Two-headed curved-arrow silhouette for whole-node rotation handles
|
|
114
|
+
// (today: the elevator's corner rotate gizmo). Symmetric arc centred on
|
|
115
|
+
// +X with sweeps to +/-halfSweep, arrowhead wings + tangentially-extended
|
|
116
|
+
// tips at each end. Drawn in 2D then extruded and rotated to lie in the
|
|
117
|
+
// XZ plane - same final-orientation contract as the chevron, so the
|
|
118
|
+
// outer rotation Y and inner-rotation chain in the renderer are reused
|
|
119
|
+
// unchanged.
|
|
120
|
+
export function createRotateArrowHandleGeometry() {
|
|
121
|
+
const R = 0.2
|
|
122
|
+
const ribbonHalfWidth = 0.02
|
|
123
|
+
const halfSweep = Math.PI / 3
|
|
124
|
+
const headHalfWidth = 0.045
|
|
125
|
+
const headOvershoot = 0.075
|
|
126
|
+
const rIn = R - ribbonHalfWidth
|
|
127
|
+
const rOut = R + ribbonHalfWidth
|
|
128
|
+
const a1 = halfSweep
|
|
129
|
+
const a2 = -halfSweep
|
|
130
|
+
|
|
131
|
+
const tip1: [number, number] = [
|
|
132
|
+
R * Math.cos(a1) - headOvershoot * Math.sin(a1),
|
|
133
|
+
R * Math.sin(a1) + headOvershoot * Math.cos(a1),
|
|
134
|
+
]
|
|
135
|
+
const tip2: [number, number] = [
|
|
136
|
+
R * Math.cos(a2) + headOvershoot * Math.sin(a2),
|
|
137
|
+
R * Math.sin(a2) - headOvershoot * Math.cos(a2),
|
|
138
|
+
]
|
|
139
|
+
const innerWing1: [number, number] = [
|
|
140
|
+
(rIn - headHalfWidth) * Math.cos(a1),
|
|
141
|
+
(rIn - headHalfWidth) * Math.sin(a1),
|
|
142
|
+
]
|
|
143
|
+
const outerWing1: [number, number] = [
|
|
144
|
+
(rOut + headHalfWidth) * Math.cos(a1),
|
|
145
|
+
(rOut + headHalfWidth) * Math.sin(a1),
|
|
146
|
+
]
|
|
147
|
+
const innerWing2: [number, number] = [
|
|
148
|
+
(rIn - headHalfWidth) * Math.cos(a2),
|
|
149
|
+
(rIn - headHalfWidth) * Math.sin(a2),
|
|
150
|
+
]
|
|
151
|
+
const outerWing2: [number, number] = [
|
|
152
|
+
(rOut + headHalfWidth) * Math.cos(a2),
|
|
153
|
+
(rOut + headHalfWidth) * Math.sin(a2),
|
|
154
|
+
]
|
|
155
|
+
const innerCorner1: [number, number] = [rIn * Math.cos(a1), rIn * Math.sin(a1)]
|
|
156
|
+
const outerCorner1: [number, number] = [rOut * Math.cos(a1), rOut * Math.sin(a1)]
|
|
157
|
+
const innerCorner2: [number, number] = [rIn * Math.cos(a2), rIn * Math.sin(a2)]
|
|
158
|
+
const outerCorner2: [number, number] = [rOut * Math.cos(a2), rOut * Math.sin(a2)]
|
|
159
|
+
|
|
160
|
+
const shape = new Shape()
|
|
161
|
+
shape.moveTo(innerCorner1[0], innerCorner1[1])
|
|
162
|
+
shape.lineTo(innerWing1[0], innerWing1[1])
|
|
163
|
+
shape.lineTo(tip1[0], tip1[1])
|
|
164
|
+
shape.lineTo(outerWing1[0], outerWing1[1])
|
|
165
|
+
shape.lineTo(outerCorner1[0], outerCorner1[1])
|
|
166
|
+
shape.absarc(0, 0, rOut, a1, a2, true)
|
|
167
|
+
shape.lineTo(outerWing2[0], outerWing2[1])
|
|
168
|
+
shape.lineTo(tip2[0], tip2[1])
|
|
169
|
+
shape.lineTo(innerWing2[0], innerWing2[1])
|
|
170
|
+
shape.lineTo(innerCorner2[0], innerCorner2[1])
|
|
171
|
+
shape.absarc(0, 0, rIn, a2, a1, false)
|
|
172
|
+
shape.closePath()
|
|
173
|
+
|
|
174
|
+
const geometry = new ExtrudeGeometry(shape, {
|
|
175
|
+
depth: 0.06,
|
|
176
|
+
bevelEnabled: true,
|
|
177
|
+
bevelThickness: 0.018,
|
|
178
|
+
bevelSize: 0.012,
|
|
179
|
+
bevelOffset: 0,
|
|
180
|
+
bevelSegments: 6,
|
|
181
|
+
curveSegments: 24,
|
|
182
|
+
steps: 1,
|
|
183
|
+
})
|
|
184
|
+
geometry.translate(0, 0, -0.03)
|
|
185
|
+
geometry.rotateX(-Math.PI / 2)
|
|
186
|
+
geometry.computeVertexNormals()
|
|
187
|
+
geometry.computeBoundingSphere()
|
|
188
|
+
return geometry
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Reused chevron+shaft silhouette. The chevron points along +X by default;
|
|
192
|
+
// callers rotate it around Y for Z-axis handles and into a vertical frame for
|
|
193
|
+
// Y-axis handles. `thin` extrudes the slimmer wall-handle profile.
|
|
194
|
+
export function createArrowHandleGeometry(thin = false) {
|
|
195
|
+
const depth = thin ? CHEVRON_THIN_DEPTH : CHEVRON_DEPTH
|
|
196
|
+
const shape = new Shape()
|
|
197
|
+
shape.moveTo(CHEVRON_MAX_X, 0)
|
|
198
|
+
shape.lineTo(CHEVRON_NOTCH_X, CHEVRON_HALF_WIDTH)
|
|
199
|
+
shape.lineTo(CHEVRON_NOTCH_X, CHEVRON_SHAFT_HALF_WIDTH)
|
|
200
|
+
shape.lineTo(CHEVRON_MIN_X, CHEVRON_SHAFT_HALF_WIDTH)
|
|
201
|
+
shape.lineTo(CHEVRON_MIN_X, -CHEVRON_SHAFT_HALF_WIDTH)
|
|
202
|
+
shape.lineTo(CHEVRON_NOTCH_X, -CHEVRON_SHAFT_HALF_WIDTH)
|
|
203
|
+
shape.lineTo(CHEVRON_NOTCH_X, -CHEVRON_HALF_WIDTH)
|
|
204
|
+
shape.lineTo(CHEVRON_MAX_X, 0)
|
|
205
|
+
const geometry = new ExtrudeGeometry(shape, {
|
|
206
|
+
depth,
|
|
207
|
+
bevelEnabled: true,
|
|
208
|
+
bevelThickness: thin ? CHEVRON_THIN_BEVEL_THICKNESS : CHEVRON_BEVEL_THICKNESS,
|
|
209
|
+
bevelSize: thin ? CHEVRON_THIN_BEVEL_SIZE : CHEVRON_BEVEL_SIZE,
|
|
210
|
+
bevelOffset: 0,
|
|
211
|
+
bevelSegments: thin ? CHEVRON_THIN_BEVEL_SEGMENTS : CHEVRON_BEVEL_SEGMENTS,
|
|
212
|
+
curveSegments: 16,
|
|
213
|
+
steps: 1,
|
|
214
|
+
})
|
|
215
|
+
geometry.translate(0, 0, -depth / 2)
|
|
216
|
+
geometry.rotateX(-Math.PI / 2)
|
|
217
|
+
geometry.computeVertexNormals()
|
|
218
|
+
geometry.computeBoundingSphere()
|
|
219
|
+
return geometry
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function createDoubleArrowShape(): Shape {
|
|
223
|
+
const shape = new Shape()
|
|
224
|
+
shape.moveTo(MOVE_CROSS_HALF_LENGTH, 0)
|
|
225
|
+
shape.lineTo(MOVE_CROSS_HEAD_INSET, MOVE_CROSS_HEAD_HALF_WIDTH)
|
|
226
|
+
shape.lineTo(MOVE_CROSS_HEAD_INSET, MOVE_CROSS_SHAFT_HALF_WIDTH)
|
|
227
|
+
shape.lineTo(-MOVE_CROSS_HEAD_INSET, MOVE_CROSS_SHAFT_HALF_WIDTH)
|
|
228
|
+
shape.lineTo(-MOVE_CROSS_HEAD_INSET, MOVE_CROSS_HEAD_HALF_WIDTH)
|
|
229
|
+
shape.lineTo(-MOVE_CROSS_HALF_LENGTH, 0)
|
|
230
|
+
shape.lineTo(-MOVE_CROSS_HEAD_INSET, -MOVE_CROSS_HEAD_HALF_WIDTH)
|
|
231
|
+
shape.lineTo(-MOVE_CROSS_HEAD_INSET, -MOVE_CROSS_SHAFT_HALF_WIDTH)
|
|
232
|
+
shape.lineTo(MOVE_CROSS_HEAD_INSET, -MOVE_CROSS_SHAFT_HALF_WIDTH)
|
|
233
|
+
shape.lineTo(MOVE_CROSS_HEAD_INSET, -MOVE_CROSS_HEAD_HALF_WIDTH)
|
|
234
|
+
shape.closePath()
|
|
235
|
+
return shape
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function createMoveCrossHandleGeometry() {
|
|
239
|
+
const shape = createDoubleArrowShape()
|
|
240
|
+
const extrudeOpts = {
|
|
241
|
+
depth: MOVE_CROSS_DEPTH,
|
|
242
|
+
bevelEnabled: true,
|
|
243
|
+
bevelThickness: MOVE_CROSS_BEVEL_THICKNESS,
|
|
244
|
+
bevelSize: MOVE_CROSS_BEVEL_SIZE,
|
|
245
|
+
bevelOffset: 0,
|
|
246
|
+
bevelSegments: MOVE_CROSS_BEVEL_SEGMENTS,
|
|
247
|
+
curveSegments: 8,
|
|
248
|
+
steps: 1,
|
|
249
|
+
}
|
|
250
|
+
const armX = new ExtrudeGeometry(shape, extrudeOpts)
|
|
251
|
+
armX.translate(0, 0, -MOVE_CROSS_DEPTH / 2)
|
|
252
|
+
armX.rotateX(-Math.PI / 2)
|
|
253
|
+
const armZ = armX.clone()
|
|
254
|
+
armZ.rotateY(Math.PI / 2)
|
|
255
|
+
const merged = mergeGeometries([armX, armZ], false)
|
|
256
|
+
if (!merged) {
|
|
257
|
+
armZ.dispose()
|
|
258
|
+
armX.computeVertexNormals()
|
|
259
|
+
armX.computeBoundingSphere()
|
|
260
|
+
return armX
|
|
261
|
+
}
|
|
262
|
+
armX.dispose()
|
|
263
|
+
armZ.dispose()
|
|
264
|
+
merged.computeVertexNormals()
|
|
265
|
+
merged.computeBoundingSphere()
|
|
266
|
+
return merged
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function createArrowHitAreaGeometry() {
|
|
270
|
+
const length = CHEVRON_MAX_X - CHEVRON_MIN_X + HIT_AREA_MARGIN * 2
|
|
271
|
+
const centerX = (CHEVRON_MIN_X + CHEVRON_MAX_X) / 2
|
|
272
|
+
const geometry = new CylinderGeometry(
|
|
273
|
+
CHEVRON_HALF_WIDTH + HIT_AREA_MARGIN,
|
|
274
|
+
CHEVRON_HALF_WIDTH + HIT_AREA_MARGIN,
|
|
275
|
+
length,
|
|
276
|
+
16,
|
|
277
|
+
)
|
|
278
|
+
geometry.rotateZ(-Math.PI / 2)
|
|
279
|
+
geometry.translate(centerX, 0, 0)
|
|
280
|
+
geometry.computeBoundingSphere()
|
|
281
|
+
return geometry
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// The move cross is a plus, not a disk. A disk-shaped hit area fills the four
|
|
285
|
+
// corner gaps between the arms, so a neighbouring node sitting next to the
|
|
286
|
+
// selected node (a lamp by a door, a slab beside a wall) gets swallowed by the
|
|
287
|
+
// invisible grip and can't be picked. Wrap the visible arms instead: two flat
|
|
288
|
+
// arm boxes (length/width + margin) merged into a plus, leaving the corners
|
|
289
|
+
// empty so co-located neighbours stay selectable while the grip stays grabbable.
|
|
290
|
+
function createMoveCrossHitAreaGeometry() {
|
|
291
|
+
const armLength = (MOVE_CROSS_HALF_LENGTH + HIT_AREA_MARGIN) * 2
|
|
292
|
+
const armWidth = (MOVE_CROSS_HEAD_HALF_WIDTH + HIT_AREA_MARGIN) * 2
|
|
293
|
+
const armX = new BoxGeometry(armLength, HIT_AREA_THICKNESS, armWidth)
|
|
294
|
+
const armZ = new BoxGeometry(armWidth, HIT_AREA_THICKNESS, armLength)
|
|
295
|
+
const merged = mergeGeometries([armX, armZ], false)
|
|
296
|
+
if (!merged) {
|
|
297
|
+
armZ.dispose()
|
|
298
|
+
armX.computeBoundingSphere()
|
|
299
|
+
return armX
|
|
300
|
+
}
|
|
301
|
+
armX.dispose()
|
|
302
|
+
armZ.dispose()
|
|
303
|
+
merged.computeBoundingSphere()
|
|
304
|
+
return merged
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function createRotateArrowHitAreaGeometry() {
|
|
308
|
+
const halfSweep = ROTATE_HANDLE_HALF_SWEEP + HIT_AREA_MARGIN / ROTATE_HANDLE_RADIUS
|
|
309
|
+
const geometry = new TorusGeometry(
|
|
310
|
+
ROTATE_HANDLE_RADIUS,
|
|
311
|
+
ROTATE_RIBBON_HALF_WIDTH + ROTATE_HEAD_HALF_WIDTH + HIT_AREA_MARGIN,
|
|
312
|
+
10,
|
|
313
|
+
64,
|
|
314
|
+
halfSweep * 2,
|
|
315
|
+
)
|
|
316
|
+
geometry.rotateZ(-halfSweep)
|
|
317
|
+
geometry.rotateX(-Math.PI / 2)
|
|
318
|
+
geometry.computeBoundingSphere()
|
|
319
|
+
return geometry
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function createTrackerHitAreaGeometry() {
|
|
323
|
+
const size = TRACKER_CUBE_SIZE + HIT_AREA_MARGIN * 2
|
|
324
|
+
const geometry = new BoxGeometry(size, size, size)
|
|
325
|
+
geometry.computeBoundingSphere()
|
|
326
|
+
return geometry
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function createEndpointHitAreaGeometry(radius: number) {
|
|
330
|
+
const geometry = new CylinderGeometry(
|
|
331
|
+
radius + HIT_AREA_MARGIN,
|
|
332
|
+
radius + HIT_AREA_MARGIN,
|
|
333
|
+
HIT_AREA_THICKNESS,
|
|
334
|
+
24,
|
|
335
|
+
)
|
|
336
|
+
geometry.rotateX(Math.PI / 2)
|
|
337
|
+
geometry.computeBoundingSphere()
|
|
338
|
+
return geometry
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Hexagon (6 segments) by default; a smooth circle (32 segments) when `round`.
|
|
342
|
+
const CORNER_DISC_SEGMENTS = 6
|
|
343
|
+
const CORNER_DISC_ROUND_SEGMENTS = 32
|
|
344
|
+
|
|
345
|
+
function createHandleArrowGeometry(shape: HandleArrowShape, thin = false, round = false) {
|
|
346
|
+
if (shape === 'chevron') return createArrowHandleGeometry(thin)
|
|
347
|
+
if (shape === 'cross') return createMoveCrossHandleGeometry()
|
|
348
|
+
if (shape === 'curved-arrow') return createRotateArrowHandleGeometry()
|
|
349
|
+
if (shape === 'tracker') {
|
|
350
|
+
const geometry = new BoxGeometry(TRACKER_CUBE_SIZE, TRACKER_CUBE_SIZE, TRACKER_CUBE_SIZE)
|
|
351
|
+
geometry.computeBoundingSphere()
|
|
352
|
+
return geometry
|
|
353
|
+
}
|
|
354
|
+
const geometry = new CircleGeometry(
|
|
355
|
+
CORNER_HEX_RADIUS,
|
|
356
|
+
round ? CORNER_DISC_ROUND_SEGMENTS : CORNER_DISC_SEGMENTS,
|
|
357
|
+
)
|
|
358
|
+
geometry.computeBoundingSphere()
|
|
359
|
+
return geometry
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function createHandleArrowHitGeometry(shape: HandleArrowShape, round = false) {
|
|
363
|
+
if (shape === 'chevron') return createArrowHitAreaGeometry()
|
|
364
|
+
if (shape === 'cross') return createMoveCrossHitAreaGeometry()
|
|
365
|
+
if (shape === 'curved-arrow') return createRotateArrowHitAreaGeometry()
|
|
366
|
+
if (shape === 'tracker') return createTrackerHitAreaGeometry()
|
|
367
|
+
const geometry = new CircleGeometry(
|
|
368
|
+
CORNER_HEX_RADIUS,
|
|
369
|
+
round ? CORNER_DISC_ROUND_SEGMENTS : CORNER_DISC_SEGMENTS,
|
|
370
|
+
)
|
|
371
|
+
geometry.computeBoundingSphere()
|
|
372
|
+
return geometry
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let sharedHitAreaMaterial: MeshBasicNodeMaterial | null = null
|
|
376
|
+
let sharedHitAreaMaterialRefs = 0
|
|
377
|
+
|
|
378
|
+
function createInvisibleHitAreaMaterial() {
|
|
379
|
+
return new MeshBasicNodeMaterial({
|
|
380
|
+
color: new Color('#ffffff'),
|
|
381
|
+
colorWrite: false,
|
|
382
|
+
depthTest: false,
|
|
383
|
+
depthWrite: false,
|
|
384
|
+
opacity: 0,
|
|
385
|
+
side: DoubleSide,
|
|
386
|
+
transparent: true,
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function useInvisibleHitAreaMaterial(): MeshBasicNodeMaterial {
|
|
391
|
+
const materialRef = useRef<MeshBasicNodeMaterial | null>(null)
|
|
392
|
+
if (!materialRef.current) {
|
|
393
|
+
sharedHitAreaMaterial ??= createInvisibleHitAreaMaterial()
|
|
394
|
+
materialRef.current = sharedHitAreaMaterial
|
|
395
|
+
}
|
|
396
|
+
useEffect(() => {
|
|
397
|
+
sharedHitAreaMaterialRefs += 1
|
|
398
|
+
return () => {
|
|
399
|
+
sharedHitAreaMaterialRefs -= 1
|
|
400
|
+
if (sharedHitAreaMaterialRefs <= 0 && sharedHitAreaMaterial) {
|
|
401
|
+
sharedHitAreaMaterial.dispose()
|
|
402
|
+
sharedHitAreaMaterial = null
|
|
403
|
+
sharedHitAreaMaterialRefs = 0
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}, [])
|
|
407
|
+
return materialRef.current
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function InvisibleHandleHitArea({
|
|
411
|
+
geometry,
|
|
412
|
+
material,
|
|
413
|
+
onPointerDown,
|
|
414
|
+
onPointerEnter,
|
|
415
|
+
onPointerLeave,
|
|
416
|
+
scale,
|
|
417
|
+
}: {
|
|
418
|
+
geometry: BufferGeometry
|
|
419
|
+
material: MeshBasicNodeMaterial
|
|
420
|
+
onPointerDown: PointerHandler
|
|
421
|
+
onPointerEnter: PointerHandler
|
|
422
|
+
onPointerLeave: PointerHandler
|
|
423
|
+
scale: number
|
|
424
|
+
}) {
|
|
425
|
+
return (
|
|
426
|
+
<mesh
|
|
427
|
+
frustumCulled={false}
|
|
428
|
+
geometry={geometry}
|
|
429
|
+
layers={EDITOR_LAYER}
|
|
430
|
+
material={material}
|
|
431
|
+
onPointerDown={onPointerDown}
|
|
432
|
+
onPointerEnter={onPointerEnter}
|
|
433
|
+
onPointerLeave={onPointerLeave}
|
|
434
|
+
raycast={hitAreaRaycast}
|
|
435
|
+
renderOrder={HIT_AREA_RENDER_ORDER}
|
|
436
|
+
scale={scale}
|
|
437
|
+
/>
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function useArrowMaterial(): MeshBasicNodeMaterial {
|
|
442
|
+
return useMemo(
|
|
443
|
+
() =>
|
|
444
|
+
new MeshBasicNodeMaterial({
|
|
445
|
+
color: new Color(ARROW_COLOR),
|
|
446
|
+
side: DoubleSide,
|
|
447
|
+
// `depthTest: false` keeps the chevron drawing on top of any
|
|
448
|
+
// geometry under it; `depthWrite: true` puts the chevron's depth
|
|
449
|
+
// into the scenePass buffer so the ink-edge shader's depth
|
|
450
|
+
// Laplacian fires on its silhouette from every angle. Without
|
|
451
|
+
// depthWrite, only the normal-discontinuity branch can detect
|
|
452
|
+
// the chevron, and that signal collapses when the arrow's faces
|
|
453
|
+
// happen to align with whatever sits behind them in screen space
|
|
454
|
+
// - which is why the lines used to drop out depending on the view.
|
|
455
|
+
depthTest: false,
|
|
456
|
+
depthWrite: true,
|
|
457
|
+
transparent: true,
|
|
458
|
+
opacity: 1,
|
|
459
|
+
}),
|
|
460
|
+
[],
|
|
461
|
+
)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function useHandleArrowMaterial(shape: HandleArrowShape): MeshBasicNodeMaterial {
|
|
465
|
+
return useMemo(
|
|
466
|
+
() =>
|
|
467
|
+
new MeshBasicNodeMaterial({
|
|
468
|
+
color: new Color(ARROW_COLOR),
|
|
469
|
+
side: DoubleSide,
|
|
470
|
+
transparent: true,
|
|
471
|
+
opacity: shape === 'corner-picker' ? 0.95 : 1,
|
|
472
|
+
depthTest: false,
|
|
473
|
+
depthWrite: shape !== 'corner-picker',
|
|
474
|
+
}),
|
|
475
|
+
[shape],
|
|
476
|
+
)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function indicatorRenderOrder(shape: HandleArrowShape) {
|
|
480
|
+
return shape === 'tracker' || shape === 'corner-picker' ? 1003 : 1010
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function HandleArrow({
|
|
484
|
+
shape,
|
|
485
|
+
placement,
|
|
486
|
+
hover,
|
|
487
|
+
cursor,
|
|
488
|
+
activeCursor,
|
|
489
|
+
children,
|
|
490
|
+
hoverScale = 1.12,
|
|
491
|
+
indicatorRotation,
|
|
492
|
+
onHoverChange,
|
|
493
|
+
onPointerDown,
|
|
494
|
+
onPointerEnter,
|
|
495
|
+
onPointerLeave,
|
|
496
|
+
thin = false,
|
|
497
|
+
round = false,
|
|
498
|
+
}: HandleArrowProps) {
|
|
499
|
+
const visualShape = normalizeHandleArrowShape(shape, cursor)
|
|
500
|
+
const geometry = useMemo(
|
|
501
|
+
() => createHandleArrowGeometry(visualShape, thin, round),
|
|
502
|
+
[visualShape, thin, round],
|
|
503
|
+
)
|
|
504
|
+
const hitGeometry = useMemo(
|
|
505
|
+
() => createHandleArrowHitGeometry(visualShape, round),
|
|
506
|
+
[visualShape, round],
|
|
507
|
+
)
|
|
508
|
+
const indicatorMaterial = useHandleArrowMaterial(visualShape)
|
|
509
|
+
const hitMaterial = useInvisibleHitAreaMaterial()
|
|
510
|
+
const rootRef = useRef<Group>(null)
|
|
511
|
+
const rotation: [number, number, number] = placement.rotation
|
|
512
|
+
? [placement.rotation[0], placement.rotation[1], placement.rotation[2]]
|
|
513
|
+
: [0, 0, 0]
|
|
514
|
+
const localRotation: [number, number, number] = indicatorRotation
|
|
515
|
+
? [indicatorRotation[0], indicatorRotation[1], indicatorRotation[2]]
|
|
516
|
+
: [0, 0, 0]
|
|
517
|
+
const scale = (hover ? hoverScale : 1) * placement.baseScale
|
|
518
|
+
const hitScale = visualShape === 'corner-picker' ? scale : placement.baseScale
|
|
519
|
+
|
|
520
|
+
useEffect(() => {
|
|
521
|
+
indicatorMaterial.color.set(hover ? ARROW_HOVER_COLOR : ARROW_COLOR)
|
|
522
|
+
}, [indicatorMaterial, hover])
|
|
523
|
+
useEffect(() => {
|
|
524
|
+
const hideForCapture = () => {
|
|
525
|
+
if (rootRef.current) rootRef.current.visible = false
|
|
526
|
+
}
|
|
527
|
+
const restoreAfterCapture = () => {
|
|
528
|
+
if (rootRef.current) rootRef.current.visible = true
|
|
529
|
+
}
|
|
530
|
+
emitter.on('thumbnail:before-capture', hideForCapture)
|
|
531
|
+
emitter.on('thumbnail:after-capture', restoreAfterCapture)
|
|
532
|
+
return () => {
|
|
533
|
+
emitter.off('thumbnail:before-capture', hideForCapture)
|
|
534
|
+
emitter.off('thumbnail:after-capture', restoreAfterCapture)
|
|
535
|
+
}
|
|
536
|
+
}, [])
|
|
537
|
+
useEffect(() => () => geometry.dispose(), [geometry])
|
|
538
|
+
useEffect(() => () => hitGeometry.dispose(), [hitGeometry])
|
|
539
|
+
useEffect(() => () => indicatorMaterial.dispose(), [indicatorMaterial])
|
|
540
|
+
|
|
541
|
+
const handleEnter: PointerHandler = (event) => {
|
|
542
|
+
event.stopPropagation()
|
|
543
|
+
onHoverChange(true)
|
|
544
|
+
if (!activeCursor || document.body.style.cursor !== activeCursor) {
|
|
545
|
+
document.body.style.cursor = cursor
|
|
546
|
+
}
|
|
547
|
+
onPointerEnter?.(event)
|
|
548
|
+
}
|
|
549
|
+
const handleLeave: PointerHandler = (event) => {
|
|
550
|
+
event.stopPropagation()
|
|
551
|
+
onHoverChange(false)
|
|
552
|
+
if (document.body.style.cursor === cursor) {
|
|
553
|
+
document.body.style.cursor = ''
|
|
554
|
+
}
|
|
555
|
+
onPointerLeave?.(event)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return (
|
|
559
|
+
<group position={placement.position} ref={rootRef} rotation={rotation}>
|
|
560
|
+
{children}
|
|
561
|
+
<group rotation={localRotation}>
|
|
562
|
+
<InvisibleHandleHitArea
|
|
563
|
+
geometry={hitGeometry}
|
|
564
|
+
material={hitMaterial}
|
|
565
|
+
onPointerDown={onPointerDown}
|
|
566
|
+
onPointerEnter={handleEnter}
|
|
567
|
+
onPointerLeave={handleLeave}
|
|
568
|
+
scale={hitScale}
|
|
569
|
+
/>
|
|
570
|
+
<mesh
|
|
571
|
+
frustumCulled={false}
|
|
572
|
+
geometry={geometry}
|
|
573
|
+
material={indicatorMaterial}
|
|
574
|
+
raycast={NO_RAYCAST}
|
|
575
|
+
renderOrder={indicatorRenderOrder(visualShape)}
|
|
576
|
+
scale={scale}
|
|
577
|
+
/>
|
|
578
|
+
</group>
|
|
579
|
+
</group>
|
|
580
|
+
)
|
|
581
|
+
}
|