@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,8 +1,10 @@
1
- import { type SlabNode } from '@pascal-app/core';
1
+ import { type SlabNode, type SlabPolygonContext } from '@pascal-app/core';
2
2
  import * as THREE from 'three';
3
3
  export declare const SlabSystem: () => null;
4
4
  /**
5
- * Generates extruded slab geometry from polygon
5
+ * Generates extruded slab geometry from polygon. `context` carries the
6
+ * slab's level neighbourhood (walls + sibling slabs) driving the per-edge
7
+ * render offsets — see `getRenderableSlabPolygon`.
6
8
  */
7
- export declare function generateSlabGeometry(slabNode: SlabNode): THREE.BufferGeometry;
9
+ export declare function generateSlabGeometry(slabNode: SlabNode, context: SlabPolygonContext): THREE.BufferGeometry;
8
10
  //# sourceMappingURL=slab-system.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slab-system.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EAGd,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAa9B,eAAO,MAAM,UAAU,YAwBtB,CAAA;AAkBD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAAC,cAAc,CAG7E"}
1
+ {"version":3,"file":"slab-system.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,QAAQ,EACb,KAAK,kBAAkB,EAIxB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAe9B,eAAO,MAAM,UAAU,YA2CtB,CAAA;AAuCD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,kBAAkB,GAC1B,KAAK,CAAC,cAAc,CAKtB"}
@@ -1,6 +1,9 @@
1
- import { getRenderableSlabPolygon, sceneRegistry, useScene, } from '@pascal-app/core';
1
+ import { getEffectiveNode, getRenderableSlabPolygon, pointInPolygon2D, polygonsIntersect, sceneRegistry, useScene, } from '@pascal-app/core';
2
2
  import { useFrame } from '@react-three/fiber';
3
+ import { useEffect } from 'react';
3
4
  import * as THREE from 'three';
5
+ import { subtractPolygonsFromPolygon } from '../../lib/polygon-union';
6
+ import { mergeSurfaceHolePolygons } from '../surface-hole-geometry';
4
7
  function ensureUv2Attribute(geometry) {
5
8
  const uv = geometry.getAttribute('uv');
6
9
  if (!uv)
@@ -13,18 +16,31 @@ function ensureUv2Attribute(geometry) {
13
16
  export const SlabSystem = () => {
14
17
  const dirtyNodes = useScene((state) => state.dirtyNodes);
15
18
  const clearDirty = useScene((state) => state.clearDirty);
19
+ const markDirty = useScene((state) => state.markDirty);
20
+ useEffect(() => {
21
+ const nodes = useScene.getState().nodes;
22
+ for (const node of Object.values(nodes)) {
23
+ if (node.type === 'slab') {
24
+ markDirty(node.id);
25
+ }
26
+ }
27
+ }, [markDirty]);
16
28
  useFrame(() => {
17
29
  if (dirtyNodes.size === 0)
18
30
  return;
19
31
  const nodes = useScene.getState().nodes;
32
+ const contextByLevel = new Map();
20
33
  // Process dirty slabs
21
34
  dirtyNodes.forEach((id) => {
22
35
  const node = nodes[id];
23
- if (!node || node.type !== 'slab')
36
+ if (node?.type !== 'slab')
24
37
  return;
25
38
  const mesh = sceneRegistry.nodes.get(id);
26
39
  if (mesh) {
27
- updateSlabGeometry(node, mesh);
40
+ const slab = node;
41
+ const levelContext = contextByLevel.get(slab.parentId) ?? buildLevelSlabContext(slab.parentId, nodes);
42
+ contextByLevel.set(slab.parentId, levelContext);
43
+ updateSlabGeometry(getEffectiveNode(slab), excludeSlabFromContext(levelContext, slab.id), mesh);
28
44
  clearDirty(id);
29
45
  }
30
46
  // If mesh not found, keep it dirty for next frame
@@ -32,11 +48,30 @@ export const SlabSystem = () => {
32
48
  }, 1);
33
49
  return null;
34
50
  };
51
+ function buildLevelSlabContext(levelId, nodes) {
52
+ const walls = [];
53
+ const siblingSlabs = [];
54
+ for (const node of Object.values(nodes)) {
55
+ if (node.parentId !== levelId)
56
+ continue;
57
+ if (node.type === 'wall')
58
+ walls.push(node);
59
+ else if (node.type === 'slab')
60
+ siblingSlabs.push(node);
61
+ }
62
+ return { walls, siblingSlabs };
63
+ }
64
+ function excludeSlabFromContext(context, slabId) {
65
+ return {
66
+ walls: context.walls,
67
+ siblingSlabs: context.siblingSlabs.filter((slab) => slab.id !== slabId),
68
+ };
69
+ }
35
70
  /**
36
71
  * Updates the geometry for a single slab
37
72
  */
38
- function updateSlabGeometry(node, mesh) {
39
- const newGeo = generateSlabGeometry(node);
73
+ function updateSlabGeometry(node, context, mesh) {
74
+ const newGeo = generateSlabGeometry(node, context);
40
75
  ensureUv2Attribute(newGeo);
41
76
  mesh.geometry.dispose();
42
77
  mesh.geometry = newGeo;
@@ -46,37 +81,143 @@ function updateSlabGeometry(node, mesh) {
46
81
  mesh.position.y = elevation < 0 ? elevation : 0;
47
82
  }
48
83
  /**
49
- * Generates extruded slab geometry from polygon
84
+ * Generates extruded slab geometry from polygon. `context` carries the
85
+ * slab's level neighbourhood (walls + sibling slabs) driving the per-edge
86
+ * render offsets — see `getRenderableSlabPolygon`.
50
87
  */
51
- export function generateSlabGeometry(slabNode) {
88
+ export function generateSlabGeometry(slabNode, context) {
52
89
  const elevation = slabNode.elevation ?? 0.05;
53
- return elevation < 0 ? generatePoolGeometry(slabNode) : generatePositiveSlabGeometry(slabNode);
90
+ return elevation < 0
91
+ ? generatePoolGeometry(slabNode, context)
92
+ : generatePositiveSlabGeometry(slabNode, context);
93
+ }
94
+ // Earcut normalizes cap triangulation regardless of input winding, but the side
95
+ // walls below assume a CCW contour (the unflipped quad's right-hand normal faces
96
+ // outward only for CCW). outsetPolygon and the slab tool preserve the drawn
97
+ // winding, so a CW-drawn slab gets inward-facing walls that FrontSide culls and
98
+ // the slab reads as see-through from the front. Normalize to CCW first.
99
+ function ensureCounterClockwisePolygon(polygon) {
100
+ let area2 = 0;
101
+ for (let i = 0; i < polygon.length; i++) {
102
+ const j = (i + 1) % polygon.length;
103
+ area2 += polygon[i][0] * polygon[j][1] - polygon[j][0] * polygon[i][1];
104
+ }
105
+ return area2 < 0 ? [...polygon].reverse() : polygon;
106
+ }
107
+ function isStrictInteriorHole(contour, hole) {
108
+ return (hole.every((point) => pointInPolygon2D(point, contour, { includeBoundary: false })) &&
109
+ !polygonsIntersect(contour, hole));
110
+ }
111
+ function affectsContour(contour, hole) {
112
+ return (polygonsIntersect(contour, hole) ||
113
+ hole.some((point) => pointInPolygon2D(point, contour, { includeBoundary: false })) ||
114
+ contour.some((point) => pointInPolygon2D(point, hole, { includeBoundary: false })));
115
+ }
116
+ function buildSlabRegions(contour, holes) {
117
+ const containedHoles = [];
118
+ const edgeCutouts = [];
119
+ for (const hole of holes) {
120
+ if (hole.length < 3)
121
+ continue;
122
+ if (isStrictInteriorHole(contour, hole))
123
+ containedHoles.push(hole);
124
+ else if (affectsContour(contour, hole))
125
+ edgeCutouts.push(hole);
126
+ }
127
+ const contours = edgeCutouts.length > 0 ? subtractPolygonsFromPolygon(contour, edgeCutouts) : [contour];
128
+ return contours.map((regionContour) => ({
129
+ contour: regionContour,
130
+ holes: containedHoles.filter((hole) => isStrictInteriorHole(regionContour, hole)),
131
+ }));
54
132
  }
55
133
  /**
56
134
  * Standard slab: flat extrusion upward from Y=0 by elevation thickness.
135
+ *
136
+ * Built directly in 3D (Y-up) rather than via ExtrudeGeometry so the hole side
137
+ * walls can be emitted double-sided. The slab material is forced to FrontSide
138
+ * (DoubleSide on the floor-role NodeMaterial poisons the MRT scene pass — see
139
+ * nodes/slab/geometry.ts), and ExtrudeGeometry's hole walls are single-sided,
140
+ * so their interior faces get back-face culled and you see straight through the
141
+ * cut. Emitting each hole-wall quad twice with opposite winding makes the inner
142
+ * thickness visible from any angle: the two coincident triangles never z-fight
143
+ * because exactly one faces the camera under FrontSide culling.
57
144
  */
58
- function generatePositiveSlabGeometry(slabNode) {
59
- const polygon = getRenderableSlabPolygon(slabNode);
145
+ function generatePositiveSlabGeometry(slabNode, context) {
146
+ const polygon = ensureCounterClockwisePolygon(getRenderableSlabPolygon(slabNode, context));
60
147
  const elevation = slabNode.elevation ?? 0.05;
148
+ const holePolygons = mergeSurfaceHolePolygons(slabNode.holes ?? []);
61
149
  if (polygon.length < 3)
62
150
  return new THREE.BufferGeometry();
63
- const shape = new THREE.Shape();
64
- shape.moveTo(polygon[0][0], -polygon[0][1]);
65
- for (let i = 1; i < polygon.length; i++)
66
- shape.lineTo(polygon[i][0], -polygon[i][1]);
67
- shape.closePath();
68
- for (const holePolygon of slabNode.holes ?? []) {
69
- if (holePolygon.length < 3)
70
- continue;
71
- const holePath = new THREE.Path();
72
- holePath.moveTo(holePolygon[0][0], -holePolygon[0][1]);
73
- for (let i = 1; i < holePolygon.length; i++)
74
- holePath.lineTo(holePolygon[i][0], -holePolygon[i][1]);
75
- holePath.closePath();
76
- shape.holes.push(holePath);
151
+ const positions = [];
152
+ const uvs = [];
153
+ const indices = [];
154
+ // --- Side walls ---
155
+ // Each segment gets its own 4 verts so computeVertexNormals doesn't average
156
+ // across faces. Outer walls are single-sided with outward normals; hole walls
157
+ // emit a second flipped quad (own verts) so they read as double-sided.
158
+ const addWall = (a, b, flipped) => {
159
+ const base = positions.length / 3;
160
+ const len = Math.max(Math.hypot(b.x - a.x, b.y - a.y), 0.001);
161
+ positions.push(a.x, 0, a.y);
162
+ uvs.push(0, 0);
163
+ positions.push(b.x, 0, b.y);
164
+ uvs.push(len, 0);
165
+ positions.push(b.x, elevation, b.y);
166
+ uvs.push(len, elevation);
167
+ positions.push(a.x, elevation, a.y);
168
+ uvs.push(0, elevation);
169
+ // Standard winding on a CCW polygon gives inward-facing normals (see pool
170
+ // path), so the unflipped quad faces outward; flipped is its back face.
171
+ if (!flipped) {
172
+ indices.push(base, base + 2, base + 1, base, base + 3, base + 2);
173
+ }
174
+ else {
175
+ indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
176
+ }
177
+ };
178
+ for (const region of buildSlabRegions(polygon, holePolygons)) {
179
+ const contour2d = ensureCounterClockwisePolygon(region.contour).map(([x, z]) => new THREE.Vector2(x, z));
180
+ const holes2d = region.holes
181
+ .filter((h) => h.length >= 3)
182
+ .map((h) => h.map(([x, z]) => new THREE.Vector2(x, z)));
183
+ // --- Top & bottom caps ---
184
+ // capPoints order (contour then holes) matches triangulateShape's index space.
185
+ // UVs reproduce ExtrudeGeometry's WorldUVGenerator mapping (shape-space x,-z)
186
+ // so textured slabs keep the same floor projection.
187
+ const capPoints = [...contour2d, ...holes2d.flat()];
188
+ const topBase = positions.length / 3;
189
+ for (const p of capPoints) {
190
+ positions.push(p.x, elevation, p.y);
191
+ uvs.push(p.x, -p.y);
192
+ }
193
+ const bottomBase = positions.length / 3;
194
+ for (const p of capPoints) {
195
+ positions.push(p.x, 0, p.y);
196
+ uvs.push(p.x, -p.y);
197
+ }
198
+ const capTris = THREE.ShapeUtils.triangulateShape(contour2d, holes2d);
199
+ for (const tri of capTris) {
200
+ const [a, b, c] = [tri[0], tri[1], tri[2]];
201
+ // Reversed winding → +Y normal on top; standard winding → -Y on bottom.
202
+ indices.push(topBase + a, topBase + c, topBase + b);
203
+ indices.push(bottomBase + a, bottomBase + b, bottomBase + c);
204
+ }
205
+ for (let i = 0; i < contour2d.length; i++) {
206
+ addWall(contour2d[i], contour2d[(i + 1) % contour2d.length], false);
207
+ }
208
+ for (const hole of holes2d) {
209
+ for (let i = 0; i < hole.length; i++) {
210
+ const a = hole[i];
211
+ const b = hole[(i + 1) % hole.length];
212
+ addWall(a, b, false);
213
+ addWall(a, b, true);
214
+ }
215
+ }
77
216
  }
78
- const geometry = new THREE.ExtrudeGeometry(shape, { depth: elevation, bevelEnabled: false });
79
- geometry.rotateX(-Math.PI / 2);
217
+ const geometry = new THREE.BufferGeometry();
218
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
219
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
220
+ geometry.setIndex(indices);
80
221
  geometry.computeVertexNormals();
81
222
  return geometry;
82
223
  }
@@ -89,62 +230,57 @@ function generatePositiveSlabGeometry(slabNode) {
89
230
  * - floor in XZ plane at Y=0, normals pointing +Y (visible when looking down into pool)
90
231
  * - walls from Y=0 to Y=depth, inward-facing normals (visible from inside pool)
91
232
  */
92
- function generatePoolGeometry(slabNode) {
93
- const polygon = getRenderableSlabPolygon(slabNode);
233
+ function generatePoolGeometry(slabNode, context) {
234
+ const polygon = ensureCounterClockwisePolygon(getRenderableSlabPolygon(slabNode, context));
94
235
  const depth = Math.abs(slabNode.elevation ?? 0.05);
236
+ const holePolygons = mergeSurfaceHolePolygons(slabNode.holes ?? []);
95
237
  if (polygon.length < 3)
96
238
  return new THREE.BufferGeometry();
97
239
  const positions = [];
98
240
  const uvs = [];
99
241
  const indices = [];
100
- const n = polygon.length;
101
- const bounds = new THREE.Box2();
102
- for (const [x, z] of polygon) {
103
- bounds.expandByPoint(new THREE.Vector2(x, z));
104
- }
105
- for (const hole of slabNode.holes ?? []) {
106
- for (const [x, z] of hole) {
107
- bounds.expandByPoint(new THREE.Vector2(x, z));
108
- }
109
- }
110
- const floorWidth = Math.max(bounds.max.x - bounds.min.x, 0.001);
111
- const floorHeight = Math.max(bounds.max.y - bounds.min.y, 0.001);
112
242
  const pushFloorVertex = (x, y, z) => {
113
243
  positions.push(x, y, z);
114
- uvs.push((x - bounds.min.x) / floorWidth, (z - bounds.min.y) / floorHeight);
244
+ // Floor UVs in metres (shape-space x, -z), matching generatePositiveSlabGeometry's
245
+ // cap mapping so a finish tiles at the same world scale on every surface.
246
+ uvs.push(x, -z);
115
247
  };
116
248
  const pushWallVertex = (x, y, z, u, v) => {
117
249
  positions.push(x, y, z);
118
250
  uvs.push(u, v);
119
251
  };
120
- // --- Floor at Y=0 ---
121
- for (const [x, z] of polygon)
122
- pushFloorVertex(x, 0, z);
123
- const pts2d = polygon.map(([x, z]) => new THREE.Vector2(x, z));
124
- const holesPts2d = (slabNode.holes ?? []).map((h) => h.map(([x, z]) => new THREE.Vector2(x, z)));
125
- for (const hole of slabNode.holes ?? []) {
126
- for (const [x, z] of hole)
252
+ for (const region of buildSlabRegions(polygon, holePolygons)) {
253
+ const contour = ensureCounterClockwisePolygon(region.contour);
254
+ const floorBase = positions.length / 3;
255
+ // --- Floor at Y=0 ---
256
+ for (const [x, z] of contour)
127
257
  pushFloorVertex(x, 0, z);
128
- }
129
- const floorTris = THREE.ShapeUtils.triangulateShape(pts2d, holesPts2d);
130
- for (const tri of floorTris) {
131
- // Reversed winding → normals point +Y (upward) in XZ plane
132
- indices.push(tri[0], tri[2], tri[1]);
133
- }
134
- // --- Inner walls (no top cap at Y=depth) ---
135
- // Standard winding on a CCW polygon in XZ gives inward-facing normals.
136
- for (let i = 0; i < n; i++) {
137
- const j = (i + 1) % n;
138
- const [x0, z0] = polygon[i];
139
- const [x1, z1] = polygon[j];
140
- const vBase = positions.length / 3;
141
- const segmentLength = Math.max(Math.hypot(x1 - x0, z1 - z0), 0.001);
142
- pushWallVertex(x0, 0, z0, 0, 0); // v0 — floor level
143
- pushWallVertex(x1, 0, z1, segmentLength, 0); // v1 — floor level
144
- pushWallVertex(x1, depth, z1, segmentLength, depth); // v2 — ground level
145
- pushWallVertex(x0, depth, z0, 0, depth); // v3 — ground level
146
- indices.push(vBase, vBase + 1, vBase + 2);
147
- indices.push(vBase, vBase + 2, vBase + 3);
258
+ const pts2d = contour.map(([x, z]) => new THREE.Vector2(x, z));
259
+ const holesPts2d = region.holes.map((h) => h.map(([x, z]) => new THREE.Vector2(x, z)));
260
+ for (const hole of region.holes) {
261
+ for (const [x, z] of hole)
262
+ pushFloorVertex(x, 0, z);
263
+ }
264
+ const floorTris = THREE.ShapeUtils.triangulateShape(pts2d, holesPts2d);
265
+ for (const tri of floorTris) {
266
+ // Reversed winding → normals point +Y (upward) in XZ plane
267
+ indices.push(floorBase + tri[0], floorBase + tri[2], floorBase + tri[1]);
268
+ }
269
+ // --- Inner walls (no top cap at Y=depth) ---
270
+ // Standard winding on a CCW polygon in XZ gives inward-facing normals.
271
+ for (let i = 0; i < contour.length; i++) {
272
+ const j = (i + 1) % contour.length;
273
+ const [x0, z0] = contour[i];
274
+ const [x1, z1] = contour[j];
275
+ const vBase = positions.length / 3;
276
+ const segmentLength = Math.max(Math.hypot(x1 - x0, z1 - z0), 0.001);
277
+ pushWallVertex(x0, 0, z0, 0, 0); // v0 — floor level
278
+ pushWallVertex(x1, 0, z1, segmentLength, 0); // v1 — floor level
279
+ pushWallVertex(x1, depth, z1, segmentLength, depth); // v2 — ground level
280
+ pushWallVertex(x0, depth, z0, 0, depth); // v3 — ground level
281
+ indices.push(vBase, vBase + 1, vBase + 2);
282
+ indices.push(vBase, vBase + 2, vBase + 3);
283
+ }
148
284
  }
149
285
  const geo = new THREE.BufferGeometry();
150
286
  geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=slab-system.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slab-system.test.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,63 @@
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 { SlabNode } from '@pascal-app/core';
5
+ import { generateSlabGeometry } from './slab-system';
6
+ const EMPTY_CONTEXT = { walls: [], siblingSlabs: [] };
7
+ function hasVertexAt(geometry, x, z) {
8
+ const positions = geometry.getAttribute('position');
9
+ for (let index = 0; index < positions.count; index += 1) {
10
+ if (Math.abs(positions.getX(index) - x) < 1e-6 && Math.abs(positions.getZ(index) - z) < 1e-6) {
11
+ return true;
12
+ }
13
+ }
14
+ return false;
15
+ }
16
+ describe('generateSlabGeometry', () => {
17
+ test('renders a boundary-overlapping hole as an open indentation', () => {
18
+ const slab = SlabNode.parse({
19
+ elevation: 0.05,
20
+ polygon: [
21
+ [0, 0],
22
+ [4, 0],
23
+ [4, 3],
24
+ [0, 3],
25
+ ],
26
+ holes: [
27
+ [
28
+ [1, -0.5],
29
+ [3, -0.5],
30
+ [3, 1],
31
+ [1, 1],
32
+ ],
33
+ ],
34
+ });
35
+ const geometry = generateSlabGeometry(slab, EMPTY_CONTEXT);
36
+ expect((geometry.index?.count ?? 0) / 3).toBeGreaterThan(0);
37
+ expect(hasVertexAt(geometry, 1, 1)).toBe(true);
38
+ expect(hasVertexAt(geometry, 3, 1)).toBe(true);
39
+ });
40
+ test('renders a boundary-overlapping hole as an open indentation on recessed slabs', () => {
41
+ const slab = SlabNode.parse({
42
+ elevation: -0.2,
43
+ polygon: [
44
+ [0, 0],
45
+ [4, 0],
46
+ [4, 3],
47
+ [0, 3],
48
+ ],
49
+ holes: [
50
+ [
51
+ [1, -0.5],
52
+ [3, -0.5],
53
+ [3, 1],
54
+ [1, 1],
55
+ ],
56
+ ],
57
+ });
58
+ const geometry = generateSlabGeometry(slab, EMPTY_CONTEXT);
59
+ expect((geometry.index?.count ?? 0) / 3).toBeGreaterThan(0);
60
+ expect(hasVertexAt(geometry, 1, 1)).toBe(true);
61
+ expect(hasVertexAt(geometry, 3, 1)).toBe(true);
62
+ });
63
+ });
@@ -1,7 +1,8 @@
1
1
  import { type StairNode, type StairSegmentNode } from '@pascal-app/core';
2
2
  import type * as THREE from 'three';
3
+ import { type ColorPreset, type RenderShading } from '../../lib/materials';
3
4
  export type StairBodyMaterials = [THREE.Material, THREE.Material];
4
- export declare function getStairBodyMaterials(stair: StairNode): StairBodyMaterials;
5
- export declare function getStairRailingMaterial(stair: StairNode): THREE.Material;
6
- export declare function getStraightStairSegmentBodyMaterials(segment: StairSegmentNode, parentNode?: StairNode): StairBodyMaterials;
5
+ export declare function getStairBodyMaterials(stair: StairNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset): StairBodyMaterials;
6
+ export declare function getStairRailingMaterial(stair: StairNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset): THREE.Material;
7
+ export declare function getStraightStairSegmentBodyMaterials(segment: StairSegmentNode, parentNode?: StairNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset): StairBodyMaterials;
7
8
  //# sourceMappingURL=stair-materials.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stair-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAOnC,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AA6BjE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,GAAG,kBAAkB,CAkB1E;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,CAAC,QAAQ,CASxE;AAED,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,gBAAgB,EACzB,UAAU,CAAC,EAAE,SAAS,GACrB,kBAAkB,CAWpB"}
1
+ {"version":3,"file":"stair-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AACnC,OAAO,EACL,KAAK,WAAW,EAKhB,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAA;AAE5B,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AAoCjE,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,GAChC,kBAAkB,CAqBpB;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,GAChC,KAAK,CAAC,QAAQ,CAoBhB;AAED,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,gBAAgB,EACzB,UAAU,CAAC,EAAE,SAAS,EACtB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,GAChC,kBAAkB,CAsBpB"}
@@ -1,5 +1,5 @@
1
1
  import { getEffectiveStairSurfaceMaterial, } from '@pascal-app/core';
2
- import { createMaterial, createMaterialFromPresetRef, DEFAULT_STAIR_MATERIAL, } from '../../lib/materials';
2
+ import { createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, DEFAULT_STAIR_MATERIAL, } from '../../lib/materials';
3
3
  const stairBodyMaterialCache = new Map();
4
4
  const stairRailingMaterialCache = new Map();
5
5
  function getSurfaceMaterialSignature(spec) {
@@ -8,19 +8,25 @@ function getSurfaceMaterialSignature(spec) {
8
8
  materialPreset: spec.materialPreset ?? null,
9
9
  });
10
10
  }
11
- function createResolvedMaterial(material, materialPreset) {
11
+ function createResolvedMaterial(material, materialPreset, shading, textures, colorPreset) {
12
+ if (!textures) {
13
+ return createSurfaceRoleMaterial('joinery', colorPreset);
14
+ }
12
15
  if (materialPreset) {
13
- return createMaterialFromPresetRef(materialPreset) ?? DEFAULT_STAIR_MATERIAL;
16
+ return createMaterialFromPresetRef(materialPreset, shading) ?? DEFAULT_STAIR_MATERIAL(shading);
14
17
  }
15
18
  if (material) {
16
- return createMaterial(material);
19
+ return createMaterial(material, shading);
17
20
  }
18
- return DEFAULT_STAIR_MATERIAL;
21
+ return DEFAULT_STAIR_MATERIAL(shading);
19
22
  }
20
- export function getStairBodyMaterials(stair) {
23
+ export function getStairBodyMaterials(stair, shading = 'rendered', textures = true, colorPreset = 'clay') {
21
24
  const tread = getEffectiveStairSurfaceMaterial(stair, 'tread');
22
25
  const side = getEffectiveStairSurfaceMaterial(stair, 'side');
23
26
  const cacheKey = JSON.stringify({
27
+ shading,
28
+ textures,
29
+ colorPreset,
24
30
  tread: getSurfaceMaterialSignature(tread),
25
31
  side: getSurfaceMaterialSignature(side),
26
32
  });
@@ -28,29 +34,38 @@ export function getStairBodyMaterials(stair) {
28
34
  if (cached)
29
35
  return cached;
30
36
  const materials = [
31
- createResolvedMaterial(tread.material, tread.materialPreset),
32
- createResolvedMaterial(side.material, side.materialPreset),
37
+ createResolvedMaterial(tread.material, tread.materialPreset, shading, textures, colorPreset),
38
+ createResolvedMaterial(side.material, side.materialPreset, shading, textures, colorPreset),
33
39
  ];
34
40
  stairBodyMaterialCache.set(cacheKey, materials);
35
41
  return materials;
36
42
  }
37
- export function getStairRailingMaterial(stair) {
43
+ export function getStairRailingMaterial(stair, shading = 'rendered', textures = true, colorPreset = 'clay') {
38
44
  const railing = getEffectiveStairSurfaceMaterial(stair, 'railing');
39
- const cacheKey = getSurfaceMaterialSignature(railing);
45
+ const cacheKey = JSON.stringify({
46
+ shading,
47
+ textures,
48
+ colorPreset,
49
+ railing: getSurfaceMaterialSignature(railing),
50
+ });
40
51
  const cached = stairRailingMaterialCache.get(cacheKey);
41
52
  if (cached)
42
53
  return cached;
43
- const material = createResolvedMaterial(railing.material, railing.materialPreset);
54
+ const material = createResolvedMaterial(railing.material, railing.materialPreset, shading, textures, colorPreset);
44
55
  stairRailingMaterialCache.set(cacheKey, material);
45
56
  return material;
46
57
  }
47
- export function getStraightStairSegmentBodyMaterials(segment, parentNode) {
58
+ export function getStraightStairSegmentBodyMaterials(segment, parentNode, shading = 'rendered', textures = true, colorPreset = 'clay') {
48
59
  if (segment.material !== undefined || typeof segment.materialPreset === 'string') {
49
- const override = createResolvedMaterial(segment.material, segment.materialPreset);
60
+ const override = createResolvedMaterial(segment.material, segment.materialPreset, shading, textures, colorPreset);
50
61
  return [override, override];
51
62
  }
52
63
  if (parentNode) {
53
- return getStairBodyMaterials(parentNode);
64
+ return getStairBodyMaterials(parentNode, shading, textures, colorPreset);
65
+ }
66
+ if (!textures) {
67
+ const material = createSurfaceRoleMaterial('joinery', colorPreset);
68
+ return [material, material];
54
69
  }
55
- return [DEFAULT_STAIR_MATERIAL, DEFAULT_STAIR_MATERIAL];
70
+ return [DEFAULT_STAIR_MATERIAL(shading), DEFAULT_STAIR_MATERIAL(shading)];
56
71
  }
@@ -1 +1 @@
1
- {"version":3,"file":"stair-system.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-system.tsx"],"names":[],"mappings":"AA4BA,eAAO,MAAM,WAAW,YAgHvB,CAAA"}
1
+ {"version":3,"file":"stair-system.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-system.tsx"],"names":[],"mappings":"AA0BA,eAAO,MAAM,WAAW,YAuGvB,CAAA"}