@pascal-app/viewer 1.0.5 → 1.0.7

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 (199) hide show
  1. package/dist/components/viewer/daylight-atmosphere.d.ts +9 -0
  2. package/dist/components/viewer/daylight-atmosphere.d.ts.map +1 -0
  3. package/dist/components/viewer/daylight-atmosphere.js +315 -0
  4. package/dist/components/viewer/drone-controller.d.ts +15 -0
  5. package/dist/components/viewer/drone-controller.d.ts.map +1 -0
  6. package/dist/components/viewer/drone-controller.js +194 -0
  7. package/dist/components/viewer/glb-interactive.d.ts +4 -18
  8. package/dist/components/viewer/glb-interactive.d.ts.map +1 -1
  9. package/dist/components/viewer/glb-interactive.js +32 -268
  10. package/dist/components/viewer/glb-scene-motion.d.ts +8 -0
  11. package/dist/components/viewer/glb-scene-motion.d.ts.map +1 -0
  12. package/dist/components/viewer/glb-scene-motion.js +57 -0
  13. package/dist/components/viewer/glb-scene.d.ts +3 -1
  14. package/dist/components/viewer/glb-scene.d.ts.map +1 -1
  15. package/dist/components/viewer/glb-scene.js +289 -49
  16. package/dist/components/viewer/glb-selection-callout.d.ts +17 -0
  17. package/dist/components/viewer/glb-selection-callout.d.ts.map +1 -0
  18. package/dist/components/viewer/glb-selection-callout.js +54 -0
  19. package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
  20. package/dist/components/viewer/glb-walkthrough-controller.js +6 -3
  21. package/dist/components/viewer/index.d.ts +5 -0
  22. package/dist/components/viewer/index.d.ts.map +1 -1
  23. package/dist/components/viewer/index.js +30 -6
  24. package/dist/components/viewer/lights.d.ts.map +1 -1
  25. package/dist/components/viewer/lights.js +192 -5
  26. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  27. package/dist/components/viewer/post-processing.js +150 -94
  28. package/dist/components/viewer/scene-environment.js +4 -5
  29. package/dist/components/viewer/selection-callout.d.ts +51 -0
  30. package/dist/components/viewer/selection-callout.d.ts.map +1 -0
  31. package/dist/components/viewer/selection-callout.js +197 -0
  32. package/dist/components/viewer/use-daylight-rooms.d.ts +13 -0
  33. package/dist/components/viewer/use-daylight-rooms.d.ts.map +1 -0
  34. package/dist/components/viewer/use-daylight-rooms.js +236 -0
  35. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  36. package/dist/components/viewer/viewer-camera.js +12 -1
  37. package/dist/index.d.ts +21 -7
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +23 -7
  40. package/dist/lib/daylight/ground.d.ts +21 -0
  41. package/dist/lib/daylight/ground.d.ts.map +1 -0
  42. package/dist/lib/daylight/ground.js +53 -0
  43. package/dist/lib/daylight/location.d.ts +32 -0
  44. package/dist/lib/daylight/location.d.ts.map +1 -0
  45. package/dist/lib/daylight/location.js +74 -0
  46. package/dist/lib/daylight/look.d.ts +33 -0
  47. package/dist/lib/daylight/look.d.ts.map +1 -0
  48. package/dist/lib/daylight/look.js +13 -0
  49. package/dist/lib/daylight/material-hooks.d.ts +2 -0
  50. package/dist/lib/daylight/material-hooks.d.ts.map +1 -0
  51. package/dist/lib/daylight/material-hooks.js +150 -0
  52. package/dist/lib/daylight/room-context.d.ts +12 -0
  53. package/dist/lib/daylight/room-context.d.ts.map +1 -0
  54. package/dist/lib/daylight/room-context.js +53 -0
  55. package/dist/lib/daylight/room-scene.d.ts +54 -0
  56. package/dist/lib/daylight/room-scene.d.ts.map +1 -0
  57. package/dist/lib/daylight/room-scene.js +356 -0
  58. package/dist/lib/daylight/rooms.d.ts +262 -0
  59. package/dist/lib/daylight/rooms.d.ts.map +1 -0
  60. package/dist/lib/daylight/rooms.js +1223 -0
  61. package/dist/lib/daylight/runtime.d.ts +33 -0
  62. package/dist/lib/daylight/runtime.d.ts.map +1 -0
  63. package/dist/lib/daylight/runtime.js +42 -0
  64. package/dist/lib/daylight/sky.d.ts +26 -0
  65. package/dist/lib/daylight/sky.d.ts.map +1 -0
  66. package/dist/lib/daylight/sky.js +217 -0
  67. package/dist/lib/daylight/solar.d.ts +46 -0
  68. package/dist/lib/daylight/solar.d.ts.map +1 -0
  69. package/dist/lib/daylight/solar.js +156 -0
  70. package/dist/lib/device-tier.d.ts +29 -0
  71. package/dist/lib/device-tier.d.ts.map +1 -0
  72. package/dist/lib/device-tier.js +71 -0
  73. package/dist/lib/dispose-object3d.d.ts.map +1 -1
  74. package/dist/lib/dispose-object3d.js +4 -2
  75. package/dist/lib/drone-flight.d.ts +44 -0
  76. package/dist/lib/drone-flight.d.ts.map +1 -0
  77. package/dist/lib/drone-flight.js +121 -0
  78. package/dist/lib/editor-highlight.d.ts +16 -0
  79. package/dist/lib/editor-highlight.d.ts.map +1 -0
  80. package/dist/lib/editor-highlight.js +33 -0
  81. package/dist/lib/foundation-materials.d.ts.map +1 -1
  82. package/dist/lib/foundation-materials.js +2 -4
  83. package/dist/lib/glb-merge.d.ts +23 -0
  84. package/dist/lib/glb-merge.d.ts.map +1 -0
  85. package/dist/lib/glb-merge.js +184 -0
  86. package/dist/lib/hardware-geometry.d.ts +21 -0
  87. package/dist/lib/hardware-geometry.d.ts.map +1 -0
  88. package/dist/lib/hardware-geometry.js +283 -0
  89. package/dist/lib/layer-pass.d.ts +2 -0
  90. package/dist/lib/layer-pass.d.ts.map +1 -1
  91. package/dist/lib/layer-pass.js +20 -0
  92. package/dist/lib/materials.d.ts.map +1 -1
  93. package/dist/lib/materials.js +50 -23
  94. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  95. package/dist/lib/merged-outline-node.js +7 -1
  96. package/dist/lib/occlusion-culler.d.ts +34 -0
  97. package/dist/lib/occlusion-culler.d.ts.map +1 -0
  98. package/dist/lib/occlusion-culler.js +698 -0
  99. package/dist/lib/post-processing-resources.d.ts +4 -0
  100. package/dist/lib/post-processing-resources.d.ts.map +1 -1
  101. package/dist/lib/post-processing-resources.js +5 -0
  102. package/dist/lib/readback-rows.d.ts +14 -0
  103. package/dist/lib/readback-rows.d.ts.map +1 -0
  104. package/dist/lib/readback-rows.js +31 -0
  105. package/dist/lib/reveal-pose.d.ts.map +1 -1
  106. package/dist/lib/reveal-pose.js +3 -0
  107. package/dist/lib/scene-matrices.d.ts +12 -0
  108. package/dist/lib/scene-matrices.d.ts.map +1 -0
  109. package/dist/lib/scene-matrices.js +287 -0
  110. package/dist/lib/scene-pass-mrt.d.ts +5 -2
  111. package/dist/lib/scene-pass-mrt.d.ts.map +1 -1
  112. package/dist/lib/scene-pass-mrt.js +7 -2
  113. package/dist/lib/scene-themes.d.ts +0 -2
  114. package/dist/lib/scene-themes.d.ts.map +1 -1
  115. package/dist/lib/scene-themes.js +0 -2
  116. package/dist/lib/scene-visibility.d.ts +2 -0
  117. package/dist/lib/scene-visibility.d.ts.map +1 -1
  118. package/dist/lib/scene-visibility.js +12 -3
  119. package/dist/lib/site-surfaces.d.ts +1 -8
  120. package/dist/lib/site-surfaces.d.ts.map +1 -1
  121. package/dist/lib/site-surfaces.js +0 -19
  122. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  123. package/dist/lib/snapshot-pipeline.js +24 -40
  124. package/dist/lib/texture-reference.d.ts.map +1 -1
  125. package/dist/lib/texture-reference.js +10 -8
  126. package/dist/lib/underground-ambiance.d.ts +0 -2
  127. package/dist/lib/underground-ambiance.d.ts.map +1 -1
  128. package/dist/lib/underground-ambiance.js +0 -2
  129. package/dist/store/use-item-light-pool.d.ts +3 -6
  130. package/dist/store/use-item-light-pool.d.ts.map +1 -1
  131. package/dist/store/use-item-light-pool.js +12 -0
  132. package/dist/store/use-viewer.d.ts +72 -15
  133. package/dist/store/use-viewer.d.ts.map +1 -1
  134. package/dist/store/use-viewer.js +103 -18
  135. package/dist/systems/ceiling/ceiling-system.d.ts +5 -0
  136. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  137. package/dist/systems/ceiling/ceiling-system.js +11 -1
  138. package/dist/systems/door/door-system.d.ts.map +1 -1
  139. package/dist/systems/door/door-system.js +501 -145
  140. package/dist/systems/door/leaf-profile.d.ts +11 -0
  141. package/dist/systems/door/leaf-profile.d.ts.map +1 -0
  142. package/dist/systems/door/leaf-profile.js +140 -0
  143. package/dist/systems/interactive/callout-actions.d.ts +72 -0
  144. package/dist/systems/interactive/callout-actions.d.ts.map +1 -0
  145. package/dist/systems/interactive/callout-actions.js +212 -0
  146. package/dist/systems/interactive/interactive-system.d.ts +5 -1
  147. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  148. package/dist/systems/interactive/interactive-system.js +44 -121
  149. package/dist/systems/interactive/opening-settled.d.ts +16 -0
  150. package/dist/systems/interactive/opening-settled.d.ts.map +1 -0
  151. package/dist/systems/interactive/opening-settled.js +53 -0
  152. package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
  153. package/dist/systems/item-light/item-light-system.js +22 -214
  154. package/dist/systems/item-light/pooled-lights.d.ts +70 -0
  155. package/dist/systems/item-light/pooled-lights.d.ts.map +1 -0
  156. package/dist/systems/item-light/pooled-lights.js +449 -0
  157. package/dist/systems/level/level-system.d.ts.map +1 -1
  158. package/dist/systems/level/level-system.js +19 -1
  159. package/dist/systems/roof/roof-material-slots.d.ts +14 -0
  160. package/dist/systems/roof/roof-material-slots.d.ts.map +1 -0
  161. package/dist/systems/roof/roof-material-slots.js +13 -0
  162. package/dist/systems/roof/roof-materials.d.ts +25 -2
  163. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  164. package/dist/systems/roof/roof-materials.js +85 -26
  165. package/dist/systems/roof/roof-system.d.ts +34 -1
  166. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  167. package/dist/systems/roof/roof-system.js +477 -105
  168. package/dist/systems/wall/opening-casing-geometry.d.ts +14 -0
  169. package/dist/systems/wall/opening-casing-geometry.d.ts.map +1 -0
  170. package/dist/systems/wall/opening-casing-geometry.js +102 -0
  171. package/dist/systems/wall/opening-cutout-geometry.d.ts +9 -0
  172. package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -1
  173. package/dist/systems/wall/opening-cutout-geometry.js +7 -3
  174. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  175. package/dist/systems/wall/wall-materials.js +28 -35
  176. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  177. package/dist/systems/wall/wall-system.js +34 -19
  178. package/dist/systems/wall/wall-t-junctions.d.ts +15 -0
  179. package/dist/systems/wall/wall-t-junctions.d.ts.map +1 -0
  180. package/dist/systems/wall/wall-t-junctions.js +149 -0
  181. package/dist/systems/window/window-animation-system.d.ts +3 -12
  182. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  183. package/dist/systems/window/window-animation-system.js +37 -122
  184. package/dist/systems/window/window-pose.d.ts +31 -0
  185. package/dist/systems/window/window-pose.d.ts.map +1 -0
  186. package/dist/systems/window/window-pose.js +152 -0
  187. package/dist/systems/window/window-roller-shutter.d.ts +8 -0
  188. package/dist/systems/window/window-roller-shutter.d.ts.map +1 -0
  189. package/dist/systems/window/window-roller-shutter.js +64 -0
  190. package/dist/systems/window/window-system.d.ts +2 -11
  191. package/dist/systems/window/window-system.d.ts.map +1 -1
  192. package/dist/systems/window/window-system.js +470 -303
  193. package/package.json +6 -6
  194. package/dist/systems/interactive/control-widget.d.ts +0 -10
  195. package/dist/systems/interactive/control-widget.d.ts.map +0 -1
  196. package/dist/systems/interactive/control-widget.js +0 -35
  197. package/dist/systems/interactive/procedural-controls.d.ts +0 -13
  198. package/dist/systems/interactive/procedural-controls.d.ts.map +0 -1
  199. package/dist/systems/interactive/procedural-controls.js +0 -17
@@ -1,11 +1,15 @@
1
1
  import { useFrame, useThree } from '@react-three/fiber';
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import { Color, Layers, Matrix4, Scene, UnsignedByteType } from 'three';
4
- import { ssgi } from 'three/addons/tsl/display/SSGINode.js';
5
- import { denoise } from 'three/examples/jsm/tsl/display/DenoiseNode.js';
6
- import { add, diffuseColor, mix, normalView, normalWorldGeometry, oscSine, output, pass, premultiplyAlpha, renderOutput, sample, saturation, screenUV, smoothstep, time, color as tslColor, float as tslFloat, uniform, vec3, vec4, } from 'three/tsl';
3
+ import { ACESFilmicToneMapping, Color, Layers, Matrix4, NeutralToneMapping, Scene, UnsignedByteType, Vector3, } from 'three';
4
+ import { bloom } from 'three/addons/tsl/display/BloomNode.js';
5
+ import { fxaa } from 'three/addons/tsl/display/FXAANode.js';
6
+ import { ao as gtao } from 'three/addons/tsl/display/GTAONode.js';
7
+ import { traa } from 'three/addons/tsl/display/TRAANode.js';
8
+ import { add, diffuseColor, ivec2, mix, normalView, normalWorldGeometry, oscSine, output, pass, premultiplyAlpha, renderOutput, sample, saturation, screenUV, smoothstep, time, color as tslColor, float as tslFloat, length as tslLength, uniform, vec3, vec4, velocity, } from 'three/tsl';
7
9
  import { RenderPipeline, TimestampQuery } from 'three/webgpu';
8
10
  import { backdropColors, backdropGradient, flatBackdropOutput } from '../../lib/backdrop';
11
+ import { DAYLIGHT_BLOOM, DAYLIGHT_GRADE, DAYLIGHT_INK_BACKDROP, daylightGradeRgb, isDaylightAtmosphere as isDaylight, } from '../../lib/daylight/look';
12
+ import { daylightRoomContext } from '../../lib/daylight/room-context';
9
13
  import { edgeColorFor, edgeOpacityScaleFor } from '../../lib/edge-style';
10
14
  import { PERF_OVERLAY_ENABLED } from '../../lib/gpu-perf';
11
15
  import { createEdgeDepthSampler, inkedEdges } from '../../lib/ink-edges';
@@ -16,18 +20,31 @@ import { isLiveFrameHeld } from '../../lib/live-frame-hold';
16
20
  import { mergedOutline } from '../../lib/merged-outline-node';
17
21
  import { recordPerfSample, timeSpan } from '../../lib/perf-tracks';
18
22
  import { PostProcessingResources } from '../../lib/post-processing-resources';
23
+ import { renderWithSceneMatrices } from '../../lib/scene-matrices';
19
24
  import { sceneLightingMask, scenePassMrt } from '../../lib/scene-pass-mrt';
20
25
  import { packNormalToRGB, unpackRGBToNormal } from '../../lib/tsl-compat';
21
26
  import { earthNow, UNDERGROUND_VIGNETTE, undergroundAmbianceNode, undergroundBackdrop, undergroundDissolve, undergroundDissolveGrain, undergroundEarthNode, undergroundFogAmount, undergroundVignette, worldPositionFromDepth, } from '../../lib/underground-ambiance';
22
27
  import useViewer from '../../store/use-viewer';
23
28
  import { useSceneAtmosphere } from './scene-atmosphere';
29
+ /** GTAO over the whole image (enhanced viewer's `gtao-whole`: strength 0.6 × 0.8). */
30
+ const AO_BLEND = 0.48;
31
+ const NEIGHBOURS_3X3 = [
32
+ [-1, -1],
33
+ [0, -1],
34
+ [1, -1],
35
+ [-1, 0],
36
+ [1, 0],
37
+ [-1, 1],
38
+ [0, 1],
39
+ [1, 1],
40
+ ];
24
41
  // A mild mid-gray contrast and saturation lift separates surfaces before the
25
- // output transform. Saved project looks can turn it off. Rendered shading only.
42
+ // output transform. Saved project looks can turn it off.
26
43
  export const GRADE_PARAMS = {
27
44
  contrast: 1.05,
28
45
  saturation: 1.1,
29
46
  };
30
- // SSGI Parameters - adjust these to fine-tune global illumination and ambient occlusion
47
+ // SSGI parameters for the snapshot pipeline's ambient occlusion.
31
48
  export const SSGI_PARAMS = {
32
49
  enabled: true,
33
50
  sliceCount: 1,
@@ -48,8 +65,8 @@ export const SSGI_PARAMS = {
48
65
  // allocation + per-frame work for that stage, so device temperature deltas
49
66
  // across combos isolate which pass is the actual culprit. Picked up once at
50
67
  // pipeline build; reload after changing the URL.
51
- // - ao: skip SSGI entirely (and denoise, since denoise has nothing to denoise)
52
- // - denoise: keep SSGI but feed its raw noisy AO straight to the composite
68
+ // - ao: skip ambient occlusion entirely
69
+ // - denoise: (snapshots) keep SSGI but feed its raw noisy AO straight to the composite
53
70
  // - outline: skip the merged-outline node and its 14 internal RTs
54
71
  // - postFx: bypass the whole RenderPipeline and use renderer.render(scene, camera)
55
72
  // directly — isolates raw scene-render cost from any post-FX overhead
@@ -218,13 +235,18 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
218
235
  const hoverPulseMix = useMemo(() => uniform(DEFAULT_HOVER_STYLE.pulse ? 0 : 1), []);
219
236
  // Subscribe to projectId so the pipeline rebuilds on project switch
220
237
  const projectId = useViewer((s) => s.projectId);
221
- const shading = useViewer((s) => s.shading);
222
238
  const ambientOcclusion = useViewer((s) => s.sceneAppearance.postprocessing?.ambientOcclusion !== false);
223
239
  const colorGrading = useViewer((s) => s.sceneAppearance.postprocessing?.colorGrading !== false);
224
240
  const flatBackground = useViewer((s) => s.sceneAppearance.backgroundSky === null);
225
241
  const edges = useViewer((s) => s.edges);
226
242
  const inkOpacityOverride = useViewer((s) => s.inkOpacity);
227
243
  const transparentBackground = useViewer((s) => s.transparentBackground);
244
+ const renderQuality = useViewer((s) => s.renderQuality);
245
+ // Daylight look uniforms, refreshed every frame from the atmosphere/exposure.
246
+ const whiteBalance = useMemo(() => uniform(new Vector3(1, 1, 1)), []);
247
+ // 1 / exposure: the bloom threshold (only display-white blooms) and the daylight look's
248
+ // compensation of unlit overlays (zones, editor overlays), which keep their designed brightness.
249
+ const exposureInverse = useMemo(() => uniform(1), []);
228
250
  const lastProjectIdRef = useRef(projectId);
229
251
  // Bump this to force a pipeline rebuild (used by retry logic)
230
252
  const [pipelineVersion, setPipelineVersion] = useState(0);
@@ -305,24 +327,33 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
305
327
  disposePipeline();
306
328
  return;
307
329
  }
308
- const ssgiEnabled = shading === 'rendered' && ambientOcclusion && SSGI_PARAMS.enabled && !perfDisable.ao;
309
- const denoiseEnabled = ssgiEnabled && !perfDisable.denoise;
330
+ // Quality tiers (both appearances; the enhanced-viewer reference):
331
+ // high — full-res GTAO from the scene pass, soft whole-image blend, TRAA, bloom (daylight)
332
+ // medium — ½-res GTAO from the scene pass, soft whole-image blend, TRAA
333
+ // (The reference's high tier fed GTAO from a depth/normal pre-pass, darkening indirect light
334
+ // only; a pre-pass draws the whole scene twice, which an editor scene of thousands of meshes
335
+ // can't afford.)
336
+ // low — no AO, FXAA
337
+ // The colored appearance adds the daylight look: Neutral tone mapping, white balance, grade
338
+ // and a display-space vignette. Ink edges belong to the monochrome appearance only.
339
+ const daylight = isDaylight(atmosphere);
340
+ renderer.toneMapping = daylight ? NeutralToneMapping : ACESFilmicToneMapping;
341
+ const aoTier = ambientOcclusion && !perfDisable.ao && renderQuality !== 'low' ? renderQuality : null;
342
+ const temporalAA = !transparentBackground && renderQuality !== 'low';
343
+ const fxaaEnabled = !transparentBackground && renderQuality === 'low';
344
+ const bloomEnabled = daylight && !transparentBackground && renderQuality === 'high';
310
345
  const outlineEnabled = !perfDisable.outline;
311
- const inkEnabled = edges !== 'off';
312
- const needsNormalMRT = ssgiEnabled || inkEnabled;
313
- // Soft = thin (1px sample radius) + faint (50% opacity); strong = thick
314
- // (2px, ~2× wider detected band) + solid (100%). The edge masks saturate,
315
- // so radius+opacity are what actually separate the two modes.
346
+ const inkEnabled = !daylight && edges !== 'off';
347
+ const needsNormalMRT = inkEnabled || aoTier !== null || temporalAA;
348
+ // Ink: thin (1px sample radius) and faint (50% opacity).
316
349
  const inkRadius = 1;
317
- const inkOpacity = inkOpacityOverride ?? (edges === 'strong' ? 1 : 0.5);
350
+ const inkOpacity = inkOpacityOverride ?? 0.5;
318
351
  console.log('[viewer/post-processing] Building pipeline', {
319
352
  version: pipelineVersion,
320
- ssgi: ssgiEnabled,
321
- denoise: denoiseEnabled,
353
+ quality: renderQuality,
322
354
  outline: outlineEnabled,
323
355
  perfDisable,
324
356
  projectId,
325
- shading,
326
357
  ambientOcclusion,
327
358
  colorGrading,
328
359
  transparentBackground,
@@ -331,7 +362,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
331
362
  height,
332
363
  });
333
364
  hasPipelineErrorRef.current = false;
334
- // WebGPU availability check: SSGI, denoise, and RenderPipeline are all
365
+ // WebGPU availability check: GTAO, TRAA, and RenderPipeline are all
335
366
  // WebGPU-only APIs. When the browser falls back to WebGL2 (no
336
367
  // `navigator.gpu`, or the device couldn't be created), building the
337
368
  // pipeline either throws silently or produces a broken output where
@@ -357,14 +388,17 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
357
388
  try {
358
389
  const layerIndex = new LayerPassIndex(scene, [ZONE_LAYER, OVERLAY_LAYER]);
359
390
  resources.layerIndex = layerIndex;
360
- const scenePass = pass(scene, camera);
391
+ // Temporal AA / FXAA replace MSAA, and the ½-res GTAO gathers from this depth (no multisampled
392
+ // `textureGather`): both want a single-sampled scene pass.
393
+ const singleSampled = temporalAA || fxaaEnabled || aoTier !== null;
394
+ const scenePass = pass(scene, camera, singleSampled ? { samples: 0 } : undefined);
361
395
  resources.passes.push(scenePass);
362
396
  scenePass.setLayers(sceneOnlyLayers);
363
397
  const zonePass = new LayerPassNode(layerIndex, camera, ZONE_LAYER, scenePass);
364
398
  resources.passes.push(zonePass);
365
399
  zonePass.setLayers(zoneLayers);
366
400
  // Editor overlays (gizmos, move handles, tool previews, grid) on their own
367
- // layer, kept out of the depth/normal MRT above so the ink + SSGI ignore
401
+ // layer, kept out of the depth/normal MRT above so the ink + AO ignore
368
402
  // them, then composited on top of the final image below.
369
403
  const overlayPass = new LayerPassNode(layerIndex, camera, OVERLAY_LAYER, scenePass);
370
404
  resources.passes.push(overlayPass);
@@ -378,11 +412,12 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
378
412
  const hasGeometry = scenePassColor.a;
379
413
  const contentAlpha = hasGeometry.max(zonePass.a);
380
414
  // Composite the zone-pass tint into the base scene so rooms show whether or
381
- // not SSGI is enabled. When SSGI is on, the branch below overwrites this
382
- // with its own zone-inclusive composite (no double-add).
383
- let sceneColor = vec4(add(scenePassColor.rgb, zonePass.rgb), contentAlpha);
384
- // Scene depth is shared by SSGI, ink, and outlines.
385
- // The normal MRT is only built when SSGI or ink needs it.
415
+ // not AO is enabled. The AO branch below rebuilds this
416
+ // composite with the zone tint kept out of the AO (no double-add).
417
+ const zoneRgb = daylight ? zonePass.rgb.mul(exposureInverse) : zonePass.rgb;
418
+ let sceneColor = vec4(add(scenePassColor.rgb, zoneRgb), contentAlpha);
419
+ // Scene depth is shared by AO, ink, and outlines.
420
+ // The normal MRT is only built when the medium AO, TRAA or ink needs it.
386
421
  const scenePassDepth = scenePass.getTextureNode('depth');
387
422
  const sampleEdgeDepth = createEdgeDepthSampler(scenePassDepth, camera);
388
423
  let scenePassNormal = null;
@@ -392,6 +427,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
392
427
  diffuseColor,
393
428
  normal: packNormalToRGB(normalView),
394
429
  lighting: sceneLightingMask(),
430
+ ...(temporalAA ? { velocity } : {}),
395
431
  }));
396
432
  scenePassNormal = scenePass.getTextureNode('normal');
397
433
  }
@@ -400,56 +436,32 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
400
436
  normalTexture.type = UnsignedByteType;
401
437
  scenePass.getTexture('lighting').type = UnsignedByteType;
402
438
  }
403
- // Extract normal from color-encoded texture (SSGI consumes the node form).
439
+ // Extract normal from color-encoded texture (GTAO consumes the node form).
404
440
  const normalNode = scenePassNormal;
405
441
  const sceneNormal = normalNode
406
442
  ? sample((uv) => unpackRGBToNormal(normalNode.sample(uv)))
407
443
  : null;
408
- if (ssgiEnabled && scenePassDepth && sceneNormal) {
409
- const scenePassDiffuse = scenePass.getTextureNode('diffuseColor');
410
- const diffuseTexture = scenePass.getTexture('diffuseColor');
411
- diffuseTexture.type = UnsignedByteType;
412
- const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, camera);
413
- giPass.sliceCount.value = SSGI_PARAMS.sliceCount;
414
- giPass.stepCount.value = SSGI_PARAMS.stepCount;
415
- giPass.radius.value = SSGI_PARAMS.radius;
416
- giPass.expFactor.value = SSGI_PARAMS.expFactor;
417
- giPass.thickness.value = SSGI_PARAMS.thickness;
418
- giPass.backfaceLighting.value = SSGI_PARAMS.backfaceLighting;
419
- giPass.aoIntensity.value = SSGI_PARAMS.aoIntensity;
420
- giPass.giIntensity.value = SSGI_PARAMS.giIntensity;
421
- giPass.useLinearThickness.value = SSGI_PARAMS.useLinearThickness;
422
- giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling;
423
- giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering;
424
- // r185: SSGI renders AO and GI into two separate textures (R8 + RG11B10)
425
- // exposed via getAONode()/getGINode() instead of one rgba texture.
426
- const aoTexture = giPass.getAONode();
427
- const gi = giPass.getGINode().rgb;
428
- let ao;
429
- if (denoiseEnabled) {
430
- // DenoiseNode only denoises RGB — alpha is passed through unchanged.
431
- // SSGI's AO is a single red channel, so we remap it into RGB before denoising.
432
- const aoAsRgb = vec4(aoTexture.r, aoTexture.r, aoTexture.r, tslFloat(1));
433
- const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
434
- denoisePass.index.value = 0;
435
- denoisePass.radius.value = 4;
436
- ao = denoisePass.r;
437
- }
438
- else {
439
- // Diagnostic path: feed raw noisy SSGI AO straight through. Will
440
- // look grainy — that's the point, it isolates denoise cost.
441
- ao = aoTexture.r;
442
- }
443
- // AO is a near/mid-field cue like the ink: fade it out with raw depth
444
- // (same ≈150→350 m window as ink-edges' distanceFade) so the horizon
445
- // disc and the geometry↔sky depth cliff never grow an AO band — that
446
- // band read as a visible line along the horizon.
447
- const aoFarFade = smoothstep(tslFloat(0.9994), tslFloat(0.9998), sampleEdgeDepth(screenUV));
448
- ao = mix(ao, tslFloat(1), aoFarFade);
449
- const lightingWeight = scenePass.getTextureNode('lighting').r;
450
- ao = mix(tslFloat(1), ao, lightingWeight);
451
- // Composite: scene * AO + diffuse * GI
452
- sceneColor = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi).mul(lightingWeight))), contentAlpha);
444
+ // AO: GTAO from the scene pass's depth and normals, blended softly over the whole image
445
+ // (full resolution on high, ½ on medium).
446
+ let temporalDepth = null;
447
+ let temporalVelocity = null;
448
+ if (aoTier && sceneNormal && scenePassDepth) {
449
+ const aoNode = gtao(scenePassDepth, sceneNormal, camera);
450
+ aoNode.radius.value = 0.6;
451
+ aoNode.thickness.value = 1.2;
452
+ aoNode.distanceExponent.value = 1.4;
453
+ aoNode.samples.value = 16;
454
+ aoNode.resolutionScale = aoTier === 'high' ? 1 : 0.5;
455
+ resources.effects.push(aoNode);
456
+ const aoBlend = mix(tslFloat(1), aoNode.getTextureNode().sample(screenUV).r, AO_BLEND);
457
+ sceneColor = vec4(add(scenePassColor.rgb.mul(aoBlend), zoneRgb), contentAlpha);
458
+ }
459
+ // The daylight look's room light (windows, lamps, bounce, night spill).
460
+ if (daylight)
461
+ scenePass.contextNode = daylightRoomContext();
462
+ if (temporalAA && !temporalVelocity && scenePassNormal) {
463
+ temporalDepth = scenePassDepth;
464
+ temporalVelocity = scenePass.getTextureNode('velocity');
453
465
  }
454
466
  // Screen-space ink outline (SketchUp look) — depth/normal edge detection
455
467
  // over the composited scene. Topology-agnostic, so it handles CSG-cut
@@ -464,13 +476,25 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
464
476
  opacity: tslFloat(inkOpacity).mul(inkOpacityScaleUniform.current),
465
477
  }), sceneColor.a);
466
478
  }
479
+ // Bloom on the HDR image; the threshold follows exposure so only display-white blooms.
480
+ if (bloomEnabled) {
481
+ // Editor highlights stay out of it (`markHighlight`): they must not haze over the object.
482
+ const bloomWeight = scenePassNormal ? scenePass.getTextureNode('lighting').g : tslFloat(1);
483
+ const glow = bloom(vec4(sceneColor.rgb.mul(bloomWeight), sceneColor.a), DAYLIGHT_BLOOM.strength, DAYLIGHT_BLOOM.radius, exposureInverse);
484
+ glow.smoothWidth.value = DAYLIGHT_BLOOM.softKnee;
485
+ resources.effects.push(glow);
486
+ sceneColor = vec4(sceneColor.rgb.add(glow.rgb), sceneColor.a);
487
+ }
467
488
  // Scene-referred grade (contrast around mid-gray + saturation) before the
468
- // pipeline's output tone mapping. Kept out of solid/schematic shading so
469
- // the flat presets stay exact. The same transform is applied to the
489
+ // pipeline's output tone mapping. The same transform is applied to the
470
490
  // backdrop below so geometry that fades to the background colour (the
471
- // horizon disc) matches it exactly.
472
- const gradeRgb = (rgb) => saturation(rgb.div(0.18).pow(vec3(GRADE_PARAMS.contrast)).mul(0.18), GRADE_PARAMS.saturation);
473
- if (shading === 'rendered' && colorGrading) {
491
+ // horizon disc) matches it exactly. The daylight look adds eye-like white
492
+ // balance (sky and scene alike).
493
+ const gradeRgb = (rgb) => daylight
494
+ ? daylightGradeRgb(rgb, whiteBalance)
495
+ : saturation(rgb.div(0.18).pow(vec3(GRADE_PARAMS.contrast)).mul(0.18), GRADE_PARAMS.saturation);
496
+ const graded = colorGrading;
497
+ if (graded) {
474
498
  sceneColor = vec4(gradeRgb(sceneColor.rgb), sceneColor.a);
475
499
  }
476
500
  // Reused scene depth lets outlined groups occlude each other; materials
@@ -509,7 +533,9 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
509
533
  .max(outlineNode.secondaryVisibleEdge)
510
534
  .max(outlineNode.secondaryHiddenEdge);
511
535
  visualAlpha = visualAlpha.max(outlineAlpha);
512
- compositeWithOutlines = vec4(add(sceneColor.rgb, selectedOutline.add(hoverOutline)), sceneColor.a);
536
+ compositeWithOutlines = vec4(add(sceneColor.rgb, daylight
537
+ ? selectedOutline.add(hoverOutline).mul(exposureInverse)
538
+ : selectedOutline.add(hoverOutline)), sceneColor.a);
513
539
  }
514
540
  // Backdrop: world-space view ray per pixel → background / horizon haze /
515
541
  // sky gradient (shared formula in lib/backdrop.ts). The horizon disc
@@ -527,7 +553,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
527
553
  sky: bgSkyUniform.current,
528
554
  skyDeep: bgSkyDeepUniform.current,
529
555
  });
530
- if (shading === 'rendered' && colorGrading) {
556
+ if (graded) {
531
557
  bgGradient = gradeRgb(bgGradient);
532
558
  }
533
559
  let sceneComposite = compositeWithOutlines.rgb;
@@ -554,14 +580,35 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
554
580
  sceneComposite = mix(sceneComposite, dark, undergroundFogAmount(worldPosition).mul(dive));
555
581
  }
556
582
  let composited = mix(bgGradient, sceneComposite, contentAlpha);
583
+ // Temporal AA resolves the whole picture, so silhouettes against the sky are smoothed too.
584
+ // Its history is only valid where the 3×3 neighbourhood holds geometry (open sky, at the far
585
+ // plane, resolves to black), so the sky away from geometry keeps the direct backdrop.
586
+ if (temporalAA && temporalVelocity && temporalDepth) {
587
+ const temporal = traa(vec4(composited, 1), temporalDepth, temporalVelocity, camera);
588
+ resources.effects.push(temporal);
589
+ let nearGeometry = contentAlpha;
590
+ for (const [x, y] of NEIGHBOURS_3X3) {
591
+ nearGeometry = nearGeometry.max(scenePassColor.offset(ivec2(x, y)).a);
592
+ }
593
+ composited = mix(composited, temporal.rgb, nearGeometry.greaterThan(0).select(1, 0));
594
+ }
557
595
  // The screen's edges close in with it.
558
596
  if (dive) {
559
597
  composited = mix(composited, tslColor(UNDERGROUND_VIGNETTE), undergroundVignette().mul(dive));
560
598
  }
561
- // Editor overlays painted on top by their own alpha — they never get inked,
562
- // AO'd, or outlined, and always read crisp regardless of scene depth.
563
- const withOverlay = mix(composited, overlayColor.rgb, overlayColor.a);
564
- let finalOutput = vec4(withOverlay, tslFloat(1));
599
+ // Display-space picture: tone mapping + colour space, then the daylight vignette (after the
600
+ // tone mapping, as in the reference). Editor overlays are painted on top by their own alpha —
601
+ // they never get inked, AO'd, outlined or vignetted, and always read crisp.
602
+ const toDisplay = (rgb) => renderOutput(vec4(rgb, 1), renderer.toneMapping, renderer.outputColorSpace);
603
+ let displayScene = toDisplay(composited);
604
+ if (daylight) {
605
+ const vignette = tslLength(screenUV.sub(0.5))
606
+ .mul(1.35)
607
+ .pow(2.5)
608
+ .mul(DAYLIGHT_GRADE.vignette);
609
+ displayScene = vec4(displayScene.rgb.mul(tslFloat(1).sub(vignette)), 1);
610
+ }
611
+ let finalOutput = vec4(mix(displayScene.rgb, toDisplay(daylight ? overlayColor.rgb.mul(exposureInverse) : overlayColor.rgb).rgb, overlayColor.a), tslFloat(1));
565
612
  if (transparentBackground) {
566
613
  const overlayAlpha = overlayColor.a;
567
614
  const alpha = overlayAlpha.add(visualAlpha.mul(overlayAlpha.oneMinus()));
@@ -582,10 +629,13 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
582
629
  const overlay = renderOutput(vec4(overlayColor.rgb, 1), renderer.toneMapping, renderer.outputColorSpace);
583
630
  finalOutput = vec4(mix(backdrop.rgb, overlay.rgb, overlayColor.a), 1);
584
631
  }
632
+ // Low tier: FXAA on the display image (no temporal AA, no MSAA).
633
+ if (fxaaEnabled)
634
+ finalOutput = fxaa(finalOutput);
585
635
  const renderPipeline = new RenderPipeline(renderer);
586
636
  resources.pipeline = renderPipeline;
587
- renderPipeline.outputColorTransform =
588
- !transparentBackground && !(flatBackground && !atmosphere);
637
+ // Every branch above ends in display space.
638
+ renderPipeline.outputColorTransform = false;
589
639
  renderPipeline.outputNode = finalOutput;
590
640
  retryCountRef.current = 0;
591
641
  }
@@ -593,7 +643,6 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
593
643
  hasPipelineErrorRef.current = true;
594
644
  console.error('[viewer/post-processing] Failed to set up post-processing pipeline. Rendering without post FX.', {
595
645
  version: pipelineVersion,
596
- ssgi: SSGI_PARAMS.enabled,
597
646
  rendererCtor: renderer.constructor?.name,
598
647
  }, error);
599
648
  disposePipeline();
@@ -618,11 +667,13 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
618
667
  projectId,
619
668
  renderer,
620
669
  scene,
621
- shading,
622
670
  ambientOcclusion,
623
671
  colorGrading,
624
672
  flatBackground,
625
673
  transparentBackground,
674
+ renderQuality,
675
+ whiteBalance,
676
+ exposureInverse,
626
677
  size.height,
627
678
  size.width,
628
679
  zoneLayers,
@@ -669,10 +720,16 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
669
720
  bgSkyDeepTarget.current.set(backdrop.skyDeep);
670
721
  bgSkyDeepCurrent.current.lerp(bgSkyDeepTarget.current, Math.min(delta, 0.1) * 4);
671
722
  bgSkyDeepUniform.current.value.copy(bgSkyDeepCurrent.current);
723
+ if (isDaylight(atmosphere))
724
+ whiteBalance.value.copy(atmosphere.whiteBalance);
725
+ exposureInverse.value = 1 / Math.max(renderer.toneMappingExposure, 1e-3);
672
726
  camProjInvUniform.current.value.copy(camera.projectionMatrixInverse);
673
727
  camWorldUniform.current.value.copy(camera.matrixWorld);
674
- // Ink colour follows the (lerping) background luminance — snaps dark↔light.
675
- const bgHex = `#${bgCurrent.current.getHexString()}`;
728
+ // Ink colour follows the (lerping) background luminance — snaps dark↔light. The daylight
729
+ // look ignores the scene theme: its edges are always the light-backdrop ink.
730
+ const bgHex = isDaylight(atmosphere)
731
+ ? DAYLIGHT_INK_BACKDROP
732
+ : `#${bgCurrent.current.getHexString()}`;
676
733
  inkColorUniform.current.value.set(edgeColorFor(bgHex));
677
734
  inkOpacityScaleUniform.current.value = edgeOpacityScaleFor(bgHex);
678
735
  const outliner = useViewer.getState().outliner;
@@ -703,8 +760,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
703
760
  }
704
761
  const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
705
762
  timeSpan('render-encode', () => {
706
- ;
707
- renderer.render(scene, camera);
763
+ renderWithSceneMatrices(scene, () => renderer.render(scene, camera));
708
764
  });
709
765
  if (PERF_OVERLAY_ENABLED)
710
766
  recordFrameGpuTiming(renderer, submittedAt);
@@ -726,7 +782,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
726
782
  renderer.setClearAlpha(0);
727
783
  const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
728
784
  timeSpan('render-encode', () => {
729
- pipeline.render();
785
+ renderWithSceneMatrices(scene, () => pipeline.render());
730
786
  });
731
787
  if (PERF_OVERLAY_ENABLED)
732
788
  recordFrameGpuTiming(renderer, submittedAt);
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { useThree } from '@react-three/fiber';
3
- import { useEffect, useRef } from 'react';
3
+ import { useEffect } from 'react';
4
4
  import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
5
5
  import * as THREE from 'three/webgpu';
6
6
  import useViewer from '../../store/use-viewer';
@@ -68,8 +68,6 @@ export function SceneEnvironment() {
68
68
  const environment = useViewer((state) => state.sceneAppearance.environment);
69
69
  const source = environment?.source ?? 'gradient';
70
70
  const atmosphere = useSceneAtmosphere();
71
- const atmosphereRef = useRef(atmosphere);
72
- atmosphereRef.current = atmosphere;
73
71
  useEffect(() => {
74
72
  const prevEnvironment = scene.environment;
75
73
  let target;
@@ -104,9 +102,10 @@ export function SceneEnvironment() {
104
102
  const previous = scene.environmentIntensity;
105
103
  scene.environmentIntensity =
106
104
  environment?.intensity ?? (appearance === 'dark' ? ENV_INTENSITY_DARK : ENV_INTENSITY);
105
+ // Always hand back what was there: when an atmosphere arrives after this ran (its owner
106
+ // installs in a layout effect, before this component sees it), that is the atmosphere's 1.
107
107
  return () => {
108
- if (!atmosphereRef.current)
109
- scene.environmentIntensity = previous;
108
+ scene.environmentIntensity = previous;
110
109
  };
111
110
  }, [appearance, atmosphere, scene, environment?.intensity]);
112
111
  return null;
@@ -0,0 +1,51 @@
1
+ import { type InteractionSound, type MessageDescriptor } from '@pascal-app/core';
2
+ import { type Object3D, Vector3 } from 'three';
3
+ export type CalloutIcon = 'door' | 'window' | 'shutters' | 'light' | 'fan' | 'power';
4
+ export type CalloutSound = {
5
+ on: InteractionSound;
6
+ off: InteractionSound;
7
+ swing?: InteractionSound;
8
+ };
9
+ /** Scene data (a name, a part's label) as written, or UI copy to translate. */
10
+ export type CalloutText = string | MessageDescriptor;
11
+ /** One control on the callout: a pill that flips something, or a pill holding a range. */
12
+ export type CalloutAction = {
13
+ kind: 'toggle';
14
+ key: string;
15
+ icon: CalloutIcon;
16
+ on: boolean;
17
+ /** The element or part it acts on, for the spoken label. */
18
+ name: string;
19
+ /** A part's own name on the pill; without it the pill shows its state word. */
20
+ text?: CalloutText;
21
+ /** `swing` sounds as a closing run starts, `off` as it lands. */
22
+ sound: CalloutSound;
23
+ /** When closing returns a promise for the end of the run, the closing sound waits for it. */
24
+ toggle: () => void | Promise<void>;
25
+ } | {
26
+ kind: 'range';
27
+ key: string;
28
+ label: string;
29
+ value: number;
30
+ min: number;
31
+ max: number;
32
+ step: number;
33
+ unit?: string;
34
+ onChange: (value: number) => void;
35
+ };
36
+ /**
37
+ * The selected element's label: a dot on it, a hairline rising from the dot,
38
+ * then its name, a detail line, its controls and a close button. It follows
39
+ * the element's world point each frame rather than living inside its object:
40
+ * kinds that rebuild their children (a window opening) would drop it.
41
+ */
42
+ export declare function SelectionCallout({ object, anchor: anchorProp, title, subtitle, actions, onDismiss, }: {
43
+ object: Object3D;
44
+ /** Where the dot sits, in the object's space; the centre of its bounds by default. */
45
+ anchor?: Vector3;
46
+ title: string;
47
+ subtitle?: CalloutText | null;
48
+ actions: CalloutAction[];
49
+ onDismiss: () => void;
50
+ }): import("react").JSX.Element;
51
+ //# sourceMappingURL=selection-callout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-callout.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-callout.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAGvB,MAAM,kBAAkB,CAAA;AAYzB,OAAO,EAAqB,KAAK,QAAQ,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAEjE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;AAEpF,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,gBAAgB,CAAC;IAAC,GAAG,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAA;AAEpG,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,iBAAiB,CAAA;AAEpD,0FAA0F;AAC1F,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,EAAE,QAAQ,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,WAAW,CAAA;IACjB,EAAE,EAAE,OAAO,CAAA;IACX,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,+EAA+E;IAC/E,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,iEAAiE;IACjE,KAAK,EAAE,YAAY,CAAA;IACnB,6FAA6F;IAC7F,MAAM,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnC,GACD;IACE,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC,CAAA;AAgBL;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,MAAM,EAAE,UAAU,EAClB,KAAK,EACL,QAAQ,EACR,OAAO,EACP,SAAS,GACV,EAAE;IACD,MAAM,EAAE,QAAQ,CAAA;IAChB,sFAAsF;IACtF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,OAAO,EAAE,aAAa,EAAE,CAAA;IACxB,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,+BAkHA"}