@pascal-app/editor 1.0.1 → 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 +16 -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 +221 -128
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +37 -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 +536 -487
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/grid.tsx +31 -7
- package/src/components/editor/group-actions.ts +59 -11
- package/src/components/editor/group-floating-action-menu.tsx +19 -9
- package/src/components/editor/group-move-3d.ts +4 -6
- package/src/components/editor/group-rotate-handle.tsx +81 -35
- package/src/components/editor/group-selection-box-3d.tsx +25 -17
- package/src/components/editor/group-transform-shared.test.ts +126 -1
- package/src/components/editor/group-transform-shared.ts +79 -73
- package/src/components/editor/handles/handle-arrow.tsx +72 -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 +272 -131
- package/src/components/editor/index.tsx +316 -176
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +70 -70
- package/src/components/editor/node-arrow-handles.tsx +167 -14
- 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 +132 -0
- 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 +114 -163
- package/src/components/editor/wall-opening-highlights.tsx +2 -3
- 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-group-move.tsx +17 -24
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +33 -12
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +242 -126
- 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-measurement.tsx +166 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +87 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +355 -245
- 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 +31 -66
- package/src/components/tools/__fixtures__/hosting.ts +10 -0
- package/src/components/tools/item/item-surface-placement.test.ts +3 -9
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +3 -9
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +63 -47
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +3 -3
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +207 -97
- package/src/components/tools/item/use-placement-coordinator.tsx +251 -111
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/item-surface-move.test.ts +10 -2
- package/src/components/tools/registry/item-surface-move.ts +46 -24
- package/src/components/tools/registry/item-surface-resolver.test.ts +2 -2
- package/src/components/tools/registry/move-registry-node-tool.tsx +26 -10
- 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/affordance-dispatch.test.ts +24 -0
- package/src/components/tools/shared/affordance-dispatch.ts +13 -0
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/shared/draft-measurement-label.tsx +38 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +27 -0
- package/src/components/tools/shared/pointer-support-cap.ts +17 -5
- package/src/components/tools/shared/surface-rejection.test.tsx +5 -4
- package/src/components/tools/shared/surface-rejection.tsx +14 -0
- 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 +82 -23
- 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 +101 -91
- package/src/components/ui/controls/material-picker.tsx +8 -67
- 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/controls/slider-control.tsx +76 -31
- 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 +348 -19
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +10 -33
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +5 -30
- 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-height-mode.tsx +27 -21
- 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 +7 -19
- 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/shortcut-token.tsx +4 -0
- 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/audio-settings-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +615 -372
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +7 -7
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
- 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-visibility.test.ts +35 -0
- package/src/components/viewer/floorplan-preview-visibility.ts +19 -0
- package/src/components/viewer/floorplan-preview.tsx +6 -18
- 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 +9 -9
- package/src/hooks/use-auto-save.integration.test.tsx +311 -0
- package/src/hooks/use-auto-save.ts +55 -14
- package/src/hooks/use-ceiling-events.test.ts +1 -0
- package/src/hooks/use-ceiling-events.ts +2 -0
- package/src/hooks/use-grid-events.test.ts +2 -0
- package/src/hooks/use-grid-events.ts +16 -11
- package/src/hooks/use-keyboard.test.ts +182 -0
- package/src/hooks/use-keyboard.ts +122 -91
- 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 +306 -39
- package/src/lib/catalog-panel-model.ts +39 -0
- package/src/lib/catalog-selection.test.ts +27 -0
- package/src/lib/catalog-selection.ts +8 -0
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/contextual-help.test.ts +27 -1
- package/src/lib/contextual-help.ts +16 -9
- 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 +15 -9
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +48 -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 +191 -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 +544 -16
- package/src/lib/glb-export.ts +256 -125
- package/src/lib/grid-frame.test.ts +19 -0
- package/src/lib/grid-frame.ts +18 -0
- 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 +27 -1
- package/src/lib/keyboard-pan.test.ts +184 -0
- package/src/lib/keyboard-pan.ts +238 -0
- 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 +74 -0
- package/src/lib/material-paint-panel-model.ts +80 -0
- 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/overlay-transform.test.ts +28 -0
- package/src/lib/overlay-transform.ts +12 -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 +96 -0
- package/src/lib/panel-tool-options.test.ts +34 -0
- package/src/lib/panel-tool-options.ts +46 -0
- package/src/lib/parametric-node-update.ts +37 -0
- 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 +87 -0
- package/src/lib/scene.ts +39 -45
- 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 +17 -2
- package/src/lib/snapping-mode.ts +19 -7
- package/src/lib/spatial-drag-plane.test.ts +53 -0
- package/src/lib/spatial-drag-plane.ts +19 -0
- package/src/lib/spatial-pointer-input.test.ts +72 -0
- package/src/lib/spatial-pointer-input.ts +62 -0
- package/src/lib/spatial-snap.test.ts +47 -0
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/terrain-panel-model.ts +117 -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/world-grid-snap.test.ts +48 -0
- package/src/lib/world-grid-snap.ts +9 -5
- 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/rotation-snapping.test.ts +53 -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 +442 -323
- package/src/store/use-floorplan-draft-preview.ts +23 -1
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-interaction-scope.ts +155 -6
- 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
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VECTOR EDGES FROM THE VIEWER — the elevation's lines taken from the same
|
|
3
|
+
* meshes the picture is rendered from, with hidden-line removal.
|
|
4
|
+
*
|
|
5
|
+
* A drawing built from a second model of the house (a plugin's section solids)
|
|
6
|
+
* never quite matches the render: its roof sat on the plate plane while the
|
|
7
|
+
* 3D roof stacks a deck and shingles on it, its door was the opening while
|
|
8
|
+
* the render showed the leaf. This is how Revit and Chief Architect avoid
|
|
9
|
+
* that — ONE model, the view's lines derived from it:
|
|
10
|
+
*
|
|
11
|
+
* 1. every visible mesh's FEATURE EDGES (creases over `thresholdDeg`,
|
|
12
|
+
* boundaries — three's EdgesGeometry, cached per geometry), carried
|
|
13
|
+
* to world space, instance by instance for InstancedMeshes;
|
|
14
|
+
* 2. a DEPTH pass of the scene through the capture camera — view-space
|
|
15
|
+
* depth as a float colour, read back once;
|
|
16
|
+
* 3. each edge projected through that camera and SAMPLED along its length
|
|
17
|
+
* against the depth buffer: a point deeper than what the buffer saw is
|
|
18
|
+
* behind something. The visible runs come back as world segments.
|
|
19
|
+
*
|
|
20
|
+
* Orthographic only (an elevation, a section): depth is linear across a face
|
|
21
|
+
* so the interpolated edge depth is exact. Not a boolean operation — an
|
|
22
|
+
* edge test, cheap enough to run at capture time.
|
|
23
|
+
*/
|
|
24
|
+
import * as THREE from 'three'
|
|
25
|
+
import { normalView, positionView, vec4 } from 'three/tsl'
|
|
26
|
+
import { MeshBasicNodeMaterial, RenderTarget } from 'three/webgpu'
|
|
27
|
+
|
|
28
|
+
export type VisibleEdges = {
|
|
29
|
+
/** World-space segments, six floats each: ax ay az bx by bz. */
|
|
30
|
+
segments: Float32Array
|
|
31
|
+
count: number
|
|
32
|
+
/** How many candidate edges were tested, and the depth pass' size. */
|
|
33
|
+
tested: number
|
|
34
|
+
width: number
|
|
35
|
+
height: number
|
|
36
|
+
ms: number
|
|
37
|
+
/** Whether the normal pass came back (without it every in-view edge is kept). */
|
|
38
|
+
normals: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const edgeCache = new WeakMap<THREE.BufferGeometry, Map<number, Float32Array | null>>()
|
|
42
|
+
|
|
43
|
+
/** The feature edges of a geometry, local space, as pairs of points (cached per threshold; null when the geometry is too heavy). */
|
|
44
|
+
function featureEdgesOf(
|
|
45
|
+
geometry: THREE.BufferGeometry,
|
|
46
|
+
thresholdDeg: number,
|
|
47
|
+
maxTriangles: number,
|
|
48
|
+
): Float32Array | null {
|
|
49
|
+
const position = geometry.getAttribute('position')
|
|
50
|
+
if (!position) return null
|
|
51
|
+
const triangles = geometry.index ? geometry.index.count / 3 : position.count / 3
|
|
52
|
+
if (triangles > maxTriangles) return null
|
|
53
|
+
let byThreshold = edgeCache.get(geometry)
|
|
54
|
+
if (!byThreshold) {
|
|
55
|
+
byThreshold = new Map()
|
|
56
|
+
edgeCache.set(geometry, byThreshold)
|
|
57
|
+
}
|
|
58
|
+
const cached = byThreshold.get(thresholdDeg)
|
|
59
|
+
if (cached !== undefined) return cached
|
|
60
|
+
let edges: Float32Array | null = null
|
|
61
|
+
try {
|
|
62
|
+
const eg = new THREE.EdgesGeometry(geometry, thresholdDeg)
|
|
63
|
+
const attr = eg.getAttribute('position')
|
|
64
|
+
edges = attr ? new Float32Array(attr.array as ArrayLike<number>) : null
|
|
65
|
+
eg.dispose()
|
|
66
|
+
} catch {
|
|
67
|
+
edges = null
|
|
68
|
+
}
|
|
69
|
+
byThreshold.set(thresholdDeg, edges)
|
|
70
|
+
return edges
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isShown(object: THREE.Object3D): boolean {
|
|
74
|
+
let o: THREE.Object3D | null = object
|
|
75
|
+
while (o) {
|
|
76
|
+
if (!o.visible) return false
|
|
77
|
+
o = o.parent
|
|
78
|
+
}
|
|
79
|
+
return true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Whether a material draws anything. `colorWrite: false` is how a pick-only
|
|
84
|
+
* collider hides on the GPU (a plugin's utility-pole proxy, 0.5 × 10.7 m, once
|
|
85
|
+
* drew a tall rectangle through the elevations, 2026-09-23 — the rule
|
|
86
|
+
* glb-export's `isRenderableMesh` already follows).
|
|
87
|
+
*/
|
|
88
|
+
export function materialShows(material: THREE.Material | THREE.Material[]): boolean {
|
|
89
|
+
const list = Array.isArray(material) ? material : [material]
|
|
90
|
+
return list.some(
|
|
91
|
+
(m) =>
|
|
92
|
+
m && m.visible !== false && m.colorWrite !== false && !(m.transparent && m.opacity <= 0.02),
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The shown meshes whose own materials draw nothing — pick proxies, hit
|
|
98
|
+
* boxes — hidden for the length of the buffer passes: under the passes'
|
|
99
|
+
* override material they would write depth and hide the real edges behind
|
|
100
|
+
* them. The returned function shows them again.
|
|
101
|
+
*/
|
|
102
|
+
export function hideUndrawnMeshes(scene: THREE.Object3D): () => void {
|
|
103
|
+
const hidden: THREE.Object3D[] = []
|
|
104
|
+
scene.traverse((object) => {
|
|
105
|
+
const mesh = object as THREE.Mesh
|
|
106
|
+
if (!mesh.isMesh || !mesh.visible || materialShows(mesh.material)) return
|
|
107
|
+
mesh.visible = false
|
|
108
|
+
hidden.push(mesh)
|
|
109
|
+
})
|
|
110
|
+
return () => {
|
|
111
|
+
for (const mesh of hidden) mesh.visible = true
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* One pass of the scene through `material` into a float target, read back:
|
|
117
|
+
* the depth pass writes view-space depth (metres in front of the camera) in
|
|
118
|
+
* red; the normal pass the view-space normal in RGB.
|
|
119
|
+
*/
|
|
120
|
+
async function bufferPass(
|
|
121
|
+
renderer: THREE.WebGLRenderer,
|
|
122
|
+
scene: THREE.Scene,
|
|
123
|
+
camera: THREE.Camera,
|
|
124
|
+
width: number,
|
|
125
|
+
height: number,
|
|
126
|
+
colorNode: unknown,
|
|
127
|
+
): Promise<{ data: Float32Array; topDown: boolean } | null> {
|
|
128
|
+
const material = new MeshBasicNodeMaterial()
|
|
129
|
+
material.colorNode = colorNode as never
|
|
130
|
+
material.side = THREE.DoubleSide
|
|
131
|
+
material.transparent = false
|
|
132
|
+
material.depthTest = true
|
|
133
|
+
material.depthWrite = true
|
|
134
|
+
const target = new RenderTarget(width, height, {
|
|
135
|
+
type: THREE.FloatType,
|
|
136
|
+
format: THREE.RGBAFormat,
|
|
137
|
+
depthBuffer: true,
|
|
138
|
+
minFilter: THREE.NearestFilter,
|
|
139
|
+
magFilter: THREE.NearestFilter,
|
|
140
|
+
generateMipmaps: false,
|
|
141
|
+
})
|
|
142
|
+
const gl = renderer as unknown as {
|
|
143
|
+
getRenderTarget: () => unknown
|
|
144
|
+
setRenderTarget: (t: unknown) => void
|
|
145
|
+
getClearColor: (c: THREE.Color) => THREE.Color
|
|
146
|
+
getClearAlpha: () => number
|
|
147
|
+
setClearColor: (c: THREE.Color | number, a?: number) => void
|
|
148
|
+
renderAsync: (s: THREE.Scene, c: THREE.Camera) => Promise<void>
|
|
149
|
+
readRenderTargetPixelsAsync: (
|
|
150
|
+
t: unknown,
|
|
151
|
+
x: number,
|
|
152
|
+
y: number,
|
|
153
|
+
w: number,
|
|
154
|
+
h: number,
|
|
155
|
+
) => Promise<ArrayLike<number>>
|
|
156
|
+
backend?: { device?: unknown }
|
|
157
|
+
}
|
|
158
|
+
const previousTarget = gl.getRenderTarget()
|
|
159
|
+
const previousOverride = scene.overrideMaterial
|
|
160
|
+
const previousClear = gl.getClearColor(new THREE.Color())
|
|
161
|
+
const previousAlpha = gl.getClearAlpha()
|
|
162
|
+
try {
|
|
163
|
+
scene.overrideMaterial = material
|
|
164
|
+
gl.setClearColor(new THREE.Color(0, 0, 0), 0)
|
|
165
|
+
gl.setRenderTarget(target)
|
|
166
|
+
await gl.renderAsync(scene, camera)
|
|
167
|
+
gl.setRenderTarget(null)
|
|
168
|
+
const raw = await gl.readRenderTargetPixelsAsync(target, 0, 0, width, height)
|
|
169
|
+
const data = raw instanceof Float32Array ? raw : new Float32Array(raw)
|
|
170
|
+
if (data.length < width * height * 4) return null
|
|
171
|
+
// WebGPU reads rows top-down; the WebGL fallback bottom-up
|
|
172
|
+
return { data, topDown: Boolean(gl.backend?.device) }
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.warn('[vector-edges] buffer pass failed', error)
|
|
175
|
+
return null
|
|
176
|
+
} finally {
|
|
177
|
+
scene.overrideMaterial = previousOverride
|
|
178
|
+
gl.setClearColor(previousClear, previousAlpha)
|
|
179
|
+
gl.setRenderTarget(previousTarget)
|
|
180
|
+
target.dispose()
|
|
181
|
+
material.dispose()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Every drawn mesh's feature edges in world space, six floats per edge —
|
|
187
|
+
* instance by instance for an InstancedMesh. What draws nothing (a hidden
|
|
188
|
+
* mesh, a pick proxy, a layer the camera does not see) has no lines.
|
|
189
|
+
*/
|
|
190
|
+
export function candidateEdges(
|
|
191
|
+
scene: THREE.Object3D,
|
|
192
|
+
camera: THREE.Camera,
|
|
193
|
+
thresholdDeg: number,
|
|
194
|
+
maxTriangles: number,
|
|
195
|
+
): number[] {
|
|
196
|
+
const world: number[] = []
|
|
197
|
+
const a = new THREE.Vector3()
|
|
198
|
+
const b = new THREE.Vector3()
|
|
199
|
+
const instance = new THREE.Matrix4()
|
|
200
|
+
const composed = new THREE.Matrix4()
|
|
201
|
+
scene.traverse((object) => {
|
|
202
|
+
const mesh = object as THREE.Mesh
|
|
203
|
+
if (!mesh.isMesh || !mesh.geometry) return
|
|
204
|
+
if ((mesh as unknown as { isSkinnedMesh?: boolean }).isSkinnedMesh) return
|
|
205
|
+
if (!camera.layers.test(mesh.layers)) return
|
|
206
|
+
if (!isShown(mesh) || !materialShows(mesh.material)) return
|
|
207
|
+
const edges = featureEdgesOf(mesh.geometry, thresholdDeg, maxTriangles)
|
|
208
|
+
if (!edges || edges.length < 6) return
|
|
209
|
+
const instanced = mesh as unknown as THREE.InstancedMesh
|
|
210
|
+
const matrices: THREE.Matrix4[] = []
|
|
211
|
+
if (instanced.isInstancedMesh) {
|
|
212
|
+
for (let i = 0; i < instanced.count; i++) {
|
|
213
|
+
instanced.getMatrixAt(i, instance)
|
|
214
|
+
matrices.push(composed.copy(mesh.matrixWorld).multiply(instance).clone())
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
matrices.push(mesh.matrixWorld)
|
|
218
|
+
}
|
|
219
|
+
for (const m of matrices) {
|
|
220
|
+
for (let i = 0; i + 5 < edges.length; i += 6) {
|
|
221
|
+
a.set(edges[i] as number, edges[i + 1] as number, edges[i + 2] as number).applyMatrix4(m)
|
|
222
|
+
b.set(edges[i + 3] as number, edges[i + 4] as number, edges[i + 5] as number).applyMatrix4(
|
|
223
|
+
m,
|
|
224
|
+
)
|
|
225
|
+
world.push(a.x, a.y, a.z, b.x, b.y, b.z)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
return world
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The visible feature edges of `scene` through `camera` (orthographic), as
|
|
234
|
+
* world segments. `width` sets the depth pass (a multiple of 16 keeps the
|
|
235
|
+
* WebGPU readback rows unpadded); `height` follows the camera's aspect.
|
|
236
|
+
*/
|
|
237
|
+
export async function extractVisibleEdges(
|
|
238
|
+
renderer: THREE.WebGLRenderer,
|
|
239
|
+
scene: THREE.Scene,
|
|
240
|
+
camera: THREE.OrthographicCamera,
|
|
241
|
+
options: { width?: number; thresholdDeg?: number; maxTriangles?: number; bias?: number } = {},
|
|
242
|
+
): Promise<VisibleEdges | null> {
|
|
243
|
+
const started = performance.now()
|
|
244
|
+
const thresholdDeg = options.thresholdDeg ?? 20
|
|
245
|
+
const maxTriangles = options.maxTriangles ?? 400_000
|
|
246
|
+
const aspect = (camera.right - camera.left) / Math.max(1e-6, camera.top - camera.bottom)
|
|
247
|
+
const width = Math.max(16, Math.round((options.width ?? 2048) / 16) * 16)
|
|
248
|
+
const height = Math.max(16, Math.round(width / aspect / 16) * 16)
|
|
249
|
+
camera.updateMatrixWorld(true)
|
|
250
|
+
scene.updateMatrixWorld(true)
|
|
251
|
+
|
|
252
|
+
const showUndrawn = hideUndrawnMeshes(scene)
|
|
253
|
+
let pass: Awaited<ReturnType<typeof bufferPass>>
|
|
254
|
+
let normalPass: Awaited<ReturnType<typeof bufferPass>> = null
|
|
255
|
+
try {
|
|
256
|
+
pass = await bufferPass(
|
|
257
|
+
renderer,
|
|
258
|
+
scene,
|
|
259
|
+
camera,
|
|
260
|
+
width,
|
|
261
|
+
height,
|
|
262
|
+
vec4(positionView.z.negate(), 0, 0, 1),
|
|
263
|
+
)
|
|
264
|
+
if (pass)
|
|
265
|
+
normalPass = await bufferPass(renderer, scene, camera, width, height, vec4(normalView, 1))
|
|
266
|
+
} finally {
|
|
267
|
+
showUndrawn()
|
|
268
|
+
}
|
|
269
|
+
if (!pass) return null
|
|
270
|
+
const { data: depth, topDown } = pass
|
|
271
|
+
const normals = normalPass?.data ?? null
|
|
272
|
+
|
|
273
|
+
const world = candidateEdges(scene, camera, thresholdDeg, maxTriangles)
|
|
274
|
+
|
|
275
|
+
// ── the visibility test ──────────────────────────────────────────────
|
|
276
|
+
const view = camera.matrixWorldInverse
|
|
277
|
+
const proj = camera.projectionMatrix
|
|
278
|
+
const bias = options.bias ?? 0.008
|
|
279
|
+
const out: number[] = []
|
|
280
|
+
const va = new THREE.Vector3()
|
|
281
|
+
const vb = new THREE.Vector3()
|
|
282
|
+
const pa = new THREE.Vector3()
|
|
283
|
+
const pb = new THREE.Vector3()
|
|
284
|
+
const indexAt = (px: number, py: number): number => {
|
|
285
|
+
const x = Math.floor(px)
|
|
286
|
+
const y = Math.floor(py)
|
|
287
|
+
if (x < 0 || y < 0 || x >= width || y >= height) return -1
|
|
288
|
+
const row = topDown ? y : height - 1 - y
|
|
289
|
+
return (row * width + x) * 4
|
|
290
|
+
}
|
|
291
|
+
const sampleAt = (px: number, py: number): number => {
|
|
292
|
+
const k = indexAt(px, py)
|
|
293
|
+
return k < 0 ? Number.POSITIVE_INFINITY : (depth[k] as number)
|
|
294
|
+
}
|
|
295
|
+
// A line is only a line where the render CHANGES across it: a step in
|
|
296
|
+
// depth (a silhouette, a reveal, the far side of a box) or a crease in
|
|
297
|
+
// the normal (wall meets roof, the corner of a post). A flat face's own
|
|
298
|
+
// triangulation — the CSG walls come with T-junctions, so the edge
|
|
299
|
+
// finder cannot pair those triangles — shows neither and is dropped.
|
|
300
|
+
const CREASE = Math.cos((15 * Math.PI) / 180)
|
|
301
|
+
const ACROSS = 2
|
|
302
|
+
const featureAt = (px: number, py: number, nx: number, ny: number): boolean => {
|
|
303
|
+
const c = sampleAt(px, py)
|
|
304
|
+
const l = sampleAt(px + nx * ACROSS, py + ny * ACROSS)
|
|
305
|
+
const r = sampleAt(px - nx * ACROSS, py - ny * ACROSS)
|
|
306
|
+
// the sky on either side, or off the frame: a silhouette
|
|
307
|
+
if (c === 0 || l === 0 || r === 0) return true
|
|
308
|
+
if (!Number.isFinite(l) || !Number.isFinite(r) || !Number.isFinite(c)) return true
|
|
309
|
+
// a step, not a slope: the second difference is zero on any plane
|
|
310
|
+
if (Math.abs(l + r - 2 * c) > 0.012) return true
|
|
311
|
+
if (!normals) return true
|
|
312
|
+
const kl = indexAt(px + nx * ACROSS, py + ny * ACROSS)
|
|
313
|
+
const kr = indexAt(px - nx * ACROSS, py - ny * ACROSS)
|
|
314
|
+
if (kl < 0 || kr < 0) return true
|
|
315
|
+
// unsigned: a CSG face's triangles may wind either way, and a flipped
|
|
316
|
+
// normal on a flat wall is not a corner
|
|
317
|
+
const dot =
|
|
318
|
+
(normals[kl] as number) * (normals[kr] as number) +
|
|
319
|
+
(normals[kl + 1] as number) * (normals[kr + 1] as number) +
|
|
320
|
+
(normals[kl + 2] as number) * (normals[kr + 2] as number)
|
|
321
|
+
return Math.abs(dot) < CREASE
|
|
322
|
+
}
|
|
323
|
+
const tested = world.length / 6
|
|
324
|
+
for (let i = 0; i + 5 < world.length; i += 6) {
|
|
325
|
+
va.set(world[i] as number, world[i + 1] as number, world[i + 2] as number).applyMatrix4(view)
|
|
326
|
+
vb.set(world[i + 3] as number, world[i + 4] as number, world[i + 5] as number).applyMatrix4(
|
|
327
|
+
view,
|
|
328
|
+
)
|
|
329
|
+
const da = -va.z
|
|
330
|
+
const db = -vb.z
|
|
331
|
+
if (da <= camera.near && db <= camera.near) continue
|
|
332
|
+
pa.copy(va).applyMatrix4(proj)
|
|
333
|
+
pb.copy(vb).applyMatrix4(proj)
|
|
334
|
+
const ax = ((pa.x + 1) / 2) * width
|
|
335
|
+
const ay = ((1 - pa.y) / 2) * height
|
|
336
|
+
const bx = ((pb.x + 1) / 2) * width
|
|
337
|
+
const by = ((1 - pb.y) / 2) * height
|
|
338
|
+
// wholly off the frame: nothing to test
|
|
339
|
+
if (
|
|
340
|
+
(ax < 0 && bx < 0) ||
|
|
341
|
+
(ax >= width && bx >= width) ||
|
|
342
|
+
(ay < 0 && by < 0) ||
|
|
343
|
+
(ay >= height && by >= height)
|
|
344
|
+
) {
|
|
345
|
+
continue
|
|
346
|
+
}
|
|
347
|
+
const lengthPx = Math.hypot(bx - ax, by - ay)
|
|
348
|
+
const n = Math.min(4096, Math.max(2, Math.ceil(lengthPx)))
|
|
349
|
+
const slope = Math.abs(db - da) / n
|
|
350
|
+
const tolerance = bias + slope * 1.5
|
|
351
|
+
// the screen-space perpendicular, to look across the edge
|
|
352
|
+
const nx = lengthPx > 1e-6 ? -(by - ay) / lengthPx : 1
|
|
353
|
+
const ny = lengthPx > 1e-6 ? (bx - ax) / lengthPx : 0
|
|
354
|
+
let runStart = -1
|
|
355
|
+
let gap = 0
|
|
356
|
+
const emit = (t0: number, t1: number) => {
|
|
357
|
+
if (t1 - t0 <= 0) return
|
|
358
|
+
out.push(
|
|
359
|
+
(world[i] as number) + ((world[i + 3] as number) - (world[i] as number)) * t0,
|
|
360
|
+
(world[i + 1] as number) + ((world[i + 4] as number) - (world[i + 1] as number)) * t0,
|
|
361
|
+
(world[i + 2] as number) + ((world[i + 5] as number) - (world[i + 2] as number)) * t0,
|
|
362
|
+
(world[i] as number) + ((world[i + 3] as number) - (world[i] as number)) * t1,
|
|
363
|
+
(world[i + 1] as number) + ((world[i + 4] as number) - (world[i + 1] as number)) * t1,
|
|
364
|
+
(world[i + 2] as number) + ((world[i + 5] as number) - (world[i + 2] as number)) * t1,
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
for (let s = 0; s <= n; s++) {
|
|
368
|
+
const t = s / n
|
|
369
|
+
const px = ax + (bx - ax) * t
|
|
370
|
+
const py = ay + (by - ay) * t
|
|
371
|
+
const d = da + (db - da) * t
|
|
372
|
+
const seen = sampleAt(px, py)
|
|
373
|
+
// the background (nothing drawn) reads 0; off the frame reads Infinity — both leave the edge in view
|
|
374
|
+
const inView = seen === 0 || seen === Number.POSITIVE_INFINITY || d <= seen + tolerance
|
|
375
|
+
const visible = inView && featureAt(px, py, nx, ny)
|
|
376
|
+
if (visible) {
|
|
377
|
+
if (runStart < 0) runStart = t
|
|
378
|
+
gap = 0
|
|
379
|
+
} else if (runStart >= 0) {
|
|
380
|
+
// a single hidden sample inside a run is rasterisation noise, not an occluder
|
|
381
|
+
gap++
|
|
382
|
+
if (gap > 1) {
|
|
383
|
+
emit(runStart, (s - gap) / n)
|
|
384
|
+
runStart = -1
|
|
385
|
+
gap = 0
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (runStart >= 0) emit(runStart, 1)
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
segments: new Float32Array(out),
|
|
393
|
+
count: out.length / 6,
|
|
394
|
+
tested,
|
|
395
|
+
width,
|
|
396
|
+
height,
|
|
397
|
+
ms: Math.round(performance.now() - started),
|
|
398
|
+
normals: normals !== null,
|
|
399
|
+
}
|
|
400
|
+
}
|