@pascal-app/core 0.9.2 → 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 +17 -10
- package/dist/events/bus.d.ts +21 -2
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +17 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.js +61 -8
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +116 -66
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +316 -441
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +46 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +193 -12
- package/dist/hooks/spatial-grid/support-host-id.d.ts +3 -0
- package/dist/hooks/spatial-grid/support-host-id.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/support-host-id.js +2 -0
- package/dist/hooks/spatial-grid/support-host-patch.d.ts +51 -0
- package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/support-host-patch.js +164 -0
- package/dist/index.d.ts +23 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -10
- package/dist/lib/measurement-geometry.d.ts +4 -0
- package/dist/lib/measurement-geometry.d.ts.map +1 -1
- package/dist/lib/measurement-geometry.js +27 -10
- package/dist/lib/slab-polygon.d.ts.map +1 -1
- package/dist/lib/slab-polygon.js +33 -1
- package/dist/lib/space-detection.d.ts +15 -4
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +174 -88
- package/dist/lib/terrain-brush.d.ts +163 -0
- package/dist/lib/terrain-brush.d.ts.map +1 -0
- package/dist/lib/terrain-brush.js +359 -0
- package/dist/lib/terrain-codec.d.ts +51 -0
- package/dist/lib/terrain-codec.d.ts.map +1 -0
- package/dist/lib/terrain-codec.js +167 -0
- package/dist/lib/terrain-field.d.ts +150 -0
- package/dist/lib/terrain-field.d.ts.map +1 -0
- package/dist/lib/terrain-field.js +248 -0
- package/dist/lib/terrain-raycast.d.ts +44 -0
- package/dist/lib/terrain-raycast.d.ts.map +1 -0
- package/dist/lib/terrain-raycast.js +188 -0
- package/dist/lib/terrain-source.d.ts +60 -0
- package/dist/lib/terrain-source.d.ts.map +1 -0
- package/dist/lib/terrain-source.js +90 -0
- package/dist/lib/terrain-support.d.ts +60 -0
- package/dist/lib/terrain-support.d.ts.map +1 -0
- package/dist/lib/terrain-support.js +150 -0
- package/dist/lib/zone-quantities.d.ts +1 -1
- package/dist/lib/zone-quantities.d.ts.map +1 -1
- package/dist/lib/zone-quantities.js +311 -66
- package/dist/material-library.d.ts +9 -1
- package/dist/material-library.d.ts.map +1 -1
- package/dist/material-library.js +56 -4
- package/dist/registry/handles.d.ts +16 -1
- package/dist/registry/handles.d.ts.map +1 -1
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/subtree.d.ts.map +1 -1
- package/dist/registry/subtree.js +5 -2
- package/dist/registry/types.d.ts +64 -1
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/schema/index.d.ts +6 -3
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +6 -3
- package/dist/schema/nodes/cabinet.d.ts +2 -0
- package/dist/schema/nodes/cabinet.d.ts.map +1 -1
- package/dist/schema/nodes/cabinet.js +4 -2
- package/dist/schema/nodes/ceiling.d.ts +1 -1
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +7 -1
- package/dist/schema/nodes/column.d.ts +1 -0
- package/dist/schema/nodes/column.d.ts.map +1 -1
- package/dist/schema/nodes/column.js +2 -0
- package/dist/schema/nodes/construction-dimension.d.ts +195 -0
- package/dist/schema/nodes/construction-dimension.d.ts.map +1 -0
- package/dist/schema/nodes/construction-dimension.js +151 -0
- package/dist/schema/nodes/door.d.ts +29 -0
- package/dist/schema/nodes/door.d.ts.map +1 -1
- package/dist/schema/nodes/door.js +20 -0
- package/dist/schema/nodes/duct-terminal.d.ts +1 -0
- package/dist/schema/nodes/duct-terminal.d.ts.map +1 -1
- package/dist/schema/nodes/duct-terminal.js +2 -0
- package/dist/schema/nodes/fence.d.ts +2 -0
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +8 -0
- package/dist/schema/nodes/hvac-equipment.d.ts +1 -0
- package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -1
- package/dist/schema/nodes/hvac-equipment.js +2 -0
- package/dist/schema/nodes/item.d.ts +1 -0
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +13 -0
- package/dist/schema/nodes/level.d.ts +2 -1
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +12 -33
- package/dist/schema/nodes/shelf.d.ts +1 -0
- package/dist/schema/nodes/shelf.d.ts.map +1 -1
- package/dist/schema/nodes/shelf.js +2 -0
- package/dist/schema/nodes/site.d.ts +9 -0
- package/dist/schema/nodes/site.d.ts.map +1 -1
- package/dist/schema/nodes/site.js +8 -6
- package/dist/schema/nodes/slab.d.ts +5 -0
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +14 -2
- package/dist/schema/nodes/spawn.d.ts +1 -0
- package/dist/schema/nodes/spawn.d.ts.map +1 -1
- package/dist/schema/nodes/spawn.js +2 -0
- package/dist/schema/nodes/stair.d.ts +3 -1
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +8 -1
- package/dist/schema/nodes/structural-grid.d.ts +27 -0
- package/dist/schema/nodes/structural-grid.d.ts.map +1 -0
- package/dist/schema/nodes/structural-grid.js +17 -0
- package/dist/schema/nodes/wall.d.ts +5 -2
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +13 -4
- package/dist/schema/nodes/window.d.ts +29 -0
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/nodes/window.js +18 -0
- package/dist/schema/nodes/zone.d.ts +20 -0
- package/dist/schema/nodes/zone.d.ts.map +1 -1
- package/dist/schema/nodes/zone.js +15 -0
- package/dist/schema/terrain.d.ts +28 -0
- package/dist/schema/terrain.d.ts.map +1 -0
- package/dist/schema/terrain.js +30 -0
- package/dist/schema/types.d.ts +211 -3
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +4 -0
- package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts +80 -0
- package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts.map +1 -0
- package/dist/services/__fixtures__/wall-plane-top-boundary-repro.js +89 -0
- package/dist/services/index.d.ts +2 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -1
- package/dist/services/level-height.d.ts +8 -9
- package/dist/services/level-height.d.ts.map +1 -1
- package/dist/services/level-height.js +48 -16
- package/dist/services/storey.d.ts +103 -0
- package/dist/services/storey.d.ts.map +1 -0
- package/dist/services/storey.js +296 -0
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +47 -3
- package/dist/store/history-control.d.ts +20 -0
- package/dist/store/history-control.d.ts.map +1 -1
- package/dist/store/history-control.js +110 -13
- package/dist/store/use-live-terrain.d.ts +65 -0
- package/dist/store/use-live-terrain.d.ts.map +1 -0
- package/dist/store/use-live-terrain.js +31 -0
- package/dist/store/use-live-transforms.d.ts +8 -0
- package/dist/store/use-live-transforms.d.ts.map +1 -1
- package/dist/store/use-scene.d.ts +31 -2
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +638 -8
- package/dist/systems/elevator/elevator-opening-system.d.ts +1 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-opening-system.js +27 -21
- package/dist/systems/elevator/elevator-service.d.ts +0 -2
- package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-service.js +2 -24
- package/dist/systems/slab/slab-placement.d.ts +11 -0
- package/dist/systems/slab/slab-placement.d.ts.map +1 -0
- package/dist/systems/slab/slab-placement.js +14 -0
- package/dist/systems/slab/slab-support.d.ts +137 -0
- package/dist/systems/slab/slab-support.d.ts.map +1 -0
- package/dist/systems/slab/slab-support.js +557 -0
- package/dist/systems/stair/stair-opening-preview.d.ts +1060 -935
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.js +18 -15
- package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-system.js +32 -4
- package/dist/systems/stair/stair-rise.d.ts +22 -0
- package/dist/systems/stair/stair-rise.d.ts.map +1 -0
- package/dist/systems/stair/stair-rise.js +79 -0
- package/dist/systems/wall/wall-curve.d.ts +10 -0
- package/dist/systems/wall/wall-curve.d.ts.map +1 -1
- package/dist/systems/wall/wall-curve.js +1 -1
- package/dist/systems/wall/wall-mitering.js +6 -6
- package/dist/systems/wall/wall-top.d.ts +40 -0
- package/dist/systems/wall/wall-top.d.ts.map +1 -0
- package/dist/systems/wall/wall-top.js +46 -0
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +34 -3
- package/dist/utils/heal-scene-graph.d.ts +1 -1
- package/dist/utils/heal-scene-graph.d.ts.map +1 -1
- package/dist/utils/heal-scene-graph.js +25 -9
- package/dist/validation/validate-build-json.d.ts +2 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -1
- package/dist/validation/validate-build-json.js +59 -3
- package/package.json +2 -2
- package/dist/hooks/scene-registry/scene-registry.test.d.ts +0 -2
- package/dist/hooks/scene-registry/scene-registry.test.d.ts.map +0 -1
- package/dist/hooks/scene-registry/scene-registry.test.js +0 -26
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +0 -2
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +0 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +0 -366
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +0 -2
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +0 -1
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +0 -363
- package/dist/lib/measurement-geometry.test.d.ts +0 -2
- package/dist/lib/measurement-geometry.test.d.ts.map +0 -1
- package/dist/lib/measurement-geometry.test.js +0 -102
- package/dist/lib/slab-polygon.test.d.ts +0 -2
- package/dist/lib/slab-polygon.test.d.ts.map +0 -1
- package/dist/lib/slab-polygon.test.js +0 -635
- package/dist/lib/space-detection-pause.test.d.ts +0 -2
- package/dist/lib/space-detection-pause.test.d.ts.map +0 -1
- package/dist/lib/space-detection-pause.test.js +0 -42
- package/dist/lib/space-detection.test.d.ts +0 -2
- package/dist/lib/space-detection.test.d.ts.map +0 -1
- package/dist/lib/space-detection.test.js +0 -376
- package/dist/lib/zone-quantities.test.d.ts +0 -2
- package/dist/lib/zone-quantities.test.d.ts.map +0 -1
- package/dist/lib/zone-quantities.test.js +0 -82
- package/dist/registry/registry.test.d.ts +0 -2
- package/dist/registry/registry.test.d.ts.map +0 -1
- package/dist/registry/registry.test.js +0 -196
- package/dist/registry/relations-resolver.test.d.ts +0 -2
- package/dist/registry/relations-resolver.test.d.ts.map +0 -1
- package/dist/registry/relations-resolver.test.js +0 -183
- package/dist/registry/scene-api.test.d.ts +0 -2
- package/dist/registry/scene-api.test.d.ts.map +0 -1
- package/dist/registry/scene-api.test.js +0 -183
- package/dist/registry/subtree.test.d.ts +0 -2
- package/dist/registry/subtree.test.d.ts.map +0 -1
- package/dist/registry/subtree.test.js +0 -163
- package/dist/schema/asset-url.test.d.ts +0 -2
- package/dist/schema/asset-url.test.d.ts.map +0 -1
- package/dist/schema/asset-url.test.js +0 -134
- package/dist/schema/material.test.d.ts +0 -2
- package/dist/schema/material.test.d.ts.map +0 -1
- package/dist/schema/material.test.js +0 -43
- package/dist/schema/nodes/measurement.test.d.ts +0 -2
- package/dist/schema/nodes/measurement.test.d.ts.map +0 -1
- package/dist/schema/nodes/measurement.test.js +0 -139
- package/dist/schema/nodes/ridge-vent-defaults.test.d.ts +0 -2
- package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +0 -1
- package/dist/schema/nodes/ridge-vent-defaults.test.js +0 -205
- package/dist/schema/nodes/roof-segment-shape.test.d.ts +0 -2
- package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +0 -1
- package/dist/schema/nodes/roof-segment-shape.test.js +0 -175
- package/dist/schema/nodes/roof-segment-surface.test.d.ts +0 -2
- package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +0 -1
- package/dist/schema/nodes/roof-segment-surface.test.js +0 -48
- package/dist/schema/nodes/roof-segment-trim.test.d.ts +0 -2
- package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +0 -1
- package/dist/schema/nodes/roof-segment-trim.test.js +0 -107
- package/dist/schema/nodes/roof-segment-walls.test.d.ts +0 -2
- package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +0 -1
- package/dist/schema/nodes/roof-segment-walls.test.js +0 -92
- package/dist/schema/nodes/wall.test.d.ts +0 -2
- package/dist/schema/nodes/wall.test.d.ts.map +0 -1
- package/dist/schema/nodes/wall.test.js +0 -208
- package/dist/services/alignment-anchors.test.d.ts +0 -2
- package/dist/services/alignment-anchors.test.d.ts.map +0 -1
- package/dist/services/alignment-anchors.test.js +0 -383
- package/dist/services/alignment.test.d.ts +0 -2
- package/dist/services/alignment.test.d.ts.map +0 -1
- package/dist/services/alignment.test.js +0 -92
- package/dist/services/drag-session.test.d.ts +0 -2
- package/dist/services/drag-session.test.d.ts.map +0 -1
- package/dist/services/drag-session.test.js +0 -219
- package/dist/services/hosting.test.d.ts +0 -2
- package/dist/services/hosting.test.d.ts.map +0 -1
- package/dist/services/hosting.test.js +0 -230
- package/dist/services/movement.test.d.ts +0 -2
- package/dist/services/movement.test.d.ts.map +0 -1
- package/dist/services/movement.test.js +0 -133
- package/dist/services/opening-guides.test.d.ts +0 -2
- package/dist/services/opening-guides.test.d.ts.map +0 -1
- package/dist/services/opening-guides.test.js +0 -208
- package/dist/services/port-connectivity.test.d.ts +0 -2
- package/dist/services/port-connectivity.test.d.ts.map +0 -1
- package/dist/services/port-connectivity.test.js +0 -297
- package/dist/services/riser-diagram.test.d.ts +0 -2
- package/dist/services/riser-diagram.test.d.ts.map +0 -1
- package/dist/services/riser-diagram.test.js +0 -66
- package/dist/services/single-undo-dance.test.d.ts +0 -2
- package/dist/services/single-undo-dance.test.d.ts.map +0 -1
- package/dist/services/single-undo-dance.test.js +0 -210
- package/dist/services/snap.test.d.ts +0 -2
- package/dist/services/snap.test.d.ts.map +0 -1
- package/dist/services/snap.test.js +0 -137
- package/dist/services/system-graph.test.d.ts +0 -2
- package/dist/services/system-graph.test.d.ts.map +0 -1
- package/dist/services/system-graph.test.js +0 -138
- package/dist/services/validate-dwv.test.d.ts +0 -2
- package/dist/services/validate-dwv.test.d.ts.map +0 -1
- package/dist/services/validate-dwv.test.js +0 -109
- package/dist/store/actions/node-mutation-sanitize.test.d.ts +0 -2
- package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +0 -1
- package/dist/store/actions/node-mutation-sanitize.test.js +0 -147
- package/dist/store/actions/reparent.test.d.ts +0 -2
- package/dist/store/actions/reparent.test.d.ts.map +0 -1
- package/dist/store/actions/reparent.test.js +0 -212
- package/dist/store/actions/ridge-vent-update.test.d.ts +0 -2
- package/dist/store/actions/ridge-vent-update.test.d.ts.map +0 -1
- package/dist/store/actions/ridge-vent-update.test.js +0 -264
- package/dist/store/use-scene-dirty-tracking.test.d.ts +0 -2
- package/dist/store/use-scene-dirty-tracking.test.d.ts.map +0 -1
- package/dist/store/use-scene-dirty-tracking.test.js +0 -68
- package/dist/store/use-scene-elevator-migration.test.d.ts +0 -2
- package/dist/store/use-scene-elevator-migration.test.d.ts.map +0 -1
- package/dist/store/use-scene-elevator-migration.test.js +0 -109
- package/dist/store/use-scene-measurement-roundtrip.test.d.ts +0 -2
- package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +0 -1
- package/dist/store/use-scene-measurement-roundtrip.test.js +0 -97
- package/dist/store/use-scene-plugins.test.d.ts +0 -2
- package/dist/store/use-scene-plugins.test.d.ts.map +0 -1
- package/dist/store/use-scene-plugins.test.js +0 -51
- package/dist/store/use-scene-wall-slot-migration.test.d.ts +0 -2
- package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +0 -1
- package/dist/store/use-scene-wall-slot-migration.test.js +0 -210
- package/dist/store/use-scene-window-migration.test.d.ts +0 -2
- package/dist/store/use-scene-window-migration.test.d.ts.map +0 -1
- package/dist/store/use-scene-window-migration.test.js +0 -85
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts +0 -2
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +0 -1
- package/dist/systems/elevator/elevator-opening-sync.test.js +0 -146
- package/dist/systems/elevator/elevator-runtime.test.d.ts +0 -2
- package/dist/systems/elevator/elevator-runtime.test.d.ts.map +0 -1
- package/dist/systems/elevator/elevator-runtime.test.js +0 -64
- package/dist/systems/fence/fence-spline.test.d.ts +0 -2
- package/dist/systems/fence/fence-spline.test.d.ts.map +0 -1
- package/dist/systems/fence/fence-spline.test.js +0 -136
- package/dist/systems/stair/stair-opening-preview.test.d.ts +0 -2
- package/dist/systems/stair/stair-opening-preview.test.d.ts.map +0 -1
- package/dist/systems/stair/stair-opening-preview.test.js +0 -80
- package/dist/systems/stair/stair-opening-sync.test.d.ts +0 -2
- package/dist/systems/stair/stair-opening-sync.test.d.ts.map +0 -1
- package/dist/systems/stair/stair-opening-sync.test.js +0 -485
- package/dist/systems/wall/wall-mitering.test.d.ts +0 -2
- package/dist/systems/wall/wall-mitering.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-mitering.test.js +0 -63
- package/dist/utils/clone-scene-graph.test.d.ts +0 -2
- package/dist/utils/clone-scene-graph.test.d.ts.map +0 -1
- package/dist/utils/clone-scene-graph.test.js +0 -62
- package/dist/utils/heal-scene-graph.test.d.ts +0 -2
- package/dist/utils/heal-scene-graph.test.d.ts.map +0 -1
- package/dist/utils/heal-scene-graph.test.js +0 -88
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { GROUND_SUPPORT_ID } from '../hooks/spatial-grid/support-host-id';
|
|
1
2
|
import { CeilingNode, SlabNode, ZoneNode, } from '../schema';
|
|
3
|
+
import { DEFAULT_LEVEL_HEIGHT } from '../services/level-height';
|
|
4
|
+
import { CEILING_CLAMP_MARGIN, findLevelAboveId, getCeilingClampBound, getLevelElevations, getStoredLevelHeight, } from '../services/storey';
|
|
2
5
|
import { getSceneHistoryPauseDepth, pauseSceneHistory, resumeSceneHistory, } from '../store/history-control';
|
|
6
|
+
import { computeWallSlabSupport } from '../systems/slab/slab-support';
|
|
3
7
|
import { getClampedWallCurveOffset, getWallCurveFrameAt, isCurvedWall, } from '../systems/wall/wall-curve';
|
|
8
|
+
import { resolveWallTop } from '../systems/wall/wall-top';
|
|
4
9
|
import { simplifyClosedPolygon } from './polygon-geometry';
|
|
10
|
+
import { terrainSupportLift } from './terrain-support';
|
|
5
11
|
const DEFAULT_AUTO_SLAB_ELEVATION = 0.05;
|
|
6
|
-
const DEFAULT_AUTO_CEILING_HEIGHT = 2.5;
|
|
7
12
|
const CEILING_HEIGHT_EPSILON = 1e-6;
|
|
8
13
|
const ROOM_CURVE_TOLERANCE = 0.04;
|
|
9
14
|
const MAX_CURVE_SUBDIVISION_DEPTH = 6;
|
|
@@ -19,6 +24,7 @@ const WALL_JUNCTION_TOLERANCE = 0.08;
|
|
|
19
24
|
// deleted) and the node is demoted to manual so user data survives.
|
|
20
25
|
const ORPHAN_MERGE_COVERAGE_THRESHOLD = 0.6;
|
|
21
26
|
const COVERAGE_SAMPLE_STEPS = 12;
|
|
27
|
+
const ROOM_VERTICAL_PLANE_EPSILON = 1e-3;
|
|
22
28
|
function pointFromTuple(point) {
|
|
23
29
|
return { x: point[0], y: point[1] };
|
|
24
30
|
}
|
|
@@ -194,51 +200,56 @@ function wallBoundsRoom(wall, roomPolygon) {
|
|
|
194
200
|
const matchingPoints = candidates.filter((point) => pointDistanceToPolygonBoundary(point, roomPolygon) <= WALL_ROOM_BOUNDARY_TOLERANCE);
|
|
195
201
|
return matchingPoints.length >= 2;
|
|
196
202
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return true;
|
|
203
|
+
/**
|
|
204
|
+
* The clamp bound for a ceiling polygon under this planning context —
|
|
205
|
+
* the context's cross-level resolver when provided, else the plane-only
|
|
206
|
+
* `storeyHeight - CEILING_CLAMP_MARGIN` degradation.
|
|
207
|
+
*/
|
|
208
|
+
function resolveCeilingClampBound(polygon, context) {
|
|
209
|
+
if (context.ceilingClampBound)
|
|
210
|
+
return context.ceilingClampBound(polygon);
|
|
211
|
+
return (context.storeyHeight ?? DEFAULT_LEVEL_HEIGHT) - CEILING_CLAMP_MARGIN;
|
|
209
212
|
}
|
|
210
|
-
function
|
|
211
|
-
if (
|
|
212
|
-
return
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
function consensusElevation(values) {
|
|
214
|
+
if (values.length === 0 || values.some((value) => !Number.isFinite(value)))
|
|
215
|
+
return undefined;
|
|
216
|
+
const min = Math.min(...values);
|
|
217
|
+
const max = Math.max(...values);
|
|
218
|
+
if (max - min > ROOM_VERTICAL_PLANE_EPSILON)
|
|
219
|
+
return undefined;
|
|
220
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
217
221
|
}
|
|
218
|
-
function
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
+
function autoRoomVerticalPlacements(spaces, walls, supportSlabs, nodes, storeyHeight) {
|
|
223
|
+
const wallsById = new Map(walls.map((wall) => [wall.id, wall]));
|
|
224
|
+
const placements = new Map();
|
|
225
|
+
for (const space of spaces) {
|
|
226
|
+
const boundaryWalls = space.wallIds.flatMap((id) => {
|
|
227
|
+
const wall = wallsById.get(id);
|
|
228
|
+
return wall ? [wall] : [];
|
|
229
|
+
});
|
|
230
|
+
if (boundaryWalls.length !== space.wallIds.length)
|
|
222
231
|
continue;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
232
|
+
const wallBases = boundaryWalls.map((wall) => {
|
|
233
|
+
const offset = wall.supportOffset ?? 0;
|
|
234
|
+
if (wall.supportSlabId === GROUND_SUPPORT_ID) {
|
|
235
|
+
return ((terrainSupportLift(nodes, space.levelId, wall.start[0], wall.start[1]) ?? 0) + offset);
|
|
236
|
+
}
|
|
237
|
+
return (computeWallSlabSupport(wall, supportSlabs, walls, wall.supportSlabId ?? null).elevation +
|
|
238
|
+
offset);
|
|
239
|
+
});
|
|
240
|
+
const base = consensusElevation(wallBases);
|
|
241
|
+
if (base === undefined)
|
|
231
242
|
continue;
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
243
|
+
const wallTops = boundaryWalls.map((wall, index) => resolveWallTop(wall, storeyHeight, wallBases[index] ?? base));
|
|
244
|
+
const top = consensusElevation(wallTops);
|
|
245
|
+
if (top === undefined)
|
|
246
|
+
continue;
|
|
247
|
+
placements.set(polygonSignature(space.polygon.map(pointFromTuple)), {
|
|
248
|
+
slabElevation: base + DEFAULT_AUTO_SLAB_ELEVATION,
|
|
249
|
+
ceilingHeight: top - CEILING_CLAMP_MARGIN,
|
|
250
|
+
});
|
|
236
251
|
}
|
|
237
|
-
return
|
|
238
|
-
}
|
|
239
|
-
function resolveAutoCeilingHeight(roomPolygon, context = {}) {
|
|
240
|
-
return (resolveRoomSlabElevation(roomPolygon, context.slabs) +
|
|
241
|
-
resolveRoomWallHeight(roomPolygon, context.walls));
|
|
252
|
+
return placements;
|
|
242
253
|
}
|
|
243
254
|
function getWallDirection(wall) {
|
|
244
255
|
const dx = wall.end[0] - wall.start[0];
|
|
@@ -612,7 +623,12 @@ function wallGeometrySignature(wall) {
|
|
|
612
623
|
wall.end[0].toFixed(4),
|
|
613
624
|
wall.end[1].toFixed(4),
|
|
614
625
|
(wall.thickness ?? 0.2).toFixed(4),
|
|
615
|
-
(
|
|
626
|
+
// Plane-bound (no stored height) is a distinct state, not a default
|
|
627
|
+
// value: it resolves to the storey plane, so it must not alias an
|
|
628
|
+
// explicit height of the same magnitude in the trigger signature.
|
|
629
|
+
wall.height == null ? 'plane' : wall.height.toFixed(4),
|
|
630
|
+
wall.supportSlabId ?? 'elected',
|
|
631
|
+
(wall.supportOffset ?? 0).toFixed(4),
|
|
616
632
|
getClampedWallCurveOffset(wall).toFixed(4),
|
|
617
633
|
].join('|');
|
|
618
634
|
}
|
|
@@ -627,13 +643,24 @@ function zoneGeometrySignature(zone) {
|
|
|
627
643
|
zone.polygon.map(([x, z]) => `${x.toFixed(4)},${z.toFixed(4)}`).join(';'),
|
|
628
644
|
].join('|');
|
|
629
645
|
}
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
// traced room footprint can adopt its enclosing walls
|
|
633
|
-
// next remodel.
|
|
646
|
+
// Slab/ceiling POLYGONS stay out of the trigger signature: including
|
|
647
|
+
// generated footprints caused delete/recreate feedback. Zones are included
|
|
648
|
+
// only so a newly traced room footprint can adopt its enclosing walls
|
|
649
|
+
// without waiting for the next remodel. Slab ELEVATIONS and the level's
|
|
650
|
+
// stored storey height ARE included — both feed the explicit-ceiling
|
|
651
|
+
// re-clamp bound (the storey plane), and neither is rewritten by
|
|
652
|
+
// the sync, so regeneration triggers when they change without feedback.
|
|
653
|
+
// Stage 3-B adds the LEVEL-ABOVE's covering-slab undersides (elevation −
|
|
654
|
+
// thickness, recessed pools excluded): a deck created, lowered, or
|
|
655
|
+
// thickened above must re-run the sync below so ceilings re-clamp under
|
|
656
|
+
// it. Same polygon exclusion applies — the level-above's own auto sync
|
|
657
|
+
// rewrites its slab footprints, and hashing them here would re-trigger
|
|
658
|
+
// this level on every remodel above.
|
|
634
659
|
function levelStructureSnapshots(nodes) {
|
|
635
660
|
const wallsByLevel = new Map();
|
|
636
661
|
const zonesByLevel = new Map();
|
|
662
|
+
const slabElevationsByLevel = new Map();
|
|
663
|
+
const coveringUndersidesByLevel = new Map();
|
|
637
664
|
for (const node of Object.values(nodes)) {
|
|
638
665
|
if (!(node && typeof node === 'object' && 'parentId' in node && node.parentId))
|
|
639
666
|
continue;
|
|
@@ -648,13 +675,33 @@ function levelStructureSnapshots(nodes) {
|
|
|
648
675
|
zones.push(ZoneNode.parse(node));
|
|
649
676
|
zonesByLevel.set(levelId, zones);
|
|
650
677
|
}
|
|
678
|
+
else if (node.type === 'slab') {
|
|
679
|
+
const elevations = slabElevationsByLevel.get(levelId) ?? [];
|
|
680
|
+
elevations.push(`${node.id}:${(node.elevation ?? DEFAULT_AUTO_SLAB_ELEVATION).toFixed(4)}`);
|
|
681
|
+
slabElevationsByLevel.set(levelId, elevations);
|
|
682
|
+
if (node.recessed !== true) {
|
|
683
|
+
const undersides = coveringUndersidesByLevel.get(levelId) ?? [];
|
|
684
|
+
const elevation = node.elevation ?? 0.05;
|
|
685
|
+
const thickness = node.thickness ?? 0.05;
|
|
686
|
+
undersides.push(`${node.id}:${(elevation - thickness).toFixed(4)}`);
|
|
687
|
+
coveringUndersidesByLevel.set(levelId, undersides);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
651
690
|
}
|
|
691
|
+
const levelElevations = getLevelElevations(nodes);
|
|
652
692
|
const snapshots = new Map();
|
|
653
693
|
const levelIds = new Set([...wallsByLevel.keys(), ...zonesByLevel.keys()]);
|
|
654
694
|
for (const levelId of levelIds) {
|
|
655
695
|
const walls = wallsByLevel.get(levelId) ?? [];
|
|
656
696
|
const zones = zonesByLevel.get(levelId) ?? [];
|
|
657
|
-
|
|
697
|
+
const level = nodes[levelId];
|
|
698
|
+
const storeyKey = level?.type === 'level' && typeof level.height === 'number' ? level.height.toFixed(4) : '';
|
|
699
|
+
const slabKey = (slabElevationsByLevel.get(levelId) ?? []).sort().join(';');
|
|
700
|
+
const aboveId = findLevelAboveId(levelId, levelElevations);
|
|
701
|
+
const aboveSlabKey = aboveId
|
|
702
|
+
? (coveringUndersidesByLevel.get(aboveId) ?? []).sort().join(';')
|
|
703
|
+
: '';
|
|
704
|
+
snapshots.set(levelId, `${storeyKey}#${levelWallSnapshot(walls)}##${zones.map(zoneGeometrySignature).sort().join('||')}##${slabKey}##${aboveSlabKey}`);
|
|
658
705
|
}
|
|
659
706
|
return snapshots;
|
|
660
707
|
}
|
|
@@ -712,7 +759,7 @@ export function resolveAutoZonePolygon(zone, resolve) {
|
|
|
712
759
|
const room = extractRooms(walls).find((candidate) => sameStringSet([...new Set(candidate.boundaryFaces.map((boundary) => boundary.wallId))], zone.boundaryWallIds));
|
|
713
760
|
return room ? room.polygon.map(pointToTuple) : zone.polygon;
|
|
714
761
|
}
|
|
715
|
-
export function planAutoSlabsForLevel(roomPolygons, existingSlabs) {
|
|
762
|
+
export function planAutoSlabsForLevel(roomPolygons, existingSlabs, context = {}) {
|
|
716
763
|
const manualSlabs = existingSlabs.filter((slab) => !slab.autoFromWalls);
|
|
717
764
|
const manualSignatures = new Set(manualSlabs.map((slab) => polygonSignature(slab.polygon.map(pointFromTuple))));
|
|
718
765
|
const manualPolygons = manualSlabs.map((slab) => slab.polygon.map(pointFromTuple));
|
|
@@ -758,7 +805,11 @@ export function planAutoSlabsForLevel(roomPolygons, existingSlabs) {
|
|
|
758
805
|
return;
|
|
759
806
|
matchedDetectedIdx.add(index);
|
|
760
807
|
matchedSlabIds.add(existing.slab.id);
|
|
761
|
-
|
|
808
|
+
const polygon = room.poly.map(pointToTuple);
|
|
809
|
+
updatesById.set(existing.slab.id, {
|
|
810
|
+
polygon,
|
|
811
|
+
elevation: context.elevationForRoom?.(polygon),
|
|
812
|
+
});
|
|
762
813
|
});
|
|
763
814
|
const remainingDetected = detected
|
|
764
815
|
.map((room, index) => ({ room, index }))
|
|
@@ -787,7 +838,11 @@ export function planAutoSlabsForLevel(roomPolygons, existingSlabs) {
|
|
|
787
838
|
continue;
|
|
788
839
|
matchedDetectedIdx.add(index);
|
|
789
840
|
matchedSlabIds.add(bestMatch.entry.slab.id);
|
|
790
|
-
|
|
841
|
+
const polygon = room.poly.map(pointToTuple);
|
|
842
|
+
updatesById.set(bestMatch.entry.slab.id, {
|
|
843
|
+
polygon,
|
|
844
|
+
elevation: context.elevationForRoom?.(polygon),
|
|
845
|
+
});
|
|
791
846
|
}
|
|
792
847
|
const detectedRoomPolygons = detectedAll.map((room) => room.poly);
|
|
793
848
|
const slabsToDelete = [];
|
|
@@ -809,10 +864,17 @@ export function planAutoSlabsForLevel(roomPolygons, existingSlabs) {
|
|
|
809
864
|
...existingAuto
|
|
810
865
|
.filter((slab) => updatesById.has(slab.id))
|
|
811
866
|
.flatMap((slab) => {
|
|
812
|
-
const
|
|
813
|
-
if (!
|
|
867
|
+
const update = updatesById.get(slab.id);
|
|
868
|
+
if (!update)
|
|
814
869
|
return [];
|
|
815
|
-
|
|
870
|
+
const data = {};
|
|
871
|
+
if (!sameTuplePolygon(slab.polygon, update.polygon))
|
|
872
|
+
data.polygon = update.polygon;
|
|
873
|
+
if (update.elevation !== undefined &&
|
|
874
|
+
Math.abs(slab.elevation - update.elevation) > ROOM_VERTICAL_PLANE_EPSILON) {
|
|
875
|
+
data.elevation = update.elevation;
|
|
876
|
+
}
|
|
877
|
+
return Object.keys(data).length > 0 ? [{ id: slab.id, data }] : [];
|
|
816
878
|
}),
|
|
817
879
|
...slabDemotions,
|
|
818
880
|
];
|
|
@@ -826,11 +888,15 @@ export function planAutoSlabsForLevel(roomPolygons, existingSlabs) {
|
|
|
826
888
|
continue;
|
|
827
889
|
const name = nextAutoRoomName(plannedSlabsForNaming, 'Slab');
|
|
828
890
|
plannedSlabsForNaming.push({ name });
|
|
891
|
+
const polygon = room.poly.map(pointToTuple);
|
|
892
|
+
const elevation = context.elevationForRoom?.(polygon);
|
|
829
893
|
slabsToCreate.push(SlabNode.parse({
|
|
830
894
|
name,
|
|
831
|
-
polygon
|
|
895
|
+
polygon,
|
|
832
896
|
holes: [],
|
|
833
|
-
elevation:
|
|
897
|
+
elevation: elevation !== undefined && Number.isFinite(elevation)
|
|
898
|
+
? elevation
|
|
899
|
+
: DEFAULT_AUTO_SLAB_ELEVATION,
|
|
834
900
|
autoFromWalls: true,
|
|
835
901
|
}));
|
|
836
902
|
}
|
|
@@ -840,8 +906,8 @@ export function planAutoSlabsForLevel(roomPolygons, existingSlabs) {
|
|
|
840
906
|
delete: slabsToDelete,
|
|
841
907
|
};
|
|
842
908
|
}
|
|
843
|
-
function syncAutoSlabsForLevel(levelId, roomPolygons, existingSlabs, sceneStore) {
|
|
844
|
-
const plan = planAutoSlabsForLevel(roomPolygons, existingSlabs);
|
|
909
|
+
function syncAutoSlabsForLevel(levelId, roomPolygons, existingSlabs, sceneStore, context = {}) {
|
|
910
|
+
const plan = planAutoSlabsForLevel(roomPolygons, existingSlabs, context);
|
|
845
911
|
if (plan.delete.length > 0) {
|
|
846
912
|
sceneStore.getState().deleteNodes(plan.delete);
|
|
847
913
|
}
|
|
@@ -853,22 +919,6 @@ function syncAutoSlabsForLevel(levelId, roomPolygons, existingSlabs, sceneStore)
|
|
|
853
919
|
}
|
|
854
920
|
return plan;
|
|
855
921
|
}
|
|
856
|
-
export function projectAutoSlabsForPlan(existingSlabs, plan) {
|
|
857
|
-
const slabsById = new Map(existingSlabs.map((slab) => [slab.id, slab]));
|
|
858
|
-
for (const id of plan.delete) {
|
|
859
|
-
slabsById.delete(id);
|
|
860
|
-
}
|
|
861
|
-
for (const update of plan.update) {
|
|
862
|
-
const slab = slabsById.get(update.id);
|
|
863
|
-
if (!slab)
|
|
864
|
-
continue;
|
|
865
|
-
slabsById.set(update.id, SlabNode.parse({ ...slab, ...update.data }));
|
|
866
|
-
}
|
|
867
|
-
for (const slab of plan.create) {
|
|
868
|
-
slabsById.set(slab.id, slab);
|
|
869
|
-
}
|
|
870
|
-
return [...slabsById.values()];
|
|
871
|
-
}
|
|
872
922
|
export function planAutoCeilingsForLevel(roomPolygons, existingCeilings, context = {}) {
|
|
873
923
|
const manualCeilings = existingCeilings.filter((ceiling) => !ceiling.autoFromWalls);
|
|
874
924
|
const manualSignatures = new Set(manualCeilings.map((ceiling) => polygonSignature(ceiling.polygon.map(pointFromTuple))));
|
|
@@ -887,7 +937,6 @@ export function planAutoCeilingsForLevel(roomPolygons, existingCeilings, context
|
|
|
887
937
|
centroid: polygonCentroid(room.poly),
|
|
888
938
|
area: Math.abs(polygonArea(room.poly)),
|
|
889
939
|
bbox: bboxOf(room.poly),
|
|
890
|
-
ceilingHeight: resolveAutoCeilingHeight(room.poly, context),
|
|
891
940
|
}));
|
|
892
941
|
const detected = detectedAll.filter(({ sig, poly }) => !manualSignatures.has(sig) && !matchesManualFootprint(poly, manualPolygons));
|
|
893
942
|
const existingAuto = existingCeilings.filter((ceiling) => ceiling.autoFromWalls);
|
|
@@ -916,9 +965,10 @@ export function planAutoCeilingsForLevel(roomPolygons, existingCeilings, context
|
|
|
916
965
|
return;
|
|
917
966
|
matchedDetectedIdx.add(index);
|
|
918
967
|
matchedCeilingIds.add(existing.ceiling.id);
|
|
968
|
+
const polygon = room.poly.map(pointToTuple);
|
|
919
969
|
updatesById.set(existing.ceiling.id, {
|
|
920
|
-
polygon
|
|
921
|
-
height:
|
|
970
|
+
polygon,
|
|
971
|
+
height: context.heightForRoom?.(polygon),
|
|
922
972
|
});
|
|
923
973
|
});
|
|
924
974
|
const remainingDetected = detected
|
|
@@ -948,9 +998,10 @@ export function planAutoCeilingsForLevel(roomPolygons, existingCeilings, context
|
|
|
948
998
|
continue;
|
|
949
999
|
matchedDetectedIdx.add(index);
|
|
950
1000
|
matchedCeilingIds.add(bestMatch.entry.ceiling.id);
|
|
1001
|
+
const polygon = room.poly.map(pointToTuple);
|
|
951
1002
|
updatesById.set(bestMatch.entry.ceiling.id, {
|
|
952
|
-
polygon
|
|
953
|
-
height:
|
|
1003
|
+
polygon,
|
|
1004
|
+
height: context.heightForRoom?.(polygon),
|
|
954
1005
|
});
|
|
955
1006
|
}
|
|
956
1007
|
const detectedRoomPolygons = detectedAll.map((room) => room.poly);
|
|
@@ -967,6 +1018,24 @@ export function planAutoCeilingsForLevel(roomPolygons, existingCeilings, context
|
|
|
967
1018
|
ceilingDemotions.push({ id: ceiling.id, data: { autoFromWalls: false } });
|
|
968
1019
|
}
|
|
969
1020
|
}
|
|
1021
|
+
// Stage 3-B reactive re-clamp (clamp-never-ask): a covering slab
|
|
1022
|
+
// created, moved, or thickened on the level above can leave an EXISTING
|
|
1023
|
+
// manual explicit-height ceiling poking into its solid. Clamp explicit
|
|
1024
|
+
// heights down to the bound; never raise them — a user-lowered ceiling
|
|
1025
|
+
// is intent, only an over-bound one is a conflict. Follows-mode
|
|
1026
|
+
// ceilings (absent height) derive under the bound by construction and
|
|
1027
|
+
// are skipped, so the clamp can never convert one to an explicit
|
|
1028
|
+
// height.
|
|
1029
|
+
const manualClamps = manualCeilings.flatMap((ceiling) => {
|
|
1030
|
+
if (ceiling.height == null)
|
|
1031
|
+
return [];
|
|
1032
|
+
const bound = resolveCeilingClampBound(ceiling.polygon, context);
|
|
1033
|
+
if (!Number.isFinite(bound))
|
|
1034
|
+
return [];
|
|
1035
|
+
return ceiling.height > bound + CEILING_HEIGHT_EPSILON
|
|
1036
|
+
? [{ id: ceiling.id, data: { height: bound } }]
|
|
1037
|
+
: [];
|
|
1038
|
+
});
|
|
970
1039
|
const ceilingsToUpdate = [
|
|
971
1040
|
...existingAuto
|
|
972
1041
|
.filter((ceiling) => updatesById.has(ceiling.id))
|
|
@@ -975,16 +1044,17 @@ export function planAutoCeilingsForLevel(roomPolygons, existingCeilings, context
|
|
|
975
1044
|
if (!update)
|
|
976
1045
|
return [];
|
|
977
1046
|
const data = {};
|
|
978
|
-
if (!sameTuplePolygon(ceiling.polygon, update.polygon))
|
|
1047
|
+
if (!sameTuplePolygon(ceiling.polygon, update.polygon))
|
|
979
1048
|
data.polygon = update.polygon;
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1049
|
+
if (update.height !== undefined &&
|
|
1050
|
+
(ceiling.height === undefined ||
|
|
1051
|
+
Math.abs(ceiling.height - update.height) > ROOM_VERTICAL_PLANE_EPSILON)) {
|
|
983
1052
|
data.height = update.height;
|
|
984
1053
|
}
|
|
985
|
-
return Object.keys(data).length
|
|
1054
|
+
return Object.keys(data).length > 0 ? [{ id: ceiling.id, data }] : [];
|
|
986
1055
|
}),
|
|
987
1056
|
...ceilingDemotions,
|
|
1057
|
+
...manualClamps,
|
|
988
1058
|
];
|
|
989
1059
|
const plannedCeilingsForNaming = [...existingCeilings];
|
|
990
1060
|
const ceilingsToCreate = [];
|
|
@@ -996,11 +1066,13 @@ export function planAutoCeilingsForLevel(roomPolygons, existingCeilings, context
|
|
|
996
1066
|
continue;
|
|
997
1067
|
const name = nextAutoRoomName(plannedCeilingsForNaming, 'Ceiling');
|
|
998
1068
|
plannedCeilingsForNaming.push({ name });
|
|
1069
|
+
const polygon = room.poly.map(pointToTuple);
|
|
1070
|
+
const height = context.heightForRoom?.(polygon);
|
|
999
1071
|
ceilingsToCreate.push(CeilingNode.parse({
|
|
1000
1072
|
name,
|
|
1001
|
-
polygon
|
|
1073
|
+
polygon,
|
|
1002
1074
|
holes: [],
|
|
1003
|
-
height:
|
|
1075
|
+
...(height !== undefined && Number.isFinite(height) ? { height } : {}),
|
|
1004
1076
|
autoFromWalls: true,
|
|
1005
1077
|
}));
|
|
1006
1078
|
}
|
|
@@ -1066,10 +1138,24 @@ function runSpaceDetection(levelIds, sceneStore, editorStore, nodes) {
|
|
|
1066
1138
|
},
|
|
1067
1139
|
})));
|
|
1068
1140
|
}
|
|
1141
|
+
const levelNode = nodes[levelId];
|
|
1142
|
+
const storeyHeight = levelNode?.type === 'level'
|
|
1143
|
+
? getStoredLevelHeight(levelNode)
|
|
1144
|
+
: DEFAULT_LEVEL_HEIGHT;
|
|
1069
1145
|
const parsedSlabs = slabs.map((slab) => SlabNode.parse(slab));
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1146
|
+
const verticalPlacements = autoRoomVerticalPlacements(spaces, walls,
|
|
1147
|
+
// A derived floor cannot be evidence for its own next elevation: that
|
|
1148
|
+
// would lift unpinned walls, then lift the floor again on every pass.
|
|
1149
|
+
parsedSlabs.filter((slab) => !slab.autoFromWalls), nodes, storeyHeight);
|
|
1150
|
+
const placementFor = (polygon) => verticalPlacements.get(polygonSignature(polygon.map(pointFromTuple)));
|
|
1151
|
+
syncAutoSlabsForLevel(levelId, roomPolygons, parsedSlabs, sceneStore, {
|
|
1152
|
+
elevationForRoom: (polygon) => placementFor(polygon)?.slabElevation,
|
|
1153
|
+
});
|
|
1154
|
+
syncAutoCeilingsForLevel(levelId, roomPolygons, ceilings.map((ceiling) => CeilingNode.parse(ceiling)), sceneStore, {
|
|
1155
|
+
storeyHeight,
|
|
1156
|
+
ceilingClampBound: (polygon) => getCeilingClampBound(levelId, nodes, polygon),
|
|
1157
|
+
heightForRoom: (polygon) => placementFor(polygon)?.ceilingHeight,
|
|
1158
|
+
});
|
|
1073
1159
|
const zonePlan = planAutoZonesForLevel(spaces, zones.map((zone) => ZoneNode.parse(zone)));
|
|
1074
1160
|
if (zonePlan.update.length > 0)
|
|
1075
1161
|
updateNodes(zonePlan.update);
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sculpt brush: pointer motion → `HeightPatch`.
|
|
3
|
+
*
|
|
4
|
+
* The obvious implementation — `h += strength * falloff` every tick — is wrong,
|
|
5
|
+
* and it is wrong in a way that is well documented by the people who ship it.
|
|
6
|
+
* Dwelling in one spot accumulates without bound, overlapping dabs double-apply,
|
|
7
|
+
* and a fast drag deposits less material than a slow one. That is exactly the
|
|
8
|
+
* "hash edges and jagged" result expert Sims builders complain about and route
|
|
9
|
+
* around. This module implements the model the mature tools converge on instead:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Freeze a snapshot at pointer-down.** Every dab is evaluated against the
|
|
12
|
+
* snapshot, never against the running result.
|
|
13
|
+
* 2. **Saturating coverage mask.** A dab raises coverage to `max(mask, falloff)`
|
|
14
|
+
* rather than adding to it, and the height is always
|
|
15
|
+
* `snapshot + delta * mask`. Re-crossing your own stroke therefore cannot
|
|
16
|
+
* compound — the second pass recomputes the same value from the same
|
|
17
|
+
* snapshot.
|
|
18
|
+
* 3. **Arc-length dab spacing.** `advanceStroke` walks from the previous dab to
|
|
19
|
+
* the new pointer position in fixed metre steps, so stroke strength is a
|
|
20
|
+
* function of distance travelled, not of mouse speed or frame rate.
|
|
21
|
+
* 4. **Cosine-bell falloff.** Of the standard sculpt kernels this is one of only
|
|
22
|
+
* two that are C¹ at *both* centre and rim, which is what prevents a visible
|
|
23
|
+
* ridge at the brush boundary.
|
|
24
|
+
*
|
|
25
|
+
* The consequence users feel: one stroke is one bounded, repeatable edit. Hold
|
|
26
|
+
* still and nothing runs away; press again to go further.
|
|
27
|
+
*
|
|
28
|
+
* The `TerrainStroke` returned by `beginStroke` is a mutable accumulator — the
|
|
29
|
+
* one mutable thing in this file, and deliberately so. It is owned by exactly
|
|
30
|
+
* one in-flight gesture and dies with it, which is cheaper and clearer than
|
|
31
|
+
* rebuilding a coverage map per pointer-move.
|
|
32
|
+
*/
|
|
33
|
+
import { type HeightPatch, type TerrainField } from './terrain-field';
|
|
34
|
+
export type BrushShape = 'round' | 'square';
|
|
35
|
+
/**
|
|
36
|
+
* `raise`/`lower` are one verb with an inverted sign at the call site, matching
|
|
37
|
+
* every surveyed tool's modifier-inverts convention. `flatten` is absolute:
|
|
38
|
+
* it converges on a target height, which is the operation that makes two
|
|
39
|
+
* disjoint building pads provably equal — the thing freehand push/pull cannot do.
|
|
40
|
+
*/
|
|
41
|
+
export type TerrainVerb = 'raise' | 'lower' | 'flatten' | 'smooth';
|
|
42
|
+
export type BrushSettings = {
|
|
43
|
+
/** Brush radius in metres. */
|
|
44
|
+
radius: number;
|
|
45
|
+
/** 0–1. Scales how far one full stroke pass gets toward its goal. */
|
|
46
|
+
strength: number;
|
|
47
|
+
/**
|
|
48
|
+
* 0–1 softness. 0 is a hard-edged stamp; 1 puts the cosine bell across the
|
|
49
|
+
* whole radius. The inner `1 - falloff` fraction stays at full coverage.
|
|
50
|
+
*/
|
|
51
|
+
falloff: number;
|
|
52
|
+
shape: BrushShape;
|
|
53
|
+
};
|
|
54
|
+
export declare const DEFAULT_BRUSH_SETTINGS: BrushSettings;
|
|
55
|
+
/**
|
|
56
|
+
* Metres a single raise/lower stroke moves the ground at full strength and full
|
|
57
|
+
* coverage. Bounding it per stroke is what makes the verb aimable: the user
|
|
58
|
+
* knows a pass is worth at most this much, and repeats for more.
|
|
59
|
+
*/
|
|
60
|
+
export declare const RAISE_METRES_PER_STROKE = 1;
|
|
61
|
+
/**
|
|
62
|
+
* Smallest brush radius that still bites, as a multiple of the field's spacing.
|
|
63
|
+
*
|
|
64
|
+
* A dab only moves samples that fall *inside* its radius, so a brush narrower
|
|
65
|
+
* than the gap between samples can land entirely between four of them and change
|
|
66
|
+
* nothing: `advanceStroke` returns `null`, and the user drags with no mark, no
|
|
67
|
+
* cursor change, and no error. 1.5 rather than the ~0.71 where a round brush
|
|
68
|
+
* first catches a corner, because a brush that only bites when it happens to
|
|
69
|
+
* straddle a sample is worse than one that is honestly too small — at 1.5 the
|
|
70
|
+
* worst-case coverage is 0.97, so the dab lands wherever it is aimed.
|
|
71
|
+
*
|
|
72
|
+
* A *multiple* of spacing rather than a constant because spacing is not fixed:
|
|
73
|
+
* `fieldExtentForSite` stretches it once a lot exceeds what 257² samples cover
|
|
74
|
+
* at the default, so on a 300 m lot the old flat 0.5 m floor sat below half the
|
|
75
|
+
* sample gap and the brush silently stopped working at the bottom of its range.
|
|
76
|
+
*/
|
|
77
|
+
export declare const MIN_BRUSH_RADIUS_IN_SPACINGS = 1.5;
|
|
78
|
+
/**
|
|
79
|
+
* Clamp a radius to what `field` can actually resolve.
|
|
80
|
+
*
|
|
81
|
+
* Lives here rather than in the editor's UI layer because it is a fact about the
|
|
82
|
+
* footprint math above, not a preference: every producer of a radius (slider,
|
|
83
|
+
* `[`/`]`, a future import preset) has to respect it or it produces a brush that
|
|
84
|
+
* does nothing.
|
|
85
|
+
*/
|
|
86
|
+
export declare function minBrushRadius(field: Pick<TerrainField, 'spacing'>): number;
|
|
87
|
+
export type TerrainStroke = {
|
|
88
|
+
readonly verb: TerrainVerb;
|
|
89
|
+
readonly settings: BrushSettings;
|
|
90
|
+
/** The field at pointer-down. Never mutated; every dab reads from it. */
|
|
91
|
+
readonly snapshot: TerrainField;
|
|
92
|
+
/** Absolute target height in metres. Only meaningful for `flatten`. */
|
|
93
|
+
readonly target: number;
|
|
94
|
+
/** Sample index → saturating coverage in 0–1. */
|
|
95
|
+
readonly mask: Map<number, number>;
|
|
96
|
+
/** Lazily computed diffused snapshot, for `smooth`. */
|
|
97
|
+
smoothed: Int16Array | null;
|
|
98
|
+
/** Last dab centre, for arc-length spacing. Null until the first dab. */
|
|
99
|
+
lastX: number | null;
|
|
100
|
+
lastZ: number | null;
|
|
101
|
+
};
|
|
102
|
+
export declare function beginStroke(options: {
|
|
103
|
+
field: TerrainField;
|
|
104
|
+
verb: TerrainVerb;
|
|
105
|
+
settings?: Partial<BrushSettings>;
|
|
106
|
+
/** Absolute height for `flatten`, in metres. Ignored by the other verbs. */
|
|
107
|
+
target?: number;
|
|
108
|
+
}): TerrainStroke;
|
|
109
|
+
/**
|
|
110
|
+
* Sample the height a `flatten` stroke should aim at, from the field itself.
|
|
111
|
+
*
|
|
112
|
+
* Sampling before the stroke starts (right-click / first click, per the surveyed
|
|
113
|
+
* tools) is what makes "flatten this pad to match that corner" a two-step
|
|
114
|
+
* gesture instead of a numeric-entry chore.
|
|
115
|
+
*/
|
|
116
|
+
export declare function sampleTarget(field: TerrainField, x: number, z: number): number;
|
|
117
|
+
/**
|
|
118
|
+
* Forget where the last dab landed, without ending the stroke.
|
|
119
|
+
*
|
|
120
|
+
* Needed because dab spacing is a true *arc length*: `advanceStroke` interpolates
|
|
121
|
+
* from the previous centre, so any gap in the pointer stream is bridged with a
|
|
122
|
+
* line of dabs when it resumes. That is exactly right for a dropped frame, and
|
|
123
|
+
* exactly wrong when the caller deliberately skipped a stretch — a camera zoom
|
|
124
|
+
* mid-stroke moves the ground under a held brush, and bridging back to a centre
|
|
125
|
+
* from before the zoom paints a stripe across everything in between.
|
|
126
|
+
*
|
|
127
|
+
* A stroke resumed after this deposits its next dab at the pointer, like the
|
|
128
|
+
* first one. The mask is untouched, so the stroke stays saturating across the
|
|
129
|
+
* break and the whole gesture is still one undo step.
|
|
130
|
+
*/
|
|
131
|
+
export declare function detachStrokeAnchor(stroke: TerrainStroke): void;
|
|
132
|
+
/**
|
|
133
|
+
* Extend the stroke to the pointer's new position and return the patch to apply.
|
|
134
|
+
*
|
|
135
|
+
* Returns `null` when the motion is shorter than the dab spacing (nothing
|
|
136
|
+
* changed yet) or when the footprint misses the field entirely. The patch is
|
|
137
|
+
* absolute heights over the union of the dabs placed by *this* call, recomputed
|
|
138
|
+
* from the snapshot and the saturated mask — which is why applying patches in
|
|
139
|
+
* order converges rather than compounding.
|
|
140
|
+
*/
|
|
141
|
+
export declare function advanceStroke(stroke: TerrainStroke, x: number, z: number): HeightPatch | null;
|
|
142
|
+
/**
|
|
143
|
+
* Coverage in 0–1 for a sample at offset `(dx, dz)` from a dab centre.
|
|
144
|
+
*
|
|
145
|
+
* Round uses Euclidean distance, square uses Chebyshev — the same radius means
|
|
146
|
+
* the square brush's inscribed circle matches the round one, so switching shape
|
|
147
|
+
* mid-session does not change the brush's apparent size.
|
|
148
|
+
*/
|
|
149
|
+
export declare function weightAt(settings: BrushSettings, dx: number, dz: number): number;
|
|
150
|
+
/**
|
|
151
|
+
* The world-XZ height under a brush, for the readout the sculpt HUD shows.
|
|
152
|
+
* Thin, but it keeps the tool from importing `heightAt` just to render a number.
|
|
153
|
+
*/
|
|
154
|
+
export declare function brushHeightAt(field: TerrainField, x: number, z: number): number;
|
|
155
|
+
/** Peak coverage the stroke reached, for tests and for the HUD's "pass" readout. */
|
|
156
|
+
export declare function maxCoverage(stroke: TerrainStroke): number;
|
|
157
|
+
/**
|
|
158
|
+
* Highest sample height in metres under a world-XZ rect — the seed a `flatten`
|
|
159
|
+
* pad uses when the user asks for "level with the high corner" rather than
|
|
160
|
+
* typing a number.
|
|
161
|
+
*/
|
|
162
|
+
export declare function highestOver(field: TerrainField, minX: number, minZ: number, maxX: number, maxZ: number): number;
|
|
163
|
+
//# sourceMappingURL=terrain-brush.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terrain-brush.d.ts","sourceRoot":"","sources":["../../src/lib/terrain-brush.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EACL,KAAK,WAAW,EAIhB,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAA;AAExB,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE3C;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAA;AAElE,MAAM,MAAM,aAAa,GAAG;IAC1B,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAED,eAAO,MAAM,sBAAsB,EAAE,aAKpC,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,MAAM,CAAA;AAE/C;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,GAAG,MAAM,CAE3E;AAaD,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAChC,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,uDAAuD;IACvD,QAAQ,EAAE,UAAU,GAAG,IAAI,CAAA;IAC3B,yEAAyE;IACzE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB,CAAA;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE;IACnC,KAAK,EAAE,YAAY,CAAA;IACnB,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAA;IACjC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,aAAa,CAWhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAG9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CA8C7F;AAmID;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAehF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAIzD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,MAAM,CAeR"}
|