@pascal-app/editor 1.0.3 → 1.0.5
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/README.md +106 -0
- package/package.json +42 -38
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +307 -5
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
- package/src/components/editor/construction-reveal-audio.tsx +40 -0
- package/src/components/editor/custom-camera-controls.tsx +275 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +37 -26
- package/src/components/editor/editor-layout-v2.tsx +132 -59
- package/src/components/editor/export-manager.tsx +47 -26
- package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
- package/src/components/editor/first-person/build-collider-world.ts +28 -7
- package/src/components/editor/first-person/ground-holes.test.ts +128 -0
- package/src/components/editor/first-person/ground-holes.ts +130 -0
- package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
- package/src/components/editor/first-person/mechanism-targets.ts +88 -0
- package/src/components/editor/first-person-controls.tsx +220 -9
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +466 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +414 -363
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +56 -2
- package/src/components/editor/group-move-3d.ts +2 -0
- package/src/components/editor/group-rotate-handle.tsx +2 -0
- package/src/components/editor/group-transform-shared.test.ts +460 -0
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +409 -197
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/riser-diagram-panel.tsx +14 -7
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +233 -0
- package/src/components/editor/room-labels.test.ts +850 -0
- package/src/components/editor/room-labels.tsx +1890 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +474 -419
- package/src/components/editor/slab-hole-highlights.tsx +5 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
- package/src/components/editor/snapshot-capture.test.ts +119 -60
- package/src/components/editor/snapshot-capture.ts +36 -5
- package/src/components/editor/thumbnail-generator.test.ts +654 -0
- package/src/components/editor/thumbnail-generator.tsx +1103 -223
- package/src/components/editor/use-floorplan-background-placement.ts +34 -12
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +663 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +343 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +8 -4
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +319 -233
- package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
- package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/point-ask/bubble.tsx +538 -0
- package/src/components/point-ask/hover-chip.tsx +91 -0
- package/src/components/point-ask/mode-pill.tsx +92 -0
- package/src/components/point-ask/optional-motion.ts +22 -0
- package/src/components/point-ask/outline-corners.tsx +93 -0
- package/src/components/point-ask/pins.tsx +711 -0
- package/src/components/point-ask/point-ask-layer.tsx +83 -0
- package/src/components/point-ask/point-ask-styles.ts +105 -0
- package/src/components/point-ask/region-marquee-styles.ts +15 -0
- package/src/components/point-ask/region-marquee.tsx +64 -0
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/roof/roof-edit-system.tsx +1 -0
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
- package/src/components/tools/distribution/connection-compatibility.ts +43 -0
- package/src/components/tools/distribution/distribution-public.ts +9 -0
- package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
- package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
- package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
- package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
- package/src/components/tools/distribution/draw-alignment.ts +35 -0
- package/src/components/tools/distribution/ports.test.ts +56 -0
- package/src/components/tools/distribution/ports.ts +378 -0
- package/src/components/tools/distribution/run-cursor.test.ts +135 -0
- package/src/components/tools/distribution/run-cursor.ts +36 -0
- package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
- package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
- package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
- package/src/components/tools/distribution/run-port-snap.ts +25 -0
- package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
- package/src/components/tools/distribution/run-screen-direction.ts +30 -0
- package/src/components/tools/elevator/elevator-tool.tsx +3 -6
- package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
- package/src/components/tools/item/placement-history.test.tsx +512 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +11 -10
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +148 -95
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/item-surface-move.test.ts +62 -63
- package/src/components/tools/registry/item-surface-move.ts +2 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
- package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/tool-manager.tsx +59 -24
- package/src/components/tools/wall/wall-drafting.test.ts +175 -47
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +9 -1
- package/src/components/ui/action-menu/camera-actions.tsx +9 -4
- package/src/components/ui/action-menu/control-modes.tsx +15 -42
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
- package/src/components/ui/action-menu/index.tsx +56 -33
- package/src/components/ui/action-menu/measurement-control.tsx +14 -19
- package/src/components/ui/action-menu/placement.ts +20 -0
- package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
- package/src/components/ui/action-menu/view-toggles.tsx +47 -42
- package/src/components/ui/build-panel/build-panel.tsx +242 -0
- package/src/components/ui/command-palette/editor-commands.tsx +7 -42
- package/src/components/ui/command-palette/index.tsx +50 -39
- package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
- package/src/components/ui/controls/action-button.tsx +5 -2
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/metric-control.tsx +1 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/controls/slider-control.tsx +1 -1
- package/src/components/ui/controls/toggle-control.tsx +3 -3
- package/src/components/ui/controls/tool-options-panel.tsx +1 -1
- package/src/components/ui/floating-level-selector.tsx +159 -59
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
- package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
- package/src/components/ui/helpers/helper-manager.tsx +331 -14
- package/src/components/ui/helpers/item-helper.tsx +17 -1
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +28 -22
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +4 -4
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +161 -5
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +130 -62
- package/src/components/ui/panels/parametric-field-control.tsx +3 -3
- package/src/components/ui/panels/parametric-inspector.tsx +7 -7
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +790 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/badge.tsx +8 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +3 -1
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +164 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +8 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
- package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +181 -223
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -37
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -4
- package/src/components/ui/sidebar/tab-bar.tsx +14 -4
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +191 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +215 -43
- package/src/hooks/use-auto-save.ts +60 -9
- package/src/hooks/use-ceiling-events.test.ts +123 -118
- package/src/hooks/use-grid-events.test.ts +105 -105
- package/src/hooks/use-keyboard.test.ts +243 -19
- package/src/hooks/use-keyboard.ts +118 -83
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +71 -0
- package/src/hooks/use-placement-notice.ts +73 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +312 -58
- package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
- package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
- package/src/lib/add-storey.test.ts +177 -0
- package/src/lib/add-storey.ts +59 -0
- package/src/lib/below-grade.test.tsx +171 -0
- package/src/lib/below-grade.ts +133 -0
- package/src/lib/camera-facing.test.ts +24 -0
- package/src/lib/camera-facing.ts +24 -0
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/direct-manipulation.ts +2 -0
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +55 -0
- package/src/lib/editor-views.ts +178 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/export-texture-utils.ts +2 -4
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +284 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +363 -27
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +124 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +230 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +157 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
- package/src/lib/floorplan/site-plan/geometry.ts +142 -0
- package/src/lib/floorplan/site-plan/index.ts +55 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +287 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +70 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
- package/src/lib/follow-pascal-exterior.test.ts +171 -0
- package/src/lib/follow-pascal-exterior.ts +39 -0
- package/src/lib/follow-pascal-presentation.test.ts +203 -0
- package/src/lib/follow-pascal-presentation.ts +129 -0
- package/src/lib/follow-pascal-sight.ts +84 -0
- package/src/lib/follow-pascal-targets.test.ts +406 -0
- package/src/lib/follow-pascal-targets.ts +198 -0
- package/src/lib/follow-pascal.test.ts +350 -0
- package/src/lib/follow-pascal.ts +284 -0
- package/src/lib/fresh-planar-placement.test.ts +148 -0
- package/src/lib/fresh-planar-placement.ts +114 -37
- package/src/lib/geometry-script/author.ts +157 -0
- package/src/lib/geometry-script/client.ts +159 -0
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +13 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +636 -126
- package/src/lib/glb-export.ts +241 -120
- package/src/lib/history.test.ts +18 -7
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +22 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +187 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +392 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +201 -0
- package/src/lib/orbit-floor-clearance.ts +154 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +122 -0
- package/src/lib/paint-pass-through.ts +78 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +267 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +16 -7
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +65 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
- package/src/lib/point-ask/__fixtures__/house.ts +81 -0
- package/src/lib/point-ask/ask-created.test.ts +195 -0
- package/src/lib/point-ask/ask-flow.test.ts +380 -0
- package/src/lib/point-ask/build-context.test.ts +173 -0
- package/src/lib/point-ask/build-context.ts +94 -0
- package/src/lib/point-ask/capture-crop.ts +129 -0
- package/src/lib/point-ask/choreography.ts +69 -0
- package/src/lib/point-ask/compare.test.ts +145 -0
- package/src/lib/point-ask/compare.ts +73 -0
- package/src/lib/point-ask/describe-target.test.ts +165 -0
- package/src/lib/point-ask/describe-target.ts +234 -0
- package/src/lib/point-ask/dictation.ts +79 -0
- package/src/lib/point-ask/edge-marker.test.ts +61 -0
- package/src/lib/point-ask/edge-marker.ts +27 -0
- package/src/lib/point-ask/frame-loop.ts +25 -0
- package/src/lib/point-ask/history-marks.test.ts +94 -0
- package/src/lib/point-ask/history-marks.ts +39 -0
- package/src/lib/point-ask/host-api.ts +32 -0
- package/src/lib/point-ask/outline-box.test.ts +30 -0
- package/src/lib/point-ask/outline-box.ts +11 -0
- package/src/lib/point-ask/overlay-metrics.ts +6 -0
- package/src/lib/point-ask/pick.test.ts +220 -0
- package/src/lib/point-ask/pick.ts +121 -0
- package/src/lib/point-ask/pin-machine.test.ts +494 -0
- package/src/lib/point-ask/pin-machine.ts +217 -0
- package/src/lib/point-ask/placement.test.ts +203 -0
- package/src/lib/point-ask/placement.ts +141 -0
- package/src/lib/point-ask/pointer-tracker.ts +34 -0
- package/src/lib/point-ask/projector.ts +83 -0
- package/src/lib/point-ask/region.test.ts +157 -0
- package/src/lib/point-ask/region.ts +136 -0
- package/src/lib/point-ask/retry.test.ts +217 -0
- package/src/lib/point-ask/retry.ts +37 -0
- package/src/lib/point-ask/scene-commit.test.ts +312 -0
- package/src/lib/point-ask/scene-commit.ts +32 -0
- package/src/lib/point-ask/session.ts +1391 -0
- package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
- package/src/lib/point-ask/spring-keyframes.ts +38 -0
- package/src/lib/point-ask/spring.test.ts +63 -0
- package/src/lib/point-ask/spring.ts +107 -0
- package/src/lib/point-ask/suggestions.test.ts +80 -0
- package/src/lib/point-ask/suggestions.ts +125 -0
- package/src/lib/point-ask/target-scope.test.ts +622 -0
- package/src/lib/point-ask/target-scope.ts +338 -0
- package/src/lib/point-ask/types.ts +48 -0
- package/src/lib/portable-export.test.ts +34 -1
- package/src/lib/portable-export.ts +119 -53
- package/src/lib/print-feature-thickness.ts +10 -1
- package/src/lib/print-roof-solids.test.ts +39 -1
- package/src/lib/print-roof-solids.ts +43 -10
- package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
- package/src/lib/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +265 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +1017 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +610 -1
- package/src/lib/scene-clipboard.ts +269 -58
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +54 -75
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sfx-bus.ts +13 -0
- package/src/lib/sfx-player.test.ts +70 -9
- package/src/lib/sfx-player.ts +6 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +214 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +580 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-changes.ts +35 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-audio.test.ts +146 -0
- package/src/store/use-audio.tsx +127 -3
- package/src/store/use-construction-reveal.test.ts +173 -0
- package/src/store/use-construction-reveal.ts +145 -0
- package/src/store/use-corner-snap-hint.test.ts +24 -0
- package/src/store/use-corner-snap-hint.ts +47 -0
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-follow-pascal.test.ts +62 -0
- package/src/store/use-follow-pascal.ts +53 -0
- package/src/store/use-point-ask.ts +247 -0
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +33 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +201 -0
- package/src/store/view-layout.ts +199 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/render-current-markup.tsx +16 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
- package/src/components/tools/stair/stair-click-guard.ts +0 -72
- package/src/components/tools/stair/stair-defaults.ts +0 -18
- package/src/components/tools/stair/stair-tool.tsx +0 -634
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- package/src/lib/level-duplication.ts +0 -195
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
type AnyNode,
|
|
4
5
|
type AnyNodeId,
|
|
5
6
|
type CameraControlEvent,
|
|
6
7
|
type CameraControlFitSceneEvent,
|
|
8
|
+
type CameraControlFrameEvent,
|
|
7
9
|
type CameraPose,
|
|
10
|
+
deepestDrawnY,
|
|
8
11
|
emitter,
|
|
12
|
+
groundHeightAt,
|
|
13
|
+
lowestGroundY,
|
|
9
14
|
sceneRegistry,
|
|
10
15
|
useScene,
|
|
11
16
|
} from '@pascal-app/core'
|
|
12
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
GRID_LAYER,
|
|
19
|
+
getLevelPresentationY,
|
|
20
|
+
useSceneGroundReplacement,
|
|
21
|
+
useViewer,
|
|
22
|
+
ZONE_LAYER,
|
|
23
|
+
} from '@pascal-app/viewer'
|
|
13
24
|
import { CameraControls, CameraControlsImpl } from '@react-three/drei'
|
|
14
25
|
import { useFrame, useThree } from '@react-three/fiber'
|
|
15
26
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
@@ -33,24 +44,37 @@ import {
|
|
|
33
44
|
import { EDITOR_LAYER } from '../../lib/constants'
|
|
34
45
|
import {
|
|
35
46
|
acceptsKeyboardPan,
|
|
47
|
+
advanceKeyboardPanMotion,
|
|
36
48
|
clearKeyboardPanKeys,
|
|
49
|
+
createKeyboardPanMotion,
|
|
37
50
|
hasKeyboardPanInput,
|
|
38
51
|
isEditableKeyboardTarget,
|
|
39
52
|
isKeyboardPanKey,
|
|
40
53
|
type KeyboardPanState,
|
|
41
|
-
keyboardPanDirection,
|
|
42
54
|
keyboardPanSpeed,
|
|
43
55
|
setKeyboardPanKey,
|
|
56
|
+
syncKeyboardPanMotion,
|
|
44
57
|
} from '../../lib/keyboard-pan'
|
|
58
|
+
import {
|
|
59
|
+
diveFloorY,
|
|
60
|
+
MAX_ORBIT_POLAR_ANGLE,
|
|
61
|
+
ORBIT_TARGET_HEIGHT,
|
|
62
|
+
orbitFloorMinY,
|
|
63
|
+
overviewEyeY,
|
|
64
|
+
} from '../../lib/orbit-floor-clearance'
|
|
45
65
|
import { editorOwnsOneFingerDrag } from '../../lib/touch-gesture-priority'
|
|
66
|
+
import { frameViewerCamera } from '../../lib/viewer-selection'
|
|
46
67
|
import { publishCameraPose } from '../../store/camera-pose-store'
|
|
47
|
-
import useEditor from '../../store/use-editor'
|
|
68
|
+
import useEditor, { takePreviewCameraRestore } from '../../store/use-editor'
|
|
48
69
|
import {
|
|
49
70
|
useActiveHandleDrag,
|
|
50
71
|
useEndpointReshape,
|
|
51
72
|
useMovingNode,
|
|
52
73
|
} from '../../store/use-interaction-scope'
|
|
74
|
+
import { visibleScene } from '../../store/view-layout'
|
|
53
75
|
import { createCameraDraggingLifecycle } from './camera-dragging-lifecycle'
|
|
76
|
+
import { FloorAwareCameraControls } from './floor-aware-camera-controls'
|
|
77
|
+
import { FloorSurface } from './floor-surface'
|
|
54
78
|
|
|
55
79
|
const currentTarget = new Vector3()
|
|
56
80
|
const tempBox = new Box3()
|
|
@@ -62,11 +86,49 @@ const tempTarget = new Vector3()
|
|
|
62
86
|
const transitionFreezePosition = new Vector3()
|
|
63
87
|
const transitionFreezeTarget = new Vector3()
|
|
64
88
|
const keyboardPanSpherical = new Spherical()
|
|
89
|
+
/** Mouse/touch orbit speed (camera-controls default 1): slower for precise looks. */
|
|
90
|
+
const ORBIT_ROTATE_SPEED = 0.6
|
|
65
91
|
// In 2D-only view the canvas is paused, so the floor plan drives WASD, orbit and
|
|
66
92
|
// top view itself (`floorplan-panel.tsx`) and the camera stands down.
|
|
67
|
-
const planOwnsNavigation = () => useEditor.getState()
|
|
93
|
+
const planOwnsNavigation = () => visibleScene(useEditor.getState()) === '2d'
|
|
68
94
|
const DEFAULT_MAX_POLAR_ANGLE = Math.PI / 2 - 0.1
|
|
69
|
-
|
|
95
|
+
// Focusing one of these aims at its floor (raised to the orbit's pivot
|
|
96
|
+
// height), not at the middle of its bounding box.
|
|
97
|
+
const GROUND_FOCUS_TYPES = new Set(['site', 'building', 'level', 'zone', 'slab'])
|
|
98
|
+
|
|
99
|
+
type LevelMode = ReturnType<typeof useViewer.getState>['levelMode']
|
|
100
|
+
|
|
101
|
+
/** Where the orbit pivot rests on a level: its floor plus the pivot height. */
|
|
102
|
+
function orbitGroundY(
|
|
103
|
+
levelId: string | null | undefined,
|
|
104
|
+
levelMode: LevelMode = useViewer.getState().levelMode,
|
|
105
|
+
) {
|
|
106
|
+
const floorY = levelId ? getLevelPresentationY(levelId, useScene.getState().nodes, levelMode) : 0
|
|
107
|
+
return floorY + ORBIT_TARGET_HEIGHT
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* `eyeY` for an overview eye at (x, z) whose pivot is being sent to another level: kept over the
|
|
112
|
+
* ground it was over (a deep level shown is looked at through the hole, not from under the lid),
|
|
113
|
+
* unless the person has dived.
|
|
114
|
+
*/
|
|
115
|
+
function keepEyeOverGround(x: number, eyeY: number, z: number, previousEyeY = eyeY): number {
|
|
116
|
+
const { nodes } = useScene.getState()
|
|
117
|
+
const siteId = sceneRegistry.byType.site?.values().next().value as AnyNodeId | undefined
|
|
118
|
+
const groundY = groundHeightAt(siteId ? nodes[siteId] : null, x, z)
|
|
119
|
+
return overviewEyeY(eyeY, previousEyeY, groundY, useViewer.getState().eyeUnderGround)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function levelOf(nodeId: string): string | null {
|
|
123
|
+
const nodes = useScene.getState().nodes
|
|
124
|
+
let node: AnyNode | undefined = nodes[nodeId as AnyNodeId]
|
|
125
|
+
while (node) {
|
|
126
|
+
if (node.type === 'level') return node.id
|
|
127
|
+
node = node.parentId ? nodes[node.parentId as AnyNodeId] : undefined
|
|
128
|
+
}
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
70
132
|
type CameraMode = ReturnType<typeof useViewer.getState>['cameraMode']
|
|
71
133
|
type CameraPoseSnapshot = {
|
|
72
134
|
mode: CameraMode
|
|
@@ -311,7 +373,7 @@ function useFirstPersonCameraPoseRestore(
|
|
|
311
373
|
}
|
|
312
374
|
|
|
313
375
|
export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) => {
|
|
314
|
-
const controls = useRef<
|
|
376
|
+
const controls = useRef<FloorAwareCameraControls | null>(null)
|
|
315
377
|
const pendingAppliedPose = useRef<CameraPoseApplicationPlan | null>(null)
|
|
316
378
|
const activePoseInterpolation = useRef<{
|
|
317
379
|
camera: Camera
|
|
@@ -325,6 +387,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
325
387
|
left: false,
|
|
326
388
|
right: false,
|
|
327
389
|
})
|
|
390
|
+
const keyboardPanMotion = useRef(createKeyboardPanMotion())
|
|
328
391
|
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
|
329
392
|
const isFirstPersonMode = useEditor((s) => s.isFirstPersonMode)
|
|
330
393
|
const allowUndergroundCamera = useEditor((s) => s.allowUndergroundCamera)
|
|
@@ -337,12 +400,40 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
337
400
|
isFirstPersonMode,
|
|
338
401
|
cameraMode,
|
|
339
402
|
)
|
|
340
|
-
|
|
403
|
+
// Selecting the building or site clears the level; the camera keeps
|
|
404
|
+
// standing on the last level rather than dropping to the ground floor.
|
|
405
|
+
const lastLevelId = useRef<string | null>(null)
|
|
406
|
+
if (selection.levelId) lastLevelId.current = selection.levelId
|
|
407
|
+
const currentLevelId = lastLevelId.current
|
|
341
408
|
const firstLoad = useRef(true)
|
|
409
|
+
const floorY = useScene((state) =>
|
|
410
|
+
currentLevelId ? getLevelPresentationY(currentLevelId, state.nodes, levelMode) : 0,
|
|
411
|
+
)
|
|
412
|
+
const allowUnderground = !isPreviewMode && allowUndergroundCamera
|
|
413
|
+
// With the ground on Auto the eye may dive under any building, down to the dive's floor; Solid
|
|
414
|
+
// keeps the eye over the ground.
|
|
415
|
+
const groundMode = useViewer((state) => state.groundMode)
|
|
416
|
+
const diveFloor = useScene((state) =>
|
|
417
|
+
groundMode === 'auto'
|
|
418
|
+
? diveFloorY(lowestGroundY(state.nodes), deepestDrawnY(state.nodes))
|
|
419
|
+
: null,
|
|
420
|
+
)
|
|
421
|
+
const floorMinY = allowUnderground ? null : orbitFloorMinY(floorY, diveFloor)
|
|
422
|
+
// The viewer mounts the eye's fill light before the first dive, so crossing the ground compiles nothing.
|
|
423
|
+
const mayDive = allowUnderground || diveFloor !== null
|
|
424
|
+
useEffect(() => {
|
|
425
|
+
useViewer.setState({ diveAvailable: mayDive })
|
|
426
|
+
return () => {
|
|
427
|
+
useViewer.setState({ diveAvailable: false })
|
|
428
|
+
}
|
|
429
|
+
}, [mayDive])
|
|
342
430
|
const maxPolarAngle =
|
|
343
|
-
|
|
431
|
+
allowUnderground || cameraMode === 'perspective'
|
|
432
|
+
? MAX_ORBIT_POLAR_ANGLE
|
|
433
|
+
: DEFAULT_MAX_POLAR_ANGLE
|
|
344
434
|
|
|
345
435
|
const camera = useThree((state) => state.camera)
|
|
436
|
+
const scene = useThree((state) => state.scene)
|
|
346
437
|
const gl = useThree((state) => state.gl)
|
|
347
438
|
const raycaster = useThree((state) => state.raycaster)
|
|
348
439
|
const viewportSize = useThree((state) => state.size)
|
|
@@ -381,7 +472,10 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
381
472
|
const beginLocalCameraInteraction = useCallback(
|
|
382
473
|
({ dragging = true }: { dragging?: boolean } = {}) => {
|
|
383
474
|
cancelPoseApplication()
|
|
384
|
-
if (dragging)
|
|
475
|
+
if (dragging) {
|
|
476
|
+
controls.current?.releaseWrittenPose()
|
|
477
|
+
cameraDraggingLifecycle.begin()
|
|
478
|
+
}
|
|
385
479
|
emitter.emit('camera-controls:interaction-start', undefined)
|
|
386
480
|
},
|
|
387
481
|
[cameraDraggingLifecycle, cancelPoseApplication],
|
|
@@ -469,11 +563,21 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
469
563
|
|
|
470
564
|
emitter.on('camera-controls:apply-pose', handleAppliedPose)
|
|
471
565
|
emitter.on('camera-controls:cancel-pose', cancelPoseApplication)
|
|
566
|
+
if (!isPreviewMode) {
|
|
567
|
+
const restore = takePreviewCameraRestore()
|
|
568
|
+
if (restore) handleAppliedPose(restore)
|
|
569
|
+
}
|
|
472
570
|
return () => {
|
|
473
571
|
emitter.off('camera-controls:apply-pose', handleAppliedPose)
|
|
474
572
|
emitter.off('camera-controls:cancel-pose', cancelPoseApplication)
|
|
475
573
|
}
|
|
476
|
-
}, [
|
|
574
|
+
}, [
|
|
575
|
+
applyPendingPose,
|
|
576
|
+
cancelPoseApplication,
|
|
577
|
+
freezeActivePoseInterpolation,
|
|
578
|
+
isFirstPersonMode,
|
|
579
|
+
isPreviewMode,
|
|
580
|
+
])
|
|
477
581
|
|
|
478
582
|
useEffect(() => cancelPoseApplication, [cancelPoseApplication])
|
|
479
583
|
|
|
@@ -484,33 +588,87 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
484
588
|
if (process.env.NODE_ENV !== 'development') return
|
|
485
589
|
const w = window as typeof window & {
|
|
486
590
|
__pascalCameraControls?: (() => CameraControlsImpl | null) | null
|
|
591
|
+
__pascalScene?: (() => unknown) | null
|
|
592
|
+
__pascalCapture?: (() => Promise<string>) | null
|
|
487
593
|
}
|
|
488
594
|
w.__pascalCameraControls = () => controls.current
|
|
595
|
+
// the live three.js scene, for the same tooling (a probe can walk the
|
|
596
|
+
// meshes for an empty geometry the WebGPU validator complains about)
|
|
597
|
+
w.__pascalScene = () => scene
|
|
598
|
+
// a fresh frame read back as a PNG data URL: the canvas is cleared after
|
|
599
|
+
// each presented frame, so a capture has to render and read in one task
|
|
600
|
+
w.__pascalCapture = async () => {
|
|
601
|
+
const r = gl as unknown as {
|
|
602
|
+
renderAsync?: (s: unknown, c: unknown) => Promise<void>
|
|
603
|
+
render: (s: unknown, c: unknown) => void
|
|
604
|
+
domElement: HTMLCanvasElement
|
|
605
|
+
}
|
|
606
|
+
if (r.renderAsync) await r.renderAsync(scene, camera)
|
|
607
|
+
else r.render(scene, camera)
|
|
608
|
+
return r.domElement.toDataURL('image/png')
|
|
609
|
+
}
|
|
489
610
|
return () => {
|
|
490
611
|
w.__pascalCameraControls = null
|
|
612
|
+
w.__pascalScene = null
|
|
613
|
+
w.__pascalCapture = null
|
|
491
614
|
}
|
|
492
|
-
}, [])
|
|
615
|
+
}, [scene, gl, camera])
|
|
493
616
|
|
|
617
|
+
// `camera` too: drei builds a fresh controls instance (pivot at the origin)
|
|
618
|
+
// when the default camera mounts or changes; it is rested on the floor too.
|
|
619
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: camera re-runs this on a new instance
|
|
494
620
|
useEffect(() => {
|
|
495
621
|
if (isPreviewMode || isFirstPersonMode || isRestoringFirstPersonPose()) return
|
|
496
622
|
// Analytic destination, not `sceneRegistry` mesh position: a level created
|
|
497
623
|
// this frame still sits at y=0 (LevelSystem lerps it later), and a mode
|
|
498
624
|
// switch leaves every level mid-lerp — the camera must pan to where the
|
|
499
625
|
// level will settle, in the CURRENT presentation mode.
|
|
500
|
-
const targetY = currentLevelId
|
|
501
|
-
? getLevelPresentationY(currentLevelId, useScene.getState().nodes, levelMode)
|
|
502
|
-
: 0
|
|
626
|
+
const targetY = orbitGroundY(currentLevelId, levelMode)
|
|
503
627
|
if (!controls.current) return
|
|
504
628
|
if (firstLoad.current) {
|
|
505
629
|
firstLoad.current = false
|
|
506
|
-
controls.current.setLookAt(
|
|
630
|
+
controls.current.setLookAt(
|
|
631
|
+
20,
|
|
632
|
+
keepEyeOverGround(20, 20 + targetY, 20, 20),
|
|
633
|
+
20,
|
|
634
|
+
0,
|
|
635
|
+
targetY,
|
|
636
|
+
0,
|
|
637
|
+
true,
|
|
638
|
+
)
|
|
507
639
|
}
|
|
508
|
-
controls.current.
|
|
640
|
+
controls.current.getOrbitTarget(currentTarget)
|
|
509
641
|
// Idempotence guard: skip when already there — also swallows the thumbnail
|
|
510
642
|
// generator's synchronous stacked→restore levelMode round-trip.
|
|
511
643
|
if (Math.abs(currentTarget.y - targetY) < 1e-3) return
|
|
644
|
+
// The pivot goes down with the level; the eye follows it unless that would sink it under the
|
|
645
|
+
// ground (a basement four storeys down): then it stays over the ground, looking down.
|
|
646
|
+
controls.current.getPosition(tempPosition, false)
|
|
647
|
+
const movedEyeY = tempPosition.y + targetY - currentTarget.y
|
|
648
|
+
const eyeY = isPerspectiveCamera(camera)
|
|
649
|
+
? keepEyeOverGround(tempPosition.x, movedEyeY, tempPosition.z, tempPosition.y)
|
|
650
|
+
: movedEyeY
|
|
651
|
+
if (eyeY > movedEyeY) {
|
|
652
|
+
controls.current.setLookAt(
|
|
653
|
+
tempPosition.x,
|
|
654
|
+
eyeY,
|
|
655
|
+
tempPosition.z,
|
|
656
|
+
currentTarget.x,
|
|
657
|
+
targetY,
|
|
658
|
+
currentTarget.z,
|
|
659
|
+
true,
|
|
660
|
+
)
|
|
661
|
+
return
|
|
662
|
+
}
|
|
512
663
|
controls.current.moveTo(currentTarget.x, targetY, currentTarget.z, true)
|
|
513
|
-
}, [
|
|
664
|
+
}, [
|
|
665
|
+
camera,
|
|
666
|
+
currentLevelId,
|
|
667
|
+
levelMode,
|
|
668
|
+
isPreviewMode,
|
|
669
|
+
isFirstPersonMode,
|
|
670
|
+
isRestoringFirstPersonPose,
|
|
671
|
+
])
|
|
514
672
|
|
|
515
673
|
useEffect(() => {
|
|
516
674
|
if (isFirstPersonMode || !controls.current) return
|
|
@@ -523,6 +681,16 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
523
681
|
}
|
|
524
682
|
}, [isFirstPersonMode, maxPolarAngle])
|
|
525
683
|
|
|
684
|
+
const floorSurface = useMemo(() => new FloorSurface(), [])
|
|
685
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: drei builds a new controls instance per camera
|
|
686
|
+
useLayoutEffect(() => {
|
|
687
|
+
floorSurface.levelId = currentLevelId ?? null
|
|
688
|
+
if (!controls.current) return
|
|
689
|
+
controls.current.floorMinY = floorMinY
|
|
690
|
+
// The level's floor and the ground hold the eye over them; the dive goes through both.
|
|
691
|
+
controls.current.floorSurface = diveFloor === null ? floorSurface : null
|
|
692
|
+
}, [camera, currentLevelId, diveFloor, floorMinY, floorSurface, isFirstPersonMode])
|
|
693
|
+
|
|
526
694
|
const focusNode = useCallback(
|
|
527
695
|
(nodeId: string) => {
|
|
528
696
|
if (isPreviewMode || isFirstPersonMode || !controls.current) return
|
|
@@ -530,17 +698,38 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
530
698
|
const object3D = sceneRegistry.nodes.get(nodeId)
|
|
531
699
|
if (!object3D) return
|
|
532
700
|
|
|
701
|
+
const node = useScene.getState().nodes[nodeId as AnyNodeId]
|
|
702
|
+
const groundFocus = node !== undefined && GROUND_FOCUS_TYPES.has(node.type)
|
|
533
703
|
tempBox.setFromObject(object3D)
|
|
534
|
-
if (tempBox.isEmpty())
|
|
535
|
-
|
|
536
|
-
|
|
704
|
+
if (!tempBox.isEmpty()) {
|
|
705
|
+
tempBox.getCenter(tempCenter)
|
|
706
|
+
} else if (groundFocus && node.type === 'zone' && node.polygon.length > 0) {
|
|
707
|
+
// A room draws nothing measurable in some display modes: aim at the
|
|
708
|
+
// middle of its outline instead.
|
|
709
|
+
tempBox.makeEmpty()
|
|
710
|
+
for (const [x, z] of node.polygon) tempBox.expandByPoint(tempCenter.set(x, 0, z))
|
|
711
|
+
tempBox.getCenter(tempCenter)
|
|
712
|
+
object3D.updateWorldMatrix(true, false)
|
|
713
|
+
tempCenter.applyMatrix4(object3D.matrixWorld)
|
|
714
|
+
} else {
|
|
715
|
+
return
|
|
716
|
+
}
|
|
717
|
+
if (groundFocus) {
|
|
718
|
+
tempCenter.y = orbitGroundY(levelOf(nodeId) ?? lastLevelId.current)
|
|
719
|
+
}
|
|
537
720
|
controls.current.getPosition(tempPosition)
|
|
538
721
|
controls.current.getTarget(tempTarget)
|
|
539
722
|
tempDelta.copy(tempCenter).sub(tempTarget)
|
|
540
723
|
|
|
724
|
+
const focusedX = tempPosition.x + tempDelta.x
|
|
725
|
+
const focusedZ = tempPosition.z + tempDelta.z
|
|
726
|
+
const focusedY = isPerspectiveCamera(camera)
|
|
727
|
+
? keepEyeOverGround(focusedX, tempPosition.y + tempDelta.y, focusedZ, tempPosition.y)
|
|
728
|
+
: tempPosition.y + tempDelta.y
|
|
729
|
+
const floorMinY = isPerspectiveCamera(camera) ? controls.current.floorLimitY : null
|
|
541
730
|
controls.current.setLookAt(
|
|
542
731
|
tempPosition.x + tempDelta.x,
|
|
543
|
-
|
|
732
|
+
floorMinY === null ? focusedY : Math.max(focusedY, floorMinY),
|
|
544
733
|
tempPosition.z + tempDelta.z,
|
|
545
734
|
tempCenter.x,
|
|
546
735
|
tempCenter.y,
|
|
@@ -548,7 +737,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
548
737
|
true,
|
|
549
738
|
)
|
|
550
739
|
},
|
|
551
|
-
[isPreviewMode, isFirstPersonMode],
|
|
740
|
+
[camera, isPreviewMode, isFirstPersonMode],
|
|
552
741
|
)
|
|
553
742
|
|
|
554
743
|
const publishCurrentPose = useCallback(() => {
|
|
@@ -630,18 +819,18 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
630
819
|
}
|
|
631
820
|
}
|
|
632
821
|
|
|
633
|
-
const
|
|
634
|
-
if (
|
|
822
|
+
const motion = keyboardPanMotion.current
|
|
823
|
+
if (!advanceKeyboardPanMotion(motion, keyboardPanKeys.current, performance.now())) return
|
|
635
824
|
|
|
636
825
|
const control = controls.current
|
|
637
826
|
|
|
638
827
|
control.getSpherical(keyboardPanSpherical, false)
|
|
639
828
|
const viewWidth = getCameraViewWidth(camera, keyboardPanSpherical.radius, viewportSize)
|
|
640
829
|
const speed = keyboardPanSpeed(viewWidth)
|
|
641
|
-
const step = (speed * Math.min(delta, 0.05)) / Math.hypot(horizontal, vertical)
|
|
642
830
|
|
|
643
|
-
|
|
644
|
-
if (
|
|
831
|
+
// The motion already eases; the controls' own smoothing would add a glide on top.
|
|
832
|
+
if (motion.stepX !== 0) control.truck(motion.stepX * speed, 0, false)
|
|
833
|
+
if (motion.stepY !== 0) control.forward(motion.stepY * speed, false)
|
|
645
834
|
}, 0)
|
|
646
835
|
|
|
647
836
|
// Configure mouse buttons based on control mode and camera mode
|
|
@@ -652,13 +841,14 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
652
841
|
? CameraControlsImpl.ACTION.ZOOM
|
|
653
842
|
: CameraControlsImpl.ACTION.DOLLY
|
|
654
843
|
|
|
844
|
+
// Left click selects, in Preview as in the published viewer.
|
|
655
845
|
return {
|
|
656
|
-
left:
|
|
846
|
+
left: CameraControlsImpl.ACTION.NONE,
|
|
657
847
|
middle: CameraControlsImpl.ACTION.SCREEN_PAN,
|
|
658
848
|
right: CameraControlsImpl.ACTION.ROTATE,
|
|
659
849
|
wheel: wheelAction,
|
|
660
850
|
}
|
|
661
|
-
}, [cameraMode
|
|
851
|
+
}, [cameraMode])
|
|
662
852
|
|
|
663
853
|
// Touch gestures (mobile / trackpad).
|
|
664
854
|
// - One finger drag → rotate by default (much easier on a phone), but
|
|
@@ -774,10 +964,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
774
964
|
controls.current.mouseButtons.wheel = wheelAction
|
|
775
965
|
controls.current.mouseButtons.middle = CameraControlsImpl.ACTION.SCREEN_PAN
|
|
776
966
|
controls.current.mouseButtons.right = CameraControlsImpl.ACTION.ROTATE
|
|
777
|
-
if (
|
|
778
|
-
// In preview mode, left-click is always pan (viewer-style)
|
|
779
|
-
controls.current.mouseButtons.left = CameraControlsImpl.ACTION.SCREEN_PAN
|
|
780
|
-
} else if (space) {
|
|
967
|
+
if (space) {
|
|
781
968
|
controls.current.mouseButtons.left = CameraControlsImpl.ACTION.SCREEN_PAN
|
|
782
969
|
} else {
|
|
783
970
|
controls.current.mouseButtons.left = CameraControlsImpl.ACTION.NONE
|
|
@@ -787,6 +974,11 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
787
974
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
788
975
|
if (isKeyboardPanKey(event.code)) {
|
|
789
976
|
if (acceptsKeyboardPan(event) && !planOwnsNavigation()) {
|
|
977
|
+
syncKeyboardPanMotion(
|
|
978
|
+
keyboardPanMotion.current,
|
|
979
|
+
keyboardPanKeys.current,
|
|
980
|
+
performance.now(),
|
|
981
|
+
)
|
|
790
982
|
const changed = setKeyboardPanKey(keyboardPanKeys.current, event.code, true)
|
|
791
983
|
if (changed) beginLocalCameraInteraction()
|
|
792
984
|
event.preventDefault()
|
|
@@ -818,6 +1010,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
818
1010
|
|
|
819
1011
|
const onKeyUp = (event: KeyboardEvent) => {
|
|
820
1012
|
if (isKeyboardPanKey(event.code)) {
|
|
1013
|
+
syncKeyboardPanMotion(keyboardPanMotion.current, keyboardPanKeys.current, performance.now())
|
|
821
1014
|
const changed = setKeyboardPanKey(keyboardPanKeys.current, event.code, false)
|
|
822
1015
|
if (changed) {
|
|
823
1016
|
if (!hasKeyboardPanInput(keyboardPanKeys.current)) {
|
|
@@ -907,14 +1100,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
907
1100
|
clearNavigationCursor()
|
|
908
1101
|
cameraDraggingLifecycle.end()
|
|
909
1102
|
}
|
|
910
|
-
}, [
|
|
911
|
-
beginLocalCameraInteraction,
|
|
912
|
-
cameraDraggingLifecycle,
|
|
913
|
-
cameraMode,
|
|
914
|
-
gl,
|
|
915
|
-
isPreviewMode,
|
|
916
|
-
isFirstPersonMode,
|
|
917
|
-
])
|
|
1103
|
+
}, [beginLocalCameraInteraction, cameraDraggingLifecycle, cameraMode, gl, isFirstPersonMode])
|
|
918
1104
|
|
|
919
1105
|
// `controlstart` fires only for user pointer interactions. Pointerdowns
|
|
920
1106
|
// mapped to ACTION.NONE must not flag the camera as dragging because no
|
|
@@ -927,18 +1113,18 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
927
1113
|
})
|
|
928
1114
|
}, [beginLocalCameraInteraction])
|
|
929
1115
|
|
|
930
|
-
// Preview
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
: null
|
|
934
|
-
|
|
1116
|
+
// Preview opens on the editor's floor (or building, or site) framed the
|
|
1117
|
+
// viewer's way; afterwards only the viewer navigation moves the camera
|
|
1118
|
+
// (`camera-controls:frame`), never a canvas selection.
|
|
935
1119
|
useEffect(() => {
|
|
936
1120
|
if (!(isPreviewMode && controls.current) || isFirstPersonMode) return
|
|
937
1121
|
|
|
938
1122
|
const nodes = useScene.getState().nodes
|
|
939
|
-
|
|
1123
|
+
const { selection: opening } = useViewer.getState()
|
|
1124
|
+
const openingNodeId = opening.levelId ?? opening.buildingId
|
|
1125
|
+
let node = openingNodeId ? nodes[openingNodeId as AnyNodeId] : null
|
|
940
1126
|
|
|
941
|
-
if (!
|
|
1127
|
+
if (!openingNodeId) {
|
|
942
1128
|
const site = Object.values(nodes).find((n) => n.type === 'site')
|
|
943
1129
|
node = site || null
|
|
944
1130
|
}
|
|
@@ -971,10 +1157,10 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
971
1157
|
return
|
|
972
1158
|
}
|
|
973
1159
|
|
|
974
|
-
if (!
|
|
1160
|
+
if (!openingNodeId) return
|
|
975
1161
|
|
|
976
1162
|
// Calculate camera position from bounding box
|
|
977
|
-
const object3D = sceneRegistry.nodes.get(
|
|
1163
|
+
const object3D = sceneRegistry.nodes.get(openingNodeId)
|
|
978
1164
|
if (!object3D) return
|
|
979
1165
|
|
|
980
1166
|
tempBox.setFromObject(object3D)
|
|
@@ -993,7 +1179,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
993
1179
|
tempCenter.z,
|
|
994
1180
|
true,
|
|
995
1181
|
)
|
|
996
|
-
}, [isPreviewMode, isFirstPersonMode
|
|
1182
|
+
}, [isPreviewMode, isFirstPersonMode])
|
|
997
1183
|
|
|
998
1184
|
// Preset capture auto-framing — when `setCaptureMode({ mode: 'preset',
|
|
999
1185
|
// isolated })` fires, fly the camera to a pose that fits the union
|
|
@@ -1159,7 +1345,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1159
1345
|
if (isFirstPersonMode || !controls.current || planOwnsNavigation()) return
|
|
1160
1346
|
|
|
1161
1347
|
const currentAzimuth = controls.current.azimuthAngle
|
|
1162
|
-
const currentPolar = controls.current.
|
|
1348
|
+
const currentPolar = controls.current.orbitPolarAngle
|
|
1163
1349
|
// Round to nearest 90° increment, then rotate 90° clockwise
|
|
1164
1350
|
const rounded = Math.round(currentAzimuth / (Math.PI / 2)) * (Math.PI / 2)
|
|
1165
1351
|
const target = rounded - Math.PI / 2
|
|
@@ -1171,7 +1357,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1171
1357
|
if (isFirstPersonMode || !controls.current || planOwnsNavigation()) return
|
|
1172
1358
|
|
|
1173
1359
|
const currentAzimuth = controls.current.azimuthAngle
|
|
1174
|
-
const currentPolar = controls.current.
|
|
1360
|
+
const currentPolar = controls.current.orbitPolarAngle
|
|
1175
1361
|
// Round to nearest 90° increment, then rotate 90° counter-clockwise
|
|
1176
1362
|
const rounded = Math.round(currentAzimuth / (Math.PI / 2)) * (Math.PI / 2)
|
|
1177
1363
|
const target = rounded + Math.PI / 2
|
|
@@ -1183,11 +1369,26 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1183
1369
|
focusNode(nodeId)
|
|
1184
1370
|
}
|
|
1185
1371
|
|
|
1372
|
+
const handleFrame = (event: CameraControlFrameEvent) => {
|
|
1373
|
+
if (isFirstPersonMode || !controls.current) return
|
|
1374
|
+
cancelPoseApplication()
|
|
1375
|
+
frameViewerCamera(controls.current, camera, event)
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1186
1378
|
const handleFitScene = ({ bounds }: CameraControlFitSceneEvent) => {
|
|
1187
1379
|
if (isFirstPersonMode || !controls.current || isPreviewMode) return
|
|
1380
|
+
const groundY = orbitGroundY(lastLevelId.current)
|
|
1188
1381
|
if (!bounds) {
|
|
1189
1382
|
// Restore default framing pose when no bounds were computed.
|
|
1190
|
-
controls.current.setLookAt(
|
|
1383
|
+
controls.current.setLookAt(
|
|
1384
|
+
20,
|
|
1385
|
+
keepEyeOverGround(20, 20 + groundY, 20, 20),
|
|
1386
|
+
20,
|
|
1387
|
+
0,
|
|
1388
|
+
groundY,
|
|
1389
|
+
0,
|
|
1390
|
+
true,
|
|
1391
|
+
)
|
|
1191
1392
|
return
|
|
1192
1393
|
}
|
|
1193
1394
|
const [cx, cz] = bounds.center
|
|
@@ -1197,11 +1398,20 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1197
1398
|
const maxExtent = Math.max(w, d)
|
|
1198
1399
|
const distance = Math.max(maxExtent * 1.4, 15)
|
|
1199
1400
|
const height = Math.max(maxExtent * 0.8, 10)
|
|
1200
|
-
controls.current.setLookAt(
|
|
1401
|
+
controls.current.setLookAt(
|
|
1402
|
+
cx + distance * 0.7,
|
|
1403
|
+
keepEyeOverGround(cx + distance * 0.7, height + groundY, cz + distance * 0.7, height),
|
|
1404
|
+
cz + distance * 0.7,
|
|
1405
|
+
cx,
|
|
1406
|
+
groundY,
|
|
1407
|
+
cz,
|
|
1408
|
+
true,
|
|
1409
|
+
)
|
|
1201
1410
|
}
|
|
1202
1411
|
|
|
1203
1412
|
emitter.on('camera-controls:capture', handleNodeCapture)
|
|
1204
1413
|
emitter.on('camera-controls:focus', handleNodeFocus)
|
|
1414
|
+
emitter.on('camera-controls:frame', handleFrame)
|
|
1205
1415
|
emitter.on('camera-controls:view', handleNodeView)
|
|
1206
1416
|
emitter.on('camera-controls:top-view', handleTopView)
|
|
1207
1417
|
emitter.on('camera-controls:orbit-cw', handleOrbitCW)
|
|
@@ -1211,13 +1421,14 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1211
1421
|
return () => {
|
|
1212
1422
|
emitter.off('camera-controls:capture', handleNodeCapture)
|
|
1213
1423
|
emitter.off('camera-controls:focus', handleNodeFocus)
|
|
1424
|
+
emitter.off('camera-controls:frame', handleFrame)
|
|
1214
1425
|
emitter.off('camera-controls:view', handleNodeView)
|
|
1215
1426
|
emitter.off('camera-controls:top-view', handleTopView)
|
|
1216
1427
|
emitter.off('camera-controls:orbit-cw', handleOrbitCW)
|
|
1217
1428
|
emitter.off('camera-controls:orbit-ccw', handleOrbitCCW)
|
|
1218
1429
|
emitter.off('camera-controls:fit-scene', handleFitScene)
|
|
1219
1430
|
}
|
|
1220
|
-
}, [focusNode, isPreviewMode, isFirstPersonMode])
|
|
1431
|
+
}, [camera, cancelPoseApplication, focusNode, isPreviewMode, isFirstPersonMode])
|
|
1221
1432
|
|
|
1222
1433
|
const onTransitionStart = useCallback(() => {
|
|
1223
1434
|
cameraDraggingLifecycle.begin()
|
|
@@ -1248,6 +1459,10 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1248
1459
|
// navigation guardrails.
|
|
1249
1460
|
const isPresetCapture = captureMode.mode === 'preset'
|
|
1250
1461
|
const minDistance = isPresetCapture ? 0.5 : 2
|
|
1462
|
+
// Over modeled or mapped surroundings (a neighbourhood instead of the
|
|
1463
|
+
// horizon disc) the camera may pull back far enough to take it all in.
|
|
1464
|
+
const groundReplaced = useSceneGroundReplacement()
|
|
1465
|
+
const maxDistance = groundReplaced ? 1500 : 100
|
|
1251
1466
|
|
|
1252
1467
|
if (isFirstPersonMode) {
|
|
1253
1468
|
return null
|
|
@@ -1255,8 +1470,10 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1255
1470
|
|
|
1256
1471
|
return (
|
|
1257
1472
|
<CameraControls
|
|
1473
|
+
azimuthRotateSpeed={ORBIT_ROTATE_SPEED}
|
|
1258
1474
|
makeDefault
|
|
1259
|
-
maxDistance={
|
|
1475
|
+
maxDistance={maxDistance}
|
|
1476
|
+
impl={FloorAwareCameraControls}
|
|
1260
1477
|
maxPolarAngle={maxPolarAngle}
|
|
1261
1478
|
minDistance={minDistance}
|
|
1262
1479
|
minPolarAngle={0}
|
|
@@ -1265,6 +1482,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1265
1482
|
onControlStart={handleControlStart}
|
|
1266
1483
|
onUpdate={handleCameraUpdate}
|
|
1267
1484
|
onRest={onRest}
|
|
1485
|
+
polarRotateSpeed={ORBIT_ROTATE_SPEED}
|
|
1268
1486
|
onSleep={onRest}
|
|
1269
1487
|
onTransitionStart={onTransitionStart}
|
|
1270
1488
|
ref={controls}
|