@pascal-app/viewer 1.0.6 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.md +54 -21
  2. package/dist/components/viewer/daylight-atmosphere.d.ts +9 -0
  3. package/dist/components/viewer/daylight-atmosphere.d.ts.map +1 -0
  4. package/dist/components/viewer/daylight-atmosphere.js +315 -0
  5. package/dist/components/viewer/drone-controller.d.ts +15 -0
  6. package/dist/components/viewer/drone-controller.d.ts.map +1 -0
  7. package/dist/components/viewer/drone-controller.js +194 -0
  8. package/dist/components/viewer/glb-interactive.d.ts +4 -18
  9. package/dist/components/viewer/glb-interactive.d.ts.map +1 -1
  10. package/dist/components/viewer/glb-interactive.js +32 -268
  11. package/dist/components/viewer/glb-replace-instances.d.ts +6 -3
  12. package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -1
  13. package/dist/components/viewer/glb-replace-instances.js +5 -4
  14. package/dist/components/viewer/glb-scene-motion.d.ts +8 -0
  15. package/dist/components/viewer/glb-scene-motion.d.ts.map +1 -0
  16. package/dist/components/viewer/glb-scene-motion.js +57 -0
  17. package/dist/components/viewer/glb-scene.d.ts +5 -1
  18. package/dist/components/viewer/glb-scene.d.ts.map +1 -1
  19. package/dist/components/viewer/glb-scene.js +290 -49
  20. package/dist/components/viewer/glb-selection-callout.d.ts +17 -0
  21. package/dist/components/viewer/glb-selection-callout.d.ts.map +1 -0
  22. package/dist/components/viewer/glb-selection-callout.js +54 -0
  23. package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
  24. package/dist/components/viewer/glb-walkthrough-controller.js +6 -3
  25. package/dist/components/viewer/index.d.ts +5 -0
  26. package/dist/components/viewer/index.d.ts.map +1 -1
  27. package/dist/components/viewer/index.js +38 -10
  28. package/dist/components/viewer/lights.d.ts.map +1 -1
  29. package/dist/components/viewer/lights.js +192 -5
  30. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  31. package/dist/components/viewer/post-processing.js +150 -94
  32. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  33. package/dist/components/viewer/registered-systems.js +2 -2
  34. package/dist/components/viewer/scene-environment.js +4 -5
  35. package/dist/components/viewer/selection-callout.d.ts +51 -0
  36. package/dist/components/viewer/selection-callout.d.ts.map +1 -0
  37. package/dist/components/viewer/selection-callout.js +197 -0
  38. package/dist/components/viewer/unsupported-gpu-fallback.d.ts.map +1 -1
  39. package/dist/components/viewer/unsupported-gpu-fallback.js +21 -1
  40. package/dist/components/viewer/use-daylight-rooms.d.ts +13 -0
  41. package/dist/components/viewer/use-daylight-rooms.d.ts.map +1 -0
  42. package/dist/components/viewer/use-daylight-rooms.js +278 -0
  43. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  44. package/dist/components/viewer/viewer-camera.js +12 -1
  45. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  46. package/dist/hooks/use-gltf-ktx2.js +2 -1
  47. package/dist/index.d.ts +26 -8
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +28 -8
  50. package/dist/lib/asset-url.d.ts +0 -1
  51. package/dist/lib/asset-url.d.ts.map +1 -1
  52. package/dist/lib/asset-url.js +3 -3
  53. package/dist/lib/daylight/ground.d.ts +21 -0
  54. package/dist/lib/daylight/ground.d.ts.map +1 -0
  55. package/dist/lib/daylight/ground.js +53 -0
  56. package/dist/lib/daylight/location.d.ts +32 -0
  57. package/dist/lib/daylight/location.d.ts.map +1 -0
  58. package/dist/lib/daylight/location.js +74 -0
  59. package/dist/lib/daylight/look.d.ts +33 -0
  60. package/dist/lib/daylight/look.d.ts.map +1 -0
  61. package/dist/lib/daylight/look.js +13 -0
  62. package/dist/lib/daylight/material-hooks.d.ts +2 -0
  63. package/dist/lib/daylight/material-hooks.d.ts.map +1 -0
  64. package/dist/lib/daylight/material-hooks.js +150 -0
  65. package/dist/lib/daylight/room-context.d.ts +12 -0
  66. package/dist/lib/daylight/room-context.d.ts.map +1 -0
  67. package/dist/lib/daylight/room-context.js +54 -0
  68. package/dist/lib/daylight/room-map.d.ts +124 -0
  69. package/dist/lib/daylight/room-map.d.ts.map +1 -0
  70. package/dist/lib/daylight/room-map.js +630 -0
  71. package/dist/lib/daylight/room-scene.d.ts +56 -0
  72. package/dist/lib/daylight/room-scene.d.ts.map +1 -0
  73. package/dist/lib/daylight/room-scene.js +391 -0
  74. package/dist/lib/daylight/rooms.d.ts +292 -0
  75. package/dist/lib/daylight/rooms.d.ts.map +1 -0
  76. package/dist/lib/daylight/rooms.js +1268 -0
  77. package/dist/lib/daylight/runtime.d.ts +33 -0
  78. package/dist/lib/daylight/runtime.d.ts.map +1 -0
  79. package/dist/lib/daylight/runtime.js +42 -0
  80. package/dist/lib/daylight/sky.d.ts +26 -0
  81. package/dist/lib/daylight/sky.d.ts.map +1 -0
  82. package/dist/lib/daylight/sky.js +217 -0
  83. package/dist/lib/daylight/solar.d.ts +46 -0
  84. package/dist/lib/daylight/solar.d.ts.map +1 -0
  85. package/dist/lib/daylight/solar.js +156 -0
  86. package/dist/lib/device-tier.d.ts +29 -0
  87. package/dist/lib/device-tier.d.ts.map +1 -0
  88. package/dist/lib/device-tier.js +71 -0
  89. package/dist/lib/dispose-object3d.d.ts.map +1 -1
  90. package/dist/lib/dispose-object3d.js +4 -2
  91. package/dist/lib/drone-flight.d.ts +44 -0
  92. package/dist/lib/drone-flight.d.ts.map +1 -0
  93. package/dist/lib/drone-flight.js +121 -0
  94. package/dist/lib/editor-highlight.d.ts +16 -0
  95. package/dist/lib/editor-highlight.d.ts.map +1 -0
  96. package/dist/lib/editor-highlight.js +33 -0
  97. package/dist/lib/foundation-materials.d.ts.map +1 -1
  98. package/dist/lib/foundation-materials.js +2 -4
  99. package/dist/lib/glb-merge.d.ts +23 -0
  100. package/dist/lib/glb-merge.d.ts.map +1 -0
  101. package/dist/lib/glb-merge.js +184 -0
  102. package/dist/lib/hardware-geometry.d.ts +21 -0
  103. package/dist/lib/hardware-geometry.d.ts.map +1 -0
  104. package/dist/lib/hardware-geometry.js +283 -0
  105. package/dist/lib/ktx2-loader.d.ts.map +1 -1
  106. package/dist/lib/ktx2-loader.js +2 -1
  107. package/dist/lib/layer-pass.d.ts +2 -0
  108. package/dist/lib/layer-pass.d.ts.map +1 -1
  109. package/dist/lib/layer-pass.js +20 -0
  110. package/dist/lib/materials.d.ts.map +1 -1
  111. package/dist/lib/materials.js +50 -23
  112. package/dist/lib/measurement-label.d.ts +4 -0
  113. package/dist/lib/measurement-label.d.ts.map +1 -0
  114. package/dist/lib/measurement-label.js +54 -0
  115. package/dist/lib/measurement-presentation.d.ts +5 -0
  116. package/dist/lib/measurement-presentation.d.ts.map +1 -0
  117. package/dist/lib/measurement-presentation.js +8 -0
  118. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  119. package/dist/lib/merged-outline-node.js +7 -1
  120. package/dist/lib/occlusion-culler.d.ts +34 -0
  121. package/dist/lib/occlusion-culler.d.ts.map +1 -0
  122. package/dist/lib/occlusion-culler.js +698 -0
  123. package/dist/lib/pointer-events.d.ts.map +1 -1
  124. package/dist/lib/pointer-events.js +3 -3
  125. package/dist/lib/post-processing-resources.d.ts +4 -0
  126. package/dist/lib/post-processing-resources.d.ts.map +1 -1
  127. package/dist/lib/post-processing-resources.js +5 -0
  128. package/dist/lib/readback-rows.d.ts +14 -0
  129. package/dist/lib/readback-rows.d.ts.map +1 -0
  130. package/dist/lib/readback-rows.js +31 -0
  131. package/dist/lib/reveal-pose.d.ts.map +1 -1
  132. package/dist/lib/reveal-pose.js +3 -0
  133. package/dist/lib/scene-matrices.d.ts +12 -0
  134. package/dist/lib/scene-matrices.d.ts.map +1 -0
  135. package/dist/lib/scene-matrices.js +287 -0
  136. package/dist/lib/scene-pass-mrt.d.ts +5 -2
  137. package/dist/lib/scene-pass-mrt.d.ts.map +1 -1
  138. package/dist/lib/scene-pass-mrt.js +7 -2
  139. package/dist/lib/scene-themes.d.ts +0 -2
  140. package/dist/lib/scene-themes.d.ts.map +1 -1
  141. package/dist/lib/scene-themes.js +0 -2
  142. package/dist/lib/scene-visibility.d.ts +2 -0
  143. package/dist/lib/scene-visibility.d.ts.map +1 -1
  144. package/dist/lib/scene-visibility.js +12 -3
  145. package/dist/lib/site-surfaces.d.ts +1 -8
  146. package/dist/lib/site-surfaces.d.ts.map +1 -1
  147. package/dist/lib/site-surfaces.js +0 -19
  148. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  149. package/dist/lib/snapshot-pipeline.js +24 -40
  150. package/dist/lib/texture-reference.d.ts.map +1 -1
  151. package/dist/lib/texture-reference.js +13 -10
  152. package/dist/lib/underground-ambiance.d.ts +0 -2
  153. package/dist/lib/underground-ambiance.d.ts.map +1 -1
  154. package/dist/lib/underground-ambiance.js +0 -2
  155. package/dist/lib/viewer-config.d.ts +21 -0
  156. package/dist/lib/viewer-config.d.ts.map +1 -0
  157. package/dist/lib/viewer-config.js +21 -0
  158. package/dist/lib/viewer-preference-storage.d.ts +7 -0
  159. package/dist/lib/viewer-preference-storage.d.ts.map +1 -0
  160. package/dist/lib/viewer-preference-storage.js +23 -0
  161. package/dist/store/use-item-light-pool.d.ts +3 -6
  162. package/dist/store/use-item-light-pool.d.ts.map +1 -1
  163. package/dist/store/use-item-light-pool.js +12 -0
  164. package/dist/store/use-viewer.d.ts +62 -59
  165. package/dist/store/use-viewer.d.ts.map +1 -1
  166. package/dist/store/use-viewer.js +106 -19
  167. package/dist/systems/ceiling/ceiling-system.d.ts +5 -0
  168. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  169. package/dist/systems/ceiling/ceiling-system.js +11 -1
  170. package/dist/systems/door/door-system.d.ts.map +1 -1
  171. package/dist/systems/door/door-system.js +501 -145
  172. package/dist/systems/door/leaf-profile.d.ts +11 -0
  173. package/dist/systems/door/leaf-profile.d.ts.map +1 -0
  174. package/dist/systems/door/leaf-profile.js +140 -0
  175. package/dist/systems/elevator/elevator-interaction-system.d.ts +16 -2
  176. package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -1
  177. package/dist/systems/elevator/elevator-interaction-system.js +41 -8
  178. package/dist/systems/geometry/geometry-status.d.ts +8 -0
  179. package/dist/systems/geometry/geometry-status.d.ts.map +1 -0
  180. package/dist/systems/geometry/geometry-status.js +24 -0
  181. package/dist/systems/interactive/callout-actions.d.ts +72 -0
  182. package/dist/systems/interactive/callout-actions.d.ts.map +1 -0
  183. package/dist/systems/interactive/callout-actions.js +212 -0
  184. package/dist/systems/interactive/interactive-system.d.ts +5 -1
  185. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  186. package/dist/systems/interactive/interactive-system.js +44 -121
  187. package/dist/systems/interactive/opening-settled.d.ts +16 -0
  188. package/dist/systems/interactive/opening-settled.d.ts.map +1 -0
  189. package/dist/systems/interactive/opening-settled.js +53 -0
  190. package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
  191. package/dist/systems/item-light/item-light-system.js +22 -214
  192. package/dist/systems/item-light/pooled-lights.d.ts +70 -0
  193. package/dist/systems/item-light/pooled-lights.d.ts.map +1 -0
  194. package/dist/systems/item-light/pooled-lights.js +449 -0
  195. package/dist/systems/level/level-system.d.ts.map +1 -1
  196. package/dist/systems/level/level-system.js +19 -1
  197. package/dist/systems/roof/roof-material-slots.d.ts +14 -0
  198. package/dist/systems/roof/roof-material-slots.d.ts.map +1 -0
  199. package/dist/systems/roof/roof-material-slots.js +13 -0
  200. package/dist/systems/roof/roof-materials.d.ts +25 -2
  201. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  202. package/dist/systems/roof/roof-materials.js +85 -26
  203. package/dist/systems/roof/roof-system.d.ts +34 -1
  204. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  205. package/dist/systems/roof/roof-system.js +582 -168
  206. package/dist/systems/wall/opening-casing-geometry.d.ts +14 -0
  207. package/dist/systems/wall/opening-casing-geometry.d.ts.map +1 -0
  208. package/dist/systems/wall/opening-casing-geometry.js +102 -0
  209. package/dist/systems/wall/opening-cutout-geometry.d.ts +9 -0
  210. package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -1
  211. package/dist/systems/wall/opening-cutout-geometry.js +7 -3
  212. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  213. package/dist/systems/wall/wall-materials.js +28 -35
  214. package/dist/systems/wall/wall-system.d.ts +11 -0
  215. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  216. package/dist/systems/wall/wall-system.js +86 -25
  217. package/dist/systems/wall/wall-t-junctions.d.ts +15 -0
  218. package/dist/systems/wall/wall-t-junctions.d.ts.map +1 -0
  219. package/dist/systems/wall/wall-t-junctions.js +149 -0
  220. package/dist/systems/window/window-animation-system.d.ts +3 -12
  221. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  222. package/dist/systems/window/window-animation-system.js +37 -122
  223. package/dist/systems/window/window-pose.d.ts +31 -0
  224. package/dist/systems/window/window-pose.d.ts.map +1 -0
  225. package/dist/systems/window/window-pose.js +152 -0
  226. package/dist/systems/window/window-roller-shutter.d.ts +8 -0
  227. package/dist/systems/window/window-roller-shutter.d.ts.map +1 -0
  228. package/dist/systems/window/window-roller-shutter.js +64 -0
  229. package/dist/systems/window/window-system.d.ts +2 -11
  230. package/dist/systems/window/window-system.d.ts.map +1 -1
  231. package/dist/systems/window/window-system.js +470 -303
  232. package/package.json +6 -6
  233. package/dist/systems/interactive/control-widget.d.ts +0 -10
  234. package/dist/systems/interactive/control-widget.d.ts.map +0 -1
  235. package/dist/systems/interactive/control-widget.js +0 -35
  236. package/dist/systems/interactive/procedural-controls.d.ts +0 -13
  237. package/dist/systems/interactive/procedural-controls.d.ts.map +0 -1
  238. package/dist/systems/interactive/procedural-controls.js +0 -17
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room-scene.d.ts","sourceRoot":"","sources":["../../../src/lib/daylight/room-scene.ts"],"names":[],"mappings":"AAQA,OAAO,EAAoD,KAAK,QAAQ,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAMhG,OAAO,KAAK,EAAE,QAAQ,EAAmB,cAAc,EAAY,MAAM,SAAS,CAAA;AAGlF;;;GAGG;AAEH,mGAAmG;AACnG,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,0BAA0B;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gBAAgB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAA;IAC3C,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,QAAQ,CAAA;IACjB,yFAAyF;IACzF,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3F,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CAC3C,CAAA;AAED,KAAK,QAAQ,GAAG;IACd,MAAM,EAAE,MAAM,QAAQ,CAAC,aAAa,CAAC,CAAA;IACrC,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,CAAA;CAC5C,CAAA;AAED,8FAA8F;AAC9F,eAAO,MAAM,SAAS,eAAsB,CAAA;AAU5C,oEAAoE;AACpE,wBAAgB,SAAS,IAAI,QAAQ,EAAE,CAuBtC;AAeD,6CAA6C;AAC7C,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAK3D;AAkCD,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,cAAc,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAuE5E;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC,EAAE,CAE3D;AAqBD,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,QAAQ,EAAE,CAEnE;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAanD;AASD,0FAA0F;AAC1F,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CA2E7F;AAiED;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CA2CvC;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,IAAI,IAAI,CAM1C"}
@@ -0,0 +1,391 @@
1
+ import { getLevelElevations, sceneRegistry, useInteractive, useScene, } from '@pascal-app/core';
2
+ import { Box3, Color, MathUtils, Vector3 } from 'three';
3
+ import { materialEmissive, smoothstep, uniform, vec3 } from 'three/tsl';
4
+ import { MeshStandardNodeMaterial } from 'three/webgpu';
5
+ import { isHighlight } from '../editor-highlight';
6
+ import { setSurfaceRaycastLayers } from '../layers';
7
+ import { daylightUniforms } from './runtime';
8
+ /** Item-light pools hand their lamps to the room system while the daylight look is active. */
9
+ export const lampFeeds = new Set();
10
+ const COVER_TYPES = {
11
+ ceiling: 'ceiling',
12
+ roof: 'roof',
13
+ 'roof-segment': 'roof',
14
+ slab: 'slab',
15
+ };
16
+ const OPENING_TYPES = ['window', 'door'];
17
+ /** Lamps from every registered feed, in the room system's units. */
18
+ export function feedLamps() {
19
+ const lamps = [];
20
+ const position = new Vector3();
21
+ for (const feed of lampFeeds) {
22
+ for (const light of feed.lights()) {
23
+ if (!light.getWorldPosition(position))
24
+ continue;
25
+ // Light effects mix conventions: artist numbers (0.6–2) and physical-looking ones
26
+ // (110, 1800 cd with 55 m reach). Log-compress so both land in a sane band; rooms are
27
+ // soft-capped later.
28
+ const peak = light.peak ?? 1;
29
+ lamps.push({
30
+ itemId: light.key,
31
+ position: position.clone(),
32
+ color: new Color(light.color || '#ffe6c4'),
33
+ intensity: 4 + 3 * Math.log2(1 + Math.max(0, peak)),
34
+ candela: Math.max(0, peak),
35
+ range: MathUtils.clamp(light.distance || 5.5, 2.5, 9),
36
+ on: feed.intensity(light) > 0,
37
+ spot: light.spot ? lampSpot(light) : undefined,
38
+ });
39
+ }
40
+ }
41
+ return lamps;
42
+ }
43
+ /** A spot's cone, as the light pools read it (half-angle = angle / 2, penumbra = softness). */
44
+ function lampSpot(light) {
45
+ const direction = new Vector3(...(light.spot?.direction ?? [0, -1, 0]));
46
+ if (light.getWorldDirection)
47
+ light.getWorldDirection(direction);
48
+ const half = Math.min(Math.PI / 2, MathUtils.degToRad((light.spot?.angle ?? 60) / 2));
49
+ const penumbra = MathUtils.clamp(light.spot?.softness ?? 0, 0, 1);
50
+ return {
51
+ direction: direction.normalize(),
52
+ cosOuter: Math.cos(half),
53
+ cosInner: Math.cos(half * (1 - penumbra)),
54
+ };
55
+ }
56
+ /** Current on/off of a fed lamp (by key). */
57
+ export function feedLampOn(key) {
58
+ for (const feed of lampFeeds) {
59
+ for (const light of feed.lights())
60
+ if (light.key === key)
61
+ return feed.intensity(light) > 0;
62
+ }
63
+ return undefined;
64
+ }
65
+ /** Meshes under a root, minus skipped subtrees and invisible helpers (hole-less collision proxies). */
66
+ function meshesOf(root, skip) {
67
+ const out = [];
68
+ const visit = (object, isRoot) => {
69
+ if (!isRoot && (skip(object) || !object.visible))
70
+ return;
71
+ if (object.isMesh)
72
+ out.push(object);
73
+ for (const child of object.children)
74
+ visit(child, false);
75
+ };
76
+ visit(root, true);
77
+ return out;
78
+ }
79
+ /** Every live level, in one stable order: its building and assembled world floor height. */
80
+ function liveLevels() {
81
+ const { nodes } = useScene.getState();
82
+ const elevations = getLevelElevations(nodes);
83
+ const buildings = new Map();
84
+ const ids = [];
85
+ const levels = [];
86
+ for (const node of Object.values(nodes)) {
87
+ if (node?.type !== 'level')
88
+ continue;
89
+ const elevation = elevations.get(node.id);
90
+ const buildingId = elevation?.buildingId ?? null;
91
+ if (!buildings.has(buildingId))
92
+ buildings.set(buildingId, buildings.size);
93
+ const building = buildingId ? nodes[buildingId] : undefined;
94
+ const siteY = building?.type === 'building' ? building.position[1] : 0;
95
+ ids.push(node.id);
96
+ levels.push({ y: siteY + (elevation?.baseY ?? 0), building: buildings.get(buildingId) });
97
+ }
98
+ return { ids, levels };
99
+ }
100
+ /** The live parametric scene (registry + node data), or null while it has no zones. */
101
+ export function liveRoomScene(virtualLampsOn) {
102
+ const zoneIds = sceneRegistry.byType.zone;
103
+ if (!zoneIds || zoneIds.size === 0)
104
+ return null;
105
+ const { nodes } = useScene.getState();
106
+ const { ids, levels } = liveLevels();
107
+ const levelIndex = new Map(ids.map((id, index) => [id, index]));
108
+ const zones = [];
109
+ const point = new Vector3();
110
+ for (const id of zoneIds) {
111
+ const node = nodes[id];
112
+ const object = sceneRegistry.nodes.get(id);
113
+ if (!node?.polygon || node.polygon.length < 3 || !object || node.visible === false)
114
+ continue;
115
+ object.updateWorldMatrix(true, false);
116
+ const toWorld = (ring) => ring.map(([x, z]) => {
117
+ object.localToWorld(point.set(x, 0, z));
118
+ return [point.x, point.z];
119
+ });
120
+ zones.push({
121
+ id,
122
+ polygon: toWorld(node.polygon),
123
+ holes: (node.holes ?? []).filter((hole) => hole.length >= 3).map(toWorld),
124
+ levelIndex: levelIndex.get(node.parentId ?? '') ?? 0,
125
+ });
126
+ }
127
+ if (!zones.length)
128
+ return null;
129
+ const registered = new Set(sceneRegistry.nodes.values());
130
+ const covers = [];
131
+ for (const [type, kind] of Object.entries(COVER_TYPES)) {
132
+ for (const id of sceneRegistry.byType[type] ?? []) {
133
+ const object = sceneRegistry.nodes.get(id);
134
+ if (object)
135
+ covers.push({ object, kind });
136
+ }
137
+ }
138
+ const walls = [];
139
+ for (const id of sceneRegistry.byType.wall ?? []) {
140
+ const object = sceneRegistry.nodes.get(id);
141
+ // Doors, windows and items hang under their wall: a closed leaf mustn't close its doorway.
142
+ if (object)
143
+ walls.push(...meshesOf(object, (child) => registered.has(child)));
144
+ }
145
+ const openings = [];
146
+ for (const kind of OPENING_TYPES) {
147
+ for (const id of sceneRegistry.byType[kind] ?? []) {
148
+ const object = sceneRegistry.nodes.get(id);
149
+ if (object)
150
+ openings.push({ object, kind, id });
151
+ }
152
+ }
153
+ const bounds = new Box3();
154
+ for (const zone of zones)
155
+ for (const [x, z] of zone.polygon)
156
+ bounds.expandByPoint(point.set(x, 0, z));
157
+ return {
158
+ bounds,
159
+ levels,
160
+ zones,
161
+ covers,
162
+ walls,
163
+ openings,
164
+ lamps: feedLamps(),
165
+ virtualLampsOn,
166
+ configureRaycaster: (raycaster) => setSurfaceRaycastLayers(raycaster.layers),
167
+ };
168
+ }
169
+ /** Live level objects in the room system's level order (explode offsets, visibility, focus). */
170
+ export function liveLevelObjects() {
171
+ return liveLevels().ids.map((id) => sceneRegistry.nodes.get(id));
172
+ }
173
+ /** A baked artifact's levels: their objects and, per level, its building and floor height. */
174
+ function bakedLevels(root) {
175
+ const objects = [];
176
+ root.traverse((object) => {
177
+ if (object.userData.kind === 'level')
178
+ objects.push(object);
179
+ });
180
+ root.updateWorldMatrix(true, true);
181
+ const buildings = new Map();
182
+ const point = new Vector3();
183
+ const levels = objects.map((object) => {
184
+ let building = object.parent;
185
+ while (building && building.userData.kind !== 'building')
186
+ building = building.parent;
187
+ if (!buildings.has(building))
188
+ buildings.set(building, buildings.size);
189
+ return { y: object.getWorldPosition(point).y, building: buildings.get(building) };
190
+ });
191
+ return { objects, levels };
192
+ }
193
+ /** A baked artifact's level objects in the room system's level order. */
194
+ export function bakedLevelObjects(root) {
195
+ return root ? bakedLevels(root).objects : [];
196
+ }
197
+ /** How open a live door is (0 closed … 1 fully open), the transient state first. */
198
+ export function liveDoorOpenness(id) {
199
+ const node = useScene.getState().nodes[id];
200
+ const transient = useInteractive.getState().doors[id];
201
+ const swing = transient?.swingAngle ?? node?.swingAngle ?? 0;
202
+ const operation = transient?.operationState ?? node?.operationState ?? 0;
203
+ return Math.max(operation, swing / (Math.PI / 2));
204
+ }
205
+ /** A baked artifact's identity tree (levels, zones, covers, walls, openings), or null. */
206
+ export function bakedRoomScene(root, virtualLampsOn) {
207
+ const zoneObjects = [];
208
+ const covers = [];
209
+ const wallRoots = [];
210
+ const openings = [];
211
+ root.traverse((object) => {
212
+ const extras = object.userData;
213
+ switch (extras.kind) {
214
+ case 'zone':
215
+ zoneObjects.push(object);
216
+ break;
217
+ case 'ceiling':
218
+ case 'roof':
219
+ case 'roof-segment':
220
+ case 'slab':
221
+ covers.push({ object, kind: COVER_TYPES[extras.kind] });
222
+ break;
223
+ case 'wall':
224
+ wallRoots.push(object);
225
+ break;
226
+ case 'window':
227
+ case 'door':
228
+ openings.push({ object, kind: extras.kind, id: extras.pascalId ?? object.uuid });
229
+ break;
230
+ }
231
+ });
232
+ if (!zoneObjects.length)
233
+ return null;
234
+ const levels = bakedLevels(root);
235
+ const levelOf = (object) => {
236
+ for (let x = object; x; x = x.parent) {
237
+ const index = levels.objects.indexOf(x);
238
+ if (index >= 0)
239
+ return { index, frame: x };
240
+ }
241
+ return { index: 0, frame: null };
242
+ };
243
+ const point = new Vector3();
244
+ const zones = [];
245
+ for (const object of zoneObjects) {
246
+ const { polygon, holes } = object.userData;
247
+ if (!polygon || polygon.length < 3)
248
+ continue;
249
+ const level = levelOf(object);
250
+ // Polygons are level-local [x, z]: bring them to world XZ through the level's frame.
251
+ const frame = level.frame ?? object.parent ?? object;
252
+ const toWorld = (ring) => ring.map(([x, z]) => {
253
+ point.set(x, 0, z).applyMatrix4(frame.matrixWorld);
254
+ return [point.x, point.z];
255
+ });
256
+ zones.push({
257
+ id: object.userData.pascalId ?? object.uuid,
258
+ polygon: toWorld(polygon),
259
+ holes: (holes ?? []).filter((hole) => hole.length >= 3).map(toWorld),
260
+ levelIndex: level.index,
261
+ });
262
+ }
263
+ if (!zones.length)
264
+ return null;
265
+ const isIdentity = (object) => {
266
+ const kind = object.userData.kind;
267
+ return kind === 'door' || kind === 'window' || kind === 'item';
268
+ };
269
+ const walls = wallRoots.flatMap((object) => meshesOf(object, isIdentity));
270
+ const bounds = new Box3();
271
+ for (const zone of zones)
272
+ for (const [x, z] of zone.polygon)
273
+ bounds.expandByPoint(point.set(x, 0, z));
274
+ return {
275
+ bounds,
276
+ levels: levels.levels,
277
+ zones,
278
+ covers,
279
+ walls,
280
+ openings,
281
+ lamps: feedLamps(),
282
+ virtualLampsOn,
283
+ configureRaycaster: (raycaster) => setSurfaceRaycastLayers(raycaster.layers),
284
+ };
285
+ }
286
+ function isEmitter(material) {
287
+ return (!material.transparent &&
288
+ // An editor highlight's tint isn't a glow: a selected lamp's frame must not light up.
289
+ !isHighlight(material) &&
290
+ !!material.emissive &&
291
+ material.emissive.getHex() !== 0 &&
292
+ (material.emissiveIntensity ?? 1) > 0);
293
+ }
294
+ function emissiveMeshes(root) {
295
+ const out = [];
296
+ root.traverse((object) => {
297
+ const mesh = object;
298
+ if (!mesh.isMesh || !mesh.material)
299
+ return;
300
+ const materials = (Array.isArray(mesh.material) ? mesh.material : [mesh.material]);
301
+ if (materials.some(isEmitter))
302
+ out.push(mesh);
303
+ });
304
+ return out;
305
+ }
306
+ /**
307
+ * A lamp's private emitter material: the exported hue at a fixed display brightness (× display
308
+ * white, ÷ exposure) while its lamp is on — always reads as lit and blooms; dark when off, even
309
+ * by day. Dark emissive parts (cords, trims) stay dark.
310
+ */
311
+ function lampEmitterMaterial(source, on) {
312
+ const material = new MeshStandardNodeMaterial();
313
+ for (const key of Object.keys(source)) {
314
+ if (key === 'type' || key === 'uuid' || key === 'id' || key === 'defines')
315
+ continue;
316
+ material[key] = source[key];
317
+ }
318
+ material.name = source.name;
319
+ material.userData = { ...source.userData, daylightLampMaterial: true };
320
+ const emissive = materialEmissive;
321
+ const lum = emissive.dot(vec3(0.2126, 0.7152, 0.0722)).max(1e-4);
322
+ material.emissiveNode = emissive
323
+ .div(lum)
324
+ .mul(smoothstep(0.02, 0.08, lum))
325
+ .mul(on)
326
+ .mul(daylightUniforms.emitterDisplay)
327
+ .mul(daylightUniforms.emitterExpInv);
328
+ return material;
329
+ }
330
+ const lampEmitters = new Map();
331
+ /**
332
+ * Lamp shades and bulbs follow their own lamp (the enhanced viewer's isolated emitters): each
333
+ * lamp's emissive meshes get private materials whose emission follows that lamp's switch. A light
334
+ * can sit on a child item while the glowing mesh belongs to its parent item: search the item, then
335
+ * its nearest item ancestor. Re-installed if the item swaps its materials.
336
+ */
337
+ export function syncLampEmitters() {
338
+ const registered = new Set(sceneRegistry.nodes.values());
339
+ for (const feed of lampFeeds) {
340
+ for (const light of feed.lights()) {
341
+ const root = light.object ?? (light.nodeId ? sceneRegistry.nodes.get(light.nodeId) : undefined);
342
+ if (!root)
343
+ continue;
344
+ let meshes = emissiveMeshes(root);
345
+ for (let parent = root.parent; !meshes.length && parent; parent = parent.parent) {
346
+ const extras = parent.userData;
347
+ if (extras.pascalId || registered.has(parent)) {
348
+ if (extras.kind === undefined || extras.kind === 'item')
349
+ meshes = emissiveMeshes(parent);
350
+ break;
351
+ }
352
+ }
353
+ const on = feed.intensity(light) > 0 ? 1 : 0;
354
+ for (const mesh of meshes) {
355
+ let emitter = lampEmitters.get(mesh);
356
+ if (emitter && mesh.material !== emitter.installed) {
357
+ for (const clone of emitter.clones)
358
+ clone.dispose();
359
+ emitter = undefined;
360
+ }
361
+ if (!emitter) {
362
+ const lit = uniform(on);
363
+ const originals = (Array.isArray(mesh.material) ? mesh.material : [mesh.material]);
364
+ const clones = [];
365
+ const next = originals.map((material) => {
366
+ if (!isEmitter(material))
367
+ return material;
368
+ const clone = lampEmitterMaterial(material, lit);
369
+ clones.push(clone);
370
+ return clone;
371
+ });
372
+ const installed = Array.isArray(mesh.material) ? next : next[0];
373
+ emitter = { original: mesh.material, installed, clones, on: lit };
374
+ lampEmitters.set(mesh, emitter);
375
+ mesh.material = installed;
376
+ }
377
+ emitter.on.value = on;
378
+ }
379
+ }
380
+ }
381
+ }
382
+ /** The lamps' own materials back (the daylight look left). */
383
+ export function restoreLampEmitters() {
384
+ for (const [mesh, emitter] of lampEmitters) {
385
+ if (mesh.material === emitter.installed)
386
+ mesh.material = emitter.original;
387
+ for (const clone of emitter.clones)
388
+ clone.dispose();
389
+ }
390
+ lampEmitters.clear();
391
+ }
@@ -0,0 +1,292 @@
1
+ import { Box3, type Camera, Color, type Object3D, Raycaster, Vector2, Vector3 } from 'three';
2
+ import { type MapLevel, RoomMap } from './room-map';
3
+ /**
4
+ * "Room-aware" interior light, the game-engine way (ported from the enhanced viewer):
5
+ *
6
+ * - Zone polygons are rasterized per level into a top-down room-id map (10 cm cells). A fragment
7
+ * finds its room by looking up `p + n·0.18` — a wall face looks *into* the room it faces, so
8
+ * light never leaks through walls and there are no shadow maps to pay for.
9
+ * - Each room owns its lamps and window "portals": glass rectangles that emit sky (+ ground
10
+ * bounce) radiance inwards with an analytic rectangle form factor, plus the internally
11
+ * reflected component (split-flux) and the sky patch on the floor. Enclosed rooms see only
12
+ * a tenth of the sky environment.
13
+ * - Openings between rooms (doorways, arches, open plan) carry light from the neighbour and
14
+ * crossfade room-wide terms at the threshold.
15
+ * - At night, lit rooms throw light out through their windows (a gobo of the main lamp + the
16
+ * pane glow), and lamps outside every room light exterior surfaces analytically.
17
+ *
18
+ * Everything feeds the material lighting through `getGI` / `getAO` contexts: real diffuse
19
+ * irradiance (× albedo, × AO), not a post overlay. Shader inputs are uniforms and data textures,
20
+ * so rebuilding after an edit never recompiles a material.
21
+ */
22
+ type N = any;
23
+ export type RoomLamp = {
24
+ position: Vector3;
25
+ color: Color;
26
+ /** Candela-ish, after the per-room soft cap. */
27
+ intensity: number;
28
+ range: number;
29
+ itemId?: string;
30
+ /** Lit right now (the lamp's own switch). */
31
+ on: boolean;
32
+ /** Spotlight cone (exterior lamps keep it; inside, lamps fill their room). */
33
+ spot?: {
34
+ direction: Vector3;
35
+ cosOuter: number;
36
+ cosInner: number;
37
+ };
38
+ /**
39
+ * The light's authored intensity (cd, as the real light shines at it). Exterior lamps light
40
+ * façades and ground with it, as the scene's lights do — `intensity` is a room-metering value,
41
+ * log-compressed from it, and only caps it (layouts with physical-looking 1800 cd stay tame).
42
+ */
43
+ candela?: number;
44
+ };
45
+ export type RoomZone = {
46
+ id: string;
47
+ /** World XZ. */
48
+ polygon: [number, number][];
49
+ holes?: [number, number][][];
50
+ levelIndex: number;
51
+ };
52
+ export type RoomOpeningRoot = {
53
+ object: Object3D;
54
+ kind: 'window' | 'door';
55
+ id: string;
56
+ };
57
+ /** What the room system needs from a scene (live parametric or baked artifact). */
58
+ export type RoomSceneInput = {
59
+ bounds: Box3;
60
+ /** Every level: assembled (not exploded) world Y of its floor and its building's index. */
61
+ levels: MapLevel[];
62
+ zones: RoomZone[];
63
+ /** Ceilings, roofs and slabs (enclosure rays) with their kind. */
64
+ covers: {
65
+ object: Object3D;
66
+ kind: 'ceiling' | 'roof' | 'slab';
67
+ }[];
68
+ /** Wall meshes only (openings and their parts excluded), for walkable-opening rays. */
69
+ walls: Object3D[];
70
+ openings: RoomOpeningRoot[];
71
+ lamps: RoomLamp[];
72
+ /** Whether rooms without lamps of their own get their virtual ceiling lamp lit. */
73
+ virtualLampsOn: boolean;
74
+ /** Layers the enclosure/opening rays test (batched originals live off layer 0). */
75
+ configureRaycaster?: (raycaster: Raycaster) => void;
76
+ };
77
+ type Portal = {
78
+ center: Vector3;
79
+ /** Points into the room. */
80
+ normal: Vector3;
81
+ width: number;
82
+ height: number;
83
+ room: number;
84
+ /** 0 window glass · 1 interior opening (neighbour light) · 2 exterior opening. */
85
+ kind: 0 | 1 | 2;
86
+ neighbor: number;
87
+ doorId?: string;
88
+ gain: number;
89
+ };
90
+ type Opening = {
91
+ a: number;
92
+ b: number;
93
+ center: Vector3;
94
+ /** Horizontal, from room a towards b. */
95
+ normal: Vector3;
96
+ width: number;
97
+ height: number;
98
+ doorId?: string;
99
+ };
100
+ type Room = {
101
+ zone: RoomZone;
102
+ levelIndex: number;
103
+ enclosed: boolean;
104
+ area: number;
105
+ centroid: Vector3;
106
+ ceilingY: number;
107
+ /** Height of the topmost cover above the room, and whether it is opaque from above. */
108
+ coverY: number;
109
+ coverIsRoof: boolean;
110
+ /** Interior surface area (floor + ceiling + walls) — used by the meters. */
111
+ surfaces: number;
112
+ /** Average irradiance from lamps at full level (after the soft cap), and from daylight. */
113
+ lampE: number;
114
+ dayE: number;
115
+ lamps: RoomLamp[];
116
+ portals: Portal[];
117
+ };
118
+ export type SkySampler = (outward: Vector3) => Color;
119
+ export declare class RoomLighting {
120
+ rooms: Room[];
121
+ /** Lamps outside every room (lanterns, garden lights), lit analytically on outside surfaces. */
122
+ exteriorLamps: RoomLamp[];
123
+ portals: Portal[];
124
+ openings: Opening[];
125
+ levels: MapLevel[];
126
+ /** Which room a point is in (tiled per level; see `room-map.ts`). */
127
+ readonly map: RoomMap;
128
+ private data;
129
+ private lampTexel;
130
+ private lastSky;
131
+ readonly portalGain: import("three/webgpu").UniformNode<"float", number>;
132
+ readonly bounceGain: import("three/webgpu").UniformNode<"float", number>;
133
+ readonly lampGain: import("three/webgpu").UniformNode<"float", number>;
134
+ /**
135
+ * Lamp light as drawn, not as metered: eased below 1 while the camera is outside at night, so
136
+ * lit rooms seen from out there keep an indoor-like exposure (the meters ignore it).
137
+ */
138
+ readonly lampView: import("three/webgpu").UniformNode<"float", number>;
139
+ /** Sky-IBL multiplier in enclosed rooms. */
140
+ readonly indoorEnv: import("three/webgpu").UniformNode<"float", number>;
141
+ /** Window spill outside at night. */
142
+ readonly spillGain: import("three/webgpu").UniformNode<"float", number>;
143
+ /** Ceiling for lamp irradiance on any surface (soft): ≈3× the room-average target. */
144
+ readonly lampMaxE: import("three/webgpu").UniformNode<"float", number>;
145
+ /** Sky patch on the floor (0 on the Low tier). */
146
+ readonly skyPatch: import("three/webgpu").UniformNode<"float", number>;
147
+ /** Light through interior openings + threshold crossfade (off on the Low tier). */
148
+ readonly softRooms: import("three/webgpu").UniformNode<"float", number>;
149
+ /** Camera world Y (live). */
150
+ readonly cameraY: import("three/webgpu").UniformNode<"float", number>;
151
+ private readonly u;
152
+ /** Rebuild from a scene: rooms, lamps, portals, openings, room-id map. */
153
+ build(input: RoomSceneInput): void;
154
+ /** CPU-side room lookup in assembled space (same rule as the shader, without the normal offset). */
155
+ roomAt(p: Vector3): number;
156
+ /**
157
+ * Whether each level is on screen. A camera over a level whose upper floors are hidden stands
158
+ * in those floors' rooms by height alone, but it is outdoors: metered and white-balanced as
159
+ * such, not as a lamplit room it cannot see.
160
+ */
161
+ levelShown: boolean[];
162
+ /** Per level: focused (its rooms open from above when the camera is over their cover). */
163
+ levelFocused: boolean[];
164
+ /** Per level: the highest one shown in its building (its rooms seen from above). */
165
+ private levelTopShown;
166
+ /**
167
+ * What the lit rooms in view add to an outside camera's meter: a blend of the exterior reading
168
+ * and the rooms' own irradiance (their indoor meter), weighted by the share of the frame each
169
+ * fills — fully where it is open from above (its level the top one shown), a little where only
170
+ * its windows show — counted twice over, as the eye goes to the lit rooms. Rooms only ever
171
+ * brighten the reading, so daytime views are unchanged. `roomsE` is the rooms' own reading (0
172
+ * with none in view): what they meter at from inside. Given the outdoor illuminant, `illuminant`
173
+ * is the light the eye adapts to: the outdoors shifted towards the rooms' light by their pull.
174
+ */
175
+ viewedMeter(camera: Camera, exteriorE: number, skyE: number, outdoor?: Color, skyLum?: number): {
176
+ sceneE: number;
177
+ roomsE: number;
178
+ illuminant: Color | null;
179
+ };
180
+ isEnclosedAt(p: Vector3): {
181
+ room: number;
182
+ inside: boolean;
183
+ };
184
+ /**
185
+ * Live level presentation, per level: explode offset (live world Y − assembled), whether it is
186
+ * shown, and whether it is the selected level of a cutaway (solo / levels above hidden). A shown
187
+ * level is focused — its rooms open from above — when selected so, or when every level over it
188
+ * in its building is hidden.
189
+ */
190
+ setLevelPresentation(offsets: number[], shown: boolean[], selected: boolean[]): void;
191
+ /** Re-run the light transfer (after a quality-tier change of the soft room map). */
192
+ refresh(): void;
193
+ /** Lamp switches changed: rewrite lamp texels (+ the light carried through openings). */
194
+ setLampStates(isOn: (lamp: RoomLamp) => boolean): boolean;
195
+ /** Door openness (0 closed … 1 open) → portal gain; a closed interior door leaves a crack. */
196
+ updateDoors(openness: (doorId: string) => number): void;
197
+ /** 0…1: share of the room's lamp power currently lit. */
198
+ lampFraction(room: number): number;
199
+ /**
200
+ * Analytic light meter: the average irradiance a camera standing in `room` would see —
201
+ * daylight (portals + bounce, scaled like the shader's gains) + lamps + leftover sky IBL.
202
+ */
203
+ meter(room: number, skyE: number): number;
204
+ /** The illuminant colour inside `room` (daylight through the windows + its lit lamps). */
205
+ roomIlluminant(room: number, outdoor: Color, skyLum: number): Color | null;
206
+ /**
207
+ * Per sun/sky change: radiance leaving each portal into the room, and the per-room sun
208
+ * bounce (sun patch on the floor re-radiating — the main thing screen-space GI misses).
209
+ * `skyRadiance(dir)` returns linear sky radiance seen *through* the window (outward dir).
210
+ */
211
+ updateSky(skyRadiance: SkySampler, sunDir: Vector3, sunColor: Color, sunIrradiance: number): void;
212
+ private portalBase;
213
+ private activeBase;
214
+ private allocateData;
215
+ /**
216
+ * The outside is lit only by windows of rooms whose lamp is on and by exterior lamps that are
217
+ * on: list just those, so the per-pixel spill and lantern loops run over nothing by day and
218
+ * over the lit few at night, not over every window of the building.
219
+ */
220
+ private writeActiveLists;
221
+ private markData;
222
+ private writeLamp;
223
+ /** Interior portals emit the neighbour room's average radiance: L = E_avg·ρ/π (lamps + daylight). */
224
+ private writeInteriorPortals;
225
+ private set;
226
+ /**
227
+ * Walk every boundary between two room cells (or a room and outside) on each level's grid and
228
+ * cast short horizontal rays against the walls at 0.35 m and 1.6 m: open at both heights = a
229
+ * walkable opening (doorway, arch, open-plan edge). Windows don't count (sills are above 0.35 m).
230
+ * `walk` runs per level while its grid exists; `finish` clusters the samples into openings.
231
+ */
232
+ private openingSearch;
233
+ /** One opening from its boundary samples: measured along their own line, facing room b. */
234
+ private openingFromRun;
235
+ /** Room index (−1 outside) at a world-space position (live heights). */
236
+ roomAtNode: import("three/tsl").FnNode<[any], import("three/webgpu").VarNode<"int", import("three/webgpu").ConstNode<"int", number>>>;
237
+ /**
238
+ * The room a surface faces (probed 18 cm off it along its normal). One node per material: its
239
+ * lighting and its exterior mask share the lookup.
240
+ */
241
+ probeRoomNode(): N;
242
+ /** E' = Emax·(1 − e^(−E/Emax)), on luminance, hue kept — near-field hot spots compress, never blow out. */
243
+ private softClampLamps;
244
+ /**
245
+ * Dollhouse / roofless views: an enclosed room is open to the sky when the camera is above its
246
+ * topmost cover and that cover is hidden from there: a ceiling (hidden from above), or anything
247
+ * once its level is focused (the levels over it hidden). From inside, under the cover, the room
248
+ * stays enclosed whatever is focused: it must not take the full sky on top of its windows.
249
+ * `h2` is the room's third header texel: [coverY, roof, level, 0]; `level` its level's
250
+ * [th, entryOffset, offset, flags] (flag 1: focused).
251
+ */
252
+ private openToSky;
253
+ private texel;
254
+ /**
255
+ * vec4(irradiance, enclosure):
256
+ * irradiance — lamp + portal + bounce diffuse irradiance (linear)
257
+ * enclosure — multiplier for the sky IBL (1 outdoors, `indoorEnv` in enclosed rooms)
258
+ */
259
+ lightingNode: import("three/tsl").FnNode<[any], import("three/webgpu").JoinNode<"vec4">>;
260
+ /**
261
+ * Light thrown *out* of lit rooms through their windows onto exterior surfaces (ground,
262
+ * terraces, balconies, reveals). Two terms per exterior portal:
263
+ * - gobo: the room's main lamp projected through the window rectangle — the crisp,
264
+ * window-shaped pool on a lawn at night (exact for an unoccluded point lamp)
265
+ * - glow: the pane as a soft area emitter (lamp-lit room radiance), for the halo
266
+ * Receivers below the window's storey floor − 0.18 m get nothing, so upper windows can't
267
+ * project through slabs onto the ground.
268
+ */
269
+ exteriorSpillNode: import("three/tsl").FnNode<[any, any], import("three/webgpu").Node<"vec3">>;
270
+ /**
271
+ * Exterior lamps (lanterns, bollards, garden lights) as sized sources with the same falloff and
272
+ * soft clamp as room lamps — no hot spots a bollard's height from the paving. Applied to
273
+ * outside surfaces only.
274
+ */
275
+ exteriorLampsNode: import("three/tsl").FnNode<[any, any], any>;
276
+ /** 1 where exterior lights may shine (outside, or rooms open to the sky), 0 inside. */
277
+ exteriorMaskNode: import("three/tsl").FnNode<[any], import("three/webgpu").VarNode<"float", import("three/webgpu").ConstNode<"float", number>>>;
278
+ /** Linear lamp radiance of a room (its lit lamps) — the night tint of its window panes. */
279
+ roomGlowNode: import("three/tsl").FnNode<[any], import("three/webgpu").Node<"vec3">>;
280
+ }
281
+ export declare function pointInPolygon(x: number, z: number, poly: [number, number][]): boolean;
282
+ export declare function polygonPerimeter(poly: [number, number][]): number;
283
+ export declare function polygonArea(poly: [number, number][]): number;
284
+ export declare function polygonCentroid(poly: [number, number][]): Vector2;
285
+ /** Tanner Helland blackbody fit → linear RGB. */
286
+ /** A point inside the polygon and outside its holes: the centroid when it is, else a scan. */
287
+ export declare function interiorPoint(poly: [number, number][], holes?: [number, number][][]): Vector2;
288
+ export declare function kelvin(k: number): Color;
289
+ /** The daylight look's room system (one viewer canvas at a time). */
290
+ export declare const daylightRooms: RoomLighting;
291
+ export {};
292
+ //# sourceMappingURL=rooms.d.ts.map