@pascal-app/nodes 0.1.1 → 1.0.0-beta.2
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 +74 -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 +15 -1
- package/dist/fence/lift.d.ts +38 -0
- package/dist/fence/lift.d.ts.map +1 -0
- package/dist/fence/lift.js +51 -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 +46 -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
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { type HeightPatch, type TerrainField } from '@pascal-app/core';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the terrain mesh buffers from a `TerrainField`.
|
|
4
|
+
*
|
|
5
|
+
* Split out of the renderer as pure array math so the vertex layout is
|
|
6
|
+
* unit-testable without a canvas, and so the partial-upload path has one
|
|
7
|
+
* definition. The renderer owns the `BufferGeometry`; this owns what goes in it.
|
|
8
|
+
*
|
|
9
|
+
* The triangulation this file emits is the convention `surfaceHeightAt`
|
|
10
|
+
* (`core/lib/terrain-field.ts`) reads back: two triangles per cell split along the
|
|
11
|
+
* `(c+1,r)`–`(c,r+1)` diagonal. Anything drawn *on* the ground samples that
|
|
12
|
+
* function, so changing the winding below without changing it there would make
|
|
13
|
+
* every draped line sink into half of every cell.
|
|
14
|
+
*
|
|
15
|
+
* Two rules that are easy to get wrong and expensive to debug:
|
|
16
|
+
*
|
|
17
|
+
* - **Normals are analytic, never `computeVertexNormals()`.** Beyond the cost
|
|
18
|
+
* (~1.5 ms at 257²), `computeVertexNormals()` rewrites the *whole* normal
|
|
19
|
+
* attribute, which silently defeats the dirty-rect partial upload that makes
|
|
20
|
+
* sculpting cheap. `normalAt` central-differences the field instead, so a
|
|
21
|
+
* patch touches only the rows it changed.
|
|
22
|
+
* - **Vertices are one-per-sample and shared between triangles**, so a partial
|
|
23
|
+
* upload is a contiguous row range. Duplicating verts per-triangle for flat
|
|
24
|
+
* shading would triple memory and scatter the dirty range.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Y of the site's presentation horizon disc (`site/renderer.tsx`).
|
|
28
|
+
*
|
|
29
|
+
* It lives here, next to the skirt, because the skirt is the thing that has to
|
|
30
|
+
* bracket it: its top edge never sits below this plane and its base always clears
|
|
31
|
+
* it, so no sightline can pass between the sculpted ground and the surrounding
|
|
32
|
+
* one. Two readers, one number.
|
|
33
|
+
*/
|
|
34
|
+
export declare const HORIZON_PLANE_Y = -0.07;
|
|
35
|
+
/** One vertex per sample, row-major — index `r * cols + c` matches the field. */
|
|
36
|
+
export type TerrainMeshBuffers = {
|
|
37
|
+
/** xyz per sample, length `cols * rows * 3`. */
|
|
38
|
+
positions: Float32Array;
|
|
39
|
+
/** xyz per sample, length `cols * rows * 3`. */
|
|
40
|
+
normals: Float32Array;
|
|
41
|
+
/** uv per sample, length `cols * rows * 2`. */
|
|
42
|
+
uvs: Float32Array;
|
|
43
|
+
/** Triangle indices, two per grid cell. */
|
|
44
|
+
indices: Uint32Array;
|
|
45
|
+
};
|
|
46
|
+
export declare function buildTerrainMesh(field: TerrainField): TerrainMeshBuffers;
|
|
47
|
+
/**
|
|
48
|
+
* A vertical curtain around the field boundary, closing the terrain against the
|
|
49
|
+
* site's horizon disc.
|
|
50
|
+
*
|
|
51
|
+
* The disc is a flat plate at `HORIZON_PLANE_Y` standing in for ground beyond the
|
|
52
|
+
* field, and the field's extent is punched out of it so an excavation is not simply
|
|
53
|
+
* hidden under it. That punch leaves an open rim, and sculpted ground turns it into
|
|
54
|
+
* a visible fault: a raised edge shows daylight under the terrain shell, a lowered
|
|
55
|
+
* one shows the disc's cut edge hanging in the air over the pit.
|
|
56
|
+
*
|
|
57
|
+
* The curtain spans `[min(h, H) - SKIRT_DROP, max(h, H)]` at every boundary
|
|
58
|
+
* sample, so it *brackets* both the terrain edge and the horizon plane no matter
|
|
59
|
+
* which is higher. That is the whole correctness argument: a sightline through the
|
|
60
|
+
* rim has to cross the curtain, because the curtain covers the rim's entire Y
|
|
61
|
+
* range at the rim's exact XZ.
|
|
62
|
+
*
|
|
63
|
+
* One vertex pair (top, bottom) per boundary sample, in a ring whose first sample
|
|
64
|
+
* is repeated at the end so the strip closes.
|
|
65
|
+
*/
|
|
66
|
+
export type TerrainSkirtBuffers = {
|
|
67
|
+
/** xyz, `(perimeter + 1) * 2` vertices — top then bottom per ring position. */
|
|
68
|
+
positions: Float32Array;
|
|
69
|
+
normals: Float32Array;
|
|
70
|
+
indices: Uint32Array;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The field's own footprint, as a polygon to punch out of the horizon disc.
|
|
74
|
+
*
|
|
75
|
+
* The punch is the only reason an excavation is visible at all: without it the disc
|
|
76
|
+
* — a flat plate at `HORIZON_PLANE_Y` standing in for ground beyond the field —
|
|
77
|
+
* roofs over every pit, so digging a basement reads as no change whatsoever. On
|
|
78
|
+
* raised or flat ground it costs nothing visually, because the terrain shell and its
|
|
79
|
+
* skirt cover the hole from both sides.
|
|
80
|
+
*
|
|
81
|
+
* Inset by half a cell, so the disc and the terrain edge *overlap* rather than meet:
|
|
82
|
+
* two coplanar edges at exactly the same XZ z-fight along the whole perimeter. The
|
|
83
|
+
* inset is what the skirt's bracketing span is covering, which is why this lives
|
|
84
|
+
* beside `buildTerrainSkirt` — the two have to agree about where the rim is, and a
|
|
85
|
+
* footprint that punched wider than the skirt spans would open a gap the skirt never
|
|
86
|
+
* closes.
|
|
87
|
+
*
|
|
88
|
+
* Kept here rather than in the renderer so it is reachable without Three.js: it
|
|
89
|
+
* decides whether a pit is visible, and that is a property to assert, not to eyeball.
|
|
90
|
+
*/
|
|
91
|
+
export declare function terrainFootprint(field: TerrainField): Array<[number, number]>;
|
|
92
|
+
export declare function buildTerrainSkirt(field: TerrainField): TerrainSkirtBuffers;
|
|
93
|
+
/**
|
|
94
|
+
* Rewrite the whole skirt in place.
|
|
95
|
+
*
|
|
96
|
+
* Whole, not dirty-ranged, unlike the surface: the perimeter is `2(cols + rows)`
|
|
97
|
+
* vertices — 1 028 at 257², under 3% of the surface's vertex count — so the
|
|
98
|
+
* bookkeeping to find which boundary samples a rectangular patch touched costs
|
|
99
|
+
* more than re-uploading all of them. The surface's dirty range earns its
|
|
100
|
+
* complexity because it saves 400 KB a dab; here there is nothing to save.
|
|
101
|
+
*/
|
|
102
|
+
export declare function updateTerrainSkirt(field: TerrainField, buffers: TerrainSkirtBuffers): void;
|
|
103
|
+
/**
|
|
104
|
+
* The contiguous vertex range a patch dirties, in **array elements** — the unit
|
|
105
|
+
* `BufferAttribute.addUpdateRange(start, count)` expects.
|
|
106
|
+
*
|
|
107
|
+
* A patch is rectangular, so the touched vertices are not contiguous unless the
|
|
108
|
+
* patch spans full rows. Rather than issue one range per row, this returns the
|
|
109
|
+
* single span from the patch's first vertex to its last: over-reporting a few
|
|
110
|
+
* clean vertices is far cheaper than many small GPU uploads, and it keeps the
|
|
111
|
+
* caller to one `addUpdateRange` call.
|
|
112
|
+
*
|
|
113
|
+
* The range is widened by one row on each side because a height change moves the
|
|
114
|
+
* *normals* of its neighbours too (central differences reach one sample out).
|
|
115
|
+
* Getting this wrong produces the classic symptom: correct silhouette, stale
|
|
116
|
+
* shading along the edit boundary.
|
|
117
|
+
*/
|
|
118
|
+
export declare function patchUpdateRange(field: TerrainField, patch: HeightPatch, itemSize: number): {
|
|
119
|
+
start: number;
|
|
120
|
+
count: number;
|
|
121
|
+
} | null;
|
|
122
|
+
/**
|
|
123
|
+
* Rewrites only the vertices a patch touched, in place.
|
|
124
|
+
*
|
|
125
|
+
* Takes the *already-patched* field (the one `applyHeightPatch` returned) and
|
|
126
|
+
* the patch that produced it, so the caller cannot accidentally rebuild from
|
|
127
|
+
* stale heights. Mutates the buffers rather than reallocating — that is the
|
|
128
|
+
* whole point of the dirty-rect path.
|
|
129
|
+
*/
|
|
130
|
+
export declare function updateTerrainMesh(field: TerrainField, buffers: TerrainMeshBuffers, patch: HeightPatch): void;
|
|
131
|
+
//# sourceMappingURL=terrain-geometry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terrain-geometry.d.ts","sourceRoot":"","sources":["../../src/site/terrain-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAA4B,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEhG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,QAAQ,CAAA;AAWpC,iFAAiF;AACjF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,gDAAgD;IAChD,SAAS,EAAE,YAAY,CAAA;IACvB,gDAAgD;IAChD,OAAO,EAAE,YAAY,CAAA;IACrB,+CAA+C;IAC/C,GAAG,EAAE,YAAY,CAAA;IACjB,2CAA2C;IAC3C,OAAO,EAAE,WAAW,CAAA;CACrB,CAAA;AAOD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,kBAAkB,CAkCxE;AA8BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,+EAA+E;IAC/E,SAAS,EAAE,YAAY,CAAA;IACvB,OAAO,EAAE,YAAY,CAAA;IACrB,OAAO,EAAE,WAAW,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAY7E;AAwBD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,mBAAmB,CAsB1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAyB1F;AAmBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,MAAM,GACf;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWzC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,WAAW,GACjB,IAAI,CASN"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { heightAtSample, normalAt } from '@pascal-app/core';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the terrain mesh buffers from a `TerrainField`.
|
|
4
|
+
*
|
|
5
|
+
* Split out of the renderer as pure array math so the vertex layout is
|
|
6
|
+
* unit-testable without a canvas, and so the partial-upload path has one
|
|
7
|
+
* definition. The renderer owns the `BufferGeometry`; this owns what goes in it.
|
|
8
|
+
*
|
|
9
|
+
* The triangulation this file emits is the convention `surfaceHeightAt`
|
|
10
|
+
* (`core/lib/terrain-field.ts`) reads back: two triangles per cell split along the
|
|
11
|
+
* `(c+1,r)`–`(c,r+1)` diagonal. Anything drawn *on* the ground samples that
|
|
12
|
+
* function, so changing the winding below without changing it there would make
|
|
13
|
+
* every draped line sink into half of every cell.
|
|
14
|
+
*
|
|
15
|
+
* Two rules that are easy to get wrong and expensive to debug:
|
|
16
|
+
*
|
|
17
|
+
* - **Normals are analytic, never `computeVertexNormals()`.** Beyond the cost
|
|
18
|
+
* (~1.5 ms at 257²), `computeVertexNormals()` rewrites the *whole* normal
|
|
19
|
+
* attribute, which silently defeats the dirty-rect partial upload that makes
|
|
20
|
+
* sculpting cheap. `normalAt` central-differences the field instead, so a
|
|
21
|
+
* patch touches only the rows it changed.
|
|
22
|
+
* - **Vertices are one-per-sample and shared between triangles**, so a partial
|
|
23
|
+
* upload is a contiguous row range. Duplicating verts per-triangle for flat
|
|
24
|
+
* shading would triple memory and scatter the dirty range.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Y of the site's presentation horizon disc (`site/renderer.tsx`).
|
|
28
|
+
*
|
|
29
|
+
* It lives here, next to the skirt, because the skirt is the thing that has to
|
|
30
|
+
* bracket it: its top edge never sits below this plane and its base always clears
|
|
31
|
+
* it, so no sightline can pass between the sculpted ground and the surrounding
|
|
32
|
+
* one. Two readers, one number.
|
|
33
|
+
*/
|
|
34
|
+
export const HORIZON_PLANE_Y = -0.07;
|
|
35
|
+
/**
|
|
36
|
+
* How far the skirt's base hangs below the lowest thing it must close over.
|
|
37
|
+
*
|
|
38
|
+
* Only the top edge is visually load-bearing; the base just has to be out of sight
|
|
39
|
+
* under the horizon disc. A metre reads as earth thickness from a grazing camera
|
|
40
|
+
* without being tall enough to poke out of a neighbouring excavation.
|
|
41
|
+
*/
|
|
42
|
+
const SKIRT_DROP = 1;
|
|
43
|
+
/** Vertex index of sample [c,r]. */
|
|
44
|
+
function vertexIndex(field, col, row) {
|
|
45
|
+
return row * field.cols + col;
|
|
46
|
+
}
|
|
47
|
+
export function buildTerrainMesh(field) {
|
|
48
|
+
const count = field.cols * field.rows;
|
|
49
|
+
const positions = new Float32Array(count * 3);
|
|
50
|
+
const normals = new Float32Array(count * 3);
|
|
51
|
+
const uvs = new Float32Array(count * 2);
|
|
52
|
+
for (let r = 0; r < field.rows; r++) {
|
|
53
|
+
for (let c = 0; c < field.cols; c++) {
|
|
54
|
+
const i = r * field.cols + c;
|
|
55
|
+
writeVertex(field, positions, normals, uvs, c, r, i);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Two triangles per cell. Winding is counter-clockwise seen from +Y so the
|
|
59
|
+
// surface faces up under the default FrontSide material.
|
|
60
|
+
const cells = (field.cols - 1) * (field.rows - 1);
|
|
61
|
+
const indices = new Uint32Array(Math.max(0, cells) * 6);
|
|
62
|
+
let n = 0;
|
|
63
|
+
for (let r = 0; r < field.rows - 1; r++) {
|
|
64
|
+
for (let c = 0; c < field.cols - 1; c++) {
|
|
65
|
+
const a = vertexIndex(field, c, r);
|
|
66
|
+
const b = vertexIndex(field, c + 1, r);
|
|
67
|
+
const d = vertexIndex(field, c, r + 1);
|
|
68
|
+
const e = vertexIndex(field, c + 1, r + 1);
|
|
69
|
+
indices[n++] = a;
|
|
70
|
+
indices[n++] = d;
|
|
71
|
+
indices[n++] = b;
|
|
72
|
+
indices[n++] = b;
|
|
73
|
+
indices[n++] = d;
|
|
74
|
+
indices[n++] = e;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { positions, normals, uvs, indices };
|
|
78
|
+
}
|
|
79
|
+
function writeVertex(field, positions, normals, uvs, col, row, index) {
|
|
80
|
+
const x = field.origin[0] + col * field.spacing;
|
|
81
|
+
const z = field.origin[1] + row * field.spacing;
|
|
82
|
+
const y = heightAtSample(field, col, row);
|
|
83
|
+
positions[index * 3] = x;
|
|
84
|
+
positions[index * 3 + 1] = y;
|
|
85
|
+
positions[index * 3 + 2] = z;
|
|
86
|
+
const [nx, ny, nz] = normalAt(field, x, z);
|
|
87
|
+
normals[index * 3] = nx;
|
|
88
|
+
normals[index * 3 + 1] = ny;
|
|
89
|
+
normals[index * 3 + 2] = nz;
|
|
90
|
+
// UVs span the field 0..1 so a tiled ground material scales with the site
|
|
91
|
+
// rather than with sample count.
|
|
92
|
+
uvs[index * 2] = field.cols > 1 ? col / (field.cols - 1) : 0;
|
|
93
|
+
uvs[index * 2 + 1] = field.rows > 1 ? row / (field.rows - 1) : 0;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The field's own footprint, as a polygon to punch out of the horizon disc.
|
|
97
|
+
*
|
|
98
|
+
* The punch is the only reason an excavation is visible at all: without it the disc
|
|
99
|
+
* — a flat plate at `HORIZON_PLANE_Y` standing in for ground beyond the field —
|
|
100
|
+
* roofs over every pit, so digging a basement reads as no change whatsoever. On
|
|
101
|
+
* raised or flat ground it costs nothing visually, because the terrain shell and its
|
|
102
|
+
* skirt cover the hole from both sides.
|
|
103
|
+
*
|
|
104
|
+
* Inset by half a cell, so the disc and the terrain edge *overlap* rather than meet:
|
|
105
|
+
* two coplanar edges at exactly the same XZ z-fight along the whole perimeter. The
|
|
106
|
+
* inset is what the skirt's bracketing span is covering, which is why this lives
|
|
107
|
+
* beside `buildTerrainSkirt` — the two have to agree about where the rim is, and a
|
|
108
|
+
* footprint that punched wider than the skirt spans would open a gap the skirt never
|
|
109
|
+
* closes.
|
|
110
|
+
*
|
|
111
|
+
* Kept here rather than in the renderer so it is reachable without Three.js: it
|
|
112
|
+
* decides whether a pit is visible, and that is a property to assert, not to eyeball.
|
|
113
|
+
*/
|
|
114
|
+
export function terrainFootprint(field) {
|
|
115
|
+
const inset = field.spacing / 2;
|
|
116
|
+
const minX = field.origin[0] + inset;
|
|
117
|
+
const minZ = field.origin[1] + inset;
|
|
118
|
+
const maxX = field.origin[0] + (field.cols - 1) * field.spacing - inset;
|
|
119
|
+
const maxZ = field.origin[1] + (field.rows - 1) * field.spacing - inset;
|
|
120
|
+
return [
|
|
121
|
+
[minX, minZ],
|
|
122
|
+
[maxX, minZ],
|
|
123
|
+
[maxX, maxZ],
|
|
124
|
+
[minX, maxZ],
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
/** Boundary sample count — a function of the grid shape alone. */
|
|
128
|
+
function perimeterCount(field) {
|
|
129
|
+
if (field.cols < 2 || field.rows < 2)
|
|
130
|
+
return 0;
|
|
131
|
+
return 2 * (field.cols - 1) + 2 * (field.rows - 1);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The `i`th boundary sample, walking top → right → bottom → left.
|
|
135
|
+
*
|
|
136
|
+
* The direction matters: traversed this way the outward horizontal is the tangent
|
|
137
|
+
* rotated as `(tx, tz) → (tz, -tx)` on every edge, which is what makes one winding
|
|
138
|
+
* rule and one normal formula cover all four sides instead of four special cases.
|
|
139
|
+
*/
|
|
140
|
+
function boundarySample(field, i) {
|
|
141
|
+
const lastCol = field.cols - 1;
|
|
142
|
+
const lastRow = field.rows - 1;
|
|
143
|
+
if (i < lastCol)
|
|
144
|
+
return { col: i, row: 0 };
|
|
145
|
+
if (i < lastCol + lastRow)
|
|
146
|
+
return { col: lastCol, row: i - lastCol };
|
|
147
|
+
if (i < 2 * lastCol + lastRow)
|
|
148
|
+
return { col: 2 * lastCol + lastRow - i, row: lastRow };
|
|
149
|
+
return { col: 0, row: 2 * (lastCol + lastRow) - i };
|
|
150
|
+
}
|
|
151
|
+
export function buildTerrainSkirt(field) {
|
|
152
|
+
const count = perimeterCount(field);
|
|
153
|
+
const vertices = count === 0 ? 0 : (count + 1) * 2;
|
|
154
|
+
const buffers = {
|
|
155
|
+
positions: new Float32Array(vertices * 3),
|
|
156
|
+
normals: new Float32Array(vertices * 3),
|
|
157
|
+
indices: new Uint32Array(count * 6),
|
|
158
|
+
};
|
|
159
|
+
for (let i = 0; i < count; i++) {
|
|
160
|
+
const base = i * 6;
|
|
161
|
+
const top = i * 2;
|
|
162
|
+
const nextTop = (i + 1) * 2;
|
|
163
|
+
// Outward faces front: see the winding note on `buildTerrainMesh`.
|
|
164
|
+
buffers.indices[base] = top;
|
|
165
|
+
buffers.indices[base + 1] = nextTop;
|
|
166
|
+
buffers.indices[base + 2] = top + 1;
|
|
167
|
+
buffers.indices[base + 3] = top + 1;
|
|
168
|
+
buffers.indices[base + 4] = nextTop;
|
|
169
|
+
buffers.indices[base + 5] = nextTop + 1;
|
|
170
|
+
}
|
|
171
|
+
updateTerrainSkirt(field, buffers);
|
|
172
|
+
return buffers;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Rewrite the whole skirt in place.
|
|
176
|
+
*
|
|
177
|
+
* Whole, not dirty-ranged, unlike the surface: the perimeter is `2(cols + rows)`
|
|
178
|
+
* vertices — 1 028 at 257², under 3% of the surface's vertex count — so the
|
|
179
|
+
* bookkeeping to find which boundary samples a rectangular patch touched costs
|
|
180
|
+
* more than re-uploading all of them. The surface's dirty range earns its
|
|
181
|
+
* complexity because it saves 400 KB a dab; here there is nothing to save.
|
|
182
|
+
*/
|
|
183
|
+
export function updateTerrainSkirt(field, buffers) {
|
|
184
|
+
const count = perimeterCount(field);
|
|
185
|
+
if (count === 0)
|
|
186
|
+
return;
|
|
187
|
+
for (let i = 0; i <= count; i++) {
|
|
188
|
+
const { col, row } = boundarySample(field, i % count);
|
|
189
|
+
const x = field.origin[0] + col * field.spacing;
|
|
190
|
+
const z = field.origin[1] + row * field.spacing;
|
|
191
|
+
const h = heightAtSample(field, col, row);
|
|
192
|
+
const previous = boundarySample(field, (i - 1 + count) % count);
|
|
193
|
+
const next = boundarySample(field, (i + 1) % count);
|
|
194
|
+
// Tangent across the neighbours, so a corner gets the bevelled average of its
|
|
195
|
+
// two edges rather than one of them arbitrarily.
|
|
196
|
+
const tx = next.col - previous.col;
|
|
197
|
+
const tz = next.row - previous.row;
|
|
198
|
+
const tLength = Math.hypot(tx, tz) || 1;
|
|
199
|
+
const nx = tz / tLength;
|
|
200
|
+
const nz = -tx / tLength;
|
|
201
|
+
const topIndex = i * 2;
|
|
202
|
+
const bottomIndex = topIndex + 1;
|
|
203
|
+
writeSkirtVertex(buffers, topIndex, x, Math.max(h, HORIZON_PLANE_Y), z, nx, nz);
|
|
204
|
+
writeSkirtVertex(buffers, bottomIndex, x, Math.min(h, HORIZON_PLANE_Y) - SKIRT_DROP, z, nx, nz);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function writeSkirtVertex(buffers, index, x, y, z, nx, nz) {
|
|
208
|
+
buffers.positions[index * 3] = x;
|
|
209
|
+
buffers.positions[index * 3 + 1] = y;
|
|
210
|
+
buffers.positions[index * 3 + 2] = z;
|
|
211
|
+
buffers.normals[index * 3] = nx;
|
|
212
|
+
buffers.normals[index * 3 + 1] = 0;
|
|
213
|
+
buffers.normals[index * 3 + 2] = nz;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* The contiguous vertex range a patch dirties, in **array elements** — the unit
|
|
217
|
+
* `BufferAttribute.addUpdateRange(start, count)` expects.
|
|
218
|
+
*
|
|
219
|
+
* A patch is rectangular, so the touched vertices are not contiguous unless the
|
|
220
|
+
* patch spans full rows. Rather than issue one range per row, this returns the
|
|
221
|
+
* single span from the patch's first vertex to its last: over-reporting a few
|
|
222
|
+
* clean vertices is far cheaper than many small GPU uploads, and it keeps the
|
|
223
|
+
* caller to one `addUpdateRange` call.
|
|
224
|
+
*
|
|
225
|
+
* The range is widened by one row on each side because a height change moves the
|
|
226
|
+
* *normals* of its neighbours too (central differences reach one sample out).
|
|
227
|
+
* Getting this wrong produces the classic symptom: correct silhouette, stale
|
|
228
|
+
* shading along the edit boundary.
|
|
229
|
+
*/
|
|
230
|
+
export function patchUpdateRange(field, patch, itemSize) {
|
|
231
|
+
const firstRow = Math.max(0, patch.row0 - 1);
|
|
232
|
+
const lastRow = Math.min(field.rows - 1, patch.row0 + patch.rows);
|
|
233
|
+
if (lastRow < firstRow)
|
|
234
|
+
return null;
|
|
235
|
+
const startVertex = firstRow * field.cols;
|
|
236
|
+
const endVertex = lastRow * field.cols + (field.cols - 1);
|
|
237
|
+
return {
|
|
238
|
+
start: startVertex * itemSize,
|
|
239
|
+
count: (endVertex - startVertex + 1) * itemSize,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Rewrites only the vertices a patch touched, in place.
|
|
244
|
+
*
|
|
245
|
+
* Takes the *already-patched* field (the one `applyHeightPatch` returned) and
|
|
246
|
+
* the patch that produced it, so the caller cannot accidentally rebuild from
|
|
247
|
+
* stale heights. Mutates the buffers rather than reallocating — that is the
|
|
248
|
+
* whole point of the dirty-rect path.
|
|
249
|
+
*/
|
|
250
|
+
export function updateTerrainMesh(field, buffers, patch) {
|
|
251
|
+
const firstRow = Math.max(0, patch.row0 - 1);
|
|
252
|
+
const lastRow = Math.min(field.rows - 1, patch.row0 + patch.rows);
|
|
253
|
+
for (let r = firstRow; r <= lastRow; r++) {
|
|
254
|
+
for (let c = 0; c < field.cols; c++) {
|
|
255
|
+
const i = r * field.cols + c;
|
|
256
|
+
writeVertex(field, buffers.positions, buffers.normals, buffers.uvs, c, r, i);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { HeightPatch, TerrainField } from '@pascal-app/core';
|
|
2
|
+
import { BufferGeometry } from 'three';
|
|
3
|
+
import { type TerrainMeshBuffers, type TerrainSkirtBuffers } from './terrain-geometry';
|
|
4
|
+
/**
|
|
5
|
+
* The Three.js side of the terrain heightfield: owns the `BufferGeometry` and the
|
|
6
|
+
* dirty-rect upload.
|
|
7
|
+
*
|
|
8
|
+
* Kept beside the pure builder rather than in `viewer` because the dependency
|
|
9
|
+
* runs `nodes -> viewer`, not the other way, and this is a per-kind concern
|
|
10
|
+
* anyway. `terrain-geometry.ts` computes *what* goes in the buffers (testable
|
|
11
|
+
* without a canvas); this owns the GPU resource. The only thing that cannot live
|
|
12
|
+
* on the pure side is `addUpdateRange`/`needsUpdate`, which is why this file is
|
|
13
|
+
* thin.
|
|
14
|
+
*
|
|
15
|
+
* Why patch instead of rebuild: a 129² field is 16 641 vertices, so a full
|
|
16
|
+
* re-upload is ~400 KB per dab. There is no render-on-demand to hide behind —
|
|
17
|
+
* `FrameLimiter` runs a perpetual rAF at 50 FPS — so a brush dragged across a
|
|
18
|
+
* slope would re-upload the whole field dozens of times a second.
|
|
19
|
+
*/
|
|
20
|
+
/** A terrain geometry plus the CPU buffers backing it. */
|
|
21
|
+
export type TerrainGeometry = {
|
|
22
|
+
readonly geometry: BufferGeometry;
|
|
23
|
+
readonly buffers: TerrainMeshBuffers;
|
|
24
|
+
/** The edge curtain that closes the field against the horizon disc. */
|
|
25
|
+
readonly skirt: {
|
|
26
|
+
readonly geometry: BufferGeometry;
|
|
27
|
+
readonly buffers: TerrainSkirtBuffers;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export declare function createTerrainGeometry(field: TerrainField): TerrainGeometry;
|
|
31
|
+
/**
|
|
32
|
+
* Push one patch to the GPU, touching only the affected rows.
|
|
33
|
+
*
|
|
34
|
+
* Both `position` and `normal` are updated: a height change moves its
|
|
35
|
+
* neighbours' normals too, and `patchUpdateRange` already widens the span by one
|
|
36
|
+
* row on each side to cover that. UVs are never touched — they are a function of
|
|
37
|
+
* grid indices, not heights.
|
|
38
|
+
*/
|
|
39
|
+
export declare function applyTerrainPatch(target: TerrainGeometry, field: TerrainField, patch: HeightPatch): void;
|
|
40
|
+
/**
|
|
41
|
+
* True when a geometry was built for a differently-shaped field.
|
|
42
|
+
*
|
|
43
|
+
* A resized field cannot be patched — the vertex count changed — so the caller
|
|
44
|
+
* must rebuild. Checking here keeps that decision in one place instead of every
|
|
45
|
+
* caller comparing `cols`/`rows` by hand.
|
|
46
|
+
*/
|
|
47
|
+
export declare function needsRebuild(target: TerrainGeometry, field: TerrainField): boolean;
|
|
48
|
+
export declare function disposeTerrainGeometry(target: TerrainGeometry): void;
|
|
49
|
+
//# sourceMappingURL=terrain-mesh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terrain-mesh.d.ts","sourceRoot":"","sources":["../../src/site/terrain-mesh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACjE,OAAO,EAAmB,cAAc,EAAmB,MAAM,OAAO,CAAA;AACxE,OAAO,EAKL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAGzB,MAAM,oBAAoB,CAAA;AAE3B;;;;;;;;;;;;;;;GAeG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;IACjC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAA;IACpC,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAA;KAAE,CAAA;CAC7F,CAAA;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,CAoB1E;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,WAAW,GACjB,IAAI,CA0BN;AA8DD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAElF;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAGpE"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { BufferAttribute, BufferGeometry, Sphere, Vector3 } from 'three';
|
|
2
|
+
import { buildTerrainMesh, buildTerrainSkirt, HORIZON_PLANE_Y, patchUpdateRange, updateTerrainMesh, updateTerrainSkirt, } from './terrain-geometry';
|
|
3
|
+
export function createTerrainGeometry(field) {
|
|
4
|
+
const buffers = buildTerrainMesh(field);
|
|
5
|
+
const geometry = new BufferGeometry();
|
|
6
|
+
geometry.setAttribute('position', new BufferAttribute(buffers.positions, 3));
|
|
7
|
+
geometry.setAttribute('normal', new BufferAttribute(buffers.normals, 3));
|
|
8
|
+
geometry.setAttribute('uv', new BufferAttribute(buffers.uvs, 2));
|
|
9
|
+
geometry.setIndex(new BufferAttribute(buffers.indices, 1));
|
|
10
|
+
setTerrainBounds(geometry, field);
|
|
11
|
+
// A separate geometry, not extra vertices on the surface: the surface's dirty
|
|
12
|
+
// range is a row span over a `cols * rows` layout, and appending a perimeter ring
|
|
13
|
+
// to it would break that indexing for a saving of one draw call.
|
|
14
|
+
const skirtBuffers = buildTerrainSkirt(field);
|
|
15
|
+
const skirtGeometry = new BufferGeometry();
|
|
16
|
+
skirtGeometry.setAttribute('position', new BufferAttribute(skirtBuffers.positions, 3));
|
|
17
|
+
skirtGeometry.setAttribute('normal', new BufferAttribute(skirtBuffers.normals, 3));
|
|
18
|
+
skirtGeometry.setIndex(new BufferAttribute(skirtBuffers.indices, 1));
|
|
19
|
+
setSkirtBounds(skirtGeometry, field);
|
|
20
|
+
return { geometry, buffers, skirt: { geometry: skirtGeometry, buffers: skirtBuffers } };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Push one patch to the GPU, touching only the affected rows.
|
|
24
|
+
*
|
|
25
|
+
* Both `position` and `normal` are updated: a height change moves its
|
|
26
|
+
* neighbours' normals too, and `patchUpdateRange` already widens the span by one
|
|
27
|
+
* row on each side to cover that. UVs are never touched — they are a function of
|
|
28
|
+
* grid indices, not heights.
|
|
29
|
+
*/
|
|
30
|
+
export function applyTerrainPatch(target, field, patch) {
|
|
31
|
+
updateTerrainMesh(field, target.buffers, patch);
|
|
32
|
+
// The skirt goes up unconditionally, before the range bail: a patch that leaves
|
|
33
|
+
// the surface's dirty span empty can still have moved a boundary sample, and a
|
|
34
|
+
// stale skirt is a hole at the property line.
|
|
35
|
+
updateTerrainSkirt(field, target.skirt.buffers);
|
|
36
|
+
for (const name of ['position', 'normal']) {
|
|
37
|
+
const attribute = target.skirt.geometry.getAttribute(name);
|
|
38
|
+
attribute.needsUpdate = true;
|
|
39
|
+
}
|
|
40
|
+
setSkirtBounds(target.skirt.geometry, field);
|
|
41
|
+
const range = patchUpdateRange(field, patch, 3);
|
|
42
|
+
if (!range)
|
|
43
|
+
return;
|
|
44
|
+
for (const name of ['position', 'normal']) {
|
|
45
|
+
const attribute = target.geometry.getAttribute(name);
|
|
46
|
+
// `clearUpdateRanges` first: ranges accumulate, so a stroke dabbing 60 times
|
|
47
|
+
// a second would otherwise grow an unbounded list that all gets re-uploaded.
|
|
48
|
+
attribute.clearUpdateRanges();
|
|
49
|
+
attribute.addUpdateRange(range.start, range.count);
|
|
50
|
+
attribute.needsUpdate = true;
|
|
51
|
+
}
|
|
52
|
+
setTerrainBounds(target.geometry, field);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Set bounds analytically instead of calling `computeBoundingSphere()`.
|
|
56
|
+
*
|
|
57
|
+
* The computed version walks every vertex twice and allocates; the field's
|
|
58
|
+
* horizontal extent is known in O(1) from `origin`/`spacing`/`cols`/`rows`, and
|
|
59
|
+
* only the height range needs a scan. Skipping bounds entirely is not an option —
|
|
60
|
+
* a stale bounding sphere gets a raised hill frustum-culled while it is still on
|
|
61
|
+
* screen, which reads as terrain flickering out at certain camera angles.
|
|
62
|
+
*/
|
|
63
|
+
function setTerrainBounds(geometry, field) {
|
|
64
|
+
const { minY, maxY } = heightSpan(field);
|
|
65
|
+
const minX = field.origin[0];
|
|
66
|
+
const minZ = field.origin[1];
|
|
67
|
+
const maxX = minX + (field.cols - 1) * field.spacing;
|
|
68
|
+
const maxZ = minZ + (field.rows - 1) * field.spacing;
|
|
69
|
+
const center = new Vector3((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
|
|
70
|
+
const radius = Math.hypot((maxX - minX) / 2, (maxY - minY) / 2, (maxZ - minZ) / 2);
|
|
71
|
+
geometry.boundingSphere = new Sphere(center, radius);
|
|
72
|
+
if (geometry.boundingBox) {
|
|
73
|
+
geometry.boundingBox.set(new Vector3(minX, minY, minZ), new Vector3(maxX, maxY, maxZ));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Bounds for the skirt, which hangs below the field's own low point.
|
|
78
|
+
*
|
|
79
|
+
* Reusing `setTerrainBounds` would cull the curtain the moment its top edge left
|
|
80
|
+
* the surface's sphere — the exact symptom the surface's analytic bounds exist to
|
|
81
|
+
* avoid, one geometry over. The vertical span brackets the horizon plane for the
|
|
82
|
+
* same reason the geometry does.
|
|
83
|
+
*/
|
|
84
|
+
function setSkirtBounds(geometry, field) {
|
|
85
|
+
const { minY, maxY } = heightSpan(field);
|
|
86
|
+
const low = Math.min(minY, HORIZON_PLANE_Y) - 1;
|
|
87
|
+
const high = Math.max(maxY, HORIZON_PLANE_Y);
|
|
88
|
+
const minX = field.origin[0];
|
|
89
|
+
const minZ = field.origin[1];
|
|
90
|
+
const maxX = minX + (field.cols - 1) * field.spacing;
|
|
91
|
+
const maxZ = minZ + (field.rows - 1) * field.spacing;
|
|
92
|
+
const center = new Vector3((minX + maxX) / 2, (low + high) / 2, (minZ + maxZ) / 2);
|
|
93
|
+
const radius = Math.hypot((maxX - minX) / 2, (high - low) / 2, (maxZ - minZ) / 2);
|
|
94
|
+
geometry.boundingSphere = new Sphere(center, radius);
|
|
95
|
+
}
|
|
96
|
+
/** Height span in metres, always including the datum so flat ground has a box. */
|
|
97
|
+
function heightSpan(field) {
|
|
98
|
+
let minH = 0;
|
|
99
|
+
let maxH = 0;
|
|
100
|
+
for (let i = 0; i < field.heights.length; i++) {
|
|
101
|
+
const h = field.heights[i] ?? 0;
|
|
102
|
+
if (h < minH)
|
|
103
|
+
minH = h;
|
|
104
|
+
if (h > maxH)
|
|
105
|
+
maxH = h;
|
|
106
|
+
}
|
|
107
|
+
return { minY: minH * field.step, maxY: maxH * field.step };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* True when a geometry was built for a differently-shaped field.
|
|
111
|
+
*
|
|
112
|
+
* A resized field cannot be patched — the vertex count changed — so the caller
|
|
113
|
+
* must rebuild. Checking here keeps that decision in one place instead of every
|
|
114
|
+
* caller comparing `cols`/`rows` by hand.
|
|
115
|
+
*/
|
|
116
|
+
export function needsRebuild(target, field) {
|
|
117
|
+
return target.buffers.positions.length !== field.cols * field.rows * 3;
|
|
118
|
+
}
|
|
119
|
+
export function disposeTerrainGeometry(target) {
|
|
120
|
+
target.geometry.dispose();
|
|
121
|
+
target.skirt.geometry.dispose();
|
|
122
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type SiteNode } from '@pascal-app/core';
|
|
2
|
+
import type { Material } from 'three';
|
|
3
|
+
/**
|
|
4
|
+
* Renders the site's sculpted ground.
|
|
5
|
+
*
|
|
6
|
+
* Mounted by `SiteRenderer` only when the site has terrain (or a stroke is in
|
|
7
|
+
* flight), so a scene that never touched terrain mounts nothing at all and pays
|
|
8
|
+
* nothing.
|
|
9
|
+
*
|
|
10
|
+
* Two things here are load-bearing and easy to get wrong:
|
|
11
|
+
*
|
|
12
|
+
* - **The geometry is a ref, not React state.** A sculpt stroke pushes dozens of
|
|
13
|
+
* patches a second; routing each through a re-render would rebuild the
|
|
14
|
+
* `BufferGeometry` object identity every dab and throw away the partial-upload
|
|
15
|
+
* win entirely. The component subscribes to the *patch*, mutates the buffers in
|
|
16
|
+
* place, and never re-renders during a stroke.
|
|
17
|
+
* - **It stays on the default `SCENE_LAYER`.** This is real, visible,
|
|
18
|
+
* shadow-receiving geometry — not an overlay. Overlay/hit-area meshes must use
|
|
19
|
+
* the editor layer, but moving actual scene geometry there would exclude it from
|
|
20
|
+
* the MRT depth/normal pass and break the screen-space ink against it.
|
|
21
|
+
*/
|
|
22
|
+
export declare const TerrainRenderer: ({ material, site, }: {
|
|
23
|
+
/** Owned by `SiteRenderer` so the ground material stays defined in one place. */
|
|
24
|
+
material: Material;
|
|
25
|
+
site: SiteNode;
|
|
26
|
+
}) => import("react").JSX.Element | null;
|
|
27
|
+
export default TerrainRenderer;
|
|
28
|
+
//# sourceMappingURL=terrain-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terrain-renderer.d.ts","sourceRoot":"","sources":["../../src/site/terrain-renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,QAAQ,EAAkC,MAAM,kBAAkB,CAAA;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AASrC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,eAAe,GAAI,qBAG7B;IACD,iFAAiF;IACjF,QAAQ,EAAE,QAAQ,CAAA;IAClB,IAAI,EAAE,QAAQ,CAAA;CACf,uCA6DA,CAAA;AAED,eAAe,eAAe,CAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { terrainFieldOf, useLiveTerrain } from '@pascal-app/core';
|
|
4
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
5
|
+
import { applyTerrainPatch, createTerrainGeometry, disposeTerrainGeometry, needsRebuild, } from './terrain-mesh';
|
|
6
|
+
/**
|
|
7
|
+
* Renders the site's sculpted ground.
|
|
8
|
+
*
|
|
9
|
+
* Mounted by `SiteRenderer` only when the site has terrain (or a stroke is in
|
|
10
|
+
* flight), so a scene that never touched terrain mounts nothing at all and pays
|
|
11
|
+
* nothing.
|
|
12
|
+
*
|
|
13
|
+
* Two things here are load-bearing and easy to get wrong:
|
|
14
|
+
*
|
|
15
|
+
* - **The geometry is a ref, not React state.** A sculpt stroke pushes dozens of
|
|
16
|
+
* patches a second; routing each through a re-render would rebuild the
|
|
17
|
+
* `BufferGeometry` object identity every dab and throw away the partial-upload
|
|
18
|
+
* win entirely. The component subscribes to the *patch*, mutates the buffers in
|
|
19
|
+
* place, and never re-renders during a stroke.
|
|
20
|
+
* - **It stays on the default `SCENE_LAYER`.** This is real, visible,
|
|
21
|
+
* shadow-receiving geometry — not an overlay. Overlay/hit-area meshes must use
|
|
22
|
+
* the editor layer, but moving actual scene geometry there would exclude it from
|
|
23
|
+
* the MRT depth/normal pass and break the screen-space ink against it.
|
|
24
|
+
*/
|
|
25
|
+
export const TerrainRenderer = ({ material, site, }) => {
|
|
26
|
+
const targetRef = useRef(null);
|
|
27
|
+
// Persisted terrain only — the live stroke is applied imperatively below, so a
|
|
28
|
+
// dab never triggers a React render.
|
|
29
|
+
const persistedField = useMemo(() => terrainFieldOf({ id: site.id, terrain: site.terrain }), [site.id, site.terrain]);
|
|
30
|
+
const target = useMemo(() => {
|
|
31
|
+
const field = useLiveTerrain.getState().fieldOf(site.id) ?? persistedField;
|
|
32
|
+
if (!field)
|
|
33
|
+
return null;
|
|
34
|
+
return createTerrainGeometry(field);
|
|
35
|
+
}, [persistedField, site.id]);
|
|
36
|
+
targetRef.current = target;
|
|
37
|
+
useEffect(() => () => {
|
|
38
|
+
if (target)
|
|
39
|
+
disposeTerrainGeometry(target);
|
|
40
|
+
}, [target]);
|
|
41
|
+
// Imperative subscription rather than a selector hook: the point is to react to
|
|
42
|
+
// a dab *without* re-rendering. Zustand's subscribe gives the previous state
|
|
43
|
+
// too, so a patch is only uploaded once even if an unrelated store update
|
|
44
|
+
// notifies.
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
let lastPatch = useLiveTerrain.getState().strokeOf(site.id)?.lastPatch ?? null;
|
|
47
|
+
return useLiveTerrain.subscribe((state) => {
|
|
48
|
+
const stroke = state.strokes.get(site.id);
|
|
49
|
+
const current = targetRef.current;
|
|
50
|
+
if (!(stroke && current)) {
|
|
51
|
+
lastPatch = null;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!stroke.lastPatch || stroke.lastPatch === lastPatch)
|
|
55
|
+
return;
|
|
56
|
+
lastPatch = stroke.lastPatch;
|
|
57
|
+
// A resized field cannot be patched — the vertex count changed. That only
|
|
58
|
+
// happens on import, never mid-stroke, so bail and let the persisted-field
|
|
59
|
+
// rebuild handle it rather than silently corrupting the buffers.
|
|
60
|
+
if (needsRebuild(current, stroke.field))
|
|
61
|
+
return;
|
|
62
|
+
applyTerrainPatch(current, stroke.field, stroke.lastPatch);
|
|
63
|
+
});
|
|
64
|
+
}, [site.id]);
|
|
65
|
+
if (!target)
|
|
66
|
+
return null;
|
|
67
|
+
return (_jsxs(_Fragment, { children: [_jsx("mesh", { castShadow: true, geometry: target.geometry, material: material, receiveShadow: true }), _jsx("mesh", { geometry: target.skirt.geometry, material: material, receiveShadow: true })] }));
|
|
68
|
+
};
|
|
69
|
+
export default TerrainRenderer;
|