@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,74 +1,232 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { sceneRegistry } from '@pascal-app/core';
2
3
  import { useFrame } from '@react-three/fiber';
3
4
  import { useMemo, useRef } from 'react';
4
5
  import * as THREE from 'three/webgpu';
6
+ import { SHADOW_ONLY_LAYER } from '../../lib/layers';
7
+ import { getSceneTheme } from '../../lib/scene-themes';
5
8
  import useViewer from '../../store/use-viewer';
9
+ // Diagnostic toggle: `?disable=shadows` skips the shadow-map render pass
10
+ // (which doubles draw calls for every shadow-casting mesh) so you can
11
+ // isolate how much of the baseline GPU cost is shadows vs. raw geometry.
12
+ const SHADOWS_DISABLED = typeof window !== 'undefined' &&
13
+ new Set((new URLSearchParams(window.location.search).get('disable') ?? '')
14
+ .split(',')
15
+ .map((s) => s.trim())).has('shadows');
16
+ // Diagnostic toggle: `?debug=shadowcamera` draws a CameraHelper for each
17
+ // shadow camera so the building-fit frustum (and thus shadow texel density)
18
+ // can be inspected while tuning margins/bias.
19
+ const SHADOW_CAMERA_DEBUG = typeof window !== 'undefined' &&
20
+ new Set((new URLSearchParams(window.location.search).get('debug') ?? '')
21
+ .split(',')
22
+ .map((s) => s.trim())).has('shadowcamera');
23
+ // Shadow darkness for the bright key lights (themes drive most lights past
24
+ // intensity 1). Runs high so shadowed areas actually lose the sun's
25
+ // contribution — the ambient/hemisphere/IBL stack provides the fill. The old
26
+ // 0.55 clamp leaked 45% of the key light into shadow and flattened interiors;
27
+ // 0.9 read too heavy in review.
28
+ const MAX_SHADOW_INTENSITY = 0.75;
29
+ // `normalBias` is measured in world units. The previous 0.3 moved shadow
30
+ // lookups 30 cm off their surfaces, visibly detaching wall shadows at the
31
+ // floor; 0.07 and below still acnes on the building-fit 1024 map (large
32
+ // texels), so 0.08 is the smallest acne-free value at that resolution — even
33
+ // with the depth bias at -0.0005 (which is itself needed: 0.08 alone still
34
+ // showed faint acne at -0.0001).
35
+ const SHADOW_DEPTH_BIAS = -0.0005;
36
+ const SHADOW_NORMAL_BIAS = 0.08;
37
+ // Shadow frustum framing. The frustum is fit to the BUILDING geometry (not the
38
+ // camera): we union the bounds of all registered scene nodes, fit a sphere, and
39
+ // size the directional light's ortho shadow camera to that sphere plus a margin.
40
+ // This keeps shadows anchored to the building and a bit of surrounding ground no
41
+ // matter how the user zooms or pans — fixing the previous camera-following
42
+ // behaviour that fell apart when zoomed out (frustum too small) or zoomed into
43
+ // an empty corner (frustum centred on nothing).
44
+ //
45
+ // `site` nodes (the ground/site plane, which can be arbitrarily large) are
46
+ // excluded so they don't blow the frustum up to cover the whole lot.
47
+ const SHADOW_EXCLUDED_TYPES = ['site'];
48
+ // How often (seconds) to recompute building bounds. Bounds only change while
49
+ // editing, so we throttle the (subtree-walking) union instead of doing it every
50
+ // frame.
51
+ const BOUNDS_REFRESH_INTERVAL = 0.4;
52
+ // Extra coverage around the building bounds — the "and a bit nearby" margin so
53
+ // shadows don't get clipped right at the walls. Scales with building size.
54
+ const SHADOW_MARGIN_SCALE = 1.15;
55
+ const SHADOW_MARGIN = 3;
56
+ // Gap between the building bounds sphere and the light / near plane.
57
+ const SHADOW_BACKOFF = 10;
58
+ // Fallback radius when the scene has no building geometry yet (empty scene).
59
+ const SHADOW_FALLBACK_RADIUS = 30;
6
60
  export function Lights() {
7
- const theme = useViewer((state) => state.theme);
8
- const isDark = theme === 'dark';
9
- const light1Ref = useRef(null);
61
+ const sceneTheme = useViewer((state) => state.sceneTheme);
62
+ const theme = getSceneTheme(sceneTheme);
63
+ const shadows = useViewer((state) => state.shadows);
64
+ const lightRefs = useRef([]);
10
65
  const shadowCamera = useRef(null);
11
- const shadowCameraSize = 50; // The "area" around the camera to shadow
12
- const light2Ref = useRef(null);
13
- const light3Ref = useRef(null);
66
+ // Initial ortho half-size; overridden each refresh to fit the building.
67
+ const shadowCameraSize = 50;
68
+ // Building bounds the shadow frustum is fit to, recomputed on an interval.
69
+ const shadowFocus = useRef(new THREE.Vector3()); // sphere centre
70
+ const shadowRadius = useRef(SHADOW_FALLBACK_RADIUS); // sphere radius
71
+ const shadowDir = useRef(new THREE.Vector3()); // scratch: per-light direction
72
+ const boundsBox = useRef(new THREE.Box3()); // scratch: union AABB
73
+ const boundsSphere = useRef(new THREE.Sphere()); // scratch: fitted sphere
74
+ const lastBoundsTime = useRef(-1); // last refresh timestamp (-1 = never)
75
+ const shadowHelpers = useRef([]);
76
+ const hemiRef = useRef(null);
14
77
  const ambientRef = useRef(null);
15
78
  const initialized = useRef(false);
79
+ const lightTargets = useRef([]);
16
80
  const targets = useMemo(() => ({
17
- l1Color: new THREE.Color(),
18
- l2Color: new THREE.Color(),
19
- l3Color: new THREE.Color(),
81
+ hemiSky: new THREE.Color(),
82
+ hemiGround: new THREE.Color(),
20
83
  ambColor: new THREE.Color(),
21
84
  }), []);
22
- useFrame((_, delta) => {
85
+ useFrame((state, delta) => {
23
86
  // clamp delta to avoid huge jumps on tab switch
24
87
  const dt = Math.min(delta, 0.1) * 4;
25
- if (!initialized.current) {
26
- if (light1Ref.current) {
27
- light1Ref.current.intensity = isDark ? 0.8 : 4;
28
- light1Ref.current.color.set(isDark ? '#e0e5ff' : '#ffffff');
29
- if (light1Ref.current.shadow)
30
- light1Ref.current.shadow.intensity = isDark ? 0.8 : 0.4;
88
+ // Fit each shadow-casting light's frustum to the BUILDING geometry rather
89
+ // than the camera. We refresh the union bounds on an interval (cheap enough,
90
+ // and bounds only change while editing), fit a sphere, and size + place the
91
+ // ortho shadow camera so the building (plus a margin) is fully covered from
92
+ // the light's direction. The light DIRECTION stays exactly as the theme
93
+ // specifies; only its position/distance and the frustum extents change.
94
+ if (shadows) {
95
+ const now = state.clock.elapsedTime;
96
+ if (now - lastBoundsTime.current >= BOUNDS_REFRESH_INTERVAL) {
97
+ lastBoundsTime.current = now;
98
+ const box = boundsBox.current.makeEmpty();
99
+ for (const [id, obj] of sceneRegistry.nodes) {
100
+ if (SHADOW_EXCLUDED_TYPES.some((t) => sceneRegistry.byType[t].has(id)))
101
+ continue;
102
+ box.expandByObject(obj);
103
+ }
104
+ box.getBoundingSphere(boundsSphere.current);
105
+ const center = boundsSphere.current.center;
106
+ const radius = boundsSphere.current.radius;
107
+ // Empty scene OR a node with a NaN position/geometry poisoning the union
108
+ // box: fall back to the origin with a default radius. The directional
109
+ // light's position is derived from `focus`, so a single non-finite mesh
110
+ // must NOT be allowed to make `focus`/`radius` NaN — that breaks every
111
+ // shadow-casting light's position and renders the whole scene black.
112
+ const finiteBounds = !box.isEmpty() &&
113
+ Number.isFinite(center.x) &&
114
+ Number.isFinite(center.y) &&
115
+ Number.isFinite(center.z) &&
116
+ Number.isFinite(radius);
117
+ if (finiteBounds) {
118
+ shadowFocus.current.copy(center);
119
+ shadowRadius.current = radius;
120
+ }
121
+ else {
122
+ shadowFocus.current.set(0, 0, 0);
123
+ shadowRadius.current = SHADOW_FALLBACK_RADIUS;
124
+ }
31
125
  }
32
- if (light2Ref.current) {
33
- light2Ref.current.intensity = isDark ? 0.2 : 0.75;
34
- light2Ref.current.color.set(isDark ? '#8090ff' : '#ffffff');
126
+ const focus = shadowFocus.current;
127
+ // Ortho half-extent: the building sphere plus a proportional margin.
128
+ const size = shadowRadius.current * SHADOW_MARGIN_SCALE + SHADOW_MARGIN;
129
+ // Park the light just outside the sphere so the near plane stays positive
130
+ // and the whole building fits between near and far along the light axis.
131
+ const distance = size + SHADOW_BACKOFF;
132
+ const near = SHADOW_BACKOFF;
133
+ const far = distance + size;
134
+ for (let index = 0; index < theme.lights.length; index++) {
135
+ const config = theme.lights[index];
136
+ const light = lightRefs.current[index];
137
+ if (!(config?.castShadow && light))
138
+ continue;
139
+ const [ox, oy, oz] = config.position;
140
+ const dir = shadowDir.current.set(ox, oy, oz);
141
+ if (dir.lengthSq() === 0)
142
+ dir.set(0, 1, 0);
143
+ dir.normalize().multiplyScalar(distance);
144
+ light.position.set(focus.x + dir.x, focus.y + dir.y, focus.z + dir.z);
145
+ light.target.position.copy(focus);
146
+ light.target.updateMatrixWorld();
147
+ // Resize the ortho frustum to the fitted bounds. The shadow camera is
148
+ // the <orthographicCamera attach="shadow-camera"> below.
149
+ const cam = light.shadow?.camera;
150
+ if (cam) {
151
+ // Shadow-caster-only geometry (hidden roofs/levels in cutaway views)
152
+ // is visible to the shadow pass alone — see lib/shadow-only.ts.
153
+ cam.layers.enable(SHADOW_ONLY_LAYER);
154
+ cam.left = -size;
155
+ cam.right = size;
156
+ cam.top = size;
157
+ cam.bottom = -size;
158
+ cam.near = near;
159
+ cam.far = far;
160
+ cam.updateProjectionMatrix();
161
+ if (SHADOW_CAMERA_DEBUG) {
162
+ let helper = shadowHelpers.current[index];
163
+ if (!helper) {
164
+ helper = new THREE.CameraHelper(cam);
165
+ shadowHelpers.current[index] = helper;
166
+ state.scene.add(helper);
167
+ }
168
+ helper.update();
169
+ }
170
+ }
35
171
  }
36
- if (light3Ref.current) {
37
- light3Ref.current.intensity = isDark ? 0.3 : 1;
38
- light3Ref.current.color.set(isDark ? '#a0b0ff' : '#ffffff');
172
+ }
173
+ if (!initialized.current) {
174
+ for (let index = 0; index < theme.lights.length; index++) {
175
+ const config = theme.lights[index];
176
+ const light = lightRefs.current[index];
177
+ if (!(config && light))
178
+ continue;
179
+ light.intensity = config.intensity;
180
+ light.color.set(config.color);
181
+ if (config.castShadow && light.shadow) {
182
+ light.shadow.intensity = config.intensity <= 1 ? config.intensity : MAX_SHADOW_INTENSITY;
183
+ }
184
+ }
185
+ if (hemiRef.current && theme.hemi) {
186
+ hemiRef.current.intensity = theme.hemi.intensity;
187
+ hemiRef.current.color.set(theme.hemi.sky);
188
+ hemiRef.current.groundColor.set(theme.hemi.ground);
39
189
  }
40
190
  if (ambientRef.current) {
41
- ambientRef.current.intensity = isDark ? 0.15 : 0.5;
42
- ambientRef.current.color.set(isDark ? '#a0b0ff' : '#ffffff');
191
+ ambientRef.current.intensity = theme.ambient.intensity;
192
+ ambientRef.current.color.set(theme.ambient.color);
43
193
  }
44
194
  initialized.current = true;
45
195
  return;
46
196
  }
47
- if (light1Ref.current) {
48
- light1Ref.current.intensity = THREE.MathUtils.lerp(light1Ref.current.intensity, isDark ? 0.8 : 4, dt);
49
- targets.l1Color.set(isDark ? '#e0e5ff' : '#ffffff');
50
- light1Ref.current.color.lerp(targets.l1Color, dt);
51
- if (light1Ref.current.shadow) {
52
- if (light1Ref.current.shadow.intensity !== undefined) {
53
- light1Ref.current.shadow.intensity = THREE.MathUtils.lerp(light1Ref.current.shadow.intensity, isDark ? 0.8 : 0.4, dt);
197
+ for (let index = 0; index < theme.lights.length; index++) {
198
+ const config = theme.lights[index];
199
+ const light = lightRefs.current[index];
200
+ if (!(config && light))
201
+ continue;
202
+ light.intensity = THREE.MathUtils.lerp(light.intensity, config.intensity, dt);
203
+ let target = lightTargets.current[index];
204
+ if (!target) {
205
+ target = new THREE.Color();
206
+ lightTargets.current[index] = target;
207
+ }
208
+ target.set(config.color);
209
+ light.color.lerp(target, dt);
210
+ if (config.castShadow && light.shadow) {
211
+ if (light.shadow.intensity !== undefined) {
212
+ light.shadow.intensity = THREE.MathUtils.lerp(light.shadow.intensity, config.intensity <= 1 ? config.intensity : MAX_SHADOW_INTENSITY, dt);
54
213
  }
55
214
  }
56
215
  }
57
- if (light2Ref.current) {
58
- light2Ref.current.intensity = THREE.MathUtils.lerp(light2Ref.current.intensity, isDark ? 0.2 : 0.75, dt);
59
- targets.l2Color.set(isDark ? '#8090ff' : '#ffffff');
60
- light2Ref.current.color.lerp(targets.l2Color, dt);
61
- }
62
- if (light3Ref.current) {
63
- light3Ref.current.intensity = THREE.MathUtils.lerp(light3Ref.current.intensity, isDark ? 0.3 : 1, dt);
64
- targets.l3Color.set(isDark ? '#a0b0ff' : '#ffffff');
65
- light3Ref.current.color.lerp(targets.l3Color, dt);
216
+ if (hemiRef.current && theme.hemi) {
217
+ hemiRef.current.intensity = THREE.MathUtils.lerp(hemiRef.current.intensity, theme.hemi.intensity, dt);
218
+ targets.hemiSky.set(theme.hemi.sky);
219
+ hemiRef.current.color.lerp(targets.hemiSky, dt);
220
+ targets.hemiGround.set(theme.hemi.ground);
221
+ hemiRef.current.groundColor.lerp(targets.hemiGround, dt);
66
222
  }
67
223
  if (ambientRef.current) {
68
- ambientRef.current.intensity = THREE.MathUtils.lerp(ambientRef.current.intensity, isDark ? 0.15 : 0.5, dt);
69
- targets.ambColor.set(isDark ? '#a0b0ff' : '#ffffff');
224
+ ambientRef.current.intensity = THREE.MathUtils.lerp(ambientRef.current.intensity, theme.ambient.intensity, dt);
225
+ targets.ambColor.set(theme.ambient.color);
70
226
  ambientRef.current.color.lerp(targets.ambColor, dt);
71
227
  }
72
228
  });
73
- return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { castShadow: true, position: [10, 10, 10], ref: light1Ref, "shadow-bias": -0.002, "shadow-mapSize": [1024, 1024], "shadow-normalBias": 0.3, "shadow-radius": 3, children: _jsx("orthographicCamera", { attach: "shadow-camera", bottom: -shadowCameraSize, far: 100, left: -shadowCameraSize, near: 1, ref: shadowCamera, right: shadowCameraSize, top: shadowCameraSize }) }), _jsx("directionalLight", { position: [-10, 10, -10], ref: light2Ref }), _jsx("directionalLight", { position: [-10, 10, 10], ref: light3Ref }), _jsx("ambientLight", { ref: ambientRef })] }));
229
+ return (_jsxs(_Fragment, { children: [theme.lights.map((light, index) => (_jsx("directionalLight", { castShadow: Boolean(light.castShadow) && !SHADOWS_DISABLED, position: light.position, ref: (ref) => {
230
+ lightRefs.current[index] = ref;
231
+ }, "shadow-bias": SHADOW_DEPTH_BIAS, "shadow-mapSize": [1024, 1024], "shadow-normalBias": SHADOW_NORMAL_BIAS, "shadow-radius": 2, children: light.castShadow && !SHADOWS_DISABLED ? (_jsx("orthographicCamera", { attach: "shadow-camera", bottom: -shadowCameraSize, far: 400, left: -shadowCameraSize, near: 1, ref: shadowCamera, right: shadowCameraSize, top: shadowCameraSize })) : null }, `${index}-${light.position.join(',')}`))), theme.hemi ? _jsx("hemisphereLight", { ref: hemiRef }) : null, _jsx("ambientLight", { ref: ambientRef })] }));
74
232
  }
@@ -1,2 +1,2 @@
1
- export declare const PerfMonitor: () => import("react/jsx-runtime").JSX.Element;
1
+ export declare const PerfMonitor: () => import("react").JSX.Element;
2
2
  //# sourceMappingURL=perf-monitor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"perf-monitor.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/perf-monitor.tsx"],"names":[],"mappings":"AAOA,eAAO,MAAM,WAAW,+CAoDvB,CAAA"}
1
+ {"version":3,"file":"perf-monitor.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/perf-monitor.tsx"],"names":[],"mappings":"AAQA,eAAO,MAAM,WAAW,mCAsJvB,CAAA"}
@@ -1,15 +1,49 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useScene } from '@pascal-app/core';
3
3
  import { Html } from '@react-three/drei';
4
- import { useFrame } from '@react-three/fiber';
5
- import { useRef, useState } from 'react';
4
+ import { useFrame, useThree } from '@react-three/fiber';
5
+ import { useEffect, useRef, useState } from 'react';
6
+ import { drainGpuSamples } from '../../lib/gpu-perf';
6
7
  const SAMPLE_INTERVAL = 0.5; // seconds between display updates
7
8
  export const PerfMonitor = () => {
8
- const [stats, setStats] = useState({ fps: 0, frameMs: 0, drawCalls: 0, triangles: 0, dirty: 0 });
9
+ const [stats, setStats] = useState({
10
+ fps: 0,
11
+ frameMs: 0,
12
+ gpuMs: 0,
13
+ gpuMaxMs: 0,
14
+ drawCalls: 0,
15
+ triangles: 0,
16
+ dirty: 0,
17
+ dirtyDetail: '',
18
+ meshes: 0,
19
+ lines: 0,
20
+ sprites: 0,
21
+ lights: 0,
22
+ });
9
23
  const frameCount = useRef(0);
10
24
  const elapsed = useRef(0);
11
25
  const lastMs = useRef(0);
12
- useFrame(({ gl, clock }) => {
26
+ // Carry the previous tick's reading forward when no fresh samples arrive,
27
+ // so the display doesn't flicker to "—" on slow resolve windows.
28
+ const lastGpuMs = useRef(0);
29
+ const lastGpuMaxMs = useRef(0);
30
+ // Take ownership of info reset. The custom RenderPipeline.render() path
31
+ // we use in post-processing doesn't trigger three.js's automatic per-frame
32
+ // info reset, so calls/triangles accumulate across frames and the display
33
+ // shows lifetime totals. Disabling autoReset and explicitly resetting at
34
+ // each window gives true per-frame averages.
35
+ const gl = useThree((s) => s.gl);
36
+ useEffect(() => {
37
+ if (!gl?.info)
38
+ return;
39
+ const previousAutoReset = gl.info.autoReset;
40
+ gl.info.autoReset = false;
41
+ gl.info.reset();
42
+ return () => {
43
+ gl.info.autoReset = previousAutoReset;
44
+ };
45
+ }, [gl]);
46
+ useFrame(({ gl, scene, clock }) => {
13
47
  frameCount.current++;
14
48
  const now = clock.elapsedTime;
15
49
  const dt = now - elapsed.current;
@@ -17,10 +51,74 @@ export const PerfMonitor = () => {
17
51
  const fps = Math.round(frameCount.current / dt);
18
52
  const frameMs = lastMs.current;
19
53
  const info = gl.info;
20
- const drawCalls = info.render?.calls ?? 0;
21
- const triangles = info.render?.triangles ?? 0;
22
- const dirty = useScene.getState().dirtyNodes.size;
23
- setStats({ fps, frameMs, drawCalls, triangles, dirty });
54
+ // calls/triangles have been accumulating since the last reset (start of
55
+ // window). Divide by frameCount to get a per-frame average.
56
+ const totalCalls = info.render?.calls ?? 0;
57
+ const totalTriangles = info.render?.triangles ?? 0;
58
+ const drawCalls = Math.round(totalCalls / Math.max(1, frameCount.current));
59
+ const triangles = totalTriangles / Math.max(1, frameCount.current);
60
+ info.reset();
61
+ const sceneState = useScene.getState();
62
+ const dirty = sceneState.dirtyNodes.size;
63
+ let dirtyDetail = '';
64
+ if (dirty > 0) {
65
+ const counts = new Map();
66
+ for (const id of sceneState.dirtyNodes) {
67
+ const type = sceneState.nodes[id]?.type ?? 'missing';
68
+ counts.set(type, (counts.get(type) ?? 0) + 1);
69
+ }
70
+ dirtyDetail = [...counts.entries()]
71
+ .sort((a, b) => b[1] - a[1])
72
+ .map(([type, count]) => `${count} ${type}`)
73
+ .join(', ');
74
+ }
75
+ // Count visible drawables by type so we can match scene contents
76
+ // against the renderer's draw count and find hidden contributors.
77
+ let meshes = 0;
78
+ let lines = 0;
79
+ let sprites = 0;
80
+ let lights = 0;
81
+ scene.traverse((obj) => {
82
+ if (!obj.visible)
83
+ return;
84
+ if (obj.isMesh)
85
+ meshes++;
86
+ else if (obj.isLine || obj.isLineSegments || obj.isLineLoop)
87
+ lines++;
88
+ else if (obj.isSprite)
89
+ sprites++;
90
+ else if (obj.isLight)
91
+ lights++;
92
+ });
93
+ // GPU samples are pushed by post-processing.tsx after each pipeline
94
+ // render via device.queue.onSubmittedWorkDone(). We drain whatever
95
+ // has accumulated since the last tick.
96
+ const samples = drainGpuSamples();
97
+ if (samples.length > 0) {
98
+ let sum = 0;
99
+ let max = 0;
100
+ for (const s of samples) {
101
+ sum += s;
102
+ if (s > max)
103
+ max = s;
104
+ }
105
+ lastGpuMs.current = sum / samples.length;
106
+ lastGpuMaxMs.current = max;
107
+ }
108
+ setStats({
109
+ fps,
110
+ frameMs,
111
+ gpuMs: lastGpuMs.current,
112
+ gpuMaxMs: lastGpuMaxMs.current,
113
+ drawCalls,
114
+ triangles,
115
+ dirty,
116
+ dirtyDetail,
117
+ meshes,
118
+ lines,
119
+ sprites,
120
+ lights,
121
+ });
24
122
  frameCount.current = 0;
25
123
  elapsed.current = now;
26
124
  }
@@ -35,8 +133,13 @@ export const PerfMonitor = () => {
35
133
  borderRadius: 6,
36
134
  padding: '6px 10px',
37
135
  whiteSpace: 'pre',
38
- }, children: `FPS ${stats.fps}
39
- DRAW ${stats.drawCalls}
40
- TRI ${(stats.triangles / 1000).toFixed(1)}k
41
- DIRTY ${stats.dirty}` }) }));
136
+ }, children: `FPS ${stats.fps}
137
+ GPU ${stats.gpuMs > 0 ? `${stats.gpuMs.toFixed(1)}ms (max ${stats.gpuMaxMs.toFixed(1)})` : '—'}
138
+ DRAW ${stats.drawCalls}
139
+ TRI ${(stats.triangles / 1000).toFixed(1)}k
140
+ DIRTY ${stats.dirty}${stats.dirtyDetail ? ` (${stats.dirtyDetail})` : ''}
141
+ MESH ${stats.meshes}
142
+ LINE ${stats.lines}
143
+ SPRITE ${stats.sprites}
144
+ LIGHT ${stats.lights}` }) }));
42
145
  };
@@ -1,3 +1,7 @@
1
+ export declare const GRADE_PARAMS: {
2
+ contrast: number;
3
+ saturation: number;
4
+ };
1
5
  export declare const SSGI_PARAMS: {
2
6
  enabled: boolean;
3
7
  sliceCount: number;
@@ -22,8 +26,10 @@ export type HoverStyles = {
22
26
  default: HoverStyle;
23
27
  } & Record<string, HoverStyle>;
24
28
  export declare const DEFAULT_HOVER_STYLES: HoverStyles;
25
- declare const PostProcessingPasses: ({ hoverStyles, }: {
29
+ declare const PostProcessingPasses: ({ hoverStyles, disablePostFx, }: {
26
30
  hoverStyles?: HoverStyles;
31
+ /** Host-controlled equivalent of `?disable=postFx` — see the Viewer prop. */
32
+ disablePostFx?: boolean;
27
33
  }) => null;
28
34
  export default PostProcessingPasses;
29
35
  //# sourceMappingURL=post-processing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA4BA,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAQD,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,UAAU,CAAA;CACpB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAS9B,eAAO,MAAM,oBAAoB,EAAE,WAElC,CAAA;AAiBD,QAAA,MAAM,oBAAoB,GAAI,kBAE3B;IACD,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B,SAyTA,CAAA;AAED,eAAe,oBAAoB,CAAA"}
1
+ {"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AAwCA,eAAO,MAAM,YAAY;;;CAGxB,CAAA;AAGD,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AA0DD,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,UAAU,CAAA;CACpB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAS9B,eAAO,MAAM,oBAAoB,EAAE,WAElC,CAAA;AAiBD,QAAA,MAAM,oBAAoB,GAAI,iCAG3B;IACD,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,SA8mBA,CAAA;AAED,eAAe,oBAAoB,CAAA"}