@pascal-app/nodes 0.1.1 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/building/definition.js +1 -1
- package/dist/cabinet/definition.d.ts.map +1 -1
- package/dist/cabinet/definition.js +809 -72
- package/dist/cabinet/geometry.d.ts.map +1 -1
- package/dist/cabinet/geometry.js +4 -0
- package/dist/cabinet/move-frame.js +3 -3
- package/dist/cabinet/panel.d.ts.map +1 -1
- package/dist/cabinet/panel.js +1 -0
- package/dist/cabinet/placement-snap.d.ts +8 -0
- package/dist/cabinet/placement-snap.d.ts.map +1 -0
- package/dist/cabinet/placement-snap.js +19 -0
- package/dist/cabinet/presets.js +8 -8
- package/dist/cabinet/quick-actions.d.ts.map +1 -1
- package/dist/cabinet/quick-actions.js +9 -3
- package/dist/cabinet/resize-limits.d.ts +11 -0
- package/dist/cabinet/resize-limits.d.ts.map +1 -0
- package/dist/cabinet/resize-limits.js +22 -0
- package/dist/cabinet/run-layout.d.ts +8 -2
- package/dist/cabinet/run-layout.d.ts.map +1 -1
- package/dist/cabinet/run-layout.js +59 -7
- package/dist/cabinet/run-ops.d.ts +44 -3
- package/dist/cabinet/run-ops.d.ts.map +1 -1
- package/dist/cabinet/run-ops.js +646 -110
- package/dist/cabinet/run-panel.d.ts +2 -1
- package/dist/cabinet/run-panel.d.ts.map +1 -1
- package/dist/cabinet/run-panel.js +38 -4
- package/dist/cabinet/stack-transitions.js +4 -4
- package/dist/cabinet/tool.d.ts.map +1 -1
- package/dist/cabinet/tool.js +95 -17
- package/dist/cabinet/wall-snap.d.ts.map +1 -1
- package/dist/cabinet/wall-snap.js +2 -6
- package/dist/ceiling/boundary-editor.d.ts.map +1 -1
- package/dist/ceiling/boundary-editor.js +2 -2
- package/dist/ceiling/definition.d.ts +1 -1
- package/dist/ceiling/definition.d.ts.map +1 -1
- package/dist/ceiling/definition.js +36 -5
- package/dist/ceiling/floorplan-affordances.d.ts +4 -4
- package/dist/ceiling/floorplan-move.d.ts +4 -4
- package/dist/ceiling/floorplan-move.d.ts.map +1 -1
- package/dist/ceiling/floorplan-move.js +5 -4
- package/dist/ceiling/hole-editor.d.ts.map +1 -1
- package/dist/ceiling/hole-editor.js +2 -2
- package/dist/ceiling/move-tool.d.ts.map +1 -1
- package/dist/ceiling/move-tool.js +4 -3
- package/dist/ceiling/panel.d.ts.map +1 -1
- package/dist/ceiling/panel.js +65 -3
- package/dist/ceiling/renderer.d.ts.map +1 -1
- package/dist/ceiling/renderer.js +6 -2
- package/dist/ceiling/tool.d.ts.map +1 -1
- package/dist/ceiling/tool.js +12 -1
- package/dist/chimney/definition.d.ts.map +1 -1
- package/dist/chimney/definition.js +1 -0
- package/dist/chimney/tool.d.ts.map +1 -1
- package/dist/chimney/tool.js +16 -1
- package/dist/column/definition.d.ts.map +1 -1
- package/dist/column/definition.js +14 -2
- package/dist/column/floorplan-affordances.d.ts.map +1 -1
- package/dist/column/floorplan-affordances.js +19 -14
- package/dist/column/floorplan-move.d.ts.map +1 -1
- package/dist/column/floorplan-move.js +7 -2
- package/dist/column/floorplan.d.ts +9 -1
- package/dist/column/floorplan.d.ts.map +1 -1
- package/dist/column/floorplan.js +59 -4
- package/dist/column/tool.d.ts.map +1 -1
- package/dist/column/tool.js +33 -7
- package/dist/construction-dimension/definition.d.ts +4 -0
- package/dist/construction-dimension/definition.d.ts.map +1 -0
- package/dist/construction-dimension/definition.js +83 -0
- package/dist/construction-dimension/drawing-coordination.d.ts +7 -0
- package/dist/construction-dimension/drawing-coordination.d.ts.map +1 -0
- package/dist/construction-dimension/drawing-coordination.js +54 -0
- package/dist/construction-dimension/floorplan-affordances.d.ts +4 -0
- package/dist/construction-dimension/floorplan-affordances.d.ts.map +1 -0
- package/dist/construction-dimension/floorplan-affordances.js +114 -0
- package/dist/construction-dimension/floorplan-tool.d.ts +21 -0
- package/dist/construction-dimension/floorplan-tool.d.ts.map +1 -0
- package/dist/construction-dimension/floorplan-tool.js +557 -0
- package/dist/construction-dimension/floorplan.d.ts +3 -0
- package/dist/construction-dimension/floorplan.d.ts.map +1 -0
- package/dist/construction-dimension/floorplan.js +457 -0
- package/dist/construction-dimension/geometry.d.ts +31 -0
- package/dist/construction-dimension/geometry.d.ts.map +1 -0
- package/dist/construction-dimension/geometry.js +144 -0
- package/dist/construction-dimension/index.d.ts +4 -0
- package/dist/construction-dimension/index.d.ts.map +1 -0
- package/dist/construction-dimension/index.js +3 -0
- package/dist/construction-dimension/panel.d.ts +2 -0
- package/dist/construction-dimension/panel.d.ts.map +1 -0
- package/dist/construction-dimension/panel.js +128 -0
- package/dist/construction-dimension/parametrics.d.ts +3 -0
- package/dist/construction-dimension/parametrics.d.ts.map +1 -0
- package/dist/construction-dimension/parametrics.js +4 -0
- package/dist/construction-dimension/schema.d.ts +2 -0
- package/dist/construction-dimension/schema.d.ts.map +1 -0
- package/dist/construction-dimension/schema.js +1 -0
- package/dist/door/contextual-dimensions.d.ts +3 -0
- package/dist/door/contextual-dimensions.d.ts.map +1 -0
- package/dist/door/contextual-dimensions.js +7 -0
- package/dist/door/definition.d.ts.map +1 -1
- package/dist/door/definition.js +13 -8
- package/dist/door/floorplan-affordances.d.ts +2 -5
- package/dist/door/floorplan-affordances.d.ts.map +1 -1
- package/dist/door/floorplan-affordances.js +9 -18
- package/dist/door/floorplan.d.ts +3 -4
- package/dist/door/floorplan.d.ts.map +1 -1
- package/dist/door/floorplan.js +88 -6
- package/dist/door/move-tool.d.ts.map +1 -1
- package/dist/door/move-tool.js +2 -2
- package/dist/door/panel.d.ts.map +1 -1
- package/dist/door/panel.js +3 -1
- package/dist/door/schema.d.ts +1 -1
- package/dist/door/schema.d.ts.map +1 -1
- package/dist/door/schema.js +1 -1
- package/dist/door/tool.d.ts.map +1 -1
- package/dist/door/tool.js +63 -6
- package/dist/dormer/definition.d.ts.map +1 -1
- package/dist/dormer/definition.js +1 -0
- package/dist/dormer/tool.d.ts.map +1 -1
- package/dist/dormer/tool.js +23 -1
- package/dist/duct-segment/tool.d.ts.map +1 -1
- package/dist/duct-segment/tool.js +20 -9
- package/dist/duct-terminal/tool.d.ts.map +1 -1
- package/dist/duct-terminal/tool.js +9 -6
- package/dist/elevator/floorplan-affordances.d.ts +2 -6
- package/dist/elevator/floorplan-affordances.d.ts.map +1 -1
- package/dist/elevator/floorplan-affordances.js +13 -12
- package/dist/fence/actions/move-endpoint.d.ts.map +1 -1
- package/dist/fence/actions/move-endpoint.js +29 -1
- package/dist/fence/definition.d.ts.map +1 -1
- package/dist/fence/definition.js +67 -14
- package/dist/fence/floorplan-affordances.d.ts.map +1 -1
- package/dist/fence/floorplan-affordances.js +48 -11
- package/dist/fence/floorplan-move.d.ts.map +1 -1
- package/dist/fence/floorplan-move.js +38 -3
- package/dist/fence/geometry.d.ts.map +1 -1
- package/dist/fence/geometry.js +9 -1
- package/dist/fence/lift.d.ts +15 -0
- package/dist/fence/lift.d.ts.map +1 -0
- package/dist/fence/lift.js +23 -0
- package/dist/fence/move-tool.d.ts.map +1 -1
- package/dist/fence/move-tool.js +25 -5
- package/dist/fence/system.d.ts +3 -0
- package/dist/fence/system.d.ts.map +1 -0
- package/dist/fence/system.js +41 -0
- package/dist/fence/tool.d.ts.map +1 -1
- package/dist/fence/tool.js +103 -32
- package/dist/hvac-equipment/tool.d.ts.map +1 -1
- package/dist/hvac-equipment/tool.js +8 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/item/definition.d.ts.map +1 -1
- package/dist/item/definition.js +7 -1
- package/dist/item/floorplan-move.d.ts.map +1 -1
- package/dist/item/floorplan-move.js +50 -43
- package/dist/item/floorplan.d.ts +1 -0
- package/dist/item/floorplan.d.ts.map +1 -1
- package/dist/item/floorplan.js +49 -0
- package/dist/item/renderer.d.ts +3 -0
- package/dist/item/renderer.d.ts.map +1 -1
- package/dist/item/renderer.js +96 -8
- package/dist/level/definition.d.ts.map +1 -1
- package/dist/level/definition.js +7 -3
- package/dist/lineset/tool.d.ts.map +1 -1
- package/dist/lineset/tool.js +5 -1
- package/dist/liquid-line/tool.d.ts.map +1 -1
- package/dist/liquid-line/tool.js +7 -1
- package/dist/measurement/definition.d.ts.map +1 -1
- package/dist/measurement/definition.js +5 -0
- package/dist/measurement/floorplan.d.ts.map +1 -1
- package/dist/measurement/floorplan.js +11 -10
- package/dist/measurement/index.d.ts +1 -1
- package/dist/measurement/index.d.ts.map +1 -1
- package/dist/measurement/index.js +1 -1
- package/dist/measurement/resolve.d.ts +5 -0
- package/dist/measurement/resolve.d.ts.map +1 -1
- package/dist/measurement/resolve.js +2 -2
- package/dist/measurement/surface-query.js +5 -5
- package/dist/measurement/tool.d.ts +6 -1
- package/dist/measurement/tool.d.ts.map +1 -1
- package/dist/measurement/tool.js +7 -11
- package/dist/pipe-segment/tool.d.ts.map +1 -1
- package/dist/pipe-segment/tool.js +22 -19
- package/dist/roof-segment/floorplan-affordances.d.ts.map +1 -1
- package/dist/roof-segment/floorplan-affordances.js +14 -8
- package/dist/shared/construction-dimension-standards.d.ts +34 -0
- package/dist/shared/construction-dimension-standards.d.ts.map +1 -0
- package/dist/shared/construction-dimension-standards.js +18 -0
- package/dist/shared/construction-length.d.ts +10 -0
- package/dist/shared/construction-length.d.ts.map +1 -0
- package/dist/shared/construction-length.js +57 -0
- package/dist/shared/dimension-string.d.ts +20 -0
- package/dist/shared/dimension-string.d.ts.map +1 -0
- package/dist/shared/dimension-string.js +19 -0
- package/dist/shared/draft-axis-guides.d.ts +36 -0
- package/dist/shared/draft-axis-guides.d.ts.map +1 -0
- package/dist/shared/draft-axis-guides.js +107 -0
- package/dist/shared/move-roof-tool.d.ts.map +1 -1
- package/dist/shared/move-roof-tool.js +17 -3
- package/dist/shared/opening-documentation-fields.d.ts +16 -0
- package/dist/shared/opening-documentation-fields.d.ts.map +1 -0
- package/dist/shared/opening-documentation-fields.js +54 -0
- package/dist/shared/opening-documentation.d.ts +47 -0
- package/dist/shared/opening-documentation.d.ts.map +1 -0
- package/dist/shared/opening-documentation.js +300 -0
- package/dist/shared/opening-guides-runtime.d.ts.map +1 -1
- package/dist/shared/opening-guides-runtime.js +3 -2
- package/dist/shared/opening-placement-dimensions.d.ts.map +1 -1
- package/dist/shared/opening-placement-dimensions.js +14 -5
- package/dist/shared/wall-opening-ceiling.d.ts +30 -0
- package/dist/shared/wall-opening-ceiling.d.ts.map +1 -0
- package/dist/shared/wall-opening-ceiling.js +29 -0
- package/dist/shelf/floorplan-affordances.d.ts.map +1 -1
- package/dist/shelf/floorplan-affordances.js +10 -5
- package/dist/shelf/floorplan-move.d.ts +2 -16
- package/dist/shelf/floorplan-move.d.ts.map +1 -1
- package/dist/shelf/floorplan-move.js +19 -40
- package/dist/shelf/tool.d.ts.map +1 -1
- package/dist/shelf/tool.js +8 -3
- package/dist/site/recessed-slab-ground-holes.d.ts.map +1 -1
- package/dist/site/recessed-slab-ground-holes.js +1 -4
- package/dist/site/renderer.d.ts.map +1 -1
- package/dist/site/renderer.js +118 -28
- package/dist/site/terrain-drape.d.ts +91 -0
- package/dist/site/terrain-drape.d.ts.map +1 -0
- package/dist/site/terrain-drape.js +205 -0
- package/dist/site/terrain-geometry.d.ts +131 -0
- package/dist/site/terrain-geometry.d.ts.map +1 -0
- package/dist/site/terrain-geometry.js +259 -0
- package/dist/site/terrain-mesh.d.ts +49 -0
- package/dist/site/terrain-mesh.d.ts.map +1 -0
- package/dist/site/terrain-mesh.js +122 -0
- package/dist/site/terrain-renderer.d.ts +28 -0
- package/dist/site/terrain-renderer.d.ts.map +1 -0
- package/dist/site/terrain-renderer.js +69 -0
- package/dist/skylight/definition.d.ts.map +1 -1
- package/dist/skylight/definition.js +1 -0
- package/dist/skylight/tool.d.ts.map +1 -1
- package/dist/skylight/tool.js +16 -1
- package/dist/slab/definition.d.ts.map +1 -1
- package/dist/slab/definition.js +87 -10
- package/dist/slab/elevation-limit.d.ts +43 -0
- package/dist/slab/elevation-limit.d.ts.map +1 -0
- package/dist/slab/elevation-limit.js +124 -0
- package/dist/slab/floorplan-affordances.d.ts +16 -0
- package/dist/slab/floorplan-affordances.d.ts.map +1 -1
- package/dist/slab/geometry.d.ts.map +1 -1
- package/dist/slab/geometry.js +98 -5
- package/dist/slab/panel.d.ts.map +1 -1
- package/dist/slab/panel.js +82 -4
- package/dist/slab/parametrics.d.ts +1 -1
- package/dist/slab/parametrics.d.ts.map +1 -1
- package/dist/slab/parametrics.js +13 -1
- package/dist/slab/placement-ownership.d.ts +3 -0
- package/dist/slab/placement-ownership.d.ts.map +1 -0
- package/dist/slab/placement-ownership.js +3 -0
- package/dist/slab/quick-measurement.d.ts.map +1 -1
- package/dist/slab/quick-measurement.js +3 -2
- package/dist/slab/system.d.ts.map +1 -1
- package/dist/slab/system.js +14 -4
- package/dist/slab/tool.d.ts.map +1 -1
- package/dist/slab/tool.js +81 -23
- package/dist/solar-panel/definition.d.ts.map +1 -1
- package/dist/solar-panel/definition.js +1 -0
- package/dist/solar-panel/tool.d.ts.map +1 -1
- package/dist/solar-panel/tool.js +17 -1
- package/dist/spawn/definition.d.ts.map +1 -1
- package/dist/spawn/definition.js +1 -0
- package/dist/spawn/floorplan-affordances.d.ts.map +1 -1
- package/dist/spawn/floorplan-affordances.js +7 -4
- package/dist/spawn/floorplan-move.d.ts.map +1 -1
- package/dist/spawn/floorplan-move.js +8 -6
- package/dist/spawn/renderer.d.ts +4 -0
- package/dist/spawn/renderer.d.ts.map +1 -1
- package/dist/spawn/renderer.js +32 -18
- package/dist/spawn/tool.d.ts +2 -2
- package/dist/spawn/tool.d.ts.map +1 -1
- package/dist/spawn/tool.js +78 -11
- package/dist/stair/definition.d.ts.map +1 -1
- package/dist/stair/definition.js +15 -7
- package/dist/stair/destination.d.ts +16 -0
- package/dist/stair/destination.d.ts.map +1 -0
- package/dist/stair/destination.js +24 -0
- package/dist/stair/documentation.d.ts +8 -0
- package/dist/stair/documentation.d.ts.map +1 -0
- package/dist/stair/documentation.js +245 -0
- package/dist/stair/floor-stack.d.ts.map +1 -1
- package/dist/stair/floor-stack.js +38 -0
- package/dist/stair/floorplan-affordances.d.ts.map +1 -1
- package/dist/stair/floorplan-affordances.js +28 -9
- package/dist/stair/floorplan-move.d.ts +3 -5
- package/dist/stair/floorplan-move.d.ts.map +1 -1
- package/dist/stair/floorplan-move.js +14 -16
- package/dist/stair/floorplan.d.ts.map +1 -1
- package/dist/stair/floorplan.js +27 -14
- package/dist/stair/panel.d.ts.map +1 -1
- package/dist/stair/panel.js +35 -2
- package/dist/stair/renderer.d.ts.map +1 -1
- package/dist/stair/renderer.js +4 -3
- package/dist/structural-grid/coordination.d.ts +16 -0
- package/dist/structural-grid/coordination.d.ts.map +1 -0
- package/dist/structural-grid/coordination.js +105 -0
- package/dist/structural-grid/definition.d.ts +4 -0
- package/dist/structural-grid/definition.d.ts.map +1 -0
- package/dist/structural-grid/definition.js +51 -0
- package/dist/structural-grid/floorplan-tool.d.ts +16 -0
- package/dist/structural-grid/floorplan-tool.d.ts.map +1 -0
- package/dist/structural-grid/floorplan-tool.js +183 -0
- package/dist/structural-grid/floorplan.d.ts +3 -0
- package/dist/structural-grid/floorplan.d.ts.map +1 -0
- package/dist/structural-grid/floorplan.js +61 -0
- package/dist/structural-grid/index.d.ts +2 -0
- package/dist/structural-grid/index.d.ts.map +1 -0
- package/dist/structural-grid/index.js +1 -0
- package/dist/structural-grid/schema.d.ts +2 -0
- package/dist/structural-grid/schema.d.ts.map +1 -0
- package/dist/structural-grid/schema.js +1 -0
- package/dist/wall/construction-dimensions.d.ts +32 -0
- package/dist/wall/construction-dimensions.d.ts.map +1 -0
- package/dist/wall/construction-dimensions.js +1031 -0
- package/dist/wall/contextual-dimensions.d.ts +13 -0
- package/dist/wall/contextual-dimensions.d.ts.map +1 -0
- package/dist/wall/contextual-dimensions.js +221 -0
- package/dist/wall/definition.d.ts.map +1 -1
- package/dist/wall/definition.js +20 -1
- package/dist/wall/floorplan-affordances.d.ts.map +1 -1
- package/dist/wall/floorplan-affordances.js +23 -8
- package/dist/wall/floorplan-overrides.d.ts +9 -6
- package/dist/wall/floorplan-overrides.d.ts.map +1 -1
- package/dist/wall/floorplan-overrides.js +20 -11
- package/dist/wall/floorplan.d.ts +12 -3
- package/dist/wall/floorplan.d.ts.map +1 -1
- package/dist/wall/floorplan.js +121 -79
- package/dist/wall/measurement.d.ts.map +1 -1
- package/dist/wall/measurement.js +17 -4
- package/dist/wall/move-endpoint-tool.d.ts.map +1 -1
- package/dist/wall/move-endpoint-tool.js +74 -27
- package/dist/wall/move-shared.d.ts.map +1 -1
- package/dist/wall/move-shared.js +3 -2
- package/dist/wall/move-tool.d.ts.map +1 -1
- package/dist/wall/move-tool.js +15 -3
- package/dist/wall/paint.d.ts.map +1 -1
- package/dist/wall/paint.js +10 -6
- package/dist/wall/panel.d.ts.map +1 -1
- package/dist/wall/panel.js +32 -5
- package/dist/wall/parametrics.d.ts.map +1 -1
- package/dist/wall/parametrics.js +2 -0
- package/dist/wall/preview.d.ts +8 -0
- package/dist/wall/preview.d.ts.map +1 -0
- package/dist/wall/preview.js +39 -0
- package/dist/wall/quick-measurement.d.ts.map +1 -1
- package/dist/wall/quick-measurement.js +3 -2
- package/dist/wall/renderer.d.ts.map +1 -1
- package/dist/wall/renderer.js +7 -5
- package/dist/wall/system.d.ts +1 -1
- package/dist/wall/system.d.ts.map +1 -1
- package/dist/wall/system.js +37 -2
- package/dist/wall/tool.d.ts.map +1 -1
- package/dist/wall/tool.js +148 -96
- package/dist/wall/treatment-level-data.d.ts +14 -0
- package/dist/wall/treatment-level-data.d.ts.map +1 -0
- package/dist/wall/treatment-level-data.js +31 -0
- package/dist/wall/treatments.d.ts +8 -2
- package/dist/wall/treatments.d.ts.map +1 -1
- package/dist/wall/treatments.js +58 -15
- package/dist/window/contextual-dimensions.d.ts +3 -0
- package/dist/window/contextual-dimensions.d.ts.map +1 -0
- package/dist/window/contextual-dimensions.js +7 -0
- package/dist/window/definition.d.ts.map +1 -1
- package/dist/window/definition.js +15 -10
- package/dist/window/floorplan-affordances.d.ts +2 -5
- package/dist/window/floorplan-affordances.d.ts.map +1 -1
- package/dist/window/floorplan-affordances.js +9 -15
- package/dist/window/floorplan-move.d.ts.map +1 -1
- package/dist/window/floorplan-move.js +1 -1
- package/dist/window/floorplan.d.ts.map +1 -1
- package/dist/window/floorplan.js +7 -1
- package/dist/window/move-tool.d.ts.map +1 -1
- package/dist/window/move-tool.js +3 -3
- package/dist/window/panel.d.ts.map +1 -1
- package/dist/window/panel.js +4 -1
- package/dist/window/schema.d.ts +1 -1
- package/dist/window/schema.d.ts.map +1 -1
- package/dist/window/schema.js +1 -1
- package/dist/window/tool.d.ts.map +1 -1
- package/dist/window/tool.js +64 -7
- package/dist/window/window-math.d.ts +7 -3
- package/dist/window/window-math.d.ts.map +1 -1
- package/dist/window/window-math.js +8 -3
- package/dist/zone/contextual-dimensions.d.ts +3 -0
- package/dist/zone/contextual-dimensions.d.ts.map +1 -0
- package/dist/zone/contextual-dimensions.js +43 -0
- package/dist/zone/definition.d.ts.map +1 -1
- package/dist/zone/definition.js +9 -1
- package/dist/zone/floorplan-affordances.d.ts +36 -0
- package/dist/zone/floorplan-affordances.d.ts.map +1 -1
- package/dist/zone/floorplan.d.ts.map +1 -1
- package/dist/zone/floorplan.js +59 -3
- package/dist/zone/quantities-panel.d.ts.map +1 -1
- package/dist/zone/quantities-panel.js +48 -4
- package/dist/zone/room-clear-dimensions.d.ts +3 -0
- package/dist/zone/room-clear-dimensions.d.ts.map +1 -0
- package/dist/zone/room-clear-dimensions.js +406 -0
- package/dist/zone/room-documentation.d.ts +11 -0
- package/dist/zone/room-documentation.d.ts.map +1 -0
- package/dist/zone/room-documentation.js +97 -0
- package/package.json +7 -7
package/dist/shelf/tool.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { collectAlignmentAnchors, emitter, ShelfNode, useScene, } from '@pascal-app/core';
|
|
3
|
+
import { collectAlignmentAnchors, emitter, resolveSupportSlabPatch, ShelfNode, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { getFloorStackPreviewPosition, isAlignmentGuideActive, isGridSnapActive, isMagneticSnapActive, movementSfxStepKey, triggerSFX, useAlignmentGuides, useEditor, } from '@pascal-app/editor';
|
|
5
5
|
import { useViewer } from '@pascal-app/viewer';
|
|
6
6
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
@@ -92,9 +92,14 @@ const ShelfTool = () => {
|
|
|
92
92
|
name: 'Shelf',
|
|
93
93
|
position,
|
|
94
94
|
rotation: [0, 0, 0],
|
|
95
|
+
parentId: activeLevelId,
|
|
95
96
|
});
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
const committedShelf = ShelfNode.parse({
|
|
98
|
+
...shelf,
|
|
99
|
+
...resolveSupportSlabPatch(shelf, useScene.getState().nodes),
|
|
100
|
+
});
|
|
101
|
+
useScene.getState().createNode(committedShelf, activeLevelId);
|
|
102
|
+
useViewer.getState().setSelection({ selectedIds: [committedShelf.id] });
|
|
98
103
|
triggerSFX('sfx:item-place');
|
|
99
104
|
useAlignmentGuides.getState().clear();
|
|
100
105
|
if (useEditor.getState().getContinuation('point') === 'repeat') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recessed-slab-ground-holes.d.ts","sourceRoot":"","sources":["../../src/site/recessed-slab-ground-holes.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,kBAAkB,CAAA;AAEzB,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"recessed-slab-ground-holes.d.ts","sourceRoot":"","sources":["../../src/site/recessed-slab-ground-holes.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,kBAAkB,CAAA;AAEzB,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CA2ChC"}
|
|
@@ -25,10 +25,7 @@ export function getRecessedSlabGroundHoles(nodes) {
|
|
|
25
25
|
pushByLevel(slabsByLevel, levelId, node);
|
|
26
26
|
}
|
|
27
27
|
return nodeList
|
|
28
|
-
.filter((node) => node.type === 'slab' &&
|
|
29
|
-
node.visible &&
|
|
30
|
-
node.polygon.length >= 3 &&
|
|
31
|
-
(node.elevation ?? 0.05) < 0)
|
|
28
|
+
.filter((node) => node.type === 'slab' && node.visible && node.polygon.length >= 3 && node.recessed === true)
|
|
32
29
|
.filter((slab) => {
|
|
33
30
|
if (!Number.isFinite(lowestLevelIndex))
|
|
34
31
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/site/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,QAAQ,
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/site/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,QAAQ,EAOd,MAAM,kBAAkB,CAAA;AAoHzB,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,uCAmRxD,CAAA;AAED,eAAe,YAAY,CAAA"}
|
package/dist/site/renderer.js
CHANGED
|
@@ -1,38 +1,58 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useLiveNodeOverrides, useRegistry, useScene, } from '@pascal-app/core';
|
|
3
|
+
import { terrainFieldOf, useLiveNodeOverrides, useLiveTerrain, useRegistry, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { backdropGradient, deepSkyColor, getSceneTheme, horizonHazeColor, NodeRenderer, unionPolygons, useNodeEvents, useViewer, } from '@pascal-app/viewer';
|
|
5
5
|
import { useEffect, useMemo, useRef } from 'react';
|
|
6
|
-
import {
|
|
6
|
+
import { BufferAttribute, BufferGeometry, Path, Shape, ShapeGeometry } from 'three';
|
|
7
7
|
import { cameraPosition, color, float, mix, positionWorld, smoothstep, vec2 } from 'three/tsl';
|
|
8
8
|
import { MeshLambertNodeMaterial } from 'three/webgpu';
|
|
9
9
|
import { getRecessedSlabGroundHoles } from './recessed-slab-ground-holes';
|
|
10
|
+
import { buildDrapedPolyline, terrainGridKey, updateDrapedHeights, } from './terrain-drape';
|
|
11
|
+
import { HORIZON_PLANE_Y, terrainFootprint } from './terrain-geometry';
|
|
12
|
+
import { TerrainRenderer } from './terrain-renderer';
|
|
10
13
|
const Y_OFFSET = 0.01;
|
|
11
14
|
// The horizon disc is presentation-only — clicks must fall through to the
|
|
12
15
|
// real site polygon / grid, so its raycast is a no-op.
|
|
13
16
|
const noopRaycast = () => { };
|
|
14
17
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
18
|
+
* The site boundary line, laid on the ground rather than across it.
|
|
19
|
+
*
|
|
20
|
+
* On flat ground this is the ring it always was: the polygon's own vertices at
|
|
21
|
+
* `Y_OFFSET`, closed. On sculpted ground `buildDrapedPolyline` subdivides each
|
|
22
|
+
* edge at the mesh's creases so the line lies *on* the rendered surface — the lot
|
|
23
|
+
* line is on screen in every frame, and a flat ring slicing through a hill is the
|
|
24
|
+
* most conspicuous way sculpted ground reads as broken.
|
|
25
|
+
*
|
|
26
|
+
* UVs keep their `(u, 0)` layout, now measured as normalized XZ arc length rather
|
|
27
|
+
* than vertex index, so a dashed or gradient material spaces evenly along the
|
|
28
|
+
* perimeter instead of bunching on short edges.
|
|
17
29
|
*/
|
|
18
|
-
const createBoundaryLineGeometry = (points) => {
|
|
30
|
+
const createBoundaryLineGeometry = (points, field) => {
|
|
19
31
|
const geometry = new BufferGeometry();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
uvs.push(1, 0);
|
|
32
|
-
geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
|
|
33
|
-
geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
|
|
34
|
-
return geometry;
|
|
32
|
+
const ring = buildDrapedPolyline({ points, field, lift: Y_OFFSET, closed: true });
|
|
33
|
+
const uvs = new Float32Array(ring.uvs.length * 2);
|
|
34
|
+
for (let index = 0; index < ring.uvs.length; index++) {
|
|
35
|
+
uvs[index * 2] = ring.uvs[index] ?? 0;
|
|
36
|
+
}
|
|
37
|
+
// `BufferAttribute`, not `Float32BufferAttribute`: the latter copies its input
|
|
38
|
+
// into a fresh array, which would leave `ring.positions` a detached CPU copy and
|
|
39
|
+
// silently break the in-place dab rewrite below. This shares the buffer.
|
|
40
|
+
geometry.setAttribute('position', new BufferAttribute(ring.positions, 3));
|
|
41
|
+
geometry.setAttribute('uv', new BufferAttribute(uvs, 2));
|
|
42
|
+
return { geometry, ring };
|
|
35
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Re-upload a draped ring's positions after an in-place height rewrite.
|
|
46
|
+
*
|
|
47
|
+
* No `addUpdateRange`: a dab can move any subset of the ring's vertices (the brush
|
|
48
|
+
* is a disc, the ring is a loop, and the two intersect in up to four arcs), so a
|
|
49
|
+
* range would either be the whole buffer or a list. The whole buffer is a few KB.
|
|
50
|
+
*/
|
|
51
|
+
function markPositionsDirty(geometry) {
|
|
52
|
+
const attribute = geometry.getAttribute('position');
|
|
53
|
+
attribute.needsUpdate = true;
|
|
54
|
+
geometry.computeBoundingSphere();
|
|
55
|
+
}
|
|
36
56
|
function polygonsMatch(a, b) {
|
|
37
57
|
return (a.length === b.length &&
|
|
38
58
|
a.every((polygon, polygonIndex) => polygon.length === b[polygonIndex]?.length &&
|
|
@@ -66,6 +86,18 @@ export const SiteRenderer = ({ node }) => {
|
|
|
66
86
|
const maxLightIntensity = useViewer((state) => Math.max(1, ...getSceneTheme(state.sceneTheme).lights.map((light) => light.intensity)));
|
|
67
87
|
const livePolygon = useLiveNodeOverrides((state) => state.overrides.get(node.id)?.polygon ?? null);
|
|
68
88
|
const polygonPoints = livePolygon?.points ?? node.polygon?.points;
|
|
89
|
+
// Persisted terrain only. A stroke is applied imperatively further down, for the
|
|
90
|
+
// same reason `TerrainRenderer` does it: a dab must not re-render this subtree.
|
|
91
|
+
const persistedField = useMemo(() => terrainFieldOf({ id: node.id, terrain: node.terrain }), [node.id, node.terrain]);
|
|
92
|
+
// The terrain *grid* — mounting, resizing, or re-origining it. `hasTerrain`
|
|
93
|
+
// catches the first dab on a site that had no terrain at all, which is the one
|
|
94
|
+
// case where a stroke legitimately changes the grid and everything keyed on it
|
|
95
|
+
// (the horizon punch, the boundary subdivision) has to rebuild.
|
|
96
|
+
const hasTerrain = useLiveTerrain((state) => state.strokes.has(node.id));
|
|
97
|
+
const terrainGrid = hasTerrain
|
|
98
|
+
? (useLiveTerrain.getState().fieldOf(node.id) ?? persistedField)
|
|
99
|
+
: persistedField;
|
|
100
|
+
const terrainKey = terrainGridKey(terrainGrid);
|
|
69
101
|
// Centroid + radius of the lot polygon, for the presentation fade below.
|
|
70
102
|
const fadeBounds = useMemo(() => {
|
|
71
103
|
if (!polygonPoints || polygonPoints.length < 3)
|
|
@@ -158,6 +190,10 @@ export const SiteRenderer = ({ node }) => {
|
|
|
158
190
|
addSlabHoles(shape, slabPolygons);
|
|
159
191
|
return shape;
|
|
160
192
|
}, [polygonPoints, slabPolygons]);
|
|
193
|
+
// The terrain footprint is punched out alongside the recessed slabs, and for the
|
|
194
|
+
// same reason: the disc must not cap ground that is modelled below it.
|
|
195
|
+
//
|
|
196
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: `terrainKey` is the grid signature the footprint is a function of; depending on the field itself would rebuild an 800 m disc every dab.
|
|
161
197
|
const horizonGeometry = useMemo(() => {
|
|
162
198
|
if (!fadeBounds)
|
|
163
199
|
return null;
|
|
@@ -170,17 +206,65 @@ export const SiteRenderer = ({ node }) => {
|
|
|
170
206
|
shape.lineTo(Math.cos(angle) * radius, Math.sin(angle) * radius);
|
|
171
207
|
}
|
|
172
208
|
shape.closePath();
|
|
173
|
-
|
|
209
|
+
const holes = terrainGrid ? [...slabPolygons, terrainFootprint(terrainGrid)] : slabPolygons;
|
|
210
|
+
addSlabHoles(shape, holes, fadeBounds.cx, fadeBounds.cz);
|
|
174
211
|
return new ShapeGeometry(shape);
|
|
175
|
-
}, [fadeBounds, slabPolygons]);
|
|
212
|
+
}, [fadeBounds, slabPolygons, terrainKey]);
|
|
176
213
|
useEffect(() => () => horizonGeometry?.dispose(), [horizonGeometry]);
|
|
177
|
-
//
|
|
178
|
-
|
|
214
|
+
// Boundary line geometry, subdivided against the terrain grid when there is one.
|
|
215
|
+
//
|
|
216
|
+
// Keyed on `terrainKey`, not on the field: the crease set depends on the grid
|
|
217
|
+
// (origin/spacing/extent) and not on the heights, so a sculpt stroke — which
|
|
218
|
+
// produces a new field object every dab — must not rebuild this. Heights are
|
|
219
|
+
// rewritten in place by the subscription below.
|
|
220
|
+
//
|
|
221
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: keyed on the grid, not the field — see above.
|
|
222
|
+
const boundary = useMemo(() => {
|
|
179
223
|
if (!polygonPoints || polygonPoints.length < 2)
|
|
180
224
|
return null;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
225
|
+
// The live field, not the persisted one: mounting mid-stroke (the very first
|
|
226
|
+
// dab on a terrain-free site remounts this subtree) must drape against the
|
|
227
|
+
// ground actually on screen.
|
|
228
|
+
const field = useLiveTerrain.getState().fieldOf(node.id) ?? persistedField;
|
|
229
|
+
return createBoundaryLineGeometry(polygonPoints, field);
|
|
230
|
+
}, [polygonPoints, terrainKey, node.id]);
|
|
231
|
+
useEffect(() => () => boundary?.geometry.dispose(), [boundary]);
|
|
232
|
+
const lineGeometry = boundary?.geometry ?? null;
|
|
233
|
+
// Per-dab height rewrite, imperative for the same reason `TerrainRenderer`'s
|
|
234
|
+
// upload is: a stroke pushes dozens of patches a second, and routing each through
|
|
235
|
+
// a React render would rebuild the ring's buffers at pointer rate. The XZ of every
|
|
236
|
+
// vertex is a function of the grid alone, so a dab only ever moves Y.
|
|
237
|
+
//
|
|
238
|
+
// A ref, not the memo value in a dependency, so the subscription outlives a
|
|
239
|
+
// polygon edit without resubscribing.
|
|
240
|
+
const boundaryRef = useRef(null);
|
|
241
|
+
boundaryRef.current = boundary;
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
let lastPatch = useLiveTerrain.getState().strokeOf(node.id)?.lastPatch ?? null;
|
|
244
|
+
return useLiveTerrain.subscribe((state) => {
|
|
245
|
+
const current = boundaryRef.current;
|
|
246
|
+
if (!current)
|
|
247
|
+
return;
|
|
248
|
+
const stroke = state.strokes.get(node.id);
|
|
249
|
+
if (!stroke) {
|
|
250
|
+
// The stroke ended. Its heights are now the node's, and the commit
|
|
251
|
+
// re-rendered this subtree with them — but a *cancelled* stroke (Escape)
|
|
252
|
+
// commits nothing, so the ring has to be put back explicitly.
|
|
253
|
+
if (lastPatch) {
|
|
254
|
+
lastPatch = null;
|
|
255
|
+
const field = terrainFieldOf({ id: node.id, terrain: node.terrain });
|
|
256
|
+
updateDrapedHeights(current.ring, field, Y_OFFSET);
|
|
257
|
+
markPositionsDirty(current.geometry);
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (!stroke.lastPatch || stroke.lastPatch === lastPatch)
|
|
262
|
+
return;
|
|
263
|
+
lastPatch = stroke.lastPatch;
|
|
264
|
+
updateDrapedHeights(current.ring, stroke.field, Y_OFFSET);
|
|
265
|
+
markPositionsDirty(current.geometry);
|
|
266
|
+
});
|
|
267
|
+
}, [node.id, node.terrain]);
|
|
184
268
|
const groundGeometry = useMemo(() => {
|
|
185
269
|
if (!groundShape)
|
|
186
270
|
return null;
|
|
@@ -188,9 +272,15 @@ export const SiteRenderer = ({ node }) => {
|
|
|
188
272
|
}, [groundShape]);
|
|
189
273
|
useEffect(() => () => groundGeometry?.dispose(), [groundGeometry]);
|
|
190
274
|
const handlers = useNodeEvents(node, 'site');
|
|
275
|
+
// Terrain replaces the flat ground fill rather than sitting on top of it: two
|
|
276
|
+
// coplanar ground surfaces z-fight, and the flat one would poke through any
|
|
277
|
+
// excavation. `hasTerrain` (above) is subscribed at the site level rather than
|
|
278
|
+
// inside TerrainRenderer so a stroke that starts on a site with no persisted
|
|
279
|
+
// terrain still mounts the mesh.
|
|
280
|
+
const showTerrain = terrainGrid !== null;
|
|
191
281
|
if (!(node && lineGeometry)) {
|
|
192
282
|
return null;
|
|
193
283
|
}
|
|
194
|
-
return (_jsxs("group", { ref: ref, ...handlers, children: [(node.children ?? []).map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId))), groundGeometry && (_jsx("mesh", { geometry: groundGeometry, material: groundMaterial, position: [0, -0.05, 0], receiveShadow: true, rotation: [-Math.PI / 2, 0, 0] })), horizonGeometry && horizonMaterial && fadeBounds && (_jsx("mesh", { geometry: horizonGeometry, material: horizonMaterial, position: [fadeBounds.cx,
|
|
284
|
+
return (_jsxs("group", { ref: ref, ...handlers, children: [(node.children ?? []).map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId))), showTerrain && _jsx(TerrainRenderer, { material: groundMaterial, site: node }), groundGeometry && !showTerrain && (_jsx("mesh", { geometry: groundGeometry, material: groundMaterial, position: [0, -0.05, 0], receiveShadow: true, rotation: [-Math.PI / 2, 0, 0] })), horizonGeometry && horizonMaterial && fadeBounds && (_jsx("mesh", { geometry: horizonGeometry, material: horizonMaterial, position: [fadeBounds.cx, HORIZON_PLANE_Y, fadeBounds.cz], raycast: noopRaycast, receiveShadow: true, rotation: [-Math.PI / 2, 0, 0], userData: { pascalExport: 'strip' } })), _jsx("line", { frustumCulled: false, geometry: lineGeometry, renderOrder: 9, children: _jsx("lineBasicMaterial", { color: "#f59e0b", linewidth: 2, opacity: 0.6, transparent: true }) })] }));
|
|
195
285
|
};
|
|
196
286
|
export default SiteRenderer;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Laying a polyline *on* the terrain surface, exactly.
|
|
3
|
+
*
|
|
4
|
+
* The site boundary is the first consumer: a ring of four flat vertices at
|
|
5
|
+
* `y = 0.01` slices straight through any hill it crosses and floats over any
|
|
6
|
+
* excavation, which is the single most obvious way sculpted ground looks broken —
|
|
7
|
+
* the lot line is the one element always on screen.
|
|
8
|
+
*
|
|
9
|
+
* The approach is exactness rather than density. The rendered surface is
|
|
10
|
+
* piecewise-**planar**: `buildTerrainMesh` emits two triangles per cell, so the
|
|
11
|
+
* only places it creases are the grid lines and each cell's diagonal. Subdivide a
|
|
12
|
+
* segment at exactly those crossings and every resulting sub-segment lies inside
|
|
13
|
+
* one triangle, where the surface is a plane and a straight line on it is on it
|
|
14
|
+
* everywhere. Uniform oversampling can only approach that, and needs an arbitrary
|
|
15
|
+
* density to argue about.
|
|
16
|
+
*
|
|
17
|
+
* In field index space (`u = (x - originX) / spacing`, `v = (z - originZ) /
|
|
18
|
+
* spacing`) the three crease families are just `u ∈ ℤ`, `v ∈ ℤ`, and `u + v ∈ ℤ` —
|
|
19
|
+
* the last being the diagonals, since a cell's diagonal runs from `(c+1, r)` to
|
|
20
|
+
* `(c, r+1)`. All three are affine in the segment parameter, so each crossing is
|
|
21
|
+
* one division.
|
|
22
|
+
*
|
|
23
|
+
* Heights come from `surfaceHeightAt`, the explicit rendered-surface alias for
|
|
24
|
+
* the shared `heightAt` triangle-plane authority.
|
|
25
|
+
*
|
|
26
|
+
* Nothing here touches Three.js: it is array math over a field, so the guarantee
|
|
27
|
+
* that a sub-segment lies inside one triangle is asserted in unit tests rather
|
|
28
|
+
* than eyeballed on a canvas.
|
|
29
|
+
*/
|
|
30
|
+
import { type TerrainField } from '@pascal-app/core';
|
|
31
|
+
/** A draped polyline: xyz per vertex, plus normalized arc length per vertex. */
|
|
32
|
+
export type DrapedPolyline = {
|
|
33
|
+
/** xyz per vertex, length `count * 3`. */
|
|
34
|
+
positions: Float32Array;
|
|
35
|
+
/** Normalized distance along the line per vertex, length `count`. */
|
|
36
|
+
uvs: Float32Array;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Parameters in `(0, 1)` where the segment crosses a crease of the rendered
|
|
40
|
+
* surface, sorted and deduplicated.
|
|
41
|
+
*
|
|
42
|
+
* Exported for tests: the crease convention is the load-bearing part, and
|
|
43
|
+
* asserting it on the parameter list is far sharper than inferring it from
|
|
44
|
+
* vertex positions.
|
|
45
|
+
*/
|
|
46
|
+
export declare function creaseCrossings(field: TerrainField, ax: number, az: number, bx: number, bz: number): number[];
|
|
47
|
+
/**
|
|
48
|
+
* Build a draped polyline through `points`.
|
|
49
|
+
*
|
|
50
|
+
* `field` of `null` means flat ground at the datum: the result is the input
|
|
51
|
+
* points at `y = lift`, no subdivision. Keeping that case *here* rather than at
|
|
52
|
+
* every call site is what lets a consumer hold one geometry and one code path
|
|
53
|
+
* whether or not the site has terrain — and it means a scene that never touched
|
|
54
|
+
* terrain builds byte-identical buffers to the ones it built before draping
|
|
55
|
+
* existed.
|
|
56
|
+
*
|
|
57
|
+
* `closed` repeats the first point at the end, so a `LineStrip` closes the ring.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildDrapedPolyline({ points, field, lift, closed, }: {
|
|
60
|
+
points: ReadonlyArray<readonly [number, number]>;
|
|
61
|
+
field: TerrainField | null;
|
|
62
|
+
lift: number;
|
|
63
|
+
closed: boolean;
|
|
64
|
+
}): DrapedPolyline;
|
|
65
|
+
/**
|
|
66
|
+
* Rewrite only the heights of an existing draped polyline, in place.
|
|
67
|
+
*
|
|
68
|
+
* This is the sculpt-stroke path. The crease set is a function of the field's
|
|
69
|
+
* *grid* — origin, spacing, sample counts — and not of its heights, so every dab
|
|
70
|
+
* of a stroke leaves the XZ of every vertex exactly where it was. Rebuilding the
|
|
71
|
+
* ring per dab would reallocate two buffers and a `BufferGeometry` at pointer
|
|
72
|
+
* rate for a result that differs only in Y.
|
|
73
|
+
*
|
|
74
|
+
* Reads each vertex's own XZ back out of the buffer rather than taking the source
|
|
75
|
+
* polygon again: there is then no second copy of the subdivision to keep in step.
|
|
76
|
+
*
|
|
77
|
+
* Arc lengths are deliberately *not* refreshed — see `writeArcLengths`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function updateDrapedHeights(ring: DrapedPolyline, field: TerrainField | null, lift: number): void;
|
|
80
|
+
/**
|
|
81
|
+
* Everything about a field that changes the crease set, and nothing that doesn't.
|
|
82
|
+
*
|
|
83
|
+
* A React consumer needs to rebuild a draped ring when the *grid* moves and only
|
|
84
|
+
* rewrite heights when the *heights* move. Keying a memo on the field object would
|
|
85
|
+
* rebuild every dab (each `applyHeightPatch` returns a new field); keying it on
|
|
86
|
+
* this rebuilds only when a resize or re-origin actually invalidates the
|
|
87
|
+
* subdivision, which in practice means import. `null` for no terrain, so the
|
|
88
|
+
* flat-ground ring is one more value in the same key space.
|
|
89
|
+
*/
|
|
90
|
+
export declare function terrainGridKey(field: TerrainField | null): string | null;
|
|
91
|
+
//# sourceMappingURL=terrain-drape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terrain-drape.d.ts","sourceRoot":"","sources":["../../src/site/terrain-drape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAErE,gFAAgF;AAChF,MAAM,MAAM,cAAc,GAAG;IAC3B,0CAA0C;IAC1C,SAAS,EAAE,YAAY,CAAA;IACvB,qEAAqE;IACrE,GAAG,EAAE,YAAY,CAAA;CAClB,CAAA;AAcD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,YAAY,EACnB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,GACT,MAAM,EAAE,CAuBV;AA+BD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,KAAK,EACL,IAAI,EACJ,MAAM,GACP,EAAE;IACD,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAChD,KAAK,EAAE,YAAY,GAAG,IAAI,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;CAChB,GAAG,cAAc,CA+BjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,YAAY,GAAG,IAAI,EAC1B,IAAI,EAAE,MAAM,GACX,IAAI,CAEN;AAmCD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAGxE"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Laying a polyline *on* the terrain surface, exactly.
|
|
3
|
+
*
|
|
4
|
+
* The site boundary is the first consumer: a ring of four flat vertices at
|
|
5
|
+
* `y = 0.01` slices straight through any hill it crosses and floats over any
|
|
6
|
+
* excavation, which is the single most obvious way sculpted ground looks broken —
|
|
7
|
+
* the lot line is the one element always on screen.
|
|
8
|
+
*
|
|
9
|
+
* The approach is exactness rather than density. The rendered surface is
|
|
10
|
+
* piecewise-**planar**: `buildTerrainMesh` emits two triangles per cell, so the
|
|
11
|
+
* only places it creases are the grid lines and each cell's diagonal. Subdivide a
|
|
12
|
+
* segment at exactly those crossings and every resulting sub-segment lies inside
|
|
13
|
+
* one triangle, where the surface is a plane and a straight line on it is on it
|
|
14
|
+
* everywhere. Uniform oversampling can only approach that, and needs an arbitrary
|
|
15
|
+
* density to argue about.
|
|
16
|
+
*
|
|
17
|
+
* In field index space (`u = (x - originX) / spacing`, `v = (z - originZ) /
|
|
18
|
+
* spacing`) the three crease families are just `u ∈ ℤ`, `v ∈ ℤ`, and `u + v ∈ ℤ` —
|
|
19
|
+
* the last being the diagonals, since a cell's diagonal runs from `(c+1, r)` to
|
|
20
|
+
* `(c, r+1)`. All three are affine in the segment parameter, so each crossing is
|
|
21
|
+
* one division.
|
|
22
|
+
*
|
|
23
|
+
* Heights come from `surfaceHeightAt`, the explicit rendered-surface alias for
|
|
24
|
+
* the shared `heightAt` triangle-plane authority.
|
|
25
|
+
*
|
|
26
|
+
* Nothing here touches Three.js: it is array math over a field, so the guarantee
|
|
27
|
+
* that a sub-segment lies inside one triangle is asserted in unit tests rather
|
|
28
|
+
* than eyeballed on a canvas.
|
|
29
|
+
*/
|
|
30
|
+
import { surfaceHeightAt } from '@pascal-app/core';
|
|
31
|
+
/**
|
|
32
|
+
* Crossings per segment, as a backstop only.
|
|
33
|
+
*
|
|
34
|
+
* A real segment produces at most ~3 × the field's sample count along it — a few
|
|
35
|
+
* hundred at 257². The cap exists so a caller that passes a segment spanning a
|
|
36
|
+
* degenerate field (tiny `spacing`, huge extent) cannot allocate unboundedly.
|
|
37
|
+
*/
|
|
38
|
+
const MAX_CROSSINGS_PER_SEGMENT = 4096;
|
|
39
|
+
/** Two crossings closer than this (in segment parameter) are the same vertex. */
|
|
40
|
+
const PARAM_EPSILON = 1e-9;
|
|
41
|
+
/**
|
|
42
|
+
* Parameters in `(0, 1)` where the segment crosses a crease of the rendered
|
|
43
|
+
* surface, sorted and deduplicated.
|
|
44
|
+
*
|
|
45
|
+
* Exported for tests: the crease convention is the load-bearing part, and
|
|
46
|
+
* asserting it on the parameter list is far sharper than inferring it from
|
|
47
|
+
* vertex positions.
|
|
48
|
+
*/
|
|
49
|
+
export function creaseCrossings(field, ax, az, bx, bz) {
|
|
50
|
+
const u0 = (ax - field.origin[0]) / field.spacing;
|
|
51
|
+
const v0 = (az - field.origin[1]) / field.spacing;
|
|
52
|
+
const u1 = (bx - field.origin[0]) / field.spacing;
|
|
53
|
+
const v1 = (bz - field.origin[1]) / field.spacing;
|
|
54
|
+
const crossings = [];
|
|
55
|
+
// Families are clamped to the index range the surface actually creases over.
|
|
56
|
+
// Outside the field the surface is the border cell's plane extruded outward, so
|
|
57
|
+
// crossings out there would add vertices to a stretch that is already flat —
|
|
58
|
+
// harmless geometrically, but an edge far from a fine grid would emit thousands.
|
|
59
|
+
addFamily(crossings, u0, u1, 0, field.cols - 1);
|
|
60
|
+
addFamily(crossings, v0, v1, 0, field.rows - 1);
|
|
61
|
+
addFamily(crossings, u0 + v0, u1 + v1, 0, field.cols - 1 + (field.rows - 1));
|
|
62
|
+
crossings.sort((a, b) => a - b);
|
|
63
|
+
const unique = [];
|
|
64
|
+
for (const t of crossings) {
|
|
65
|
+
const previous = unique.at(-1);
|
|
66
|
+
if (previous !== undefined && t - previous <= PARAM_EPSILON)
|
|
67
|
+
continue;
|
|
68
|
+
unique.push(t);
|
|
69
|
+
}
|
|
70
|
+
return unique;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Push the parameters where an affine coordinate `from → to` passes an integer in
|
|
74
|
+
* `[loBound, hiBound]`.
|
|
75
|
+
*/
|
|
76
|
+
function addFamily(out, from, to, loBound, hiBound) {
|
|
77
|
+
const delta = to - from;
|
|
78
|
+
// Parallel to this family: no crossing, whether or not it lies *on* a crease.
|
|
79
|
+
// A segment running along a grid line is already inside the planes on both
|
|
80
|
+
// sides, so it needs no extra vertex.
|
|
81
|
+
if (Math.abs(delta) < 1e-12)
|
|
82
|
+
return;
|
|
83
|
+
const lo = Math.max(loBound, Math.ceil(Math.min(from, to)));
|
|
84
|
+
const hi = Math.min(hiBound, Math.floor(Math.max(from, to)));
|
|
85
|
+
let emitted = 0;
|
|
86
|
+
for (let k = lo; k <= hi; k++) {
|
|
87
|
+
const t = (k - from) / delta;
|
|
88
|
+
if (t <= PARAM_EPSILON || t >= 1 - PARAM_EPSILON)
|
|
89
|
+
continue;
|
|
90
|
+
out.push(t);
|
|
91
|
+
emitted += 1;
|
|
92
|
+
if (emitted >= MAX_CROSSINGS_PER_SEGMENT)
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build a draped polyline through `points`.
|
|
98
|
+
*
|
|
99
|
+
* `field` of `null` means flat ground at the datum: the result is the input
|
|
100
|
+
* points at `y = lift`, no subdivision. Keeping that case *here* rather than at
|
|
101
|
+
* every call site is what lets a consumer hold one geometry and one code path
|
|
102
|
+
* whether or not the site has terrain — and it means a scene that never touched
|
|
103
|
+
* terrain builds byte-identical buffers to the ones it built before draping
|
|
104
|
+
* existed.
|
|
105
|
+
*
|
|
106
|
+
* `closed` repeats the first point at the end, so a `LineStrip` closes the ring.
|
|
107
|
+
*/
|
|
108
|
+
export function buildDrapedPolyline({ points, field, lift, closed, }) {
|
|
109
|
+
const xz = [];
|
|
110
|
+
const segments = closed ? points.length : points.length - 1;
|
|
111
|
+
for (let index = 0; index < segments; index++) {
|
|
112
|
+
const from = points[index];
|
|
113
|
+
const to = points[(index + 1) % points.length];
|
|
114
|
+
if (!(from && to))
|
|
115
|
+
continue;
|
|
116
|
+
const [ax, az] = from;
|
|
117
|
+
const [bx, bz] = to;
|
|
118
|
+
xz.push([ax, az]);
|
|
119
|
+
if (!field)
|
|
120
|
+
continue;
|
|
121
|
+
for (const t of creaseCrossings(field, ax, az, bx, bz)) {
|
|
122
|
+
xz.push([ax + (bx - ax) * t, az + (bz - az) * t]);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// The final vertex: the ring's start again when closed, otherwise the last
|
|
126
|
+
// point, which the segment loop above never emits.
|
|
127
|
+
const last = closed ? points[0] : points.at(-1);
|
|
128
|
+
if (last)
|
|
129
|
+
xz.push([last[0], last[1]]);
|
|
130
|
+
const positions = new Float32Array(xz.length * 3);
|
|
131
|
+
for (let index = 0; index < xz.length; index++) {
|
|
132
|
+
const [x, z] = xz[index];
|
|
133
|
+
positions[index * 3] = x;
|
|
134
|
+
positions[index * 3 + 2] = z;
|
|
135
|
+
}
|
|
136
|
+
const ring = { positions, uvs: new Float32Array(xz.length) };
|
|
137
|
+
writeDrapedHeights(ring, field, lift);
|
|
138
|
+
writeArcLengths(ring);
|
|
139
|
+
return ring;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Rewrite only the heights of an existing draped polyline, in place.
|
|
143
|
+
*
|
|
144
|
+
* This is the sculpt-stroke path. The crease set is a function of the field's
|
|
145
|
+
* *grid* — origin, spacing, sample counts — and not of its heights, so every dab
|
|
146
|
+
* of a stroke leaves the XZ of every vertex exactly where it was. Rebuilding the
|
|
147
|
+
* ring per dab would reallocate two buffers and a `BufferGeometry` at pointer
|
|
148
|
+
* rate for a result that differs only in Y.
|
|
149
|
+
*
|
|
150
|
+
* Reads each vertex's own XZ back out of the buffer rather than taking the source
|
|
151
|
+
* polygon again: there is then no second copy of the subdivision to keep in step.
|
|
152
|
+
*
|
|
153
|
+
* Arc lengths are deliberately *not* refreshed — see `writeArcLengths`.
|
|
154
|
+
*/
|
|
155
|
+
export function updateDrapedHeights(ring, field, lift) {
|
|
156
|
+
writeDrapedHeights(ring, field, lift);
|
|
157
|
+
}
|
|
158
|
+
function writeDrapedHeights(ring, field, lift) {
|
|
159
|
+
const count = ring.uvs.length;
|
|
160
|
+
for (let index = 0; index < count; index++) {
|
|
161
|
+
const x = ring.positions[index * 3] ?? 0;
|
|
162
|
+
const z = ring.positions[index * 3 + 2] ?? 0;
|
|
163
|
+
ring.positions[index * 3 + 1] = field ? surfaceHeightAt(field, x, z) + lift : lift;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Normalized cumulative distance along the line, measured in **XZ**.
|
|
168
|
+
*
|
|
169
|
+
* Horizontal, not 3D: the value has to be stable while the ground moves under a
|
|
170
|
+
* stroke, and a 3D arc length is not — it would stretch as a hill rises, sliding
|
|
171
|
+
* any dash pattern along the ring dab by dab. XZ length is what a plan-view
|
|
172
|
+
* reading of the lot line uses anyway, so the 2D and 3D views agree on it.
|
|
173
|
+
*/
|
|
174
|
+
function writeArcLengths(ring) {
|
|
175
|
+
const count = ring.uvs.length;
|
|
176
|
+
if (count === 0)
|
|
177
|
+
return;
|
|
178
|
+
let total = 0;
|
|
179
|
+
for (let index = 1; index < count; index++) {
|
|
180
|
+
const dx = (ring.positions[index * 3] ?? 0) - (ring.positions[(index - 1) * 3] ?? 0);
|
|
181
|
+
const dz = (ring.positions[index * 3 + 2] ?? 0) - (ring.positions[(index - 1) * 3 + 2] ?? 0);
|
|
182
|
+
total += Math.hypot(dx, dz);
|
|
183
|
+
ring.uvs[index] = total;
|
|
184
|
+
}
|
|
185
|
+
if (total <= 0)
|
|
186
|
+
return;
|
|
187
|
+
for (let index = 1; index < count; index++) {
|
|
188
|
+
ring.uvs[index] = (ring.uvs[index] ?? 0) / total;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Everything about a field that changes the crease set, and nothing that doesn't.
|
|
193
|
+
*
|
|
194
|
+
* A React consumer needs to rebuild a draped ring when the *grid* moves and only
|
|
195
|
+
* rewrite heights when the *heights* move. Keying a memo on the field object would
|
|
196
|
+
* rebuild every dab (each `applyHeightPatch` returns a new field); keying it on
|
|
197
|
+
* this rebuilds only when a resize or re-origin actually invalidates the
|
|
198
|
+
* subdivision, which in practice means import. `null` for no terrain, so the
|
|
199
|
+
* flat-ground ring is one more value in the same key space.
|
|
200
|
+
*/
|
|
201
|
+
export function terrainGridKey(field) {
|
|
202
|
+
if (!field)
|
|
203
|
+
return null;
|
|
204
|
+
return `${field.origin[0]}:${field.origin[1]}:${field.spacing}:${field.cols}:${field.rows}`;
|
|
205
|
+
}
|