@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
|
@@ -1,28 +1,102 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type AlignmentAnchor,
|
|
2
3
|
type AnyNode,
|
|
3
4
|
type AnyNodeId,
|
|
5
|
+
collectAlignmentAnchors,
|
|
4
6
|
emitter,
|
|
5
7
|
type GridEvent,
|
|
6
8
|
type LevelNode,
|
|
7
9
|
RoofNode,
|
|
8
10
|
RoofSegmentNode,
|
|
11
|
+
resolveBuildingForLevel,
|
|
9
12
|
sceneRegistry,
|
|
10
13
|
useScene,
|
|
14
|
+
type WallNode,
|
|
15
|
+
wallSegmentAnchors,
|
|
11
16
|
} from '@pascal-app/core'
|
|
17
|
+
import { clearSurfacePlanSnapFeedback, resolveSurfacePlanPointSnap } from '@pascal-app/editor'
|
|
12
18
|
import { useViewer } from '@pascal-app/viewer'
|
|
13
19
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
14
20
|
import * as THREE from 'three'
|
|
15
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
BufferGeometry,
|
|
23
|
+
DoubleSide,
|
|
24
|
+
Float32BufferAttribute,
|
|
25
|
+
type Group,
|
|
26
|
+
type Line,
|
|
27
|
+
Vector3,
|
|
28
|
+
} from 'three'
|
|
16
29
|
import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
|
|
17
30
|
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
18
31
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
19
|
-
import
|
|
32
|
+
import { snapWorldXZForActiveBuilding } from '../../../lib/world-grid-snap'
|
|
33
|
+
import useEditor, { isGridSnapActive, isMagneticSnapActive } from '../../../store/use-editor'
|
|
20
34
|
import { CursorSphere } from '../shared/cursor-sphere'
|
|
21
35
|
|
|
22
36
|
const DEFAULT_WALL_HEIGHT = 0.5
|
|
23
|
-
const
|
|
37
|
+
const DEFAULT_PITCH_DEG = 40
|
|
24
38
|
const GRID_OFFSET = 0.02
|
|
25
39
|
|
|
40
|
+
// Walls that are direct children of a level.
|
|
41
|
+
function getLevelWalls(
|
|
42
|
+
levelId: string | null,
|
|
43
|
+
nodes: Readonly<Record<string, AnyNode>>,
|
|
44
|
+
): WallNode[] {
|
|
45
|
+
if (!levelId) return []
|
|
46
|
+
const levelNode = nodes[levelId]
|
|
47
|
+
if (levelNode?.type !== 'level') return []
|
|
48
|
+
return (levelNode as LevelNode).children
|
|
49
|
+
.map((childId) => nodes[childId])
|
|
50
|
+
.filter((node): node is WallNode => node?.type === 'wall')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Walls on the level directly beneath the active one. Levels share the same
|
|
54
|
+
// local XZ origin (they only differ in world Y), so these walls live in the
|
|
55
|
+
// identical coordinate frame and feed straight into both the alignment pool
|
|
56
|
+
// and the magnetic wall-snap pipeline — letting a roof drawn on the upper
|
|
57
|
+
// floor snap onto the wall corners of the floor below.
|
|
58
|
+
function getBelowLevelWalls(
|
|
59
|
+
currentLevelId: string | null,
|
|
60
|
+
nodes: Readonly<Record<string, AnyNode>>,
|
|
61
|
+
): WallNode[] {
|
|
62
|
+
if (!currentLevelId) return []
|
|
63
|
+
const currentLevel = nodes[currentLevelId]
|
|
64
|
+
if (currentLevel?.type !== 'level') return []
|
|
65
|
+
const buildingId = resolveBuildingForLevel(currentLevel.id, nodes)
|
|
66
|
+
if (!buildingId) return []
|
|
67
|
+
const building = nodes[buildingId]
|
|
68
|
+
if (building?.type !== 'building') return []
|
|
69
|
+
const currentIndex = (currentLevel as LevelNode).level
|
|
70
|
+
const belowLevel = (building.children ?? [])
|
|
71
|
+
.map((childId) => nodes[childId])
|
|
72
|
+
.filter((node): node is LevelNode => node?.type === 'level' && node.level < currentIndex)
|
|
73
|
+
.sort((a, b) => b.level - a.level)[0]
|
|
74
|
+
return getLevelWalls(belowLevel?.id ?? null, nodes)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Current-level + floor-below walls — the magnetic snap targets the roof draft
|
|
78
|
+
// locks onto (corners, midpoints, crossings, wall bodies), matching the wall
|
|
79
|
+
// tool. Same coordinate frame, so no transform is needed.
|
|
80
|
+
function getRoofSnapWalls(
|
|
81
|
+
currentLevelId: string | null,
|
|
82
|
+
nodes: Readonly<Record<string, AnyNode>>,
|
|
83
|
+
): WallNode[] {
|
|
84
|
+
return [...getLevelWalls(currentLevelId, nodes), ...getBelowLevelWalls(currentLevelId, nodes)]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Current-level alignment anchors plus the floor-below wall corners.
|
|
88
|
+
function collectRoofAlignmentAnchors(
|
|
89
|
+
nodes: Readonly<Record<string, AnyNode>>,
|
|
90
|
+
currentLevelId: string | null,
|
|
91
|
+
): AlignmentAnchor[] {
|
|
92
|
+
return [
|
|
93
|
+
...collectAlignmentAnchors(nodes, '', currentLevelId),
|
|
94
|
+
...getBelowLevelWalls(currentLevelId, nodes).flatMap((wall) =>
|
|
95
|
+
wallSegmentAnchors(wall.id, wall.start, wall.end, wall.thickness),
|
|
96
|
+
),
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
|
|
26
100
|
/**
|
|
27
101
|
* Creates a roof group with one default gable segment
|
|
28
102
|
*/
|
|
@@ -34,6 +108,13 @@ const commitRoofPlacement = (
|
|
|
34
108
|
): AnyNode['id'] => {
|
|
35
109
|
const { createNode, createNodes, nodes } = useScene.getState()
|
|
36
110
|
|
|
111
|
+
// A placed roof preset seeds `toolDefaults.roof` with the flattened
|
|
112
|
+
// subtree params (roofType, pitch, wallHeight, overhang, materials, …)
|
|
113
|
+
// before the tool activates. The footprint (width/depth) and placement
|
|
114
|
+
// come from the drawn rectangle and always win; the segment carries the
|
|
115
|
+
// shape/material params, the roof container picks up the materials.
|
|
116
|
+
const defaults = useEditor.getState().toolDefaults.roof ?? {}
|
|
117
|
+
|
|
37
118
|
const centerX = (corner1[0] + corner2[0]) / 2
|
|
38
119
|
const centerZ = (corner1[2] + corner2[2]) / 2
|
|
39
120
|
|
|
@@ -74,11 +155,12 @@ const commitRoofPlacement = (
|
|
|
74
155
|
}
|
|
75
156
|
|
|
76
157
|
const segment = RoofSegmentNode.parse({
|
|
77
|
-
width,
|
|
78
|
-
depth,
|
|
79
158
|
wallHeight: DEFAULT_WALL_HEIGHT,
|
|
80
|
-
|
|
159
|
+
pitch: DEFAULT_PITCH_DEG,
|
|
81
160
|
roofType: 'gable',
|
|
161
|
+
...defaults,
|
|
162
|
+
width,
|
|
163
|
+
depth,
|
|
82
164
|
position: [localX, 0, localZ],
|
|
83
165
|
})
|
|
84
166
|
|
|
@@ -93,16 +175,19 @@ const commitRoofPlacement = (
|
|
|
93
175
|
|
|
94
176
|
// Create the segment first (centered in its new parent)
|
|
95
177
|
const segment = RoofSegmentNode.parse({
|
|
96
|
-
width,
|
|
97
|
-
depth,
|
|
98
178
|
wallHeight: DEFAULT_WALL_HEIGHT,
|
|
99
|
-
|
|
179
|
+
pitch: DEFAULT_PITCH_DEG,
|
|
100
180
|
roofType: 'gable',
|
|
181
|
+
...defaults,
|
|
182
|
+
width,
|
|
183
|
+
depth,
|
|
101
184
|
position: [0, 0, 0],
|
|
102
185
|
})
|
|
103
186
|
|
|
104
|
-
// Create the roof container
|
|
187
|
+
// Create the roof container. Segment-shaped params (roofType, pitch, …) are
|
|
188
|
+
// dropped by the RoofNode schema; surface materials in `defaults` carry over.
|
|
105
189
|
const roof = RoofNode.parse({
|
|
190
|
+
...defaults,
|
|
106
191
|
name,
|
|
107
192
|
position: [centerX, 0, centerZ],
|
|
108
193
|
children: [segment.id],
|
|
@@ -124,20 +209,178 @@ type PreviewState = {
|
|
|
124
209
|
levelY: number
|
|
125
210
|
}
|
|
126
211
|
|
|
212
|
+
function buildRoofGhostGeometry(
|
|
213
|
+
width: number,
|
|
214
|
+
depth: number,
|
|
215
|
+
wallHeight: number,
|
|
216
|
+
pitchDeg: number,
|
|
217
|
+
) {
|
|
218
|
+
const safeWidth = Math.max(width, 0.1)
|
|
219
|
+
const safeDepth = Math.max(depth, 0.1)
|
|
220
|
+
const halfWidth = safeWidth / 2
|
|
221
|
+
const halfDepth = safeDepth / 2
|
|
222
|
+
const ridgeHeight = wallHeight + Math.tan((pitchDeg * Math.PI) / 180) * halfDepth
|
|
223
|
+
|
|
224
|
+
const vertices = [
|
|
225
|
+
// Front slope
|
|
226
|
+
-halfWidth,
|
|
227
|
+
wallHeight,
|
|
228
|
+
-halfDepth,
|
|
229
|
+
halfWidth,
|
|
230
|
+
wallHeight,
|
|
231
|
+
-halfDepth,
|
|
232
|
+
halfWidth,
|
|
233
|
+
ridgeHeight,
|
|
234
|
+
0,
|
|
235
|
+
|
|
236
|
+
-halfWidth,
|
|
237
|
+
wallHeight,
|
|
238
|
+
-halfDepth,
|
|
239
|
+
halfWidth,
|
|
240
|
+
ridgeHeight,
|
|
241
|
+
0,
|
|
242
|
+
-halfWidth,
|
|
243
|
+
ridgeHeight,
|
|
244
|
+
0,
|
|
245
|
+
|
|
246
|
+
// Back slope
|
|
247
|
+
-halfWidth,
|
|
248
|
+
ridgeHeight,
|
|
249
|
+
0,
|
|
250
|
+
halfWidth,
|
|
251
|
+
ridgeHeight,
|
|
252
|
+
0,
|
|
253
|
+
halfWidth,
|
|
254
|
+
wallHeight,
|
|
255
|
+
halfDepth,
|
|
256
|
+
|
|
257
|
+
-halfWidth,
|
|
258
|
+
ridgeHeight,
|
|
259
|
+
0,
|
|
260
|
+
halfWidth,
|
|
261
|
+
wallHeight,
|
|
262
|
+
halfDepth,
|
|
263
|
+
-halfWidth,
|
|
264
|
+
wallHeight,
|
|
265
|
+
halfDepth,
|
|
266
|
+
|
|
267
|
+
// Left gable
|
|
268
|
+
-halfWidth,
|
|
269
|
+
wallHeight,
|
|
270
|
+
-halfDepth,
|
|
271
|
+
-halfWidth,
|
|
272
|
+
ridgeHeight,
|
|
273
|
+
0,
|
|
274
|
+
-halfWidth,
|
|
275
|
+
wallHeight,
|
|
276
|
+
halfDepth,
|
|
277
|
+
|
|
278
|
+
// Right gable
|
|
279
|
+
halfWidth,
|
|
280
|
+
wallHeight,
|
|
281
|
+
-halfDepth,
|
|
282
|
+
halfWidth,
|
|
283
|
+
wallHeight,
|
|
284
|
+
halfDepth,
|
|
285
|
+
halfWidth,
|
|
286
|
+
ridgeHeight,
|
|
287
|
+
0,
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
const geometry = new BufferGeometry()
|
|
291
|
+
geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3))
|
|
292
|
+
geometry.computeVertexNormals()
|
|
293
|
+
return geometry
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function buildRoofGhostEdges(width: number, depth: number, wallHeight: number, pitchDeg: number) {
|
|
297
|
+
const safeWidth = Math.max(width, 0.1)
|
|
298
|
+
const safeDepth = Math.max(depth, 0.1)
|
|
299
|
+
const halfWidth = safeWidth / 2
|
|
300
|
+
const halfDepth = safeDepth / 2
|
|
301
|
+
const ridgeHeight = wallHeight + Math.tan((pitchDeg * Math.PI) / 180) * halfDepth
|
|
302
|
+
|
|
303
|
+
const vertices = [
|
|
304
|
+
// Base rectangle
|
|
305
|
+
-halfWidth,
|
|
306
|
+
wallHeight,
|
|
307
|
+
-halfDepth,
|
|
308
|
+
halfWidth,
|
|
309
|
+
wallHeight,
|
|
310
|
+
-halfDepth,
|
|
311
|
+
halfWidth,
|
|
312
|
+
wallHeight,
|
|
313
|
+
-halfDepth,
|
|
314
|
+
halfWidth,
|
|
315
|
+
wallHeight,
|
|
316
|
+
halfDepth,
|
|
317
|
+
halfWidth,
|
|
318
|
+
wallHeight,
|
|
319
|
+
halfDepth,
|
|
320
|
+
-halfWidth,
|
|
321
|
+
wallHeight,
|
|
322
|
+
halfDepth,
|
|
323
|
+
-halfWidth,
|
|
324
|
+
wallHeight,
|
|
325
|
+
halfDepth,
|
|
326
|
+
-halfWidth,
|
|
327
|
+
wallHeight,
|
|
328
|
+
-halfDepth,
|
|
329
|
+
|
|
330
|
+
// Ridge + gable edges
|
|
331
|
+
-halfWidth,
|
|
332
|
+
ridgeHeight,
|
|
333
|
+
0,
|
|
334
|
+
halfWidth,
|
|
335
|
+
ridgeHeight,
|
|
336
|
+
0,
|
|
337
|
+
-halfWidth,
|
|
338
|
+
wallHeight,
|
|
339
|
+
-halfDepth,
|
|
340
|
+
-halfWidth,
|
|
341
|
+
ridgeHeight,
|
|
342
|
+
0,
|
|
343
|
+
-halfWidth,
|
|
344
|
+
ridgeHeight,
|
|
345
|
+
0,
|
|
346
|
+
-halfWidth,
|
|
347
|
+
wallHeight,
|
|
348
|
+
halfDepth,
|
|
349
|
+
halfWidth,
|
|
350
|
+
wallHeight,
|
|
351
|
+
-halfDepth,
|
|
352
|
+
halfWidth,
|
|
353
|
+
ridgeHeight,
|
|
354
|
+
0,
|
|
355
|
+
halfWidth,
|
|
356
|
+
ridgeHeight,
|
|
357
|
+
0,
|
|
358
|
+
halfWidth,
|
|
359
|
+
wallHeight,
|
|
360
|
+
halfDepth,
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
const geometry = new BufferGeometry()
|
|
364
|
+
geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3))
|
|
365
|
+
return geometry
|
|
366
|
+
}
|
|
367
|
+
|
|
127
368
|
export const RoofTool: React.FC = () => {
|
|
128
369
|
const cursorRef = useRef<Group>(null)
|
|
129
370
|
const outlineRef = useRef<Line>(null!)
|
|
130
371
|
const currentLevelId = useViewer((state) => state.selection.levelId)
|
|
131
372
|
const selectedIds = useViewer((state) => state.selection.selectedIds)
|
|
132
373
|
const setSelection = useViewer((state) => state.setSelection)
|
|
133
|
-
const setTool = useEditor((state) => state.setTool)
|
|
134
|
-
const setMode = useEditor((state) => state.setMode)
|
|
135
374
|
|
|
136
375
|
const selectedIdsRef = useRef(selectedIds)
|
|
137
376
|
useEffect(() => {
|
|
138
377
|
selectedIdsRef.current = selectedIds
|
|
139
378
|
}, [selectedIds])
|
|
140
379
|
|
|
380
|
+
// Clear preset-seeded defaults on deactivation so a later manual roof draw
|
|
381
|
+
// isn't built with a stale preset's parameters. Unmount-only.
|
|
382
|
+
useEffect(() => () => useEditor.getState().setToolDefaults('roof', null), [])
|
|
383
|
+
|
|
141
384
|
const corner1Ref = useRef<[number, number, number] | null>(null)
|
|
142
385
|
const previousGridPosRef = useRef<[number, number] | null>(null)
|
|
143
386
|
const [preview, setPreview] = useState<PreviewState>({
|
|
@@ -151,6 +394,40 @@ export const RoofTool: React.FC = () => {
|
|
|
151
394
|
|
|
152
395
|
outlineRef.current.geometry = new BufferGeometry()
|
|
153
396
|
|
|
397
|
+
// Alignment candidates — anchors of every alignable object on the active
|
|
398
|
+
// level plus the wall corners of the floor directly below, so a roof drawn
|
|
399
|
+
// on the upper floor aligns to the walls beneath it. Refreshed after each
|
|
400
|
+
// roof commits. Both corners of the rectangle align.
|
|
401
|
+
let alignmentCandidates = collectRoofAlignmentAnchors(useScene.getState().nodes, currentLevelId)
|
|
402
|
+
|
|
403
|
+
// Resolve a grid:move/click into the drafted corner via the shared surface
|
|
404
|
+
// snap pipeline: magnetic lock onto wall corners / midpoints / crossings /
|
|
405
|
+
// bodies on the active level + floor below (raising the green beacon),
|
|
406
|
+
// falling back to alignment guides, then to the world-grid snap. The same
|
|
407
|
+
// path the slab/ceiling tools use, so the beacon and coloring match. The
|
|
408
|
+
// pipeline reads the snapping mode itself (Shift bypass, magnetic on/off),
|
|
409
|
+
// so this tool never inspects the flags. `levelId` is intentionally omitted
|
|
410
|
+
// so the explicit floor-below `walls` aren't filtered back out.
|
|
411
|
+
const resolveDraftPoint = (event: GridEvent): [number, number] => {
|
|
412
|
+
const rawPoint: [number, number] = [event.localPosition[0], event.localPosition[2]]
|
|
413
|
+
const gridFallback: [number, number] = isGridSnapActive()
|
|
414
|
+
? snapWorldXZForActiveBuilding(
|
|
415
|
+
event.position[0],
|
|
416
|
+
event.position[2],
|
|
417
|
+
useEditor.getState().gridSnapStep,
|
|
418
|
+
).local
|
|
419
|
+
: rawPoint
|
|
420
|
+
const nodes = useScene.getState().nodes
|
|
421
|
+
return resolveSurfacePlanPointSnap({
|
|
422
|
+
rawPoint,
|
|
423
|
+
fallbackPoint: gridFallback,
|
|
424
|
+
walls: getRoofSnapWalls(currentLevelId, nodes),
|
|
425
|
+
candidates: alignmentCandidates,
|
|
426
|
+
movingId: '__roof-draft__',
|
|
427
|
+
highlightWalls: true,
|
|
428
|
+
}).point
|
|
429
|
+
}
|
|
430
|
+
|
|
154
431
|
const updateOutline = (
|
|
155
432
|
corner1: [number, number, number],
|
|
156
433
|
corner2: [number, number, number],
|
|
@@ -173,8 +450,7 @@ export const RoofTool: React.FC = () => {
|
|
|
173
450
|
const onGridMove = (event: GridEvent) => {
|
|
174
451
|
if (!cursorRef.current) return
|
|
175
452
|
|
|
176
|
-
const gridX =
|
|
177
|
-
const gridZ = Math.round(event.localPosition[2] * 2) / 2
|
|
453
|
+
const [gridX, gridZ] = resolveDraftPoint(event)
|
|
178
454
|
const y = event.localPosition[1]
|
|
179
455
|
|
|
180
456
|
const cursorPosition: [number, number, number] = [gridX, y, gridZ]
|
|
@@ -183,6 +459,7 @@ export const RoofTool: React.FC = () => {
|
|
|
183
459
|
cursorRef.current.position.set(gridX, gridY, gridZ)
|
|
184
460
|
|
|
185
461
|
if (
|
|
462
|
+
(isGridSnapActive() || isMagneticSnapActive()) &&
|
|
186
463
|
corner1Ref.current &&
|
|
187
464
|
previousGridPosRef.current &&
|
|
188
465
|
(gridX !== previousGridPosRef.current[0] || gridZ !== previousGridPosRef.current[1])
|
|
@@ -206,8 +483,7 @@ export const RoofTool: React.FC = () => {
|
|
|
206
483
|
const onGridClick = (event: GridEvent) => {
|
|
207
484
|
if (!currentLevelId) return
|
|
208
485
|
|
|
209
|
-
const gridX =
|
|
210
|
-
const gridZ = Math.round(event.localPosition[2] * 2) / 2
|
|
486
|
+
const [gridX, gridZ] = resolveDraftPoint(event)
|
|
211
487
|
const y = event.localPosition[1]
|
|
212
488
|
|
|
213
489
|
if (corner1Ref.current) {
|
|
@@ -222,8 +498,11 @@ export const RoofTool: React.FC = () => {
|
|
|
222
498
|
|
|
223
499
|
corner1Ref.current = null
|
|
224
500
|
outlineRef.current.visible = false
|
|
501
|
+
alignmentCandidates = collectRoofAlignmentAnchors(useScene.getState().nodes, currentLevelId)
|
|
502
|
+
clearSurfacePlanSnapFeedback()
|
|
225
503
|
} else {
|
|
226
504
|
corner1Ref.current = [gridX, y, gridZ]
|
|
505
|
+
sfxEmitter.emit('sfx:structure-build-start')
|
|
227
506
|
setPreview((prev) => ({
|
|
228
507
|
...prev,
|
|
229
508
|
corner1: corner1Ref.current,
|
|
@@ -238,6 +517,7 @@ export const RoofTool: React.FC = () => {
|
|
|
238
517
|
outlineRef.current.visible = false
|
|
239
518
|
setPreview((prev) => ({ ...prev, corner1: null }))
|
|
240
519
|
}
|
|
520
|
+
clearSurfacePlanSnapFeedback()
|
|
241
521
|
}
|
|
242
522
|
|
|
243
523
|
emitter.on('grid:move', onGridMove)
|
|
@@ -248,6 +528,7 @@ export const RoofTool: React.FC = () => {
|
|
|
248
528
|
emitter.off('grid:move', onGridMove)
|
|
249
529
|
emitter.off('grid:click', onGridClick)
|
|
250
530
|
emitter.off('tool:cancel', onCancel)
|
|
531
|
+
clearSurfacePlanSnapFeedback()
|
|
251
532
|
|
|
252
533
|
corner1Ref.current = null
|
|
253
534
|
}
|
|
@@ -264,6 +545,34 @@ export const RoofTool: React.FC = () => {
|
|
|
264
545
|
return { length, width, centerX, centerZ }
|
|
265
546
|
}, [corner1, cursorPosition])
|
|
266
547
|
|
|
548
|
+
const roofGhostGeometry = useMemo(() => {
|
|
549
|
+
if (!previewDimensions) return null
|
|
550
|
+
return buildRoofGhostGeometry(
|
|
551
|
+
previewDimensions.length,
|
|
552
|
+
previewDimensions.width,
|
|
553
|
+
DEFAULT_WALL_HEIGHT,
|
|
554
|
+
DEFAULT_PITCH_DEG,
|
|
555
|
+
)
|
|
556
|
+
}, [previewDimensions])
|
|
557
|
+
|
|
558
|
+
const roofGhostEdges = useMemo(() => {
|
|
559
|
+
if (!previewDimensions) return null
|
|
560
|
+
return buildRoofGhostEdges(
|
|
561
|
+
previewDimensions.length,
|
|
562
|
+
previewDimensions.width,
|
|
563
|
+
DEFAULT_WALL_HEIGHT,
|
|
564
|
+
DEFAULT_PITCH_DEG,
|
|
565
|
+
)
|
|
566
|
+
}, [previewDimensions])
|
|
567
|
+
|
|
568
|
+
useEffect(
|
|
569
|
+
() => () => {
|
|
570
|
+
roofGhostGeometry?.dispose()
|
|
571
|
+
roofGhostEdges?.dispose()
|
|
572
|
+
},
|
|
573
|
+
[roofGhostEdges, roofGhostGeometry],
|
|
574
|
+
)
|
|
575
|
+
|
|
267
576
|
return (
|
|
268
577
|
<group>
|
|
269
578
|
<CursorSphere ref={cursorRef} />
|
|
@@ -297,21 +606,34 @@ export const RoofTool: React.FC = () => {
|
|
|
297
606
|
)}
|
|
298
607
|
|
|
299
608
|
{previewDimensions && previewDimensions.length > 0.1 && previewDimensions.width > 0.1 && (
|
|
300
|
-
<
|
|
609
|
+
<group
|
|
301
610
|
layers={EDITOR_LAYER}
|
|
302
611
|
position={[previewDimensions.centerX, levelY + GRID_OFFSET, previewDimensions.centerZ]}
|
|
303
|
-
rotation={[-Math.PI / 2, 0, 0]}
|
|
304
612
|
>
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
613
|
+
{roofGhostGeometry && (
|
|
614
|
+
<mesh geometry={roofGhostGeometry} layers={EDITOR_LAYER} renderOrder={1}>
|
|
615
|
+
<meshBasicMaterial
|
|
616
|
+
color="#818cf8"
|
|
617
|
+
depthTest={false}
|
|
618
|
+
depthWrite={false}
|
|
619
|
+
opacity={0.16}
|
|
620
|
+
side={DoubleSide}
|
|
621
|
+
transparent
|
|
622
|
+
/>
|
|
623
|
+
</mesh>
|
|
624
|
+
)}
|
|
625
|
+
{roofGhostEdges && (
|
|
626
|
+
<lineSegments geometry={roofGhostEdges} layers={EDITOR_LAYER} renderOrder={2}>
|
|
627
|
+
<lineBasicMaterial
|
|
628
|
+
color="#818cf8"
|
|
629
|
+
depthTest={false}
|
|
630
|
+
depthWrite={false}
|
|
631
|
+
opacity={0.5}
|
|
632
|
+
transparent
|
|
633
|
+
/>
|
|
634
|
+
</lineSegments>
|
|
635
|
+
)}
|
|
636
|
+
</group>
|
|
315
637
|
)}
|
|
316
638
|
</group>
|
|
317
639
|
)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export let boxSelectHandled = false
|
|
2
|
+
|
|
3
|
+
let resetTimeout: ReturnType<typeof setTimeout> | null = null
|
|
4
|
+
const suppressedPointerIds = new Set<number>()
|
|
5
|
+
const suppressionCleanups = new Map<number, () => void>()
|
|
6
|
+
|
|
7
|
+
type PointerEventLike = {
|
|
8
|
+
pointerId?: number
|
|
9
|
+
nativeEvent?: PointerEvent | PointerEventLike
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type SuppressBoxSelectOptions = {
|
|
13
|
+
markHandled?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function pointerIdFor(event: PointerEvent | PointerEventLike): number | null {
|
|
17
|
+
if ('pointerId' in event && typeof event.pointerId === 'number') {
|
|
18
|
+
return event.pointerId
|
|
19
|
+
}
|
|
20
|
+
const nativeEvent = 'nativeEvent' in event ? event.nativeEvent : undefined
|
|
21
|
+
return nativeEvent ? pointerIdFor(nativeEvent) : null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function markBoxSelectHandled() {
|
|
25
|
+
boxSelectHandled = true
|
|
26
|
+
if (resetTimeout) {
|
|
27
|
+
clearTimeout(resetTimeout)
|
|
28
|
+
}
|
|
29
|
+
resetTimeout = setTimeout(() => {
|
|
30
|
+
boxSelectHandled = false
|
|
31
|
+
resetTimeout = null
|
|
32
|
+
}, 50)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function suppressBoxSelectForPointer(
|
|
36
|
+
event: PointerEvent | PointerEventLike,
|
|
37
|
+
options: SuppressBoxSelectOptions = {},
|
|
38
|
+
) {
|
|
39
|
+
const markHandled = options.markHandled ?? true
|
|
40
|
+
if (markHandled) markBoxSelectHandled()
|
|
41
|
+
|
|
42
|
+
const pointerId = pointerIdFor(event)
|
|
43
|
+
if (pointerId === null || suppressedPointerIds.has(pointerId)) return
|
|
44
|
+
|
|
45
|
+
suppressedPointerIds.add(pointerId)
|
|
46
|
+
|
|
47
|
+
const clear = (releaseEvent?: PointerEvent) => {
|
|
48
|
+
if (releaseEvent && releaseEvent.pointerId !== pointerId) return
|
|
49
|
+
if (markHandled) markBoxSelectHandled()
|
|
50
|
+
suppressedPointerIds.delete(pointerId)
|
|
51
|
+
const cleanup = suppressionCleanups.get(pointerId)
|
|
52
|
+
suppressionCleanups.delete(pointerId)
|
|
53
|
+
cleanup?.()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const onPointerUp = (releaseEvent: PointerEvent) => clear(releaseEvent)
|
|
57
|
+
const onPointerCancel = (releaseEvent: PointerEvent) => clear(releaseEvent)
|
|
58
|
+
const onBlur = () => clear()
|
|
59
|
+
// Click-preserving handle interactions need suppression cleared before
|
|
60
|
+
// canvas-level pointerup handlers decide whether to block the follow-up click.
|
|
61
|
+
const releaseListenerOptions = markHandled ? undefined : { capture: true }
|
|
62
|
+
const cleanup = () => {
|
|
63
|
+
window.removeEventListener('pointerup', onPointerUp, releaseListenerOptions)
|
|
64
|
+
window.removeEventListener('pointercancel', onPointerCancel, releaseListenerOptions)
|
|
65
|
+
window.removeEventListener('blur', onBlur)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
suppressionCleanups.set(pointerId, cleanup)
|
|
69
|
+
window.addEventListener('pointerup', onPointerUp, releaseListenerOptions)
|
|
70
|
+
window.addEventListener('pointercancel', onPointerCancel, releaseListenerOptions)
|
|
71
|
+
window.addEventListener('blur', onBlur)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function isBoxSelectPointerSuppressed(event: PointerEvent | PointerEventLike) {
|
|
75
|
+
const pointerId = pointerIdFor(event)
|
|
76
|
+
return pointerId !== null && suppressedPointerIds.has(pointerId)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function clearBoxSelectHandled() {
|
|
80
|
+
if (resetTimeout) {
|
|
81
|
+
clearTimeout(resetTimeout)
|
|
82
|
+
resetTimeout = null
|
|
83
|
+
}
|
|
84
|
+
boxSelectHandled = false
|
|
85
|
+
for (const cleanup of suppressionCleanups.values()) cleanup()
|
|
86
|
+
suppressionCleanups.clear()
|
|
87
|
+
suppressedPointerIds.clear()
|
|
88
|
+
}
|