@pascal-app/viewer 1.0.0-beta.5 → 1.0.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/capture/asset-url.d.ts +2 -0
- package/dist/capture/asset-url.d.ts.map +1 -0
- package/dist/capture/asset-url.js +13 -0
- package/dist/capture/capture-runtime.d.ts +43 -0
- package/dist/capture/capture-runtime.d.ts.map +1 -0
- package/dist/capture/capture-runtime.js +198 -0
- package/dist/capture/frame.d.ts +4 -0
- package/dist/capture/frame.d.ts.map +1 -0
- package/dist/capture/frame.js +21 -0
- package/dist/capture/index.d.ts +12 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +11 -0
- package/dist/capture/layer-visibility.d.ts +5 -0
- package/dist/capture/layer-visibility.d.ts.map +1 -0
- package/dist/capture/layer-visibility.js +11 -0
- package/dist/capture/layers/clay-matcap.d.ts +3 -0
- package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
- package/dist/capture/layers/clay-matcap.js +26 -0
- package/dist/capture/layers/device-motion-layer.d.ts +7 -0
- package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
- package/dist/capture/layers/device-motion-layer.js +76 -0
- package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
- package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
- package/dist/capture/layers/point-cloud-layer.js +106 -0
- package/dist/capture/layers/room-model-layer.d.ts +9 -0
- package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
- package/dist/capture/layers/room-model-layer.js +71 -0
- package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
- package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-data.js +74 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-layer.js +16 -0
- package/dist/capture/preview.d.ts +3 -0
- package/dist/capture/preview.d.ts.map +1 -0
- package/dist/capture/preview.js +2 -0
- package/dist/capture/source-state.d.ts +22 -0
- package/dist/capture/source-state.d.ts.map +1 -0
- package/dist/capture/source-state.js +182 -0
- package/dist/capture/stream-rendering.d.ts +14 -0
- package/dist/capture/stream-rendering.d.ts.map +1 -0
- package/dist/capture/stream-rendering.js +70 -0
- package/dist/capture/trajectory.d.ts +20 -0
- package/dist/capture/trajectory.d.ts.map +1 -0
- package/dist/capture/trajectory.js +77 -0
- package/dist/capture/use-json-artifact.d.ts +2 -0
- package/dist/capture/use-json-artifact.d.ts.map +1 -0
- package/dist/capture/use-json-artifact.js +42 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
- package/dist/components/viewer/batched-mesh-spike.js +64 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +4 -3
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +3 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +70 -62
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +123 -89
- package/dist/components/viewer/perf-monitor.d.ts +6 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +307 -116
- package/dist/components/viewer/perf-panel.d.ts +3 -0
- package/dist/components/viewer/perf-panel.d.ts.map +1 -0
- package/dist/components/viewer/perf-panel.js +178 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
- package/dist/components/viewer/pointer-raycast-layers.js +28 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +130 -83
- package/dist/components/viewer/registered-systems.d.ts +14 -5
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +22 -10
- package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
- package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
- package/dist/components/viewer/scene-atmosphere.js +89 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +19 -5
- package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
- package/dist/components/viewer/scene-ground-replacement.js +35 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +8 -16
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +4 -1
- package/dist/components/viewer/viewer-presentations.d.ts +67 -0
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
- package/dist/components/viewer/viewer-presentations.js +120 -0
- package/dist/hooks/use-library-materials-version.d.ts +7 -0
- package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
- package/dist/hooks/use-library-materials-version.js +10 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +1 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -7
- package/dist/lib/csg-utils.d.ts +1 -0
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +6 -1
- package/dist/lib/gpu-perf.d.ts +0 -2
- package/dist/lib/gpu-perf.d.ts.map +1 -1
- package/dist/lib/gpu-perf.js +7 -21
- package/dist/lib/isolation.d.ts +14 -30
- package/dist/lib/isolation.d.ts.map +1 -1
- package/dist/lib/isolation.js +62 -66
- package/dist/lib/layer-pass.d.ts +28 -0
- package/dist/lib/layer-pass.d.ts.map +1 -0
- package/dist/lib/layer-pass.js +254 -0
- package/dist/lib/materials.d.ts +12 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +52 -17
- package/dist/lib/merged-outline-node.d.ts +17 -8
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +194 -52
- package/dist/lib/perf-actions.d.ts +55 -0
- package/dist/lib/perf-actions.d.ts.map +1 -0
- package/dist/lib/perf-actions.js +211 -0
- package/dist/lib/perf-observers.d.ts +2 -0
- package/dist/lib/perf-observers.d.ts.map +1 -0
- package/dist/lib/perf-observers.js +17 -0
- package/dist/lib/perf-panel-store.d.ts +71 -0
- package/dist/lib/perf-panel-store.d.ts.map +1 -0
- package/dist/lib/perf-panel-store.js +29 -0
- package/dist/lib/perf-tracks.d.ts +43 -0
- package/dist/lib/perf-tracks.d.ts.map +1 -0
- package/dist/lib/perf-tracks.js +113 -0
- package/dist/lib/pointer-events.d.ts +7 -0
- package/dist/lib/pointer-events.d.ts.map +1 -0
- package/dist/lib/pointer-events.js +680 -0
- package/dist/lib/post-processing-resources.d.ts +12 -0
- package/dist/lib/post-processing-resources.d.ts.map +1 -0
- package/dist/lib/post-processing-resources.js +19 -0
- package/dist/lib/renderer-capability.d.ts.map +1 -1
- package/dist/lib/renderer-capability.js +4 -2
- package/dist/lib/scene-visibility.d.ts +2 -1
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +25 -2
- package/dist/lib/snapshot-pipeline.d.ts +3 -1
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +32 -16
- package/dist/store/use-viewer.d.ts +5 -3
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +2 -0
- package/dist/systems/ceiling/ceiling-system.js +1 -1
- package/dist/systems/door/door-animation-system.d.ts.map +1 -1
- package/dist/systems/door/door-animation-system.js +6 -9
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +13 -2
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +7 -3
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +7 -2
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +1 -1
- package/dist/systems/level/level-utils.d.ts +9 -0
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +14 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1196 -58
- package/dist/systems/scan/scan-system.js +6 -5
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +1 -2
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +9 -8
- package/dist/systems/surface-hole-geometry.d.ts +1 -1
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
- package/dist/systems/surface-hole-geometry.js +1 -1
- package/dist/systems/wall/level-miter-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
- package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
- package/dist/systems/wall/wall-build-lifecycle.js +74 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
- package/dist/systems/wall/wall-cutout-cache.js +304 -0
- package/dist/systems/wall/wall-cutout.d.ts +12 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +24 -161
- package/dist/systems/wall/wall-material-variant.d.ts +31 -0
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
- package/dist/systems/wall/wall-material-variant.js +10 -0
- package/dist/systems/wall/wall-materials.d.ts +6 -0
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +29 -13
- package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
- package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
- package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
- package/dist/systems/wall/wall-system.d.ts +11 -4
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +405 -153
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +9 -8
- package/dist/systems/window/window-system.d.ts +1 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +16 -2
- package/dist/systems/zone/zone-system.d.ts.map +1 -1
- package/dist/systems/zone/zone-system.js +28 -2
- package/package.json +17 -4
- package/dist/lib/polygon-union.d.ts +0 -4
- package/dist/lib/polygon-union.d.ts.map +0 -1
- package/dist/lib/polygon-union.js +0 -289
|
@@ -3,13 +3,19 @@ import { useFrame } from '@react-three/fiber';
|
|
|
3
3
|
import { useEffect } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
5
5
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
6
|
-
import { Brush, Evaluator, SUBTRACTION } from 'three-bvh-csg';
|
|
6
|
+
import { ADDITION, Brush, Evaluator, SUBTRACTION } from 'three-bvh-csg';
|
|
7
7
|
import { computeBoundsTree } from 'three-mesh-bvh';
|
|
8
8
|
import { ensureRenderableGeometryAttributes, prepareBrushForCSG } from '../../lib/csg-utils';
|
|
9
9
|
import { setGroupsSortedByMaterial } from '../../lib/geometry-groups';
|
|
10
|
+
import { timeSpan } from '../../lib/perf-tracks';
|
|
10
11
|
import { buildTerrainPerimeterFillGeometry } from '../../lib/terrain-perimeter-fill';
|
|
11
12
|
import { clearLevelMiterCache, getCachedLevelMiters } from './level-miter-cache';
|
|
12
13
|
import { buildOpeningCutoutGeometry, getOpeningCutoutBottomPadding, } from './opening-cutout-geometry';
|
|
14
|
+
import { drainStats, endInitialBuild, initiallyBuiltWalls, isWallInitialBuildActive, pendingAdjacentByLevel, publishWallDrainStats, } from './wall-build-lifecycle';
|
|
15
|
+
import { sweepUnbuiltWalls, WALL_PLACEHOLDER_SWEEP_INTERVAL } from './wall-placeholder-sweep';
|
|
16
|
+
import { notifyWallRebuilt } from './wall-rebuild-notifications';
|
|
17
|
+
export { isWallInitialBuildActive } from './wall-build-lifecycle';
|
|
18
|
+
export { drainRebuiltWalls } from './wall-rebuild-notifications';
|
|
13
19
|
// Reusable CSG evaluator for better performance
|
|
14
20
|
const csgEvaluator = new Evaluator();
|
|
15
21
|
csgEvaluator.attributes = ['position', 'normal', 'uv', 'uv2'];
|
|
@@ -43,6 +49,125 @@ function computeGeometryBoundsTree(geometry) {
|
|
|
43
49
|
function csgGeometry(brush) {
|
|
44
50
|
return brush.geometry;
|
|
45
51
|
}
|
|
52
|
+
function isBoxCutout(brush, bounds) {
|
|
53
|
+
const geometry = csgGeometry(brush);
|
|
54
|
+
const positions = geometry.getAttribute('position');
|
|
55
|
+
if ((geometry.index?.count ?? positions.count) !== 36)
|
|
56
|
+
return false;
|
|
57
|
+
const vertex = new THREE.Vector3();
|
|
58
|
+
const corners = new Set();
|
|
59
|
+
for (let index = 0; index < positions.count; index++) {
|
|
60
|
+
vertex.fromBufferAttribute(positions, index).applyMatrix4(brush.matrixWorld);
|
|
61
|
+
let corner = 0;
|
|
62
|
+
for (const [bit, axis] of ['x', 'y', 'z'].entries()) {
|
|
63
|
+
const coordinate = axis;
|
|
64
|
+
if (Math.abs(vertex[coordinate] - bounds.min[coordinate]) <= 1e-6)
|
|
65
|
+
continue;
|
|
66
|
+
if (Math.abs(vertex[coordinate] - bounds.max[coordinate]) > 1e-6)
|
|
67
|
+
return false;
|
|
68
|
+
corner |= 1 << bit;
|
|
69
|
+
}
|
|
70
|
+
corners.add(corner);
|
|
71
|
+
}
|
|
72
|
+
// A rotated box's AABB can contain another cutter without the solid doing so.
|
|
73
|
+
return corners.size === 8;
|
|
74
|
+
}
|
|
75
|
+
export function mergeWallCutoutBrushes(brushes) {
|
|
76
|
+
const cutouts = brushes.map((brush) => {
|
|
77
|
+
prepareBrushForCSG(brush);
|
|
78
|
+
const geometry = csgGeometry(brush);
|
|
79
|
+
geometry.computeBoundingBox();
|
|
80
|
+
const bounds = geometry.boundingBox.clone().applyMatrix4(brush.matrixWorld);
|
|
81
|
+
return {
|
|
82
|
+
brush,
|
|
83
|
+
bounds,
|
|
84
|
+
containerBounds: bounds.clone().expandByScalar(1e-5),
|
|
85
|
+
isBox: isBoxCutout(brush, bounds),
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
const retained = [];
|
|
89
|
+
for (const cutout of cutouts) {
|
|
90
|
+
if (cutout.isBox) {
|
|
91
|
+
if (retained.some((other) => other.isBox && other.containerBounds.containsBox(cutout.bounds))) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
for (let index = retained.length - 1; index >= 0; index--) {
|
|
95
|
+
const other = retained[index];
|
|
96
|
+
if (other.isBox && cutout.containerBounds.containsBox(other.bounds)) {
|
|
97
|
+
retained.splice(index, 1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
retained.push(cutout);
|
|
102
|
+
}
|
|
103
|
+
const droppedCount = cutouts.length - retained.length;
|
|
104
|
+
const bounds = retained.map((cutout) => cutout.bounds.clone().expandByScalar(1e-6));
|
|
105
|
+
const parents = retained.map((_, index) => index);
|
|
106
|
+
const root = (index) => {
|
|
107
|
+
while (parents[index] !== index) {
|
|
108
|
+
parents[index] = parents[parents[index]];
|
|
109
|
+
index = parents[index];
|
|
110
|
+
}
|
|
111
|
+
return index;
|
|
112
|
+
};
|
|
113
|
+
for (let a = 0; a < retained.length; a++) {
|
|
114
|
+
for (let b = a + 1; b < retained.length; b++) {
|
|
115
|
+
if (bounds[a].intersectsBox(bounds[b]))
|
|
116
|
+
parents[root(b)] = root(a);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const groups = new Map();
|
|
120
|
+
retained.forEach(({ brush }, index) => {
|
|
121
|
+
const key = root(index);
|
|
122
|
+
const group = groups.get(key) ?? [];
|
|
123
|
+
group.push(brush);
|
|
124
|
+
groups.set(key, group);
|
|
125
|
+
});
|
|
126
|
+
const geometries = [];
|
|
127
|
+
const intermediateGeometries = new Set();
|
|
128
|
+
const fallbackBrushes = [];
|
|
129
|
+
try {
|
|
130
|
+
for (const group of groups.values()) {
|
|
131
|
+
// Long unions of coplanar openings can grow explosively; subtract these directly.
|
|
132
|
+
if (group.length > 4) {
|
|
133
|
+
fallbackBrushes.push(...group);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
let result = group[0];
|
|
137
|
+
for (let index = 1; index < group.length; index++) {
|
|
138
|
+
const next = csgEvaluator.evaluate(result, group[index], ADDITION);
|
|
139
|
+
intermediateGeometries.add(csgGeometry(next));
|
|
140
|
+
if (intermediateGeometries.delete(csgGeometry(result)))
|
|
141
|
+
csgGeometry(result).dispose();
|
|
142
|
+
result = next;
|
|
143
|
+
}
|
|
144
|
+
const source = csgGeometry(result);
|
|
145
|
+
const geometry = source.index ? source.toNonIndexed() : source.clone();
|
|
146
|
+
geometries.push(geometry);
|
|
147
|
+
geometry.applyMatrix4(result.matrixWorld);
|
|
148
|
+
for (const attribute of Object.keys(geometry.attributes)) {
|
|
149
|
+
if (!csgEvaluator.attributes.includes(attribute))
|
|
150
|
+
geometry.deleteAttribute(attribute);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (geometries.length === 0)
|
|
154
|
+
return { cutter: null, fallbackBrushes, droppedCount };
|
|
155
|
+
// CSG material indices are temporary: assignWallMaterialGroups classifies
|
|
156
|
+
// the final faces, including reveals, into the wall's semantic slots.
|
|
157
|
+
const merged = mergeGeometries(geometries, false);
|
|
158
|
+
if (!merged)
|
|
159
|
+
throw new Error('Unable to merge wall cutout geometries');
|
|
160
|
+
const cutter = new Brush(merged);
|
|
161
|
+
prepareBrushForCSG(cutter);
|
|
162
|
+
return { cutter, fallbackBrushes, droppedCount };
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
for (const geometry of geometries)
|
|
166
|
+
geometry.dispose();
|
|
167
|
+
for (const geometry of intermediateGeometries)
|
|
168
|
+
geometry.dispose();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
46
171
|
function insetCurvedWallBoundaryPointsFor3D(wall, boundaryPoints, miterData) {
|
|
47
172
|
if (!(boundaryPoints && isCurvedWall(wall))) {
|
|
48
173
|
return boundaryPoints;
|
|
@@ -335,168 +460,275 @@ const DRAG_FLUSH_MS = 80;
|
|
|
335
460
|
const MAX_WALL_REBUILDS_PER_FRAME = 8;
|
|
336
461
|
const WALL_PROGRESSIVE_DIRTY_THRESHOLD = MAX_WALL_REBUILDS_PER_FRAME;
|
|
337
462
|
const WALL_PROGRESSIVE_TIME_BUDGET_MS = 8;
|
|
463
|
+
const HEAVY_WALL_OPENINGS = 6;
|
|
338
464
|
let lastWallDirtyAtMs = 0;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
465
|
+
let unmountedFrames = 0;
|
|
466
|
+
let stalledHydrationToken = null;
|
|
467
|
+
function wallRebuildExitReason(wallId, nodes, rebuiltThisFrame, elapsedMs, initialBuild = false) {
|
|
468
|
+
if (!initialBuild && rebuiltThisFrame >= MAX_WALL_REBUILDS_PER_FRAME)
|
|
469
|
+
return 'cap';
|
|
470
|
+
if (rebuiltThisFrame === 0)
|
|
471
|
+
return null;
|
|
472
|
+
if (elapsedMs >= WALL_PROGRESSIVE_TIME_BUDGET_MS)
|
|
473
|
+
return 'budget';
|
|
474
|
+
const wall = nodes[wallId];
|
|
475
|
+
if (wall?.type !== 'wall')
|
|
476
|
+
return null;
|
|
477
|
+
let cutouts = 0;
|
|
478
|
+
for (const childId of getEffectiveWall(wall).children ?? []) {
|
|
479
|
+
const child = nodes[childId];
|
|
480
|
+
if (child?.type === 'door' ||
|
|
481
|
+
child?.type === 'window' ||
|
|
482
|
+
(child?.type === 'item' &&
|
|
483
|
+
sceneRegistry.nodes.get(childId)?.getObjectByName('cutout')?.geometry?.getAttribute('position')?.count)) {
|
|
484
|
+
cutouts++;
|
|
485
|
+
if (cutouts >= HEAVY_WALL_OPENINGS)
|
|
486
|
+
return 'heavy';
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
export function shouldDeferWallRebuild(wallId, nodes, rebuiltThisFrame, elapsedMs) {
|
|
492
|
+
return wallRebuildExitReason(wallId, nodes, rebuiltThisFrame, elapsedMs) !== null;
|
|
352
493
|
}
|
|
353
494
|
/** Rebuilds this system still owes — neighbours deferred during a drag. */
|
|
354
495
|
export function getPendingWallRebuildCount() {
|
|
355
|
-
|
|
356
|
-
for (const ids of pendingAdjacentByLevel.values()) {
|
|
357
|
-
count += ids.size;
|
|
358
|
-
}
|
|
359
|
-
return count;
|
|
496
|
+
return drainStats.pendingNeighbours;
|
|
360
497
|
}
|
|
498
|
+
let placeholderSweepCountdown = WALL_PLACEHOLDER_SWEEP_INTERVAL;
|
|
361
499
|
export const WallSystem = () => {
|
|
362
|
-
|
|
363
|
-
const clearDirty = useScene((state) => state.clearDirty);
|
|
364
|
-
// Subscribe so override-only changes (no scene write) still re-run
|
|
365
|
-
// this component, which lets the gate below pick up the latest
|
|
366
|
-
// `dirtyNodes` set from the same render pass that received the
|
|
367
|
-
// override-publishing `markDirty` call. Without this, very fast
|
|
368
|
-
// drags could land an override and a markDirty in the same React
|
|
369
|
-
// tick and the next `useFrame` would still see the stale closure.
|
|
500
|
+
useScene((state) => state.dirtyNodes);
|
|
370
501
|
useLiveNodeOverrides((s) => s.overrides);
|
|
371
|
-
// The miter cache is module-level, so it outlives this mount. Editor
|
|
372
|
-
// teardown resets the other shared singletons; without the same reset here a
|
|
373
|
-
// remount in the same tab keeps every previous level's walls reachable.
|
|
374
502
|
useEffect(() => () => clearLevelMiterCache(), []);
|
|
375
|
-
useFrame(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
503
|
+
useFrame(runWallBuildFrame, 4);
|
|
504
|
+
return null;
|
|
505
|
+
};
|
|
506
|
+
export function runWallBuildFrame() {
|
|
507
|
+
const initialBuild = isWallInitialBuildActive();
|
|
508
|
+
const token = useScene.getState().hydrationToken;
|
|
509
|
+
if (token !== stalledHydrationToken) {
|
|
510
|
+
unmountedFrames = 0;
|
|
511
|
+
stalledHydrationToken = token;
|
|
512
|
+
}
|
|
513
|
+
drainStats.wallsConsumedThisFrame = 0;
|
|
514
|
+
try {
|
|
515
|
+
consumeWallBuildFrame(initialBuild);
|
|
516
|
+
}
|
|
517
|
+
finally {
|
|
518
|
+
publishWallDrainStats();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function consumeWallBuildFrame(initialBuild) {
|
|
522
|
+
const clearDirty = useScene.getState().clearDirty;
|
|
523
|
+
// Self-heal: any registered wall still on its mount-time placeholder
|
|
524
|
+
// geometry with NO dirty mark gets re-marked, so a lost mark (system
|
|
525
|
+
// mounted late, suspense remount, mark consumed elsewhere) can never
|
|
526
|
+
// strand a wall as a degenerate point forever (QA f2 probe5/probe6 —
|
|
527
|
+
// scene loaded with the X-ray active never built any of its 24 walls).
|
|
528
|
+
placeholderSweepCountdown -= 1;
|
|
529
|
+
if (placeholderSweepCountdown <= 0) {
|
|
530
|
+
placeholderSweepCountdown = WALL_PLACEHOLDER_SWEEP_INTERVAL;
|
|
531
|
+
const sceneState = useScene.getState();
|
|
532
|
+
sweepUnbuiltWalls({
|
|
533
|
+
wallIds: sceneRegistry.byType.wall ?? [],
|
|
534
|
+
geometryOf: (wallId) => sceneRegistry.nodes.get(wallId)?.geometry ?? null,
|
|
535
|
+
isDirty: (wallId) => sceneState.dirtyNodes.has(wallId),
|
|
536
|
+
markDirty: (wallId) => sceneState.markDirty(wallId),
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
const dirtyNodes = useScene.getState().dirtyNodes;
|
|
540
|
+
const hasDirty = dirtyNodes.size > 0;
|
|
541
|
+
const hasPending = pendingAdjacentByLevel.size > 0;
|
|
542
|
+
if (!hasDirty && !hasPending) {
|
|
543
|
+
endInitialBuild();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const nodes = useScene.getState().nodes;
|
|
547
|
+
const now = performance.now();
|
|
548
|
+
// Collect dirty walls and their levels
|
|
549
|
+
const dirtyWallsByLevel = new Map();
|
|
550
|
+
let dirtyWallCount = 0;
|
|
551
|
+
let unmountedWallCount = 0;
|
|
552
|
+
useFrameNb += 1;
|
|
553
|
+
if (hasDirty) {
|
|
554
|
+
dirtyNodes.forEach((id) => {
|
|
555
|
+
const node = nodes[id];
|
|
556
|
+
if (node?.type !== 'wall')
|
|
557
|
+
return;
|
|
558
|
+
dirtyWallCount += 1;
|
|
559
|
+
if (!sceneRegistry.nodes.has(id))
|
|
560
|
+
unmountedWallCount++;
|
|
561
|
+
const levelId = node.parentId;
|
|
562
|
+
if (!levelId)
|
|
563
|
+
return;
|
|
564
|
+
if (!dirtyWallsByLevel.has(levelId)) {
|
|
565
|
+
dirtyWallsByLevel.set(levelId, new Set());
|
|
566
|
+
}
|
|
567
|
+
dirtyWallsByLevel.get(levelId)?.add(id);
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
const hasDirtyWalls = dirtyWallCount > unmountedWallCount;
|
|
571
|
+
if (hasDirtyWalls) {
|
|
572
|
+
lastWallDirtyAtMs = now;
|
|
573
|
+
}
|
|
574
|
+
const useProgressiveWallRebuilds = initialBuild || dirtyWallCount > WALL_PROGRESSIVE_DIRTY_THRESHOLD;
|
|
575
|
+
let rebuiltWallsThisFrame = 0;
|
|
576
|
+
const rebuildFrameStartedAt = now;
|
|
577
|
+
let deferWallRebuilds = false;
|
|
578
|
+
let exitReason = null;
|
|
579
|
+
// Process each level that has dirty walls
|
|
580
|
+
for (const [levelId, dirtyWallIds] of dirtyWallsByLevel) {
|
|
581
|
+
if (!initialBuild &&
|
|
582
|
+
useProgressiveWallRebuilds &&
|
|
583
|
+
rebuiltWallsThisFrame >= MAX_WALL_REBUILDS_PER_FRAME) {
|
|
584
|
+
exitReason = 'cap';
|
|
585
|
+
break;
|
|
404
586
|
}
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
587
|
+
const levelWalls = getLevelWalls(levelId);
|
|
588
|
+
const miterData = timeSpan('wall-miter', () => getCachedLevelMiters(levelId, levelWalls));
|
|
589
|
+
const rebuiltWallIds = new Set();
|
|
590
|
+
// Update dirty walls — always, no throttling. The dragged wall must
|
|
591
|
+
// follow the cursor with full fidelity (cutouts and all). Large imports
|
|
592
|
+
// enter the progressive path so initial load can't lock the tab.
|
|
593
|
+
for (const wallId of dirtyWallIds) {
|
|
594
|
+
exitReason = useProgressiveWallRebuilds
|
|
595
|
+
? wallRebuildExitReason(wallId, nodes, rebuiltWallsThisFrame, performance.now() - rebuildFrameStartedAt, initialBuild)
|
|
596
|
+
: null;
|
|
597
|
+
if (exitReason) {
|
|
598
|
+
deferWallRebuilds = true;
|
|
411
599
|
break;
|
|
412
600
|
}
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if (rebuiltWallsThisFrame > 0 &&
|
|
425
|
-
performance.now() - rebuildFrameStartedAt >= WALL_PROGRESSIVE_TIME_BUDGET_MS) {
|
|
426
|
-
break;
|
|
427
|
-
}
|
|
601
|
+
const mesh = sceneRegistry.nodes.get(wallId);
|
|
602
|
+
if (mesh) {
|
|
603
|
+
timeSpan('wall-rebuild', () => updateWallGeometry(wallId, miterData), {
|
|
604
|
+
properties: [['node', wallId]],
|
|
605
|
+
});
|
|
606
|
+
clearDirty(wallId);
|
|
607
|
+
notifyWallRebuilt(wallId);
|
|
608
|
+
const firstBuild = !initiallyBuiltWalls.has(wallId);
|
|
609
|
+
if (firstBuild) {
|
|
610
|
+
initiallyBuiltWalls.add(wallId);
|
|
611
|
+
drainStats.firstBuilds++;
|
|
428
612
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
rebuiltWalls.add(wallId);
|
|
613
|
+
else {
|
|
614
|
+
drainStats.reinvalidationBuilds++;
|
|
615
|
+
}
|
|
616
|
+
if (!initialBuild || !firstBuild)
|
|
434
617
|
rebuiltWallIds.add(wallId);
|
|
435
|
-
|
|
618
|
+
rebuiltWallsThisFrame += 1;
|
|
619
|
+
drainStats.wallsConsumedThisFrame++;
|
|
620
|
+
if (initialBuild && wallRebuildExitReason(wallId, nodes, 1, 0, true) === 'heavy') {
|
|
621
|
+
exitReason = 'heavy';
|
|
622
|
+
deferWallRebuilds = true;
|
|
623
|
+
break;
|
|
436
624
|
}
|
|
437
|
-
// If mesh not found, keep it dirty for next frame
|
|
438
|
-
}
|
|
439
|
-
if (rebuiltWallIds.size === 0) {
|
|
440
|
-
continue;
|
|
441
625
|
}
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
626
|
+
// If mesh not found, keep it dirty for next frame
|
|
627
|
+
}
|
|
628
|
+
if (rebuiltWallIds.size === 0) {
|
|
629
|
+
if (deferWallRebuilds)
|
|
630
|
+
break;
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
// First builds use the same hydrated inputs as every queued neighbour.
|
|
634
|
+
// Only subsequent invalidations need the adjacency scan and trailing flush.
|
|
635
|
+
// Adjacent walls sharing junctions — *defer* during active drag
|
|
636
|
+
// (dirty arrived this frame), flush on the trailing edge.
|
|
637
|
+
const adjacentWallIds = getAdjacentWallIds(levelWalls, rebuiltWallIds);
|
|
638
|
+
let pending = pendingAdjacentByLevel.get(levelId);
|
|
639
|
+
if (!pending) {
|
|
640
|
+
pending = new Set();
|
|
641
|
+
pendingAdjacentByLevel.set(levelId, pending);
|
|
642
|
+
}
|
|
643
|
+
for (const wallId of adjacentWallIds) {
|
|
644
|
+
if (!dirtyWallIds.has(wallId) && !pending.has(wallId)) {
|
|
645
|
+
pending.add(wallId);
|
|
646
|
+
drainStats.pendingNeighbours++;
|
|
647
|
+
drainStats.neighbourEnqueues++;
|
|
454
648
|
}
|
|
455
649
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
rebuiltWalls.add(wallId);
|
|
484
|
-
}
|
|
485
|
-
pendingIds.delete(wallId);
|
|
486
|
-
rebuiltAdjacentThisFrame += 1;
|
|
650
|
+
if (pending.size === 0)
|
|
651
|
+
pendingAdjacentByLevel.delete(levelId);
|
|
652
|
+
if (deferWallRebuilds)
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
// Trailing-edge flush: if no new dirty marks for DRAG_FLUSH_MS, the
|
|
656
|
+
// drag has ended — rebuild the queued neighbors so corners snap into
|
|
657
|
+
// their correct miter joins.
|
|
658
|
+
const quiet = !hasDirtyWalls && now - lastWallDirtyAtMs >= DRAG_FLUSH_MS;
|
|
659
|
+
if (quiet && pendingAdjacentByLevel.size > 0) {
|
|
660
|
+
const pendingCount = getPendingWallRebuildCount();
|
|
661
|
+
const useProgressiveAdjacentRebuilds = initialBuild || pendingCount > WALL_PROGRESSIVE_DIRTY_THRESHOLD;
|
|
662
|
+
let rebuiltAdjacentThisFrame = 0;
|
|
663
|
+
const adjacentFrameStartedAt = performance.now();
|
|
664
|
+
let deferAdjacentRebuilds = false;
|
|
665
|
+
for (const [levelId, pendingIds] of pendingAdjacentByLevel) {
|
|
666
|
+
if (pendingIds.size === 0)
|
|
667
|
+
continue;
|
|
668
|
+
const levelWalls = getLevelWalls(levelId);
|
|
669
|
+
const miterData = timeSpan('wall-miter', () => getCachedLevelMiters(levelId, levelWalls));
|
|
670
|
+
for (const wallId of Array.from(pendingIds)) {
|
|
671
|
+
exitReason = useProgressiveAdjacentRebuilds
|
|
672
|
+
? wallRebuildExitReason(wallId, nodes, rebuiltAdjacentThisFrame, performance.now() - adjacentFrameStartedAt, initialBuild)
|
|
673
|
+
: null;
|
|
674
|
+
if (exitReason) {
|
|
675
|
+
deferAdjacentRebuilds = true;
|
|
676
|
+
break;
|
|
487
677
|
}
|
|
488
|
-
|
|
489
|
-
|
|
678
|
+
const mesh = sceneRegistry.nodes.get(wallId);
|
|
679
|
+
if (mesh) {
|
|
680
|
+
timeSpan('wall-rebuild', () => updateWallGeometry(wallId, miterData), {
|
|
681
|
+
properties: [['node', wallId]],
|
|
682
|
+
});
|
|
683
|
+
notifyWallRebuilt(wallId);
|
|
684
|
+
drainStats.wallsConsumedThisFrame++;
|
|
685
|
+
if (initiallyBuiltWalls.has(wallId))
|
|
686
|
+
drainStats.reinvalidationBuilds++;
|
|
687
|
+
else {
|
|
688
|
+
initiallyBuiltWalls.add(wallId);
|
|
689
|
+
drainStats.firstBuilds++;
|
|
690
|
+
}
|
|
490
691
|
}
|
|
491
|
-
|
|
492
|
-
|
|
692
|
+
pendingIds.delete(wallId);
|
|
693
|
+
drainStats.pendingNeighbours--;
|
|
694
|
+
rebuiltAdjacentThisFrame += 1;
|
|
695
|
+
if (initialBuild && wallRebuildExitReason(wallId, nodes, 1, 0, true) === 'heavy') {
|
|
696
|
+
exitReason = 'heavy';
|
|
697
|
+
deferAdjacentRebuilds = true;
|
|
493
698
|
break;
|
|
494
699
|
}
|
|
495
700
|
}
|
|
701
|
+
if (pendingIds.size === 0) {
|
|
702
|
+
pendingAdjacentByLevel.delete(levelId);
|
|
703
|
+
}
|
|
704
|
+
if (deferAdjacentRebuilds ||
|
|
705
|
+
(!initialBuild &&
|
|
706
|
+
useProgressiveAdjacentRebuilds &&
|
|
707
|
+
rebuiltAdjacentThisFrame >= MAX_WALL_REBUILDS_PER_FRAME)) {
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
496
710
|
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
|
|
711
|
+
}
|
|
712
|
+
if (initialBuild && drainStats.wallsConsumedThisFrame === 0 && unmountedWallCount > 0) {
|
|
713
|
+
unmountedFrames++;
|
|
714
|
+
if (unmountedFrames >= WALL_PLACEHOLDER_SWEEP_INTERVAL) {
|
|
715
|
+
useScene.getState().invalidateHydration();
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
else
|
|
719
|
+
unmountedFrames = 0;
|
|
720
|
+
if (exitReason === 'budget')
|
|
721
|
+
drainStats.budgetExits++;
|
|
722
|
+
else if (exitReason === 'heavy')
|
|
723
|
+
drainStats.heavyExits++;
|
|
724
|
+
else if (exitReason === 'cap')
|
|
725
|
+
drainStats.capExits++;
|
|
726
|
+
if (dirtyWallCount === rebuiltWallsThisFrame && drainStats.pendingNeighbours === 0) {
|
|
727
|
+
if (drainStats.wallsConsumedThisFrame > 0 || drainStats.initialBuildActive)
|
|
728
|
+
drainStats.drainedExits++;
|
|
729
|
+
endInitialBuild();
|
|
730
|
+
}
|
|
731
|
+
}
|
|
500
732
|
/**
|
|
501
733
|
* Merge any live override for a wall into the scene record. Lets the
|
|
502
734
|
* 2D move handler publish `{ start, end, curveOffset }` to
|
|
@@ -581,6 +813,9 @@ function updateWallGeometry(wallId, miterData) {
|
|
|
581
813
|
const wallWorldMatrix = new THREE.Matrix4().compose(new THREE.Vector3(node.start[0], slabElevation, node.start[1]), new THREE.Quaternion().setFromAxisAngle(WALL_UV_Y_AXIS, -wallAngle), WALL_UV_UNIT_SCALE);
|
|
582
814
|
const newGeo = applyWorldPlanarWallUVs(builtGeo, wallWorldMatrix);
|
|
583
815
|
mesh.geometry.dispose();
|
|
816
|
+
// A degenerate rebuild (zero-length or fully cut wall) yields as few vertices
|
|
817
|
+
// as the mount-time placeholder; the stamp keeps the sweep from re-marking it.
|
|
818
|
+
newGeo.userData.built = true;
|
|
584
819
|
mesh.geometry = newGeo;
|
|
585
820
|
// Update collision mesh
|
|
586
821
|
const collisionMesh = mesh.getObjectByName('collision-mesh');
|
|
@@ -857,7 +1092,6 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
|
|
|
857
1092
|
computeGeometryBoundsTree(cutoutGeometry);
|
|
858
1093
|
baseProfileCutouts.push(new Brush(cutoutGeometry));
|
|
859
1094
|
}
|
|
860
|
-
// Apply base-profile and opening cutouts in one CSG pass.
|
|
861
1095
|
const cutoutBrushes = [
|
|
862
1096
|
...baseProfileCutouts,
|
|
863
1097
|
...collectCutoutBrushes(wallNode, childrenNodes, thickness),
|
|
@@ -875,21 +1109,39 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
|
|
|
875
1109
|
computeGeometryBoundsTree(geometry);
|
|
876
1110
|
const wallBrush = new Brush(geometry);
|
|
877
1111
|
wallBrush.updateMatrixWorld();
|
|
878
|
-
|
|
1112
|
+
let mergedCutter = null;
|
|
879
1113
|
let resultBrush = wallBrush;
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
1114
|
+
try {
|
|
1115
|
+
const properties = [];
|
|
1116
|
+
const merged = timeSpan('wall-csg-union', () => {
|
|
1117
|
+
const cutouts = mergeWallCutoutBrushes(cutoutBrushes);
|
|
1118
|
+
properties.push(['droppedCutouts', String(cutouts.droppedCount)]);
|
|
1119
|
+
return cutouts;
|
|
1120
|
+
}, { properties });
|
|
1121
|
+
mergedCutter = merged.cutter;
|
|
1122
|
+
timeSpan('wall-csg', () => {
|
|
1123
|
+
if (mergedCutter) {
|
|
1124
|
+
resultBrush = csgEvaluator.evaluate(resultBrush, mergedCutter, SUBTRACTION);
|
|
1125
|
+
}
|
|
1126
|
+
for (const cutter of merged.fallbackBrushes) {
|
|
1127
|
+
const next = csgEvaluator.evaluate(resultBrush, cutter, SUBTRACTION);
|
|
1128
|
+
if (resultBrush !== wallBrush)
|
|
1129
|
+
csgGeometry(resultBrush).dispose();
|
|
1130
|
+
resultBrush = next;
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
catch (error) {
|
|
1135
|
+
if (resultBrush !== wallBrush)
|
|
885
1136
|
csgGeometry(resultBrush).dispose();
|
|
886
|
-
|
|
887
|
-
resultBrush = newResult;
|
|
1137
|
+
throw error;
|
|
888
1138
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1139
|
+
finally {
|
|
1140
|
+
csgGeometry(wallBrush).dispose();
|
|
1141
|
+
if (mergedCutter)
|
|
1142
|
+
csgGeometry(mergedCutter).dispose();
|
|
1143
|
+
for (const brush of cutoutBrushes)
|
|
1144
|
+
csgGeometry(brush).dispose();
|
|
893
1145
|
}
|
|
894
1146
|
const resultGeometry = csgGeometry(resultBrush);
|
|
895
1147
|
const splitResultGeometry = splitGeometryAtHorizontalPlanes(resultGeometry, getWallBandSplitPlanes(wallNode, effectiveWallHeight));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"window-animation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/window/window-animation-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"window-animation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/window/window-animation-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAiBrC;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,GACZ,OAAO,CAqFT;AAQD,eAAO,MAAM,qBAAqB,YAoDjC,CAAA"}
|