@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,62 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { FloorplanPalette } from '@pascal-app/core'
|
|
4
|
+
import { createContext, type ReactNode, useContext, useMemo } from 'react'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Per-frame render context shared between the legacy `floorplan-panel.tsx`
|
|
8
|
+
* and the registry-driven `<FloorplanRegistryLayer>`.
|
|
9
|
+
*
|
|
10
|
+
* The legacy panel is the authoritative owner of the floor-plan SVG —
|
|
11
|
+
* it computes `unitsPerPixel` from the viewBox / surface size, mounts the
|
|
12
|
+
* pan/zoom `<g>`, and knows the active theme. The registry layer is mounted
|
|
13
|
+
* inside the same `<g>`, so anything it draws shares the same coordinate
|
|
14
|
+
* system; this context plumbs through the bits it can't recompute on its
|
|
15
|
+
* own without re-implementing the legacy's resize / theme logic.
|
|
16
|
+
*
|
|
17
|
+
* Once `floorplan-panel.tsx` is fully migrated (Phase 6), this provider
|
|
18
|
+
* moves into a kind-agnostic 2D editor shell and the context loses the
|
|
19
|
+
* "legacy bridge" connotation.
|
|
20
|
+
*/
|
|
21
|
+
export type FloorplanRenderContextValue = {
|
|
22
|
+
/** SVG units per screen pixel — used to keep handle radii consistent at any zoom. */
|
|
23
|
+
unitsPerPixel: number
|
|
24
|
+
/** Themed palette mirroring the legacy `FloorplanPalette` accent slots. */
|
|
25
|
+
palette: FloorplanPalette
|
|
26
|
+
/** SVG `<pattern>` id mounted in `<defs>` by the legacy panel for selection hatch fills. */
|
|
27
|
+
hatchPatternId: string
|
|
28
|
+
/**
|
|
29
|
+
* Rotation (degrees) applied to the registry layer's parent `<g>` by the
|
|
30
|
+
* legacy panel — 90° by default, adjusted by building rotation. Renderers
|
|
31
|
+
* that emit text labels use this to keep their final on-screen orientation
|
|
32
|
+
* readable instead of mirroring whatever the parent rotation is.
|
|
33
|
+
*/
|
|
34
|
+
sceneRotationDeg: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const FloorplanRenderContext = createContext<FloorplanRenderContextValue | null>(null)
|
|
38
|
+
|
|
39
|
+
export function FloorplanRenderProvider({
|
|
40
|
+
children,
|
|
41
|
+
unitsPerPixel,
|
|
42
|
+
palette,
|
|
43
|
+
hatchPatternId,
|
|
44
|
+
sceneRotationDeg,
|
|
45
|
+
}: FloorplanRenderContextValue & { children: ReactNode }) {
|
|
46
|
+
const value = useMemo<FloorplanRenderContextValue>(
|
|
47
|
+
() => ({ unitsPerPixel, palette, hatchPatternId, sceneRotationDeg }),
|
|
48
|
+
[unitsPerPixel, palette, hatchPatternId, sceneRotationDeg],
|
|
49
|
+
)
|
|
50
|
+
return <FloorplanRenderContext.Provider value={value}>{children}</FloorplanRenderContext.Provider>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Read the active render context. Returns `null` when called outside a
|
|
55
|
+
* provider — the registry layer treats this as "render statically, skip
|
|
56
|
+
* theme-aware chrome and interactive handles". This makes the layer
|
|
57
|
+
* usable in isolation tests + future editor shells without bringing the
|
|
58
|
+
* whole legacy panel along.
|
|
59
|
+
*/
|
|
60
|
+
export function useFloorplanRender(): FloorplanRenderContextValue | null {
|
|
61
|
+
return useContext(FloorplanRenderContext)
|
|
62
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useWallSnapIndicator, type WallSnapKind } from '@pascal-app/editor'
|
|
4
|
+
import { memo } from 'react'
|
|
5
|
+
import { useFloorplanRender } from './floorplan-render-context'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* "Magnetic" wall-snap beacon for the 2D floor plan — the top-down twin of the
|
|
9
|
+
* 3D `WallSnapBeaconLayer`. Subscribes to the shared `useWallSnapIndicator`
|
|
10
|
+
* store (published by the floor-plan wall draft + endpoint-move handlers) and
|
|
11
|
+
* draws a marker at the snap point whose shape tells you *what* it caught,
|
|
12
|
+
* matching the 3D glyphs:
|
|
13
|
+
*
|
|
14
|
+
* endpoint (corner) → square midpoint → triangle
|
|
15
|
+
* intersection → ✕ cross wall body (edge) → circle
|
|
16
|
+
*
|
|
17
|
+
* Indigo to match the 3D beacon, except the corner (endpoint) square which is
|
|
18
|
+
* green to match the alignment guides. Sizes are pixel-budgeted via
|
|
19
|
+
* `unitsPerPixel` so the marker stays a
|
|
20
|
+
* constant size on screen at any zoom. Mounted inside the `data-floorplan-scene`
|
|
21
|
+
* group so coordinates are world meters (XZ) 1:1, like the alignment guides.
|
|
22
|
+
*/
|
|
23
|
+
const COLOR = '#6366f1' // indigo-500 — matches the 3D beacon
|
|
24
|
+
const ENDPOINT_COLOR = '#22c55e' // green-500 — corner (endpoint) snap accent
|
|
25
|
+
|
|
26
|
+
export const FloorplanSnapBeaconLayer = memo(function FloorplanSnapBeaconLayer() {
|
|
27
|
+
const point = useWallSnapIndicator((s) => s.point)
|
|
28
|
+
const ctx = useFloorplanRender()
|
|
29
|
+
|
|
30
|
+
if (!point) return null
|
|
31
|
+
|
|
32
|
+
const upp = ctx?.unitsPerPixel ?? 0.01
|
|
33
|
+
const m = 6 * upp // base half-size of the glyph in world meters
|
|
34
|
+
const stroke = 1.5 * upp
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<g pointerEvents="none">
|
|
38
|
+
<SnapMarker color={COLOR} kind={point.kind} m={m} stroke={stroke} x={point.x} z={point.z} />
|
|
39
|
+
</g>
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
function SnapMarker({
|
|
44
|
+
color,
|
|
45
|
+
kind,
|
|
46
|
+
m,
|
|
47
|
+
stroke,
|
|
48
|
+
x,
|
|
49
|
+
z,
|
|
50
|
+
}: {
|
|
51
|
+
color: string
|
|
52
|
+
kind: WallSnapKind
|
|
53
|
+
m: number
|
|
54
|
+
stroke: number
|
|
55
|
+
x: number
|
|
56
|
+
z: number
|
|
57
|
+
}) {
|
|
58
|
+
if (kind === 'endpoint') {
|
|
59
|
+
return <rect fill={ENDPOINT_COLOR} height={m * 2} width={m * 2} x={x - m} y={z - m} />
|
|
60
|
+
}
|
|
61
|
+
if (kind === 'midpoint') {
|
|
62
|
+
const t = m * 1.3
|
|
63
|
+
const points = `${x},${z - t} ${x - t},${z + t} ${x + t},${z + t}`
|
|
64
|
+
return <polygon fill={color} points={points} />
|
|
65
|
+
}
|
|
66
|
+
if (kind === 'intersection') {
|
|
67
|
+
const c = m * 1.4
|
|
68
|
+
return (
|
|
69
|
+
<g stroke={color} strokeLinecap="round" strokeWidth={stroke * 2}>
|
|
70
|
+
<line x1={x - c} x2={x + c} y1={z - c} y2={z + c} />
|
|
71
|
+
<line x1={x - c} x2={x + c} y1={z + c} y2={z - c} />
|
|
72
|
+
</g>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
// 'wall' (edge / along-wall) → circle
|
|
76
|
+
return <circle cx={x} cy={z} fill={color} r={m} />
|
|
77
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { memo } from 'react'
|
|
4
|
+
import { useWallMoveGhosts } from '../../store/use-wall-move-ghosts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders translucent dashed previews of bridge walls that the wall
|
|
8
|
+
* junction planner will insert on commit. Mirrors the 3D
|
|
9
|
+
* `GhostWallPreviewMesh` so 2D and 3D show the same intent mid-drag.
|
|
10
|
+
*
|
|
11
|
+
* Subscribes to `useWallMoveGhosts.bridges`; writes happen inside
|
|
12
|
+
* `wallFloorplanMoveTarget.apply` (cleared on `commit` and by the move
|
|
13
|
+
* overlay's unmount cleanup as a safety net).
|
|
14
|
+
*/
|
|
15
|
+
export const FloorplanWallMoveGhostLayer = memo(function FloorplanWallMoveGhostLayer() {
|
|
16
|
+
const bridges = useWallMoveGhosts((s) => s.bridges)
|
|
17
|
+
|
|
18
|
+
if (bridges.length === 0) return null
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<g pointerEvents="none">
|
|
22
|
+
{bridges.map((bridge) => {
|
|
23
|
+
// Draw the bridge as a thick stroke at the wall's plan-space
|
|
24
|
+
// thickness — same convention as the actual wall renderer.
|
|
25
|
+
// Dashes scale with thickness so they read consistently no
|
|
26
|
+
// matter the wall size or viewport zoom (no `non-scaling-stroke`
|
|
27
|
+
// here; we want the line to be N meters thick, not N pixels).
|
|
28
|
+
const dash = bridge.thickness * 1.4
|
|
29
|
+
const gap = bridge.thickness * 0.9
|
|
30
|
+
return (
|
|
31
|
+
<line
|
|
32
|
+
key={bridge.id}
|
|
33
|
+
opacity={0.45}
|
|
34
|
+
stroke={bridge.color}
|
|
35
|
+
strokeDasharray={`${dash} ${gap}`}
|
|
36
|
+
strokeLinecap="butt"
|
|
37
|
+
strokeWidth={bridge.thickness}
|
|
38
|
+
x1={bridge.start[0]}
|
|
39
|
+
x2={bridge.end[0]}
|
|
40
|
+
y1={bridge.start[1]}
|
|
41
|
+
y2={bridge.end[1]}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
})}
|
|
45
|
+
</g>
|
|
46
|
+
)
|
|
47
|
+
})
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type FloorplanGeometry, loadAssetUrl } from '@pascal-app/core'
|
|
4
|
+
import { memo, useEffect, useState } from 'react'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Pure-data → SVG converter. Walks a `FloorplanGeometry` tree returned by
|
|
8
|
+
* `def.floorplan(node, ctx)` and emits the matching React-SVG nodes.
|
|
9
|
+
*
|
|
10
|
+
* Coordinates are level-local meters. The wrapping floor-plan panel
|
|
11
|
+
* applies the world→SVG transform via its viewBox, so kinds emit
|
|
12
|
+
* geometry in the same units they reason about in 3D.
|
|
13
|
+
*
|
|
14
|
+
* Group transforms compose: `transform={translate(x y) rotate(deg)}`.
|
|
15
|
+
* Rotations are radians at the data layer (consistent with three.js
|
|
16
|
+
* conventions used by `def.geometry`) and converted to degrees for SVG
|
|
17
|
+
* here — kinds never touch units.
|
|
18
|
+
*
|
|
19
|
+
* Styling props map straight onto SVG attributes. Builders that need
|
|
20
|
+
* theme colors should declare them inline or expose them as registry
|
|
21
|
+
* tokens later (deferred until a real need surfaces — AI-authored kinds
|
|
22
|
+
* can pick safe defaults today).
|
|
23
|
+
*/
|
|
24
|
+
export const FloorplanGeometryRenderer = memo(function FloorplanGeometryRenderer({
|
|
25
|
+
geometry,
|
|
26
|
+
pointerEventsOverride,
|
|
27
|
+
}: {
|
|
28
|
+
geometry: FloorplanGeometry
|
|
29
|
+
pointerEventsOverride?: string
|
|
30
|
+
}) {
|
|
31
|
+
return renderNode(geometry, 0, pointerEventsOverride)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
function styleAttrs(
|
|
35
|
+
g: FloorplanGeometry & { kind: Exclude<FloorplanGeometry['kind'], 'group'> },
|
|
36
|
+
pointerEventsOverride?: string,
|
|
37
|
+
) {
|
|
38
|
+
// Shared SVG attribute mapping for any styled primitive. Keeps the per-
|
|
39
|
+
// primitive switch arms terse and ensures new style fields land
|
|
40
|
+
// everywhere at once. `as any` avoids re-asserting every variant
|
|
41
|
+
// includes the style fields — they all do, except `group` (which is
|
|
42
|
+
// filtered out by the caller's type bound).
|
|
43
|
+
const s = g as unknown as {
|
|
44
|
+
fill?: string
|
|
45
|
+
fillOpacity?: number
|
|
46
|
+
stroke?: string
|
|
47
|
+
strokeWidth?: number
|
|
48
|
+
strokeDasharray?: string
|
|
49
|
+
strokeLinecap?: 'butt' | 'round' | 'square'
|
|
50
|
+
strokeLinejoin?: 'miter' | 'round' | 'bevel'
|
|
51
|
+
strokeOpacity?: number
|
|
52
|
+
opacity?: number
|
|
53
|
+
vectorEffect?: 'non-scaling-stroke'
|
|
54
|
+
pointerEvents?: string
|
|
55
|
+
cursor?: string
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
fill: s.fill ?? 'none',
|
|
59
|
+
fillOpacity: s.fillOpacity,
|
|
60
|
+
stroke: s.stroke,
|
|
61
|
+
strokeWidth: s.strokeWidth,
|
|
62
|
+
strokeDasharray: s.strokeDasharray,
|
|
63
|
+
strokeLinecap: s.strokeLinecap,
|
|
64
|
+
strokeLinejoin: s.strokeLinejoin,
|
|
65
|
+
strokeOpacity: s.strokeOpacity,
|
|
66
|
+
opacity: s.opacity,
|
|
67
|
+
vectorEffect: s.vectorEffect,
|
|
68
|
+
pointerEvents: pointerEventsOverride ?? s.pointerEvents,
|
|
69
|
+
style: s.cursor ? { cursor: s.cursor } : undefined,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function renderNode(
|
|
74
|
+
g: FloorplanGeometry,
|
|
75
|
+
keyHint: number,
|
|
76
|
+
pointerEventsOverride?: string,
|
|
77
|
+
): React.ReactElement | null {
|
|
78
|
+
switch (g.kind) {
|
|
79
|
+
case 'path':
|
|
80
|
+
return <path d={g.d} key={keyHint} {...styleAttrs(g, pointerEventsOverride)} />
|
|
81
|
+
|
|
82
|
+
case 'polygon':
|
|
83
|
+
return (
|
|
84
|
+
<polygon
|
|
85
|
+
key={keyHint}
|
|
86
|
+
points={pointsToAttr(g.points)}
|
|
87
|
+
{...styleAttrs(g, pointerEventsOverride)}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
case 'polyline':
|
|
92
|
+
return (
|
|
93
|
+
<polyline
|
|
94
|
+
key={keyHint}
|
|
95
|
+
points={pointsToAttr(g.points)}
|
|
96
|
+
{...styleAttrs(g, pointerEventsOverride)}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
case 'rect':
|
|
101
|
+
return (
|
|
102
|
+
<rect
|
|
103
|
+
height={g.height}
|
|
104
|
+
key={keyHint}
|
|
105
|
+
rx={g.rx}
|
|
106
|
+
ry={g.ry}
|
|
107
|
+
width={g.width}
|
|
108
|
+
x={g.x}
|
|
109
|
+
y={g.y}
|
|
110
|
+
{...styleAttrs(g, pointerEventsOverride)}
|
|
111
|
+
/>
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
case 'circle':
|
|
115
|
+
return (
|
|
116
|
+
<circle
|
|
117
|
+
cx={g.cx}
|
|
118
|
+
cy={g.cy}
|
|
119
|
+
key={keyHint}
|
|
120
|
+
r={g.r}
|
|
121
|
+
{...styleAttrs(g, pointerEventsOverride)}
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
case 'line':
|
|
126
|
+
return (
|
|
127
|
+
<line
|
|
128
|
+
key={keyHint}
|
|
129
|
+
x1={g.x1}
|
|
130
|
+
x2={g.x2}
|
|
131
|
+
y1={g.y1}
|
|
132
|
+
y2={g.y2}
|
|
133
|
+
{...styleAttrs(g, pointerEventsOverride)}
|
|
134
|
+
/>
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
case 'text':
|
|
138
|
+
return (
|
|
139
|
+
<text
|
|
140
|
+
dominantBaseline={g.dominantBaseline ?? 'middle'}
|
|
141
|
+
fill={g.fill ?? '#171717'}
|
|
142
|
+
fontFamily={g.fontFamily}
|
|
143
|
+
fontSize={g.fontSize}
|
|
144
|
+
fontWeight={g.fontWeight}
|
|
145
|
+
key={keyHint}
|
|
146
|
+
opacity={g.opacity}
|
|
147
|
+
paintOrder={g.paintOrder}
|
|
148
|
+
stroke={g.stroke}
|
|
149
|
+
strokeLinecap={g.stroke ? 'round' : undefined}
|
|
150
|
+
strokeLinejoin={g.stroke ? 'round' : undefined}
|
|
151
|
+
strokeWidth={g.strokeWidth}
|
|
152
|
+
textAnchor={g.textAnchor ?? 'start'}
|
|
153
|
+
pointerEvents={pointerEventsOverride}
|
|
154
|
+
x={g.x}
|
|
155
|
+
y={g.y}
|
|
156
|
+
>
|
|
157
|
+
{g.text}
|
|
158
|
+
</text>
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
case 'image':
|
|
162
|
+
return (
|
|
163
|
+
<FloorplanImage
|
|
164
|
+
center={g.center}
|
|
165
|
+
height={g.height}
|
|
166
|
+
key={keyHint}
|
|
167
|
+
opacity={g.opacity}
|
|
168
|
+
preserveAspectRatio={g.preserveAspectRatio ?? 'xMidYMid meet'}
|
|
169
|
+
rotation={g.rotation ?? 0}
|
|
170
|
+
url={g.url}
|
|
171
|
+
width={g.width}
|
|
172
|
+
/>
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
case 'group': {
|
|
176
|
+
const transform = formatTransform(g.transform)
|
|
177
|
+
return (
|
|
178
|
+
<g key={keyHint} transform={transform}>
|
|
179
|
+
{g.children.map((child, i) => renderNode(child, i, pointerEventsOverride))}
|
|
180
|
+
</g>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// The interactive primitives (hatch / hit-line / endpoint-handle /
|
|
185
|
+
// dimension-label) need the SVG context + theme palette + units-per-
|
|
186
|
+
// pixel that only the registry layer has access to. They're rendered
|
|
187
|
+
// by `floorplan-registry-layer.tsx`'s interactive walker instead. If
|
|
188
|
+
// a caller routes one of these through this pure renderer it
|
|
189
|
+
// silently drops — the static renderer is for static output.
|
|
190
|
+
default:
|
|
191
|
+
return null
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function pointsToAttr(points: readonly (readonly [number, number])[]): string {
|
|
196
|
+
return points.map(([x, y]) => `${x},${y}`).join(' ')
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function formatTransform(t?: {
|
|
200
|
+
translate?: readonly [number, number]
|
|
201
|
+
rotate?: number
|
|
202
|
+
}): string | undefined {
|
|
203
|
+
if (!t) return undefined
|
|
204
|
+
const parts: string[] = []
|
|
205
|
+
if (t.translate) parts.push(`translate(${t.translate[0]} ${t.translate[1]})`)
|
|
206
|
+
if (t.rotate !== undefined) parts.push(`rotate(${(t.rotate * 180) / Math.PI})`)
|
|
207
|
+
return parts.length > 0 ? parts.join(' ') : undefined
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* `image` primitive renderer. Resolves the URL asynchronously via
|
|
212
|
+
* `loadAssetUrl` (handles CDN / Supabase storage) and renders an SVG
|
|
213
|
+
* `<image>` centered at `center`, rotated around it, sized in plan-local
|
|
214
|
+
* metres. While the resolution is in flight, renders nothing.
|
|
215
|
+
*/
|
|
216
|
+
function FloorplanImage({
|
|
217
|
+
url,
|
|
218
|
+
center,
|
|
219
|
+
width,
|
|
220
|
+
height,
|
|
221
|
+
rotation,
|
|
222
|
+
preserveAspectRatio,
|
|
223
|
+
opacity,
|
|
224
|
+
}: {
|
|
225
|
+
url: string
|
|
226
|
+
center: readonly [number, number]
|
|
227
|
+
width: number
|
|
228
|
+
height: number
|
|
229
|
+
rotation: number
|
|
230
|
+
preserveAspectRatio: string
|
|
231
|
+
opacity?: number
|
|
232
|
+
}) {
|
|
233
|
+
const [resolvedUrl, setResolvedUrl] = useState<string | null>(null)
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (!url) {
|
|
236
|
+
setResolvedUrl(null)
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
let cancelled = false
|
|
240
|
+
setResolvedUrl(null)
|
|
241
|
+
loadAssetUrl(url).then((next) => {
|
|
242
|
+
if (!cancelled) setResolvedUrl(next)
|
|
243
|
+
})
|
|
244
|
+
return () => {
|
|
245
|
+
cancelled = true
|
|
246
|
+
}
|
|
247
|
+
}, [url])
|
|
248
|
+
if (!resolvedUrl) return null
|
|
249
|
+
const rotationDeg = (rotation * 180) / Math.PI
|
|
250
|
+
return (
|
|
251
|
+
<g
|
|
252
|
+
pointerEvents="none"
|
|
253
|
+
transform={`translate(${center[0]} ${center[1]}) rotate(${rotationDeg})`}
|
|
254
|
+
>
|
|
255
|
+
<image
|
|
256
|
+
height={height}
|
|
257
|
+
href={resolvedUrl}
|
|
258
|
+
opacity={opacity}
|
|
259
|
+
preserveAspectRatio={preserveAspectRatio}
|
|
260
|
+
width={width}
|
|
261
|
+
x={-width / 2}
|
|
262
|
+
y={-height / 2}
|
|
263
|
+
/>
|
|
264
|
+
</g>
|
|
265
|
+
)
|
|
266
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { resolveFloorplanLabelAngle } from './floorplan-label-angle'
|
|
3
|
+
|
|
4
|
+
describe('resolveFloorplanLabelAngle', () => {
|
|
5
|
+
test('keeps segment labels readable while preserving their screen direction', () => {
|
|
6
|
+
expect(resolveFloorplanLabelAngle(0, 0)).toBe(0)
|
|
7
|
+
expect(resolveFloorplanLabelAngle(Math.PI, 0)).toBe(0)
|
|
8
|
+
expect(resolveFloorplanLabelAngle(Math.PI / 2, 90)).toBe(-90)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('counter-rotates aggregate labels to remain horizontal', () => {
|
|
12
|
+
expect(resolveFloorplanLabelAngle(0, 90, true)).toBe(-90)
|
|
13
|
+
expect(resolveFloorplanLabelAngle(Math.PI / 3, -35, true)).toBe(35)
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function resolveFloorplanLabelAngle(
|
|
2
|
+
angleRadians: number,
|
|
3
|
+
sceneRotationDeg: number,
|
|
4
|
+
screenUpright = false,
|
|
5
|
+
): number {
|
|
6
|
+
if (screenUpright) return -sceneRotationDeg
|
|
7
|
+
|
|
8
|
+
let localAngleDeg = (angleRadians * 180) / Math.PI
|
|
9
|
+
let screenAngleDeg = localAngleDeg + sceneRotationDeg
|
|
10
|
+
screenAngleDeg = ((((screenAngleDeg + 180) % 360) + 360) % 360) - 180
|
|
11
|
+
if (screenAngleDeg > 90) localAngleDeg -= 180
|
|
12
|
+
else if (screenAngleDeg <= -90) localAngleDeg += 180
|
|
13
|
+
return ((((localAngleDeg + 180) % 360) + 360) % 360) - 180
|
|
14
|
+
}
|