@pascal-app/viewer 1.0.0-beta.5 → 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 +55 -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.map +1 -1
- package/dist/components/viewer/frame-limiter.js +4 -3
- 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 +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/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 +15 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -6
- 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/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 +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.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 +22 -1
- package/dist/store/use-viewer.d.ts +2 -2
- 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/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +3 -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/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
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { emitter,
|
|
1
|
+
import { emitter, sceneRegistry, useScene } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
|
-
import { useEffect,
|
|
3
|
+
import { useEffect, useMemo } from 'react';
|
|
4
4
|
import { Vector3 } from 'three/webgpu';
|
|
5
5
|
import useViewer, {} from '../../store/use-viewer';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import { sameMaterialArray, WallCutoutCache, wallHiddenFromFacing, } from './wall-cutout-cache';
|
|
7
|
+
import { getMaterialsForWall, getSelectionHighlightMaterials } from './wall-materials';
|
|
8
|
+
import { subscribeWallRebuilds } from './wall-rebuild-notifications';
|
|
9
9
|
const v = new Vector3();
|
|
10
|
+
export const WALL_CUTOUT_FRAME_PRIORITY = 0;
|
|
11
|
+
export function runWallCutoutFrame(cache, { camera, clock }) {
|
|
12
|
+
cache.update(camera, clock.elapsedTime);
|
|
13
|
+
}
|
|
10
14
|
/**
|
|
11
15
|
* Whether a wall should be hidden or see-through for the current camera and
|
|
12
16
|
* wall mode. Pure: reads only its arguments and the mesh's world direction.
|
|
@@ -16,161 +20,20 @@ const v = new Vector3();
|
|
|
16
20
|
* from the assigned material variant.
|
|
17
21
|
*/
|
|
18
22
|
export function getWallHideState(wallNode, wallMesh, wallMode, cameraDir) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
wallMesh.getWorldDirection(v);
|
|
28
|
-
if (v.dot(cameraDir) < 0) {
|
|
29
|
-
if (wallNode.frontSide === 'exterior' && wallNode.backSide !== 'exterior') {
|
|
30
|
-
hideWall = true;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
else if (wallNode.backSide === 'exterior' && wallNode.frontSide !== 'exterior') {
|
|
34
|
-
hideWall = true;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return hideWall;
|
|
38
|
-
}
|
|
39
|
-
function sameMaterialArray(a, b) {
|
|
40
|
-
return Array.isArray(a) && a.length === b.length && a.every((material, i) => material === b[i]);
|
|
23
|
+
if (wallMode === 'up')
|
|
24
|
+
return false;
|
|
25
|
+
if (wallMode === 'down')
|
|
26
|
+
return true;
|
|
27
|
+
wallMesh.getWorldDirection(v);
|
|
28
|
+
return wallHiddenFromFacing(wallNode, wallMode, v.dot(cameraDir) < 0);
|
|
41
29
|
}
|
|
42
|
-
export const WallCutout = () => {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const lastHighlightKey = useRef('');
|
|
50
|
-
const lastWallAppearanceKey = useRef('');
|
|
51
|
-
const wallAppearanceKeyRef = useRef('');
|
|
52
|
-
const wallAppearanceInputs = useRef({
|
|
53
|
-
nodes: null,
|
|
54
|
-
materials: null,
|
|
55
|
-
shading: null,
|
|
56
|
-
wallCount: -1,
|
|
57
|
-
});
|
|
58
|
-
const lastTextures = useRef(useViewer.getState().textures);
|
|
59
|
-
const lastColorPreset = useRef(useViewer.getState().colorPreset);
|
|
60
|
-
const lastSceneTheme = useRef(useViewer.getState().sceneTheme);
|
|
61
|
-
useFrame(({ camera, clock }) => {
|
|
62
|
-
const wallMode = useViewer.getState().wallMode;
|
|
63
|
-
const shading = useViewer.getState().shading;
|
|
64
|
-
const textures = useViewer.getState().textures;
|
|
65
|
-
const colorPreset = useViewer.getState().colorPreset;
|
|
66
|
-
const sceneTheme = useViewer.getState().sceneTheme;
|
|
67
|
-
const selectedIds = useViewer.getState().selection.selectedIds;
|
|
68
|
-
const previewSelectedIds = useViewer.getState().previewSelectedIds;
|
|
69
|
-
const hoveredId = useViewer.getState().hoveredId;
|
|
70
|
-
const hoverHighlightMode = useViewer.getState().hoverHighlightMode;
|
|
71
|
-
const sceneState = useScene.getState();
|
|
72
|
-
const currentTime = clock.elapsedTime;
|
|
73
|
-
const currentCameraPosition = camera.position;
|
|
74
|
-
camera.getWorldDirection(tmpVec);
|
|
75
|
-
tmpVec.add(currentCameraPosition);
|
|
76
|
-
const highlightedWallIds = new Set([...selectedIds, ...previewSelectedIds].filter((id) => sceneState.nodes[id]?.type === 'wall'));
|
|
77
|
-
const deleteHoveredWallId = hoverHighlightMode === 'delete' &&
|
|
78
|
-
hoveredId &&
|
|
79
|
-
sceneState.nodes[hoveredId]?.type === 'wall'
|
|
80
|
-
? hoveredId
|
|
81
|
-
: null;
|
|
82
|
-
const highlightKey = `${Array.from(highlightedWallIds).sort().join('|')}::${deleteHoveredWallId ?? ''}`;
|
|
83
|
-
// Sorting every wall id, hashing each wall's material and JSON-dumping its
|
|
84
|
-
// face bands is a full-scene scan; its inputs are immutable store slices,
|
|
85
|
-
// so identity is enough to know the key cannot have changed.
|
|
86
|
-
const wallCount = sceneRegistry.byType.wall.size;
|
|
87
|
-
const appearanceInputs = wallAppearanceInputs.current;
|
|
88
|
-
if (appearanceInputs.nodes !== sceneState.nodes ||
|
|
89
|
-
appearanceInputs.materials !== sceneState.materials ||
|
|
90
|
-
appearanceInputs.shading !== shading ||
|
|
91
|
-
appearanceInputs.wallCount !== wallCount) {
|
|
92
|
-
appearanceInputs.nodes = sceneState.nodes;
|
|
93
|
-
appearanceInputs.materials = sceneState.materials;
|
|
94
|
-
appearanceInputs.shading = shading;
|
|
95
|
-
appearanceInputs.wallCount = wallCount;
|
|
96
|
-
wallAppearanceKeyRef.current = Array.from(sceneRegistry.byType.wall)
|
|
97
|
-
.sort()
|
|
98
|
-
.map((wallId) => {
|
|
99
|
-
const wallNode = sceneState.nodes[wallId];
|
|
100
|
-
if (wallNode?.type !== 'wall')
|
|
101
|
-
return `${wallId}:missing`;
|
|
102
|
-
return `${wallId}:${getWallMaterialHash(wallNode, shading, sceneState.materials)}:${JSON.stringify(wallNode.faceBands ?? null)}`;
|
|
103
|
-
})
|
|
104
|
-
.join('|');
|
|
105
|
-
}
|
|
106
|
-
const wallAppearanceKey = wallAppearanceKeyRef.current;
|
|
107
|
-
const distanceMoved = currentCameraPosition.distanceTo(lastCameraPosition.current);
|
|
108
|
-
const directionChanged = tmpVec.distanceTo(lastCameraTarget.current);
|
|
109
|
-
const timeSinceUpdate = currentTime - lastUpdateTime.current;
|
|
110
|
-
if (((distanceMoved > 0.5 || directionChanged > 0.3) && timeSinceUpdate > 0.1) ||
|
|
111
|
-
lastWallMode.current !== wallMode ||
|
|
112
|
-
lastShading.current !== shading ||
|
|
113
|
-
lastTextures.current !== textures ||
|
|
114
|
-
lastColorPreset.current !== colorPreset ||
|
|
115
|
-
lastSceneTheme.current !== sceneTheme ||
|
|
116
|
-
sceneRegistry.byType.wall.size !== lastNumberOfWalls.current ||
|
|
117
|
-
lastHighlightKey.current !== highlightKey ||
|
|
118
|
-
lastWallAppearanceKey.current !== wallAppearanceKey) {
|
|
119
|
-
lastCameraPosition.current.copy(currentCameraPosition);
|
|
120
|
-
lastCameraTarget.current.copy(tmpVec);
|
|
121
|
-
lastUpdateTime.current = currentTime;
|
|
122
|
-
camera.getWorldDirection(u);
|
|
123
|
-
const walls = sceneRegistry.byType.wall;
|
|
124
|
-
walls.forEach((wallId) => {
|
|
125
|
-
const wallMesh = sceneRegistry.nodes.get(wallId);
|
|
126
|
-
if (!wallMesh)
|
|
127
|
-
return;
|
|
128
|
-
const wallNode = sceneState.nodes[wallId];
|
|
129
|
-
if (wallNode?.type !== 'wall')
|
|
130
|
-
return;
|
|
131
|
-
const hideWall = getWallHideState(wallNode, wallMesh, wallMode, u);
|
|
132
|
-
const isDeleteHighlighted = deleteHoveredWallId === wallId;
|
|
133
|
-
const isSelectionHighlighted = !isDeleteHighlighted && highlightedWallIds.has(wallId);
|
|
134
|
-
const levelId = resolveLevelId(wallNode, sceneState.nodes);
|
|
135
|
-
const support = spatialGridManager.getSlabSupportForWall(levelId, wallNode.start, wallNode.end, wallNode.curveOffset ?? 0, wallNode.thickness, wallNode.supportSlabId);
|
|
136
|
-
const effectiveWallHeight = resolveWallEffectiveHeight(wallNode, getWallPlaneTop(wallNode, levelId, sceneState.nodes), support.elevation);
|
|
137
|
-
const shouldSelectionHighlight = isSelectionHighlighted && !getWallFaceBandConfig(wallNode, effectiveWallHeight).enabled;
|
|
138
|
-
const materials = getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme, sceneState.materials);
|
|
139
|
-
if (wallMode === 'translucent') {
|
|
140
|
-
;
|
|
141
|
-
wallMesh.material = isDeleteHighlighted
|
|
142
|
-
? materials.deleteTranslucent
|
|
143
|
-
: shouldSelectionHighlight
|
|
144
|
-
? getSelectionHighlightMaterials(materials.translucent)
|
|
145
|
-
: materials.translucent;
|
|
146
|
-
}
|
|
147
|
-
else if (hideWall) {
|
|
148
|
-
;
|
|
149
|
-
wallMesh.material = isDeleteHighlighted
|
|
150
|
-
? materials.deleteInvisible
|
|
151
|
-
: shouldSelectionHighlight
|
|
152
|
-
? getSelectionHighlightMaterials(materials.invisible)
|
|
153
|
-
: materials.invisible;
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
;
|
|
157
|
-
wallMesh.material = isDeleteHighlighted
|
|
158
|
-
? materials.deleteVisible
|
|
159
|
-
: shouldSelectionHighlight
|
|
160
|
-
? getSelectionHighlightMaterials(materials.visible)
|
|
161
|
-
: materials.visible;
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
lastWallMode.current = wallMode;
|
|
165
|
-
lastShading.current = shading;
|
|
166
|
-
lastTextures.current = textures;
|
|
167
|
-
lastColorPreset.current = colorPreset;
|
|
168
|
-
lastSceneTheme.current = sceneTheme;
|
|
169
|
-
lastNumberOfWalls.current = sceneRegistry.byType.wall.size;
|
|
170
|
-
lastHighlightKey.current = highlightKey;
|
|
171
|
-
lastWallAppearanceKey.current = wallAppearanceKey;
|
|
172
|
-
}
|
|
173
|
-
});
|
|
30
|
+
export const WallCutout = ({ viewerStore = useViewer, }) => {
|
|
31
|
+
const cache = useMemo(() => new WallCutoutCache(viewerStore), [viewerStore]);
|
|
32
|
+
useEffect(() => subscribeWallRebuilds((id) => cache.rebuilt.add(id)), [cache]);
|
|
33
|
+
useEffect(() => cache.subscribeLiveTransforms(), [cache]);
|
|
34
|
+
// Camera changes reach PostProcessing (1) in this frame. WallSystem (4)
|
|
35
|
+
// notifies the next frame; WallBatchSystem (5) reads this frame's stamps.
|
|
36
|
+
useFrame((state) => runWallCutoutFrame(cache, state), WALL_CUTOUT_FRAME_PRIORITY);
|
|
174
37
|
useEffect(() => {
|
|
175
38
|
const snapshot = new Map();
|
|
176
39
|
const restoreForCapture = () => {
|
|
@@ -181,7 +44,7 @@ export const WallCutout = () => {
|
|
|
181
44
|
const wallNode = useScene.getState().nodes[wallId];
|
|
182
45
|
if (wallNode?.type !== 'wall')
|
|
183
46
|
return;
|
|
184
|
-
const mats = getMaterialsForWall(wallNode,
|
|
47
|
+
const mats = getMaterialsForWall(wallNode, viewerStore.getState().shading, viewerStore.getState().textures, viewerStore.getState().colorPreset, viewerStore.getState().sceneTheme, useScene.getState().materials);
|
|
185
48
|
const current = wallMesh.material;
|
|
186
49
|
snapshot.set(wallMesh, current);
|
|
187
50
|
if (current === mats.deleteVisible) {
|
|
@@ -208,6 +71,6 @@ export const WallCutout = () => {
|
|
|
208
71
|
emitter.off('thumbnail:before-capture', restoreForCapture);
|
|
209
72
|
emitter.off('thumbnail:after-capture', reapplyAfterCapture);
|
|
210
73
|
};
|
|
211
|
-
}, []);
|
|
74
|
+
}, [viewerStore]);
|
|
212
75
|
return null;
|
|
213
76
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which material variant does a wall draw this pass?
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `WallCutout`'s per-wall assignment so the precedence is a
|
|
5
|
+
* testable truth table. Precedence within each display mode:
|
|
6
|
+
*
|
|
7
|
+
* delete-hover > selection > select-hover > base
|
|
8
|
+
*
|
|
9
|
+
* The SELECT-HOVER variant exists for HIDDEN walls only (QA f2, REVISE):
|
|
10
|
+
* with the Bones X-ray on, hidden walls are hover/selection ray targets
|
|
11
|
+
* (nearest-first, see nodes/wall/pointer-transparency.ts) — but an
|
|
12
|
+
* invisible wall gave no hover feedback at all, so what the user saw
|
|
13
|
+
* lighting up on hover was the furniture BEHIND the wall. Hovering a hidden
|
|
14
|
+
* wall now glows its stipple film. Visible and translucent walls keep their
|
|
15
|
+
* existing hover affordance (the post-processing outline pass) — a material
|
|
16
|
+
* glow there would double-highlight.
|
|
17
|
+
*/
|
|
18
|
+
export type WallMaterialVariant = 'visible' | 'invisible' | 'translucent' | 'delete-visible' | 'delete-invisible' | 'delete-translucent' | 'selection-visible' | 'selection-invisible' | 'selection-translucent' | 'hover-invisible';
|
|
19
|
+
export declare const resolveWallMaterialVariant: ({ translucentMode, hidden, deleteHighlighted, selectionHighlighted, hoverHighlighted, }: {
|
|
20
|
+
/** wallMode === 'translucent' (overrides the hide state). */
|
|
21
|
+
translucentMode: boolean;
|
|
22
|
+
/** The wall-mode pass hides this wall ('down', cutaway, auto-interior). */
|
|
23
|
+
hidden: boolean;
|
|
24
|
+
/** Delete-mode hover on this wall (deleteInvisible flow). */
|
|
25
|
+
deleteHighlighted: boolean;
|
|
26
|
+
/** Selected (and the wall's face-band config allows the highlight). */
|
|
27
|
+
selectionHighlighted: boolean;
|
|
28
|
+
/** Select-mode hover on this wall (useViewer.hoveredId). */
|
|
29
|
+
hoverHighlighted: boolean;
|
|
30
|
+
}) => WallMaterialVariant;
|
|
31
|
+
//# sourceMappingURL=wall-material-variant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wall-material-variant.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-material-variant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,kBAAkB,GAClB,oBAAoB,GACpB,mBAAmB,GACnB,qBAAqB,GACrB,uBAAuB,GACvB,iBAAiB,CAAA;AAErB,eAAO,MAAM,0BAA0B,GAAI,yFAMxC;IACD,6DAA6D;IAC7D,eAAe,EAAE,OAAO,CAAA;IACxB,2EAA2E;IAC3E,MAAM,EAAE,OAAO,CAAA;IACf,6DAA6D;IAC7D,iBAAiB,EAAE,OAAO,CAAA;IAC1B,uEAAuE;IACvE,oBAAoB,EAAE,OAAO,CAAA;IAC7B,4DAA4D;IAC5D,gBAAgB,EAAE,OAAO,CAAA;CAC1B,KAAG,mBAMH,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const resolveWallMaterialVariant = ({ translucentMode, hidden, deleteHighlighted, selectionHighlighted, hoverHighlighted, }) => {
|
|
2
|
+
const base = translucentMode ? 'translucent' : hidden ? 'invisible' : 'visible';
|
|
3
|
+
if (deleteHighlighted)
|
|
4
|
+
return `delete-${base}`;
|
|
5
|
+
if (selectionHighlighted)
|
|
6
|
+
return `selection-${base}`;
|
|
7
|
+
if (hoverHighlighted && base === 'invisible')
|
|
8
|
+
return 'hover-invisible';
|
|
9
|
+
return base;
|
|
10
|
+
};
|
|
@@ -14,6 +14,12 @@ export interface WallMaterials {
|
|
|
14
14
|
}
|
|
15
15
|
/** Lazy light-emissive selection variant of a wall's material array (keeps texture). */
|
|
16
16
|
export declare function getSelectionHighlightMaterials(materials: WallMaterialArray): WallMaterialArray;
|
|
17
|
+
/**
|
|
18
|
+
* Softer hover sibling of the selection variant — the affordance for a
|
|
19
|
+
* hovered HIDDEN wall (`WallCutout` applies it to the invisible stipple
|
|
20
|
+
* film so the wall the click would select reads under the cursor).
|
|
21
|
+
*/
|
|
22
|
+
export declare function getHoverHighlightMaterials(materials: WallMaterialArray): WallMaterialArray;
|
|
17
23
|
export declare function getWallMaterialHash(wallNode: WallNode, shading: RenderShading, sceneMaterials?: SceneMaterials): string;
|
|
18
24
|
export declare function getMaterialsForWall(wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string, sceneMaterials?: SceneMaterials): WallMaterials;
|
|
19
25
|
export declare function getVisibleWallMaterials(wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string, sceneMaterials?: SceneMaterials): WallMaterialArray;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,eAAe,EAGpB,KAAK,QAAQ,EAId,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAG5C,OAAO,EAEL,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"wall-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,eAAe,EAGpB,KAAK,QAAQ,EAId,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAG5C,OAAO,EAEL,KAAK,WAAW,EAMhB,KAAK,aAAa,EAGnB,MAAM,qBAAqB,CAAA;AAE5B,KAAK,cAAc,GAAG,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC,GAAG,SAAS,CAAA;AAexE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,EAAE,CAAA;AAE1C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,iBAAiB,CAAA;IAC1B,SAAS,EAAE,iBAAiB,CAAA;IAC5B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,aAAa,EAAE,iBAAiB,CAAA;IAChC,eAAe,EAAE,iBAAiB,CAAA;IAClC,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;CACrB;AA+SD,wFAAwF;AACxF,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAS9F;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAS1F;AA4DD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,cAAc,GAC9B,MAAM,CAcR;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,GAC9B,aAAa,CAgIf;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,GAC9B,iBAAiB,CAGnB"}
|
|
@@ -2,7 +2,7 @@ import { getEffectiveWallSurfaceMaterial, getMaterialPresetByRef, getWallSurface
|
|
|
2
2
|
import { Color } from 'three';
|
|
3
3
|
import { Fn, float, fract, length, mix, positionLocal, smoothstep, step, vec2 } from 'three/tsl';
|
|
4
4
|
import { MeshLambertNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
5
|
-
import { baseMaterial, createDefaultMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, resolveMaterialRef, resolveSurfaceColor, } from '../../lib/materials';
|
|
5
|
+
import { baseMaterial, createDefaultMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, materialPresetRefSignature, resolveMaterialRef, resolveSurfaceColor, } from '../../lib/materials';
|
|
6
6
|
const DEFAULT_WALL_COLOR = '#e9e7e3';
|
|
7
7
|
const WALL_HIGHLIGHT_PROFILES = {
|
|
8
8
|
delete: {
|
|
@@ -74,8 +74,10 @@ function resolveWallSlotMaterial(wallNode, slotId, shading, sceneMaterials) {
|
|
|
74
74
|
}
|
|
75
75
|
// Cache-key fragment for one face: the slot ref plus, for a `scene:` ref, the
|
|
76
76
|
// referenced material's *content* — so editing a scene material assigned to a
|
|
77
|
-
// wall invalidates the cache
|
|
78
|
-
//
|
|
77
|
+
// wall invalidates the cache. A `library:` ref carries its resolution state
|
|
78
|
+
// instead: AI-generated materials register asynchronously, and a dangling ref
|
|
79
|
+
// resolved to the slot default must not stay cached once the library lands.
|
|
80
|
+
// Falls back to the legacy signature when unmigrated.
|
|
79
81
|
function wallFaceMaterialSignature(wallNode, side, sceneMaterials) {
|
|
80
82
|
const ref = wallNode.slots?.[side];
|
|
81
83
|
if (ref) {
|
|
@@ -86,7 +88,7 @@ function wallFaceMaterialSignature(wallNode, side, sceneMaterials) {
|
|
|
86
88
|
material: sceneMaterials?.[parsed.id]?.material ?? null,
|
|
87
89
|
});
|
|
88
90
|
}
|
|
89
|
-
return JSON.stringify({ ref });
|
|
91
|
+
return JSON.stringify({ ref: materialPresetRefSignature(ref) });
|
|
90
92
|
}
|
|
91
93
|
return getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(wallNode, side));
|
|
92
94
|
}
|
|
@@ -100,7 +102,7 @@ function wallSlotMaterialSignature(wallNode, slotId, sceneMaterials) {
|
|
|
100
102
|
material: sceneMaterials?.[parsed.id]?.material ?? null,
|
|
101
103
|
});
|
|
102
104
|
}
|
|
103
|
-
return JSON.stringify({ ref });
|
|
105
|
+
return JSON.stringify({ ref: materialPresetRefSignature(ref) });
|
|
104
106
|
}
|
|
105
107
|
const side = getWallSurfaceSideFromBandSlot(slotId);
|
|
106
108
|
if (side)
|
|
@@ -181,6 +183,13 @@ function createHighlightedWallMaterial(material, kind) {
|
|
|
181
183
|
const SELECTION_HIGHLIGHT_COLOR = new Color('#818cf8');
|
|
182
184
|
const SELECTION_EMISSIVE_BLEND = 0.4;
|
|
183
185
|
const SELECTION_EMISSIVE_INTENSITY = 0.12;
|
|
186
|
+
// Softer sibling of the selection glow, for HOVERING a hidden wall (the
|
|
187
|
+
// X-ray nearest-first selection made hidden walls hover targets; without a
|
|
188
|
+
// material affordance the only thing lighting up was the furniture behind
|
|
189
|
+
// them). Same indigo so hover reads as "this will select", weaker so a
|
|
190
|
+
// hovered-then-selected wall still steps up on click.
|
|
191
|
+
const HOVER_EMISSIVE_BLEND = 0.4;
|
|
192
|
+
const HOVER_EMISSIVE_INTENSITY = 0.2;
|
|
184
193
|
const SELECTION_TEXTURE_MAP_KEYS = [
|
|
185
194
|
'map',
|
|
186
195
|
'normalMap',
|
|
@@ -194,9 +203,10 @@ const SELECTION_TEXTURE_MAP_KEYS = [
|
|
|
194
203
|
'lightMap',
|
|
195
204
|
];
|
|
196
205
|
const selectionHighlightCache = new WeakMap();
|
|
197
|
-
|
|
206
|
+
const hoverHighlightCache = new WeakMap();
|
|
207
|
+
function getEmissiveHighlightMaterial(base, cache, emissiveBlend, emissiveIntensity) {
|
|
198
208
|
const baseMap = base.map ?? null;
|
|
199
|
-
const cached =
|
|
209
|
+
const cached = cache.get(base);
|
|
200
210
|
if (cached && cached.map === baseMap)
|
|
201
211
|
return cached.clone;
|
|
202
212
|
const clone = base.clone();
|
|
@@ -208,20 +218,26 @@ function getSelectionHighlightMaterial(base) {
|
|
|
208
218
|
dst[key] = src[key];
|
|
209
219
|
}
|
|
210
220
|
if ('emissive' in clone && clone.emissive) {
|
|
211
|
-
clone.emissive = clone.emissive
|
|
212
|
-
.clone()
|
|
213
|
-
.lerp(SELECTION_HIGHLIGHT_COLOR, SELECTION_EMISSIVE_BLEND);
|
|
221
|
+
clone.emissive = clone.emissive.clone().lerp(SELECTION_HIGHLIGHT_COLOR, emissiveBlend);
|
|
214
222
|
}
|
|
215
223
|
if ('emissiveIntensity' in clone) {
|
|
216
|
-
clone.emissiveIntensity = Math.max(clone.emissiveIntensity ?? 0,
|
|
224
|
+
clone.emissiveIntensity = Math.max(clone.emissiveIntensity ?? 0, emissiveIntensity);
|
|
217
225
|
}
|
|
218
226
|
clone.needsUpdate = true;
|
|
219
|
-
|
|
227
|
+
cache.set(base, { clone, map: baseMap });
|
|
220
228
|
return clone;
|
|
221
229
|
}
|
|
222
230
|
/** Lazy light-emissive selection variant of a wall's material array (keeps texture). */
|
|
223
231
|
export function getSelectionHighlightMaterials(materials) {
|
|
224
|
-
return materials.map(
|
|
232
|
+
return materials.map((material) => getEmissiveHighlightMaterial(material, selectionHighlightCache, SELECTION_EMISSIVE_BLEND, SELECTION_EMISSIVE_INTENSITY));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Softer hover sibling of the selection variant — the affordance for a
|
|
236
|
+
* hovered HIDDEN wall (`WallCutout` applies it to the invisible stipple
|
|
237
|
+
* film so the wall the click would select reads under the cursor).
|
|
238
|
+
*/
|
|
239
|
+
export function getHoverHighlightMaterials(materials) {
|
|
240
|
+
return materials.map((material) => getEmissiveHighlightMaterial(material, hoverHighlightCache, HOVER_EMISSIVE_BLEND, HOVER_EMISSIVE_INTENSITY));
|
|
225
241
|
}
|
|
226
242
|
function createInvisibleWallMaterial(color, shading) {
|
|
227
243
|
const material = shading === 'solid'
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-healing backstop for wall geometry: every registered wall whose mesh
|
|
3
|
+
* still carries the mount-time PLACEHOLDER geometry must eventually rebuild,
|
|
4
|
+
* no matter what consumed its dirty mark or when the wall system mounted.
|
|
5
|
+
*
|
|
6
|
+
* Why it exists: a live session (QA f2 probe5/probe6 — scene loaded with
|
|
7
|
+
* the Bones X-ray already active) surfaced all 24 walls stuck on their
|
|
8
|
+
* degenerate placeholder collision meshes indefinitely: no wall on the
|
|
9
|
+
* level ever became a ray target, so the hidden-wall selection gate could
|
|
10
|
+
* not engage even in principle. The renderer marks a wall dirty exactly
|
|
11
|
+
* once, on mount — if that one mark is consumed while the rebuild loop
|
|
12
|
+
* isn't looking (system mounted late, mark cleared by an unrelated flow,
|
|
13
|
+
* suspense unmount/remount), nothing ever re-marks it. This sweep closes
|
|
14
|
+
* that class: placeholder + not dirty → re-mark, and the normal rebuild
|
|
15
|
+
* path takes it from there. Idempotent and cheap (one geometry probe per
|
|
16
|
+
* wall, run every `WALL_PLACEHOLDER_SWEEP_INTERVAL` frames).
|
|
17
|
+
*
|
|
18
|
+
* Placeholder detection: `createPlaceholderGeometry` (packages/nodes,
|
|
19
|
+
* shared/placeholder-geometry.ts) mints a 3-vertex degenerate triangle and
|
|
20
|
+
* stamps `userData.placeholder`. Real wall extrusions always carry far more
|
|
21
|
+
* vertices, so the vertex-count signature doubles as a fallback for
|
|
22
|
+
* placeholders minted before the stamp existed.
|
|
23
|
+
*/
|
|
24
|
+
export declare const WALL_PLACEHOLDER_SWEEP_INTERVAL = 30;
|
|
25
|
+
type GeometryLike = {
|
|
26
|
+
userData?: {
|
|
27
|
+
placeholder?: unknown;
|
|
28
|
+
built?: unknown;
|
|
29
|
+
};
|
|
30
|
+
getAttribute?: (name: string) => {
|
|
31
|
+
count: number;
|
|
32
|
+
} | undefined;
|
|
33
|
+
} | null;
|
|
34
|
+
/** Is this still the mount-time placeholder (never built by the system)? */
|
|
35
|
+
export declare const isPlaceholderWallGeometry: (geometry: GeometryLike) => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Re-mark every registered wall still on placeholder geometry that carries
|
|
38
|
+
* no dirty mark. Returns the ids it marked (for tests / diagnostics).
|
|
39
|
+
*/
|
|
40
|
+
export declare const sweepUnbuiltWalls: ({ wallIds, geometryOf, isDirty, markDirty, }: {
|
|
41
|
+
/** Registered wall node ids (sceneRegistry.byType.wall). */
|
|
42
|
+
wallIds: Iterable<string>;
|
|
43
|
+
/** The wall root mesh's current geometry, or null when unmounted. */
|
|
44
|
+
geometryOf: (wallId: string) => GeometryLike;
|
|
45
|
+
/** Whether the id already has a dirty mark (rebuild pending). */
|
|
46
|
+
isDirty: (wallId: string) => boolean;
|
|
47
|
+
markDirty: (wallId: string) => void;
|
|
48
|
+
}) => string[];
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=wall-placeholder-sweep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wall-placeholder-sweep.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-placeholder-sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,eAAO,MAAM,+BAA+B,KAAK,CAAA;AAEjD,KAAK,YAAY,GAAG;IAClB,QAAQ,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;IACrD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAA;CAC/D,GAAG,IAAI,CAAA;AAER,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB,GAAI,UAAU,YAAY,KAAG,OAMlE,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,8CAK/B;IACD,4DAA4D;IAC5D,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;IACzB,qEAAqE;IACrE,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,YAAY,CAAA;IAC5C,iEAAiE;IACjE,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;IACpC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC,KAAG,MAAM,EAST,CAAA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-healing backstop for wall geometry: every registered wall whose mesh
|
|
3
|
+
* still carries the mount-time PLACEHOLDER geometry must eventually rebuild,
|
|
4
|
+
* no matter what consumed its dirty mark or when the wall system mounted.
|
|
5
|
+
*
|
|
6
|
+
* Why it exists: a live session (QA f2 probe5/probe6 — scene loaded with
|
|
7
|
+
* the Bones X-ray already active) surfaced all 24 walls stuck on their
|
|
8
|
+
* degenerate placeholder collision meshes indefinitely: no wall on the
|
|
9
|
+
* level ever became a ray target, so the hidden-wall selection gate could
|
|
10
|
+
* not engage even in principle. The renderer marks a wall dirty exactly
|
|
11
|
+
* once, on mount — if that one mark is consumed while the rebuild loop
|
|
12
|
+
* isn't looking (system mounted late, mark cleared by an unrelated flow,
|
|
13
|
+
* suspense unmount/remount), nothing ever re-marks it. This sweep closes
|
|
14
|
+
* that class: placeholder + not dirty → re-mark, and the normal rebuild
|
|
15
|
+
* path takes it from there. Idempotent and cheap (one geometry probe per
|
|
16
|
+
* wall, run every `WALL_PLACEHOLDER_SWEEP_INTERVAL` frames).
|
|
17
|
+
*
|
|
18
|
+
* Placeholder detection: `createPlaceholderGeometry` (packages/nodes,
|
|
19
|
+
* shared/placeholder-geometry.ts) mints a 3-vertex degenerate triangle and
|
|
20
|
+
* stamps `userData.placeholder`. Real wall extrusions always carry far more
|
|
21
|
+
* vertices, so the vertex-count signature doubles as a fallback for
|
|
22
|
+
* placeholders minted before the stamp existed.
|
|
23
|
+
*/
|
|
24
|
+
export const WALL_PLACEHOLDER_SWEEP_INTERVAL = 30;
|
|
25
|
+
/** Is this still the mount-time placeholder (never built by the system)? */
|
|
26
|
+
export const isPlaceholderWallGeometry = (geometry) => {
|
|
27
|
+
if (!geometry)
|
|
28
|
+
return false;
|
|
29
|
+
if (geometry.userData?.placeholder === true)
|
|
30
|
+
return true;
|
|
31
|
+
if (geometry.userData?.built === true)
|
|
32
|
+
return false;
|
|
33
|
+
const position = geometry.getAttribute?.('position');
|
|
34
|
+
return position !== undefined && position.count === 3;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Re-mark every registered wall still on placeholder geometry that carries
|
|
38
|
+
* no dirty mark. Returns the ids it marked (for tests / diagnostics).
|
|
39
|
+
*/
|
|
40
|
+
export const sweepUnbuiltWalls = ({ wallIds, geometryOf, isDirty, markDirty, }) => {
|
|
41
|
+
const marked = [];
|
|
42
|
+
for (const wallId of wallIds) {
|
|
43
|
+
if (isDirty(wallId))
|
|
44
|
+
continue;
|
|
45
|
+
if (!isPlaceholderWallGeometry(geometryOf(wallId)))
|
|
46
|
+
continue;
|
|
47
|
+
markDirty(wallId);
|
|
48
|
+
marked.push(wallId);
|
|
49
|
+
}
|
|
50
|
+
return marked;
|
|
51
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function notifyWallRebuilt(wallId: string): void;
|
|
2
|
+
export declare function subscribeWallRebuilds(listener: (wallId: string) => void): () => void;
|
|
3
|
+
/** Moves every rebuild notice collected so far into `into`. */
|
|
4
|
+
export declare function drainRebuiltWalls(into: Set<string>): void;
|
|
5
|
+
//# sourceMappingURL=wall-rebuild-notifications.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wall-rebuild-notifications.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-rebuild-notifications.ts"],"names":[],"mappings":"AAOA,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAGtD;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAKpF;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAGzD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// New kind-specific modules belong in nodes; viewer must not depend on nodes.
|
|
2
|
+
// This extracts the existing viewer-owned WallSystem rebuild signal.
|
|
3
|
+
// Dirty marks disappear before later systems run. Keep the batch drain and
|
|
4
|
+
// cutout subscribers independent, including deferred neighbour rebuilds.
|
|
5
|
+
const rebuiltWalls = new Set();
|
|
6
|
+
const listeners = new Set();
|
|
7
|
+
export function notifyWallRebuilt(wallId) {
|
|
8
|
+
rebuiltWalls.add(wallId);
|
|
9
|
+
for (const listener of listeners)
|
|
10
|
+
listener(wallId);
|
|
11
|
+
}
|
|
12
|
+
export function subscribeWallRebuilds(listener) {
|
|
13
|
+
listeners.add(listener);
|
|
14
|
+
return () => {
|
|
15
|
+
listeners.delete(listener);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Moves every rebuild notice collected so far into `into`. */
|
|
19
|
+
export function drainRebuiltWalls(into) {
|
|
20
|
+
for (const wallId of rebuiltWalls)
|
|
21
|
+
into.add(wallId);
|
|
22
|
+
rebuiltWalls.clear();
|
|
23
|
+
}
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { type AnyNode, type WallMiterData, type WallNode, type WallSlabSupportSegment } from '@pascal-app/core';
|
|
1
|
+
import { type AnyNode, type AnyNodeId, type WallMiterData, type WallNode, type WallSlabSupportSegment } from '@pascal-app/core';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
|
-
|
|
4
|
-
export
|
|
3
|
+
import { Brush } from 'three-bvh-csg';
|
|
4
|
+
export { isWallInitialBuildActive } from './wall-build-lifecycle';
|
|
5
|
+
export { drainRebuiltWalls } from './wall-rebuild-notifications';
|
|
6
|
+
export declare function mergeWallCutoutBrushes(brushes: readonly Brush[]): {
|
|
7
|
+
cutter: Brush | null;
|
|
8
|
+
fallbackBrushes: Brush[];
|
|
9
|
+
droppedCount: number;
|
|
10
|
+
};
|
|
11
|
+
export declare function shouldDeferWallRebuild(wallId: string, nodes: Record<AnyNodeId, AnyNode>, rebuiltThisFrame: number, elapsedMs: number): boolean;
|
|
5
12
|
/** Rebuilds this system still owes — neighbours deferred during a drag. */
|
|
6
13
|
export declare function getPendingWallRebuildCount(): number;
|
|
7
14
|
export declare const WallSystem: () => null;
|
|
15
|
+
export declare function runWallBuildFrame(): void;
|
|
8
16
|
type WallTerrainBottomSampler = (x: number, z: number) => number | null;
|
|
9
17
|
export declare function generateExtrudedWall(wallNode: WallNode, childrenNodes: AnyNode[], miterData: WallMiterData, slabElevation?: number, baseElevation?: number, baseSegments?: readonly WallSlabSupportSegment[], storeyHeight?: number, terrainBottomAt?: WallTerrainBottomSampler): THREE.BufferGeometry;
|
|
10
|
-
export {};
|
|
11
18
|
//# sourceMappingURL=wall-system.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAyBd,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,sBAAsB,EAI5B,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AAsBvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AA2DhE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,GAAG;IACjE,MAAM,EAAE,KAAK,GAAG,IAAI,CAAA;IACpB,eAAe,EAAE,KAAK,EAAE,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;CACrB,CA4FA;AAmbD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EACjC,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAET;AAED,2EAA2E;AAC3E,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAID,eAAO,MAAM,UAAU,YAMtB,CAAA;AAED,wBAAgB,iBAAiB,SAahC;AA6bD,KAAK,wBAAwB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;AAgEvE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,OAAO,EAAE,EACxB,SAAS,EAAE,aAAa,EACxB,aAAa,SAAI,EACjB,aAAa,SAAgB,EAC7B,YAAY,GAAE,SAAS,sBAAsB,EAE5C,EACD,YAAY,SAAuB,EACnC,eAAe,CAAC,EAAE,wBAAwB,GACzC,KAAK,CAAC,cAAc,CAwPtB"}
|