@pascal-app/core 1.0.0-beta.4 → 1.0.0
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/LICENSE +21 -0
- package/README.md +23 -0
- package/dist/capture/index.d.ts +3 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +2 -0
- package/dist/capture/schema.d.ts +417 -0
- package/dist/capture/schema.d.ts.map +1 -0
- package/dist/capture/schema.js +348 -0
- package/dist/capture/source.d.ts +60 -0
- package/dist/capture/source.d.ts.map +1 -0
- package/dist/capture/source.js +254 -0
- package/dist/events/bus.d.ts +55 -7
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/hidden-wall-pointer-hold.d.ts +28 -0
- package/dist/events/hidden-wall-pointer-hold.d.ts.map +1 -0
- package/dist/events/hidden-wall-pointer-hold.js +37 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +12 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +39 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +2785 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +132 -19
- package/dist/hooks/spatial-grid/support-host-patch.d.ts +45 -1
- package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/support-host-patch.js +171 -6
- package/dist/index.d.ts +15 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -5
- package/dist/lib/asset-storage.d.ts.map +1 -1
- package/dist/lib/asset-storage.js +4 -1
- package/dist/lib/polygon-union.d.ts +4 -0
- package/dist/lib/polygon-union.d.ts.map +1 -0
- package/dist/lib/polygon-union.js +306 -0
- package/dist/lib/roof-overlap.d.ts +32 -0
- package/dist/lib/roof-overlap.d.ts.map +1 -0
- package/dist/lib/roof-overlap.js +72 -0
- package/dist/lib/room-topology-index.d.ts +50 -0
- package/dist/lib/room-topology-index.d.ts.map +1 -0
- package/dist/lib/room-topology-index.js +237 -0
- package/dist/lib/slab-polygon.d.ts +35 -2
- package/dist/lib/slab-polygon.d.ts.map +1 -1
- package/dist/lib/slab-polygon.js +133 -27
- package/dist/lib/space-detection.d.ts +40 -3
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +890 -246
- package/dist/registry/handles.d.ts +24 -3
- package/dist/registry/handles.d.ts.map +1 -1
- package/dist/registry/index.d.ts +3 -3
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/registry/registry.d.ts +29 -6
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +111 -4
- package/dist/registry/scene-api.d.ts +16 -0
- package/dist/registry/scene-api.d.ts.map +1 -1
- package/dist/registry/scene-api.js +49 -1
- package/dist/registry/types.d.ts +261 -13
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry/use-registry-version.d.ts +11 -0
- package/dist/registry/use-registry-version.d.ts.map +1 -0
- package/dist/registry/use-registry-version.js +15 -0
- package/dist/schema/__bench__/node-parsers.bench.d.ts +24 -0
- package/dist/schema/__bench__/node-parsers.bench.d.ts.map +1 -0
- package/dist/schema/__bench__/node-parsers.bench.js +314 -0
- package/dist/schema/__fixtures__/node-fixtures.d.ts +28 -0
- package/dist/schema/__fixtures__/node-fixtures.d.ts.map +1 -0
- package/dist/schema/__fixtures__/node-fixtures.js +125 -0
- package/dist/schema/base.d.ts +1 -1
- package/dist/schema/base.d.ts.map +1 -1
- package/dist/schema/base.js +8 -1
- package/dist/schema/compiled-node-parsers.d.ts +40 -0
- package/dist/schema/compiled-node-parsers.d.ts.map +1 -0
- package/dist/schema/compiled-node-parsers.js +95 -0
- package/dist/schema/index.d.ts +20 -15
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +19 -12
- package/dist/schema/material.d.ts +2 -1
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +1 -0
- package/dist/schema/nodes/automatic-downspout.d.ts +17 -0
- package/dist/schema/nodes/automatic-downspout.d.ts.map +1 -0
- package/dist/schema/nodes/automatic-downspout.js +237 -0
- package/dist/schema/nodes/block.d.ts +107 -0
- package/dist/schema/nodes/block.d.ts.map +1 -0
- package/dist/schema/nodes/block.js +207 -0
- package/dist/schema/nodes/box-vent.d.ts +8 -2
- package/dist/schema/nodes/box-vent.d.ts.map +1 -1
- package/dist/schema/nodes/box-vent.js +2 -0
- package/dist/schema/nodes/building.d.ts +1 -1
- package/dist/schema/nodes/cabinet.d.ts +25 -3
- package/dist/schema/nodes/cabinet.d.ts.map +1 -1
- package/dist/schema/nodes/cabinet.js +26 -5
- package/dist/schema/nodes/ceiling.d.ts +1 -1
- package/dist/schema/nodes/chimney.d.ts +2 -2
- package/dist/schema/nodes/column.d.ts +6 -6
- package/dist/schema/nodes/construction-dimension.d.ts +4 -4
- package/dist/schema/nodes/cupola.d.ts +9 -1
- package/dist/schema/nodes/cupola.d.ts.map +1 -1
- package/dist/schema/nodes/cupola.js +2 -0
- package/dist/schema/nodes/door.d.ts +1 -1
- package/dist/schema/nodes/dormer.d.ts +44 -1
- package/dist/schema/nodes/dormer.d.ts.map +1 -1
- package/dist/schema/nodes/dormer.js +131 -7
- package/dist/schema/nodes/downspout.d.ts +11 -1
- package/dist/schema/nodes/downspout.d.ts.map +1 -1
- package/dist/schema/nodes/downspout.js +24 -0
- package/dist/schema/nodes/duct-fitting.d.ts +18 -1
- package/dist/schema/nodes/duct-fitting.d.ts.map +1 -1
- package/dist/schema/nodes/duct-fitting.js +25 -4
- package/dist/schema/nodes/duct-segment.d.ts +23 -1
- package/dist/schema/nodes/duct-segment.d.ts.map +1 -1
- package/dist/schema/nodes/duct-segment.js +20 -0
- package/dist/schema/nodes/duct-terminal.d.ts +1 -1
- package/dist/schema/nodes/elevator.d.ts +1 -1
- package/dist/schema/nodes/eyebrow-vent.d.ts +8 -2
- package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -1
- package/dist/schema/nodes/eyebrow-vent.js +2 -0
- package/dist/schema/nodes/fence.d.ts +1 -1
- package/dist/schema/nodes/guide.d.ts +1 -1
- package/dist/schema/nodes/gutter.d.ts +36 -2
- package/dist/schema/nodes/gutter.d.ts.map +1 -1
- package/dist/schema/nodes/gutter.js +308 -1
- package/dist/schema/nodes/hanger-overrides.d.ts +7 -0
- package/dist/schema/nodes/hanger-overrides.d.ts.map +1 -0
- package/dist/schema/nodes/hanger-overrides.js +6 -0
- package/dist/schema/nodes/hvac-equipment.d.ts +1 -1
- package/dist/schema/nodes/item.d.ts +4 -3
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +5 -0
- package/dist/schema/nodes/lean-to-extension.d.ts +212 -0
- package/dist/schema/nodes/lean-to-extension.d.ts.map +1 -0
- package/dist/schema/nodes/lean-to-extension.js +114 -0
- package/dist/schema/nodes/level.d.ts +5 -2
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +9 -0
- package/dist/schema/nodes/lineset.d.ts +1 -1
- package/dist/schema/nodes/liquid-line.d.ts +1 -1
- package/dist/schema/nodes/measurement.d.ts +1 -1
- package/dist/schema/nodes/pipe-fitting.d.ts +9 -1
- package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -1
- package/dist/schema/nodes/pipe-fitting.js +6 -2
- package/dist/schema/nodes/pipe-segment.d.ts +23 -1
- package/dist/schema/nodes/pipe-segment.d.ts.map +1 -1
- package/dist/schema/nodes/pipe-segment.js +20 -0
- package/dist/schema/nodes/pipe-trap.d.ts +1 -1
- package/dist/schema/nodes/ridge-vent.d.ts +1 -1
- package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
- package/dist/schema/nodes/ridge-vent.js +4 -1
- package/dist/schema/nodes/roof-segment-shape.d.ts +4 -0
- package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment-shape.js +64 -1
- package/dist/schema/nodes/roof-segment-walls.d.ts +3 -1
- package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment-walls.js +1 -1
- package/dist/schema/nodes/roof-segment.d.ts +41 -1
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +79 -2
- package/dist/schema/nodes/roof.d.ts +22 -1
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +14 -0
- package/dist/schema/nodes/scan.d.ts +19 -2
- package/dist/schema/nodes/scan.d.ts.map +1 -1
- package/dist/schema/nodes/scan.js +11 -1
- package/dist/schema/nodes/shelf.d.ts +2 -2
- package/dist/schema/nodes/site.d.ts +1 -1
- package/dist/schema/nodes/skylight.d.ts +1 -1
- package/dist/schema/nodes/slab.d.ts +1 -1
- package/dist/schema/nodes/solar-panel.d.ts +1 -1
- package/dist/schema/nodes/spawn.d.ts +1 -1
- package/dist/schema/nodes/stair-segment.d.ts +1 -1
- package/dist/schema/nodes/stair.d.ts +1 -1
- package/dist/schema/nodes/structural-grid.d.ts +1 -1
- package/dist/schema/nodes/turbine-vent.d.ts +7 -1
- package/dist/schema/nodes/turbine-vent.d.ts.map +1 -1
- package/dist/schema/nodes/turbine-vent.js +2 -0
- package/dist/schema/nodes/wall.d.ts +4 -4
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +7 -1
- package/dist/schema/nodes/window.d.ts +10 -3
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/nodes/window.js +4 -0
- package/dist/schema/nodes/zone.d.ts +2 -2
- package/dist/schema/nodes/zone.js +1 -1
- package/dist/schema/types.d.ts +485 -109
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +27 -1
- package/dist/services/hosting.d.ts +2 -2
- package/dist/services/hosting.d.ts.map +1 -1
- package/dist/services/hosting.js +4 -4
- package/dist/services/index.d.ts +2 -2
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -2
- package/dist/services/storey.d.ts +3 -10
- package/dist/services/storey.d.ts.map +1 -1
- package/dist/services/storey.js +45 -15
- package/dist/services/system-graph.d.ts +11 -0
- package/dist/services/system-graph.d.ts.map +1 -1
- package/dist/services/system-graph.js +35 -5
- package/dist/store/actions/node-actions.d.ts +15 -4
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +458 -19
- package/dist/store/history-control.d.ts +5 -0
- package/dist/store/history-control.d.ts.map +1 -1
- package/dist/store/history-control.js +68 -6
- package/dist/store/history-invalidation.d.ts +3 -0
- package/dist/store/history-invalidation.d.ts.map +1 -0
- package/dist/store/history-invalidation.js +72 -0
- package/dist/store/scene-hydration.d.ts +5 -0
- package/dist/store/scene-hydration.d.ts.map +1 -0
- package/dist/store/scene-hydration.js +57 -0
- package/dist/store/use-scene.d.ts +3 -0
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +407 -227
- package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-service.js +16 -11
- package/dist/systems/roof/roof-elevation-system.d.ts +3 -0
- package/dist/systems/roof/roof-elevation-system.d.ts.map +1 -0
- package/dist/systems/roof/roof-elevation-system.js +94 -0
- package/dist/systems/roof/roof-elevation.d.ts +4 -0
- package/dist/systems/roof/roof-elevation.d.ts.map +1 -0
- package/dist/systems/roof/roof-elevation.js +95 -0
- package/dist/systems/roof/roof-footprint.d.ts +17 -0
- package/dist/systems/roof/roof-footprint.d.ts.map +1 -0
- package/dist/systems/roof/roof-footprint.js +83 -0
- package/dist/systems/slab/slab-support.d.ts +0 -31
- package/dist/systems/slab/slab-support.d.ts.map +1 -1
- package/dist/systems/slab/slab-support.js +26 -4
- package/dist/systems/stair/stair-flight.d.ts +17 -0
- package/dist/systems/stair/stair-flight.d.ts.map +1 -0
- package/dist/systems/stair/stair-flight.js +27 -0
- package/dist/systems/stair/stair-opening-preview.d.ts +1556 -1328
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-system.d.ts +1 -0
- package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-system.js +93 -96
- package/dist/systems/stair/stair-rise.js +21 -20
- package/dist/systems/wall/wall-curve.d.ts +1 -0
- package/dist/systems/wall/wall-curve.d.ts.map +1 -1
- package/dist/systems/wall/wall-curve.js +73 -0
- package/dist/systems/wall/wall-footprint.d.ts +1 -0
- package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
- package/dist/systems/wall/wall-footprint.js +1 -0
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +88 -9
- package/dist/systems/wall/wall-topology.d.ts +53 -0
- package/dist/systems/wall/wall-topology.d.ts.map +1 -0
- package/dist/systems/wall/wall-topology.js +414 -0
- package/dist/utils/clone-scene-graph.d.ts +2 -0
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +39 -2
- package/dist/utils/scene-migrations.d.ts +1 -0
- package/dist/utils/scene-migrations.d.ts.map +1 -1
- package/dist/utils/scene-migrations.js +1 -0
- package/dist/utils/vertical-scene-migration.d.ts +13 -0
- package/dist/utils/vertical-scene-migration.d.ts.map +1 -0
- package/dist/utils/vertical-scene-migration.js +179 -0
- package/dist/validation/validate-build-json.d.ts +3 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -1
- package/dist/validation/validate-build-json.js +49 -2
- package/package.json +10 -3
- package/dist/systems/elevator/elevator-runtime-system.d.ts +0 -2
- package/dist/systems/elevator/elevator-runtime-system.d.ts.map +0 -1
- package/dist/systems/elevator/elevator-runtime-system.js +0 -8
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { getClampedWallCurveOffset } from '../systems/wall/wall-curve';
|
|
2
|
+
const CELL_SIZE = 2;
|
|
3
|
+
function bboxOf(points) {
|
|
4
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
5
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
6
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
7
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
8
|
+
for (const [x, y] of points) {
|
|
9
|
+
minX = Math.min(minX, x);
|
|
10
|
+
minY = Math.min(minY, y);
|
|
11
|
+
maxX = Math.max(maxX, x);
|
|
12
|
+
maxY = Math.max(maxY, y);
|
|
13
|
+
}
|
|
14
|
+
return { minX, minY, maxX, maxY };
|
|
15
|
+
}
|
|
16
|
+
function expandedBbox(box, margin) {
|
|
17
|
+
return {
|
|
18
|
+
minX: box.minX - margin,
|
|
19
|
+
minY: box.minY - margin,
|
|
20
|
+
maxX: box.maxX + margin,
|
|
21
|
+
maxY: box.maxY + margin,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function cellKeysForBbox(box) {
|
|
25
|
+
const keys = [];
|
|
26
|
+
for (let x = Math.floor(box.minX / CELL_SIZE); x <= Math.floor(box.maxX / CELL_SIZE); x += 1) {
|
|
27
|
+
for (let y = Math.floor(box.minY / CELL_SIZE); y <= Math.floor(box.maxY / CELL_SIZE); y += 1) {
|
|
28
|
+
keys.push(`${x},${y}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return keys;
|
|
32
|
+
}
|
|
33
|
+
export function distanceToSegment(point, segStart, segEnd) {
|
|
34
|
+
const [px, py] = point;
|
|
35
|
+
const [x1, y1] = segStart;
|
|
36
|
+
const [x2, y2] = segEnd;
|
|
37
|
+
const dx = x2 - x1;
|
|
38
|
+
const dy = y2 - y1;
|
|
39
|
+
const lenSq = dx * dx + dy * dy;
|
|
40
|
+
if (lenSq < 0.0001)
|
|
41
|
+
return Math.hypot(px - x1, py - y1);
|
|
42
|
+
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
43
|
+
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
44
|
+
}
|
|
45
|
+
function roomUsesAnyWall(room, wallIds) {
|
|
46
|
+
return room.boundaryFaces.some((boundary) => wallIds.has(boundary.wallId));
|
|
47
|
+
}
|
|
48
|
+
function sameIndexedWall(left, right) {
|
|
49
|
+
if (!(left && right))
|
|
50
|
+
return left === right;
|
|
51
|
+
return (left.parentId === right.parentId &&
|
|
52
|
+
left.start[0] === right.start[0] &&
|
|
53
|
+
left.start[1] === right.start[1] &&
|
|
54
|
+
left.end[0] === right.end[0] &&
|
|
55
|
+
left.end[1] === right.end[1] &&
|
|
56
|
+
getClampedWallCurveOffset(left) === getClampedWallCurveOffset(right));
|
|
57
|
+
}
|
|
58
|
+
export class RoomTopologyIndex {
|
|
59
|
+
options;
|
|
60
|
+
levels = new Map();
|
|
61
|
+
queryMargin;
|
|
62
|
+
constructor(options) {
|
|
63
|
+
this.options = options;
|
|
64
|
+
this.queryMargin = options.junctionTolerance + 0.02;
|
|
65
|
+
}
|
|
66
|
+
rebuild(nodes) {
|
|
67
|
+
this.levels.clear();
|
|
68
|
+
const wallsByLevel = new Map();
|
|
69
|
+
for (const node of Object.values(nodes)) {
|
|
70
|
+
if (node?.type !== 'wall' || !node.parentId)
|
|
71
|
+
continue;
|
|
72
|
+
const walls = wallsByLevel.get(node.parentId) ?? [];
|
|
73
|
+
walls.push(node);
|
|
74
|
+
wallsByLevel.set(node.parentId, walls);
|
|
75
|
+
}
|
|
76
|
+
for (const [levelId, walls] of wallsByLevel) {
|
|
77
|
+
this.levels.set(levelId, this.createLevel(walls));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
rebuildLevel(levelId, nodes) {
|
|
81
|
+
const level = this.createLevel(this.wallsForLevel(nodes, levelId));
|
|
82
|
+
this.levels.set(levelId, level);
|
|
83
|
+
return level;
|
|
84
|
+
}
|
|
85
|
+
applyWallDelta(levelId, changedWallIds, beforeNodes, currentNodes) {
|
|
86
|
+
let strategy = 'indexed';
|
|
87
|
+
let level = this.levels.get(levelId);
|
|
88
|
+
if (!level) {
|
|
89
|
+
level = this.rebuildLevel(levelId, beforeNodes);
|
|
90
|
+
strategy = 'fallback';
|
|
91
|
+
}
|
|
92
|
+
for (const wallId of changedWallIds) {
|
|
93
|
+
const cached = level.walls.get(wallId);
|
|
94
|
+
const previous = beforeNodes[wallId];
|
|
95
|
+
const previousWall = previous?.type === 'wall' && previous.parentId === levelId ? previous : undefined;
|
|
96
|
+
if (!sameIndexedWall(cached, previousWall)) {
|
|
97
|
+
level = this.rebuildLevel(levelId, beforeNodes);
|
|
98
|
+
strategy = 'fallback';
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const beforeComponentIds = this.connectedWallIds(level, changedWallIds);
|
|
103
|
+
for (const wallId of changedWallIds) {
|
|
104
|
+
const current = currentNodes[wallId];
|
|
105
|
+
if (current?.type === 'wall' && current.parentId === levelId) {
|
|
106
|
+
this.setWall(level, current);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
this.removeWall(level, wallId);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const currentSeedIds = new Set(changedWallIds);
|
|
113
|
+
for (const wallId of beforeComponentIds) {
|
|
114
|
+
if (level.walls.has(wallId))
|
|
115
|
+
currentSeedIds.add(wallId);
|
|
116
|
+
}
|
|
117
|
+
const currentComponentIds = this.connectedWallIds(level, currentSeedIds);
|
|
118
|
+
const examinedIds = new Set([...changedWallIds, ...beforeComponentIds, ...currentComponentIds]);
|
|
119
|
+
const beforeRooms = level.rooms.filter((room) => roomUsesAnyWall(room, examinedIds));
|
|
120
|
+
const previousWalls = this.wallsFromNodes(beforeNodes, levelId, examinedIds);
|
|
121
|
+
const currentWalls = this.wallsFromNodes(currentNodes, levelId, examinedIds);
|
|
122
|
+
const currentRooms = this.options.detectRooms(currentWalls);
|
|
123
|
+
const allCurrentRooms = [
|
|
124
|
+
...level.rooms.filter((room) => !roomUsesAnyWall(room, examinedIds)),
|
|
125
|
+
...currentRooms,
|
|
126
|
+
];
|
|
127
|
+
level.rooms = allCurrentRooms;
|
|
128
|
+
return {
|
|
129
|
+
strategy,
|
|
130
|
+
beforeRooms,
|
|
131
|
+
currentRooms,
|
|
132
|
+
allCurrentRooms,
|
|
133
|
+
previousWalls,
|
|
134
|
+
currentWalls,
|
|
135
|
+
examinedWallIds: [...examinedIds].sort(),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
wallsForLevel(nodes, levelId) {
|
|
139
|
+
const level = nodes[levelId];
|
|
140
|
+
if (level?.type !== 'level')
|
|
141
|
+
return [];
|
|
142
|
+
return level.children.flatMap((id) => {
|
|
143
|
+
const node = nodes[id];
|
|
144
|
+
return node?.type === 'wall' && node.parentId === levelId ? [node] : [];
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
wallsFromNodes(nodes, levelId, ids) {
|
|
148
|
+
return [...ids].flatMap((wallId) => {
|
|
149
|
+
const node = nodes[wallId];
|
|
150
|
+
return node?.type === 'wall' && node.parentId === levelId ? [node] : [];
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
createLevel(walls) {
|
|
154
|
+
const level = {
|
|
155
|
+
walls: new Map(),
|
|
156
|
+
rooms: this.options.detectRooms(walls),
|
|
157
|
+
wallIdsByCell: new Map(),
|
|
158
|
+
cellKeysByWallId: new Map(),
|
|
159
|
+
};
|
|
160
|
+
for (const wall of walls)
|
|
161
|
+
this.setWall(level, wall);
|
|
162
|
+
return level;
|
|
163
|
+
}
|
|
164
|
+
wallBbox(wall) {
|
|
165
|
+
return bboxOf(this.options.sampleWall(wall));
|
|
166
|
+
}
|
|
167
|
+
cellKeysForWall(wall) {
|
|
168
|
+
return cellKeysForBbox(expandedBbox(this.wallBbox(wall), this.queryMargin));
|
|
169
|
+
}
|
|
170
|
+
removeWall(level, wallId) {
|
|
171
|
+
for (const key of level.cellKeysByWallId.get(wallId) ?? []) {
|
|
172
|
+
const ids = level.wallIdsByCell.get(key);
|
|
173
|
+
ids?.delete(wallId);
|
|
174
|
+
if (ids?.size === 0)
|
|
175
|
+
level.wallIdsByCell.delete(key);
|
|
176
|
+
}
|
|
177
|
+
level.cellKeysByWallId.delete(wallId);
|
|
178
|
+
level.walls.delete(wallId);
|
|
179
|
+
}
|
|
180
|
+
setWall(level, wall) {
|
|
181
|
+
this.removeWall(level, wall.id);
|
|
182
|
+
level.walls.set(wall.id, wall);
|
|
183
|
+
const keys = this.cellKeysForWall(wall);
|
|
184
|
+
level.cellKeysByWallId.set(wall.id, keys);
|
|
185
|
+
for (const key of keys) {
|
|
186
|
+
const ids = level.wallIdsByCell.get(key) ?? new Set();
|
|
187
|
+
ids.add(wall.id);
|
|
188
|
+
level.wallIdsByCell.set(key, ids);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
queryWalls(level, wall) {
|
|
192
|
+
const ids = new Set();
|
|
193
|
+
for (const key of this.cellKeysForWall(wall)) {
|
|
194
|
+
for (const id of level.wallIdsByCell.get(key) ?? [])
|
|
195
|
+
ids.add(id);
|
|
196
|
+
}
|
|
197
|
+
return ids;
|
|
198
|
+
}
|
|
199
|
+
wallsTouch(left, right) {
|
|
200
|
+
const leftPoints = this.options.sampleWall(left);
|
|
201
|
+
const rightPoints = this.options.sampleWall(right);
|
|
202
|
+
const touchesPolyline = (points, other) => {
|
|
203
|
+
const endpoints = [points[0], points.at(-1)].filter((point) => Boolean(point));
|
|
204
|
+
for (const endpoint of endpoints) {
|
|
205
|
+
for (let index = 0; index < other.length - 1; index += 1) {
|
|
206
|
+
if (distanceToSegment(endpoint, other[index], other[index + 1]) <=
|
|
207
|
+
this.options.junctionTolerance) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
};
|
|
214
|
+
return touchesPolyline(leftPoints, rightPoints) || touchesPolyline(rightPoints, leftPoints);
|
|
215
|
+
}
|
|
216
|
+
connectedWallIds(level, seedIds) {
|
|
217
|
+
const connected = new Set();
|
|
218
|
+
const queue = [...seedIds];
|
|
219
|
+
while (queue.length > 0) {
|
|
220
|
+
const wallId = queue.pop();
|
|
221
|
+
if (connected.has(wallId))
|
|
222
|
+
continue;
|
|
223
|
+
const wall = level.walls.get(wallId);
|
|
224
|
+
if (!wall)
|
|
225
|
+
continue;
|
|
226
|
+
connected.add(wallId);
|
|
227
|
+
for (const neighborId of this.queryWalls(level, wall)) {
|
|
228
|
+
if (connected.has(neighborId))
|
|
229
|
+
continue;
|
|
230
|
+
const neighbor = level.walls.get(neighborId);
|
|
231
|
+
if (neighbor && this.wallsTouch(wall, neighbor))
|
|
232
|
+
queue.push(neighborId);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return connected;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
import type { GeometryContext } from '../registry/types';
|
|
2
|
-
import type { SlabNode, WallNode } from '../schema';
|
|
2
|
+
import type { AnyNode, AnyNodeId, SlabNode, WallNode } from '../schema';
|
|
3
3
|
export type SlabPolygonContext = {
|
|
4
4
|
/** Walls on the slab's level. */
|
|
5
5
|
walls: WallNode[];
|
|
6
6
|
/** Other slabs on the same level — the slab itself must be excluded. */
|
|
7
7
|
siblingSlabs: SlabNode[];
|
|
8
8
|
};
|
|
9
|
+
/** [ax, az, bx, bz] segment in plan space. */
|
|
10
|
+
type Segment = [number, number, number, number];
|
|
11
|
+
/** A sibling slab edge and the direction of its polygon interior. */
|
|
12
|
+
type NeighborSegment = {
|
|
13
|
+
slab: SlabNode;
|
|
14
|
+
segment: Segment;
|
|
15
|
+
bounds: Bounds;
|
|
16
|
+
elevation: number;
|
|
17
|
+
/** Unit normal pointing into the sibling polygon at this edge. */
|
|
18
|
+
inwardX: number;
|
|
19
|
+
inwardZ: number;
|
|
20
|
+
};
|
|
9
21
|
/**
|
|
10
22
|
* Derive a {@link SlabPolygonContext} from a registry `GeometryContext`:
|
|
11
23
|
* sibling slabs come from `ctx.siblings` (same kind, same parent, self
|
|
12
24
|
* excluded) and walls from the parent level's children.
|
|
13
25
|
*/
|
|
14
26
|
export declare function slabPolygonContextFromGeometry(ctx: GeometryContext | undefined): SlabPolygonContext;
|
|
15
|
-
export declare function
|
|
27
|
+
export declare function slabPolygonContextForLevel(parent: AnyNode | null, resolve: (id: AnyNodeId) => AnyNode | undefined, fallbackSlabs?: SlabNode[]): SlabPolygonContext;
|
|
28
|
+
export declare function getRenderableSlabPolygon(slabNode: SlabNode, context: SlabPolygonContext | PreparedSlabPolygonContext): Array<[number, number]>;
|
|
29
|
+
type WallCandidate = {
|
|
30
|
+
wall: WallNode;
|
|
31
|
+
segments: Segment[];
|
|
32
|
+
halfThickness: number;
|
|
33
|
+
};
|
|
16
34
|
export type SlabEdgeWallBandSnap = {
|
|
17
35
|
wallId: WallNode['id'];
|
|
18
36
|
/** The candidate edge translated perpendicular onto the wall centerline. */
|
|
@@ -29,4 +47,19 @@ export type SlabEdgeWallBandSnap = {
|
|
|
29
47
|
export declare function snapSlabEdgeToWallBand(a: [number, number], b: [number, number], walls: readonly WallNode[], options?: {
|
|
30
48
|
maxLateral?: number;
|
|
31
49
|
}): SlabEdgeWallBandSnap | null;
|
|
50
|
+
type Bounds = [number, number, number, number];
|
|
51
|
+
export declare function scopeSlabPolygonContext(slab: SlabNode, context: PreparedSlabPolygonContext): PreparedSlabPolygonContext;
|
|
52
|
+
export declare function slabPolygonContextChanges(before: PreparedSlabPolygonContext, after: PreparedSlabPolygonContext): (slab: SlabNode) => boolean;
|
|
53
|
+
type PreparedWallCandidate = WallCandidate & {
|
|
54
|
+
bounds: Bounds;
|
|
55
|
+
};
|
|
56
|
+
type PreparedSlabPolygonContext = SlabPolygonContext & {
|
|
57
|
+
wallCache: WeakMap<WallNode, PreparedWallCandidate>;
|
|
58
|
+
neighborCache: WeakMap<SlabNode, NeighborSegment[]>;
|
|
59
|
+
wallCandidates: PreparedWallCandidate[];
|
|
60
|
+
neighborSegments: NeighborSegment[];
|
|
61
|
+
siblingBreakTolerance: number;
|
|
62
|
+
};
|
|
63
|
+
export declare function prepareSlabPolygonContext(context: SlabPolygonContext, previous?: PreparedSlabPolygonContext): PreparedSlabPolygonContext;
|
|
64
|
+
export {};
|
|
32
65
|
//# sourceMappingURL=slab-polygon.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slab-polygon.d.ts","sourceRoot":"","sources":["../../src/lib/slab-polygon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"slab-polygon.d.ts","sourceRoot":"","sources":["../../src/lib/slab-polygon.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/slab-polygon.js
CHANGED
|
@@ -114,16 +114,24 @@ function polygonWindingSign(polygon) {
|
|
|
114
114
|
export function slabPolygonContextFromGeometry(ctx) {
|
|
115
115
|
if (!ctx)
|
|
116
116
|
return { walls: [], siblingSlabs: [] };
|
|
117
|
-
|
|
117
|
+
return {
|
|
118
|
+
...slabPolygonContextForLevel(ctx.parent, ctx.resolve),
|
|
119
|
+
siblingSlabs: ctx.siblings.filter((node) => node.type === 'slab'),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function slabPolygonContextForLevel(parent, resolve, fallbackSlabs = []) {
|
|
118
123
|
const walls = [];
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
const siblingSlabs = [];
|
|
125
|
+
const childIds = parent?.children;
|
|
126
|
+
if (!Array.isArray(childIds)) {
|
|
127
|
+
return { walls, siblingSlabs: parent ? fallbackSlabs : [] };
|
|
128
|
+
}
|
|
129
|
+
for (const id of childIds) {
|
|
130
|
+
const child = resolve(id);
|
|
131
|
+
if (child?.type === 'wall')
|
|
132
|
+
walls.push(child);
|
|
133
|
+
else if (child?.type === 'slab')
|
|
134
|
+
siblingSlabs.push(child);
|
|
127
135
|
}
|
|
128
136
|
return { walls, siblingSlabs };
|
|
129
137
|
}
|
|
@@ -132,7 +140,7 @@ export function getRenderableSlabPolygon(slabNode, context) {
|
|
|
132
140
|
if (polygon.length < 3 || isFloatingSlab(slabNode)) {
|
|
133
141
|
return polygon.map(([x, z]) => [x, z]);
|
|
134
142
|
}
|
|
135
|
-
const subSpans = computeEdgeSubSpans(polygon, slabNode.elevation ?? DEFAULT_SLAB_ELEVATION, context);
|
|
143
|
+
const subSpans = computeEdgeSubSpans(polygon, slabNode.elevation ?? DEFAULT_SLAB_ELEVATION, 'wallCandidates' in context ? context : prepareSlabPolygonContext(context), slabNode.id);
|
|
136
144
|
if (subSpans.every((spans) => spans.length === 1 && spans[0].offset === 0)) {
|
|
137
145
|
return polygon.map(([x, z]) => [x, z]);
|
|
138
146
|
}
|
|
@@ -254,17 +262,71 @@ export function snapSlabEdgeToWallBand(a, b, walls, options) {
|
|
|
254
262
|
],
|
|
255
263
|
};
|
|
256
264
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
function polygonBounds(polygon) {
|
|
266
|
+
const bounds = [Infinity, Infinity, -Infinity, -Infinity];
|
|
267
|
+
for (const [x, z] of polygon) {
|
|
268
|
+
bounds[0] = Math.min(bounds[0], x);
|
|
269
|
+
bounds[1] = Math.min(bounds[1], z);
|
|
270
|
+
bounds[2] = Math.max(bounds[2], x);
|
|
271
|
+
bounds[3] = Math.max(bounds[3], z);
|
|
272
|
+
}
|
|
273
|
+
return bounds;
|
|
274
|
+
}
|
|
275
|
+
function boundsOverlap(a, b, padding) {
|
|
276
|
+
return (a[0] <= b[2] + padding &&
|
|
277
|
+
a[2] >= b[0] - padding &&
|
|
278
|
+
a[1] <= b[3] + padding &&
|
|
279
|
+
a[3] >= b[1] - padding);
|
|
280
|
+
}
|
|
281
|
+
export function scopeSlabPolygonContext(slab, context) {
|
|
282
|
+
const bounds = polygonBounds(slab.polygon);
|
|
283
|
+
const wallCandidates = context.wallCandidates.filter((candidate) => boundsOverlap(bounds, candidate.bounds, candidate.halfThickness + WALL_ADOPTION_TOLERANCE));
|
|
284
|
+
// Keep the level-wide tolerance: even an unadopted wall can widen sibling
|
|
285
|
+
// breakpoint collection, which may affect short-span fusion.
|
|
286
|
+
const neighborSegments = context.neighborSegments.filter((neighbor) => neighbor.slab.id !== slab.id &&
|
|
287
|
+
boundsOverlap(bounds, neighbor.bounds, context.siblingBreakTolerance));
|
|
288
|
+
return {
|
|
289
|
+
...context,
|
|
290
|
+
walls: wallCandidates.map((candidate) => candidate.wall),
|
|
291
|
+
siblingSlabs: [...new Set(neighborSegments.map((neighbor) => neighbor.slab))],
|
|
292
|
+
wallCandidates,
|
|
293
|
+
neighborSegments,
|
|
294
|
+
siblingBreakTolerance: context.siblingBreakTolerance,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
export function slabPolygonContextChanges(before, after) {
|
|
298
|
+
if (before.siblingBreakTolerance !== after.siblingBreakTolerance)
|
|
299
|
+
return () => true;
|
|
300
|
+
const changedWalls = new Set();
|
|
301
|
+
const changedSlabs = new Set();
|
|
302
|
+
for (let i = 0; i < Math.max(before.walls.length, after.walls.length); i++) {
|
|
303
|
+
if (before.walls[i] === after.walls[i])
|
|
304
|
+
continue;
|
|
305
|
+
if (before.walls[i])
|
|
306
|
+
changedWalls.add(before.walls[i]);
|
|
307
|
+
if (after.walls[i])
|
|
308
|
+
changedWalls.add(after.walls[i]);
|
|
309
|
+
}
|
|
310
|
+
for (let i = 0; i < Math.max(before.siblingSlabs.length, after.siblingSlabs.length); i++) {
|
|
311
|
+
if (before.siblingSlabs[i] === after.siblingSlabs[i])
|
|
312
|
+
continue;
|
|
313
|
+
if (before.siblingSlabs[i])
|
|
314
|
+
changedSlabs.add(before.siblingSlabs[i]);
|
|
315
|
+
if (after.siblingSlabs[i])
|
|
316
|
+
changedSlabs.add(after.siblingSlabs[i]);
|
|
317
|
+
}
|
|
318
|
+
const walls = [...before.wallCandidates, ...after.wallCandidates].filter((candidate) => changedWalls.has(candidate.wall));
|
|
319
|
+
const neighbors = [...before.neighborSegments, ...after.neighborSegments].filter((neighbor) => changedSlabs.has(neighbor.slab));
|
|
320
|
+
return (slab) => {
|
|
321
|
+
const bounds = polygonBounds(slab.polygon);
|
|
322
|
+
return (walls.some((candidate) => boundsOverlap(bounds, candidate.bounds, candidate.halfThickness + WALL_ADOPTION_TOLERANCE)) ||
|
|
323
|
+
neighbors.some((neighbor) => neighbor.slab.id !== slab.id &&
|
|
324
|
+
boundsOverlap(bounds, neighbor.bounds, after.siblingBreakTolerance)));
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
export function prepareSlabPolygonContext(context, previous) {
|
|
328
|
+
const wallCache = previous?.wallCache ?? new WeakMap();
|
|
329
|
+
const neighborCache = previous?.neighborCache ?? new WeakMap();
|
|
268
330
|
const neighborSegments = [];
|
|
269
331
|
for (const sibling of context.siblingSlabs) {
|
|
270
332
|
// A floating deck keeps its drawn polygon, so it can't be a seam
|
|
@@ -273,6 +335,12 @@ function computeEdgeSubSpans(polygon, selfElevation, context) {
|
|
|
273
335
|
// only describe room floors meeting under a wall.
|
|
274
336
|
if (isFloatingSlab(sibling))
|
|
275
337
|
continue;
|
|
338
|
+
const cached = neighborCache.get(sibling);
|
|
339
|
+
if (cached) {
|
|
340
|
+
neighborSegments.push(...cached);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const segments = [];
|
|
276
344
|
const siblingPolygon = sibling.polygon;
|
|
277
345
|
if (siblingPolygon.length < 2)
|
|
278
346
|
continue;
|
|
@@ -286,19 +354,35 @@ function computeEdgeSubSpans(polygon, selfElevation, context) {
|
|
|
286
354
|
const length = Math.hypot(dx, dz);
|
|
287
355
|
if (length < 1e-9)
|
|
288
356
|
continue;
|
|
289
|
-
|
|
357
|
+
segments.push({
|
|
358
|
+
slab: sibling,
|
|
359
|
+
bounds: polygonBounds([from, to]),
|
|
290
360
|
segment: [from[0], from[1], to[0], to[1]],
|
|
291
361
|
elevation,
|
|
292
362
|
inwardX: (-siblingWinding * dz) / length,
|
|
293
363
|
inwardZ: (siblingWinding * dx) / length,
|
|
294
364
|
});
|
|
295
365
|
}
|
|
366
|
+
neighborCache.set(sibling, segments);
|
|
367
|
+
neighborSegments.push(...segments);
|
|
296
368
|
}
|
|
297
|
-
const wallCandidates = context.walls.map((wall) =>
|
|
298
|
-
wall
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
369
|
+
const wallCandidates = context.walls.map((wall) => {
|
|
370
|
+
const cached = wallCache.get(wall);
|
|
371
|
+
if (cached)
|
|
372
|
+
return cached;
|
|
373
|
+
const segments = wallCenterlineSegments(wall);
|
|
374
|
+
const candidate = {
|
|
375
|
+
wall,
|
|
376
|
+
segments,
|
|
377
|
+
halfThickness: getWallThickness(wall) / 2,
|
|
378
|
+
bounds: polygonBounds(segments.flatMap(([ax, az, bx, bz]) => [
|
|
379
|
+
[ax, az],
|
|
380
|
+
[bx, bz],
|
|
381
|
+
])),
|
|
382
|
+
};
|
|
383
|
+
wallCache.set(wall, candidate);
|
|
384
|
+
return candidate;
|
|
385
|
+
});
|
|
302
386
|
// Sibling breakpoints also matter for legacy face-aligned polygons up
|
|
303
387
|
// to a full wall band away from the edge (the band-sibling interior
|
|
304
388
|
// rule in classifySpan), so clip them with the widest band reach any
|
|
@@ -308,6 +392,28 @@ function computeEdgeSubSpans(polygon, selfElevation, context) {
|
|
|
308
392
|
for (const candidate of wallCandidates) {
|
|
309
393
|
siblingBreakTolerance = Math.max(siblingBreakTolerance, 2 * (candidate.halfThickness + WALL_ADOPTION_TOLERANCE));
|
|
310
394
|
}
|
|
395
|
+
return {
|
|
396
|
+
...context,
|
|
397
|
+
wallCache,
|
|
398
|
+
neighborCache,
|
|
399
|
+
wallCandidates,
|
|
400
|
+
neighborSegments,
|
|
401
|
+
siblingBreakTolerance,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Split every polygon edge at candidate span boundaries and classify
|
|
406
|
+
* each sub-span independently. Returns one non-empty span list per
|
|
407
|
+
* edge, covering [0, edgeLength] without gaps.
|
|
408
|
+
*/
|
|
409
|
+
function computeEdgeSubSpans(polygon, selfElevation, context, slabId) {
|
|
410
|
+
const n = polygon.length;
|
|
411
|
+
// Winding sign: the outward normal of an edge with direction `dir` is
|
|
412
|
+
// `s * (dirZ, -dirX)`, so a target lateral `L` measured along
|
|
413
|
+
// `(dirZ, -dirX)` is `s * L` along the outward normal.
|
|
414
|
+
const s = polygonWindingSign(polygon);
|
|
415
|
+
const { wallCandidates, siblingBreakTolerance } = context;
|
|
416
|
+
const neighborSegments = context.neighborSegments.filter((segment) => segment.slab.id !== slabId);
|
|
311
417
|
const subSpans = [];
|
|
312
418
|
for (let index = 0; index < n; index += 1) {
|
|
313
419
|
const a = polygon[index];
|
|
@@ -16,6 +16,20 @@ export type Space = {
|
|
|
16
16
|
boundaryFaces: SpaceBoundaryFace[];
|
|
17
17
|
isExterior: boolean;
|
|
18
18
|
};
|
|
19
|
+
export type SpaceTopologyReconcileEvent = {
|
|
20
|
+
levelId: string;
|
|
21
|
+
strategy: 'indexed' | 'fallback';
|
|
22
|
+
examinedWallIds: string[];
|
|
23
|
+
affectedBeforeRoomCount: number;
|
|
24
|
+
affectedCurrentRoomCount: number;
|
|
25
|
+
};
|
|
26
|
+
export type SpaceDetectionSyncOptions = {
|
|
27
|
+
onTopologyReconcile?: (event: SpaceTopologyReconcileEvent) => void;
|
|
28
|
+
};
|
|
29
|
+
type ExtractedRoom = {
|
|
30
|
+
polygon: Point2D[];
|
|
31
|
+
boundaryFaces: SpaceBoundaryFace[];
|
|
32
|
+
};
|
|
19
33
|
type WallSideUpdate = {
|
|
20
34
|
wallId: string;
|
|
21
35
|
frontSide: 'interior' | 'exterior' | 'unknown';
|
|
@@ -31,6 +45,7 @@ export type AutoSlabSyncPlan = {
|
|
|
31
45
|
};
|
|
32
46
|
export type AutoSlabPlanningContext = {
|
|
33
47
|
elevationForRoom?: (polygon: Array<[number, number]>) => number | undefined;
|
|
48
|
+
previousElevationForRoom?: (polygon: Array<[number, number]>) => number | undefined;
|
|
34
49
|
};
|
|
35
50
|
export type AutoCeilingSyncPlan = {
|
|
36
51
|
create: CeilingNodeType[];
|
|
@@ -39,6 +54,10 @@ export type AutoCeilingSyncPlan = {
|
|
|
39
54
|
data: Partial<CeilingNodeType>;
|
|
40
55
|
}>;
|
|
41
56
|
delete: Array<CeilingNodeType['id']>;
|
|
57
|
+
reparent: Array<{
|
|
58
|
+
id: AnyNodeId;
|
|
59
|
+
parentId: CeilingNodeType['id'];
|
|
60
|
+
}>;
|
|
42
61
|
};
|
|
43
62
|
export type AutoZoneSyncPlan = {
|
|
44
63
|
update: Array<{
|
|
@@ -58,6 +77,14 @@ export type AutoCeilingPlanningContext = {
|
|
|
58
77
|
*/
|
|
59
78
|
ceilingClampBound?: (polygon: Array<[number, number]>) => number;
|
|
60
79
|
heightForRoom?: (polygon: Array<[number, number]>) => number | undefined;
|
|
80
|
+
previousHeightForRoom?: (polygon: Array<[number, number]>) => number | undefined;
|
|
81
|
+
childPosition?: (childId: AnyNodeId) => [number, number] | undefined;
|
|
82
|
+
};
|
|
83
|
+
declare function bboxOf(points: Point2D[]): {
|
|
84
|
+
minX: number;
|
|
85
|
+
minY: number;
|
|
86
|
+
maxX: number;
|
|
87
|
+
maxY: number;
|
|
61
88
|
};
|
|
62
89
|
/**
|
|
63
90
|
* True when `wall` lies on the boundary of a room enclosed by `walls`, using the
|
|
@@ -69,11 +96,21 @@ export type AutoCeilingPlanningContext = {
|
|
|
69
96
|
*/
|
|
70
97
|
export declare function wallClosesRoom(walls: WallNode[], wall: WallNode): boolean;
|
|
71
98
|
export declare function resolveWallSurfaceSides(wall: Pick<WallNode, 'start' | 'end' | 'thickness' | 'frontSide' | 'backSide'>, roomPolygons: Point2D[][]): Pick<WallSideUpdate, 'frontSide' | 'backSide'>;
|
|
99
|
+
type BoundedPolygon = {
|
|
100
|
+
polygon: Point2D[];
|
|
101
|
+
bbox: ReturnType<typeof bboxOf>;
|
|
102
|
+
};
|
|
103
|
+
export declare function surfaceTouchesRooms(surface: BoundedPolygon, rooms: BoundedPolygon[]): boolean;
|
|
72
104
|
export declare function planAutoZonesForLevel(spaces: readonly Space[], existingZones: readonly ZoneNodeType[]): AutoZoneSyncPlan;
|
|
73
105
|
export declare function resolveAutoZonePolygon(zone: Pick<ZoneNodeType, 'autoFromWalls' | 'boundaryWallIds' | 'polygon'>, resolve: (id: AnyNodeId) => unknown): ZoneNodeType['polygon'];
|
|
74
|
-
export declare function planAutoSlabsForLevel(roomPolygons: Point2D[][], existingSlabs: SlabNodeType[], context?: AutoSlabPlanningContext
|
|
75
|
-
|
|
106
|
+
export declare function planAutoSlabsForLevel(roomPolygons: Point2D[][], existingSlabs: SlabNodeType[], context?: AutoSlabPlanningContext, namingSlabs?: Array<{
|
|
107
|
+
name?: string;
|
|
108
|
+
}>): AutoSlabSyncPlan;
|
|
109
|
+
export declare function planAutoCeilingsForLevel(roomPolygons: Point2D[][], existingCeilings: CeilingNodeType[], context?: AutoCeilingPlanningContext, namingCeilings?: Array<{
|
|
110
|
+
name?: string;
|
|
111
|
+
}>): AutoCeilingSyncPlan;
|
|
76
112
|
export declare function detectSpacesForLevel(levelId: string, walls: WallNode[]): {
|
|
113
|
+
rooms: ExtractedRoom[];
|
|
77
114
|
roomPolygons: Point2D[][];
|
|
78
115
|
spaces: Space[];
|
|
79
116
|
wallUpdates: WallSideUpdate[];
|
|
@@ -84,7 +121,7 @@ export declare function pauseSpaceDetection(): void;
|
|
|
84
121
|
export declare function resumeSpaceDetection(): void;
|
|
85
122
|
/** True iff the wall-driven auto slab/ceiling sync is currently paused. */
|
|
86
123
|
export declare function isSpaceDetectionPaused(): boolean;
|
|
87
|
-
export declare function initSpaceDetectionSync(sceneStore: any, editorStore: any): () => void;
|
|
124
|
+
export declare function initSpaceDetectionSync(sceneStore: any, editorStore: any, options?: SpaceDetectionSyncOptions): () => void;
|
|
88
125
|
export declare function wallTouchesOthers(wall: WallNode, otherWalls: WallNode[]): boolean;
|
|
89
126
|
export {};
|
|
90
127
|
//# sourceMappingURL=space-detection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"space-detection.d.ts","sourceRoot":"","sources":["../../src/lib/space-detection.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,WAAW,IAAI,eAAe,EAGnC,KAAK,QAAQ,IAAI,YAAY,EAC7B,KAAK,QAAQ,EAEb,KAAK,QAAQ,IAAI,YAAY,EAC9B,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"space-detection.d.ts","sourceRoot":"","sources":["../../src/lib/space-detection.ts"],"names":[],"mappings":"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"}
|