@pascal-app/viewer 0.8.0 → 0.9.2

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 (284) hide show
  1. package/dist/components/error-boundary.d.ts +6 -0
  2. package/dist/components/error-boundary.d.ts.map +1 -1
  3. package/dist/components/error-boundary.js +6 -0
  4. package/dist/components/renderers/node-renderer.d.ts +6 -2
  5. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  6. package/dist/components/renderers/node-renderer.js +41 -22
  7. package/dist/components/renderers/parametric-node-renderer.d.ts +5 -0
  8. package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -0
  9. package/dist/components/renderers/parametric-node-renderer.js +37 -0
  10. package/dist/components/renderers/scene-renderer.d.ts +1 -1
  11. package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
  12. package/dist/components/viewer/bvh-ecctrl.d.ts +77 -0
  13. package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -0
  14. package/dist/components/viewer/bvh-ecctrl.js +552 -0
  15. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  16. package/dist/components/viewer/frame-limiter.js +39 -4
  17. package/dist/components/viewer/glb-interactive.d.ts +41 -0
  18. package/dist/components/viewer/glb-interactive.d.ts.map +1 -0
  19. package/dist/components/viewer/glb-interactive.js +394 -0
  20. package/dist/components/viewer/glb-reference-nodes.d.ts +29 -0
  21. package/dist/components/viewer/glb-reference-nodes.d.ts.map +1 -0
  22. package/dist/components/viewer/glb-reference-nodes.js +80 -0
  23. package/dist/components/viewer/glb-replace-instances.d.ts +18 -0
  24. package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -0
  25. package/dist/components/viewer/glb-replace-instances.js +61 -0
  26. package/dist/components/viewer/glb-scene.d.ts +54 -0
  27. package/dist/components/viewer/glb-scene.d.ts.map +1 -0
  28. package/dist/components/viewer/glb-scene.js +920 -0
  29. package/dist/components/viewer/glb-walkthrough-controller.d.ts +12 -0
  30. package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -0
  31. package/dist/components/viewer/glb-walkthrough-controller.js +357 -0
  32. package/dist/components/viewer/index.d.ts +56 -3
  33. package/dist/components/viewer/index.d.ts.map +1 -1
  34. package/dist/components/viewer/index.js +280 -65
  35. package/dist/components/viewer/lights.d.ts +1 -1
  36. package/dist/components/viewer/lights.d.ts.map +1 -1
  37. package/dist/components/viewer/lights.js +201 -43
  38. package/dist/components/viewer/perf-monitor.d.ts +1 -1
  39. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  40. package/dist/components/viewer/perf-monitor.js +115 -12
  41. package/dist/components/viewer/post-processing.d.ts +7 -1
  42. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  43. package/dist/components/viewer/post-processing.js +395 -63
  44. package/dist/components/viewer/registered-systems.d.ts +12 -0
  45. package/dist/components/viewer/registered-systems.d.ts.map +1 -0
  46. package/dist/components/viewer/registered-systems.js +49 -0
  47. package/dist/components/viewer/scene-bvh.d.ts +18 -0
  48. package/dist/components/viewer/scene-bvh.d.ts.map +1 -0
  49. package/dist/components/viewer/scene-bvh.js +97 -0
  50. package/dist/components/viewer/scene-bvh.test.d.ts +2 -0
  51. package/dist/components/viewer/scene-bvh.test.d.ts.map +1 -0
  52. package/dist/components/viewer/scene-bvh.test.js +13 -0
  53. package/dist/components/viewer/scene-environment.d.ts +2 -0
  54. package/dist/components/viewer/scene-environment.d.ts.map +1 -0
  55. package/dist/components/viewer/scene-environment.js +79 -0
  56. package/dist/components/viewer/selection-manager.d.ts +1 -1
  57. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  58. package/dist/components/viewer/selection-manager.js +37 -14
  59. package/dist/components/viewer/viewer-camera.d.ts +1 -1
  60. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  61. package/dist/components/viewer/walkthrough-controls.d.ts +2 -1
  62. package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
  63. package/dist/components/viewer/walkthrough-controls.js +20 -0
  64. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  65. package/dist/hooks/use-gltf-ktx2.js +3 -22
  66. package/dist/hooks/use-node-events.d.ts +6 -74
  67. package/dist/hooks/use-node-events.d.ts.map +1 -1
  68. package/dist/hooks/use-node-events.js +28 -8
  69. package/dist/index.d.ts +48 -5
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +82 -4
  72. package/dist/lib/backdrop.d.ts +33 -0
  73. package/dist/lib/backdrop.d.ts.map +1 -0
  74. package/dist/lib/backdrop.js +92 -0
  75. package/dist/lib/box-uv.d.ts +13 -0
  76. package/dist/lib/box-uv.d.ts.map +1 -0
  77. package/dist/lib/box-uv.js +32 -0
  78. package/dist/lib/csg-utils.d.ts +10 -0
  79. package/dist/lib/csg-utils.d.ts.map +1 -0
  80. package/dist/lib/csg-utils.js +93 -0
  81. package/dist/lib/csg-utils.test.d.ts +2 -0
  82. package/dist/lib/csg-utils.test.d.ts.map +1 -0
  83. package/dist/lib/csg-utils.test.js +45 -0
  84. package/dist/lib/drawable-geometry.d.ts +21 -0
  85. package/dist/lib/drawable-geometry.d.ts.map +1 -0
  86. package/dist/lib/drawable-geometry.js +22 -0
  87. package/dist/lib/edge-style.d.ts +4 -0
  88. package/dist/lib/edge-style.d.ts.map +1 -0
  89. package/dist/lib/edge-style.js +31 -0
  90. package/dist/lib/gpu-perf.d.ts +4 -0
  91. package/dist/lib/gpu-perf.d.ts.map +1 -0
  92. package/dist/lib/gpu-perf.js +23 -0
  93. package/dist/lib/ink-edges.d.ts +9 -0
  94. package/dist/lib/ink-edges.d.ts.map +1 -0
  95. package/dist/lib/ink-edges.js +49 -0
  96. package/dist/lib/isolation.d.ts +39 -0
  97. package/dist/lib/isolation.d.ts.map +1 -0
  98. package/dist/lib/isolation.js +103 -0
  99. package/dist/lib/ktx2-loader.d.ts +43 -0
  100. package/dist/lib/ktx2-loader.d.ts.map +1 -0
  101. package/dist/lib/ktx2-loader.js +136 -0
  102. package/dist/lib/layers.d.ts +28 -0
  103. package/dist/lib/layers.d.ts.map +1 -1
  104. package/dist/lib/layers.js +28 -0
  105. package/dist/lib/materials.d.ts +38 -15
  106. package/dist/lib/materials.d.ts.map +1 -1
  107. package/dist/lib/materials.js +364 -72
  108. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  109. package/dist/lib/merged-outline-node.js +7 -0
  110. package/dist/lib/polygon-union.d.ts +4 -0
  111. package/dist/lib/polygon-union.d.ts.map +1 -0
  112. package/dist/lib/polygon-union.js +289 -0
  113. package/dist/lib/polygon-union.test.d.ts +2 -0
  114. package/dist/lib/polygon-union.test.d.ts.map +1 -0
  115. package/dist/lib/polygon-union.test.js +105 -0
  116. package/dist/lib/scene-themes.d.ts +30 -0
  117. package/dist/lib/scene-themes.d.ts.map +1 -0
  118. package/dist/lib/scene-themes.js +202 -0
  119. package/dist/lib/shadow-only.d.ts +4 -0
  120. package/dist/lib/shadow-only.d.ts.map +1 -0
  121. package/dist/lib/shadow-only.js +36 -0
  122. package/dist/lib/tsl-compat.d.ts +12 -0
  123. package/dist/lib/tsl-compat.d.ts.map +1 -0
  124. package/dist/lib/tsl-compat.js +13 -0
  125. package/dist/store/use-viewer.d.ts +92 -12
  126. package/dist/store/use-viewer.d.ts.map +1 -1
  127. package/dist/store/use-viewer.js +201 -6
  128. package/dist/store/use-viewer.test.d.ts +2 -0
  129. package/dist/store/use-viewer.test.d.ts.map +1 -0
  130. package/dist/store/use-viewer.test.js +42 -0
  131. package/dist/systems/ceiling/ceiling-system.d.ts +6 -2
  132. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  133. package/dist/systems/ceiling/ceiling-system.js +66 -13
  134. package/dist/systems/door/door-animation.test.d.ts +2 -0
  135. package/dist/systems/door/door-animation.test.d.ts.map +1 -0
  136. package/dist/systems/door/door-animation.test.js +51 -0
  137. package/dist/systems/door/door-floor-alignment.test.d.ts +2 -0
  138. package/dist/systems/door/door-floor-alignment.test.d.ts.map +1 -0
  139. package/dist/systems/door/door-floor-alignment.test.js +50 -0
  140. package/dist/systems/door/door-system.d.ts +24 -0
  141. package/dist/systems/door/door-system.d.ts.map +1 -1
  142. package/dist/systems/door/door-system.js +864 -180
  143. package/dist/systems/elevator/elevator-interaction-system.d.ts +2 -0
  144. package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -0
  145. package/dist/systems/elevator/elevator-interaction-system.js +62 -0
  146. package/dist/systems/fence/fence-system.d.ts +20 -0
  147. package/dist/systems/fence/fence-system.d.ts.map +1 -1
  148. package/dist/systems/fence/fence-system.js +229 -57
  149. package/dist/systems/floor-elevation/floor-elevation-system.d.ts +20 -0
  150. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -0
  151. package/dist/systems/floor-elevation/floor-elevation-system.js +78 -0
  152. package/dist/systems/geometry/geometry-system.d.ts +41 -0
  153. package/dist/systems/geometry/geometry-system.d.ts.map +1 -0
  154. package/dist/systems/geometry/geometry-system.js +348 -0
  155. package/dist/systems/geometry/geometry-system.test.d.ts +2 -0
  156. package/dist/systems/geometry/geometry-system.test.d.ts.map +1 -0
  157. package/dist/systems/geometry/geometry-system.test.js +30 -0
  158. package/dist/systems/interactive/control-widget.d.ts +10 -0
  159. package/dist/systems/interactive/control-widget.d.ts.map +1 -0
  160. package/dist/systems/interactive/control-widget.js +35 -0
  161. package/dist/systems/interactive/interactive-system.d.ts +1 -1
  162. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  163. package/dist/systems/interactive/interactive-system.js +55 -56
  164. package/dist/systems/item/item-system.d.ts +7 -0
  165. package/dist/systems/item/item-system.d.ts.map +1 -1
  166. package/dist/systems/item/item-system.js +28 -17
  167. package/dist/systems/item-light/item-light-system.d.ts +1 -1
  168. package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
  169. package/dist/systems/item-light/item-light-system.js +18 -5
  170. package/dist/systems/level/level-system.d.ts.map +1 -1
  171. package/dist/systems/level/level-system.js +28 -4
  172. package/dist/systems/level/level-utils.d.ts +0 -3
  173. package/dist/systems/level/level-utils.d.ts.map +1 -1
  174. package/dist/systems/level/level-utils.js +2 -41
  175. package/dist/systems/roof/roof-materials.d.ts +3 -2
  176. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  177. package/dist/systems/roof/roof-materials.js +59 -14
  178. package/dist/systems/roof/roof-system.d.ts +46 -6
  179. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  180. package/dist/systems/roof/roof-system.js +1365 -170
  181. package/dist/systems/slab/slab-system.d.ts +5 -3
  182. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  183. package/dist/systems/slab/slab-system.js +204 -68
  184. package/dist/systems/slab/slab-system.test.d.ts +2 -0
  185. package/dist/systems/slab/slab-system.test.d.ts.map +1 -0
  186. package/dist/systems/slab/slab-system.test.js +63 -0
  187. package/dist/systems/stair/stair-materials.d.ts +4 -3
  188. package/dist/systems/stair/stair-materials.d.ts.map +1 -1
  189. package/dist/systems/stair/stair-materials.js +30 -15
  190. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  191. package/dist/systems/stair/stair-system.js +55 -63
  192. package/dist/systems/surface-hole-geometry.d.ts +3 -0
  193. package/dist/systems/surface-hole-geometry.d.ts.map +1 -0
  194. package/dist/systems/surface-hole-geometry.js +4 -0
  195. package/dist/systems/wall/opening-cutout-geometry.d.ts +31 -0
  196. package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -0
  197. package/dist/systems/wall/opening-cutout-geometry.js +167 -0
  198. package/dist/systems/wall/opening-cutout-geometry.test.d.ts +2 -0
  199. package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +1 -0
  200. package/dist/systems/wall/opening-cutout-geometry.test.js +172 -0
  201. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  202. package/dist/systems/wall/wall-cutout.js +62 -17
  203. package/dist/systems/wall/wall-materials.d.ts +12 -7
  204. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  205. package/dist/systems/wall/wall-materials.js +279 -46
  206. package/dist/systems/wall/wall-opening-cutout.test.d.ts +2 -0
  207. package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +1 -0
  208. package/dist/systems/wall/wall-opening-cutout.test.js +59 -0
  209. package/dist/systems/wall/wall-support-extension.test.d.ts +2 -0
  210. package/dist/systems/wall/wall-support-extension.test.d.ts.map +1 -0
  211. package/dist/systems/wall/wall-support-extension.test.js +48 -0
  212. package/dist/systems/wall/wall-system.d.ts +2 -2
  213. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  214. package/dist/systems/wall/wall-system.js +493 -193
  215. package/dist/systems/window/window-animation-system.d.ts +12 -0
  216. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  217. package/dist/systems/window/window-animation-system.js +16 -5
  218. package/dist/systems/window/window-system.d.ts +7 -0
  219. package/dist/systems/window/window-system.d.ts.map +1 -1
  220. package/dist/systems/window/window-system.js +308 -18
  221. package/package.json +5 -6
  222. package/dist/components/renderers/building/building-renderer.d.ts +0 -5
  223. package/dist/components/renderers/building/building-renderer.d.ts.map +0 -1
  224. package/dist/components/renderers/building/building-renderer.js +0 -11
  225. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +0 -5
  226. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +0 -1
  227. package/dist/components/renderers/ceiling/ceiling-renderer.js +0 -58
  228. package/dist/components/renderers/column/column-renderer.d.ts +0 -5
  229. package/dist/components/renderers/column/column-renderer.d.ts.map +0 -1
  230. package/dist/components/renderers/column/column-renderer.js +0 -597
  231. package/dist/components/renderers/door/door-renderer.d.ts +0 -5
  232. package/dist/components/renderers/door/door-renderer.d.ts.map +0 -1
  233. package/dist/components/renderers/door/door-renderer.js +0 -16
  234. package/dist/components/renderers/fence/fence-renderer.d.ts +0 -5
  235. package/dist/components/renderers/fence/fence-renderer.d.ts.map +0 -1
  236. package/dist/components/renderers/fence/fence-renderer.js +0 -15
  237. package/dist/components/renderers/guide/guide-renderer.d.ts +0 -5
  238. package/dist/components/renderers/guide/guide-renderer.d.ts.map +0 -1
  239. package/dist/components/renderers/guide/guide-renderer.js +0 -38
  240. package/dist/components/renderers/item/item-renderer.d.ts +0 -5
  241. package/dist/components/renderers/item/item-renderer.d.ts.map +0 -1
  242. package/dist/components/renderers/item/item-renderer.js +0 -172
  243. package/dist/components/renderers/level/level-renderer.d.ts +0 -5
  244. package/dist/components/renderers/level/level-renderer.d.ts.map +0 -1
  245. package/dist/components/renderers/level/level-renderer.js +0 -11
  246. package/dist/components/renderers/roof/roof-materials.d.ts +0 -4
  247. package/dist/components/renderers/roof/roof-materials.d.ts.map +0 -1
  248. package/dist/components/renderers/roof/roof-materials.js +0 -16
  249. package/dist/components/renderers/roof/roof-renderer.d.ts +0 -5
  250. package/dist/components/renderers/roof/roof-renderer.d.ts.map +0 -1
  251. package/dist/components/renderers/roof/roof-renderer.js +0 -32
  252. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +0 -5
  253. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +0 -1
  254. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +0 -40
  255. package/dist/components/renderers/scan/scan-renderer.d.ts +0 -5
  256. package/dist/components/renderers/scan/scan-renderer.d.ts.map +0 -1
  257. package/dist/components/renderers/scan/scan-renderer.js +0 -54
  258. package/dist/components/renderers/site/site-renderer.d.ts +0 -5
  259. package/dist/components/renderers/site/site-renderer.d.ts.map +0 -1
  260. package/dist/components/renderers/site/site-renderer.js +0 -106
  261. package/dist/components/renderers/slab/slab-renderer.d.ts +0 -5
  262. package/dist/components/renderers/slab/slab-renderer.d.ts.map +0 -1
  263. package/dist/components/renderers/slab/slab-renderer.js +0 -58
  264. package/dist/components/renderers/spawn/spawn-renderer.d.ts +0 -5
  265. package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +0 -1
  266. package/dist/components/renderers/spawn/spawn-renderer.js +0 -32
  267. package/dist/components/renderers/stair/stair-renderer.d.ts +0 -5
  268. package/dist/components/renderers/stair/stair-renderer.d.ts.map +0 -1
  269. package/dist/components/renderers/stair/stair-renderer.js +0 -603
  270. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts +0 -5
  271. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +0 -1
  272. package/dist/components/renderers/stair-segment/stair-segment-renderer.js +0 -51
  273. package/dist/components/renderers/wall/wall-renderer.d.ts +0 -5
  274. package/dist/components/renderers/wall/wall-renderer.d.ts.map +0 -1
  275. package/dist/components/renderers/wall/wall-renderer.js +0 -16
  276. package/dist/components/renderers/window/window-renderer.d.ts +0 -5
  277. package/dist/components/renderers/window/window-renderer.d.ts.map +0 -1
  278. package/dist/components/renderers/window/window-renderer.js +0 -21
  279. package/dist/components/renderers/zone/zone-renderer.d.ts +0 -5
  280. package/dist/components/renderers/zone/zone-renderer.d.ts.map +0 -1
  281. package/dist/components/renderers/zone/zone-renderer.js +0 -181
  282. package/dist/components/viewer/ground-occluder.d.ts +0 -2
  283. package/dist/components/viewer/ground-occluder.d.ts.map +0 -1
  284. package/dist/components/viewer/ground-occluder.js +0 -79
@@ -1,9 +1,9 @@
1
- import { emitter, sceneRegistry, useScene } from '@pascal-app/core';
1
+ import { emitter, getWallFaceBandConfig, sceneRegistry, useScene, } from '@pascal-app/core';
2
2
  import { useFrame } from '@react-three/fiber';
3
3
  import { useEffect, useRef } from 'react';
4
4
  import { Vector3 } from 'three/webgpu';
5
5
  import useViewer from '../../store/use-viewer';
6
- import { getMaterialsForWall } from './wall-materials';
6
+ import { getMaterialsForWall, getSelectionHighlightMaterials, getWallMaterialHash, } from './wall-materials';
7
7
  const tmpVec = new Vector3();
8
8
  const u = new Vector3();
9
9
  const v = new Vector3();
@@ -28,37 +28,64 @@ function getWallHideState(wallNode, wallMesh, wallMode, cameraDir) {
28
28
  }
29
29
  return hideWall;
30
30
  }
31
+ function sameMaterialArray(a, b) {
32
+ return Array.isArray(a) && a.length === b.length && a.every((material, i) => material === b[i]);
33
+ }
31
34
  export const WallCutout = () => {
32
35
  const lastCameraPosition = useRef(new Vector3());
33
36
  const lastCameraTarget = useRef(new Vector3());
34
37
  const lastUpdateTime = useRef(0);
35
38
  const lastWallMode = useRef(useViewer.getState().wallMode);
39
+ const lastShading = useRef(useViewer.getState().shading);
36
40
  const lastNumberOfWalls = useRef(0);
37
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);
38
46
  useFrame(({ camera, clock }) => {
39
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;
40
52
  const selectedIds = useViewer.getState().selection.selectedIds;
41
53
  const previewSelectedIds = useViewer.getState().previewSelectedIds;
42
54
  const hoveredId = useViewer.getState().hoveredId;
43
55
  const hoverHighlightMode = useViewer.getState().hoverHighlightMode;
56
+ const sceneState = useScene.getState();
44
57
  const currentTime = clock.elapsedTime;
45
58
  const currentCameraPosition = camera.position;
46
59
  camera.getWorldDirection(tmpVec);
47
60
  tmpVec.add(currentCameraPosition);
48
- const highlightedWallIds = new Set([...selectedIds, ...previewSelectedIds].filter((id) => useScene.getState().nodes[id]?.type === 'wall'));
61
+ const highlightedWallIds = new Set([...selectedIds, ...previewSelectedIds].filter((id) => sceneState.nodes[id]?.type === 'wall'));
49
62
  const deleteHoveredWallId = hoverHighlightMode === 'delete' &&
50
63
  hoveredId &&
51
- useScene.getState().nodes[hoveredId]?.type === 'wall'
64
+ sceneState.nodes[hoveredId]?.type === 'wall'
52
65
  ? hoveredId
53
66
  : null;
54
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('|');
55
77
  const distanceMoved = currentCameraPosition.distanceTo(lastCameraPosition.current);
56
78
  const directionChanged = tmpVec.distanceTo(lastCameraTarget.current);
57
79
  const timeSinceUpdate = currentTime - lastUpdateTime.current;
58
80
  if (((distanceMoved > 0.5 || directionChanged > 0.3) && timeSinceUpdate > 0.1) ||
59
81
  lastWallMode.current !== wallMode ||
82
+ lastShading.current !== shading ||
83
+ lastTextures.current !== textures ||
84
+ lastColorPreset.current !== colorPreset ||
85
+ lastSceneTheme.current !== sceneTheme ||
60
86
  sceneRegistry.byType.wall.size !== lastNumberOfWalls.current ||
61
- lastHighlightKey.current !== highlightKey) {
87
+ lastHighlightKey.current !== highlightKey ||
88
+ lastWallAppearanceKey.current !== wallAppearanceKey) {
62
89
  lastCameraPosition.current.copy(currentCameraPosition);
63
90
  lastCameraTarget.current.copy(tmpVec);
64
91
  lastUpdateTime.current = currentTime;
@@ -68,33 +95,47 @@ export const WallCutout = () => {
68
95
  const wallMesh = sceneRegistry.nodes.get(wallId);
69
96
  if (!wallMesh)
70
97
  return;
71
- const wallNode = useScene.getState().nodes[wallId];
72
- if (!wallNode || wallNode.type !== 'wall')
98
+ const wallNode = sceneState.nodes[wallId];
99
+ if (wallNode?.type !== 'wall')
73
100
  return;
74
101
  const hideWall = getWallHideState(wallNode, wallMesh, wallMode, u);
75
102
  const isDeleteHighlighted = deleteHoveredWallId === wallId;
76
103
  const isSelectionHighlighted = !isDeleteHighlighted && highlightedWallIds.has(wallId);
77
- const materials = getMaterialsForWall(wallNode);
78
- if (hideWall) {
104
+ const shouldSelectionHighlight = isSelectionHighlighted && !getWallFaceBandConfig(wallNode).enabled;
105
+ const materials = getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme, sceneState.materials);
106
+ if (wallMode === 'translucent') {
107
+ ;
108
+ wallMesh.material = isDeleteHighlighted
109
+ ? materials.deleteTranslucent
110
+ : shouldSelectionHighlight
111
+ ? getSelectionHighlightMaterials(materials.translucent)
112
+ : materials.translucent;
113
+ }
114
+ else if (hideWall) {
79
115
  ;
80
116
  wallMesh.material = isDeleteHighlighted
81
117
  ? materials.deleteInvisible
82
- : isSelectionHighlighted
83
- ? materials.highlightedInvisible
118
+ : shouldSelectionHighlight
119
+ ? getSelectionHighlightMaterials(materials.invisible)
84
120
  : materials.invisible;
85
121
  }
86
122
  else {
87
123
  ;
88
124
  wallMesh.material = isDeleteHighlighted
89
125
  ? materials.deleteVisible
90
- : isSelectionHighlighted
91
- ? materials.highlightedVisible
126
+ : shouldSelectionHighlight
127
+ ? getSelectionHighlightMaterials(materials.visible)
92
128
  : materials.visible;
93
129
  }
94
130
  });
95
131
  lastWallMode.current = wallMode;
132
+ lastShading.current = shading;
133
+ lastTextures.current = textures;
134
+ lastColorPreset.current = colorPreset;
135
+ lastSceneTheme.current = sceneTheme;
96
136
  lastNumberOfWalls.current = sceneRegistry.byType.wall.size;
97
137
  lastHighlightKey.current = highlightKey;
138
+ lastWallAppearanceKey.current = wallAppearanceKey;
98
139
  }
99
140
  });
100
141
  useEffect(() => {
@@ -105,17 +146,21 @@ export const WallCutout = () => {
105
146
  if (!wallMesh)
106
147
  return;
107
148
  const wallNode = useScene.getState().nodes[wallId];
108
- if (!wallNode || wallNode.type !== 'wall')
149
+ if (wallNode?.type !== 'wall')
109
150
  return;
110
- const mats = getMaterialsForWall(wallNode);
151
+ const mats = getMaterialsForWall(wallNode, useViewer.getState().shading, useViewer.getState().textures, useViewer.getState().colorPreset, useViewer.getState().sceneTheme, useScene.getState().materials);
111
152
  const current = wallMesh.material;
112
153
  snapshot.set(wallMesh, current);
113
- if (current === mats.highlightedVisible || current === mats.deleteVisible) {
154
+ if (current === mats.deleteVisible) {
114
155
  wallMesh.material = mats.visible;
115
156
  }
116
- else if (current === mats.highlightedInvisible || current === mats.deleteInvisible) {
157
+ else if (current === mats.deleteInvisible) {
117
158
  wallMesh.material = mats.invisible;
118
159
  }
160
+ else if (current === mats.deleteTranslucent ||
161
+ sameMaterialArray(current, getSelectionHighlightMaterials(mats.translucent))) {
162
+ wallMesh.material = mats.translucent;
163
+ }
119
164
  });
120
165
  };
121
166
  const reapplyAfterCapture = () => {
@@ -1,16 +1,21 @@
1
- import { type WallNode } from '@pascal-app/core';
1
+ import { type SceneMaterial, type SceneMaterialId, type WallNode } from '@pascal-app/core';
2
2
  import { type Material } from 'three';
3
- export type WallMaterialArray = [Material, Material, Material];
3
+ import { type ColorPreset, type RenderShading } from '../../lib/materials';
4
+ type SceneMaterials = Record<SceneMaterialId, SceneMaterial> | undefined;
5
+ export type WallMaterialArray = Material[];
4
6
  export interface WallMaterials {
5
7
  visible: WallMaterialArray;
6
8
  invisible: WallMaterialArray;
9
+ translucent: WallMaterialArray;
7
10
  deleteVisible: WallMaterialArray;
8
11
  deleteInvisible: WallMaterialArray;
9
- highlightedVisible: WallMaterialArray;
10
- highlightedInvisible: WallMaterialArray;
12
+ deleteTranslucent: WallMaterialArray;
11
13
  materialHash: string;
12
14
  }
13
- export declare function getWallMaterialHash(wallNode: WallNode): string;
14
- export declare function getMaterialsForWall(wallNode: WallNode): WallMaterials;
15
- export declare function getVisibleWallMaterials(wallNode: WallNode): WallMaterialArray;
15
+ /** Lazy light-emissive selection variant of a wall's material array (keeps texture). */
16
+ export declare function getSelectionHighlightMaterials(materials: WallMaterialArray): WallMaterialArray;
17
+ export declare function getWallMaterialHash(wallNode: WallNode, shading: RenderShading, sceneMaterials?: SceneMaterials): string;
18
+ export declare function getMaterialsForWall(wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string, sceneMaterials?: SceneMaterials): WallMaterials;
19
+ export declare function getVisibleWallMaterials(wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string, sceneMaterials?: SceneMaterials): WallMaterialArray;
20
+ export {};
16
21
  //# sourceMappingURL=wall-materials.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wall-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAwB5C,MAAM,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;AAE9D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,iBAAiB,CAAA;IAC1B,SAAS,EAAE,iBAAiB,CAAA;IAC5B,aAAa,EAAE,iBAAiB,CAAA;IAChC,eAAe,EAAE,iBAAiB,CAAA;IAClC,kBAAkB,EAAE,iBAAiB,CAAA;IACrC,oBAAoB,EAAE,iBAAiB,CAAA;IACvC,YAAY,EAAE,MAAM,CAAA;CACrB;AA4GD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAS9D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,aAAa,CA2DrE;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,iBAAiB,CAE7E"}
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,9 +1,9 @@
1
- import { getEffectiveWallSurfaceMaterial, getMaterialPresetByRef, getWallSurfaceMaterialSignature, resolveMaterial, } from '@pascal-app/core';
1
+ import { getEffectiveWallSurfaceMaterial, getMaterialPresetByRef, getWallSurfaceMaterialSignature, getWallSurfaceSideFromBandSlot, parseMaterialRef, resolveMaterial, WALL_SLOT_DEFAULT, WALL_SURFACE_SLOT_DEFAULTS, } from '@pascal-app/core';
2
2
  import { Color } from 'three';
3
3
  import { Fn, float, fract, length, mix, positionLocal, smoothstep, step, vec2 } from 'three/tsl';
4
- import { MeshStandardNodeMaterial } from 'three/webgpu';
5
- import { baseMaterial, createMaterial, createMaterialFromPresetRef } from '../../lib/materials';
6
- const DEFAULT_WALL_COLOR = '#f2f0ed';
4
+ import { MeshLambertNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
5
+ import { baseMaterial, createDefaultMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, resolveMaterialRef, resolveSurfaceColor, } from '../../lib/materials';
6
+ const DEFAULT_WALL_COLOR = '#e9e7e3';
7
7
  const WALL_HIGHLIGHT_PROFILES = {
8
8
  delete: {
9
9
  color: new Color('#dc2626'),
@@ -11,12 +11,6 @@ const WALL_HIGHLIGHT_PROFILES = {
11
11
  emissiveBlend: 0.92,
12
12
  emissiveIntensity: 0.46,
13
13
  },
14
- selection: {
15
- color: new Color('#818cf8'),
16
- blend: 0.32,
17
- emissiveBlend: 0.7,
18
- emissiveIntensity: 0.42,
19
- },
20
14
  };
21
15
  const wallMaterialCache = new Map();
22
16
  const dotPattern = Fn(() => {
@@ -30,14 +24,119 @@ const dotPattern = Fn(() => {
30
24
  const yFade = float(1).sub(smoothstep(float(0), fadeHeight, positionLocal.y));
31
25
  return dots.mul(yFade);
32
26
  });
33
- function getSurfaceVisibleMaterial(spec) {
27
+ function getSurfaceVisibleMaterial(spec, shading) {
34
28
  if (spec.materialPreset) {
35
- return createMaterialFromPresetRef(spec.materialPreset) ?? baseMaterial;
29
+ return createMaterialFromPresetRef(spec.materialPreset, shading) ?? baseMaterial(shading);
36
30
  }
37
31
  if (spec.material) {
38
- return createMaterial(spec.material);
32
+ return createMaterial(spec.material, shading);
33
+ }
34
+ return baseMaterial(shading);
35
+ }
36
+ function hasExplicitMaterial(spec) {
37
+ return Boolean(spec.materialPreset || spec.material);
38
+ }
39
+ // Resolve a wall face's declared default — a catalog `library:` finish or a
40
+ // flat colour — to a renderable material.
41
+ function resolveWallSlotDefault(slotDefault, shading) {
42
+ if (parseMaterialRef(slotDefault)?.kind === 'library') {
43
+ return createMaterialFromPresetRef(slotDefault, shading) ?? baseMaterial(shading);
44
+ }
45
+ return createDefaultMaterial(slotDefault, 0.9, shading);
46
+ }
47
+ // Slot-first resolution for one wall face, matching every other paintable kind:
48
+ // node.slots[side] ref → legacy inline fields → declared slot default.
49
+ // A dangling `scene:` ref (material deleted / copied across scenes) falls back
50
+ // to the declared default — it never blocks rendering (the dangling-ref rule).
51
+ function resolveWallFaceMaterial(wallNode, side, shading, sceneMaterials) {
52
+ const ref = wallNode.slots?.[side];
53
+ if (ref) {
54
+ return (resolveMaterialRef(ref, sceneMaterials, shading) ??
55
+ resolveWallSlotDefault(WALL_SLOT_DEFAULT[side], shading));
56
+ }
57
+ const spec = getEffectiveWallSurfaceMaterial(wallNode, side);
58
+ if (hasExplicitMaterial(spec)) {
59
+ return getSurfaceVisibleMaterial(spec, shading);
60
+ }
61
+ return resolveWallSlotDefault(WALL_SLOT_DEFAULT[side], shading);
62
+ }
63
+ function resolveWallSlotMaterial(wallNode, slotId, shading, sceneMaterials) {
64
+ const ref = wallNode.slots?.[slotId];
65
+ if (ref) {
66
+ return (resolveMaterialRef(ref, sceneMaterials, shading) ??
67
+ resolveWallSlotDefault(WALL_SURFACE_SLOT_DEFAULTS[slotId], shading));
68
+ }
69
+ const side = getWallSurfaceSideFromBandSlot(slotId);
70
+ if (side) {
71
+ return resolveWallFaceMaterial(wallNode, side, shading, sceneMaterials);
72
+ }
73
+ return resolveWallSlotDefault(WALL_SURFACE_SLOT_DEFAULTS[slotId], shading);
74
+ }
75
+ // Cache-key fragment for one face: the slot ref plus, for a `scene:` ref, the
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.
79
+ function wallFaceMaterialSignature(wallNode, side, sceneMaterials) {
80
+ const ref = wallNode.slots?.[side];
81
+ if (ref) {
82
+ const parsed = parseMaterialRef(ref);
83
+ if (parsed?.kind === 'scene') {
84
+ return JSON.stringify({
85
+ ref,
86
+ material: sceneMaterials?.[parsed.id]?.material ?? null,
87
+ });
88
+ }
89
+ return JSON.stringify({ ref });
90
+ }
91
+ return getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(wallNode, side));
92
+ }
93
+ function wallSlotMaterialSignature(wallNode, slotId, sceneMaterials) {
94
+ const ref = wallNode.slots?.[slotId];
95
+ if (ref) {
96
+ const parsed = parseMaterialRef(ref);
97
+ if (parsed?.kind === 'scene') {
98
+ return JSON.stringify({
99
+ ref,
100
+ material: sceneMaterials?.[parsed.id]?.material ?? null,
101
+ });
102
+ }
103
+ return JSON.stringify({ ref });
104
+ }
105
+ const side = getWallSurfaceSideFromBandSlot(slotId);
106
+ if (side)
107
+ return wallFaceMaterialSignature(wallNode, side, sceneMaterials);
108
+ return JSON.stringify({ default: WALL_SURFACE_SLOT_DEFAULTS[slotId] });
109
+ }
110
+ // Slot-first tint for the cutaway/invisible wall variant.
111
+ function resolveWallFaceColor(wallNode, side, sceneMaterials, fallback) {
112
+ const ref = wallNode.slots?.[side];
113
+ if (ref) {
114
+ const parsed = parseMaterialRef(ref);
115
+ if (parsed?.kind === 'library') {
116
+ return getMaterialPresetByRef(ref)?.mapProperties?.color ?? fallback;
117
+ }
118
+ if (parsed?.kind === 'scene') {
119
+ const sceneMaterial = sceneMaterials?.[parsed.id];
120
+ return sceneMaterial ? resolveMaterial(sceneMaterial.material).color : fallback;
121
+ }
122
+ return fallback;
123
+ }
124
+ return getSurfaceColor(getEffectiveWallSurfaceMaterial(wallNode, side), fallback);
125
+ }
126
+ function resolveWallSlotColor(wallNode, slotId, sceneMaterials, fallback) {
127
+ const ref = wallNode.slots?.[slotId];
128
+ if (ref) {
129
+ const parsed = parseMaterialRef(ref);
130
+ if (parsed?.kind === 'library') {
131
+ return getMaterialPresetByRef(ref)?.mapProperties?.color ?? fallback;
132
+ }
133
+ if (parsed?.kind === 'scene') {
134
+ const sceneMaterial = sceneMaterials?.[parsed.id];
135
+ return sceneMaterial ? resolveMaterial(sceneMaterial.material).color : fallback;
136
+ }
39
137
  }
40
- return baseMaterial;
138
+ const side = getWallSurfaceSideFromBandSlot(slotId);
139
+ return side ? resolveWallFaceColor(wallNode, side, sceneMaterials, fallback) : fallback;
41
140
  }
42
141
  function getSurfaceColor(spec, fallback = DEFAULT_WALL_COLOR) {
43
142
  const preset = getMaterialPresetByRef(spec.materialPreset);
@@ -70,14 +169,92 @@ function createHighlightedWallMaterial(material, kind) {
70
169
  highlightedMaterial.needsUpdate = true;
71
170
  return highlightedMaterial;
72
171
  }
73
- function createInvisibleWallMaterial(color) {
74
- return new MeshStandardNodeMaterial({
75
- transparent: true,
76
- opacityNode: mix(float(0.0), float(0.24), dotPattern()),
77
- color,
78
- depthWrite: false,
79
- emissive: color,
80
- });
172
+ // Light selection highlight for walls (walls are excluded from the generic
173
+ // editor selection highlight, so they need their own). Adds a gentle indigo
174
+ // emissive (no albedo tint) so the real material/texture stays readable with a
175
+ // soft "selected" glow. Two NodeMaterial-clone gotchas are handled:
176
+ // 1. `clone()` on the WebGPU backend drops the texture-map nodes → re-attach
177
+ // them from the source (shared by reference).
178
+ // 2. The wall's finish texture loads async, so an early clone has no map yet →
179
+ // cache keyed by the source `.map` and rebuild when it changes (self-heals
180
+ // once the texture lands).
181
+ const SELECTION_HIGHLIGHT_COLOR = new Color('#818cf8');
182
+ const SELECTION_EMISSIVE_BLEND = 0.4;
183
+ const SELECTION_EMISSIVE_INTENSITY = 0.12;
184
+ const SELECTION_TEXTURE_MAP_KEYS = [
185
+ 'map',
186
+ 'normalMap',
187
+ 'roughnessMap',
188
+ 'metalnessMap',
189
+ 'aoMap',
190
+ 'emissiveMap',
191
+ 'bumpMap',
192
+ 'displacementMap',
193
+ 'alphaMap',
194
+ 'lightMap',
195
+ ];
196
+ const selectionHighlightCache = new WeakMap();
197
+ function getSelectionHighlightMaterial(base) {
198
+ const baseMap = base.map ?? null;
199
+ const cached = selectionHighlightCache.get(base);
200
+ if (cached && cached.map === baseMap)
201
+ return cached.clone;
202
+ const clone = base.clone();
203
+ // Re-attach texture maps the WebGPU NodeMaterial clone drops.
204
+ const src = base;
205
+ const dst = clone;
206
+ for (const key of SELECTION_TEXTURE_MAP_KEYS) {
207
+ if (src[key])
208
+ dst[key] = src[key];
209
+ }
210
+ if ('emissive' in clone && clone.emissive) {
211
+ clone.emissive = clone.emissive
212
+ .clone()
213
+ .lerp(SELECTION_HIGHLIGHT_COLOR, SELECTION_EMISSIVE_BLEND);
214
+ }
215
+ if ('emissiveIntensity' in clone) {
216
+ clone.emissiveIntensity = Math.max(clone.emissiveIntensity ?? 0, SELECTION_EMISSIVE_INTENSITY);
217
+ }
218
+ clone.needsUpdate = true;
219
+ selectionHighlightCache.set(base, { clone, map: baseMap });
220
+ return clone;
221
+ }
222
+ /** Lazy light-emissive selection variant of a wall's material array (keeps texture). */
223
+ export function getSelectionHighlightMaterials(materials) {
224
+ return materials.map(getSelectionHighlightMaterial);
225
+ }
226
+ function createInvisibleWallMaterial(color, shading) {
227
+ const material = shading === 'solid'
228
+ ? new MeshLambertNodeMaterial({
229
+ transparent: true,
230
+ color,
231
+ depthWrite: false,
232
+ emissive: color,
233
+ })
234
+ : new MeshStandardNodeMaterial({
235
+ transparent: true,
236
+ color,
237
+ depthWrite: false,
238
+ emissive: color,
239
+ });
240
+ material.opacityNode = mix(float(0.0), float(0.24), dotPattern());
241
+ return material;
242
+ }
243
+ function createTranslucentWallMaterial(color, shading) {
244
+ const material = shading === 'solid'
245
+ ? new MeshLambertNodeMaterial({
246
+ transparent: true,
247
+ color,
248
+ opacity: 0.35,
249
+ depthWrite: false,
250
+ })
251
+ : new MeshStandardNodeMaterial({
252
+ transparent: true,
253
+ color,
254
+ opacity: 0.35,
255
+ depthWrite: false,
256
+ });
257
+ return material;
81
258
  }
82
259
  function mapWallMaterialArray(materials, iteratee) {
83
260
  return materials.map(iteratee);
@@ -93,15 +270,26 @@ function disposeOwnedMaterials(materials) {
93
270
  material.dispose();
94
271
  });
95
272
  }
96
- export function getWallMaterialHash(wallNode) {
273
+ export function getWallMaterialHash(wallNode, shading, sceneMaterials) {
97
274
  return JSON.stringify({
98
- interior: getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(wallNode, 'interior')),
99
- exterior: getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(wallNode, 'exterior')),
275
+ shading,
276
+ interior: wallFaceMaterialSignature(wallNode, 'interior', sceneMaterials),
277
+ exterior: wallFaceMaterialSignature(wallNode, 'exterior', sceneMaterials),
278
+ lowerInterior: wallSlotMaterialSignature(wallNode, 'lowerInterior', sceneMaterials),
279
+ middleInterior: wallSlotMaterialSignature(wallNode, 'middleInterior', sceneMaterials),
280
+ upperInterior: wallSlotMaterialSignature(wallNode, 'upperInterior', sceneMaterials),
281
+ topInterior: wallSlotMaterialSignature(wallNode, 'topInterior', sceneMaterials),
282
+ lowerExterior: wallSlotMaterialSignature(wallNode, 'lowerExterior', sceneMaterials),
283
+ middleExterior: wallSlotMaterialSignature(wallNode, 'middleExterior', sceneMaterials),
284
+ upperExterior: wallSlotMaterialSignature(wallNode, 'upperExterior', sceneMaterials),
285
+ topExterior: wallSlotMaterialSignature(wallNode, 'topExterior', sceneMaterials),
100
286
  });
101
287
  }
102
- export function getMaterialsForWall(wallNode) {
103
- const cacheKey = wallNode.id;
104
- const materialHash = getWallMaterialHash(wallNode);
288
+ export function getMaterialsForWall(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme, sceneMaterials) {
289
+ const cacheKey = `${wallNode.id}-${shading}-${textures}-${colorPreset}-${sceneTheme ?? 'base'}`;
290
+ const materialHash = textures
291
+ ? getWallMaterialHash(wallNode, shading, sceneMaterials)
292
+ : JSON.stringify({ textures, colorPreset, sceneTheme });
105
293
  const existing = wallMaterialCache.get(cacheKey);
106
294
  if (existing && existing.materialHash === materialHash) {
107
295
  return existing;
@@ -109,40 +297,85 @@ export function getMaterialsForWall(wallNode) {
109
297
  if (existing) {
110
298
  disposeOwnedMaterials([
111
299
  existing.invisible,
300
+ existing.translucent,
112
301
  existing.deleteVisible,
113
302
  existing.deleteInvisible,
114
- existing.highlightedVisible,
115
- existing.highlightedInvisible,
303
+ existing.deleteTranslucent,
116
304
  ]);
117
305
  }
118
- const interiorSpec = getEffectiveWallSurfaceMaterial(wallNode, 'interior');
119
- const exteriorSpec = getEffectiveWallSurfaceMaterial(wallNode, 'exterior');
120
- const visible = [
121
- baseMaterial,
122
- getSurfaceVisibleMaterial(interiorSpec),
123
- getSurfaceVisibleMaterial(exteriorSpec),
124
- ];
306
+ const wallRoleMaterial = createSurfaceRoleMaterial('wall', colorPreset, undefined, sceneTheme);
307
+ // Colored mode: each face resolves slot-first (node.slots ref → legacy inline
308
+ // fields → declared slot default, parity with the retired DEFAULT_WALL_MATERIAL).
309
+ // Textures-off collapses every face to the themed wall role (the guaranteed
310
+ // escape hatch). The edge/cap slot (index 0) stays role-based.
311
+ const visible = textures
312
+ ? [
313
+ wallRoleMaterial,
314
+ resolveWallFaceMaterial(wallNode, 'interior', shading, sceneMaterials),
315
+ resolveWallFaceMaterial(wallNode, 'exterior', shading, sceneMaterials),
316
+ resolveWallSlotMaterial(wallNode, 'lowerInterior', shading, sceneMaterials),
317
+ resolveWallSlotMaterial(wallNode, 'middleInterior', shading, sceneMaterials),
318
+ resolveWallSlotMaterial(wallNode, 'upperInterior', shading, sceneMaterials),
319
+ resolveWallSlotMaterial(wallNode, 'topInterior', shading, sceneMaterials),
320
+ resolveWallSlotMaterial(wallNode, 'lowerExterior', shading, sceneMaterials),
321
+ resolveWallSlotMaterial(wallNode, 'middleExterior', shading, sceneMaterials),
322
+ resolveWallSlotMaterial(wallNode, 'upperExterior', shading, sceneMaterials),
323
+ resolveWallSlotMaterial(wallNode, 'topExterior', shading, sceneMaterials),
324
+ ]
325
+ : Array.from({ length: 11 }, () => wallRoleMaterial);
326
+ const wallRoleColor = resolveSurfaceColor('wall', colorPreset, sceneTheme);
125
327
  const invisible = [
126
- createInvisibleWallMaterial(DEFAULT_WALL_COLOR),
127
- createInvisibleWallMaterial(getSurfaceColor(interiorSpec, DEFAULT_WALL_COLOR)),
128
- createInvisibleWallMaterial(getSurfaceColor(exteriorSpec, DEFAULT_WALL_COLOR)),
328
+ createInvisibleWallMaterial(wallRoleColor, textures ? shading : 'solid'),
329
+ ...['interior', 'exterior'].map((side) => createInvisibleWallMaterial(textures
330
+ ? resolveWallFaceColor(wallNode, side, sceneMaterials, wallRoleColor)
331
+ : wallRoleColor, textures ? shading : 'solid')),
332
+ ...[
333
+ 'lowerInterior',
334
+ 'middleInterior',
335
+ 'upperInterior',
336
+ 'topInterior',
337
+ 'lowerExterior',
338
+ 'middleExterior',
339
+ 'upperExterior',
340
+ 'topExterior',
341
+ ].map((slotId) => createInvisibleWallMaterial(textures
342
+ ? resolveWallSlotColor(wallNode, slotId, sceneMaterials, wallRoleColor)
343
+ : wallRoleColor, textures ? shading : 'solid')),
344
+ ];
345
+ const translucent = [
346
+ createTranslucentWallMaterial(wallRoleColor, textures ? shading : 'solid'),
347
+ ...['interior', 'exterior'].map((side) => createTranslucentWallMaterial(textures
348
+ ? resolveWallFaceColor(wallNode, side, sceneMaterials, wallRoleColor)
349
+ : wallRoleColor, textures ? shading : 'solid')),
350
+ ...[
351
+ 'lowerInterior',
352
+ 'middleInterior',
353
+ 'upperInterior',
354
+ 'topInterior',
355
+ 'lowerExterior',
356
+ 'middleExterior',
357
+ 'upperExterior',
358
+ 'topExterior',
359
+ ].map((slotId) => createTranslucentWallMaterial(textures
360
+ ? resolveWallSlotColor(wallNode, slotId, sceneMaterials, wallRoleColor)
361
+ : wallRoleColor, textures ? shading : 'solid')),
129
362
  ];
130
- const highlightedVisible = mapWallMaterialArray(visible, (material) => createHighlightedWallMaterial(material, 'selection'));
131
- const highlightedInvisible = mapWallMaterialArray(invisible, (material) => createHighlightedWallMaterial(material, 'selection'));
132
363
  const deleteVisible = mapWallMaterialArray(visible, (material) => createHighlightedWallMaterial(material, 'delete'));
133
364
  const deleteInvisible = mapWallMaterialArray(invisible, (material) => createHighlightedWallMaterial(material, 'delete'));
365
+ const deleteTranslucent = mapWallMaterialArray(translucent, (material) => createHighlightedWallMaterial(material, 'delete'));
134
366
  const result = {
135
367
  visible,
136
368
  invisible,
369
+ translucent,
137
370
  deleteVisible,
138
371
  deleteInvisible,
139
- highlightedVisible,
140
- highlightedInvisible,
372
+ deleteTranslucent,
141
373
  materialHash,
142
374
  };
143
375
  wallMaterialCache.set(cacheKey, result);
144
376
  return result;
145
377
  }
146
- export function getVisibleWallMaterials(wallNode) {
147
- return getMaterialsForWall(wallNode).visible;
378
+ export function getVisibleWallMaterials(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme, sceneMaterials) {
379
+ return getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme, sceneMaterials)
380
+ .visible;
148
381
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=wall-opening-cutout.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wall-opening-cutout.test.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-opening-cutout.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,59 @@
1
+ // @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
2
+ // depend on @types/bun so the import type is unresolved at compile time.
3
+ import { describe, expect, test } from 'bun:test';
4
+ import { calculateLevelMiters, DoorNode, sceneRegistry, WallNode } from '@pascal-app/core';
5
+ import * as THREE from 'three';
6
+ import { generateExtrudedWall } from './wall-system';
7
+ describe('wall opening cutout', () => {
8
+ test('cuts a floor-level door directly from node geometry without a proxy mesh', () => {
9
+ const wall = WallNode.parse({
10
+ id: 'wall_floor-opening-cutout',
11
+ start: [0, 0],
12
+ end: [2, 0],
13
+ height: 2.5,
14
+ thickness: 0.1,
15
+ });
16
+ const door = DoorNode.parse({
17
+ id: 'door_floor-opening-cutout',
18
+ wallId: wall.id,
19
+ position: [1, 1.05, 0],
20
+ width: 0.9,
21
+ height: 2.1,
22
+ });
23
+ const wallMesh = new THREE.Mesh();
24
+ sceneRegistry.nodes.set(wall.id, wallMesh);
25
+ try {
26
+ const geometry = generateExtrudedWall(wall, [door], calculateLevelMiters([wall]));
27
+ const position = geometry.getAttribute('position');
28
+ const index = geometry.index;
29
+ const openingLeft = door.position[0] - door.width / 2;
30
+ const openingRight = door.position[0] + door.width / 2;
31
+ let wallFaceTrianglesInsideOpening = 0;
32
+ let baseTrianglesInsideOpening = 0;
33
+ for (let offset = 0; offset < (index?.count ?? position.count); offset += 3) {
34
+ const indices = [0, 1, 2].map((corner) => index ? index.getX(offset + corner) : offset + corner);
35
+ const vertices = indices.map((vertexIndex) => new THREE.Vector3(position.getX(vertexIndex), position.getY(vertexIndex), position.getZ(vertexIndex)));
36
+ const centroid = vertices
37
+ .reduce((sum, vertex) => sum.add(vertex), new THREE.Vector3())
38
+ .multiplyScalar(1 / 3);
39
+ const insideOpeningX = centroid.x > openingLeft + 1e-4 && centroid.x < openingRight - 1e-4;
40
+ if (!insideOpeningX)
41
+ continue;
42
+ const onWallFace = vertices.every((vertex) => Math.abs(Math.abs(vertex.z) - (wall.thickness ?? 0.1) / 2) < 1e-5);
43
+ if (onWallFace && centroid.y > 1e-4 && centroid.y < door.height - 1e-4) {
44
+ wallFaceTrianglesInsideOpening += 1;
45
+ }
46
+ if (vertices.every((vertex) => Math.abs(vertex.y) < 1e-5)) {
47
+ baseTrianglesInsideOpening += 1;
48
+ }
49
+ }
50
+ expect(wallFaceTrianglesInsideOpening).toBe(0);
51
+ expect(baseTrianglesInsideOpening).toBe(0);
52
+ geometry.dispose();
53
+ }
54
+ finally {
55
+ sceneRegistry.nodes.delete(wall.id);
56
+ wallMesh.geometry.dispose();
57
+ }
58
+ });
59
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=wall-support-extension.test.d.ts.map