@pascal-app/editor 1.0.3 → 1.0.4
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 +11 -9
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +315 -4
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/custom-camera-controls.tsx +202 -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 +36 -25
- package/src/components/editor/editor-layout-v2.tsx +131 -58
- package/src/components/editor/export-manager.tsx +34 -8
- package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
- package/src/components/editor/first-person/build-collider-world.ts +6 -2
- package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
- package/src/components/editor/first-person/mechanism-targets.ts +36 -0
- package/src/components/editor/first-person-controls.tsx +205 -2
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +442 -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-panel.tsx +344 -342
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +54 -2
- 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 +294 -169
- 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/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 +225 -0
- package/src/components/editor/room-labels.test.ts +357 -0
- package/src/components/editor/room-labels.tsx +990 -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 +451 -317
- package/src/components/editor/slab-hole-highlights.tsx +3 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
- package/src/components/editor/snapshot-capture.test.ts +52 -4
- package/src/components/editor/snapshot-capture.ts +30 -5
- package/src/components/editor/thumbnail-generator.test.ts +308 -0
- package/src/components/editor/thumbnail-generator.tsx +1035 -229
- package/src/components/editor/use-floorplan-background-placement.ts +31 -11
- 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 +670 -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.tsx +220 -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 +3 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- 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-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
- 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 +291 -227
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/procedural-items/inspector.tsx +40 -15
- 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/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +8 -4
- 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 +93 -46
- 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/move-registry-node-tool.tsx +3 -1
- 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/stair/stair-defaults.ts +0 -3
- package/src/components/tools/stair/stair-tool.tsx +90 -42
- package/src/components/tools/tool-manager.tsx +46 -20
- package/src/components/tools/wall/wall-drafting.test.ts +19 -4
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +3 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -1
- package/src/components/ui/action-menu/control-modes.tsx +2 -35
- package/src/components/ui/action-menu/measurement-control.tsx +3 -10
- package/src/components/ui/build-panel/build-panel.tsx +235 -0
- package/src/components/ui/command-palette/editor-commands.tsx +4 -30
- package/src/components/ui/command-palette/index.tsx +5 -24
- 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/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/floating-level-selector.tsx +9 -14
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
- package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
- package/src/components/ui/helpers/helper-manager.tsx +325 -14
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- 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 +20 -14
- 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 +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
- 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 +43 -4
- 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 +78 -36
- package/src/components/ui/panels/parametric-field-control.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -4
- 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 +725 -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/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +2 -0
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +145 -0
- package/src/components/ui/sidebar/icon-rail.tsx +5 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- 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 +176 -211
- 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 +0 -27
- 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 +0 -2
- package/src/components/ui/sidebar/tab-bar.tsx +10 -0
- 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-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +175 -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 +123 -9
- package/src/hooks/use-auto-save.ts +34 -8
- package/src/hooks/use-keyboard.test.ts +178 -3
- package/src/hooks/use-keyboard.ts +89 -71
- 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 +48 -0
- package/src/hooks/use-placement-notice.ts +42 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +246 -39
- 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/door-interaction.ts +14 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +48 -0
- package/src/lib/editor-views.ts +144 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- 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 +283 -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 +339 -18
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -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/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +100 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -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 +232 -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 +156 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
- package/src/lib/floorplan/site-plan/geometry.ts +557 -0
- package/src/lib/floorplan/site-plan/index.ts +71 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +379 -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 +69 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/fresh-planar-placement.test.ts +36 -0
- package/src/lib/fresh-planar-placement.ts +59 -36
- package/src/lib/geometry-script/author.ts +152 -0
- package/src/lib/geometry-script/client.ts +152 -0
- package/src/lib/geometry-script/protocol.ts +12 -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 +485 -0
- package/src/lib/glb-export.ts +237 -119
- package/src/lib/history.test.ts +6 -0
- 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 +18 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- 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 +186 -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 +407 -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/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 +165 -0
- package/src/lib/orbit-floor-clearance.ts +106 -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 +111 -0
- package/src/lib/paint-pass-through.ts +76 -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 +266 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +1 -1
- 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 +50 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- 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 +266 -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 +973 -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 +364 -0
- package/src/lib/scene-clipboard.ts +229 -48
- 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 +53 -74
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -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/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -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 +569 -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-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-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-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-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +13 -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 +175 -0
- package/src/store/view-layout.ts +196 -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/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/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
|
@@ -417,7 +417,10 @@ function SlabHoleHighlight({
|
|
|
417
417
|
[holeIndex, metadata, slabId],
|
|
418
418
|
)
|
|
419
419
|
|
|
420
|
+
// Only a release that ends a press on a hole is the hole's: any other
|
|
421
|
+
// (a handle drag let go over the hole) must reach its owner.
|
|
420
422
|
const handlePointerUp = useCallback((event: ThreeEvent<PointerEvent>) => {
|
|
423
|
+
if (!restoreNodeClickSuppression) return
|
|
421
424
|
releaseNodeClickSuppression()
|
|
422
425
|
stopPointerPropagation(event)
|
|
423
426
|
}, [])
|
|
@@ -29,6 +29,7 @@ import useEditor, {
|
|
|
29
29
|
import { useFirstPersonHud } from '../../store/use-first-person-hud'
|
|
30
30
|
import { Slider } from '../ui/slider'
|
|
31
31
|
import { WalkthroughCrosshair } from '../walkthrough-hud'
|
|
32
|
+
import { CaptureLevelPicker } from './capture-level-picker'
|
|
32
33
|
import { isOverlaySnapshotSave } from './snapshot-capture'
|
|
33
34
|
|
|
34
35
|
// Local alias — distinct from `useEditor.captureMode` (which describes *why*
|
|
@@ -710,6 +711,9 @@ export function SnapshotCaptureOverlay({ projectId }: { projectId: string }) {
|
|
|
710
711
|
</div>
|
|
711
712
|
)}
|
|
712
713
|
|
|
714
|
+
{/* Top-left: which levels the shot shows (the whole building by default). */}
|
|
715
|
+
{!isPreset && <CaptureLevelPicker />}
|
|
716
|
+
|
|
713
717
|
{/* Top-right dismiss button (icon-only on mobile) */}
|
|
714
718
|
<div className="pointer-events-auto absolute top-4 right-4">
|
|
715
719
|
<button
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import {
|
|
3
|
+
emitter,
|
|
4
|
+
type SnapshotCapturedEvent,
|
|
5
|
+
type SnapshotCaptureFailedEvent,
|
|
6
|
+
type SnapshotCapturePose,
|
|
7
|
+
type ThumbnailGenerateEvent,
|
|
6
8
|
} from '@pascal-app/core'
|
|
7
9
|
import { Euler, PerspectiveCamera, Quaternion, Vector3 } from 'three'
|
|
8
10
|
import {
|
|
9
11
|
applySnapshotCapturePose,
|
|
10
12
|
captureSnapshotScene,
|
|
11
13
|
createSnapshotQueue,
|
|
14
|
+
deliverSnapshot,
|
|
12
15
|
enqueueSnapshotCapture,
|
|
13
16
|
isOverlaySnapshotSave,
|
|
14
17
|
runSnapshotCapture,
|
|
@@ -420,3 +423,48 @@ describe('snapshot request correlation', () => {
|
|
|
420
423
|
expect(failures).toEqual([])
|
|
421
424
|
})
|
|
422
425
|
})
|
|
426
|
+
|
|
427
|
+
// An agent looks at the scene from a viewpoint of its own (`view_scene` in the hosted chat): the
|
|
428
|
+
// frame is for the agent only, never a project snapshot or the project's thumbnail.
|
|
429
|
+
describe('an ephemeral capture', () => {
|
|
430
|
+
const cameraData = { position: [0, 0, 0] as [number, number, number], target: null }
|
|
431
|
+
|
|
432
|
+
test('comes back to its caller on snapshot:captured and never reaches the host storage', async () => {
|
|
433
|
+
const stored: Blob[] = []
|
|
434
|
+
const received: SnapshotCapturedEvent[] = []
|
|
435
|
+
const onCaptured = (event: SnapshotCapturedEvent) => received.push(event)
|
|
436
|
+
emitter.on('snapshot:captured', onCaptured)
|
|
437
|
+
try {
|
|
438
|
+
const blob = new Blob(['frame'], { type: 'image/webp' })
|
|
439
|
+
await deliverSnapshot(
|
|
440
|
+
{ requestId: 'view-1', ephemeral: true },
|
|
441
|
+
blob,
|
|
442
|
+
{ ...cameraData, resolution: { w: 1280, h: 800 } },
|
|
443
|
+
(frame) => {
|
|
444
|
+
stored.push(frame)
|
|
445
|
+
},
|
|
446
|
+
)
|
|
447
|
+
expect(stored).toEqual([])
|
|
448
|
+
expect(received).toEqual([{ requestId: 'view-1', blob, width: 1280, height: 800 }])
|
|
449
|
+
} finally {
|
|
450
|
+
emitter.off('snapshot:captured', onCaptured)
|
|
451
|
+
}
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
test('an ephemeral frame with no caller to answer is stored nowhere', async () => {
|
|
455
|
+
const stored: Blob[] = []
|
|
456
|
+
await deliverSnapshot({ ephemeral: true }, new Blob(['frame']), cameraData, (frame) => {
|
|
457
|
+
stored.push(frame)
|
|
458
|
+
})
|
|
459
|
+
expect(stored).toEqual([])
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
test('any other capture goes to the host as before', async () => {
|
|
463
|
+
const stored: Blob[] = []
|
|
464
|
+
const blob = new Blob(['frame'])
|
|
465
|
+
await deliverSnapshot({ requestId: 'shot-1' }, blob, cameraData, (frame) => {
|
|
466
|
+
stored.push(frame)
|
|
467
|
+
})
|
|
468
|
+
expect(stored).toEqual([blob])
|
|
469
|
+
})
|
|
470
|
+
})
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
emitter,
|
|
3
|
+
type SnapshotCaptureFailedEvent,
|
|
4
|
+
type SnapshotCapturePose,
|
|
5
|
+
type SnapshotSavedEvent,
|
|
6
|
+
type ThumbnailGenerateEvent,
|
|
6
7
|
} from '@pascal-app/core'
|
|
7
8
|
import { MathUtils, type PerspectiveCamera } from 'three'
|
|
8
9
|
|
|
@@ -143,3 +144,27 @@ export async function runSnapshotCapture(
|
|
|
143
144
|
busy.current = false
|
|
144
145
|
}
|
|
145
146
|
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Where a captured frame goes: to the host, which stores it (a snapshot, the project's
|
|
150
|
+
* thumbnail), or for an ephemeral capture straight back to its caller, stored nowhere.
|
|
151
|
+
*/
|
|
152
|
+
export function deliverSnapshot<T extends object & { resolution?: { w: number; h: number } }>(
|
|
153
|
+
event: Pick<ThumbnailGenerateEvent, 'requestId' | 'ephemeral'>,
|
|
154
|
+
blob: Blob,
|
|
155
|
+
cameraData: T,
|
|
156
|
+
onCapture: (blob: Blob, cameraData: T) => void | Promise<void>,
|
|
157
|
+
): void | Promise<void> {
|
|
158
|
+
// An ephemeral frame is never stored: with no caller to answer, it is dropped.
|
|
159
|
+
if (event.ephemeral) {
|
|
160
|
+
if (event.requestId)
|
|
161
|
+
emitter.emit('snapshot:captured', {
|
|
162
|
+
requestId: event.requestId,
|
|
163
|
+
blob,
|
|
164
|
+
width: cameraData.resolution?.w ?? 0,
|
|
165
|
+
height: cameraData.resolution?.h ?? 0,
|
|
166
|
+
})
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
return onCapture(blob, cameraData)
|
|
170
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { type AnyNode, sceneRegistry, useScene } from '@pascal-app/core'
|
|
3
|
+
import { BuildingNode, CeilingNode, LevelNode, WallNode } from '@pascal-app/core/schema'
|
|
4
|
+
import { hideFromScene, SCENE_LAYER, useViewer } from '@pascal-app/viewer'
|
|
5
|
+
import { Group, Layers, Mesh, OrthographicCamera, PerspectiveCamera, Vector2 } from 'three'
|
|
6
|
+
import { CanvasTarget, ClippingGroup, type WebGPURenderer } from 'three/webgpu'
|
|
7
|
+
import {
|
|
8
|
+
atCaptureSize,
|
|
9
|
+
capturesOrthographically,
|
|
10
|
+
interiorCutaway,
|
|
11
|
+
presentFinished,
|
|
12
|
+
pumpFrames,
|
|
13
|
+
tileGrid,
|
|
14
|
+
} from './thumbnail-generator'
|
|
15
|
+
|
|
16
|
+
type FakeCanvas = { width: number; height: number; style: Record<string, string> }
|
|
17
|
+
|
|
18
|
+
const realDocument = (globalThis as { document?: unknown }).document
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
;(globalThis as { document?: unknown }).document = {
|
|
22
|
+
createElement: (): FakeCanvas => ({ width: 0, height: 0, style: {} }),
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
;(globalThis as { document?: unknown }).document = realDocument
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/** The part of three's renderer a capture's passes size themselves from. */
|
|
31
|
+
function fakeRenderer(canvas: FakeCanvas, pixelRatio: number) {
|
|
32
|
+
const [cssWidth, cssHeight] = [canvas.width / pixelRatio, canvas.height / pixelRatio]
|
|
33
|
+
let target = new CanvasTarget(canvas as unknown as HTMLCanvasElement)
|
|
34
|
+
target.setPixelRatio(pixelRatio)
|
|
35
|
+
target.setSize(cssWidth, cssHeight, false)
|
|
36
|
+
const renderer = {
|
|
37
|
+
getCanvasTarget: () => target,
|
|
38
|
+
setCanvasTarget: (next: CanvasTarget) => {
|
|
39
|
+
target = next
|
|
40
|
+
},
|
|
41
|
+
getDrawingBufferSize: (v: Vector2) => target.getDrawingBufferSize(v),
|
|
42
|
+
get domElement() {
|
|
43
|
+
return target.domElement
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
return renderer as unknown as WebGPURenderer & typeof renderer
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('supersampled capture', () => {
|
|
50
|
+
test('renders at its own size while the live canvas keeps its size', () => {
|
|
51
|
+
// a 1.5 dpr screen: the old capture resized the live canvas with drawing
|
|
52
|
+
// buffer pixels where CSS pixels were due — 3× during the capture, 1.5×
|
|
53
|
+
// after it, compounding capture after capture
|
|
54
|
+
const live: FakeCanvas = { width: 1156, height: 791, style: {} }
|
|
55
|
+
const renderer = fakeRenderer(live, 1.5)
|
|
56
|
+
const liveTarget = renderer.getCanvasTarget()
|
|
57
|
+
|
|
58
|
+
const seen = atCaptureSize(renderer, 2312, 1582, () => {
|
|
59
|
+
expect(live.width).toBe(1156)
|
|
60
|
+
expect(live.height).toBe(791)
|
|
61
|
+
return {
|
|
62
|
+
buffer: renderer.getDrawingBufferSize(new Vector2()).toArray(),
|
|
63
|
+
canvas: [renderer.domElement.width, renderer.domElement.height],
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
expect(seen.buffer).toEqual([2312, 1582])
|
|
68
|
+
expect(seen.canvas).toEqual([2312, 1582])
|
|
69
|
+
expect(renderer.getCanvasTarget()).toBe(liveTarget)
|
|
70
|
+
expect([live.width, live.height]).toEqual([1156, 791])
|
|
71
|
+
expect(renderer.getDrawingBufferSize(new Vector2()).toArray()).toEqual([1156, 791])
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('puts the live canvas target back when the render throws', () => {
|
|
75
|
+
const live: FakeCanvas = { width: 800, height: 600, style: {} }
|
|
76
|
+
const renderer = fakeRenderer(live, 1)
|
|
77
|
+
const liveTarget = renderer.getCanvasTarget()
|
|
78
|
+
expect(() =>
|
|
79
|
+
atCaptureSize(renderer, 1600, 1200, () => {
|
|
80
|
+
throw new Error('pipeline failed')
|
|
81
|
+
}),
|
|
82
|
+
).toThrow('pipeline failed')
|
|
83
|
+
expect(renderer.getCanvasTarget()).toBe(liveTarget)
|
|
84
|
+
expect([live.width, live.height]).toEqual([800, 600])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('a capture at the canvas size renders through the live target', () => {
|
|
88
|
+
const live: FakeCanvas = { width: 800, height: 600, style: {} }
|
|
89
|
+
const renderer = fakeRenderer(live, 1)
|
|
90
|
+
const liveTarget = renderer.getCanvasTarget()
|
|
91
|
+
const during = atCaptureSize(renderer, 800, 600, () => renderer.getCanvasTarget())
|
|
92
|
+
expect(during).toBe(liveTarget)
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
/** R3F's `advance` with `frameloop="never"`: the delta is the timestamp less the clock's elapsed time. */
|
|
97
|
+
function fakeLoop(startSeconds: number) {
|
|
98
|
+
const clock = { elapsedTime: startSeconds, oldTime: startSeconds }
|
|
99
|
+
const deltas: number[] = []
|
|
100
|
+
const advance = (timestamp: number) => {
|
|
101
|
+
deltas.push(timestamp - clock.elapsedTime)
|
|
102
|
+
clock.oldTime = clock.elapsedTime
|
|
103
|
+
clock.elapsedTime = timestamp
|
|
104
|
+
}
|
|
105
|
+
return { clock, deltas, advance }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
describe('frames a capture runs by hand', () => {
|
|
109
|
+
test('never hand the next real frame a delta it did not earn', async () => {
|
|
110
|
+
// the viewer's frame limiter drives R3F's clock in seconds since it
|
|
111
|
+
// mounted; performance.now() is milliseconds since the page loaded
|
|
112
|
+
const loop = fakeLoop(330.62)
|
|
113
|
+
await pumpFrames(loop.advance, loop.clock, 2)
|
|
114
|
+
// the frame limiter's next frame, one 50 fps interval on
|
|
115
|
+
loop.advance(330.64)
|
|
116
|
+
expect(loop.deltas).toHaveLength(3)
|
|
117
|
+
expect(loop.deltas[0]).toBeCloseTo(0.001, 9)
|
|
118
|
+
expect(loop.deltas[1]).toBeCloseTo(0.001, 9)
|
|
119
|
+
expect(loop.deltas[2]).toBeCloseTo(0.02, 9)
|
|
120
|
+
expect(loop.clock.elapsedTime).toBe(330.64)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('put the clock back when a frame throws', async () => {
|
|
124
|
+
const loop = fakeLoop(12)
|
|
125
|
+
const failing = () => {
|
|
126
|
+
throw new Error('a system failed')
|
|
127
|
+
}
|
|
128
|
+
await expect(pumpFrames(failing, loop.clock, 1)).rejects.toThrow('a system failed')
|
|
129
|
+
expect(loop.clock.elapsedTime).toBe(12)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('a sheet capture session', () => {
|
|
134
|
+
test('shows the finished house whatever view the user was in, then puts the view back', () => {
|
|
135
|
+
const viewer = useViewer.getState()
|
|
136
|
+
viewer.setWallMode('down')
|
|
137
|
+
viewer.setLevelMode('exploded')
|
|
138
|
+
viewer.setShading('solid')
|
|
139
|
+
viewer.setTextures(false)
|
|
140
|
+
viewer.setColorPreset('white')
|
|
141
|
+
const view = () => {
|
|
142
|
+
const { wallMode, levelMode, shading, textures, colorPreset } = useViewer.getState()
|
|
143
|
+
return { wallMode, levelMode, shading, textures, colorPreset }
|
|
144
|
+
}
|
|
145
|
+
// the shading stays the user's: the capture brings its own AO
|
|
146
|
+
|
|
147
|
+
const restore = presentFinished()
|
|
148
|
+
expect(view()).toEqual({
|
|
149
|
+
wallMode: 'up',
|
|
150
|
+
levelMode: 'stacked',
|
|
151
|
+
shading: 'solid',
|
|
152
|
+
textures: true,
|
|
153
|
+
colorPreset: 'clay',
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
restore()
|
|
157
|
+
expect(view()).toEqual({
|
|
158
|
+
wallMode: 'down',
|
|
159
|
+
levelMode: 'exploded',
|
|
160
|
+
shading: 'solid',
|
|
161
|
+
textures: false,
|
|
162
|
+
colorPreset: 'white',
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('leaves a view already finished untouched', () => {
|
|
167
|
+
const viewer = useViewer.getState()
|
|
168
|
+
viewer.setWallMode('up')
|
|
169
|
+
viewer.setLevelMode('stacked')
|
|
170
|
+
viewer.setTextures(true)
|
|
171
|
+
viewer.setColorPreset('clay')
|
|
172
|
+
let writes = 0
|
|
173
|
+
const unsubscribe = useViewer.subscribe(() => {
|
|
174
|
+
writes++
|
|
175
|
+
})
|
|
176
|
+
presentFinished()()
|
|
177
|
+
unsubscribe()
|
|
178
|
+
expect(writes).toBe(0)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
describe('a print-size picture past one render pass', () => {
|
|
183
|
+
test('fits one pass: one tile, the whole picture', () => {
|
|
184
|
+
expect(tileGrid(3209, 2588, 4096)).toEqual([
|
|
185
|
+
{ x: 0, y: 0, w: 3209, h: 2588, inner: { x: 0, y: 0, w: 3209, h: 2588 } },
|
|
186
|
+
])
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('splits into equal tiles that cover every pixel once, each rendering past its seams', () => {
|
|
190
|
+
// a 200 ppi elevation on Arch D: 22.1 in across
|
|
191
|
+
const width = 4423
|
|
192
|
+
const height = 3567
|
|
193
|
+
const tiles = tileGrid(width, height, 4096)
|
|
194
|
+
expect(tiles).toHaveLength(2)
|
|
195
|
+
let covered = 0
|
|
196
|
+
for (const tile of tiles) {
|
|
197
|
+
expect(tile.w).toBeLessThanOrEqual(4096)
|
|
198
|
+
expect(tile.h).toBeLessThanOrEqual(4096)
|
|
199
|
+
// the rendered rect holds the contributed one, on the picture
|
|
200
|
+
expect(tile.x).toBeLessThanOrEqual(tile.inner.x)
|
|
201
|
+
expect(tile.x + tile.w).toBeGreaterThanOrEqual(tile.inner.x + tile.inner.w)
|
|
202
|
+
expect(tile.x).toBeGreaterThanOrEqual(0)
|
|
203
|
+
expect(tile.x + tile.w).toBeLessThanOrEqual(width)
|
|
204
|
+
covered += tile.inner.w * tile.inner.h
|
|
205
|
+
}
|
|
206
|
+
expect(covered).toBe(width * height)
|
|
207
|
+
const [left, right] = tiles as [(typeof tiles)[number], (typeof tiles)[number]]
|
|
208
|
+
expect(left.inner.x + left.inner.w).toBe(right.inner.x)
|
|
209
|
+
// the seam: each side renders the other's first pixels too
|
|
210
|
+
expect(left.x + left.w - (left.inner.x + left.inner.w)).toBeGreaterThan(0)
|
|
211
|
+
expect(right.inner.x - right.x).toBeGreaterThan(0)
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
describe('an interior view of a floor', () => {
|
|
216
|
+
/** A two-storey house as the editor holds it: the scene graph, and each node's object. */
|
|
217
|
+
function house() {
|
|
218
|
+
const ground = LevelNode.parse({ id: 'level_ground', parentId: 'building_h', level: 0 })
|
|
219
|
+
const upper = LevelNode.parse({ id: 'level_upper', parentId: 'building_h', level: 1 })
|
|
220
|
+
const building = BuildingNode.parse({ id: 'building_h', children: [ground.id, upper.id] })
|
|
221
|
+
const wall = WallNode.parse({
|
|
222
|
+
id: 'wall_ground',
|
|
223
|
+
parentId: ground.id,
|
|
224
|
+
start: [0, 0],
|
|
225
|
+
end: [6, 0],
|
|
226
|
+
})
|
|
227
|
+
const ceiling = CeilingNode.parse({
|
|
228
|
+
id: 'ceiling_ground',
|
|
229
|
+
parentId: ground.id,
|
|
230
|
+
polygon: [
|
|
231
|
+
[0, 0],
|
|
232
|
+
[6, 0],
|
|
233
|
+
[6, 4],
|
|
234
|
+
[0, 4],
|
|
235
|
+
],
|
|
236
|
+
})
|
|
237
|
+
ground.children = [wall.id, ceiling.id]
|
|
238
|
+
const nodes = Object.fromEntries(
|
|
239
|
+
[building, ground, upper, wall, ceiling].map((node) => [node.id, node]),
|
|
240
|
+
) as Record<string, AnyNode>
|
|
241
|
+
useScene.setState({ nodes } as never)
|
|
242
|
+
const objects = Object.fromEntries(
|
|
243
|
+
[ground, upper, ceiling].map((node) => [node.id, new Group()]),
|
|
244
|
+
) as Record<string, Group>
|
|
245
|
+
const wallMesh = new Mesh()
|
|
246
|
+
wallMesh.layers.set(SCENE_LAYER)
|
|
247
|
+
objects.level_ground!.add(wallMesh, objects.ceiling_ground!)
|
|
248
|
+
for (const [id, object] of Object.entries({ ...objects, wall_ground: wallMesh }))
|
|
249
|
+
sceneRegistry.nodes.set(id, object)
|
|
250
|
+
return { objects, wallMesh }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
afterEach(() => {
|
|
254
|
+
for (const id of ['level_ground', 'level_upper', 'ceiling_ground', 'wall_ground'])
|
|
255
|
+
sceneRegistry.nodes.delete(id)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test('leaves the rest of the house out and cuts the floor, for the one render', () => {
|
|
259
|
+
const { objects, wallMesh } = house()
|
|
260
|
+
hideFromScene(wallMesh, 'isolated')
|
|
261
|
+
const restorers: (() => void)[] = []
|
|
262
|
+
interiorCutaway('level_ground', (restore) => restorers.push(restore))
|
|
263
|
+
|
|
264
|
+
expect(objects.level_upper!.visible).toBe(false)
|
|
265
|
+
expect(objects.ceiling_ground!.visible).toBe(false)
|
|
266
|
+
const [cut] = objects.level_ground!.children as ClippingGroup[]
|
|
267
|
+
expect(objects.level_ground!.children).toHaveLength(1)
|
|
268
|
+
expect(cut).toBeInstanceOf(ClippingGroup)
|
|
269
|
+
expect(cut!.clipShadows).toBe(true)
|
|
270
|
+
// Walls stand below the plane: y = 0.65 × a 2.5 m storey.
|
|
271
|
+
const [plane] = cut!.clippingPlanes
|
|
272
|
+
expect(plane!.normal.toArray()).toEqual([0, -1, 0])
|
|
273
|
+
expect(plane!.constant).toBeCloseTo(1.625, 6)
|
|
274
|
+
expect(cut!.children).toContain(wallMesh)
|
|
275
|
+
// The user isolated something else: the floor still shows whole.
|
|
276
|
+
expect(wallMesh.layers.test(new Layers())).toBe(true)
|
|
277
|
+
|
|
278
|
+
for (const restore of restorers.reverse()) restore()
|
|
279
|
+
expect(objects.level_upper!.visible).toBe(true)
|
|
280
|
+
expect(objects.ceiling_ground!.visible).toBe(true)
|
|
281
|
+
expect(objects.level_ground!.children).toEqual([wallMesh, objects.ceiling_ground!])
|
|
282
|
+
expect(wallMesh.layers.test(new Layers())).toBe(false)
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test('refuses a floor the editor has not built', () => {
|
|
286
|
+
house()
|
|
287
|
+
sceneRegistry.nodes.delete('level_ground')
|
|
288
|
+
expect(() => interiorCutaway('level_ground', () => {})).toThrow('not built yet')
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
describe("a capture's projection", () => {
|
|
293
|
+
const user = { ortho: new OrthographicCamera(), perspective: new PerspectiveCamera() }
|
|
294
|
+
const view = { position: [8, 6, 8], target: [0, 1, 0] } as never
|
|
295
|
+
|
|
296
|
+
test("a perspective view asked for stays perspective under the user's orthographic camera", () => {
|
|
297
|
+
// Else view_scene's planned camera, a look into a floor from 55° or a photo's, would give
|
|
298
|
+
// way to the user's own orthographic view while the result reports the planned one.
|
|
299
|
+
expect(capturesOrthographically({ perspective: view }, user.ortho)).toBe(false)
|
|
300
|
+
expect(capturesOrthographically({ ortho: view }, user.perspective)).toBe(true)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
test("with no view of its own, a capture takes the user's projection", () => {
|
|
304
|
+
expect(capturesOrthographically({}, user.ortho)).toBe(true)
|
|
305
|
+
expect(capturesOrthographically({}, user.perspective)).toBe(false)
|
|
306
|
+
expect(capturesOrthographically({ snapLevels: true }, user.ortho)).toBe(false)
|
|
307
|
+
})
|
|
308
|
+
})
|