@pascal-app/viewer 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 +107 -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 +228 -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 +78 -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 +69 -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/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 +11 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +59 -18
- package/dist/components/viewer/index.d.ts +15 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +74 -82
- 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 +306 -115
- 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 +124 -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 +7 -2
- package/dist/components/viewer/unsupported-gpu-fallback.d.ts +2 -0
- package/dist/components/viewer/unsupported-gpu-fallback.d.ts.map +1 -0
- package/dist/components/viewer/unsupported-gpu-fallback.js +4 -0
- 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 +20 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -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/dispose-object3d.d.ts +4 -0
- package/dist/lib/dispose-object3d.d.ts.map +1 -0
- package/dist/lib/dispose-object3d.js +27 -0
- package/dist/lib/geometry-groups.d.ts +17 -0
- package/dist/lib/geometry-groups.d.ts.map +1 -0
- package/dist/lib/geometry-groups.js +53 -0
- 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 +8 -8
- package/dist/lib/isolation.d.ts.map +1 -1
- package/dist/lib/isolation.js +18 -30
- 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/layers.d.ts +16 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +18 -0
- package/dist/lib/materials.d.ts +12 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +51 -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 +56 -0
- package/dist/lib/renderer-capability.d.ts.map +1 -0
- package/dist/lib/renderer-capability.js +116 -0
- package/dist/lib/scene-visibility.d.ts +15 -0
- package/dist/lib/scene-visibility.d.ts.map +1 -0
- package/dist/lib/scene-visibility.js +69 -0
- package/dist/lib/shadow-only.d.ts +16 -0
- package/dist/lib/shadow-only.d.ts.map +1 -1
- package/dist/lib/shadow-only.js +8 -17
- package/dist/lib/snapshot-pipeline.d.ts +9 -0
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +33 -9
- package/dist/store/use-viewer.d.ts +7 -6
- package/dist/store/use-viewer.d.ts.map +1 -1
- 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 +9 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +5 -28
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +8 -2
- 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 +10 -0
- package/dist/systems/wall/level-miter-cache.d.ts.map +1 -0
- package/dist/systems/wall/level-miter-cache.js +40 -0
- 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 +24 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +33 -140
- 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 +13 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +413 -148
- 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/package.json +18 -3
- package/dist/components/viewer/scene-bvh.test.d.ts +0 -2
- package/dist/components/viewer/scene-bvh.test.d.ts.map +0 -1
- package/dist/components/viewer/scene-bvh.test.js +0 -13
- package/dist/lib/csg-utils.test.d.ts +0 -2
- package/dist/lib/csg-utils.test.d.ts.map +0 -1
- package/dist/lib/csg-utils.test.js +0 -45
- package/dist/lib/materials.test.d.ts +0 -2
- package/dist/lib/materials.test.d.ts.map +0 -1
- package/dist/lib/materials.test.js +0 -25
- package/dist/lib/merged-outline-node.test.d.ts +0 -2
- package/dist/lib/merged-outline-node.test.d.ts.map +0 -1
- package/dist/lib/merged-outline-node.test.js +0 -22
- 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
- package/dist/lib/polygon-union.test.d.ts +0 -2
- package/dist/lib/polygon-union.test.d.ts.map +0 -1
- package/dist/lib/polygon-union.test.js +0 -105
- package/dist/lib/texture-reference.test.d.ts +0 -2
- package/dist/lib/texture-reference.test.d.ts.map +0 -1
- package/dist/lib/texture-reference.test.js +0 -86
- package/dist/store/use-viewer.test.d.ts +0 -2
- package/dist/store/use-viewer.test.d.ts.map +0 -1
- package/dist/store/use-viewer.test.js +0 -61
- package/dist/systems/door/door-animation.test.d.ts +0 -2
- package/dist/systems/door/door-animation.test.d.ts.map +0 -1
- package/dist/systems/door/door-animation.test.js +0 -51
- package/dist/systems/door/door-floor-alignment.test.d.ts +0 -2
- package/dist/systems/door/door-floor-alignment.test.d.ts.map +0 -1
- package/dist/systems/door/door-floor-alignment.test.js +0 -50
- package/dist/systems/geometry/geometry-system.test.d.ts +0 -2
- package/dist/systems/geometry/geometry-system.test.d.ts.map +0 -1
- package/dist/systems/geometry/geometry-system.test.js +0 -30
- package/dist/systems/slab/slab-system.test.d.ts +0 -2
- package/dist/systems/slab/slab-system.test.d.ts.map +0 -1
- package/dist/systems/slab/slab-system.test.js +0 -102
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts +0 -2
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +0 -1
- package/dist/systems/wall/opening-cutout-geometry.test.js +0 -172
- package/dist/systems/wall/wall-opening-cutout.test.d.ts +0 -2
- package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-opening-cutout.test.js +0 -59
- package/dist/systems/wall/wall-support-extension.test.d.ts +0 -2
- package/dist/systems/wall/wall-support-extension.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-support-extension.test.js +0 -116
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts +0 -2
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-terrain-bottom.test.js +0 -34
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DEFAULT_LEVEL_HEIGHT, getAdjacentWallIds, getEffectiveNode, getWallBandSlotId, getWallCurveFrameAt, getWallFaceBandConfig, getWallFaceBandForHeight, getWallMiterBoundaryPoints, getWallPlaneTop, getWallPlanFootprint, getWallSurfacePolygon, getWallThickness, isCurvedWall, pointToKey, resolveLevelId, resolveWallTop, sceneRegistry, spatialGridManager, terrainSupportLift, useLiveNodeOverrides, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
|
+
import { useEffect } from 'react';
|
|
3
4
|
import * as THREE from 'three';
|
|
4
5
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
5
|
-
import { Brush, Evaluator, SUBTRACTION } from 'three-bvh-csg';
|
|
6
|
+
import { ADDITION, Brush, Evaluator, SUBTRACTION } from 'three-bvh-csg';
|
|
6
7
|
import { computeBoundsTree } from 'three-mesh-bvh';
|
|
7
8
|
import { ensureRenderableGeometryAttributes, prepareBrushForCSG } from '../../lib/csg-utils';
|
|
9
|
+
import { setGroupsSortedByMaterial } from '../../lib/geometry-groups';
|
|
10
|
+
import { timeSpan } from '../../lib/perf-tracks';
|
|
8
11
|
import { buildTerrainPerimeterFillGeometry } from '../../lib/terrain-perimeter-fill';
|
|
12
|
+
import { clearLevelMiterCache, getCachedLevelMiters } from './level-miter-cache';
|
|
9
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';
|
|
10
19
|
// Reusable CSG evaluator for better performance
|
|
11
20
|
const csgEvaluator = new Evaluator();
|
|
12
21
|
csgEvaluator.attributes = ['position', 'normal', 'uv', 'uv2'];
|
|
@@ -40,6 +49,125 @@ function computeGeometryBoundsTree(geometry) {
|
|
|
40
49
|
function csgGeometry(brush) {
|
|
41
50
|
return brush.geometry;
|
|
42
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
|
+
}
|
|
43
171
|
function insetCurvedWallBoundaryPointsFor3D(wall, boundaryPoints, miterData) {
|
|
44
172
|
if (!(boundaryPoints && isCurvedWall(wall))) {
|
|
45
173
|
return boundaryPoints;
|
|
@@ -210,18 +338,7 @@ function assignWallMaterialGroups(geometry, wall, boundaryEdges, effectiveWallHe
|
|
|
210
338
|
}
|
|
211
339
|
triangleMaterials[triangleIndex] = getWallFaceMaterialIndex(wall, nearestTag, centroid.y, effectiveWallHeight);
|
|
212
340
|
}
|
|
213
|
-
geometry
|
|
214
|
-
let currentMaterial = triangleMaterials[0] ?? 0;
|
|
215
|
-
let groupStart = 0;
|
|
216
|
-
for (let triangleIndex = 1; triangleIndex < triangleCount; triangleIndex += 1) {
|
|
217
|
-
const materialIndex = triangleMaterials[triangleIndex] ?? 0;
|
|
218
|
-
if (materialIndex === currentMaterial)
|
|
219
|
-
continue;
|
|
220
|
-
geometry.addGroup(groupStart * 3, (triangleIndex - groupStart) * 3, currentMaterial);
|
|
221
|
-
groupStart = triangleIndex;
|
|
222
|
-
currentMaterial = materialIndex;
|
|
223
|
-
}
|
|
224
|
-
geometry.addGroup(groupStart * 3, (triangleCount - groupStart) * 3, currentMaterial);
|
|
341
|
+
setGroupsSortedByMaterial(geometry, triangleMaterials);
|
|
225
342
|
}
|
|
226
343
|
function interpolateSplitVertex(a, b, t) {
|
|
227
344
|
return {
|
|
@@ -343,147 +460,275 @@ const DRAG_FLUSH_MS = 80;
|
|
|
343
460
|
const MAX_WALL_REBUILDS_PER_FRAME = 8;
|
|
344
461
|
const WALL_PROGRESSIVE_DIRTY_THRESHOLD = MAX_WALL_REBUILDS_PER_FRAME;
|
|
345
462
|
const WALL_PROGRESSIVE_TIME_BUDGET_MS = 8;
|
|
463
|
+
const HEAVY_WALL_OPENINGS = 6;
|
|
346
464
|
let lastWallDirtyAtMs = 0;
|
|
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
|
+
}
|
|
352
488
|
}
|
|
353
|
-
return
|
|
489
|
+
return null;
|
|
354
490
|
}
|
|
491
|
+
export function shouldDeferWallRebuild(wallId, nodes, rebuiltThisFrame, elapsedMs) {
|
|
492
|
+
return wallRebuildExitReason(wallId, nodes, rebuiltThisFrame, elapsedMs) !== null;
|
|
493
|
+
}
|
|
494
|
+
/** Rebuilds this system still owes — neighbours deferred during a drag. */
|
|
495
|
+
export function getPendingWallRebuildCount() {
|
|
496
|
+
return drainStats.pendingNeighbours;
|
|
497
|
+
}
|
|
498
|
+
let placeholderSweepCountdown = WALL_PLACEHOLDER_SWEEP_INTERVAL;
|
|
355
499
|
export const WallSystem = () => {
|
|
356
|
-
|
|
357
|
-
const clearDirty = useScene((state) => state.clearDirty);
|
|
358
|
-
// Subscribe so override-only changes (no scene write) still re-run
|
|
359
|
-
// this component, which lets the gate below pick up the latest
|
|
360
|
-
// `dirtyNodes` set from the same render pass that received the
|
|
361
|
-
// override-publishing `markDirty` call. Without this, very fast
|
|
362
|
-
// drags could land an override and a markDirty in the same React
|
|
363
|
-
// tick and the next `useFrame` would still see the stale closure.
|
|
500
|
+
useScene((state) => state.dirtyNodes);
|
|
364
501
|
useLiveNodeOverrides((s) => s.overrides);
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
502
|
+
useEffect(() => () => clearLevelMiterCache(), []);
|
|
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;
|
|
394
586
|
}
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
const
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
|
|
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;
|
|
401
599
|
break;
|
|
402
600
|
}
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (rebuiltWallsThisFrame > 0 &&
|
|
415
|
-
performance.now() - rebuildFrameStartedAt >= WALL_PROGRESSIVE_TIME_BUDGET_MS) {
|
|
416
|
-
break;
|
|
417
|
-
}
|
|
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++;
|
|
418
612
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
613
|
+
else {
|
|
614
|
+
drainStats.reinvalidationBuilds++;
|
|
615
|
+
}
|
|
616
|
+
if (!initialBuild || !firstBuild)
|
|
423
617
|
rebuiltWallIds.add(wallId);
|
|
424
|
-
|
|
618
|
+
rebuiltWallsThisFrame += 1;
|
|
619
|
+
drainStats.wallsConsumedThisFrame++;
|
|
620
|
+
if (initialBuild && wallRebuildExitReason(wallId, nodes, 1, 0, true) === 'heavy') {
|
|
621
|
+
exitReason = 'heavy';
|
|
622
|
+
deferWallRebuilds = true;
|
|
623
|
+
break;
|
|
425
624
|
}
|
|
426
|
-
// If mesh not found, keep it dirty for next frame
|
|
427
|
-
}
|
|
428
|
-
if (rebuiltWallIds.size === 0) {
|
|
429
|
-
continue;
|
|
430
|
-
}
|
|
431
|
-
// Adjacent walls sharing junctions — *defer* during active drag
|
|
432
|
-
// (dirty arrived this frame), flush on the trailing edge.
|
|
433
|
-
const adjacentWallIds = getAdjacentWallIds(levelWalls, rebuiltWallIds);
|
|
434
|
-
let pending = pendingAdjacentByLevel.get(levelId);
|
|
435
|
-
if (!pending) {
|
|
436
|
-
pending = new Set();
|
|
437
|
-
pendingAdjacentByLevel.set(levelId, pending);
|
|
438
625
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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++;
|
|
443
648
|
}
|
|
444
649
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
pendingIds.delete(wallId);
|
|
473
|
-
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;
|
|
474
677
|
}
|
|
475
|
-
|
|
476
|
-
|
|
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
|
+
}
|
|
477
691
|
}
|
|
478
|
-
|
|
479
|
-
|
|
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;
|
|
480
698
|
break;
|
|
481
699
|
}
|
|
482
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
|
+
}
|
|
483
710
|
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
|
|
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
|
+
}
|
|
487
732
|
/**
|
|
488
733
|
* Merge any live override for a wall into the scene record. Lets the
|
|
489
734
|
* 2D move handler publish `{ start, end, curveOffset }` to
|
|
@@ -568,6 +813,9 @@ function updateWallGeometry(wallId, miterData) {
|
|
|
568
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);
|
|
569
814
|
const newGeo = applyWorldPlanarWallUVs(builtGeo, wallWorldMatrix);
|
|
570
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;
|
|
571
819
|
mesh.geometry = newGeo;
|
|
572
820
|
// Update collision mesh
|
|
573
821
|
const collisionMesh = mesh.getObjectByName('collision-mesh');
|
|
@@ -844,7 +1092,6 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
|
|
|
844
1092
|
computeGeometryBoundsTree(cutoutGeometry);
|
|
845
1093
|
baseProfileCutouts.push(new Brush(cutoutGeometry));
|
|
846
1094
|
}
|
|
847
|
-
// Apply base-profile and opening cutouts in one CSG pass.
|
|
848
1095
|
const cutoutBrushes = [
|
|
849
1096
|
...baseProfileCutouts,
|
|
850
1097
|
...collectCutoutBrushes(wallNode, childrenNodes, thickness),
|
|
@@ -862,21 +1109,39 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
|
|
|
862
1109
|
computeGeometryBoundsTree(geometry);
|
|
863
1110
|
const wallBrush = new Brush(geometry);
|
|
864
1111
|
wallBrush.updateMatrixWorld();
|
|
865
|
-
|
|
1112
|
+
let mergedCutter = null;
|
|
866
1113
|
let resultBrush = wallBrush;
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
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)
|
|
872
1136
|
csgGeometry(resultBrush).dispose();
|
|
873
|
-
|
|
874
|
-
resultBrush = newResult;
|
|
1137
|
+
throw error;
|
|
875
1138
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1139
|
+
finally {
|
|
1140
|
+
csgGeometry(wallBrush).dispose();
|
|
1141
|
+
if (mergedCutter)
|
|
1142
|
+
csgGeometry(mergedCutter).dispose();
|
|
1143
|
+
for (const brush of cutoutBrushes)
|
|
1144
|
+
csgGeometry(brush).dispose();
|
|
880
1145
|
}
|
|
881
1146
|
const resultGeometry = csgGeometry(resultBrush);
|
|
882
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"}
|