@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
@@ -1,129 +1,52 @@
1
1
  'use client';
2
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
3
- import { isScriptedNode, pointInPolygon, sceneRegistry, scriptedOrigin, scriptedSize, scriptInteractive, useInteractive, useScene, } from '@pascal-app/core';
4
- import { evaluateRecipe, operableParts, } from '@pascal-app/core/procedural-items';
5
- import { Html } from '@react-three/drei';
6
- import { createPortal, useFrame } from '@react-three/fiber';
7
- import { useEffect, useMemo, useState } from 'react';
8
- import { Vector3 } from 'three';
9
- import { useShallow } from 'zustand/react/shallow';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { sceneRegistry, useInteractive, useScene, } from '@pascal-app/core';
4
+ import { useFrame } from '@react-three/fiber';
5
+ import { useCallback, useState } from 'react';
6
+ import { SelectionCallout } from '../../components/viewer/selection-callout';
10
7
  import useViewer from '../../store/use-viewer';
11
- import { ControlWidget } from './control-widget';
12
- import { proceduralControlDescriptors } from './procedural-controls';
13
- const _tempVec = new Vector3();
14
- // ---- Parent: overlays for selected interactive items and the selected zone ----
15
- //
16
- // Mounting <Html> overlays unconditionally is not an option: each drei <Html>
17
- // repositions and re-sorts its DOM element on every camera-move frame, and
18
- // with `occlude` it also raycasts the entire scene per overlay per frame. On
19
- // large scenes (hundreds of interactive items) that starves the frame budget
20
- // and the display/z-index churn makes the whole DOM UI flicker.
21
- //
8
+ import { calloutSubtitle, hasCallout, nodeCalloutActions } from './callout-actions';
9
+ function calloutTitle(node) {
10
+ if (node.name)
11
+ return node.name;
12
+ const asset = node.asset;
13
+ if (asset?.name)
14
+ return asset.name;
15
+ const word = node.type.replaceAll('-', ' ');
16
+ return word.charAt(0).toUpperCase() + word.slice(1);
17
+ }
18
+ /**
19
+ * The parametric viewer's callout on the one selected element: its name, what
20
+ * it is, and what it can do (open a door, switch a lamp, close the shutters).
21
+ */
22
22
  export const InteractiveSystem = () => {
23
- const zoneId = useViewer((s) => s.selection.zoneId);
24
23
  const selectedIds = useViewer((s) => s.selection.selectedIds);
25
- const zonePolygon = useScene((s) => {
26
- if (!zoneId)
27
- return null;
28
- const z = s.nodes[zoneId];
29
- return z?.polygon ?? null;
30
- });
31
- const interactiveNodeIds = useScene(useShallow((state) => Object.values(state.nodes)
32
- .filter((n) => (isScriptedNode(n) && scriptInteractive(n.source.manifest) != null) ||
33
- (n.type === 'item' && n.asset.interactive != null) ||
34
- (n.type === 'procedural-item' &&
35
- (operableParts(n.recipe).length > 0 || n.recipe.parts.some((part) => part.light))))
36
- .map((n) => n.id)));
37
- return (_jsx(_Fragment, { children: interactiveNodeIds
38
- .filter((id) => zonePolygon?.length || selectedIds.includes(id))
39
- .map((id) => (_jsx(ItemControlsOverlay, { isSelected: selectedIds.includes(id), nodeId: id, zonePolygon: zonePolygon }, id))) }));
24
+ const walkthrough = useViewer((s) => s.walkthroughMode);
25
+ const node = useScene((s) => selectedIds.length === 1
26
+ ? s.nodes[selectedIds[0]]
27
+ : undefined);
28
+ if (walkthrough || !node || !hasCallout(node.type))
29
+ return null;
30
+ return _jsx(NodeCallout, { node: node }, node.id);
40
31
  };
41
- // ---- Child: polls sceneRegistry then portals controls into the item group ----
42
- const FADE_MS = 300;
43
- const ItemControlsOverlay = ({ nodeId, zonePolygon, isSelected, }) => {
44
- const node = useScene((state) => state.nodes[nodeId]);
45
- const [itemObj, setItemObj] = useState(null);
32
+ function NodeCallout({ node }) {
33
+ const [object, setObject] = useState(null);
34
+ // The renderer registers the object a frame or more after selection lands,
35
+ // and may swap it for a new one when the element rebuilds (a sash opening).
46
36
  useFrame(() => {
47
- if (itemObj)
48
- return;
49
- const obj = sceneRegistry.nodes.get(nodeId);
50
- if (obj)
51
- setItemObj(obj);
37
+ const registered = sceneRegistry.nodes.get(node.id) ?? null;
38
+ if (registered !== object)
39
+ setObject(registered);
52
40
  });
53
- const controlValues = useInteractive(useShallow((state) => state.items[nodeId]?.controlValues));
54
- const proceduralState = useInteractive((state) => state.procedural[nodeId]);
55
- const lampDefault = useInteractive((state) => state.lampDefault);
56
- const setControlValue = useInteractive((state) => state.setControlValue);
57
- const togglePart = useInteractive((state) => state.toggleProceduralPart);
58
- const toggleLights = useInteractive((state) => state.toggleProceduralLights);
59
- const proceduralHeight = useMemo(() => node?.type === 'procedural-item' ? evaluateRecipe(node.recipe, node.parameters).max[1] : 0, [
60
- node?.type === 'procedural-item' ? node.recipe : null,
61
- node?.type === 'procedural-item' ? node.parameters : null,
62
- ]);
63
- let descriptors = [];
64
- let height = 0;
65
- const interactive = isScriptedNode(node)
66
- ? scriptInteractive(node.source.manifest, scriptedOrigin(node))
67
- : node?.type === 'item'
68
- ? node.asset.interactive
69
- : undefined;
70
- if (interactive && controlValues) {
71
- descriptors = interactive.controls.map((control, i) => ({
72
- key: String(i),
73
- control,
74
- value: controlValues[i] ?? false,
75
- onChange: (value) => setControlValue(nodeId, i, value),
76
- }));
77
- height = isScriptedNode(node)
78
- ? scriptedSize(node.source.manifest)[1] + scriptedOrigin(node)[1]
79
- : node?.type === 'item'
80
- ? node.asset.dimensions[1]
81
- : 0;
82
- }
83
- else if (node?.type === 'procedural-item') {
84
- descriptors = proceduralControlDescriptors(node.recipe, proceduralState, (partId) => togglePart(nodeId, partId), () => toggleLights(nodeId), lampDefault);
85
- height = proceduralHeight;
86
- }
87
- let visible = isSelected;
88
- if (itemObj && zonePolygon?.length) {
89
- itemObj.getWorldPosition(_tempVec);
90
- visible = visible || pointInPolygon(_tempVec.x, _tempVec.z, zonePolygon);
91
- }
92
- // Fade in on mount and fade out before unmounting the <Html>.
93
- const [mounted, setMounted] = useState(false);
94
- const [shown, setShown] = useState(false);
95
- useEffect(() => {
96
- if (visible) {
97
- setMounted(true);
98
- // Double rAF: the overlay has to paint once at opacity 0 before the
99
- // opacity-1 style lands, otherwise the fade-in transition is skipped.
100
- let raf2 = 0;
101
- const raf1 = requestAnimationFrame(() => {
102
- raf2 = requestAnimationFrame(() => setShown(true));
103
- });
104
- return () => {
105
- cancelAnimationFrame(raf1);
106
- cancelAnimationFrame(raf2);
107
- };
108
- }
109
- setShown(false);
110
- const timeout = setTimeout(() => setMounted(false), FADE_MS);
111
- return () => clearTimeout(timeout);
112
- }, [visible]);
113
- if (!(mounted && itemObj && descriptors.length))
41
+ const title = calloutTitle(node);
42
+ const interactive = useInteractive();
43
+ const actions = nodeCalloutActions(node, title, interactive);
44
+ const dismiss = useCallback(() => {
45
+ const { selection, setSelection } = useViewer.getState();
46
+ if (selection.selectedIds.includes(node.id))
47
+ setSelection({ selectedIds: [] });
48
+ }, [node.id]);
49
+ if (!object)
114
50
  return null;
115
- return createPortal(_jsx(Html, { center: true, distanceFactor: 8, eps: -1, position: [0, height + 0.3, 0], zIndexRange: [20, 0], children: _jsx("div", { style: {
116
- display: 'flex',
117
- flexDirection: 'column',
118
- gap: 6,
119
- background: 'rgba(0,0,0,0.75)',
120
- backdropFilter: 'blur(8px)',
121
- borderRadius: 8,
122
- padding: '8px 12px',
123
- minWidth: 120,
124
- pointerEvents: visible ? 'auto' : 'none',
125
- userSelect: 'none',
126
- opacity: shown ? 1 : 0,
127
- transition: `opacity ${FADE_MS}ms ease`,
128
- }, children: descriptors.map((descriptor) => (_jsx(ControlWidget, { control: descriptor.control, onChange: descriptor.onChange, value: descriptor.value }, descriptor.key))) }) }), itemObj);
129
- };
51
+ return (_jsx(SelectionCallout, { actions: actions, object: object, onDismiss: dismiss, subtitle: calloutSubtitle(node, title), title: title }));
52
+ }
@@ -0,0 +1,16 @@
1
+ import { type AnyNodeId } from '@pascal-app/core';
2
+ /**
3
+ * Resolves when a built-in door's leaf or a window's sash (or, with `shutters`,
4
+ * its shutters) finishes the run just started, so the latch can sound on the
5
+ * frame the leaf lands.
6
+ */
7
+ export declare function openingSettled(node: {
8
+ id: string;
9
+ type: string;
10
+ }, part?: 'leaf' | 'shutters'): Promise<void>;
11
+ /**
12
+ * Resolves when a procedural item's latest commanded run (a cabinet door, a
13
+ * drawer, a lid) has its moving parts at rest. Call it right after the toggle.
14
+ */
15
+ export declare function proceduralSettled(nodeId: AnyNodeId): Promise<void>;
16
+ //# sourceMappingURL=opening-settled.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opening-settled.d.ts","sourceRoot":"","sources":["../../../src/systems/interactive/opening-settled.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAA2B,MAAM,kBAAkB,CAAA;AAK1E;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAClC,IAAI,GAAE,MAAM,GAAG,UAAmB,GACjC,OAAO,CAAC,IAAI,CAAC,CAmBf;AAKD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAclE"}
@@ -0,0 +1,53 @@
1
+ import { emitter, useInteractive } from '@pascal-app/core';
2
+ /** Longer than any built-in leaf, sash or shutter run, so a missed event never strands a sound. */
3
+ const FALLBACK_MS = 2000;
4
+ /**
5
+ * Resolves when a built-in door's leaf or a window's sash (or, with `shutters`,
6
+ * its shutters) finishes the run just started, so the latch can sound on the
7
+ * frame the leaf lands.
8
+ */
9
+ export function openingSettled(node, part = 'leaf') {
10
+ return new Promise((resolve) => {
11
+ const done = () => {
12
+ clearTimeout(timer);
13
+ emitter.off('door:animation-completed', onDoor);
14
+ emitter.off('window:animation-completed', onWindow);
15
+ resolve();
16
+ };
17
+ const onDoor = (event) => {
18
+ if (node.type === 'door' && event.doorId === node.id)
19
+ done();
20
+ };
21
+ const onWindow = (event) => {
22
+ if (node.type !== 'window' || event.windowId !== node.id)
23
+ return;
24
+ if ((event.field === 'operationState') === (part === 'leaf'))
25
+ done();
26
+ };
27
+ const timer = setTimeout(done, FALLBACK_MS);
28
+ emitter.on('door:animation-completed', onDoor);
29
+ emitter.on('window:animation-completed', onWindow);
30
+ });
31
+ }
32
+ /** Recipe timelines run longer than a door leaf; past this, a lost event still frees the sound. */
33
+ const MOTION_FALLBACK_MS = 6000;
34
+ /**
35
+ * Resolves when a procedural item's latest commanded run (a cabinet door, a
36
+ * drawer, a lid) has its moving parts at rest. Call it right after the toggle.
37
+ */
38
+ export function proceduralSettled(nodeId) {
39
+ const sequence = useInteractive.getState().procedural[nodeId]?.motionCommand?.sequence ?? 0;
40
+ return new Promise((resolve) => {
41
+ const done = () => {
42
+ clearTimeout(timer);
43
+ emitter.off('procedural:motion-settled', onSettled);
44
+ resolve();
45
+ };
46
+ const onSettled = (event) => {
47
+ if (event.nodeId === nodeId && event.sequence >= sequence)
48
+ done();
49
+ };
50
+ const timer = setTimeout(done, MOTION_FALLBACK_MS);
51
+ emitter.on('procedural:motion-settled', onSettled);
52
+ });
53
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"item-light-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item-light/item-light-system.tsx"],"names":[],"mappings":"AAgGA,wBAAgB,eAAe,uCA0M9B"}
1
+ {"version":3,"file":"item-light-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item-light/item-light-system.tsx"],"names":[],"mappings":"AA+CA,wBAAgB,eAAe,uCAoB9B"}
@@ -1,25 +1,10 @@
1
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { findLevelAncestorId, sceneRegistry, useScene } from '@pascal-app/core';
3
- import { useFrame, useThree } from '@react-three/fiber';
4
- import { useRef } from 'react';
5
- import { MathUtils, Vector3 } from 'three';
3
+ import { useThree } from '@react-three/fiber';
6
4
  import { SCENE_LAYER } from '../../lib/layers';
7
5
  import { useItemLightPool } from '../../store/use-item-light-pool';
8
6
  import useViewer from '../../store/use-viewer';
9
- const POOL_SIZE = 12;
10
- // How often (in seconds) to re-evaluate which items have lights assigned (fallback timer)
11
- const REASSIGN_INTERVAL = 0.2;
12
- // Hysteresis: a currently-assigned slot keeps its key unless an unassigned
13
- // candidate beats it by at least this much (prevents flickering at the boundary)
14
- const HYSTERESIS = 0.15;
15
- // Camera movement thresholds that trigger an early re-evaluation
16
- const CAM_MOVE_DIST = 0.5; // units
17
- const CAM_ROT_DOT = 0.995; // cos(~5.7°)
18
- // Module-level temp vectors reused every frame (avoids GC pressure)
19
- const _dir = new Vector3();
20
- const _camPos = new Vector3();
21
- const _camFwd = new Vector3();
22
- const _itemPos = new Vector3();
7
+ import { PooledLights } from './pooled-lights';
23
8
  function isRendered(nodeId) {
24
9
  let object = sceneRegistry.nodes.get(nodeId);
25
10
  if (!object?.layers.isEnabled(SCENE_LAYER))
@@ -31,213 +16,36 @@ function isRendered(nodeId) {
31
16
  }
32
17
  return true;
33
18
  }
34
- function scoreRegistration(reg, nodes, selectedLevelId, levelMode, hideAbove) {
35
- if (!reg.isEligible() || !isRendered(reg.nodeId) || !reg.getWorldPosition(_itemPos))
36
- return Number.POSITIVE_INFINITY;
19
+ // A light on another storey than the one being edited counts for this much of what it adds.
20
+ const OTHER_LEVEL_WEIGHT = 0.25;
21
+ /** 0 while the light cannot be seen lit (off, hidden, on a hidden level), else its level factor. */
22
+ function registrationWeight(reg, nodes, selectedLevelId, levelMode, hideAbove) {
23
+ if (!reg.isEligible() || !isRendered(reg.nodeId))
24
+ return 0;
37
25
  const { nodeId } = reg;
38
26
  let current = nodes[nodeId];
39
27
  while (current) {
40
28
  if (current.type !== 'site' && current.visible === false)
41
- return Number.POSITIVE_INFINITY;
29
+ return 0;
42
30
  current = current.parentId ? nodes[current.parentId] : undefined;
43
31
  }
44
32
  const itemLevelId = findLevelAncestorId(nodeId, nodes);
45
- if (selectedLevelId && itemLevelId !== selectedLevelId && levelMode === 'solo')
46
- return Number.POSITIVE_INFINITY;
47
- _dir.copy(_itemPos).sub(_camPos).normalize();
48
- const dot = _camFwd.dot(_dir); // 1 = ahead, -1 = behind
49
- // Angular component (0 = dead ahead, 2 = directly behind)
50
- const angular = 1 - dot;
51
- // Normalised distance component (assumes scenes < 200 units)
52
- const dist = _camPos.distanceTo(_itemPos) / 200;
53
- // ── Level factor ──────────────────────────────────────────────────────────
54
- let levelPenalty = 0;
55
- if (selectedLevelId) {
56
- if (itemLevelId !== selectedLevelId) {
57
- // Items on hidden levels (every other one in solo, those above the
58
- // selected one while editing) are invisible — deprioritize strongly
59
- const itemIndex = nodes[itemLevelId]?.level ?? 0;
60
- const selectedIndex = nodes[selectedLevelId]?.level ?? 0;
61
- const hidden = levelMode === 'solo' || (hideAbove && itemIndex > selectedIndex);
62
- levelPenalty = hidden ? 100 : 0.8;
63
- }
64
- }
65
- else if (itemLevelId) {
66
- // No level selected — lightly prefer items on level index 0
67
- const levelNode = nodes[itemLevelId];
68
- const levelIndex = levelNode?.level ?? 0;
69
- if (levelIndex !== 0)
70
- levelPenalty = 0.3;
71
- }
72
- return angular * 0.7 + dist * 0.3 + levelPenalty;
33
+ if (!selectedLevelId || itemLevelId === selectedLevelId)
34
+ return 1;
35
+ if (levelMode === 'solo')
36
+ return 0;
37
+ // Above the selected level while editing, a storey is hidden.
38
+ const itemIndex = nodes[itemLevelId]?.level ?? 0;
39
+ const selectedIndex = nodes[selectedLevelId]?.level ?? 0;
40
+ return hideAbove && itemIndex > selectedIndex ? 0 : OTHER_LEVEL_WEIGHT;
73
41
  }
74
42
  export function ItemLightSystem() {
75
43
  const scene = useThree((state) => state.scene);
76
44
  const bakedOwner = useItemLightPool((state) => state.bakedCanvases.has(scene));
77
- const lightRefs = useRef(Array.from({ length: POOL_SIZE }, () => null));
78
- const slots = useRef(Array.from({ length: POOL_SIZE }, () => ({ key: null, pendingKey: null, isFadingOut: false })));
79
- const reassignTimer = useRef(0);
80
- // Track camera state at last reassignment to detect meaningful movement
81
- const prevReassignCamPos = useRef(new Vector3());
82
- const prevReassignCamFwd = useRef(new Vector3(0, 0, -1));
83
- useFrame(({ camera }, delta) => {
84
- if (bakedOwner)
85
- return;
86
- const dt = Math.min(delta, 0.1);
87
- const { registrations } = useItemLightPool.getState();
88
- // ── 1. Throttled priority reassignment ──────────────────────────────────
89
- camera.getWorldPosition(_camPos);
90
- camera.getWorldDirection(_camFwd);
91
- const camMoved = _camPos.distanceTo(prevReassignCamPos.current) > CAM_MOVE_DIST ||
92
- _camFwd.dot(prevReassignCamFwd.current) < CAM_ROT_DOT;
93
- reassignTimer.current -= delta;
94
- const shouldReassign = reassignTimer.current <= 0 || camMoved;
95
- if (shouldReassign) {
96
- reassignTimer.current = REASSIGN_INTERVAL;
97
- prevReassignCamPos.current.copy(_camPos);
98
- prevReassignCamFwd.current.copy(_camFwd);
99
- // Read level/scene state once for the whole tick
100
- const nodes = useScene.getState().nodes;
101
- const viewerState = useViewer.getState();
102
- const selectedLevelId = viewerState.selection.levelId;
103
- const levelMode = viewerState.levelMode;
104
- const hideAbove = viewerState.hideLevelsAboveSelection;
105
- // Score every registration
106
- const scored = [];
107
- for (const [key, reg] of registrations) {
108
- scored.push({
109
- key,
110
- score: scoreRegistration(reg, nodes, selectedLevelId, levelMode, hideAbove),
111
- });
112
- }
113
- scored.sort((a, b) => a.score - b.score);
114
- // Build the desired assignment (top POOL_SIZE keys)
115
- const desired = scored
116
- .filter((s) => Number.isFinite(s.score))
117
- .slice(0, POOL_SIZE)
118
- .map((s) => s.key);
119
- // Build a map of currently-assigned keys → slot index for hysteresis
120
- const currentlyAssigned = new Map();
121
- for (let i = 0; i < POOL_SIZE; i++) {
122
- const s = slots.current[i];
123
- if (!s)
124
- continue;
125
- const k = s.key ?? s.pendingKey;
126
- if (k)
127
- currentlyAssigned.set(k, i);
128
- }
129
- // Assign desired keys to slots — prefer keeping existing assignments
130
- const usedSlots = new Set();
131
- const assignedKeys = new Set();
132
- // Pass 1: keep existing slots where the key is still in desired
133
- for (const key of desired) {
134
- const existingSlot = currentlyAssigned.get(key);
135
- if (existingSlot !== undefined && !usedSlots.has(existingSlot)) {
136
- usedSlots.add(existingSlot);
137
- assignedKeys.add(key);
138
- }
139
- }
140
- // Pass 2: assign remaining desired keys to free slots
141
- let freeSlot = 0;
142
- for (const key of desired) {
143
- if (assignedKeys.has(key))
144
- continue;
145
- while (freeSlot < POOL_SIZE && usedSlots.has(freeSlot))
146
- freeSlot++;
147
- if (freeSlot >= POOL_SIZE)
148
- break;
149
- // Hysteresis: only evict the current occupant if the new key scores
150
- // meaningfully better than it
151
- const freeSlotData = slots.current[freeSlot];
152
- const currentKey = freeSlotData ? (freeSlotData.key ?? freeSlotData.pendingKey) : null;
153
- if (currentKey && !desired.includes(currentKey)) {
154
- const currentScore = scored.find((s) => s.key === currentKey)?.score ?? Number.POSITIVE_INFINITY;
155
- const newScore = scored.find((s) => s.key === key)?.score ?? 0;
156
- if (currentScore - newScore < HYSTERESIS) {
157
- freeSlot++;
158
- continue;
159
- }
160
- }
161
- usedSlots.add(freeSlot);
162
- assignedKeys.add(key);
163
- const slot = slots.current[freeSlot];
164
- if (slot && slot.key !== key) {
165
- slot.pendingKey = key;
166
- slot.isFadingOut = slot.key !== null;
167
- if (!slot.isFadingOut) {
168
- // Slot was idle — skip fade-out, assign immediately
169
- slot.key = key;
170
- slot.pendingKey = null;
171
- const light = lightRefs.current[freeSlot];
172
- const reg = registrations.get(key);
173
- if (light && reg) {
174
- light.color.set(reg.color);
175
- light.distance = reg.distance;
176
- }
177
- }
178
- }
179
- freeSlot++;
180
- }
181
- // Clear slots whose key is no longer in desired and not pending
182
- for (let i = 0; i < POOL_SIZE; i++) {
183
- if (!usedSlots.has(i)) {
184
- const slot = slots.current[i];
185
- if (slot?.key && !desired.includes(slot.key)) {
186
- slot.pendingKey = null;
187
- slot.isFadingOut = true;
188
- }
189
- }
190
- }
191
- }
192
- // ── 2. Per-frame light updates ───────────────────────────────────────────
193
- for (let i = 0; i < POOL_SIZE; i++) {
194
- const light = lightRefs.current[i];
195
- if (!light)
196
- continue;
197
- const slot = slots.current[i];
198
- if (!slot)
199
- continue;
200
- // Fade-out phase: lerp intensity → 0, then complete the transition
201
- if (slot.isFadingOut) {
202
- light.intensity = MathUtils.lerp(light.intensity, 0, dt * 12);
203
- if (light.intensity < 0.01) {
204
- light.intensity = 0;
205
- slot.isFadingOut = false;
206
- slot.key = slot.pendingKey;
207
- slot.pendingKey = null;
208
- if (slot.key) {
209
- const reg = registrations.get(slot.key);
210
- if (reg) {
211
- light.color.set(reg.color);
212
- light.distance = reg.distance;
213
- }
214
- }
215
- }
216
- continue;
217
- }
218
- if (!slot.key) {
219
- // Idle slot — keep dark
220
- light.intensity = 0;
221
- continue;
222
- }
223
- const reg = registrations.get(slot.key);
224
- if (!reg) {
225
- slot.key = null;
226
- light.intensity = 0;
227
- continue;
228
- }
229
- if (reg.getWorldPosition(_itemPos))
230
- light.position.copy(_itemPos);
231
- const targetIntensity = reg.isEligible() && isRendered(reg.nodeId) ? reg.getIntensity() : 0;
232
- light.intensity = MathUtils.lerp(light.intensity, targetIntensity, dt * 12);
233
- if (targetIntensity <= 0 && light.intensity < 0.01) {
234
- light.intensity = 0;
235
- }
236
- }
237
- }, 6);
238
45
  if (bakedOwner)
239
46
  return null;
240
- return (_jsx(_Fragment, { children: Array.from({ length: POOL_SIZE }, (_, i) => (_jsx("pointLight", { castShadow: false, intensity: 0, visible: true, ref: (el) => {
241
- lightRefs.current[i] = el;
242
- } }, i))) }));
47
+ return (_jsx(PooledLights, { getLights: () => useItemLightPool.getState().registrations.values(), intensity: (reg) => (reg.isEligible() && isRendered(reg.nodeId) ? reg.getIntensity() : 0), weight: (reg) => {
48
+ const viewer = useViewer.getState();
49
+ return registrationWeight(reg, useScene.getState().nodes, viewer.selection.levelId, viewer.levelMode, viewer.hideLevelsAboveSelection);
50
+ } }));
243
51
  }
@@ -0,0 +1,70 @@
1
+ import type { LightSpot } from '@pascal-app/core';
2
+ import { type Camera, Frustum, type Object3D, Vector3 } from 'three/webgpu';
3
+ /** A light an item holds, as the pools drive it. */
4
+ export type PooledLight = {
5
+ key: string;
6
+ color: string;
7
+ distance: number;
8
+ spot?: LightSpot;
9
+ shadows?: boolean;
10
+ glow?: {
11
+ size: number;
12
+ strength?: number;
13
+ };
14
+ /** Its fully-on intensity, so its glow dims with it. */
15
+ peak?: number;
16
+ /** Where it shines from; false while its object is not mounted. */
17
+ getWorldPosition: (out: Vector3) => boolean;
18
+ /** Its spot's aim in world space, turned with its item. */
19
+ getWorldDirection?: (out: Vector3) => void;
20
+ };
21
+ type Props<T extends PooledLight> = {
22
+ getLights: () => Iterable<T>;
23
+ /**
24
+ * 0 while it cannot be seen lit (off, hidden, on a hidden level); otherwise a factor on what it
25
+ * adds to the view (a light on another storey than the one being edited counts for less).
26
+ */
27
+ weight: (light: T) => number;
28
+ /** What it shines at now: 0 while off or not drawn. */
29
+ intensity: (light: T) => number;
30
+ };
31
+ type Slot = {
32
+ key: string | null;
33
+ pendingKey: string | null;
34
+ isFadingOut: boolean;
35
+ };
36
+ type Candidate = {
37
+ key: string;
38
+ priority: number;
39
+ };
40
+ /**
41
+ * Roughly what a light adds to a view: its intensity over its squared distance from the eye, or 0
42
+ * when its reach (its distance, else where it falls under REACH_CUTOFF) misses the view.
43
+ */
44
+ export declare function lightContribution(position: Vector3, intensity: number, distance: number, eye: Vector3, frustum: Frustum): number;
45
+ /**
46
+ * Gives the wanted keys (most first) the pool's slots: a key that holds a slot keeps it, and a
47
+ * newcomer evicts a key no longer wanted only on a clear win. A slot changing hands fades out
48
+ * first; an idle slot takes its key at once.
49
+ */
50
+ export declare function assignSlots(slots: Slot[], wanted: Candidate[], priorityByKey: Map<string, number>): void;
51
+ /** How much a glow shows: 1 at night, nearly nothing in daylight. */
52
+ export declare function glowDarkness(sunDirection: Vector3 | null, appearance: 'light' | 'dark'): number;
53
+ /**
54
+ * Sets the item lights of `scene` for a picture taken through `camera`: the pools re-ranked for
55
+ * what that camera sees and every light straight at its strength, no fade. A capture camera is not
56
+ * the live view's, and a theme switched for the picture leaves no time for lamps to fade in.
57
+ * Returns what puts the live view's lights back.
58
+ */
59
+ export declare function itemLightsForCapture(scene: Object3D, camera: Camera): () => void;
60
+ /**
61
+ * The lights items hold, through fixed pools of point lights, spots and two shadow-casting slots,
62
+ * handed to the lights that add most to the view (intensity over squared distance, among those
63
+ * whose reach meets the view frustum), with hysteresis, and faded across reassignment. Every pool
64
+ * light stays mounted and visible and only its intensity moves: changing the light count
65
+ * recompiles every material. The spot and shadow pools mount the first time a light needs them,
66
+ * and stay. Glows are one instanced sprite for every glowing lamp, pooled or not.
67
+ */
68
+ export declare function PooledLights<T extends PooledLight>(props: Props<T>): import("react").JSX.Element;
69
+ export {};
70
+ //# sourceMappingURL=pooled-lights.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pooled-lights.d.ts","sourceRoot":"","sources":["../../../src/systems/item-light/pooled-lights.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAYjD,OAAO,EAEL,KAAK,MAAM,EAGX,OAAO,EAIP,KAAK,QAAQ,EAMb,OAAO,EACR,MAAM,cAAc,CAAA;AAMrB,oDAAoD;AACpD,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,mEAAmE;IACnE,gBAAgB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAA;IAC3C,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CAC3C,CAAA;AAED,KAAK,KAAK,CAAC,CAAC,SAAS,WAAW,IAAI;IAClC,SAAS,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC5B;;;OAGG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAA;IAC5B,uDAAuD;IACvD,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAA;CAChC,CAAA;AAmCD,KAAK,IAAI,GAAG;IAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAA;AACnF,KAAK,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAoBlD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,OAAO,GACf,MAAM,CAKR;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,IAAI,EAAE,EACb,MAAM,EAAE,SAAS,EAAE,EACnB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,QAiDnC;AAWD,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,YAAY,EAAE,OAAO,GAAG,IAAI,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAI/F;AAuGD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,CAKhF;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,+BAKlE"}