@pascal-app/viewer 1.0.4 → 1.0.5

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 (183) hide show
  1. package/dist/capture/capture-runtime.js +1 -1
  2. package/dist/components/error-boundary.d.ts.map +1 -1
  3. package/dist/components/renderers/node-renderer.d.ts +1 -1
  4. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  5. package/dist/components/renderers/node-renderer.js +4 -1
  6. package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -1
  7. package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -1
  8. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  9. package/dist/components/viewer/glb-interactive.js +1 -1
  10. package/dist/components/viewer/glb-reference-nodes.js +1 -1
  11. package/dist/components/viewer/glb-replace-instances.js +1 -1
  12. package/dist/components/viewer/glb-scene.d.ts.map +1 -1
  13. package/dist/components/viewer/glb-scene.js +2 -3
  14. package/dist/components/viewer/glb-walkthrough-controller.js +1 -1
  15. package/dist/components/viewer/index.d.ts +10 -1
  16. package/dist/components/viewer/index.d.ts.map +1 -1
  17. package/dist/components/viewer/index.js +28 -12
  18. package/dist/components/viewer/lights.d.ts.map +1 -1
  19. package/dist/components/viewer/lights.js +107 -21
  20. package/dist/components/viewer/perf-panel.d.ts.map +1 -1
  21. package/dist/components/viewer/perf-panel.js +1 -1
  22. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  23. package/dist/components/viewer/post-processing.js +96 -33
  24. package/dist/components/viewer/registered-systems.js +1 -1
  25. package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
  26. package/dist/components/viewer/scene-bvh.js +1 -1
  27. package/dist/components/viewer/scene-environment.d.ts.map +1 -1
  28. package/dist/components/viewer/scene-environment.js +31 -11
  29. package/dist/components/viewer/selection-manager.js +15 -7
  30. package/dist/components/viewer/underground-ambiance.d.ts +9 -0
  31. package/dist/components/viewer/underground-ambiance.d.ts.map +1 -0
  32. package/dist/components/viewer/underground-ambiance.js +105 -0
  33. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  34. package/dist/components/viewer/viewer-camera.js +1 -1
  35. package/dist/components/viewer/viewer-presentations.d.ts.map +1 -1
  36. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  37. package/dist/hooks/use-gltf-ktx2.js +2 -0
  38. package/dist/hooks/use-node-events.d.ts.map +1 -1
  39. package/dist/index.d.ts +16 -7
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +16 -7
  42. package/dist/lib/backdrop.d.ts +17 -0
  43. package/dist/lib/backdrop.d.ts.map +1 -1
  44. package/dist/lib/backdrop.js +18 -1
  45. package/dist/lib/construction-dust.d.ts +57 -0
  46. package/dist/lib/construction-dust.d.ts.map +1 -0
  47. package/dist/lib/construction-dust.js +187 -0
  48. package/dist/lib/csg-utils.js +1 -1
  49. package/dist/lib/foundation-materials.d.ts +26 -0
  50. package/dist/lib/foundation-materials.d.ts.map +1 -0
  51. package/dist/lib/foundation-materials.js +65 -0
  52. package/dist/lib/gltf-unlit.d.ts +10 -0
  53. package/dist/lib/gltf-unlit.d.ts.map +1 -0
  54. package/dist/lib/gltf-unlit.js +23 -0
  55. package/dist/lib/ground-window.d.ts +33 -0
  56. package/dist/lib/ground-window.d.ts.map +1 -0
  57. package/dist/lib/ground-window.js +114 -0
  58. package/dist/lib/hero-pose.d.ts +1 -1
  59. package/dist/lib/hero-pose.d.ts.map +1 -1
  60. package/dist/lib/ink-edges.d.ts +2 -1
  61. package/dist/lib/ink-edges.d.ts.map +1 -1
  62. package/dist/lib/ktx2-loader.d.ts +7 -0
  63. package/dist/lib/ktx2-loader.d.ts.map +1 -1
  64. package/dist/lib/ktx2-loader.js +35 -0
  65. package/dist/lib/layer-pass.d.ts.map +1 -1
  66. package/dist/lib/layers.d.ts +11 -7
  67. package/dist/lib/layers.d.ts.map +1 -1
  68. package/dist/lib/layers.js +16 -6
  69. package/dist/lib/materials.d.ts +5 -0
  70. package/dist/lib/materials.d.ts.map +1 -1
  71. package/dist/lib/materials.js +34 -4
  72. package/dist/lib/merged-outline-node.d.ts +5 -0
  73. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  74. package/dist/lib/merged-outline-node.js +80 -2
  75. package/dist/lib/pointer-events.d.ts +20 -4
  76. package/dist/lib/pointer-events.d.ts.map +1 -1
  77. package/dist/lib/pointer-events.js +78 -14
  78. package/dist/lib/post-processing-resources.d.ts.map +1 -1
  79. package/dist/lib/reveal-candidates.d.ts +80 -0
  80. package/dist/lib/reveal-candidates.d.ts.map +1 -0
  81. package/dist/lib/reveal-candidates.js +524 -0
  82. package/dist/lib/reveal-ghost.d.ts +9 -0
  83. package/dist/lib/reveal-ghost.d.ts.map +1 -0
  84. package/dist/lib/reveal-ghost.js +62 -0
  85. package/dist/lib/reveal-pending.d.ts +12 -0
  86. package/dist/lib/reveal-pending.d.ts.map +1 -0
  87. package/dist/lib/reveal-pending.js +12 -0
  88. package/dist/lib/reveal-pose.d.ts +70 -0
  89. package/dist/lib/reveal-pose.d.ts.map +1 -0
  90. package/dist/lib/reveal-pose.js +248 -0
  91. package/dist/lib/reveal-schedule.d.ts +208 -0
  92. package/dist/lib/reveal-schedule.d.ts.map +1 -0
  93. package/dist/lib/reveal-schedule.js +410 -0
  94. package/dist/lib/reveal-texture-scale.d.ts +5 -0
  95. package/dist/lib/reveal-texture-scale.d.ts.map +1 -0
  96. package/dist/lib/reveal-texture-scale.js +113 -0
  97. package/dist/lib/reveal-windows.d.ts +23 -0
  98. package/dist/lib/reveal-windows.d.ts.map +1 -0
  99. package/dist/lib/reveal-windows.js +31 -0
  100. package/dist/lib/reveal-wipe.d.ts +71 -0
  101. package/dist/lib/reveal-wipe.d.ts.map +1 -0
  102. package/dist/lib/reveal-wipe.js +105 -0
  103. package/dist/lib/scene-pass-mrt.d.ts +12 -0
  104. package/dist/lib/scene-pass-mrt.d.ts.map +1 -0
  105. package/dist/lib/scene-pass-mrt.js +23 -0
  106. package/dist/lib/scene-themes.d.ts +30 -8
  107. package/dist/lib/scene-themes.d.ts.map +1 -1
  108. package/dist/lib/scene-themes.js +36 -0
  109. package/dist/lib/scene-visibility.d.ts +11 -1
  110. package/dist/lib/scene-visibility.d.ts.map +1 -1
  111. package/dist/lib/scene-visibility.js +38 -0
  112. package/dist/lib/site-surfaces.d.ts +32 -0
  113. package/dist/lib/site-surfaces.d.ts.map +1 -0
  114. package/dist/lib/site-surfaces.js +70 -0
  115. package/dist/lib/snapshot-pipeline.d.ts +2 -1
  116. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  117. package/dist/lib/snapshot-pipeline.js +35 -15
  118. package/dist/lib/tsl-compat.d.ts +2 -2
  119. package/dist/lib/tsl-compat.d.ts.map +1 -1
  120. package/dist/lib/underground-ambiance.d.ts +168 -0
  121. package/dist/lib/underground-ambiance.d.ts.map +1 -0
  122. package/dist/lib/underground-ambiance.js +334 -0
  123. package/dist/store/use-viewer.d.ts +184 -27
  124. package/dist/store/use-viewer.d.ts.map +1 -1
  125. package/dist/store/use-viewer.js +155 -19
  126. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  127. package/dist/systems/ceiling/ceiling-system.js +2 -0
  128. package/dist/systems/construction-reveal/construction-dust.d.ts +16 -0
  129. package/dist/systems/construction-reveal/construction-dust.d.ts.map +1 -0
  130. package/dist/systems/construction-reveal/construction-dust.js +92 -0
  131. package/dist/systems/construction-reveal/construction-reveal.d.ts +276 -0
  132. package/dist/systems/construction-reveal/construction-reveal.d.ts.map +1 -0
  133. package/dist/systems/construction-reveal/construction-reveal.js +2383 -0
  134. package/dist/systems/construction-reveal/dust-material.d.ts +10 -0
  135. package/dist/systems/construction-reveal/dust-material.d.ts.map +1 -0
  136. package/dist/systems/construction-reveal/dust-material.js +17 -0
  137. package/dist/systems/construction-reveal/ghost-dress.d.ts +10 -0
  138. package/dist/systems/construction-reveal/ghost-dress.d.ts.map +1 -0
  139. package/dist/systems/construction-reveal/ghost-dress.js +24 -0
  140. package/dist/systems/elevator/elevator-interaction-system.d.ts +7 -0
  141. package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -1
  142. package/dist/systems/elevator/elevator-interaction-system.js +18 -1
  143. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  144. package/dist/systems/interactive/control-widget.d.ts.map +1 -1
  145. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  146. package/dist/systems/interactive/interactive-system.js +16 -6
  147. package/dist/systems/interactive/scripted-clips.js +1 -1
  148. package/dist/systems/item-light/item-light-system.js +1 -1
  149. package/dist/systems/level/level-hold.d.ts +4 -0
  150. package/dist/systems/level/level-hold.d.ts.map +1 -0
  151. package/dist/systems/level/level-hold.js +14 -0
  152. package/dist/systems/level/level-system.d.ts.map +1 -1
  153. package/dist/systems/level/level-system.js +16 -1
  154. package/dist/systems/level/level-utils.d.ts +13 -3
  155. package/dist/systems/level/level-utils.d.ts.map +1 -1
  156. package/dist/systems/level/level-utils.js +56 -8
  157. package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts +103 -0
  158. package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts.map +1 -0
  159. package/dist/systems/roof/__fixtures__/roof-plane-cases.js +317 -0
  160. package/dist/systems/roof/roof-materials.d.ts +4 -2
  161. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  162. package/dist/systems/roof/roof-materials.js +18 -24
  163. package/dist/systems/roof/roof-system.d.ts +8 -1
  164. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  165. package/dist/systems/roof/roof-system.js +195 -45
  166. package/dist/systems/wall/wall-cutout-cache.d.ts +32 -3
  167. package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -1
  168. package/dist/systems/wall/wall-cutout-cache.js +95 -2
  169. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  170. package/dist/systems/wall/wall-material-variant.d.ts.map +1 -1
  171. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  172. package/dist/systems/wall/wall-materials.js +39 -4
  173. package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -1
  174. package/dist/systems/wall/wall-system.d.ts +5 -0
  175. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  176. package/dist/systems/wall/wall-system.js +42 -19
  177. package/dist/systems/window/corner-frame.d.ts +10 -0
  178. package/dist/systems/window/corner-frame.d.ts.map +1 -0
  179. package/dist/systems/window/corner-frame.js +17 -0
  180. package/dist/systems/window/window-system.d.ts +1 -1
  181. package/dist/systems/window/window-system.d.ts.map +1 -1
  182. package/dist/systems/window/window-system.js +31 -6
  183. package/package.json +8 -8
@@ -3,26 +3,26 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Color, Layers, Matrix4, Scene, UnsignedByteType } from 'three';
4
4
  import { ssgi } from 'three/addons/tsl/display/SSGINode.js';
5
5
  import { denoise } from 'three/examples/jsm/tsl/display/DenoiseNode.js';
6
- import { add, diffuseColor, mix, mrt, normalView, normalWorldGeometry, oscSine, output, pass, premultiplyAlpha, renderOutput, sample, saturation, screenUV, smoothstep, time, float as tslFloat, uniform, vec3, vec4, } from 'three/tsl';
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';
7
7
  import { RenderPipeline, TimestampQuery } from 'three/webgpu';
8
- import { backdropGradient, deepSkyColor, horizonHazeColor } from '../../lib/backdrop';
8
+ import { backdropColors, backdropGradient, flatBackdropOutput } from '../../lib/backdrop';
9
9
  import { edgeColorFor, edgeOpacityScaleFor } from '../../lib/edge-style';
10
10
  import { PERF_OVERLAY_ENABLED } from '../../lib/gpu-perf';
11
11
  import { createEdgeDepthSampler, inkedEdges } from '../../lib/ink-edges';
12
12
  import { refreshIsolation } from '../../lib/isolation';
13
13
  import { LayerPassIndex, LayerPassNode } from '../../lib/layer-pass';
14
- import { GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, ZONE_LAYER } from '../../lib/layers';
14
+ import { OVERLAY_LAYER, SCENE_LAYER, scenePassLayers, ZONE_LAYER } from '../../lib/layers';
15
15
  import { isLiveFrameHeld } from '../../lib/live-frame-hold';
16
16
  import { mergedOutline } from '../../lib/merged-outline-node';
17
17
  import { recordPerfSample, timeSpan } from '../../lib/perf-tracks';
18
18
  import { PostProcessingResources } from '../../lib/post-processing-resources';
19
- import { getSceneTheme } from '../../lib/scene-themes';
19
+ import { sceneLightingMask, scenePassMrt } from '../../lib/scene-pass-mrt';
20
20
  import { packNormalToRGB, unpackRGBToNormal } from '../../lib/tsl-compat';
21
+ import { earthNow, UNDERGROUND_VIGNETTE, undergroundAmbianceNode, undergroundBackdrop, undergroundDissolve, undergroundDissolveGrain, undergroundEarthNode, undergroundFogAmount, undergroundVignette, worldPositionFromDepth, } from '../../lib/underground-ambiance';
21
22
  import useViewer from '../../store/use-viewer';
22
23
  import { useSceneAtmosphere } from './scene-atmosphere';
23
- // Scene-referred grade applied before the output tone mapping (AgX). AgX rolls
24
- // highlights off gently but reads flat on its own; a mild mid-gray-pivot
25
- // contrast + saturation lift restores the punch. Rendered shading only.
24
+ // 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.
26
26
  export const GRADE_PARAMS = {
27
27
  contrast: 1.05,
28
28
  saturation: 1.1,
@@ -143,29 +143,33 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
143
143
  const { gl: renderer, invalidate, scene, camera, size } = useThree();
144
144
  const resourcesRef = useRef(null);
145
145
  const atmosphere = useSceneAtmosphere();
146
- const directSkyNode = useMemo(() => (atmosphere ? atmosphere.skyRadiance(normalWorldGeometry) : null), [atmosphere]);
146
+ // Under the ground the sky is the dark mode's: the same mix the pipeline's backdrop makes.
147
+ const directSkyNode = useMemo(() => atmosphere
148
+ ? mix(atmosphere.skyRadiance(normalWorldGeometry), undergroundBackdrop(normalWorldGeometry.y), undergroundEarthNode())
149
+ : null, [atmosphere]);
147
150
  const hasPipelineErrorRef = useRef(false);
148
151
  const retryCountRef = useRef(0);
149
152
  const rebuildTimeoutRef = useRef(null);
150
153
  const skippedZeroSizeRef = useRef(false);
151
154
  // Background color uniform — updated every frame via lerp, read by the TSL pipeline.
152
155
  // Initialised from the current scene theme so there's no flash on first render.
153
- const initTheme = getSceneTheme(useViewer.getState().sceneTheme);
156
+ const initTheme = useViewer.getState().sceneAppearance;
157
+ const initBackdrop = backdropColors(initTheme);
154
158
  const initBg = initTheme.background;
155
159
  const bgUniform = useRef(uniform(new Color(initBg)));
156
160
  const bgCurrent = useRef(new Color(initBg));
157
161
  const bgTarget = useRef(new Color());
158
162
  // Zenith colour of the backdrop gradient (falls back to the flat background).
159
- const initSky = initTheme.backgroundSky ?? initBg;
163
+ const initSky = initBackdrop.sky;
160
164
  const bgSkyUniform = useRef(uniform(new Color(initSky)));
161
165
  const bgSkyCurrent = useRef(new Color(initSky));
162
166
  const bgSkyTarget = useRef(new Color());
163
167
  // Horizon haze band + deep zenith (derived — see lib/backdrop.ts).
164
- const initHaze = horizonHazeColor(initSky, initTheme.appearance);
168
+ const initHaze = initBackdrop.haze;
165
169
  const bgHazeUniform = useRef(uniform(new Color(initHaze)));
166
170
  const bgHazeCurrent = useRef(new Color(initHaze));
167
171
  const bgHazeTarget = useRef(new Color());
168
- const initSkyDeep = deepSkyColor(initSky);
172
+ const initSkyDeep = initBackdrop.skyDeep;
169
173
  const bgSkyDeepUniform = useRef(uniform(new Color(initSkyDeep)));
170
174
  const bgSkyDeepCurrent = useRef(new Color(initSkyDeep));
171
175
  const bgSkyDeepTarget = useRef(new Color());
@@ -174,6 +178,9 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
174
178
  // view ray from these to find the true horizon (dir.y = 0).
175
179
  const camProjInvUniform = useRef(uniform(new Matrix4()));
176
180
  const camWorldUniform = useRef(uniform(new Matrix4()));
181
+ // The clear colour of the no-pipeline fallback under the ground (scratch, and the dark mode's tone).
182
+ const fallbackClear = useRef(new Color());
183
+ const fallbackEarth = useRef(new Color(UNDERGROUND_VIGNETTE));
177
184
  // Ink-line colour follows the scene-theme background luminance (dark lines on
178
185
  // light scenes, light on dark), refreshed each frame like the background.
179
186
  // Dark scenes also scale the ink opacity down (see edge-style.ts).
@@ -190,13 +197,10 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
190
197
  // picking), so without this the gizmos/handles/tool previews land in the
191
198
  // depth+normal MRT and get inked / AO'd as if they were geometry. The grid is
192
199
  // kept in here (not the overlay pass) so scene geometry depth-occludes it; it's
193
- // a flat, depth-non-writing plane so the ink never picks it up.
194
- const sceneOnlyLayers = useMemo(() => {
195
- const l = new Layers();
196
- l.set(SCENE_LAYER);
197
- l.enable(GRID_LAYER);
198
- return l;
199
- }, []);
200
+ // a flat, depth-non-writing plane. The dust shares the layer and writes alpha 0 to
201
+ // the normal and diffuse attachments, which `scenePassMrt` blends, so the ink
202
+ // never sees its quads.
203
+ const sceneOnlyLayers = useMemo(scenePassLayers, []);
200
204
  // Editor overlays render in their own pass, composited on top after the ink
201
205
  // and outlines so they read as crisp UI rather than scene geometry.
202
206
  const overlayLayers = useMemo(() => {
@@ -204,7 +208,10 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
204
208
  l.set(OVERLAY_LAYER);
205
209
  return l;
206
210
  }, []);
207
- const hoverHighlightMode = useViewer((s) => s.hoverHighlightMode);
211
+ const hoverMode = useViewer((s) => s.hoverHighlightMode);
212
+ // Elements a chat chip points at draw in the `point` style whatever the editor's hover mode is.
213
+ const hasPointedIds = useViewer((s) => s.pointedIds.length > 0);
214
+ const hoverHighlightMode = hasPointedIds ? 'point' : hoverMode;
208
215
  const hoverVisibleColor = useMemo(() => uniform(new Color(DEFAULT_HOVER_STYLE.visibleColor)), []);
209
216
  const hoverHiddenColor = useMemo(() => uniform(new Color(DEFAULT_HOVER_STYLE.hiddenColor)), []);
210
217
  const hoverStrength = useMemo(() => uniform(DEFAULT_HOVER_STYLE.strength), []);
@@ -212,6 +219,9 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
212
219
  // Subscribe to projectId so the pipeline rebuilds on project switch
213
220
  const projectId = useViewer((s) => s.projectId);
214
221
  const shading = useViewer((s) => s.shading);
222
+ const ambientOcclusion = useViewer((s) => s.sceneAppearance.postprocessing?.ambientOcclusion !== false);
223
+ const colorGrading = useViewer((s) => s.sceneAppearance.postprocessing?.colorGrading !== false);
224
+ const flatBackground = useViewer((s) => s.sceneAppearance.backgroundSky === null);
215
225
  const edges = useViewer((s) => s.edges);
216
226
  const inkOpacityOverride = useViewer((s) => s.inkOpacity);
217
227
  const transparentBackground = useViewer((s) => s.transparentBackground);
@@ -295,7 +305,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
295
305
  disposePipeline();
296
306
  return;
297
307
  }
298
- const ssgiEnabled = shading === 'rendered' && SSGI_PARAMS.enabled && !perfDisable.ao;
308
+ const ssgiEnabled = shading === 'rendered' && ambientOcclusion && SSGI_PARAMS.enabled && !perfDisable.ao;
299
309
  const denoiseEnabled = ssgiEnabled && !perfDisable.denoise;
300
310
  const outlineEnabled = !perfDisable.outline;
301
311
  const inkEnabled = edges !== 'off';
@@ -313,6 +323,8 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
313
323
  perfDisable,
314
324
  projectId,
315
325
  shading,
326
+ ambientOcclusion,
327
+ colorGrading,
316
328
  transparentBackground,
317
329
  rendererCtor: renderer.constructor?.name,
318
330
  width,
@@ -375,16 +387,18 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
375
387
  const sampleEdgeDepth = createEdgeDepthSampler(scenePassDepth, camera);
376
388
  let scenePassNormal = null;
377
389
  if (needsNormalMRT) {
378
- scenePass.setMRT(mrt({
390
+ scenePass.setMRT(scenePassMrt({
379
391
  output,
380
392
  diffuseColor,
381
393
  normal: packNormalToRGB(normalView),
394
+ lighting: sceneLightingMask(),
382
395
  }));
383
396
  scenePassNormal = scenePass.getTextureNode('normal');
384
397
  }
385
398
  if (scenePassNormal) {
386
399
  const normalTexture = scenePass.getTexture('normal');
387
400
  normalTexture.type = UnsignedByteType;
401
+ scenePass.getTexture('lighting').type = UnsignedByteType;
388
402
  }
389
403
  // Extract normal from color-encoded texture (SSGI consumes the node form).
390
404
  const normalNode = scenePassNormal;
@@ -432,8 +446,10 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
432
446
  // band read as a visible line along the horizon.
433
447
  const aoFarFade = smoothstep(tslFloat(0.9994), tslFloat(0.9998), sampleEdgeDepth(screenUV));
434
448
  ao = mix(ao, tslFloat(1), aoFarFade);
449
+ const lightingWeight = scenePass.getTextureNode('lighting').r;
450
+ ao = mix(tslFloat(1), ao, lightingWeight);
435
451
  // Composite: scene * AO + diffuse * GI
436
- sceneColor = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))), contentAlpha);
452
+ sceneColor = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi).mul(lightingWeight))), contentAlpha);
437
453
  }
438
454
  // Screen-space ink outline (SketchUp look) — depth/normal edge detection
439
455
  // over the composited scene. Topology-agnostic, so it handles CSG-cut
@@ -454,7 +470,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
454
470
  // backdrop below so geometry that fades to the background colour (the
455
471
  // horizon disc) matches it exactly.
456
472
  const gradeRgb = (rgb) => saturation(rgb.div(0.18).pow(vec3(GRADE_PARAMS.contrast)).mul(0.18), GRADE_PARAMS.saturation);
457
- if (shading === 'rendered') {
473
+ if (shading === 'rendered' && colorGrading) {
458
474
  sceneColor = vec4(gradeRgb(sceneColor.rgb), sceneColor.a);
459
475
  }
460
476
  // Reused scene depth lets outlined groups occlude each other; materials
@@ -511,11 +527,37 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
511
527
  sky: bgSkyUniform.current,
512
528
  skyDeep: bgSkyDeepUniform.current,
513
529
  });
514
- if (shading === 'rendered') {
530
+ if (shading === 'rendered' && colorGrading) {
515
531
  bgGradient = gradeRgb(bgGradient);
516
532
  }
517
- const sceneComposite = compositeWithOutlines.rgb;
518
- const composited = mix(bgGradient, sceneComposite, contentAlpha);
533
+ let sceneComposite = compositeWithOutlines.rgb;
534
+ // Under the ground (the eye below the ground surface) the scene goes to its dark mode: the
535
+ // sky is the dark theme's backdrop, and what stands far from the shown level dissolves into
536
+ // it (distance outside the level's box, from the scene pass's own depth; backdrop and fog
537
+ // meet in one colour per pixel). A noise mask closes over the screen as the eye crosses,
538
+ // 0 everywhere above the ground, 1 everywhere below.
539
+ let dive = null;
540
+ if (!transparentBackground) {
541
+ const grain = undergroundDissolveGrain();
542
+ dive = undergroundDissolve(screenUV, undergroundAmbianceNode(), grain);
543
+ // The backdrop follows the earth factor, which is the dark mode's own in the overview and
544
+ // alone in the walkthrough (looking out of a basement window shows earth, the rooms keep
545
+ // their light: no fog, no vignette).
546
+ const earthDive = undergroundDissolve(screenUV, undergroundEarthNode(), grain);
547
+ const dark = undergroundBackdrop(worldDir.y);
548
+ // The day's backdrop leaves ahead of the blobs: it greys and dims over the first half of
549
+ // the crossing, so the anthracite spreads over a neutral grey picture, not a blue sky.
550
+ const leaving = smoothstep(tslFloat(0), tslFloat(0.5), undergroundEarthNode());
551
+ const day = saturation(bgGradient, tslFloat(1).sub(leaving)).mul(tslFloat(1).sub(leaving.mul(0.35)));
552
+ bgGradient = mix(day, dark, earthDive);
553
+ const worldPosition = worldPositionFromDepth(scenePassDepth.sample(screenUV).r, camProjInvUniform.current, camWorldUniform.current);
554
+ sceneComposite = mix(sceneComposite, dark, undergroundFogAmount(worldPosition).mul(dive));
555
+ }
556
+ let composited = mix(bgGradient, sceneComposite, contentAlpha);
557
+ // The screen's edges close in with it.
558
+ if (dive) {
559
+ composited = mix(composited, tslColor(UNDERGROUND_VIGNETTE), undergroundVignette().mul(dive));
560
+ }
519
561
  // Editor overlays painted on top by their own alpha — they never get inked,
520
562
  // AO'd, or outlined, and always read crisp regardless of scene depth.
521
563
  const withOverlay = mix(composited, overlayColor.rgb, overlayColor.a);
@@ -529,9 +571,21 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
529
571
  .div(alpha.max(tslFloat(0.00001)));
530
572
  finalOutput = premultiplyAlpha(renderOutput(vec4(straightRgb, alpha)));
531
573
  }
574
+ else if (flatBackground && !atmosphere) {
575
+ const backdrop = flatBackdropOutput({
576
+ background: bgUniform.current,
577
+ foreground: sceneComposite,
578
+ alpha: contentAlpha,
579
+ toneMapping: renderer.toneMapping,
580
+ outputColorSpace: renderer.outputColorSpace,
581
+ });
582
+ const overlay = renderOutput(vec4(overlayColor.rgb, 1), renderer.toneMapping, renderer.outputColorSpace);
583
+ finalOutput = vec4(mix(backdrop.rgb, overlay.rgb, overlayColor.a), 1);
584
+ }
532
585
  const renderPipeline = new RenderPipeline(renderer);
533
586
  resources.pipeline = renderPipeline;
534
- renderPipeline.outputColorTransform = !transparentBackground;
587
+ renderPipeline.outputColorTransform =
588
+ !transparentBackground && !(flatBackground && !atmosphere);
535
589
  renderPipeline.outputNode = finalOutput;
536
590
  retryCountRef.current = 0;
537
591
  }
@@ -565,6 +619,9 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
565
619
  renderer,
566
620
  scene,
567
621
  shading,
622
+ ambientOcclusion,
623
+ colorGrading,
624
+ flatBackground,
568
625
  transparentBackground,
569
626
  size.height,
570
627
  size.width,
@@ -598,17 +655,18 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
598
655
  return;
599
656
  }
600
657
  // Animate background colour toward the current scene theme target (same lerp as AnimatedBackground)
601
- const bgTheme = getSceneTheme(useViewer.getState().sceneTheme);
658
+ const bgTheme = useViewer.getState().sceneAppearance;
659
+ const backdrop = backdropColors(bgTheme);
602
660
  bgTarget.current.set(bgTheme.background);
603
661
  bgCurrent.current.lerp(bgTarget.current, Math.min(delta, 0.1) * 4);
604
662
  bgUniform.current.value.copy(bgCurrent.current);
605
- bgSkyTarget.current.set(bgTheme.backgroundSky ?? bgTheme.background);
663
+ bgSkyTarget.current.set(backdrop.sky);
606
664
  bgSkyCurrent.current.lerp(bgSkyTarget.current, Math.min(delta, 0.1) * 4);
607
665
  bgSkyUniform.current.value.copy(bgSkyCurrent.current);
608
- bgHazeTarget.current.set(horizonHazeColor(bgTheme.backgroundSky ?? bgTheme.background, bgTheme.appearance));
666
+ bgHazeTarget.current.set(backdrop.haze);
609
667
  bgHazeCurrent.current.lerp(bgHazeTarget.current, Math.min(delta, 0.1) * 4);
610
668
  bgHazeUniform.current.value.copy(bgHazeCurrent.current);
611
- bgSkyDeepTarget.current.set(deepSkyColor(bgTheme.backgroundSky ?? bgTheme.background));
669
+ bgSkyDeepTarget.current.set(backdrop.skyDeep);
612
670
  bgSkyDeepCurrent.current.lerp(bgSkyDeepTarget.current, Math.min(delta, 0.1) * 4);
613
671
  bgSkyDeepUniform.current.value.copy(bgSkyDeepCurrent.current);
614
672
  camProjInvUniform.current.value.copy(camera.projectionMatrixInverse);
@@ -630,9 +688,14 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
630
688
  scene.backgroundNode = directSkyNode;
631
689
  try {
632
690
  const clearAlpha = transparentBackground ? 0 : 1;
691
+ // Without the pipeline there is no fog and no vignette: the clear colour is the dark mode's.
692
+ const underground = transparentBackground ? 0 : earthNow();
693
+ const clearColor = underground > 0 ? fallbackClear.current.copy(bgCurrent.current) : bgCurrent.current;
694
+ if (underground > 0)
695
+ clearColor.lerp(fallbackEarth.current, underground);
633
696
  if (renderer.setClearColor) {
634
697
  ;
635
- renderer.setClearColor(bgCurrent.current, clearAlpha);
698
+ renderer.setClearColor(clearColor, clearAlpha);
636
699
  }
637
700
  else if (renderer.setClearAlpha) {
638
701
  ;
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createSceneApi, isNodeKindEnabled, nodeRegistry, useRegistryVersion, useScene, } from '@pascal-app/core';
4
4
  import { Component, lazy, Suspense, useMemo } from 'react';
5
5
  const DEFAULT_PRIORITY = 5;
@@ -1 +1 @@
1
- {"version":3,"file":"scene-bvh.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/scene-bvh.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,SAAS,EAA0C,MAAM,OAAO,CAAA;AAC1F,OAAO,EAAuB,KAAK,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAC7D,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAA;AAEvB,KAAK,aAAa,GAAG;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAKD,eAAO,MAAM,kBAAkB,GAAI,QAAQ,IAAI,YAA4C,CAAA;AAY3F,eAAO,MAAM,QAAQ,mIAoGpB,CAAA"}
1
+ {"version":3,"file":"scene-bvh.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/scene-bvh.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,SAAS,EAA0C,MAAM,OAAO,CAAA;AAC1F,OAAO,EAAuB,KAAK,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAC7D,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAA;AAEvB,KAAK,aAAa,GAAG;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAKD,eAAO,MAAM,kBAAkB,WAAY,IAAI,YAA4C,CAAA;AAY3F,eAAO,MAAM,QAAQ,mIAoGpB,CAAA"}
@@ -26,7 +26,7 @@ export const SceneBvh = forwardRef(({ children, enabled = true, firstHitOnly = f
26
26
  verbose,
27
27
  setBoundingBox,
28
28
  maxDepth,
29
- maxLeafSize,
29
+ targetLeafSize: maxLeafSize,
30
30
  indirect,
31
31
  };
32
32
  const group = ref.current;
@@ -1 +1 @@
1
- {"version":3,"file":"scene-environment.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/scene-environment.tsx"],"names":[],"mappings":"AAoEA,wBAAgB,gBAAgB,SA8B/B"}
1
+ {"version":3,"file":"scene-environment.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/scene-environment.tsx"],"names":[],"mappings":"AAoEA,wBAAgB,gBAAgB,SA8C/B"}
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import { useThree } from '@react-three/fiber';
3
- import { useEffect, useMemo, useRef } from 'react';
3
+ import { useEffect, useRef } from 'react';
4
+ import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
4
5
  import * as THREE from 'three/webgpu';
5
- import { getSceneTheme } from '../../lib/scene-themes';
6
6
  import useViewer from '../../store/use-viewer';
7
7
  import { useSceneAtmosphere } from './scene-atmosphere';
8
8
  /**
@@ -63,31 +63,51 @@ function buildGradientSky() {
63
63
  }
64
64
  export function SceneEnvironment() {
65
65
  const scene = useThree((state) => state.scene);
66
- const texture = useMemo(buildGradientSky, []);
67
- const appearance = useViewer((state) => getSceneTheme(state.sceneTheme).appearance);
66
+ const renderer = useThree((state) => state.gl);
67
+ const appearance = useViewer((state) => state.sceneAppearance.appearance);
68
+ const environment = useViewer((state) => state.sceneAppearance.environment);
69
+ const source = environment?.source ?? 'gradient';
68
70
  const atmosphere = useSceneAtmosphere();
69
71
  const atmosphereRef = useRef(atmosphere);
70
72
  atmosphereRef.current = atmosphere;
71
73
  useEffect(() => {
72
74
  const prevEnvironment = scene.environment;
73
- const prevIntensity = scene.environmentIntensity;
75
+ let target;
76
+ let texture = null;
77
+ if (source === 'room') {
78
+ const room = new RoomEnvironment();
79
+ const generator = new THREE.PMREMGenerator(renderer);
80
+ target = generator.fromScene(room, 0.04);
81
+ texture = target.texture;
82
+ room.dispose();
83
+ generator.dispose();
84
+ }
85
+ else if (source === 'gradient')
86
+ texture = buildGradientSky();
74
87
  scene.environment = texture;
75
88
  return () => {
76
89
  if (scene.environment === texture) {
77
90
  scene.environment = prevEnvironment;
78
- if (!atmosphereRef.current)
79
- scene.environmentIntensity = prevIntensity;
80
91
  }
81
- texture.dispose();
92
+ if (target)
93
+ target.dispose();
94
+ else
95
+ texture?.dispose();
82
96
  };
83
- }, [scene, texture]);
97
+ }, [renderer, scene, source]);
84
98
  useEffect(() => {
85
99
  // An atmosphere owns environmentIntensity while its environmentNode is
86
100
  // active. The gradient remains installed as the fallback texture and takes
87
101
  // over immediately when the atmosphere restores scene ownership.
88
102
  if (atmosphere)
89
103
  return;
90
- scene.environmentIntensity = appearance === 'dark' ? ENV_INTENSITY_DARK : ENV_INTENSITY;
91
- }, [appearance, atmosphere, scene]);
104
+ const previous = scene.environmentIntensity;
105
+ scene.environmentIntensity =
106
+ environment?.intensity ?? (appearance === 'dark' ? ENV_INTENSITY_DARK : ENV_INTENSITY);
107
+ return () => {
108
+ if (!atmosphereRef.current)
109
+ scene.environmentIntensity = previous;
110
+ };
111
+ }, [appearance, atmosphere, scene, environment?.intensity]);
92
112
  return null;
93
113
  }
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { emitter, getSelectableKinds, pointInPolygon, resolveSelectionProxyId, sceneRegistry, useRegistryVersion, useScene, } from '@pascal-app/core';
4
4
  import { useThree } from '@react-three/fiber';
5
5
  import { useEffect, useRef } from 'react';
@@ -362,6 +362,7 @@ const OutlinerSync = () => {
362
362
  const selection = useViewer((s) => s.selection);
363
363
  const externalSelectedIds = useViewer((s) => s.externalSelectedIds);
364
364
  const hoveredId = useViewer((s) => s.hoveredId);
365
+ const pointedIds = useViewer((s) => s.pointedIds);
365
366
  const outliner = useViewer((s) => s.outliner);
366
367
  const geometryRevision = useViewer((s) => s.geometryRevision);
367
368
  const nodes = useScene((s) => s.nodes);
@@ -377,16 +378,23 @@ const OutlinerSync = () => {
377
378
  if (obj)
378
379
  outliner.selectedObjects.push(obj);
379
380
  }
380
- // Sync hovered objects
381
+ // Sync hovered objects, then the ones a chat chip points at (Point and ask).
381
382
  outliner.hoveredObjects.length = 0;
382
383
  if (hoveredId) {
383
384
  const hoveredNode = nodes[hoveredId];
384
- if (hoveredNode?.type === 'slab')
385
- return;
386
- const obj = sceneRegistry.nodes.get(hoveredId);
387
- if (obj)
385
+ if (hoveredNode?.type !== 'slab') {
386
+ const obj = sceneRegistry.nodes.get(hoveredId);
387
+ if (obj)
388
+ outliner.hoveredObjects.push(obj);
389
+ }
390
+ }
391
+ for (const id of new Set(pointedIds)) {
392
+ if (nodes[id]?.type === 'slab')
393
+ continue;
394
+ const obj = sceneRegistry.nodes.get(id);
395
+ if (obj && !outliner.hoveredObjects.includes(obj))
388
396
  outliner.hoveredObjects.push(obj);
389
397
  }
390
- }, [selection, externalSelectedIds, hoveredId, outliner, nodes, geometryRevision]);
398
+ }, [selection, externalSelectedIds, hoveredId, pointedIds, outliner, nodes, geometryRevision]);
391
399
  return null;
392
400
  };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Keeps the two states the ground is the medium of. The eye above the ground, a level below
3
+ * grade shown with the ground on Auto: a hole opens in the ground (the factor `hole`, eased).
4
+ * The eye under the ground: the dark mode, the factor `ambiance`, eased in over a short
5
+ * crossing and back out when the eye comes up. Neither follows the other: a veiled basement
6
+ * seen from above has no ambiance. Mount beside `Lights`, which does.
7
+ */
8
+ export declare function UndergroundAmbiance(): null;
9
+ //# sourceMappingURL=underground-ambiance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"underground-ambiance.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/underground-ambiance.tsx"],"names":[],"mappings":"AAoCA;;;;;;GAMG;AACH,wBAAgB,mBAAmB,SAyGlC"}
@@ -0,0 +1,105 @@
1
+ 'use client';
2
+ import { buildingGroundBox, groundHeightAt, levelStackGroundBox, sceneRegistry, useScene, } from '@pascal-app/core';
3
+ import { useFrame } from '@react-three/fiber';
4
+ import { useRef } from 'react';
5
+ import { Vector3 } from 'three';
6
+ import { AMBIANCE_SECONDS, ambianceNow, ambianceTarget, applyAmbianceToSkyLights, earthNow, earthTarget, easeAmbiance, eyeUnderTheGround, FADE_SECONDS, restoreSkyLights, setAmbianceFactor, setAmbianceFocus, setDiveGrainWeight, setEarthFactor, setGroundHoleOpen, stepAmbianceProgress, } from '../../lib/underground-ambiance';
7
+ import useViewer, { groundVeiled } from '../../store/use-viewer';
8
+ import { revealMotionOff } from '../../systems/construction-reveal/construction-reveal';
9
+ /**
10
+ * Keeps the two states the ground is the medium of. The eye above the ground, a level below
11
+ * grade shown with the ground on Auto: a hole opens in the ground (the factor `hole`, eased).
12
+ * The eye under the ground: the dark mode, the factor `ambiance`, eased in over a short
13
+ * crossing and back out when the eye comes up. Neither follows the other: a veiled basement
14
+ * seen from above has no ambiance. Mount beside `Lights`, which does.
15
+ */
16
+ export function UndergroundAmbiance() {
17
+ const progress = useRef(null);
18
+ const holeProgress = useRef(null);
19
+ const earthProgress = useRef(null);
20
+ const eye = useRef(new Vector3());
21
+ const lastEye = useRef(new Vector3(Number.NaN, Number.NaN, Number.NaN));
22
+ const lastNodes = useRef(null);
23
+ const groundY = useRef(0);
24
+ // The box the fog keeps clear: the host's ground window box when it keeps one, else the level
25
+ // shown (or the building) worked out here, so a host without the window still gets it.
26
+ const focus = useRef(null);
27
+ const focusFrom = useRef([]);
28
+ // Before `Lights` lerps the sky's lights (-1) they are given back as the theme set them, and
29
+ // after it they are moved for the frame the pipeline (1) draws.
30
+ useFrame((state, delta) => {
31
+ restoreSkyLights();
32
+ const viewer = useViewer.getState();
33
+ state.camera.getWorldPosition(eye.current);
34
+ const { nodes } = useScene.getState();
35
+ if (!eye.current.equals(lastEye.current) || nodes !== lastNodes.current) {
36
+ lastEye.current.copy(eye.current);
37
+ lastNodes.current = nodes;
38
+ const siteId = sceneRegistry.byType.site?.values().next().value;
39
+ const site = siteId ? nodes[siteId] : null;
40
+ groundY.current = groundHeightAt(site, eye.current.x, eye.current.z);
41
+ }
42
+ const under = eyeUnderTheGround(viewer.eyeUnderGround, eye.current.y, groundY.current);
43
+ if (under !== viewer.eyeUnderGround)
44
+ useViewer.setState({ eyeUnderGround: under });
45
+ // The first frame takes the world as it is: a project opened below grade does not dive.
46
+ const instant = revealMotionOff() || viewer.isExporting;
47
+ const fade = viewer.undergroundLook.transition === 'fade';
48
+ const seconds = instant ? 0 : fade ? FADE_SECONDS : AMBIANCE_SECONDS;
49
+ setDiveGrainWeight(fade ? 0 : 1);
50
+ const dt = Math.min(delta, 0.1);
51
+ const target = ambianceTarget({
52
+ eyeUnderGround: under,
53
+ groundMode: viewer.groundMode,
54
+ walkthroughMode: viewer.walkthroughMode,
55
+ });
56
+ const next = stepAmbianceProgress(progress.current ?? target, target, dt, seconds);
57
+ progress.current = next;
58
+ const eased = easeAmbiance(next);
59
+ setAmbianceFactor(eased);
60
+ const active = eased > 0;
61
+ if (viewer.ambianceActive !== active)
62
+ useViewer.setState({ ambianceActive: active });
63
+ // What is seen beyond the walls turns to earth with the eye, in the walkthrough too: there it
64
+ // is the only part of the dark mode that does (the rooms keep their light, no fog, no vignette).
65
+ const earthWanted = earthTarget({ eyeUnderGround: under, groundMode: viewer.groundMode });
66
+ const earthNext = stepAmbianceProgress(earthProgress.current ?? earthWanted, earthWanted, dt, seconds);
67
+ earthProgress.current = earthNext;
68
+ setEarthFactor(easeAmbiance(earthNext));
69
+ const holeTarget = groundVeiled(viewer) ? 1 : 0;
70
+ const holeNext = stepAmbianceProgress(holeProgress.current ?? holeTarget, holeTarget, dt, instant ? 0 : AMBIANCE_SECONDS);
71
+ holeProgress.current = holeNext;
72
+ setGroundHoleOpen(easeAmbiance(holeNext));
73
+ if (next !== target || holeNext !== holeTarget || earthNext !== earthWanted)
74
+ state.invalidate();
75
+ if (eased > 0 || earthNext > 0) {
76
+ const { selection, hideLevelsAboveSelection, belowGrade, groundWindowBox } = viewer;
77
+ const from = [
78
+ nodes,
79
+ selection.levelId,
80
+ selection.buildingId,
81
+ hideLevelsAboveSelection,
82
+ belowGrade,
83
+ groundWindowBox,
84
+ ];
85
+ if (from.some((value, index) => value !== focusFrom.current[index])) {
86
+ focusFrom.current = from;
87
+ // Where the world stays clear: the shown level and the ones under it (a deep level is
88
+ // seen with the stack over it opened), else the host's window box, else the building.
89
+ focus.current =
90
+ (selection.levelId && (hideLevelsAboveSelection || belowGrade)
91
+ ? levelStackGroundBox(nodes, selection.levelId)
92
+ : null) ??
93
+ groundWindowBox ??
94
+ buildingGroundBox(nodes, selection.buildingId ?? null);
95
+ }
96
+ }
97
+ }, -2);
98
+ useFrame(() => {
99
+ const amount = ambianceNow();
100
+ if (amount > 0 || earthNow() > 0)
101
+ setAmbianceFocus(focus.current, eye.current);
102
+ applyAmbianceToSkyLights(amount);
103
+ }, 0);
104
+ return null;
105
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"viewer-camera.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/viewer-camera.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAOnC,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,cAWxF;AAcD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,OAAO;;;EAExD;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,sBAAsB,IAAI,IAAI,CAAA;CAAE,EACrE,WAAW,EAAE,OAAO,QAMrB;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ,sBAAsB,IAAI,IAAI,CAAA;CAC/B,cASA;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,WAEnF;AAED,eAAO,MAAM,YAAY,GAAI,iBAAyB;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,gCAqB9E,CAAA"}
1
+ {"version":3,"file":"viewer-camera.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/viewer-camera.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAOnC,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,cAWxF;AAcD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,OAAO;;;EAExD;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,sBAAsB,IAAI,IAAI,CAAA;CAAE,EACrE,WAAW,EAAE,OAAO,QAMrB;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ,sBAAsB,IAAI,IAAI,CAAA;CAC/B,cASA;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,WAEnF;AAED,eAAO,MAAM,YAAY,oBAA6B;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,gCAqB9E,CAAA"}
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { OrthographicCamera, PerspectiveCamera } from '@react-three/drei';
3
3
  import { useThree } from '@react-three/fiber';
4
4
  import { useEffect } from 'react';
@@ -1 +1 @@
1
- {"version":3,"file":"viewer-presentations.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/viewer-presentations.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,aAAa,EAAY,MAAM,kBAAkB,CAAA;AAE7E,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAG9C,MAAM,MAAM,+BAA+B,GAAG;IAC5C,kFAAkF;IAClF,WAAW,EAAE,MAAM,OAAO,CAAA;IAC1B,6DAA6D;IAC7D,OAAO,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IACpC,8DAA8D;IAC9D,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,wEAAwE;IACxE,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAChD,CAAA;AACD,MAAM,MAAM,+BAA+B,GAAG;IAC5C,iFAAiF;IACjF,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACxC,4EAA4E;IAC5E,aAAa,EAAE,OAAO,CAAA;IACtB,WAAW,EAAE,OAAO,CAAA;IACpB,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,CAAC,GAAG,EAAE,+BAA+B,KAAK,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;CAC5F,CAAA;AAGD;;;;GAIG;AACH,wBAAgB,qCAAqC,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAGtF;AAED,wBAAgB,mCAAmC,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAE7E;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,kFAAkF;IAClF,EAAE,EAAE,MAAM,CAAA;IACV,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,SAAS,EAAE,aAAa,CAAA;IACxB,gFAAgF;IAChF,aAAa,CAAC,EAAE,+BAA+B,CAAA;IAC/C;;;OAGG;IACH,YAAY,CAAC,EAAE,8BAA8B,CAAA;CAC9C,CAAA;AAeD,cAAM,8BAA8B;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoD;IAClF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,MAAM,CAAuC;IAErD,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAK/C;IAED,WAAW,QAAO,8BAA8B,EAAE,CAAe;IAEjE,KAAK,IAAI,IAAI;IAKb,QAAQ,CAAC,YAAY,EAAE,8BAA8B,GAAG,IAAI;IAmD5D,OAAO,CAAC,IAAI;CAIb;AAED,eAAO,MAAM,0BAA0B,gCAAuC,CAAA;AAE9E,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,8BAA8B,GAAG,IAAI,CAE7F;AA2BD;;;;;GAKG;AACH,wBAAgB,mBAAmB,2CAalC"}
1
+ {"version":3,"file":"viewer-presentations.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/viewer-presentations.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,aAAa,EAAY,MAAM,kBAAkB,CAAA;AAE7E,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAG9C,MAAM,MAAM,+BAA+B,GAAG;IAC5C,kFAAkF;IAClF,WAAW,EAAE,MAAM,OAAO,CAAA;IAC1B,6DAA6D;IAC7D,OAAO,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IACpC,8DAA8D;IAC9D,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,wEAAwE;IACxE,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAChD,CAAA;AACD,MAAM,MAAM,+BAA+B,GAAG;IAC5C,iFAAiF;IACjF,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACxC,4EAA4E;IAC5E,aAAa,EAAE,OAAO,CAAA;IACtB,WAAW,EAAE,OAAO,CAAA;IACpB,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,CAAC,GAAG,EAAE,+BAA+B,KAAK,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;CAC5F,CAAA;AAGD;;;;GAIG;AACH,wBAAgB,qCAAqC,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAGtF;AAED,wBAAgB,mCAAmC,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAE7E;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,kFAAkF;IAClF,EAAE,EAAE,MAAM,CAAA;IACV,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,SAAS,EAAE,aAAa,CAAA;IACxB,gFAAgF;IAChF,aAAa,CAAC,EAAE,+BAA+B,CAAA;IAC/C;;;OAGG;IACH,YAAY,CAAC,EAAE,8BAA8B,CAAA;CAC9C,CAAA;AAeD,cAAM,8BAA8B;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoD;IAClF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,MAAM,CAAuC;IAErD,SAAS,aAAc,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAK/C;IAED,WAAW,QAAO,8BAA8B,EAAE,CAAe;IAEjE,KAAK,IAAI,IAAI,CAGZ;IAED,QAAQ,CAAC,YAAY,EAAE,8BAA8B,GAAG,IAAI,CAiD3D;IAED,OAAO,CAAC,IAAI;CAIb;AAED,eAAO,MAAM,0BAA0B,gCAAuC,CAAA;AAE9E,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,8BAA8B,GAAG,IAAI,CAE7F;AA2BD;;;;;GAKG;AACH,wBAAgB,mBAAmB,2CAalC"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-gltf-ktx2.d.ts","sourceRoot":"","sources":["../../src/hooks/use-gltf-ktx2.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAK3C,QAAA,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,UAAU,CAAC,OAAO,OAAO,CAO5D,CAAA;AAED,OAAO,EAAE,WAAW,EAAE,CAAA"}
1
+ {"version":3,"file":"use-gltf-ktx2.d.ts","sourceRoot":"","sources":["../../src/hooks/use-gltf-ktx2.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAM3C,QAAA,MAAM,WAAW,SAAU,MAAM,KAAG,UAAU,CAAC,OAAO,OAAO,CAQ5D,CAAA;AAED,OAAO,EAAE,WAAW,EAAE,CAAA"}
@@ -1,10 +1,12 @@
1
1
  import { useGLTF } from '@react-three/drei';
2
2
  import { useThree } from '@react-three/fiber';
3
3
  import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
4
+ import { preserveGltfUnlit } from '../lib/gltf-unlit';
4
5
  import { configureKtx2Support } from '../lib/ktx2-loader';
5
6
  const useGLTFKTX2 = (path) => {
6
7
  const gl = useThree((state) => state.gl);
7
8
  return useGLTF(path, true, true, (loader) => {
9
+ loader.register(preserveGltfUnlit);
8
10
  configureKtx2Support(loader, gl);
9
11
  loader.setMeshoptDecoder(MeshoptDecoder);
10
12
  });
@@ -1 +1 @@
1
- {"version":3,"file":"use-node-events.d.ts","sourceRoot":"","sources":["../../src/hooks/use-node-events.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,WAAW,EAIjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAQpD,KAAK,UAAU,CAAC,CAAC,SAAS,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,CAAA;AAEtE,wBAAgB,aAAa,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;uBAyC1D,UAAU,CAAC,YAAY,CAAC;qBAK1B,UAAU,CAAC,YAAY,CAAC;kBAQ3B,UAAU,CAAC,YAAY,CAAC;wBAIlB,UAAU,CAAC,YAAY,CAAC;wBAIxB,UAAU,CAAC,YAAY,CAAC;uBAIzB,UAAU,CAAC,YAAY,CAAC;uBAIxB,UAAU,CAAC,YAAY,CAAC;uBAIxB,UAAU,CAAC,YAAY,CAAC;EAK9C"}
1
+ {"version":3,"file":"use-node-events.d.ts","sourceRoot":"","sources":["../../src/hooks/use-node-events.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,WAAW,EAIjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAQpD,KAAK,UAAU,CAAC,CAAC,SAAS,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,CAAA;AAEtE,wBAAgB,aAAa,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IAyC7E,aAAa,MAAM,UAAU,CAAC,YAAY,CAAC;IAK3C,WAAW,MAAM,UAAU,CAAC,YAAY,CAAC;IAQzC,OAAO,OAAO,UAAU,CAAC,YAAY,CAAC;IAItC,cAAc,MAAM,UAAU,CAAC,YAAY,CAAC;IAI5C,cAAc,MAAM,UAAU,CAAC,YAAY,CAAC;IAI5C,aAAa,MAAM,UAAU,CAAC,YAAY,CAAC;IAI3C,aAAa,MAAM,UAAU,CAAC,YAAY,CAAC;IAI3C,aAAa,MAAM,UAAU,CAAC,YAAY,CAAC;EAK9C"}