@pascal-app/editor 1.0.5 → 1.0.7
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 +20 -17
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +20 -8
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +72 -61
- package/src/components/editor/floating-action-menu.tsx +27 -9
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +285 -132
- package/src/components/editor/footprint-height-handle.tsx +1 -1
- package/src/components/editor/grid.tsx +24 -15
- package/src/components/editor/group-actions.ts +19 -1
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +27 -2
- package/src/components/editor/group-selection-box-3d.tsx +4 -1
- package/src/components/editor/group-transform-shared.ts +20 -0
- package/src/components/editor/index.tsx +130 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +1 -2
- package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/registry-action-contributions.test.tsx +67 -6
- package/src/components/editor/registry-action-contributions.tsx +115 -15
- package/src/components/editor/riser-diagram-panel.tsx +59 -9
- package/src/components/editor/room-controls.tsx +1 -1
- package/src/components/editor/room-handles-2d.tsx +156 -6
- package/src/components/editor/room-handles.tsx +241 -51
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +29 -15
- package/src/components/editor/selection-manager.tsx +15 -102
- package/src/components/editor/separator-end-grip.tsx +74 -0
- package/src/components/editor/site-edge-labels.tsx +1 -1
- package/src/components/editor/thumbnail-generator.test.ts +15 -6
- package/src/components/editor/thumbnail-generator.tsx +26 -1
- package/src/components/editor/use-floorplan-background-placement.ts +55 -52
- package/src/components/editor/views/view-bar.tsx +111 -26
- package/src/components/editor/wall-measurement-label.tsx +1 -1
- package/src/components/editor/wall-move-side-handles.tsx +5 -1
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +81 -1
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +31 -3
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +22 -13
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +11 -3
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +7 -1
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +12 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +197 -37
- package/src/components/procedural-items/inspector.tsx +59 -12
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
- package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
- package/src/components/tools/distribution/distribution-run-tool.tsx +50 -26
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/placement-strategies.test.ts +64 -1
- package/src/components/tools/item/placement-strategies.ts +5 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +30 -34
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- package/src/components/tools/select/select-candidates.test.ts +45 -0
- package/src/components/tools/select/select-candidates.ts +13 -2
- package/src/components/tools/shared/cursor-sphere.tsx +10 -3
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +1 -1
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/wall/wall-drafting.test.ts +299 -23
- package/src/components/tools/wall/wall-drafting.ts +132 -24
- package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
- package/src/components/tools/wall/wall-pull-snap.ts +563 -0
- package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
- package/src/components/tools/zone/zone-tool.tsx +3 -3
- package/src/components/ui/action-menu/action-button.tsx +2 -0
- package/src/components/ui/action-menu/camera-actions.tsx +8 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -5
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
- package/src/components/ui/action-menu/index.tsx +1 -0
- package/src/components/ui/action-menu/measurement-control.tsx +35 -29
- package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
- package/src/components/ui/action-menu/structure-tools.tsx +25 -23
- package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
- package/src/components/ui/action-menu/view-toggles.tsx +256 -51
- package/src/components/ui/build-panel/build-panel.tsx +163 -25
- package/src/components/ui/command-palette/editor-commands.tsx +38 -11
- package/src/components/ui/controls/finish-swatch.tsx +45 -19
- package/src/components/ui/controls/material-picker.tsx +24 -17
- package/src/components/ui/controls/metric-control.test.ts +52 -0
- package/src/components/ui/controls/metric-control.tsx +62 -24
- package/src/components/ui/controls/segmented-control.tsx +5 -2
- package/src/components/ui/controls/slider-control.test.tsx +23 -0
- package/src/components/ui/controls/slider-control.tsx +30 -14
- package/src/components/ui/floating-level-selector.tsx +100 -48
- package/src/components/ui/helpers/building-helper.tsx +5 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
- package/src/components/ui/helpers/helper-manager.test.tsx +53 -3
- package/src/components/ui/helpers/helper-manager.tsx +82 -57
- package/src/components/ui/helpers/item-helper.tsx +6 -4
- package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
- package/src/components/ui/level-duplicate-dialog.tsx +32 -23
- package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
- package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +113 -38
- package/src/components/ui/panels/floor-opening-panel.tsx +73 -25
- package/src/components/ui/panels/inspector-extensions.tsx +66 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -6
- package/src/components/ui/panels/multi-field-value.test.ts +27 -0
- package/src/components/ui/panels/multi-field-value.ts +20 -1
- package/src/components/ui/panels/multi-height-mode.tsx +8 -7
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +75 -35
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +5 -3
- package/src/components/ui/panels/panel-wrapper.tsx +22 -109
- package/src/components/ui/panels/parametric-field-control.tsx +13 -10
- package/src/components/ui/panels/parametric-inspector.tsx +18 -12
- package/src/components/ui/panels/reference-panel.tsx +51 -35
- package/src/components/ui/panels/room-construction-rows.tsx +464 -65
- package/src/components/ui/panels/room-name-options.test.ts +36 -3
- package/src/components/ui/panels/room-name-options.ts +110 -11
- package/src/components/ui/panels/room-openings-field.tsx +21 -12
- package/src/components/ui/panels/room-panel.tsx +206 -99
- package/src/components/ui/panels/shape-choice.tsx +7 -5
- package/src/components/ui/panels/wall-region-list.tsx +7 -7
- package/src/components/ui/primitives/shortcut-token.tsx +10 -6
- package/src/components/ui/sidebar/icon-rail.tsx +11 -9
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +117 -64
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +8 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +18 -8
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
- package/src/components/ui/sidebar/tab-bar.tsx +4 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
- package/src/components/ui/snap-target-badge.tsx +7 -6
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
- package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
- package/src/components/viewer/floorplan-compass-button.tsx +12 -3
- package/src/components/viewer/floorplan-preview.tsx +27 -19
- package/src/components/viewer/render-settings-menu.tsx +294 -0
- package/src/components/viewer/sky-control.tsx +710 -0
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
- package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
- package/src/components/viewer/viewer-controls-bar.tsx +278 -260
- package/src/components/viewer/viewer-hover-label.tsx +12 -4
- package/src/components/viewer/viewer-rooms.tsx +61 -4
- package/src/components/viewer/viewer-scene-header.tsx +28 -17
- package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
- package/src/components/viewer-overlay.tsx +8 -0
- package/src/components/walkthrough-hud.tsx +75 -2
- package/src/hooks/use-keyboard.test.ts +85 -4
- package/src/hooks/use-keyboard.ts +65 -7
- package/src/hooks/use-selected-room.ts +12 -1
- package/src/i18n/floor-footprint-name.ts +30 -0
- package/src/i18n/level-name.ts +39 -0
- package/src/i18n/locales/en/messages.po +15117 -0
- package/src/i18n/locales/en/messages.ts +1 -0
- package/src/i18n/locales/en-XA/messages.ts +1 -0
- package/src/i18n/locales/es/messages.po +37501 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +37501 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +37501 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +37501 -0
- package/src/i18n/locales/zh-CN/messages.ts +1 -0
- package/src/i18n/locales.test.ts +63 -0
- package/src/i18n/locales.ts +32 -0
- package/src/i18n/message-slot.tsx +25 -0
- package/src/i18n/node-messages.ts +169 -0
- package/src/i18n/pseudo-locale.ts +17 -0
- package/src/i18n/published-entry.test.ts +17 -0
- package/src/i18n/scene-theme-name.ts +16 -0
- package/src/index.tsx +156 -4
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +95 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/floor-footprints.test.ts +2 -1
- package/src/lib/floor-foundation-support.test.ts +7 -3
- package/src/lib/floorplan/dimension-drive.test.ts +36 -2
- package/src/lib/floorplan/dimension-drive.ts +68 -3
- package/src/lib/floorplan/floorplan-export.test.ts +16 -5
- package/src/lib/floorplan/floorplan-export.tsx +13 -16
- package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
- package/src/lib/floorplan/floorplan-extension.ts +37 -18
- package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
- package/src/lib/floorplan/floorplan-mode.ts +14 -12
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
- package/src/lib/floorplan/floorplan-readonly.ts +3 -3
- package/src/lib/floorplan/schedules.ts +10 -6
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
- package/src/lib/geometry-script/author.test.ts +114 -0
- package/src/lib/geometry-script/author.ts +55 -23
- package/src/lib/geometry-script/client.ts +3 -0
- package/src/lib/geometry-script/finish-scripts.ts +7 -7
- package/src/lib/glb-export.test.ts +327 -0
- package/src/lib/glb-export.ts +70 -2
- package/src/lib/grid-frame.test.ts +56 -3
- package/src/lib/grid-frame.ts +28 -1
- package/src/lib/history.test.ts +2 -1
- package/src/lib/hud-title.ts +48 -24
- package/src/lib/ifc-export.test.ts +41 -1
- package/src/lib/inspector-card-mode.test.ts +3 -31
- package/src/lib/inspector-card-mode.ts +5 -10
- package/src/lib/inspector-expanded.test.ts +13 -0
- package/src/lib/inspector-expanded.ts +13 -1
- package/src/lib/keyboard-pan.test.ts +7 -0
- package/src/lib/keyboard-pan.ts +9 -2
- package/src/lib/layer-thickness.test.ts +22 -0
- package/src/lib/layer-thickness.ts +79 -0
- package/src/lib/lot/drop-in.test.ts +59 -1
- package/src/lib/lot/drop-in.ts +42 -50
- package/src/lib/material-swatch-style.ts +24 -0
- package/src/lib/measurement-parser.ts +7 -3
- package/src/lib/measurements.test.ts +17 -0
- package/src/lib/measurements.ts +20 -5
- package/src/lib/mezzanine-selection.test.ts +3 -1
- package/src/lib/orbit-near-plane.test.ts +12 -0
- package/src/lib/orbit-near-plane.ts +13 -0
- package/src/lib/paint-regions.test.ts +8 -5
- package/src/lib/paint-scope.test.ts +53 -0
- package/src/lib/paint-scope.ts +18 -4
- package/src/lib/panel-rows.ts +17 -8
- package/src/lib/reference-scale.ts +83 -0
- package/src/lib/room-built-on.test.ts +26 -12
- package/src/lib/room-built-on.ts +20 -2
- package/src/lib/room-construction.test.ts +28 -1
- package/src/lib/room-construction.ts +14 -0
- package/src/lib/room-controls-phase6.test.ts +140 -0
- package/src/lib/room-handle-drag.ts +557 -42
- package/src/lib/room-name-catalog.test.ts +8 -0
- package/src/lib/room-name-catalog.ts +8 -1
- package/src/lib/room-name-messages.test.ts +132 -0
- package/src/lib/room-name-messages.ts +147 -0
- package/src/lib/room-name-storage.test.ts +334 -0
- package/src/lib/room-push-dimensions.test.ts +37 -0
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +9 -4
- package/src/lib/selection-cycle.ts +63 -0
- package/src/lib/separator-edit.test.ts +431 -0
- package/src/lib/separator-edit.ts +179 -0
- package/src/lib/separator-lines.ts +38 -0
- package/src/lib/sfx-bus.ts +27 -1
- package/src/lib/sfx-player.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +5 -1
- package/src/lib/tool-shortcuts.ts +130 -0
- package/src/lib/viewer-selection.test.ts +28 -24
- package/src/lib/viewer-selection.ts +20 -5
- package/src/lib/wall-draw-variant.test.ts +7 -6
- package/src/lib/wall-draw-variant.ts +107 -6
- package/src/lib/wall-nudge.test.ts +44 -0
- package/src/lib/wall-nudge.ts +131 -0
- package/src/lib/wall-parallel-snap.test.ts +83 -0
- package/src/lib/wall-parallel-snap.ts +176 -0
- package/src/lib/wall-placement-pointer.test.ts +23 -0
- package/src/lib/wall-placement-pointer.ts +18 -0
- package/src/lib/wall-pull.test.ts +245 -0
- package/src/lib/wall-pull.ts +209 -0
- package/src/lib/wall-push-line-up.test.ts +132 -0
- package/src/lib/wall-region-list.ts +14 -5
- package/src/lib/wall-run-snap.test.ts +188 -0
- package/src/lib/wall-run-snap.ts +83 -0
- package/src/lib/wall-type.test.ts +125 -0
- package/src/lib/wall-type.ts +119 -0
- package/src/lib/window-interaction.test.ts +42 -0
- package/src/lib/window-interaction.ts +47 -5
- package/src/lib/world-grid-snap.test.ts +139 -2
- package/src/lib/world-grid-snap.ts +102 -105
- package/src/store/use-alignment-guides.ts +27 -1
- package/src/store/use-editor.tsx +48 -0
- package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
- package/src/store/use-floorplan-annotation-visibility.ts +47 -22
- package/src/store/use-wall-snap-indicator.ts +15 -2
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Pascal building editor component",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.tsx",
|
|
9
9
|
"./catalog": "./src/components/ui/item-catalog/catalog-items.tsx",
|
|
10
|
+
"./locales": "./src/i18n/locales.ts",
|
|
11
|
+
"./locales/pseudo": "./src/i18n/pseudo-locale.ts",
|
|
10
12
|
"./procedural-items": "./src/components/procedural-items/inspector.tsx",
|
|
11
13
|
"./distribution": "./src/components/tools/distribution/distribution-public.ts"
|
|
12
14
|
},
|
|
@@ -16,23 +18,24 @@
|
|
|
16
18
|
"test": "bun test src"
|
|
17
19
|
},
|
|
18
20
|
"peerDependencies": {
|
|
19
|
-
"@pascal-app/core": "^1.0.
|
|
20
|
-
"@pascal-app/viewer": "^1.0.
|
|
21
|
+
"@pascal-app/core": "^1.0.7",
|
|
22
|
+
"@pascal-app/viewer": "^1.0.7",
|
|
21
23
|
"@react-three/drei": "^10",
|
|
22
24
|
"@react-three/fiber": "^9",
|
|
23
25
|
"next": ">=15",
|
|
24
26
|
"react": "^18 || ^19",
|
|
25
27
|
"react-dom": "^18 || ^19",
|
|
26
|
-
"three": "^0.
|
|
28
|
+
"three": "^0.187"
|
|
27
29
|
},
|
|
28
30
|
"dependencies": {
|
|
29
31
|
"@dnd-kit/core": "^6.3.1",
|
|
30
32
|
"@dnd-kit/sortable": "^10.0.0",
|
|
31
33
|
"@dnd-kit/utilities": "^3.2.2",
|
|
32
|
-
"@iconify/react": "^6.0.
|
|
34
|
+
"@iconify/react": "^6.0.4",
|
|
35
|
+
"@lingui/core": "6.12.0",
|
|
33
36
|
"@number-flow/react": "^0.6.2",
|
|
34
|
-
"@pascal-app/geometry-script": "^1.0.
|
|
35
|
-
"@pascal-app/ifc-converter": "^1.0.
|
|
37
|
+
"@pascal-app/geometry-script": "^1.0.7",
|
|
38
|
+
"@pascal-app/ifc-converter": "^1.0.7",
|
|
36
39
|
"@pascal-app/lingo": "^0.5.0",
|
|
37
40
|
"@radix-ui/react-alert-dialog": "^1.2.1",
|
|
38
41
|
"@radix-ui/react-context-menu": "^2.4.1",
|
|
@@ -53,31 +56,31 @@
|
|
|
53
56
|
"cmdk": "^1.1.1",
|
|
54
57
|
"fflate": "^0.8.3",
|
|
55
58
|
"howler": "^2.2.4",
|
|
56
|
-
"lucide-react": "^1.
|
|
59
|
+
"lucide-react": "^1.69.0",
|
|
57
60
|
"manifold-3d": "3.5.4",
|
|
58
61
|
"mitt": "^3.0.1",
|
|
59
|
-
"motion": "^14.
|
|
62
|
+
"motion": "^14.9.1",
|
|
60
63
|
"nanoid": "^6.0.2",
|
|
61
64
|
"pdfkit": "^0.20.2",
|
|
62
65
|
"tailwind-merge": "^3.7.0",
|
|
63
66
|
"three-bvh-csg": "^0.0.18",
|
|
64
|
-
"three-mesh-bvh": "~0.9.
|
|
67
|
+
"three-mesh-bvh": "~0.9.18",
|
|
65
68
|
"zod": ">=4.6.7 <4.7",
|
|
66
|
-
"zustand": "^5.0.
|
|
69
|
+
"zustand": "^5.0.16"
|
|
67
70
|
},
|
|
68
71
|
"devDependencies": {
|
|
69
|
-
"@pascal-app/core": "^1.0.
|
|
70
|
-
"@pascal-app/viewer": "^1.0.
|
|
72
|
+
"@pascal-app/core": "^1.0.7",
|
|
73
|
+
"@pascal-app/viewer": "^1.0.7",
|
|
71
74
|
"@pascal/typescript-config": "*",
|
|
72
75
|
"@react-three/test-renderer": "^9.1.1",
|
|
73
76
|
"@types/blob-stream": "^0.1.33",
|
|
74
|
-
"@types/bun": "^1.4.
|
|
77
|
+
"@types/bun": "^1.4.4",
|
|
75
78
|
"@types/howler": "^2.2.13",
|
|
76
|
-
"@types/pdfkit": "^0.17.
|
|
79
|
+
"@types/pdfkit": "^0.17.7",
|
|
77
80
|
"@types/react": "19.3.0",
|
|
78
81
|
"@types/react-dom": "19.3.0",
|
|
79
|
-
"@types/three": "^0.
|
|
80
|
-
"fast-xml-parser": "^5.
|
|
82
|
+
"@types/three": "^0.187",
|
|
83
|
+
"fast-xml-parser": "^5.12.0",
|
|
81
84
|
"typescript": "7.0.2"
|
|
82
85
|
},
|
|
83
86
|
"repository": {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { usePascalI18n } from '@pascal-app/core'
|
|
3
4
|
import type { MouseEventHandler, ReactNode } from 'react'
|
|
5
|
+
import { toolHintKey } from '../../i18n/node-messages'
|
|
4
6
|
import { cn } from '../../lib/utils'
|
|
5
7
|
import { shortcutDisplayValue } from '../ui/primitives/shortcut-token'
|
|
6
8
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/primitives/tooltip'
|
|
@@ -37,6 +39,7 @@ export function ActionMenuButton({
|
|
|
37
39
|
pressed?: boolean
|
|
38
40
|
children: ReactNode
|
|
39
41
|
}) {
|
|
42
|
+
const i18n = usePascalI18n()
|
|
40
43
|
return (
|
|
41
44
|
<Tooltip>
|
|
42
45
|
<TooltipTrigger asChild>
|
|
@@ -72,7 +75,7 @@ export function ActionMenuButton({
|
|
|
72
75
|
className="rounded border border-background/30 px-1 font-mono text-[10px] leading-4 opacity-80"
|
|
73
76
|
key={key}
|
|
74
77
|
>
|
|
75
|
-
{shortcutDisplayValue(key)}
|
|
78
|
+
{toolHintKey(shortcutDisplayValue(key), i18n)}
|
|
76
79
|
</kbd>
|
|
77
80
|
))}
|
|
78
81
|
</span>
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import { type AlignmentGuide, sceneRegistry } from '@pascal-app/core'
|
|
4
4
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
|
-
import { Html } from '@react-three/drei'
|
|
6
5
|
import { useFrame } from '@react-three/fiber'
|
|
7
6
|
import { memo, useMemo, useRef } from 'react'
|
|
8
|
-
import { BoxGeometry, CircleGeometry, type Group, Vector3 } from 'three'
|
|
7
|
+
import { BoxGeometry, CircleGeometry, type Group, RingGeometry, Vector3 } from 'three'
|
|
9
8
|
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
10
9
|
import { EDITOR_LAYER } from '../../lib/constants'
|
|
11
|
-
import useAlignmentGuides
|
|
10
|
+
import useAlignmentGuides, {
|
|
11
|
+
type DraftGuideLine,
|
|
12
|
+
type DraftGuides,
|
|
13
|
+
} from '../../store/use-alignment-guides'
|
|
14
|
+
import { Html } from './localized-html'
|
|
12
15
|
import { formatMeasurement } from './measurement-pill'
|
|
13
16
|
|
|
14
17
|
/**
|
|
@@ -22,13 +25,18 @@ import { formatMeasurement } from './measurement-pill'
|
|
|
22
25
|
* the design reference — so they're real 3D geometry, not screen billboards.
|
|
23
26
|
* Only the distance pill is screen-space (`<Html>`).
|
|
24
27
|
*
|
|
25
|
-
* Guide coordinates are XZ meters in the
|
|
26
|
-
* on the
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Guide coordinates are XZ meters in the active building's LOCAL frame —
|
|
29
|
+
* alignment resolves on the building's own axes, the axes its 3D grid is laid
|
|
30
|
+
* on — and the layer is mounted inside the building-local ToolManager group.
|
|
31
|
+
* The whole ribbon is lifted to the active level's WORLD Y each frame so it
|
|
32
|
+
* lies on the floor being edited — not the building base — when floors are
|
|
33
|
+
* stacked.
|
|
34
|
+
*
|
|
35
|
+
* A wall pulled from a wall end (and the run it continues) publishes its
|
|
36
|
+
* projection guides beside them (`useAlignmentGuides.draft`): the extension of
|
|
37
|
+
* the wall it comes from in amber, lines through other walls' ends in indigo,
|
|
38
|
+
* lines through the run's start in emerald, all dashed, with a ringed marker
|
|
39
|
+
* where two of them cross.
|
|
32
40
|
*/
|
|
33
41
|
|
|
34
42
|
const LINE_COLOR = 0x81_8c_f8 // indigo-400 — matches the editor's selection accent (box-select / wall highlights)
|
|
@@ -59,11 +67,33 @@ const dotMaterial = new MeshBasicNodeMaterial({
|
|
|
59
67
|
})
|
|
60
68
|
const DASH_GEOMETRY = new BoxGeometry(1, 1, 1)
|
|
61
69
|
const DOT_GEOMETRY = new CircleGeometry(1, 24)
|
|
70
|
+
const RING_GEOMETRY = new RingGeometry(0.7, 1, 28)
|
|
71
|
+
|
|
72
|
+
// Draft projection guides: one material per kind, shared like the above.
|
|
73
|
+
const draftGuideMaterial = (color: number) =>
|
|
74
|
+
new MeshBasicNodeMaterial({
|
|
75
|
+
color,
|
|
76
|
+
depthTest: false,
|
|
77
|
+
depthWrite: false,
|
|
78
|
+
toneMapped: false,
|
|
79
|
+
transparent: true,
|
|
80
|
+
opacity: 0.9,
|
|
81
|
+
})
|
|
82
|
+
const DRAFT_GUIDE_MATERIALS: Record<DraftGuideLine['kind'], MeshBasicNodeMaterial> = {
|
|
83
|
+
extension: draftGuideMaterial(0xf5_9e_0b), // amber-500
|
|
84
|
+
alignment: guideMaterial,
|
|
85
|
+
'run-start': draftGuideMaterial(0x10_b9_81), // emerald-500 — the pull grip's colour
|
|
86
|
+
}
|
|
87
|
+
const DRAFT_DASH_LEN = 0.14
|
|
88
|
+
const DRAFT_DASH_GAP = 0.12
|
|
89
|
+
const DRAFT_LINE_WIDTH = 0.035
|
|
90
|
+
const INTERSECTION_RADIUS = 0.13
|
|
62
91
|
|
|
63
92
|
type Vec3 = [number, number, number]
|
|
64
93
|
|
|
65
94
|
export const Alignment3DGuideLayer = memo(function Alignment3DGuideLayer() {
|
|
66
95
|
const guides = useAlignmentGuides((s) => s.guides)
|
|
96
|
+
const draft = useAlignmentGuides((s) => s.draft)
|
|
67
97
|
const levelId = useViewer((s) => s.selection.levelId)
|
|
68
98
|
const unit = useViewer((s) => s.unit)
|
|
69
99
|
const metricNotation = useViewer((s) => s.metricNotation)
|
|
@@ -82,16 +112,77 @@ export const Alignment3DGuideLayer = memo(function Alignment3DGuideLayer() {
|
|
|
82
112
|
group.position.y = levelMesh ? levelMesh.getWorldPosition(worldYWork).y : 0
|
|
83
113
|
})
|
|
84
114
|
|
|
85
|
-
if (guides.length === 0) return null
|
|
115
|
+
if (guides.length === 0 && !draft) return null
|
|
86
116
|
return (
|
|
87
117
|
<group ref={groupRef}>
|
|
88
118
|
{guides.map((guide, i) => (
|
|
89
119
|
<GuideLine guide={guide} key={i} metricNotation={metricNotation} unit={unit} />
|
|
90
120
|
))}
|
|
121
|
+
{draft ? <DraftGuidesLayer draft={draft} /> : null}
|
|
91
122
|
</group>
|
|
92
123
|
)
|
|
93
124
|
})
|
|
94
125
|
|
|
126
|
+
/** A wall draft's dashed projection guides and the markers where they cross. */
|
|
127
|
+
function DraftGuidesLayer({ draft }: { draft: DraftGuides }) {
|
|
128
|
+
return (
|
|
129
|
+
<>
|
|
130
|
+
{draft.lines.map((line, i) => (
|
|
131
|
+
<DraftLine key={`l${i}`} line={line} />
|
|
132
|
+
))}
|
|
133
|
+
{draft.intersections.map((point, i) => (
|
|
134
|
+
<mesh
|
|
135
|
+
geometry={RING_GEOMETRY}
|
|
136
|
+
key={`x${i}`}
|
|
137
|
+
layers={EDITOR_LAYER}
|
|
138
|
+
material={guideMaterial}
|
|
139
|
+
position={[point[0], GUIDE_Y + 0.002, point[1]]}
|
|
140
|
+
renderOrder={1001}
|
|
141
|
+
rotation={[-Math.PI / 2, 0, 0]}
|
|
142
|
+
scale={[INTERSECTION_RADIUS, INTERSECTION_RADIUS, INTERSECTION_RADIUS]}
|
|
143
|
+
/>
|
|
144
|
+
))}
|
|
145
|
+
</>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function DraftLine({ line }: { line: DraftGuideLine }) {
|
|
150
|
+
const [fx, fz] = line.from
|
|
151
|
+
const [tx, tz] = line.to
|
|
152
|
+
const { dashes, angleY } = useMemo(() => {
|
|
153
|
+
const dx = tx - fx
|
|
154
|
+
const dz = tz - fz
|
|
155
|
+
const length = Math.hypot(dx, dz)
|
|
156
|
+
const angle = -Math.atan2(dz, dx)
|
|
157
|
+
if (length < 1e-4) return { dashes: [] as Vec3[], angleY: angle }
|
|
158
|
+
const ux = dx / length
|
|
159
|
+
const uz = dz / length
|
|
160
|
+
const period = Math.max(DRAFT_DASH_LEN + DRAFT_DASH_GAP, length / MAX_DASHES)
|
|
161
|
+
const centres: Vec3[] = []
|
|
162
|
+
for (let d = period / 2; d - DRAFT_DASH_LEN / 2 <= length; d += period) {
|
|
163
|
+
centres.push([fx + ux * d, GUIDE_Y, fz + uz * d])
|
|
164
|
+
}
|
|
165
|
+
return { dashes: centres, angleY: angle }
|
|
166
|
+
}, [fx, fz, tx, tz])
|
|
167
|
+
const material = DRAFT_GUIDE_MATERIALS[line.kind]
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
{dashes.map((centre, i) => (
|
|
171
|
+
<mesh
|
|
172
|
+
geometry={DASH_GEOMETRY}
|
|
173
|
+
key={i}
|
|
174
|
+
layers={EDITOR_LAYER}
|
|
175
|
+
material={material}
|
|
176
|
+
position={centre}
|
|
177
|
+
renderOrder={1000}
|
|
178
|
+
rotation={[0, angleY, 0]}
|
|
179
|
+
scale={[DRAFT_DASH_LEN, 0.002, DRAFT_LINE_WIDTH]}
|
|
180
|
+
/>
|
|
181
|
+
))}
|
|
182
|
+
</>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
95
186
|
function GuideLine({
|
|
96
187
|
guide,
|
|
97
188
|
metricNotation,
|
|
@@ -47,8 +47,9 @@ const THUMBNAIL_BACKDROP = { inner: '#FAFAFA', outer: '#E4E4E4' }
|
|
|
47
47
|
const THUMBNAIL_SHADOW_CATCHER_OPACITY = 0.7
|
|
48
48
|
/** Soft shade hugging the ground-floor walls so the building never floats. */
|
|
49
49
|
const THUMBNAIL_WALL_CONTACT = { opacity: 0.5, spreadM: 0.9 }
|
|
50
|
-
/**
|
|
51
|
-
* prepared-drywall default, which reads as unfinished
|
|
50
|
+
/** A roof's unpainted trim and gable bands render as smooth plaster rather
|
|
51
|
+
* than the screw-dotted prepared-drywall default, which reads as unfinished
|
|
52
|
+
* at card size. Unpainted walls already wear the interior paint. */
|
|
52
53
|
const THUMBNAIL_SLOT_DEFAULT_OVERRIDES = {
|
|
53
54
|
'library:concrete-drywall': 'library:concrete-plaster',
|
|
54
55
|
} as const
|
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
orbitFloorMinY,
|
|
63
63
|
overviewEyeY,
|
|
64
64
|
} from '../../lib/orbit-floor-clearance'
|
|
65
|
+
import { orbitNearPlane } from '../../lib/orbit-near-plane'
|
|
65
66
|
import { editorOwnsOneFingerDrag } from '../../lib/touch-gesture-priority'
|
|
66
67
|
import { frameViewerCamera } from '../../lib/viewer-selection'
|
|
67
68
|
import { publishCameraPose } from '../../store/camera-pose-store'
|
|
@@ -766,8 +767,16 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
766
767
|
}, [camera, isFirstPersonMode, viewportSize])
|
|
767
768
|
|
|
768
769
|
const handleCameraUpdate = useCallback(() => {
|
|
770
|
+
const control = controls.current
|
|
771
|
+
if (control && isPerspectiveCamera(camera)) {
|
|
772
|
+
const near = orbitNearPlane(control.distance)
|
|
773
|
+
if (Math.abs(camera.near - near) > 1e-4) {
|
|
774
|
+
camera.near = near
|
|
775
|
+
camera.updateProjectionMatrix()
|
|
776
|
+
}
|
|
777
|
+
}
|
|
769
778
|
publishCurrentPose()
|
|
770
|
-
}, [publishCurrentPose])
|
|
779
|
+
}, [camera, publishCurrentPose])
|
|
771
780
|
|
|
772
781
|
useEffect(() => {
|
|
773
782
|
publishInitialCameraPose(publishCurrentPose)
|
|
@@ -1054,7 +1063,10 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1054
1063
|
updateNavigationCursor()
|
|
1055
1064
|
}
|
|
1056
1065
|
|
|
1057
|
-
const onWheel = () => {
|
|
1066
|
+
const onWheel = (event: WheelEvent) => {
|
|
1067
|
+
// Zooming in carries on past the closest orbit, the target with it; zooming out stops at
|
|
1068
|
+
// the farthest, or the eye would drift off the site. Seen before the controls' own listener.
|
|
1069
|
+
if (controls.current) controls.current.infinityDolly = event.deltaY < 0
|
|
1058
1070
|
beginLocalCameraInteraction()
|
|
1059
1071
|
cameraDraggingLifecycle.scheduleEnd()
|
|
1060
1072
|
}
|
|
@@ -1452,13 +1464,12 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1452
1464
|
}
|
|
1453
1465
|
}, [cameraDraggingLifecycle])
|
|
1454
1466
|
|
|
1455
|
-
//
|
|
1456
|
-
// so the
|
|
1457
|
-
//
|
|
1458
|
-
//
|
|
1459
|
-
// navigation guardrails.
|
|
1467
|
+
// The wheel dollies toward the cursor and, at the closest orbit, carries the
|
|
1468
|
+
// target on (infinity dolly), so the eye goes right up to a window's trim
|
|
1469
|
+
// or through a doorway rather than stopping short of the orbit's pivot.
|
|
1470
|
+
// Preset capture frames a single small subtree and orbits it closer still.
|
|
1460
1471
|
const isPresetCapture = captureMode.mode === 'preset'
|
|
1461
|
-
const minDistance = isPresetCapture ? 0.
|
|
1472
|
+
const minDistance = isPresetCapture ? 0.25 : 0.5
|
|
1462
1473
|
// Over modeled or mapped surroundings (a neighbourhood instead of the
|
|
1463
1474
|
// horizon disc) the camera may pull back far enough to take it all in.
|
|
1464
1475
|
const groundReplaced = useSceneGroundReplacement()
|
|
@@ -1471,6 +1482,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1471
1482
|
return (
|
|
1472
1483
|
<CameraControls
|
|
1473
1484
|
azimuthRotateSpeed={ORBIT_ROTATE_SPEED}
|
|
1485
|
+
dollyToCursor
|
|
1474
1486
|
makeDefault
|
|
1475
1487
|
maxDistance={maxDistance}
|
|
1476
1488
|
impl={FloorAwareCameraControls}
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { sceneRegistry } from '@pascal-app/core'
|
|
4
4
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
|
-
import { Html } from '@react-three/drei'
|
|
6
5
|
import { useFrame } from '@react-three/fiber'
|
|
7
6
|
import { memo, useMemo, useRef } from 'react'
|
|
8
7
|
import { BoxGeometry, CircleGeometry, type Group } from 'three'
|
|
9
8
|
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
10
9
|
import { EDITOR_LAYER } from '../../lib/constants'
|
|
11
10
|
import useElevationGuides from '../../store/use-elevation-guides'
|
|
11
|
+
import { Html } from './localized-html'
|
|
12
12
|
import { formatMeasurement } from './measurement-pill'
|
|
13
13
|
|
|
14
14
|
const LINE_COLOR = 0x81_8c_f8
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
getLevelDisplayName,
|
|
19
19
|
getLevelElevations,
|
|
20
20
|
getResolvedElevatorDoorStyle,
|
|
21
|
+
type InteractionSound,
|
|
21
22
|
itemPrompt,
|
|
22
23
|
nodeMechanism,
|
|
23
24
|
openElevatorDoor,
|
|
@@ -45,12 +46,20 @@ import {
|
|
|
45
46
|
CROUCH_FLOAT_HEIGHT,
|
|
46
47
|
CROUCH_RUN_SPEED,
|
|
47
48
|
CROUCH_WALK_SPEED,
|
|
49
|
+
createDroneInput,
|
|
50
|
+
DRONE_SLOW_MULTIPLIER,
|
|
51
|
+
type DroneLook,
|
|
48
52
|
EYE_LERP_SPEED,
|
|
49
53
|
type MovementInput,
|
|
54
|
+
openingSettled,
|
|
55
|
+
proceduralSettled,
|
|
50
56
|
STAND_CAPSULE,
|
|
51
57
|
STAND_CLEARANCE,
|
|
52
58
|
STAND_FLOAT_HEIGHT,
|
|
59
|
+
seedDroneLook,
|
|
53
60
|
setSurfaceRaycastLayers,
|
|
61
|
+
stepDroneFlight,
|
|
62
|
+
turnFirstPersonLook,
|
|
54
63
|
useViewer,
|
|
55
64
|
WALKTHROUGH_FOV,
|
|
56
65
|
} from '@pascal-app/viewer'
|
|
@@ -90,7 +99,7 @@ import {
|
|
|
90
99
|
} from '../../lib/window-interaction'
|
|
91
100
|
import useEditor from '../../store/use-editor'
|
|
92
101
|
import { useFirstPersonHud, type WalkthroughInteract } from '../../store/use-first-person-hud'
|
|
93
|
-
import { WalkthroughHud } from '../walkthrough-hud'
|
|
102
|
+
import { DroneHud, WalkthroughHud } from '../walkthrough-hud'
|
|
94
103
|
import {
|
|
95
104
|
buildFirstPersonColliderWorldFromRegistry,
|
|
96
105
|
deriveFirstPersonSpawn,
|
|
@@ -107,13 +116,6 @@ import {
|
|
|
107
116
|
} from './first-person/mechanism-targets'
|
|
108
117
|
|
|
109
118
|
const CAMERA_EYE_OFFSET = 0.45
|
|
110
|
-
const LOOK_SENSITIVITY = 0.002
|
|
111
|
-
// Drone mode: metres per second, and how hard Shift boosts it. The smoothing
|
|
112
|
-
// constant is an exponential approach rate, not a linear acceleration.
|
|
113
|
-
const DRONE_SPEED = 7
|
|
114
|
-
const DRONE_RUN_MULTIPLIER = 3
|
|
115
|
-
const DRONE_SLOW_MULTIPLIER = 0.2
|
|
116
|
-
const DRONE_SMOOTHING = 12
|
|
117
119
|
const CONTROLLER_CENTER_FROM_EYE = 0.85
|
|
118
120
|
const DOOR_INTERACTION_DISTANCE = 2.5
|
|
119
121
|
const DOOR_LEAF_INTERACTION_DEPTH = 0.08
|
|
@@ -168,10 +170,7 @@ function focusFirstPersonCanvas(canvas: HTMLCanvasElement) {
|
|
|
168
170
|
|
|
169
171
|
const cameraOffset = new Vector3()
|
|
170
172
|
const cameraEuler = new Euler(0, 0, 0, 'YXZ')
|
|
171
|
-
const
|
|
172
|
-
const droneForward = new Vector3()
|
|
173
|
-
const droneRight = new Vector3()
|
|
174
|
-
const droneDesiredVelocity = new Vector3()
|
|
173
|
+
const droneInput = createDroneInput()
|
|
175
174
|
const standClearanceRaycaster = new Raycaster()
|
|
176
175
|
const standClearanceUp = new Vector3(0, 1, 0)
|
|
177
176
|
const centerScreenPoint = new Vector2(0, 0)
|
|
@@ -425,6 +424,40 @@ function resolveHudInteract(target: FirstPersonInteractableTarget | null): Walkt
|
|
|
425
424
|
return { label: node.name || 'door', verb: isOpen ? 'close' : 'open' }
|
|
426
425
|
}
|
|
427
426
|
|
|
427
|
+
/** E plays what the viewer's callout plays for the same door, window or switch. */
|
|
428
|
+
function playTargetSound(target: FirstPersonInteractableTarget) {
|
|
429
|
+
const verb = resolveHudInteract(target)?.verb
|
|
430
|
+
if (!verb || verb === 'press') return
|
|
431
|
+
const node = useScene.getState().nodes[target.id]
|
|
432
|
+
// A recipe's door or lid swings and claps shut, its drawer slides; both land on the motion's end.
|
|
433
|
+
if (target.type === 'procedural' && (verb === 'open' || verb === 'close')) {
|
|
434
|
+
if (verb === 'open') {
|
|
435
|
+
emitter.emit('interaction:sound', 'panel-open')
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
if (target.kind !== 'slide') emitter.emit('interaction:sound', 'panel-swing')
|
|
439
|
+
void proceduralSettled(target.id).then(() => emitter.emit('interaction:sound', 'panel-close'))
|
|
440
|
+
return
|
|
441
|
+
}
|
|
442
|
+
let sound: InteractionSound
|
|
443
|
+
if (verb !== 'open' && verb !== 'close')
|
|
444
|
+
sound = verb === 'turn on' || verb === 'play' ? 'switch-on' : 'switch-off'
|
|
445
|
+
else if (node?.type === 'door' && node.doorType?.startsWith('garage-')) sound = 'garage'
|
|
446
|
+
else if (node?.type === 'window') sound = verb === 'open' ? 'window-open' : 'window-close'
|
|
447
|
+
else sound = verb === 'open' ? 'door-open' : 'door-close'
|
|
448
|
+
// A built-in leaf or sash swings shut, then latches on the frame it lands.
|
|
449
|
+
if (
|
|
450
|
+
(sound === 'door-close' || sound === 'window-close') &&
|
|
451
|
+
(target.type === 'door' || target.type === 'window') &&
|
|
452
|
+
node
|
|
453
|
+
) {
|
|
454
|
+
emitter.emit('interaction:sound', sound === 'door-close' ? 'door-swing' : 'window-swing')
|
|
455
|
+
void openingSettled(node).then(() => emitter.emit('interaction:sound', sound))
|
|
456
|
+
return
|
|
457
|
+
}
|
|
458
|
+
emitter.emit('interaction:sound', sound)
|
|
459
|
+
}
|
|
460
|
+
|
|
428
461
|
function resolveElevatorButtonTarget(object: Object3D): ElevatorButtonTarget | null {
|
|
429
462
|
let current: Object3D | null = object
|
|
430
463
|
|
|
@@ -734,8 +767,7 @@ export const FirstPersonControls = () => {
|
|
|
734
767
|
const controllerRef = useRef<BVHEcctrlApi | null>(null)
|
|
735
768
|
const movementInputRef = useRef<MovementInput>({ ...inactiveMovementInput })
|
|
736
769
|
const hadPointerLockRef = useRef(false)
|
|
737
|
-
const
|
|
738
|
-
const pitchRef = useRef(0)
|
|
770
|
+
const lookRef = useRef<DroneLook>({ yaw: 0, pitch: 0 })
|
|
739
771
|
const interactableTargetRef = useRef<FirstPersonInteractableTarget | null>(null)
|
|
740
772
|
const hudLabelFrameRef = useRef(HUD_LABEL_SAMPLE_FRAMES - 1)
|
|
741
773
|
const crouchKeyRef = useRef(false)
|
|
@@ -1131,6 +1163,7 @@ export const FirstPersonControls = () => {
|
|
|
1131
1163
|
|
|
1132
1164
|
const target = interactableTargetRef.current ?? resolveInteractableTarget()
|
|
1133
1165
|
if (!target) return
|
|
1166
|
+
playTargetSound(target)
|
|
1134
1167
|
if (target.type === 'mechanism') {
|
|
1135
1168
|
const node = useScene.getState().nodes[target.id]
|
|
1136
1169
|
if (node) toggleNodeMechanism(node)
|
|
@@ -1325,9 +1358,7 @@ export const FirstPersonControls = () => {
|
|
|
1325
1358
|
// Drone picks up wherever the camera currently is — orbit pose or walk eye.
|
|
1326
1359
|
useEffect(() => {
|
|
1327
1360
|
if (!isDroneMode) return
|
|
1328
|
-
|
|
1329
|
-
yawRef.current = droneEuler.y
|
|
1330
|
-
pitchRef.current = droneEuler.x
|
|
1361
|
+
seedDroneLook(lookRef.current, camera.quaternion)
|
|
1331
1362
|
droneVelocityRef.current.set(0, 0, 0)
|
|
1332
1363
|
|
|
1333
1364
|
// Interaction prompts belong to walk; drop whatever its frame left behind.
|
|
@@ -1344,8 +1375,8 @@ export const FirstPersonControls = () => {
|
|
|
1344
1375
|
|
|
1345
1376
|
const spawn = placedSpawn ?? deriveFirstPersonSpawn(camera, world)
|
|
1346
1377
|
const [x, y, z] = spawn.position
|
|
1347
|
-
|
|
1348
|
-
|
|
1378
|
+
lookRef.current.yaw = spawn.yaw
|
|
1379
|
+
lookRef.current.pitch = 0
|
|
1349
1380
|
setControllerStart({
|
|
1350
1381
|
position: [x, y - CONTROLLER_CENTER_FROM_EYE, z],
|
|
1351
1382
|
yaw: spawn.yaw,
|
|
@@ -1381,15 +1412,13 @@ export const FirstPersonControls = () => {
|
|
|
1381
1412
|
// Shutter hold: the shot is rendering — a mouse twitch must not pan it.
|
|
1382
1413
|
if (useEditor.getState().captureShutterHold) return
|
|
1383
1414
|
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1415
|
+
turnFirstPersonLook(
|
|
1416
|
+
lookRef.current,
|
|
1417
|
+
e.movementX,
|
|
1418
|
+
e.movementY,
|
|
1419
|
+
useEditor.getState().firstPersonMovementMode === 'drone' && droneSlowKeyRef.current
|
|
1387
1420
|
? DRONE_SLOW_MULTIPLIER
|
|
1388
|
-
: 1
|
|
1389
|
-
yawRef.current -= e.movementX * lookSensitivity
|
|
1390
|
-
pitchRef.current = Math.max(
|
|
1391
|
-
-(Math.PI / 2 - 0.05),
|
|
1392
|
-
Math.min(Math.PI / 2 - 0.05, pitchRef.current - e.movementY * lookSensitivity),
|
|
1421
|
+
: 1,
|
|
1393
1422
|
)
|
|
1394
1423
|
}
|
|
1395
1424
|
|
|
@@ -1817,45 +1846,24 @@ export const FirstPersonControls = () => {
|
|
|
1817
1846
|
return standClearanceRaycaster.intersectObjects(meshes, false).length === 0
|
|
1818
1847
|
}, [])
|
|
1819
1848
|
|
|
1820
|
-
// Drone:
|
|
1821
|
-
//
|
|
1822
|
-
// rises, Q (or Ctrl) sinks,
|
|
1849
|
+
// Drone: the viewer's free flight (`stepDroneFlight`, also behind the viewer's
|
|
1850
|
+
// `DroneController`) fed from this controller's keys — WASD along the view
|
|
1851
|
+
// axes, Space or E rises, Q (or Ctrl) sinks, Shift boosts, Alt slows.
|
|
1823
1852
|
useFrame((_, delta) => {
|
|
1824
1853
|
if (!isDroneMode) return
|
|
1825
1854
|
// Shutter hold: freeze the drone mid-air while the shot renders.
|
|
1826
1855
|
if (useEditor.getState().captureShutterHold) return
|
|
1827
1856
|
|
|
1828
|
-
const step = Math.min(delta, 0.1)
|
|
1829
1857
|
const movement = movementInputRef.current
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
if (movement.rightward) droneDesiredVelocity.add(droneRight)
|
|
1840
|
-
if (movement.leftward) droneDesiredVelocity.sub(droneRight)
|
|
1841
|
-
if (movement.jump || droneAscendKeyRef.current) droneDesiredVelocity.y += 1
|
|
1842
|
-
if (droneDescendKeyRef.current || crouchKeyRef.current) droneDesiredVelocity.y -= 1
|
|
1843
|
-
if (droneDesiredVelocity.lengthSq() > 0) {
|
|
1844
|
-
droneDesiredVelocity
|
|
1845
|
-
.normalize()
|
|
1846
|
-
.multiplyScalar(
|
|
1847
|
-
DRONE_SPEED *
|
|
1848
|
-
(droneSlowKeyRef.current
|
|
1849
|
-
? DRONE_SLOW_MULTIPLIER
|
|
1850
|
-
: movement.run
|
|
1851
|
-
? DRONE_RUN_MULTIPLIER
|
|
1852
|
-
: 1),
|
|
1853
|
-
)
|
|
1854
|
-
}
|
|
1855
|
-
|
|
1856
|
-
droneVelocityRef.current.lerp(droneDesiredVelocity, 1 - Math.exp(-step * DRONE_SMOOTHING))
|
|
1857
|
-
camera.position.addScaledVector(droneVelocityRef.current, step)
|
|
1858
|
-
camera.updateMatrixWorld(true)
|
|
1858
|
+
droneInput.forward = movement.forward === true
|
|
1859
|
+
droneInput.backward = movement.backward === true
|
|
1860
|
+
droneInput.leftward = movement.leftward === true
|
|
1861
|
+
droneInput.rightward = movement.rightward === true
|
|
1862
|
+
droneInput.up = movement.jump === true || droneAscendKeyRef.current
|
|
1863
|
+
droneInput.down = droneDescendKeyRef.current || crouchKeyRef.current
|
|
1864
|
+
droneInput.boost = movement.run === true
|
|
1865
|
+
droneInput.slow = droneSlowKeyRef.current
|
|
1866
|
+
stepDroneFlight(camera, lookRef.current, droneInput, droneVelocityRef.current, delta)
|
|
1859
1867
|
}, 2.5)
|
|
1860
1868
|
|
|
1861
1869
|
useFrame((_, delta) => {
|
|
@@ -1899,7 +1907,7 @@ export const FirstPersonControls = () => {
|
|
|
1899
1907
|
|
|
1900
1908
|
group.rotation.y = 0
|
|
1901
1909
|
camera.position.copy(group.position).add(cameraOffset)
|
|
1902
|
-
cameraEuler.set(
|
|
1910
|
+
cameraEuler.set(lookRef.current.pitch, lookRef.current.yaw, 0, 'YXZ')
|
|
1903
1911
|
camera.quaternion.setFromEuler(cameraEuler)
|
|
1904
1912
|
camera.updateMatrixWorld(true)
|
|
1905
1913
|
syncElevatorRide(group)
|
|
@@ -1991,6 +1999,7 @@ export const FirstPersonOverlay = ({ onExit }: { onExit: () => void }) => {
|
|
|
1991
1999
|
const zoneLabel = useFirstPersonHud((state) => state.zoneLabel)
|
|
1992
2000
|
const interact = useFirstPersonHud((state) => state.interact)
|
|
1993
2001
|
const suspended = useViewer((state) => state.walkthroughSuspended)
|
|
2002
|
+
const isDroneMode = useEditor((state) => state.firstPersonMovementMode === 'drone')
|
|
1994
2003
|
|
|
1995
2004
|
const handleExit = useCallback(() => {
|
|
1996
2005
|
if (document.pointerLockElement) {
|
|
@@ -1999,6 +2008,8 @@ export const FirstPersonOverlay = ({ onExit }: { onExit: () => void }) => {
|
|
|
1999
2008
|
onExit()
|
|
2000
2009
|
}, [onExit])
|
|
2001
2010
|
|
|
2011
|
+
if (isDroneMode) return <DroneHud onExit={handleExit} suspended={suspended} />
|
|
2012
|
+
|
|
2002
2013
|
return (
|
|
2003
2014
|
<WalkthroughHud
|
|
2004
2015
|
floorLabel={floorLabel}
|