@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,449 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useFrame, useThree } from '@react-three/fiber';
4
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
5
+ import { float, instancedDynamicBufferAttribute, mrt, output, smoothstep, uv, vec4, } from 'three/tsl';
6
+ import { AdditiveBlending, Color, DynamicDrawUsage, Frustum, InstancedBufferAttribute, MathUtils, Matrix4, Sphere, Sprite, SpriteNodeMaterial, Vector3, } from 'three/webgpu';
7
+ import { useSceneAtmosphere } from '../../components/viewer/scene-atmosphere';
8
+ import { isDaylightAtmosphere } from '../../lib/daylight/look';
9
+ import { lampFeeds } from '../../lib/daylight/room-scene';
10
+ import useViewer from '../../store/use-viewer';
11
+ /**
12
+ * Unshadowed pool lights only lengthen the shaders' light loop (no pass, and no recompile while
13
+ * the count holds); a shadow slot adds a depth render each frame, so those stay few. Touch
14
+ * devices get smaller pools.
15
+ */
16
+ function poolSizes() {
17
+ const touch = typeof window !== 'undefined' &&
18
+ typeof window.matchMedia === 'function' &&
19
+ window.matchMedia('(pointer: coarse)').matches;
20
+ return touch ? { point: 6, spot: 6, shadow: 2 } : { point: 16, spot: 16, shadow: 2 };
21
+ }
22
+ // A point light's shadow is six renders and a spot's one, so spots win the shadow slots.
23
+ const POINT_SHADOW_FACTOR = 0.25;
24
+ const GLOW_CAPACITY = 256;
25
+ // How bright a glow stays in full daylight, against 1 at night.
26
+ const DAYLIGHT_GLOW = 0.08;
27
+ // Strength 1 is a lamp's plain halo at night, read against tone-mapped light colours.
28
+ const GLOW_GAIN = 3;
29
+ // Where a light with no distance of its own stops counting: intensity / d² under this.
30
+ const REACH_CUTOFF = 0.01;
31
+ // Nearer than this (metres squared) a light counts as this near, so one at the eye cannot win alone.
32
+ const MIN_DISTANCE_SQ = 0.25;
33
+ // How often (in seconds) to re-evaluate which lights hold slots (fallback timer)
34
+ const REASSIGN_INTERVAL = 0.2;
35
+ // A held slot keeps its key unless a newcomer adds this much more (no flicker at the boundary).
36
+ const HYSTERESIS = 0.15;
37
+ // Camera movement that triggers an early re-evaluation
38
+ const CAM_MOVE_DIST = 0.5;
39
+ const CAM_ROT_DOT = 0.995;
40
+ const slotsOf = (size) => Array.from({ length: size }, () => ({ key: null, pendingKey: null, isFadingOut: false }));
41
+ const _sphere = new Sphere();
42
+ const _frustum = new Frustum();
43
+ const _viewProjection = new Matrix4();
44
+ /** The frustum `camera` sees, for a capture camera that never rendered as well as the live one. */
45
+ function frustumOf(camera) {
46
+ camera.updateMatrixWorld();
47
+ _viewProjection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
48
+ return _frustum.setFromProjectionMatrix(_viewProjection, camera.coordinateSystem, camera.reversedDepth);
49
+ }
50
+ /**
51
+ * Roughly what a light adds to a view: its intensity over its squared distance from the eye, or 0
52
+ * when its reach (its distance, else where it falls under REACH_CUTOFF) misses the view.
53
+ */
54
+ export function lightContribution(position, intensity, distance, eye, frustum) {
55
+ if (intensity <= 0)
56
+ return 0;
57
+ const reach = distance > 0 ? distance : Math.sqrt(intensity / REACH_CUTOFF);
58
+ if (!frustum.intersectsSphere(_sphere.set(position, reach)))
59
+ return 0;
60
+ return intensity / Math.max(position.distanceToSquared(eye), MIN_DISTANCE_SQ);
61
+ }
62
+ /**
63
+ * Gives the wanted keys (most first) the pool's slots: a key that holds a slot keeps it, and a
64
+ * newcomer evicts a key no longer wanted only on a clear win. A slot changing hands fades out
65
+ * first; an idle slot takes its key at once.
66
+ */
67
+ export function assignSlots(slots, wanted, priorityByKey) {
68
+ const desired = wanted.map((candidate) => candidate.key);
69
+ const held = new Map();
70
+ slots.forEach((slot, i) => {
71
+ const key = slot.key ?? slot.pendingKey;
72
+ if (key)
73
+ held.set(key, i);
74
+ });
75
+ const used = new Set();
76
+ const assigned = new Set();
77
+ for (const key of desired) {
78
+ const index = held.get(key);
79
+ if (index !== undefined && !used.has(index)) {
80
+ used.add(index);
81
+ assigned.add(key);
82
+ }
83
+ }
84
+ let free = 0;
85
+ for (const key of desired) {
86
+ if (assigned.has(key))
87
+ continue;
88
+ while (free < slots.length && used.has(free))
89
+ free++;
90
+ if (free >= slots.length)
91
+ break;
92
+ const slot = slots[free];
93
+ const current = slot.key ?? slot.pendingKey;
94
+ if (current && !desired.includes(current)) {
95
+ const currentPriority = priorityByKey.get(current) ?? 0;
96
+ if ((priorityByKey.get(key) ?? 0) < currentPriority * (1 + HYSTERESIS)) {
97
+ free++;
98
+ continue;
99
+ }
100
+ }
101
+ used.add(free);
102
+ assigned.add(key);
103
+ if (slot.key !== key) {
104
+ slot.pendingKey = key;
105
+ slot.isFadingOut = slot.key !== null;
106
+ if (!slot.isFadingOut) {
107
+ slot.key = key;
108
+ slot.pendingKey = null;
109
+ }
110
+ }
111
+ free++;
112
+ }
113
+ slots.forEach((slot, i) => {
114
+ if (!used.has(i) && slot.key && !desired.includes(slot.key)) {
115
+ slot.pendingKey = null;
116
+ slot.isFadingOut = true;
117
+ }
118
+ });
119
+ }
120
+ /** A pool handed over at once, for a picture: no fades, each slot on the key it is going to. */
121
+ function settle(slots) {
122
+ for (const slot of slots) {
123
+ if (slot.isFadingOut)
124
+ slot.key = slot.pendingKey;
125
+ slot.pendingKey = null;
126
+ slot.isFadingOut = false;
127
+ }
128
+ }
129
+ /** How much a glow shows: 1 at night, nearly nothing in daylight. */
130
+ export function glowDarkness(sunDirection, appearance) {
131
+ if (!sunDirection)
132
+ return appearance === 'dark' ? 1 : DAYLIGHT_GLOW;
133
+ const elevation = sunDirection.y / (sunDirection.length() || 1);
134
+ return MathUtils.lerp(1, DAYLIGHT_GLOW, MathUtils.smoothstep(elevation, -0.05, 0.3));
135
+ }
136
+ const _pos = new Vector3();
137
+ const _dir = new Vector3();
138
+ const _camPos = new Vector3();
139
+ const _camFwd = new Vector3();
140
+ const _color = new Color();
141
+ function place(light, source) {
142
+ light.color.set(source.color);
143
+ light.distance = source.distance;
144
+ if (source.getWorldPosition(_pos))
145
+ light.position.copy(_pos);
146
+ const spot = light;
147
+ if (!spot.isSpotLight)
148
+ return;
149
+ spot.angle = Math.min(Math.PI / 2, MathUtils.degToRad((source.spot?.angle ?? 60) / 2));
150
+ spot.penumbra = source.spot?.softness ?? 0;
151
+ if (source.getWorldDirection)
152
+ source.getWorldDirection(_dir);
153
+ else
154
+ _dir.set(0, -1, 0);
155
+ spot.target.position.copy(spot.position).add(_dir);
156
+ spot.target.updateMatrixWorld();
157
+ }
158
+ const fade = (light, to, dt) => {
159
+ light.intensity = MathUtils.lerp(light.intensity, to, Math.min(1, dt * 12));
160
+ if (to <= 0 && light.intensity < 0.01)
161
+ light.intensity = 0;
162
+ };
163
+ /**
164
+ * Drives a pool's slots for a frame. A slot's lights are its point light and, in the shadow
165
+ * pool, a spot beside it: the one matching its source shines, the other rests dark with its
166
+ * shadow map frozen.
167
+ */
168
+ function drivePool(slots, lights, byKey, intensity, dt) {
169
+ // dt Infinity: every light straight to its target, for a picture.
170
+ slots.forEach((slot, i) => {
171
+ const pair = lights[i];
172
+ if (!pair?.every(Boolean))
173
+ return;
174
+ const members = pair;
175
+ if (slot.isFadingOut) {
176
+ for (const light of members)
177
+ fade(light, 0, dt);
178
+ if (members.every((light) => light.intensity === 0)) {
179
+ slot.isFadingOut = false;
180
+ slot.key = slot.pendingKey;
181
+ slot.pendingKey = null;
182
+ }
183
+ return;
184
+ }
185
+ const source = slot.key ? byKey.get(slot.key) : undefined;
186
+ if (!source)
187
+ slot.key = null;
188
+ const active = source
189
+ ? (members.find((light) => Boolean(light.isSpotLight) === !!source.spot) ??
190
+ members[0])
191
+ : null;
192
+ for (const light of members) {
193
+ if (light !== active || !source) {
194
+ fade(light, 0, dt);
195
+ if (light.castShadow)
196
+ light.shadow.autoUpdate = light.intensity > 0;
197
+ continue;
198
+ }
199
+ place(light, source);
200
+ fade(light, intensity(source), dt);
201
+ if (light.castShadow)
202
+ light.shadow.autoUpdate = true;
203
+ }
204
+ });
205
+ }
206
+ function createGlows() {
207
+ const positions = new InstancedBufferAttribute(new Float32Array(GLOW_CAPACITY * 3), 3);
208
+ const colors = new InstancedBufferAttribute(new Float32Array(GLOW_CAPACITY * 3), 3);
209
+ const sizes = new InstancedBufferAttribute(new Float32Array(GLOW_CAPACITY), 1);
210
+ for (const attribute of [positions, colors, sizes])
211
+ attribute.setUsage(DynamicDrawUsage);
212
+ const material = new SpriteNodeMaterial({
213
+ transparent: true,
214
+ depthWrite: false,
215
+ blending: AdditiveBlending,
216
+ });
217
+ material.positionNode = instancedDynamicBufferAttribute(positions);
218
+ material.scaleNode = instancedDynamicBufferAttribute(sizes);
219
+ material.colorNode = instancedDynamicBufferAttribute(colors, 'vec3');
220
+ // A soft disc: full at the centre, smoothly nothing at the rim.
221
+ const falloff = smoothstep(float(1), float(0), uv().sub(0.5).length().mul(2));
222
+ material.opacityNode = falloff.mul(falloff);
223
+ // Nothing into the scene pass's normals and diffuse colour (alpha 0 under material blending): the
224
+ // ink reads the normals, and the whole quad, not just its disc, was an edge there. As the dust.
225
+ material.mrtNode = mrt({ output, diffuseColor: vec4(0), normal: vec4(0) });
226
+ material.fog = false;
227
+ const sprite = new Sprite(material);
228
+ sprite.count = 0;
229
+ sprite.frustumCulled = false;
230
+ sprite.raycast = () => { };
231
+ sprite.name = 'light-glows';
232
+ return { sprite, positions, colors, sizes };
233
+ }
234
+ const captureHooks = new WeakMap();
235
+ /**
236
+ * Sets the item lights of `scene` for a picture taken through `camera`: the pools re-ranked for
237
+ * what that camera sees and every light straight at its strength, no fade. A capture camera is not
238
+ * the live view's, and a theme switched for the picture leaves no time for lamps to fade in.
239
+ * Returns what puts the live view's lights back.
240
+ */
241
+ export function itemLightsForCapture(scene, camera) {
242
+ const restores = [...(captureHooks.get(scene) ?? [])].map((hook) => hook(camera));
243
+ return () => {
244
+ for (const restore of restores)
245
+ restore();
246
+ };
247
+ }
248
+ /**
249
+ * The lights items hold, through fixed pools of point lights, spots and two shadow-casting slots,
250
+ * handed to the lights that add most to the view (intensity over squared distance, among those
251
+ * whose reach meets the view frustum), with hysteresis, and faded across reassignment. Every pool
252
+ * light stays mounted and visible and only its intensity moves: changing the light count
253
+ * recompiles every material. The spot and shadow pools mount the first time a light needs them,
254
+ * and stay. Glows are one instanced sprite for every glowing lamp, pooled or not.
255
+ */
256
+ export function PooledLights(props) {
257
+ // The daylight look lights rooms analytically (room lamps, window spill, exterior lamps): the
258
+ // pool hands its lamps over instead of mounting point lights and glows.
259
+ const daylight = isDaylightAtmosphere(useSceneAtmosphere());
260
+ return daylight ? _jsx(DaylightLampFeed, { ...props }) : _jsx(PooledLightPool, { ...props });
261
+ }
262
+ function DaylightLampFeed(props) {
263
+ const propsRef = useRef(props);
264
+ propsRef.current = props;
265
+ useLayoutEffect(() => {
266
+ const feed = {
267
+ lights: () => propsRef.current.getLights(),
268
+ // The feed hands back the pool's own lights.
269
+ intensity: (light) => propsRef.current.intensity(light),
270
+ };
271
+ lampFeeds.add(feed);
272
+ return () => {
273
+ lampFeeds.delete(feed);
274
+ };
275
+ }, []);
276
+ return null;
277
+ }
278
+ function PooledLightPool(props) {
279
+ const propsRef = useRef(props);
280
+ propsRef.current = props;
281
+ const scene = useThree((state) => state.scene);
282
+ const atmosphere = useSceneAtmosphere();
283
+ const atmosphereRef = useRef(atmosphere);
284
+ atmosphereRef.current = atmosphere;
285
+ const [sizes] = useState(poolSizes);
286
+ const [needs, setNeeds] = useState({ spots: false, shadows: false, glows: false });
287
+ const needsRef = useRef(needs);
288
+ needsRef.current = needs;
289
+ const pools = useRef({
290
+ point: slotsOf(sizes.point),
291
+ spot: slotsOf(sizes.spot),
292
+ shadow: slotsOf(sizes.shadow),
293
+ });
294
+ const pointRefs = useRef(Array.from({ length: sizes.point }, () => [null]));
295
+ const spotRefs = useRef(Array.from({ length: sizes.spot }, () => [null]));
296
+ const shadowRefs = useRef(Array.from({ length: sizes.shadow }, () => [null, null]));
297
+ const glows = useMemo(createGlows, []);
298
+ const reassignTimer = useRef(0);
299
+ const prevCamPos = useRef(new Vector3());
300
+ const prevCamFwd = useRef(new Vector3(0, 0, -1));
301
+ const collect = () => {
302
+ const byKey = new Map();
303
+ for (const light of propsRef.current.getLights())
304
+ byKey.set(light.key, light);
305
+ return byKey;
306
+ };
307
+ /** Hands the pools to the lights that add most to what `camera` sees. */
308
+ const rank = (camera, byKey) => {
309
+ const { weight, intensity } = propsRef.current;
310
+ const frustum = frustumOf(camera);
311
+ camera.getWorldPosition(_camPos);
312
+ const mounted = needsRef.current;
313
+ const wants = { ...mounted };
314
+ const ranked = [];
315
+ for (const light of byKey.values()) {
316
+ wants.spots ||= Boolean(light.spot);
317
+ wants.shadows ||= Boolean(light.shadows);
318
+ wants.glows ||= Boolean(light.glow);
319
+ const factor = weight(light);
320
+ if (factor <= 0 || !light.getWorldPosition(_pos))
321
+ continue;
322
+ const priority = factor * lightContribution(_pos, intensity(light), light.distance, _camPos, frustum);
323
+ if (priority > 0)
324
+ ranked.push({ key: light.key, priority, light });
325
+ }
326
+ if (wants.spots !== mounted.spots ||
327
+ wants.shadows !== mounted.shadows ||
328
+ wants.glows !== mounted.glows)
329
+ setNeeds(wants);
330
+ ranked.sort((a, b) => b.priority - a.priority);
331
+ const priorityByKey = new Map(ranked.map((candidate) => [candidate.key, candidate.priority]));
332
+ const shadowed = mounted.shadows
333
+ ? ranked
334
+ .filter((candidate) => candidate.light.shadows)
335
+ .map((candidate) => ({
336
+ key: candidate.key,
337
+ priority: candidate.priority * (candidate.light.spot ? 1 : POINT_SHADOW_FACTOR),
338
+ }))
339
+ .sort((a, b) => b.priority - a.priority)
340
+ .slice(0, sizes.shadow)
341
+ : [];
342
+ const inShadowPool = new Set(shadowed.map((candidate) => candidate.key));
343
+ const rest = ranked.filter((candidate) => !inShadowPool.has(candidate.key));
344
+ assignSlots(pools.current.shadow, shadowed, priorityByKey);
345
+ assignSlots(pools.current.spot, mounted.spots ? rest.filter((candidate) => candidate.light.spot).slice(0, sizes.spot) : [], priorityByKey);
346
+ // Until the spot pool mounts, a spot shines as a point light.
347
+ assignSlots(pools.current.point, rest.filter((candidate) => !(mounted.spots && candidate.light.spot)).slice(0, sizes.point), priorityByKey);
348
+ };
349
+ const drive = (byKey, dt) => {
350
+ const { intensity } = propsRef.current;
351
+ drivePool(pools.current.point, pointRefs.current, byKey, intensity, dt);
352
+ drivePool(pools.current.spot, spotRefs.current, byKey, intensity, dt);
353
+ drivePool(pools.current.shadow, shadowRefs.current, byKey, intensity, dt);
354
+ };
355
+ /**
356
+ * The glows, each at its bulb and depth-tested: the fixture's opaque housing hides what lies
357
+ * behind it and its glass shows it. Drawn nearer the eye, a glow covered the fixture's own body.
358
+ */
359
+ const placeGlows = (byKey) => {
360
+ if (!needsRef.current.glows)
361
+ return;
362
+ const { intensity } = propsRef.current;
363
+ const darkness = glowDarkness(atmosphereRef.current?.sunDirection ?? null, useViewer.getState().sceneAppearance.appearance);
364
+ let count = 0;
365
+ for (const light of byKey.values()) {
366
+ if (!light.glow || count >= GLOW_CAPACITY)
367
+ continue;
368
+ const now = intensity(light);
369
+ if (now <= 0 || !light.getWorldPosition(_pos))
370
+ continue;
371
+ const level = light.peak ? Math.min(1, now / light.peak) : 1;
372
+ _color
373
+ .set(light.color)
374
+ .multiplyScalar(GLOW_GAIN * (light.glow.strength ?? 1) * level * darkness);
375
+ glows.positions.setXYZ(count, _pos.x, _pos.y, _pos.z);
376
+ glows.colors.setXYZ(count, _color.r, _color.g, _color.b);
377
+ glows.sizes.setX(count, light.glow.size);
378
+ count++;
379
+ }
380
+ glows.sprite.count = count;
381
+ glows.sprite.visible = count > 0;
382
+ glows.positions.needsUpdate = true;
383
+ glows.colors.needsUpdate = true;
384
+ glows.sizes.needsUpdate = true;
385
+ };
386
+ useEffect(() => {
387
+ const hook = (camera) => {
388
+ const slots = Object.values(pools.current).map((pool) => pool.map((slot) => ({ ...slot })));
389
+ const lights = [...pointRefs.current, ...spotRefs.current, ...shadowRefs.current]
390
+ .flat()
391
+ .filter((light) => Boolean(light));
392
+ const intensities = lights.map((light) => light.intensity);
393
+ const byKey = collect();
394
+ rank(camera, byKey);
395
+ for (const pool of Object.values(pools.current))
396
+ settle(pool);
397
+ drive(byKey, Number.POSITIVE_INFINITY);
398
+ placeGlows(byKey);
399
+ return () => {
400
+ Object.values(pools.current).forEach((pool, i) => {
401
+ pool.forEach((slot, j) => {
402
+ Object.assign(slot, slots[i][j]);
403
+ });
404
+ });
405
+ lights.forEach((light, i) => {
406
+ light.intensity = intensities[i];
407
+ });
408
+ reassignTimer.current = 0;
409
+ };
410
+ };
411
+ let hooks = captureHooks.get(scene);
412
+ if (!hooks) {
413
+ hooks = new Set();
414
+ captureHooks.set(scene, hooks);
415
+ }
416
+ hooks.add(hook);
417
+ return () => {
418
+ hooks.delete(hook);
419
+ };
420
+ // biome-ignore lint/correctness/useExhaustiveDependencies: the hook reads the latest props and pools through refs
421
+ }, [scene]);
422
+ useFrame(({ camera }, delta) => {
423
+ const byKey = collect();
424
+ camera.getWorldPosition(_camPos);
425
+ camera.getWorldDirection(_camFwd);
426
+ const camMoved = _camPos.distanceTo(prevCamPos.current) > CAM_MOVE_DIST ||
427
+ _camFwd.dot(prevCamFwd.current) < CAM_ROT_DOT;
428
+ reassignTimer.current -= delta;
429
+ if (reassignTimer.current <= 0 || camMoved) {
430
+ reassignTimer.current = REASSIGN_INTERVAL;
431
+ prevCamPos.current.copy(_camPos);
432
+ prevCamFwd.current.copy(_camFwd);
433
+ rank(camera, byKey);
434
+ }
435
+ drive(byKey, Math.min(delta, 0.1));
436
+ placeGlows(byKey);
437
+ }, 6);
438
+ return (_jsxs(_Fragment, { children: [pointRefs.current.map((_, i) => (_jsx("pointLight", { castShadow: false, intensity: 0, ref: (el) => {
439
+ pointRefs.current[i][0] = el;
440
+ } }, i))), needs.spots &&
441
+ spotRefs.current.map((_, i) => (_jsx("spotLight", { castShadow: false, intensity: 0, ref: (el) => {
442
+ spotRefs.current[i][0] = el;
443
+ } }, i))), needs.shadows &&
444
+ shadowRefs.current.map((_, i) => (_jsxs("group", { children: [_jsx("pointLight", { castShadow: true, intensity: 0, ref: (el) => {
445
+ shadowRefs.current[i][0] = el;
446
+ }, "shadow-bias": -0.0005, "shadow-camera-far": 20, "shadow-camera-near": 0.05, "shadow-mapSize": [512, 512], "shadow-normalBias": 0.02 }), _jsx("spotLight", { castShadow: true, intensity: 0, ref: (el) => {
447
+ shadowRefs.current[i][1] = el;
448
+ }, "shadow-bias": -0.0005, "shadow-camera-far": 20, "shadow-camera-near": 0.05, "shadow-mapSize": [1024, 1024], "shadow-normalBias": 0.02 })] }, i))), needs.glows && _jsx("primitive", { object: glows.sprite })] }));
449
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"level-system.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-system.tsx"],"names":[],"mappings":"AAWA,eAAO,MAAM,WAAW,YAyEvB,CAAA"}
1
+ {"version":3,"file":"level-system.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-system.tsx"],"names":[],"mappings":"AAqBA,eAAO,MAAM,WAAW,YAiFvB,CAAA"}
@@ -1,12 +1,21 @@
1
1
  import { getLevelElevations, sceneRegistry, useScene } from '@pascal-app/core';
2
2
  import { useFrame } from '@react-three/fiber';
3
3
  import { lerp } from 'three/src/math/MathUtils.js';
4
+ import { getLayoutRevision, isLayoutObserved } from '../../lib/layer-pass';
4
5
  import { applyShadowOnly, clearShadowOnly } from '../../lib/shadow-only';
5
6
  import useViewer from '../../store/use-viewer';
6
7
  import { heldLevelY } from './level-hold';
7
8
  import { EXPLODED_GAP, resolveLevelVisibility } from './level-utils';
8
9
  import { shadowOnlyLevels } from './shadow-only-levels';
9
10
  const placed = new WeakSet();
11
+ /** Layout revision right after a level was last made shadow-only (see layer-pass.ts). */
12
+ const shadowOnlyAt = new WeakMap();
13
+ function sceneRoot(object) {
14
+ let root = object;
15
+ while (root.parent)
16
+ root = root.parent;
17
+ return root;
18
+ }
10
19
  export const LevelSystem = () => {
11
20
  useFrame((_, delta) => {
12
21
  const nodes = useScene.getState().nodes;
@@ -64,12 +73,21 @@ export const LevelSystem = () => {
64
73
  revealing: levelsRevealing.includes(levelId),
65
74
  });
66
75
  if (shadowOnly) {
67
- applyShadowOnly(obj);
76
+ // Re-applied so meshes rebuilt while hidden are hidden too — a rebuild inserts children
77
+ // or writes masks, which moves the observed layout revision; without either the level
78
+ // is as it was left.
79
+ const observed = isLayoutObserved(sceneRoot(obj));
80
+ if (!observed || shadowOnlyAt.get(obj) !== getLayoutRevision()) {
81
+ applyShadowOnly(obj);
82
+ if (observed)
83
+ shadowOnlyAt.set(obj, getLayoutRevision());
84
+ }
68
85
  shadowOnlyLevels.add(obj);
69
86
  }
70
87
  else if (shadowOnlyLevels.has(obj)) {
71
88
  clearShadowOnly(obj);
72
89
  shadowOnlyLevels.delete(obj);
90
+ shadowOnlyAt.delete(obj);
73
91
  }
74
92
  obj.visible = visible;
75
93
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * A roof mesh's material slots. Slots 0–3 come out of the CSG (0 wall / trim,
3
+ * 1 deck, 2 interior and fascia, 3 shingle top); the wall bands are split off
4
+ * slot 0 after it (`splitRoofWallBandFaces`): the heel band (the wall between
5
+ * the top plate and the eaves) and each gable end, so each paints apart from
6
+ * the edge role. The merged shell splits only the heel band; a segment's own
7
+ * mesh splits its gables too.
8
+ */
9
+ export declare const ROOF_HEEL_MATERIAL_INDEX = 4;
10
+ export declare const ROOF_GABLE_LEFT_MATERIAL_INDEX = 5;
11
+ export declare const ROOF_GABLE_RIGHT_MATERIAL_INDEX = 6;
12
+ /** Every slot a roof mesh can carry: the four CSG slots plus the wall bands. */
13
+ export declare const ROOF_RENDER_MATERIAL_COUNT = 7;
14
+ //# sourceMappingURL=roof-material-slots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roof-material-slots.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-material-slots.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAA;AACzC,eAAO,MAAM,8BAA8B,IAAI,CAAA;AAC/C,eAAO,MAAM,+BAA+B,IAAI,CAAA;AAChD,gFAAgF;AAChF,eAAO,MAAM,0BAA0B,IAAI,CAAA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * A roof mesh's material slots. Slots 0–3 come out of the CSG (0 wall / trim,
3
+ * 1 deck, 2 interior and fascia, 3 shingle top); the wall bands are split off
4
+ * slot 0 after it (`splitRoofWallBandFaces`): the heel band (the wall between
5
+ * the top plate and the eaves) and each gable end, so each paints apart from
6
+ * the edge role. The merged shell splits only the heel band; a segment's own
7
+ * mesh splits its gables too.
8
+ */
9
+ export const ROOF_HEEL_MATERIAL_INDEX = 4;
10
+ export const ROOF_GABLE_LEFT_MATERIAL_INDEX = 5;
11
+ export const ROOF_GABLE_RIGHT_MATERIAL_INDEX = 6;
12
+ /** Every slot a roof mesh can carry: the four CSG slots plus the wall bands. */
13
+ export const ROOF_RENDER_MATERIAL_COUNT = 7;
@@ -2,7 +2,20 @@ import { type AnyNode, type RoofNode, type SceneMaterial, type SceneMaterialId }
2
2
  import type * as THREE from 'three';
3
3
  import { type ColorPreset, type RenderShading } from '../../lib/materials';
4
4
  type SceneMaterials = Record<SceneMaterialId, SceneMaterial> | undefined;
5
- export type RoofMaterialArray = [THREE.Material, THREE.Material, THREE.Material, THREE.Material];
5
+ /**
6
+ * One material per roof mesh slot (`roof-material-slots.ts`): 0 wall / trim
7
+ * (the edge role), 1 deck, 2 interior, 3 shingle top, 4 the heel band, 5 and
8
+ * 6 the gable ends — the merged shell's gables are the edge role's.
9
+ */
10
+ export type RoofMaterialArray = [
11
+ THREE.Material,
12
+ THREE.Material,
13
+ THREE.Material,
14
+ THREE.Material,
15
+ THREE.Material,
16
+ THREE.Material,
17
+ THREE.Material
18
+ ];
6
19
  /**
7
20
  * The cladding the walls under a roof declare through their ASSEMBLIES
8
21
  * (`wall.assembly.exterior.finish` → catalog ref), so the roof's gable band —
@@ -13,8 +26,18 @@ export type RoofMaterialArray = [THREE.Material, THREE.Material, THREE.Material,
13
26
  * null when none declares one.
14
27
  */
15
28
  export declare function levelWallCladdingRef(nodes: Readonly<Record<string, AnyNode | undefined>>, roof: Pick<RoofNode, 'parentId'>): string | null;
29
+ /**
30
+ * What the walls under a roof show outside, for the heel band — the strip of
31
+ * wall between their top plate and the eaves, which is wall, not trim: their
32
+ * exterior face's paint, else the cladding their assembly declares
33
+ * (`levelWallCladdingRef`'s walls). The most common wins; null when none has
34
+ * either.
35
+ */
36
+ export declare function levelWallFaceRef(nodes: Readonly<Record<string, AnyNode | undefined>>, roof: Pick<RoofNode, 'parentId'>): string | null;
16
37
  export declare function getRoofMaterialArray(node: RoofNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string,
17
38
  /** Catalog ref for the gable/trim band when unpainted — see `levelWallCladdingRef`. */
18
- wallCladdingRef?: string | null, sceneMaterials?: SceneMaterials): RoofMaterialArray | null;
39
+ wallCladdingRef?: string | null, sceneMaterials?: SceneMaterials,
40
+ /** Catalog ref for the heel band when unpainted — see `levelWallFaceRef`. */
41
+ wallFaceRef?: string | null): RoofMaterialArray | null;
19
42
  export {};
20
43
  //# sourceMappingURL=roof-materials.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"roof-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAMZ,KAAK,QAAQ,EAEb,KAAK,aAAa,EAClB,KAAK,eAAe,EAGrB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AACnC,OAAO,EACL,KAAK,WAAW,EAGhB,KAAK,aAAa,EAGnB,MAAM,qBAAqB,CAAA;AAE5B,KAAK,cAAc,GAAG,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC,GAAG,SAAS,CAAA;AAaxE,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AAqChG;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC,CAAC,EACpD,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,GAC/B,MAAM,GAAG,IAAI,CAyBf;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,EACd,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM;AACnB,uFAAuF;AACvF,eAAe,GAAE,MAAM,GAAG,IAAW,EACrC,cAAc,CAAC,EAAE,cAAc,GAC9B,iBAAiB,GAAG,IAAI,CA4D1B"}
1
+ {"version":3,"file":"roof-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAOZ,KAAK,QAAQ,EAEb,KAAK,aAAa,EAClB,KAAK,eAAe,EAGrB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AACnC,OAAO,EACL,KAAK,WAAW,EAGhB,KAAK,aAAa,EAGnB,MAAM,qBAAqB,CAAA;AAE5B,KAAK,cAAc,GAAG,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC,GAAG,SAAS,CAAA;AAaxE;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,CAAC,QAAQ;IACd,KAAK,CAAC,QAAQ;IACd,KAAK,CAAC,QAAQ;IACd,KAAK,CAAC,QAAQ;IACd,KAAK,CAAC,QAAQ;IACd,KAAK,CAAC,QAAQ;IACd,KAAK,CAAC,QAAQ;CACf,CAAA;AA4FD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC,CAAC,EACpD,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,GAC/B,MAAM,GAAG,IAAI,CAEf;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC,CAAC,EACpD,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,GAC/B,MAAM,GAAG,IAAI,CAEf;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,EACd,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM;AACnB,uFAAuF;AACvF,eAAe,GAAE,MAAM,GAAG,IAAW,EACrC,cAAc,CAAC,EAAE,cAAc;AAC/B,6EAA6E;AAC7E,WAAW,GAAE,MAAM,GAAG,IAAW,GAChC,iBAAiB,GAAG,IAAI,CAqF1B"}