@pascal-app/viewer 1.0.3 → 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 (276) 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 +12 -4
  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 +1 -0
  9. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  10. package/dist/components/viewer/frame-limiter.js +22 -3
  11. package/dist/components/viewer/glb-interactive.d.ts +25 -1
  12. package/dist/components/viewer/glb-interactive.d.ts.map +1 -1
  13. package/dist/components/viewer/glb-interactive.js +253 -86
  14. package/dist/components/viewer/glb-mechanisms.d.ts +10 -0
  15. package/dist/components/viewer/glb-mechanisms.d.ts.map +1 -0
  16. package/dist/components/viewer/glb-mechanisms.js +19 -0
  17. package/dist/components/viewer/glb-reference-nodes.js +1 -1
  18. package/dist/components/viewer/glb-replace-instances.js +1 -1
  19. package/dist/components/viewer/glb-scene.d.ts +45 -27
  20. package/dist/components/viewer/glb-scene.d.ts.map +1 -1
  21. package/dist/components/viewer/glb-scene.js +421 -508
  22. package/dist/components/viewer/glb-walkthrough-controller.d.ts +1 -1
  23. package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
  24. package/dist/components/viewer/glb-walkthrough-controller.js +13 -2
  25. package/dist/components/viewer/index.d.ts +24 -1
  26. package/dist/components/viewer/index.d.ts.map +1 -1
  27. package/dist/components/viewer/index.js +111 -37
  28. package/dist/components/viewer/lights.d.ts.map +1 -1
  29. package/dist/components/viewer/lights.js +113 -22
  30. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  31. package/dist/components/viewer/perf-monitor.js +6 -0
  32. package/dist/components/viewer/perf-panel.d.ts.map +1 -1
  33. package/dist/components/viewer/perf-panel.js +1 -1
  34. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  35. package/dist/components/viewer/post-processing.js +104 -36
  36. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  37. package/dist/components/viewer/registered-systems.js +17 -3
  38. package/dist/components/viewer/render-error.d.ts +18 -0
  39. package/dist/components/viewer/render-error.d.ts.map +1 -0
  40. package/dist/components/viewer/render-error.js +29 -0
  41. package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
  42. package/dist/components/viewer/scene-bvh.js +1 -1
  43. package/dist/components/viewer/scene-environment.d.ts.map +1 -1
  44. package/dist/components/viewer/scene-environment.js +31 -11
  45. package/dist/components/viewer/scene-ground-replacement.d.ts +6 -0
  46. package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -1
  47. package/dist/components/viewer/scene-ground-replacement.js +15 -0
  48. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  49. package/dist/components/viewer/selection-manager.js +25 -7
  50. package/dist/components/viewer/underground-ambiance.d.ts +9 -0
  51. package/dist/components/viewer/underground-ambiance.d.ts.map +1 -0
  52. package/dist/components/viewer/underground-ambiance.js +105 -0
  53. package/dist/components/viewer/viewer-camera.d.ts +4 -0
  54. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  55. package/dist/components/viewer/viewer-camera.js +12 -2
  56. package/dist/components/viewer/viewer-presentations.d.ts.map +1 -1
  57. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  58. package/dist/hooks/use-gltf-ktx2.js +2 -0
  59. package/dist/hooks/use-node-events.d.ts.map +1 -1
  60. package/dist/index.d.ts +36 -16
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +34 -19
  63. package/dist/lib/asset-url.d.ts.map +1 -1
  64. package/dist/lib/asset-url.js +7 -1
  65. package/dist/lib/backdrop.d.ts +17 -0
  66. package/dist/lib/backdrop.d.ts.map +1 -1
  67. package/dist/lib/backdrop.js +18 -1
  68. package/dist/lib/construction-dust.d.ts +57 -0
  69. package/dist/lib/construction-dust.d.ts.map +1 -0
  70. package/dist/lib/construction-dust.js +187 -0
  71. package/dist/lib/csg-utils.js +1 -1
  72. package/dist/lib/display-state.d.ts +25 -0
  73. package/dist/lib/display-state.d.ts.map +1 -0
  74. package/dist/lib/display-state.js +1 -0
  75. package/dist/lib/foundation-materials.d.ts +26 -0
  76. package/dist/lib/foundation-materials.d.ts.map +1 -0
  77. package/dist/lib/foundation-materials.js +65 -0
  78. package/dist/lib/gltf-unlit.d.ts +10 -0
  79. package/dist/lib/gltf-unlit.d.ts.map +1 -0
  80. package/dist/lib/gltf-unlit.js +23 -0
  81. package/dist/lib/ground-window.d.ts +33 -0
  82. package/dist/lib/ground-window.d.ts.map +1 -0
  83. package/dist/lib/ground-window.js +114 -0
  84. package/dist/lib/hero-pose.d.ts +13 -3
  85. package/dist/lib/hero-pose.d.ts.map +1 -1
  86. package/dist/lib/hero-pose.js +72 -5
  87. package/dist/lib/ink-edges.d.ts +6 -3
  88. package/dist/lib/ink-edges.d.ts.map +1 -1
  89. package/dist/lib/ink-edges.js +17 -7
  90. package/dist/lib/key-light-override.d.ts +11 -0
  91. package/dist/lib/key-light-override.d.ts.map +1 -0
  92. package/dist/lib/key-light-override.js +14 -0
  93. package/dist/lib/ktx2-loader.d.ts +7 -0
  94. package/dist/lib/ktx2-loader.d.ts.map +1 -1
  95. package/dist/lib/ktx2-loader.js +35 -0
  96. package/dist/lib/layer-pass.d.ts.map +1 -1
  97. package/dist/lib/layers.d.ts +11 -7
  98. package/dist/lib/layers.d.ts.map +1 -1
  99. package/dist/lib/layers.js +16 -6
  100. package/dist/lib/live-frame-hold.d.ts +14 -0
  101. package/dist/lib/live-frame-hold.d.ts.map +1 -0
  102. package/dist/lib/live-frame-hold.js +28 -0
  103. package/dist/lib/materials.d.ts +19 -7
  104. package/dist/lib/materials.d.ts.map +1 -1
  105. package/dist/lib/materials.js +65 -9
  106. package/dist/lib/merged-outline-node.d.ts +5 -0
  107. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  108. package/dist/lib/merged-outline-node.js +80 -2
  109. package/dist/lib/node-top-surface-height.d.ts +6 -0
  110. package/dist/lib/node-top-surface-height.d.ts.map +1 -0
  111. package/dist/lib/node-top-surface-height.js +74 -0
  112. package/dist/lib/perf-probe-state.d.ts +39 -0
  113. package/dist/lib/perf-probe-state.d.ts.map +1 -0
  114. package/dist/lib/perf-probe-state.js +17 -0
  115. package/dist/lib/pointer-events.d.ts +26 -2
  116. package/dist/lib/pointer-events.d.ts.map +1 -1
  117. package/dist/lib/pointer-events.js +99 -12
  118. package/dist/lib/post-processing-resources.d.ts.map +1 -1
  119. package/dist/lib/procedural-emission.d.ts +7 -0
  120. package/dist/lib/procedural-emission.d.ts.map +1 -0
  121. package/dist/lib/procedural-emission.js +54 -0
  122. package/dist/lib/reveal-candidates.d.ts +80 -0
  123. package/dist/lib/reveal-candidates.d.ts.map +1 -0
  124. package/dist/lib/reveal-candidates.js +524 -0
  125. package/dist/lib/reveal-ghost.d.ts +9 -0
  126. package/dist/lib/reveal-ghost.d.ts.map +1 -0
  127. package/dist/lib/reveal-ghost.js +62 -0
  128. package/dist/lib/reveal-pending.d.ts +12 -0
  129. package/dist/lib/reveal-pending.d.ts.map +1 -0
  130. package/dist/lib/reveal-pending.js +12 -0
  131. package/dist/lib/reveal-pose.d.ts +70 -0
  132. package/dist/lib/reveal-pose.d.ts.map +1 -0
  133. package/dist/lib/reveal-pose.js +248 -0
  134. package/dist/lib/reveal-schedule.d.ts +208 -0
  135. package/dist/lib/reveal-schedule.d.ts.map +1 -0
  136. package/dist/lib/reveal-schedule.js +410 -0
  137. package/dist/lib/reveal-texture-scale.d.ts +5 -0
  138. package/dist/lib/reveal-texture-scale.d.ts.map +1 -0
  139. package/dist/lib/reveal-texture-scale.js +113 -0
  140. package/dist/lib/reveal-windows.d.ts +23 -0
  141. package/dist/lib/reveal-windows.d.ts.map +1 -0
  142. package/dist/lib/reveal-windows.js +31 -0
  143. package/dist/lib/reveal-wipe.d.ts +71 -0
  144. package/dist/lib/reveal-wipe.d.ts.map +1 -0
  145. package/dist/lib/reveal-wipe.js +105 -0
  146. package/dist/lib/scene-pass-mrt.d.ts +12 -0
  147. package/dist/lib/scene-pass-mrt.d.ts.map +1 -0
  148. package/dist/lib/scene-pass-mrt.js +23 -0
  149. package/dist/lib/scene-support-height.d.ts +4 -0
  150. package/dist/lib/scene-support-height.d.ts.map +1 -0
  151. package/dist/lib/scene-support-height.js +34 -0
  152. package/dist/lib/scene-themes.d.ts +30 -8
  153. package/dist/lib/scene-themes.d.ts.map +1 -1
  154. package/dist/lib/scene-themes.js +36 -0
  155. package/dist/lib/scene-visibility.d.ts +17 -1
  156. package/dist/lib/scene-visibility.d.ts.map +1 -1
  157. package/dist/lib/scene-visibility.js +53 -3
  158. package/dist/lib/scripted-opening.d.ts +11 -0
  159. package/dist/lib/scripted-opening.d.ts.map +1 -0
  160. package/dist/lib/scripted-opening.js +20 -0
  161. package/dist/lib/site-surfaces.d.ts +32 -0
  162. package/dist/lib/site-surfaces.d.ts.map +1 -0
  163. package/dist/lib/site-surfaces.js +70 -0
  164. package/dist/lib/snapshot-pipeline.d.ts +34 -3
  165. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  166. package/dist/lib/snapshot-pipeline.js +247 -117
  167. package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -1
  168. package/dist/lib/terrain-perimeter-fill.js +22 -14
  169. package/dist/lib/tsl-compat.d.ts +2 -2
  170. package/dist/lib/tsl-compat.d.ts.map +1 -1
  171. package/dist/lib/underground-ambiance.d.ts +168 -0
  172. package/dist/lib/underground-ambiance.d.ts.map +1 -0
  173. package/dist/lib/underground-ambiance.js +334 -0
  174. package/dist/lib/zone-geometry.d.ts +11 -0
  175. package/dist/lib/zone-geometry.d.ts.map +1 -0
  176. package/dist/lib/zone-geometry.js +46 -0
  177. package/dist/store/use-item-light-pool.d.ts +14 -10
  178. package/dist/store/use-item-light-pool.d.ts.map +1 -1
  179. package/dist/store/use-item-light-pool.js +52 -26
  180. package/dist/store/use-viewer.d.ts +194 -26
  181. package/dist/store/use-viewer.d.ts.map +1 -1
  182. package/dist/store/use-viewer.js +164 -21
  183. package/dist/systems/ceiling/ceiling-system.d.ts +15 -1
  184. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  185. package/dist/systems/ceiling/ceiling-system.js +88 -6
  186. package/dist/systems/construction-reveal/construction-dust.d.ts +16 -0
  187. package/dist/systems/construction-reveal/construction-dust.d.ts.map +1 -0
  188. package/dist/systems/construction-reveal/construction-dust.js +92 -0
  189. package/dist/systems/construction-reveal/construction-reveal.d.ts +276 -0
  190. package/dist/systems/construction-reveal/construction-reveal.d.ts.map +1 -0
  191. package/dist/systems/construction-reveal/construction-reveal.js +2383 -0
  192. package/dist/systems/construction-reveal/dust-material.d.ts +10 -0
  193. package/dist/systems/construction-reveal/dust-material.d.ts.map +1 -0
  194. package/dist/systems/construction-reveal/dust-material.js +17 -0
  195. package/dist/systems/construction-reveal/ghost-dress.d.ts +10 -0
  196. package/dist/systems/construction-reveal/ghost-dress.d.ts.map +1 -0
  197. package/dist/systems/construction-reveal/ghost-dress.js +24 -0
  198. package/dist/systems/door/door-system.d.ts.map +1 -1
  199. package/dist/systems/door/door-system.js +55 -17
  200. package/dist/systems/elevator/elevator-interaction-system.d.ts +7 -0
  201. package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -1
  202. package/dist/systems/elevator/elevator-interaction-system.js +18 -1
  203. package/dist/systems/floor-elevation/floor-elevation-system.d.ts +3 -1
  204. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
  205. package/dist/systems/floor-elevation/floor-elevation-system.js +33 -5
  206. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  207. package/dist/systems/geometry/geometry-system.js +71 -11
  208. package/dist/systems/interactive/control-widget.d.ts.map +1 -1
  209. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  210. package/dist/systems/interactive/interactive-system.js +52 -17
  211. package/dist/systems/interactive/procedural-controls.d.ts +13 -0
  212. package/dist/systems/interactive/procedural-controls.d.ts.map +1 -0
  213. package/dist/systems/interactive/procedural-controls.js +17 -0
  214. package/dist/systems/interactive/scripted-clips.d.ts +19 -0
  215. package/dist/systems/interactive/scripted-clips.d.ts.map +1 -0
  216. package/dist/systems/interactive/scripted-clips.js +134 -0
  217. package/dist/systems/item/item-system.d.ts.map +1 -1
  218. package/dist/systems/item/item-system.js +9 -3
  219. package/dist/systems/item/object-cut-invalidation.d.ts +3 -0
  220. package/dist/systems/item/object-cut-invalidation.d.ts.map +1 -0
  221. package/dist/systems/item/object-cut-invalidation.js +68 -0
  222. package/dist/systems/item-light/item-light-system.d.ts +1 -1
  223. package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
  224. package/dist/systems/item-light/item-light-system.js +48 -53
  225. package/dist/systems/level/level-hold.d.ts +4 -0
  226. package/dist/systems/level/level-hold.d.ts.map +1 -0
  227. package/dist/systems/level/level-hold.js +14 -0
  228. package/dist/systems/level/level-system.d.ts.map +1 -1
  229. package/dist/systems/level/level-system.js +19 -6
  230. package/dist/systems/level/level-utils.d.ts +19 -6
  231. package/dist/systems/level/level-utils.d.ts.map +1 -1
  232. package/dist/systems/level/level-utils.js +68 -9
  233. package/dist/systems/level/shadow-only-levels.d.ts +9 -0
  234. package/dist/systems/level/shadow-only-levels.d.ts.map +1 -0
  235. package/dist/systems/level/shadow-only-levels.js +7 -0
  236. package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts +103 -0
  237. package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts.map +1 -0
  238. package/dist/systems/roof/__fixtures__/roof-plane-cases.js +317 -0
  239. package/dist/systems/roof/roof-materials.d.ts +16 -2
  240. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  241. package/dist/systems/roof/roof-materials.js +64 -31
  242. package/dist/systems/roof/roof-system.d.ts +21 -4
  243. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  244. package/dist/systems/roof/roof-system.js +374 -49
  245. package/dist/systems/slab/slab-system.d.ts +1 -1
  246. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  247. package/dist/systems/slab/slab-system.js +49 -19
  248. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  249. package/dist/systems/stair/stair-system.js +133 -354
  250. package/dist/systems/wall/wall-cutout-cache.d.ts +39 -3
  251. package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -1
  252. package/dist/systems/wall/wall-cutout-cache.js +141 -19
  253. package/dist/systems/wall/wall-cutout.d.ts +6 -0
  254. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  255. package/dist/systems/wall/wall-cutout.js +42 -28
  256. package/dist/systems/wall/wall-finish-data.d.ts +22 -0
  257. package/dist/systems/wall/wall-finish-data.d.ts.map +1 -0
  258. package/dist/systems/wall/wall-finish-data.js +17 -0
  259. package/dist/systems/wall/wall-material-variant.d.ts.map +1 -1
  260. package/dist/systems/wall/wall-materials.d.ts +9 -4
  261. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  262. package/dist/systems/wall/wall-materials.js +135 -153
  263. package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -1
  264. package/dist/systems/wall/wall-system.d.ts +33 -2
  265. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  266. package/dist/systems/wall/wall-system.js +621 -159
  267. package/dist/systems/window/corner-frame.d.ts +10 -0
  268. package/dist/systems/window/corner-frame.d.ts.map +1 -0
  269. package/dist/systems/window/corner-frame.js +17 -0
  270. package/dist/systems/window/window-system.d.ts +1 -1
  271. package/dist/systems/window/window-system.d.ts.map +1 -1
  272. package/dist/systems/window/window-system.js +71 -27
  273. package/package.json +10 -10
  274. package/dist/systems/fence/fence-system.d.ts +0 -22
  275. package/dist/systems/fence/fence-system.d.ts.map +0 -1
  276. package/dist/systems/fence/fence-system.js +0 -357
@@ -1,14 +1,14 @@
1
- import { Color, Matrix4, UnsignedByteType } from 'three';
1
+ import { Color, Matrix4, SRGBColorSpace, UnsignedByteType } from 'three';
2
2
  import { ssgi } from 'three/addons/tsl/display/SSGINode.js';
3
3
  import { denoise } from 'three/examples/jsm/tsl/display/DenoiseNode.js';
4
4
  import { fxaa } from 'three/examples/jsm/tsl/display/FXAANode.js';
5
- import { convertToTexture, diffuseColor, float, mix, mrt, normalView, output, sample, saturation, screenUV, smoothstep, uniform, vec3, vec4, } from 'three/tsl';
5
+ import { clamp, convertToTexture, diffuseColor, float, mix, normalView, output, renderOutput, sample, saturation, screenUV, smoothstep, uniform, vec2, vec3, vec4, } from 'three/tsl';
6
6
  import { PassNode, RenderPipeline, RenderTarget, } from 'three/webgpu';
7
7
  import { GRADE_PARAMS, SSGI_PARAMS } from '../components/viewer/post-processing';
8
- import { backdropGradient, deepSkyColor, horizonHazeColor } from './backdrop';
8
+ import { backdropColors, backdropGradient, flatBackdropOutput } from './backdrop';
9
9
  import { edgeColorFor, edgeOpacityScaleFor } from './edge-style';
10
- import { inkedEdges } from './ink-edges';
11
- import { getSceneTheme } from './scene-themes';
10
+ import { createEdgeDepthSampler, inkedEdges } from './ink-edges';
11
+ import { sceneLightingMask, scenePassMrt } from './scene-pass-mrt';
12
12
  import { packNormalToRGB, unpackRGBToNormal } from './tsl-compat';
13
13
  export const THUMBNAIL_WIDTH = 1920;
14
14
  export const THUMBNAIL_HEIGHT = 1080;
@@ -29,6 +29,11 @@ function clampSnapshotSize(width, height) {
29
29
  const scale = SNAPSHOT_MAX_EDGE / maxEdge;
30
30
  return { w: Math.round(width * scale), h: Math.round(height * scale) };
31
31
  }
32
+ function srgbHexToVec3(hex) {
33
+ const color = new Color(hex);
34
+ const { r, g, b } = color.getRGB({ r: 0, g: 0, b: 0 }, SRGBColorSpace);
35
+ return vec3(r, g, b);
36
+ }
32
37
  class SnapshotScenePass extends PassNode {
33
38
  updateBefore(frame) {
34
39
  const renderer = frame.renderer;
@@ -44,7 +49,7 @@ class SnapshotScenePass extends PassNode {
44
49
  }
45
50
  }
46
51
  }
47
- export async function createSnapshotPipeline({ renderer, scene, camera, atmosphere = null, }) {
52
+ export async function createSnapshotPipeline({ renderer, scene, camera, atmosphere = null, studioBackdrop, }) {
48
53
  try {
49
54
  if (renderer.init)
50
55
  await renderer.init();
@@ -59,7 +64,9 @@ export async function createSnapshotPipeline({ renderer, scene, camera, atmosphe
59
64
  const bgProjInvUniform = uniform(new Matrix4());
60
65
  const bgCamWorldUniform = uniform(new Matrix4());
61
66
  const bgMixUniform = uniform(1);
67
+ const flatBgMixUniform = uniform(0);
62
68
  const gradeMixUniform = uniform(0);
69
+ const aoMixUniform = uniform(1);
63
70
  const inkMixUniform = uniform(0);
64
71
  const inkColorUniform = uniform(new Color('#1a1d24'));
65
72
  const inkOpacityUniform = uniform(0.5);
@@ -67,20 +74,24 @@ export async function createSnapshotPipeline({ renderer, scene, camera, atmosphe
67
74
  // The scene pass owns MRT and its transparent clear, including when nested
68
75
  // inside FXAA's intermediate render target.
69
76
  const scenePass = new SnapshotScenePass('color', scene, camera);
70
- scenePass.setMRT(mrt({
77
+ scenePass.setMRT(scenePassMrt({
71
78
  output,
72
79
  diffuseColor,
73
80
  normal: packNormalToRGB(normalView),
81
+ lighting: sceneLightingMask(),
74
82
  }));
75
83
  const scenePassColor = scenePass.getTextureNode('output');
76
84
  const scenePassDepth = scenePass.getTextureNode('depth');
85
+ const sampleEdgeDepth = createEdgeDepthSampler(scenePassDepth, camera);
77
86
  const scenePassNormal = scenePass.getTextureNode('normal');
78
87
  scenePass.getTexture('diffuseColor').type = UnsignedByteType;
79
88
  scenePass.getTexture('normal').type = UnsignedByteType;
89
+ scenePass.getTexture('lighting').type = UnsignedByteType;
80
90
  const sceneNormal = sample((uv) => unpackRGBToNormal(scenePassNormal.sample(uv)));
81
91
  const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, camera);
82
- giPass.sliceCount.value = SSGI_PARAMS.sliceCount;
83
- giPass.stepCount.value = SSGI_PARAMS.stepCount;
92
+ // A saved still has no later frames to hide the viewport's sampling noise.
93
+ giPass.sliceCount.value = 3;
94
+ giPass.stepCount.value = 8;
84
95
  giPass.radius.value = SSGI_PARAMS.radius;
85
96
  giPass.expFactor.value = SSGI_PARAMS.expFactor;
86
97
  giPass.thickness.value = SSGI_PARAMS.thickness;
@@ -99,18 +110,18 @@ export async function createSnapshotPipeline({ renderer, scene, camera, atmosphe
99
110
  denoisePass.radius.value = 4;
100
111
  // Same far-field AO fade as the viewport pipeline — without it the
101
112
  // horizon picks up a visible AO line in captures.
102
- const aoFarFade = smoothstep(float(0.9994), float(0.9998), scenePassDepth.sample(screenUV).r);
113
+ const aoFarFade = smoothstep(float(0.9994), float(0.9998), sampleEdgeDepth(screenUV));
103
114
  const ao = mix(denoisePass.r, float(1), aoFarFade);
104
- const aoRgb = scenePassColor.rgb.mul(ao);
115
+ const aoRgb = scenePassColor.rgb.mul(mix(float(1), ao, scenePass.getTextureNode('lighting').r.mul(aoMixUniform)));
105
116
  // Ink edges, mirroring the viewport pipeline (AO → ink → grade) so
106
117
  // captures carry the same soft/strong edge look the canvas shows.
107
118
  // Uniform-gated like grade/backdrop: one cached pipeline serves all modes.
108
119
  // Radius scales with render height: a supersampled capture (4K → 1080p)
109
120
  // would otherwise halve the apparent line weight vs the viewport's 1px.
110
- const inkRadius = Math.max(1, Math.round(renderer.domElement.height / 1080));
121
+ const inkRadius = uniform(1);
111
122
  const inkedRgb = inkedEdges({
112
123
  sceneRgb: aoRgb,
113
- depthTex: scenePassDepth,
124
+ sampleDepth: sampleEdgeDepth,
114
125
  normalTex: scenePassNormal,
115
126
  inkColor: inkColorUniform,
116
127
  radius: inkRadius,
@@ -137,28 +148,63 @@ export async function createSnapshotPipeline({ renderer, scene, camera, atmosphe
137
148
  const bgGradient = mix(ungradedBgGradient, gradeRgb(ungradedBgGradient), gradeMixUniform);
138
149
  const alpha = scenePassColor.a;
139
150
  const finalOutput = vec4(mix(sceneRgb, mix(bgGradient, sceneRgb, alpha), bgMixUniform), mix(alpha, float(1), bgMixUniform));
140
- // FXAA requires a texture node as input; convertToTexture renders finalOutput
141
- // into an intermediate RT so FXAA can sample it with neighbour UV offsets.
142
- const aaOutput = fxaa(convertToTexture(finalOutput));
143
151
  const pipeline = new RenderPipeline(renderer);
144
- pipeline.outputNode = aaOutput;
152
+ if (studioBackdrop) {
153
+ // Composite in display space: the scene gets the renderer's own output
154
+ // transform (tone mapping + sRGB), the backdrop is already sRGB — so
155
+ // the pipeline's final transform is off.
156
+ const { width, height } = renderer.domElement;
157
+ const aspect = width / height;
158
+ const [cx, cy] = studioBackdrop.center;
159
+ const radius = Math.max(...[
160
+ [0, 0],
161
+ [1, 0],
162
+ [0, 1],
163
+ [1, 1],
164
+ ].map(([x, y]) => Math.hypot((x - cx) * aspect, y - cy)));
165
+ const offset = screenUV.sub(vec2(cx, cy)).mul(vec2(aspect, 1));
166
+ const t = smoothstep(float(0), float(1), clamp(offset.length().div(radius), 0, 1));
167
+ const backdrop = mix(srgbHexToVec3(studioBackdrop.inner), srgbHexToVec3(studioBackdrop.outer), t);
168
+ const sceneDisplay = renderOutput(vec4(sceneRgb, 1), renderer.toneMapping, renderer.outputColorSpace).rgb;
169
+ pipeline.outputColorTransform = false;
170
+ pipeline.outputNode = fxaa(convertToTexture(vec4(mix(backdrop, sceneDisplay, alpha), 1)));
171
+ }
172
+ else {
173
+ // FXAA requires a texture node as input; convertToTexture renders finalOutput
174
+ // into an intermediate RT so FXAA can sample it with neighbour UV offsets.
175
+ const flatOutput = flatBackdropOutput({
176
+ background: bgColorUniform,
177
+ foreground: sceneRgb,
178
+ alpha,
179
+ toneMapping: renderer.toneMapping,
180
+ outputColorSpace: renderer.outputColorSpace,
181
+ });
182
+ const antialiased = fxaa(convertToTexture(mix(finalOutput, flatOutput, flatBgMixUniform)));
183
+ // One FXAA pass serves both inputs; transform only the linear input at output.
184
+ pipeline.outputColorTransform = false;
185
+ pipeline.outputNode = mix(renderOutput(antialiased, renderer.toneMapping, renderer.outputColorSpace), antialiased, flatBgMixUniform);
186
+ }
145
187
  // Dedicated render target — pipeline outputs here instead of the canvas,
146
188
  // so R3F's main render loop can never overwrite our capture.
147
189
  const { width, height } = renderer.domElement;
148
190
  const renderTarget = new RenderTarget(width, height, { depthBuffer: true });
149
191
  return {
150
192
  applyEnvironment: ({ theme, transparent, grade, edges, camera: captureCamera }) => {
151
- const sceneTheme = getSceneTheme(theme);
193
+ const sceneTheme = theme;
194
+ const backdrop = backdropColors(sceneTheme);
152
195
  inkMixUniform.value = edges === 'off' ? 0 : 1;
153
196
  inkOpacityUniform.value = edges === 'strong' ? 1 : 0.5;
154
197
  inkColorUniform.value.set(edgeColorFor(sceneTheme.background));
155
198
  inkOpacityScaleUniform.value = edgeOpacityScaleFor(sceneTheme.background);
156
199
  bgColorUniform.value.set(sceneTheme.background);
157
- bgSkyUniform.value.set(sceneTheme.backgroundSky ?? sceneTheme.background);
158
- bgSkyDeepUniform.value.set(deepSkyColor(sceneTheme.backgroundSky ?? sceneTheme.background));
159
- bgHazeUniform.value.set(horizonHazeColor(sceneTheme.backgroundSky ?? sceneTheme.background, sceneTheme.appearance));
200
+ bgSkyUniform.value.set(backdrop.sky);
201
+ bgSkyDeepUniform.value.set(backdrop.skyDeep);
202
+ bgHazeUniform.value.set(backdrop.haze);
160
203
  bgMixUniform.value = transparent ? 0 : 1;
161
- gradeMixUniform.value = grade ? 1 : 0;
204
+ flatBgMixUniform.value =
205
+ !transparent && !atmosphere && sceneTheme.backgroundSky === null ? 1 : 0;
206
+ gradeMixUniform.value = grade && sceneTheme.postprocessing?.colorGrading !== false ? 1 : 0;
207
+ aoMixUniform.value = sceneTheme.postprocessing?.ambientOcclusion === false ? 0 : 1;
162
208
  // The capture camera never joins the scene graph, so its matrixWorld
163
209
  // is only refreshed by the render itself — too late for the backdrop
164
210
  // uniforms below.
@@ -166,10 +212,11 @@ export async function createSnapshotPipeline({ renderer, scene, camera, atmosphe
166
212
  bgProjInvUniform.value.copy(captureCamera.projectionMatrixInverse);
167
213
  bgCamWorldUniform.value.copy(captureCamera.matrixWorld);
168
214
  },
169
- capture: async ({ captureMode, cropRegion, standardSize }) => {
215
+ capture: async ({ captureMode, cropRegion, standardSize, mime }) => {
170
216
  const standardW = standardSize?.w ?? THUMBNAIL_WIDTH;
171
217
  const standardH = standardSize?.h ?? THUMBNAIL_HEIGHT;
172
218
  const { width: captureWidth, height: captureHeight } = renderer.domElement;
219
+ inkRadius.value = Math.max(1, Math.round(captureHeight / 1080));
173
220
  // Resize RT if the canvas dimensions changed
174
221
  if (renderTarget.width !== captureWidth || renderTarget.height !== captureHeight) {
175
222
  renderTarget.setSize(captureWidth, captureHeight);
@@ -184,100 +231,13 @@ export async function createSnapshotPipeline({ renderer, scene, camera, atmosphe
184
231
  // Let callers restore visibility and other capture policy immediately
185
232
  // after the render, before the asynchronous GPU readback begins.
186
233
  await Promise.resolve();
187
- // Read pixels from the RT asynchronously.
188
- // WebGPU copyTextureToBuffer aligns each row to 256 bytes, so we must
189
- // depad the rows before constructing ImageData.
190
- const pixels = (await renderer.readRenderTargetPixelsAsync(renderTarget, 0, 0, captureWidth, captureHeight));
191
- const actualBytesPerRow = captureWidth * 4;
192
- const tightTotal = actualBytesPerRow * captureHeight;
193
- const paddedBytesPerRow = Math.ceil(actualBytesPerRow / 256) * 256;
194
- // Two readback shapes to handle:
195
- // - WebGPU (`copyTextureToBuffer`): top-down + 256-byte row padding
196
- // when width*4 isn't already a multiple of 256.
197
- // - WebGL2 fallback (iOS Chrome, etc.): tightly-packed but bottom-up
198
- // (OpenGL framebuffer convention).
199
- // `isWebGPURenderer` lies — it stays true even when the renderer
200
- // falls back to the WebGL backend. Inspect the actual backend
201
- // instead (presence of a GPU device, or backend constructor name).
202
- const backend = renderer.backend;
203
- const isWebGPU = !!backend?.device ||
204
- backend?.isWebGPUBackend === true ||
205
- backend?.constructor?.name === 'WebGPUBackend';
206
- let tightPixels;
207
- if (isWebGPU) {
208
- // WebGPU: depad rows if needed; orientation is already top-down.
209
- if (paddedBytesPerRow === actualBytesPerRow) {
210
- tightPixels = new Uint8ClampedArray(pixels.buffer, pixels.byteOffset, Math.min(pixels.byteLength, tightTotal));
211
- }
212
- else {
213
- tightPixels = new Uint8ClampedArray(tightTotal);
214
- for (let row = 0; row < captureHeight; row++) {
215
- tightPixels.set(pixels.subarray(row * paddedBytesPerRow, row * paddedBytesPerRow + actualBytesPerRow), row * actualBytesPerRow);
216
- }
217
- }
218
- }
219
- else {
220
- // WebGL2: tight buffer in bottom-up order — flip rows.
221
- tightPixels = new Uint8ClampedArray(tightTotal);
222
- for (let row = 0; row < captureHeight; row++) {
223
- const srcStart = (captureHeight - 1 - row) * actualBytesPerRow;
224
- tightPixels.set(pixels.subarray(srcStart, srcStart + actualBytesPerRow), row * actualBytesPerRow);
225
- }
226
- }
227
- const imageData = new ImageData(tightPixels, captureWidth, captureHeight);
228
- const srcCanvas = new OffscreenCanvas(captureWidth, captureHeight);
229
- srcCanvas.getContext('2d').putImageData(imageData, 0, 0);
230
- let outW;
231
- let outH;
232
- let blob;
233
- if (captureMode === 'viewport') {
234
- ;
235
- ({ w: outW, h: outH } = clampSnapshotSize(captureWidth, captureHeight));
236
- const offscreen = new OffscreenCanvas(outW, outH);
237
- const ctx = offscreen.getContext('2d');
238
- if (outW !== captureWidth || outH !== captureHeight)
239
- ctx.imageSmoothingQuality = 'high';
240
- ctx.drawImage(srcCanvas, 0, 0, captureWidth, captureHeight, 0, 0, outW, outH);
241
- blob = await offscreen.convertToBlob({ type: SNAPSHOT_MIME, quality: SNAPSHOT_QUALITY });
242
- }
243
- else if (captureMode === 'area' && cropRegion) {
244
- const sx = Math.round(cropRegion.x * captureWidth);
245
- const sy = Math.round(cropRegion.y * captureHeight);
246
- const sourceW = Math.round(cropRegion.width * captureWidth);
247
- const sourceH = Math.round(cropRegion.height * captureHeight);
248
- ({ w: outW, h: outH } = clampSnapshotSize(sourceW, sourceH));
249
- const offscreen = new OffscreenCanvas(outW, outH);
250
- const ctx = offscreen.getContext('2d');
251
- if (outW !== sourceW || outH !== sourceH)
252
- ctx.imageSmoothingQuality = 'high';
253
- ctx.drawImage(srcCanvas, sx, sy, sourceW, sourceH, 0, 0, outW, outH);
254
- blob = await offscreen.convertToBlob({ type: SNAPSHOT_MIME, quality: SNAPSHOT_QUALITY });
255
- }
256
- else {
257
- // Standard: center-crop to the requested aspect (default 1920×1080)
258
- const srcAspect = captureWidth / captureHeight;
259
- const dstAspect = standardW / standardH;
260
- let sx = 0;
261
- let sy = 0;
262
- let sWidth = captureWidth;
263
- let sHeight = captureHeight;
264
- if (srcAspect > dstAspect) {
265
- sWidth = Math.round(captureHeight * dstAspect);
266
- sx = Math.round((captureWidth - sWidth) / 2);
267
- }
268
- else if (srcAspect < dstAspect) {
269
- sHeight = Math.round(captureWidth / dstAspect);
270
- sy = Math.round((captureHeight - sHeight) / 2);
271
- }
272
- outW = standardW;
273
- outH = standardH;
274
- const offscreen = new OffscreenCanvas(outW, outH);
275
- offscreen
276
- .getContext('2d')
277
- .drawImage(srcCanvas, sx, sy, sWidth, sHeight, 0, 0, outW, outH);
278
- blob = await offscreen.convertToBlob({ type: SNAPSHOT_MIME, quality: SNAPSHOT_QUALITY });
279
- }
280
- return { blob, outW, outH };
234
+ return encodeCapture(renderer, renderTarget, captureWidth, captureHeight, {
235
+ captureMode,
236
+ cropRegion,
237
+ standardW,
238
+ standardH,
239
+ mime,
240
+ });
281
241
  },
282
242
  dispose: () => {
283
243
  pipeline.dispose();
@@ -292,3 +252,173 @@ export async function createSnapshotPipeline({ renderer, scene, camera, atmosphe
292
252
  return null;
293
253
  }
294
254
  }
255
+ /**
256
+ * The frame in `renderTarget`, read back and encoded: WebGPU's 256-byte row
257
+ * padding undone (the WebGL fallback's bottom-up rows flipped), then cut to
258
+ * the capture mode — the viewport as is, an area, or the standard aspect.
259
+ * Shared by the post-processed pipeline and the plain one.
260
+ */
261
+ async function encodeCapture(renderer, renderTarget, captureWidth, captureHeight, { captureMode, cropRegion, standardW, standardH, mime, }) {
262
+ const encoding = mime ? { type: mime } : { type: SNAPSHOT_MIME, quality: SNAPSHOT_QUALITY };
263
+ // Read pixels from the RT asynchronously.
264
+ // WebGPU copyTextureToBuffer aligns each row to 256 bytes, so we must
265
+ // depad the rows before constructing ImageData.
266
+ const pixels = (await renderer.readRenderTargetPixelsAsync(renderTarget, 0, 0, captureWidth, captureHeight));
267
+ const actualBytesPerRow = captureWidth * 4;
268
+ const tightTotal = actualBytesPerRow * captureHeight;
269
+ const paddedBytesPerRow = Math.ceil(actualBytesPerRow / 256) * 256;
270
+ // Two readback shapes to handle:
271
+ // - WebGPU (`copyTextureToBuffer`): top-down + 256-byte row padding
272
+ // when width*4 isn't already a multiple of 256.
273
+ // - WebGL2 fallback (iOS Chrome, etc.): tightly-packed but bottom-up
274
+ // (OpenGL framebuffer convention).
275
+ // `isWebGPURenderer` lies — it stays true even when the renderer
276
+ // falls back to the WebGL backend. Inspect the actual backend
277
+ // instead (presence of a GPU device, or backend constructor name).
278
+ const backend = renderer.backend;
279
+ const isWebGPU = !!backend?.device ||
280
+ backend?.isWebGPUBackend === true ||
281
+ backend?.constructor?.name === 'WebGPUBackend';
282
+ let tightPixels;
283
+ if (isWebGPU) {
284
+ // WebGPU: depad rows if needed; orientation is already top-down.
285
+ if (paddedBytesPerRow === actualBytesPerRow) {
286
+ tightPixels = new Uint8ClampedArray(pixels.buffer, pixels.byteOffset, Math.min(pixels.byteLength, tightTotal));
287
+ }
288
+ else {
289
+ tightPixels = new Uint8ClampedArray(tightTotal);
290
+ for (let row = 0; row < captureHeight; row++) {
291
+ tightPixels.set(pixels.subarray(row * paddedBytesPerRow, row * paddedBytesPerRow + actualBytesPerRow), row * actualBytesPerRow);
292
+ }
293
+ }
294
+ }
295
+ else {
296
+ // WebGL2: tight buffer in bottom-up order — flip rows.
297
+ tightPixels = new Uint8ClampedArray(tightTotal);
298
+ for (let row = 0; row < captureHeight; row++) {
299
+ const srcStart = (captureHeight - 1 - row) * actualBytesPerRow;
300
+ tightPixels.set(pixels.subarray(srcStart, srcStart + actualBytesPerRow), row * actualBytesPerRow);
301
+ }
302
+ }
303
+ const imageData = new ImageData(tightPixels, captureWidth, captureHeight);
304
+ const srcCanvas = new OffscreenCanvas(captureWidth, captureHeight);
305
+ srcCanvas.getContext('2d').putImageData(imageData, 0, 0);
306
+ let outW;
307
+ let outH;
308
+ let blob;
309
+ if (captureMode === 'viewport') {
310
+ ;
311
+ ({ w: outW, h: outH } = clampSnapshotSize(captureWidth, captureHeight));
312
+ const offscreen = new OffscreenCanvas(outW, outH);
313
+ const ctx = offscreen.getContext('2d');
314
+ if (outW !== captureWidth || outH !== captureHeight)
315
+ ctx.imageSmoothingQuality = 'high';
316
+ ctx.drawImage(srcCanvas, 0, 0, captureWidth, captureHeight, 0, 0, outW, outH);
317
+ blob = await offscreen.convertToBlob(encoding);
318
+ }
319
+ else if (captureMode === 'area' && cropRegion) {
320
+ const sx = Math.round(cropRegion.x * captureWidth);
321
+ const sy = Math.round(cropRegion.y * captureHeight);
322
+ const sourceW = Math.round(cropRegion.width * captureWidth);
323
+ const sourceH = Math.round(cropRegion.height * captureHeight);
324
+ ({ w: outW, h: outH } = clampSnapshotSize(sourceW, sourceH));
325
+ const offscreen = new OffscreenCanvas(outW, outH);
326
+ const ctx = offscreen.getContext('2d');
327
+ if (outW !== sourceW || outH !== sourceH)
328
+ ctx.imageSmoothingQuality = 'high';
329
+ ctx.drawImage(srcCanvas, sx, sy, sourceW, sourceH, 0, 0, outW, outH);
330
+ blob = await offscreen.convertToBlob(encoding);
331
+ }
332
+ else {
333
+ // Standard: center-crop to the requested aspect (default 1920×1080)
334
+ const srcAspect = captureWidth / captureHeight;
335
+ const dstAspect = standardW / standardH;
336
+ let sx = 0;
337
+ let sy = 0;
338
+ let sWidth = captureWidth;
339
+ let sHeight = captureHeight;
340
+ if (srcAspect > dstAspect) {
341
+ sWidth = Math.round(captureHeight * dstAspect);
342
+ sx = Math.round((captureWidth - sWidth) / 2);
343
+ }
344
+ else if (srcAspect < dstAspect) {
345
+ sHeight = Math.round(captureWidth / dstAspect);
346
+ sy = Math.round((captureHeight - sHeight) / 2);
347
+ }
348
+ outW = standardW;
349
+ outH = standardH;
350
+ const offscreen = new OffscreenCanvas(outW, outH);
351
+ offscreen.getContext('2d').drawImage(srcCanvas, sx, sy, sWidth, sHeight, 0, 0, outW, outH);
352
+ blob = await offscreen.convertToBlob(encoding);
353
+ }
354
+ return { blob, outW, outH };
355
+ }
356
+ /**
357
+ * A capture WITHOUT the post-processing stack: the scene as the renderer
358
+ * draws it, into an offscreen target, read back and encoded like the
359
+ * post-processed frames. The sheets' orthographic elevation captures use
360
+ * it (2026-09-10): an SSGI pass built for a second, orthographic camera
361
+ * never delivered a frame and held the generator's guard forever.
362
+ */
363
+ export async function createPlainSnapshotPipeline({ renderer, scene, camera, }) {
364
+ try {
365
+ if (renderer.init)
366
+ await renderer.init();
367
+ const { width, height } = renderer.domElement;
368
+ const renderTarget = new RenderTarget(width, height, { depthBuffer: true });
369
+ let transparent = true;
370
+ const background = new Color('#ffffff');
371
+ return {
372
+ applyEnvironment: ({ theme, transparent: alpha }) => {
373
+ transparent = alpha;
374
+ background.set(theme.background);
375
+ },
376
+ capture: async ({ captureMode, cropRegion, standardSize }) => {
377
+ const standardW = standardSize?.w ?? THUMBNAIL_WIDTH;
378
+ const standardH = standardSize?.h ?? THUMBNAIL_HEIGHT;
379
+ const { width: captureWidth, height: captureHeight } = renderer.domElement;
380
+ if (renderTarget.width !== captureWidth || renderTarget.height !== captureHeight) {
381
+ renderTarget.setSize(captureWidth, captureHeight);
382
+ }
383
+ const previousBackground = scene.background;
384
+ const previousClear = renderer.getClearColor(new Color());
385
+ const previousAlpha = renderer.getClearAlpha();
386
+ try {
387
+ scene.background = null;
388
+ renderer.setClearColor(background, transparent ? 0 : 1);
389
+ renderer.setRenderTarget(renderTarget);
390
+ // outside the frame loop the async render is the one that submits
391
+ // its work and settles once the GPU took it
392
+ const asyncRender = renderer.renderAsync;
393
+ if (asyncRender)
394
+ await asyncRender.call(renderer, scene, camera);
395
+ else
396
+ renderer.render(scene, camera);
397
+ }
398
+ finally {
399
+ renderer.setRenderTarget(null);
400
+ scene.background = previousBackground;
401
+ renderer.setClearColor(previousClear, previousAlpha);
402
+ }
403
+ await Promise.resolve();
404
+ // a readback that never returns must not hold a caller for the session
405
+ return Promise.race([
406
+ encodeCapture(renderer, renderTarget, captureWidth, captureHeight, {
407
+ captureMode,
408
+ cropRegion,
409
+ standardW,
410
+ standardH,
411
+ }),
412
+ new Promise((_, reject) => setTimeout(() => reject(new Error('the plain capture readback did not return within 12 s')), 12000)),
413
+ ]);
414
+ },
415
+ dispose: () => {
416
+ renderTarget.dispose();
417
+ },
418
+ };
419
+ }
420
+ catch (error) {
421
+ console.error('[thumbnail] Failed to build the plain capture pipeline.', error);
422
+ return null;
423
+ }
424
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"terrain-perimeter-fill.d.ts","sourceRoot":"","sources":["../../src/lib/terrain-perimeter-fill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA+C,MAAM,OAAO,CAAA;AAGnF,MAAM,MAAM,qBAAqB,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5D,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,SAAS,qBAAqB,EAAE,EACxC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,SAAO,GACb,cAAc,GAAG,IAAI,CA2EvB"}
1
+ {"version":3,"file":"terrain-perimeter-fill.d.ts","sourceRoot":"","sources":["../../src/lib/terrain-perimeter-fill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA+C,MAAM,OAAO,CAAA;AAGnF,MAAM,MAAM,qBAAqB,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5D,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,SAAS,qBAAqB,EAAE,EACxC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,SAAO,GACb,cAAc,GAAG,IAAI,CAkFvB"}
@@ -13,32 +13,39 @@ export function buildTerrainPerimeterFillGeometry(points, bottomY, topY, epsilon
13
13
  }
14
14
  const counterClockwise = signedArea > 0;
15
15
  const positions = [];
16
- const push = (point, y) => {
16
+ const uvs = [];
17
+ const push = (point, y, u = point.x, v = point.z) => {
17
18
  positions.push(point.x, y, point.z);
19
+ uvs.push(u, v);
18
20
  };
21
+ let perimeter = 0;
19
22
  for (let index = 0; index < points.length; index += 1) {
20
23
  const next = (index + 1) % points.length;
21
24
  const a = points[index];
22
25
  const b = points[next];
23
26
  const ay = bottomY[index];
24
27
  const by = bottomY[next];
25
- if (ay >= topY - epsilon && by >= topY - epsilon)
28
+ const length = Math.hypot(b.x - a.x, b.z - a.z);
29
+ const ua = perimeter;
30
+ perimeter += length;
31
+ const ub = perimeter;
32
+ if (length < epsilon || (ay >= topY - epsilon && by >= topY - epsilon))
26
33
  continue;
27
34
  if (counterClockwise) {
28
- push(a, topY);
29
- push(b, by);
30
- push(a, ay);
31
- push(a, topY);
32
- push(b, topY);
33
- push(b, by);
35
+ push(a, topY, ua, topY);
36
+ push(b, by, ub, by);
37
+ push(a, ay, ua, ay);
38
+ push(a, topY, ua, topY);
39
+ push(b, topY, ub, topY);
40
+ push(b, by, ub, by);
34
41
  }
35
42
  else {
36
- push(a, topY);
37
- push(a, ay);
38
- push(b, by);
39
- push(a, topY);
40
- push(b, by);
41
- push(b, topY);
43
+ push(a, topY, ua, topY);
44
+ push(a, ay, ua, ay);
45
+ push(b, by, ub, by);
46
+ push(a, topY, ua, topY);
47
+ push(b, by, ub, by);
48
+ push(b, topY, ub, topY);
42
49
  }
43
50
  }
44
51
  const faces = ShapeUtils.triangulateShape(points.map((point) => new Vector2(point.x, point.z)), []);
@@ -69,6 +76,7 @@ export function buildTerrainPerimeterFillGeometry(points, bottomY, topY, epsilon
69
76
  return null;
70
77
  const geometry = new BufferGeometry();
71
78
  geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
79
+ geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
72
80
  geometry.computeVertexNormals();
73
81
  ensureRenderableGeometryAttributes(geometry);
74
82
  return geometry;
@@ -7,6 +7,6 @@
7
7
  * @types/three pin) once tsgo handles the 0.185 types; the `typeof` references
8
8
  * to the removed old names will fail the build as a reminder.
9
9
  */
10
- export declare const packNormalToRGB: (node: import("three/webgpu").Node) => import("three/webgpu").Node<"vec3">;
11
- export declare const unpackRGBToNormal: (node: import("three/webgpu").Node) => import("three/webgpu").Node<"vec3">;
10
+ export declare const packNormalToRGB: (node: import("three/webgpu").Node<"vec3"> | import("three").Vector3) => import("three/webgpu").Node<"vec3">;
11
+ export declare const unpackRGBToNormal: (node: import("three/webgpu").Node<"vec3"> | import("three/webgpu").Node<"vec4"> | import("three").Vector3) => import("three/webgpu").Node<"vec3">;
12
12
  //# sourceMappingURL=tsl-compat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tsl-compat.d.ts","sourceRoot":"","sources":["../../src/lib/tsl-compat.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,4EACV,CAAA;AAClB,eAAO,MAAM,iBAAiB,4EAEX,CAAA"}
1
+ {"version":3,"file":"tsl-compat.d.ts","sourceRoot":"","sources":["../../src/lib/tsl-compat.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,8GACV,CAAA;AAClB,eAAO,MAAM,iBAAiB,oJAEX,CAAA"}