@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.
Files changed (265) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +107 -0
  3. package/dist/capture/asset-url.d.ts +2 -0
  4. package/dist/capture/asset-url.d.ts.map +1 -0
  5. package/dist/capture/asset-url.js +13 -0
  6. package/dist/capture/capture-runtime.d.ts +43 -0
  7. package/dist/capture/capture-runtime.d.ts.map +1 -0
  8. package/dist/capture/capture-runtime.js +228 -0
  9. package/dist/capture/frame.d.ts +4 -0
  10. package/dist/capture/frame.d.ts.map +1 -0
  11. package/dist/capture/frame.js +21 -0
  12. package/dist/capture/index.d.ts +12 -0
  13. package/dist/capture/index.d.ts.map +1 -0
  14. package/dist/capture/index.js +11 -0
  15. package/dist/capture/layer-visibility.d.ts +5 -0
  16. package/dist/capture/layer-visibility.d.ts.map +1 -0
  17. package/dist/capture/layer-visibility.js +11 -0
  18. package/dist/capture/layers/clay-matcap.d.ts +3 -0
  19. package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
  20. package/dist/capture/layers/clay-matcap.js +26 -0
  21. package/dist/capture/layers/device-motion-layer.d.ts +7 -0
  22. package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
  23. package/dist/capture/layers/device-motion-layer.js +78 -0
  24. package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
  25. package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
  26. package/dist/capture/layers/point-cloud-layer.js +106 -0
  27. package/dist/capture/layers/room-model-layer.d.ts +9 -0
  28. package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
  29. package/dist/capture/layers/room-model-layer.js +71 -0
  30. package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
  31. package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
  32. package/dist/capture/layers/surface-mesh-data.js +74 -0
  33. package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
  34. package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
  35. package/dist/capture/layers/surface-mesh-layer.js +16 -0
  36. package/dist/capture/preview.d.ts +3 -0
  37. package/dist/capture/preview.d.ts.map +1 -0
  38. package/dist/capture/preview.js +2 -0
  39. package/dist/capture/source-state.d.ts +22 -0
  40. package/dist/capture/source-state.d.ts.map +1 -0
  41. package/dist/capture/source-state.js +182 -0
  42. package/dist/capture/stream-rendering.d.ts +14 -0
  43. package/dist/capture/stream-rendering.d.ts.map +1 -0
  44. package/dist/capture/stream-rendering.js +69 -0
  45. package/dist/capture/trajectory.d.ts +20 -0
  46. package/dist/capture/trajectory.d.ts.map +1 -0
  47. package/dist/capture/trajectory.js +77 -0
  48. package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
  49. package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
  50. package/dist/components/viewer/batched-mesh-spike.js +64 -0
  51. package/dist/components/viewer/frame-limiter.d.ts +11 -0
  52. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  53. package/dist/components/viewer/frame-limiter.js +59 -18
  54. package/dist/components/viewer/index.d.ts +15 -0
  55. package/dist/components/viewer/index.d.ts.map +1 -1
  56. package/dist/components/viewer/index.js +74 -82
  57. package/dist/components/viewer/lights.d.ts.map +1 -1
  58. package/dist/components/viewer/lights.js +123 -89
  59. package/dist/components/viewer/perf-monitor.d.ts +6 -1
  60. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  61. package/dist/components/viewer/perf-monitor.js +306 -115
  62. package/dist/components/viewer/perf-panel.d.ts +3 -0
  63. package/dist/components/viewer/perf-panel.d.ts.map +1 -0
  64. package/dist/components/viewer/perf-panel.js +178 -0
  65. package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
  66. package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
  67. package/dist/components/viewer/pointer-raycast-layers.js +28 -0
  68. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  69. package/dist/components/viewer/post-processing.js +124 -83
  70. package/dist/components/viewer/registered-systems.d.ts +14 -5
  71. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  72. package/dist/components/viewer/registered-systems.js +22 -10
  73. package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
  74. package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
  75. package/dist/components/viewer/scene-atmosphere.js +89 -0
  76. package/dist/components/viewer/scene-environment.d.ts.map +1 -1
  77. package/dist/components/viewer/scene-environment.js +19 -5
  78. package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
  79. package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
  80. package/dist/components/viewer/scene-ground-replacement.js +35 -0
  81. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  82. package/dist/components/viewer/selection-manager.js +7 -2
  83. package/dist/components/viewer/unsupported-gpu-fallback.d.ts +2 -0
  84. package/dist/components/viewer/unsupported-gpu-fallback.d.ts.map +1 -0
  85. package/dist/components/viewer/unsupported-gpu-fallback.js +4 -0
  86. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  87. package/dist/components/viewer/viewer-camera.js +4 -1
  88. package/dist/components/viewer/viewer-presentations.d.ts +67 -0
  89. package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
  90. package/dist/components/viewer/viewer-presentations.js +120 -0
  91. package/dist/hooks/use-library-materials-version.d.ts +7 -0
  92. package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
  93. package/dist/hooks/use-library-materials-version.js +10 -0
  94. package/dist/hooks/use-node-events.d.ts.map +1 -1
  95. package/dist/hooks/use-node-events.js +1 -0
  96. package/dist/index.d.ts +20 -8
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +19 -7
  99. package/dist/lib/csg-utils.d.ts +1 -0
  100. package/dist/lib/csg-utils.d.ts.map +1 -1
  101. package/dist/lib/csg-utils.js +6 -1
  102. package/dist/lib/dispose-object3d.d.ts +4 -0
  103. package/dist/lib/dispose-object3d.d.ts.map +1 -0
  104. package/dist/lib/dispose-object3d.js +27 -0
  105. package/dist/lib/geometry-groups.d.ts +17 -0
  106. package/dist/lib/geometry-groups.d.ts.map +1 -0
  107. package/dist/lib/geometry-groups.js +53 -0
  108. package/dist/lib/gpu-perf.d.ts +0 -2
  109. package/dist/lib/gpu-perf.d.ts.map +1 -1
  110. package/dist/lib/gpu-perf.js +7 -21
  111. package/dist/lib/isolation.d.ts +8 -8
  112. package/dist/lib/isolation.d.ts.map +1 -1
  113. package/dist/lib/isolation.js +18 -30
  114. package/dist/lib/layer-pass.d.ts +28 -0
  115. package/dist/lib/layer-pass.d.ts.map +1 -0
  116. package/dist/lib/layer-pass.js +254 -0
  117. package/dist/lib/layers.d.ts +16 -0
  118. package/dist/lib/layers.d.ts.map +1 -1
  119. package/dist/lib/layers.js +18 -0
  120. package/dist/lib/materials.d.ts +12 -0
  121. package/dist/lib/materials.d.ts.map +1 -1
  122. package/dist/lib/materials.js +51 -17
  123. package/dist/lib/merged-outline-node.d.ts +17 -8
  124. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  125. package/dist/lib/merged-outline-node.js +194 -52
  126. package/dist/lib/perf-actions.d.ts +55 -0
  127. package/dist/lib/perf-actions.d.ts.map +1 -0
  128. package/dist/lib/perf-actions.js +211 -0
  129. package/dist/lib/perf-observers.d.ts +2 -0
  130. package/dist/lib/perf-observers.d.ts.map +1 -0
  131. package/dist/lib/perf-observers.js +17 -0
  132. package/dist/lib/perf-panel-store.d.ts +71 -0
  133. package/dist/lib/perf-panel-store.d.ts.map +1 -0
  134. package/dist/lib/perf-panel-store.js +29 -0
  135. package/dist/lib/perf-tracks.d.ts +43 -0
  136. package/dist/lib/perf-tracks.d.ts.map +1 -0
  137. package/dist/lib/perf-tracks.js +113 -0
  138. package/dist/lib/pointer-events.d.ts +7 -0
  139. package/dist/lib/pointer-events.d.ts.map +1 -0
  140. package/dist/lib/pointer-events.js +680 -0
  141. package/dist/lib/post-processing-resources.d.ts +12 -0
  142. package/dist/lib/post-processing-resources.d.ts.map +1 -0
  143. package/dist/lib/post-processing-resources.js +19 -0
  144. package/dist/lib/renderer-capability.d.ts +56 -0
  145. package/dist/lib/renderer-capability.d.ts.map +1 -0
  146. package/dist/lib/renderer-capability.js +116 -0
  147. package/dist/lib/scene-visibility.d.ts +15 -0
  148. package/dist/lib/scene-visibility.d.ts.map +1 -0
  149. package/dist/lib/scene-visibility.js +69 -0
  150. package/dist/lib/shadow-only.d.ts +16 -0
  151. package/dist/lib/shadow-only.d.ts.map +1 -1
  152. package/dist/lib/shadow-only.js +8 -17
  153. package/dist/lib/snapshot-pipeline.d.ts +9 -0
  154. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  155. package/dist/lib/snapshot-pipeline.js +33 -9
  156. package/dist/store/use-viewer.d.ts +7 -6
  157. package/dist/store/use-viewer.d.ts.map +1 -1
  158. package/dist/systems/ceiling/ceiling-system.js +1 -1
  159. package/dist/systems/door/door-animation-system.d.ts.map +1 -1
  160. package/dist/systems/door/door-animation-system.js +6 -9
  161. package/dist/systems/door/door-system.d.ts.map +1 -1
  162. package/dist/systems/door/door-system.js +13 -2
  163. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
  164. package/dist/systems/floor-elevation/floor-elevation-system.js +9 -0
  165. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  166. package/dist/systems/geometry/geometry-system.js +5 -28
  167. package/dist/systems/level/level-system.d.ts.map +1 -1
  168. package/dist/systems/level/level-system.js +8 -2
  169. package/dist/systems/level/level-utils.d.ts +9 -0
  170. package/dist/systems/level/level-utils.d.ts.map +1 -1
  171. package/dist/systems/level/level-utils.js +14 -0
  172. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
  173. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
  174. package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
  175. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  176. package/dist/systems/roof/roof-system.js +1196 -58
  177. package/dist/systems/scan/scan-system.js +6 -5
  178. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  179. package/dist/systems/slab/slab-system.js +1 -2
  180. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  181. package/dist/systems/stair/stair-system.js +9 -8
  182. package/dist/systems/surface-hole-geometry.d.ts +1 -1
  183. package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
  184. package/dist/systems/surface-hole-geometry.js +1 -1
  185. package/dist/systems/wall/level-miter-cache.d.ts +10 -0
  186. package/dist/systems/wall/level-miter-cache.d.ts.map +1 -0
  187. package/dist/systems/wall/level-miter-cache.js +40 -0
  188. package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
  189. package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
  190. package/dist/systems/wall/wall-build-lifecycle.js +74 -0
  191. package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
  192. package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
  193. package/dist/systems/wall/wall-cutout-cache.js +304 -0
  194. package/dist/systems/wall/wall-cutout.d.ts +24 -1
  195. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  196. package/dist/systems/wall/wall-cutout.js +33 -140
  197. package/dist/systems/wall/wall-material-variant.d.ts +31 -0
  198. package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
  199. package/dist/systems/wall/wall-material-variant.js +10 -0
  200. package/dist/systems/wall/wall-materials.d.ts +6 -0
  201. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  202. package/dist/systems/wall/wall-materials.js +29 -13
  203. package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
  204. package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
  205. package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
  206. package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
  207. package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
  208. package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
  209. package/dist/systems/wall/wall-system.d.ts +13 -2
  210. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  211. package/dist/systems/wall/wall-system.js +413 -148
  212. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  213. package/dist/systems/window/window-animation-system.js +9 -8
  214. package/dist/systems/window/window-system.d.ts +1 -0
  215. package/dist/systems/window/window-system.d.ts.map +1 -1
  216. package/dist/systems/window/window-system.js +16 -2
  217. package/package.json +18 -3
  218. package/dist/components/viewer/scene-bvh.test.d.ts +0 -2
  219. package/dist/components/viewer/scene-bvh.test.d.ts.map +0 -1
  220. package/dist/components/viewer/scene-bvh.test.js +0 -13
  221. package/dist/lib/csg-utils.test.d.ts +0 -2
  222. package/dist/lib/csg-utils.test.d.ts.map +0 -1
  223. package/dist/lib/csg-utils.test.js +0 -45
  224. package/dist/lib/materials.test.d.ts +0 -2
  225. package/dist/lib/materials.test.d.ts.map +0 -1
  226. package/dist/lib/materials.test.js +0 -25
  227. package/dist/lib/merged-outline-node.test.d.ts +0 -2
  228. package/dist/lib/merged-outline-node.test.d.ts.map +0 -1
  229. package/dist/lib/merged-outline-node.test.js +0 -22
  230. package/dist/lib/polygon-union.d.ts +0 -4
  231. package/dist/lib/polygon-union.d.ts.map +0 -1
  232. package/dist/lib/polygon-union.js +0 -289
  233. package/dist/lib/polygon-union.test.d.ts +0 -2
  234. package/dist/lib/polygon-union.test.d.ts.map +0 -1
  235. package/dist/lib/polygon-union.test.js +0 -105
  236. package/dist/lib/texture-reference.test.d.ts +0 -2
  237. package/dist/lib/texture-reference.test.d.ts.map +0 -1
  238. package/dist/lib/texture-reference.test.js +0 -86
  239. package/dist/store/use-viewer.test.d.ts +0 -2
  240. package/dist/store/use-viewer.test.d.ts.map +0 -1
  241. package/dist/store/use-viewer.test.js +0 -61
  242. package/dist/systems/door/door-animation.test.d.ts +0 -2
  243. package/dist/systems/door/door-animation.test.d.ts.map +0 -1
  244. package/dist/systems/door/door-animation.test.js +0 -51
  245. package/dist/systems/door/door-floor-alignment.test.d.ts +0 -2
  246. package/dist/systems/door/door-floor-alignment.test.d.ts.map +0 -1
  247. package/dist/systems/door/door-floor-alignment.test.js +0 -50
  248. package/dist/systems/geometry/geometry-system.test.d.ts +0 -2
  249. package/dist/systems/geometry/geometry-system.test.d.ts.map +0 -1
  250. package/dist/systems/geometry/geometry-system.test.js +0 -30
  251. package/dist/systems/slab/slab-system.test.d.ts +0 -2
  252. package/dist/systems/slab/slab-system.test.d.ts.map +0 -1
  253. package/dist/systems/slab/slab-system.test.js +0 -102
  254. package/dist/systems/wall/opening-cutout-geometry.test.d.ts +0 -2
  255. package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +0 -1
  256. package/dist/systems/wall/opening-cutout-geometry.test.js +0 -172
  257. package/dist/systems/wall/wall-opening-cutout.test.d.ts +0 -2
  258. package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +0 -1
  259. package/dist/systems/wall/wall-opening-cutout.test.js +0 -59
  260. package/dist/systems/wall/wall-support-extension.test.d.ts +0 -2
  261. package/dist/systems/wall/wall-support-extension.test.d.ts.map +0 -1
  262. package/dist/systems/wall/wall-support-extension.test.js +0 -116
  263. package/dist/systems/wall/wall-terrain-bottom.test.d.ts +0 -2
  264. package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +0 -1
  265. package/dist/systems/wall/wall-terrain-bottom.test.js +0 -34
@@ -1,146 +1,39 @@
1
- import { emitter, getWallFaceBandConfig, getWallPlaneTop, resolveLevelId, resolveWallEffectiveHeight, sceneRegistry, spatialGridManager, useScene, } from '@pascal-app/core';
1
+ import { emitter, sceneRegistry, useScene } from '@pascal-app/core';
2
2
  import { useFrame } from '@react-three/fiber';
3
- import { useEffect, useRef } from 'react';
3
+ import { useEffect, useMemo } from 'react';
4
4
  import { Vector3 } from 'three/webgpu';
5
- import useViewer from '../../store/use-viewer';
6
- import { getMaterialsForWall, getSelectionHighlightMaterials, getWallMaterialHash, } from './wall-materials';
7
- const tmpVec = new Vector3();
8
- const u = new Vector3();
5
+ import useViewer, {} from '../../store/use-viewer';
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
- function getWallHideState(wallNode, wallMesh, wallMode, cameraDir) {
11
- let hideWall = wallNode.frontSide === 'interior' && wallNode.backSide === 'interior';
12
- if (wallMode === 'up') {
13
- hideWall = false;
14
- }
15
- else if (wallMode === 'down') {
16
- hideWall = true;
17
- }
18
- else {
19
- wallMesh.getWorldDirection(v);
20
- if (v.dot(cameraDir) < 0) {
21
- if (wallNode.frontSide === 'exterior' && wallNode.backSide !== 'exterior') {
22
- hideWall = true;
23
- }
24
- }
25
- else if (wallNode.backSide === 'exterior' && wallNode.frontSide !== 'exterior') {
26
- hideWall = true;
27
- }
28
- }
29
- return hideWall;
10
+ export const WALL_CUTOUT_FRAME_PRIORITY = 0;
11
+ export function runWallCutoutFrame(cache, { camera, clock }) {
12
+ cache.update(camera, clock.elapsedTime);
30
13
  }
31
- function sameMaterialArray(a, b) {
32
- return Array.isArray(a) && a.length === b.length && a.every((material, i) => material === b[i]);
14
+ /**
15
+ * Whether a wall should be hidden or see-through for the current camera and
16
+ * wall mode. Pure: reads only its arguments and the mesh's world direction.
17
+ *
18
+ * Exported so hosts rendering their own layers inside `<Viewer>` can match
19
+ * these semantics instead of re-deriving the facing test or inferring state
20
+ * from the assigned material variant.
21
+ */
22
+ export function getWallHideState(wallNode, wallMesh, wallMode, cameraDir) {
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);
33
29
  }
34
- export const WallCutout = () => {
35
- const lastCameraPosition = useRef(new Vector3());
36
- const lastCameraTarget = useRef(new Vector3());
37
- const lastUpdateTime = useRef(0);
38
- const lastWallMode = useRef(useViewer.getState().wallMode);
39
- const lastShading = useRef(useViewer.getState().shading);
40
- const lastNumberOfWalls = useRef(0);
41
- const lastHighlightKey = useRef('');
42
- const lastWallAppearanceKey = useRef('');
43
- const lastTextures = useRef(useViewer.getState().textures);
44
- const lastColorPreset = useRef(useViewer.getState().colorPreset);
45
- const lastSceneTheme = useRef(useViewer.getState().sceneTheme);
46
- useFrame(({ camera, clock }) => {
47
- const wallMode = useViewer.getState().wallMode;
48
- const shading = useViewer.getState().shading;
49
- const textures = useViewer.getState().textures;
50
- const colorPreset = useViewer.getState().colorPreset;
51
- const sceneTheme = useViewer.getState().sceneTheme;
52
- const selectedIds = useViewer.getState().selection.selectedIds;
53
- const previewSelectedIds = useViewer.getState().previewSelectedIds;
54
- const hoveredId = useViewer.getState().hoveredId;
55
- const hoverHighlightMode = useViewer.getState().hoverHighlightMode;
56
- const sceneState = useScene.getState();
57
- const currentTime = clock.elapsedTime;
58
- const currentCameraPosition = camera.position;
59
- camera.getWorldDirection(tmpVec);
60
- tmpVec.add(currentCameraPosition);
61
- const highlightedWallIds = new Set([...selectedIds, ...previewSelectedIds].filter((id) => sceneState.nodes[id]?.type === 'wall'));
62
- const deleteHoveredWallId = hoverHighlightMode === 'delete' &&
63
- hoveredId &&
64
- sceneState.nodes[hoveredId]?.type === 'wall'
65
- ? hoveredId
66
- : null;
67
- const highlightKey = `${Array.from(highlightedWallIds).sort().join('|')}::${deleteHoveredWallId ?? ''}`;
68
- const wallAppearanceKey = Array.from(sceneRegistry.byType.wall)
69
- .sort()
70
- .map((wallId) => {
71
- const wallNode = sceneState.nodes[wallId];
72
- if (wallNode?.type !== 'wall')
73
- return `${wallId}:missing`;
74
- return `${wallId}:${getWallMaterialHash(wallNode, shading, sceneState.materials)}:${JSON.stringify(wallNode.faceBands ?? null)}`;
75
- })
76
- .join('|');
77
- const distanceMoved = currentCameraPosition.distanceTo(lastCameraPosition.current);
78
- const directionChanged = tmpVec.distanceTo(lastCameraTarget.current);
79
- const timeSinceUpdate = currentTime - lastUpdateTime.current;
80
- if (((distanceMoved > 0.5 || directionChanged > 0.3) && timeSinceUpdate > 0.1) ||
81
- lastWallMode.current !== wallMode ||
82
- lastShading.current !== shading ||
83
- lastTextures.current !== textures ||
84
- lastColorPreset.current !== colorPreset ||
85
- lastSceneTheme.current !== sceneTheme ||
86
- sceneRegistry.byType.wall.size !== lastNumberOfWalls.current ||
87
- lastHighlightKey.current !== highlightKey ||
88
- lastWallAppearanceKey.current !== wallAppearanceKey) {
89
- lastCameraPosition.current.copy(currentCameraPosition);
90
- lastCameraTarget.current.copy(tmpVec);
91
- lastUpdateTime.current = currentTime;
92
- camera.getWorldDirection(u);
93
- const walls = sceneRegistry.byType.wall;
94
- walls.forEach((wallId) => {
95
- const wallMesh = sceneRegistry.nodes.get(wallId);
96
- if (!wallMesh)
97
- return;
98
- const wallNode = sceneState.nodes[wallId];
99
- if (wallNode?.type !== 'wall')
100
- return;
101
- const hideWall = getWallHideState(wallNode, wallMesh, wallMode, u);
102
- const isDeleteHighlighted = deleteHoveredWallId === wallId;
103
- const isSelectionHighlighted = !isDeleteHighlighted && highlightedWallIds.has(wallId);
104
- const levelId = resolveLevelId(wallNode, sceneState.nodes);
105
- const support = spatialGridManager.getSlabSupportForWall(levelId, wallNode.start, wallNode.end, wallNode.curveOffset ?? 0, wallNode.thickness, wallNode.supportSlabId);
106
- const effectiveWallHeight = resolveWallEffectiveHeight(wallNode, getWallPlaneTop(wallNode, levelId, sceneState.nodes), support.elevation);
107
- const shouldSelectionHighlight = isSelectionHighlighted && !getWallFaceBandConfig(wallNode, effectiveWallHeight).enabled;
108
- const materials = getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme, sceneState.materials);
109
- if (wallMode === 'translucent') {
110
- ;
111
- wallMesh.material = isDeleteHighlighted
112
- ? materials.deleteTranslucent
113
- : shouldSelectionHighlight
114
- ? getSelectionHighlightMaterials(materials.translucent)
115
- : materials.translucent;
116
- }
117
- else if (hideWall) {
118
- ;
119
- wallMesh.material = isDeleteHighlighted
120
- ? materials.deleteInvisible
121
- : shouldSelectionHighlight
122
- ? getSelectionHighlightMaterials(materials.invisible)
123
- : materials.invisible;
124
- }
125
- else {
126
- ;
127
- wallMesh.material = isDeleteHighlighted
128
- ? materials.deleteVisible
129
- : shouldSelectionHighlight
130
- ? getSelectionHighlightMaterials(materials.visible)
131
- : materials.visible;
132
- }
133
- });
134
- lastWallMode.current = wallMode;
135
- lastShading.current = shading;
136
- lastTextures.current = textures;
137
- lastColorPreset.current = colorPreset;
138
- lastSceneTheme.current = sceneTheme;
139
- lastNumberOfWalls.current = sceneRegistry.byType.wall.size;
140
- lastHighlightKey.current = highlightKey;
141
- lastWallAppearanceKey.current = wallAppearanceKey;
142
- }
143
- });
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);
144
37
  useEffect(() => {
145
38
  const snapshot = new Map();
146
39
  const restoreForCapture = () => {
@@ -151,7 +44,7 @@ export const WallCutout = () => {
151
44
  const wallNode = useScene.getState().nodes[wallId];
152
45
  if (wallNode?.type !== 'wall')
153
46
  return;
154
- const mats = getMaterialsForWall(wallNode, useViewer.getState().shading, useViewer.getState().textures, useViewer.getState().colorPreset, useViewer.getState().sceneTheme, useScene.getState().materials);
47
+ const mats = getMaterialsForWall(wallNode, viewerStore.getState().shading, viewerStore.getState().textures, viewerStore.getState().colorPreset, viewerStore.getState().sceneTheme, useScene.getState().materials);
155
48
  const current = wallMesh.material;
156
49
  snapshot.set(wallMesh, current);
157
50
  if (current === mats.deleteVisible) {
@@ -178,6 +71,6 @@ export const WallCutout = () => {
178
71
  emitter.off('thumbnail:before-capture', restoreForCapture);
179
72
  emitter.off('thumbnail:after-capture', reapplyAfterCapture);
180
73
  };
181
- }, []);
74
+ }, [viewerStore]);
182
75
  return null;
183
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,EAKhB,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;AAiSD,wFAAwF;AACxF,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAE9F;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"}
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 (a `library:` ref is static catalog content, so
78
- // its id alone is enough). Falls back to the legacy signature when unmigrated.
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
- function getSelectionHighlightMaterial(base) {
206
+ const hoverHighlightCache = new WeakMap();
207
+ function getEmissiveHighlightMaterial(base, cache, emissiveBlend, emissiveIntensity) {
198
208
  const baseMap = base.map ?? null;
199
- const cached = selectionHighlightCache.get(base);
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, SELECTION_EMISSIVE_INTENSITY);
224
+ clone.emissiveIntensity = Math.max(clone.emissiveIntensity ?? 0, emissiveIntensity);
217
225
  }
218
226
  clone.needsUpdate = true;
219
- selectionHighlightCache.set(base, { clone, map: baseMap });
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(getSelectionHighlightMaterial);
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,7 +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
+ 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;
12
+ /** Rebuilds this system still owes — neighbours deferred during a drag. */
13
+ export declare function getPendingWallRebuildCount(): number;
3
14
  export declare const WallSystem: () => null;
15
+ export declare function runWallBuildFrame(): void;
4
16
  type WallTerrainBottomSampler = (x: number, z: number) => number | null;
5
17
  export declare function generateExtrudedWall(wallNode: WallNode, childrenNodes: AnyNode[], miterData: WallMiterData, slabElevation?: number, baseElevation?: number, baseSegments?: readonly WallSlabSupportSegment[], storeyHeight?: number, terrainBottomAt?: WallTerrainBottomSampler): THREE.BufferGeometry;
6
- export {};
7
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,EA2BZ,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,sBAAsB,EAI5B,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqd9B,eAAO,MAAM,UAAU,YAwJtB,CAAA;AAgND,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,CA0OtB"}
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"}