@3driseai/3drise-viewer 0.1.6 → 0.1.8

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 (277) hide show
  1. package/dist/components/Clouds.d.ts.map +1 -1
  2. package/dist/components/Clouds.js +0 -1
  3. package/dist/components/CloudsGenerator.d.ts +32 -0
  4. package/dist/components/CloudsGenerator.d.ts.map +1 -0
  5. package/dist/components/CloudsGenerator.js +58 -0
  6. package/dist/components/CustomPrimitive.js +1 -1
  7. package/dist/components/EnvironmentManager.d.ts +47 -5
  8. package/dist/components/EnvironmentManager.d.ts.map +1 -1
  9. package/dist/components/EnvironmentManager.js +35 -66
  10. package/dist/components/GeometryShape.d.ts.map +1 -1
  11. package/dist/components/GeometryShape.js +19 -3
  12. package/dist/components/HdrEnvironment.d.ts +20 -0
  13. package/dist/components/HdrEnvironment.d.ts.map +1 -0
  14. package/dist/components/HdrEnvironment.js +44 -0
  15. package/dist/components/LandscapeBuilder.d.ts +23 -0
  16. package/dist/components/LandscapeBuilder.d.ts.map +1 -0
  17. package/dist/components/LandscapeBuilder.js +25 -0
  18. package/dist/components/LandscapeObject.d.ts +24 -0
  19. package/dist/components/LandscapeObject.d.ts.map +1 -0
  20. package/dist/components/LandscapeObject.js +27 -0
  21. package/dist/components/ObjectNode.d.ts.map +1 -1
  22. package/dist/components/ObjectNode.js +11 -9
  23. package/dist/components/OceanBuilder.d.ts +35 -0
  24. package/dist/components/OceanBuilder.d.ts.map +1 -0
  25. package/dist/components/OceanBuilder.js +53 -0
  26. package/dist/components/ParticlesR3f.d.ts.map +1 -1
  27. package/dist/components/ParticlesR3f.js +63 -12
  28. package/dist/components/PointCloud.d.ts.map +1 -1
  29. package/dist/components/PointCloud.js +50 -10
  30. package/dist/components/SceneBuilder.d.ts.map +1 -1
  31. package/dist/components/SkySystem.d.ts +27 -6
  32. package/dist/components/SkySystem.d.ts.map +1 -1
  33. package/dist/components/SkySystem.js +6 -5
  34. package/dist/components/SunLight.d.ts.map +1 -1
  35. package/dist/components/SunLight.js +4 -1
  36. package/dist/components/TerrainBuilder.d.ts +30 -0
  37. package/dist/components/TerrainBuilder.d.ts.map +1 -0
  38. package/dist/components/TerrainBuilder.js +44 -0
  39. package/dist/components/index.d.ts +17 -1
  40. package/dist/components/index.d.ts.map +1 -1
  41. package/dist/components/index.js +10 -0
  42. package/dist/components/landscape/LandscapeMesh.d.ts +18 -0
  43. package/dist/components/landscape/LandscapeMesh.d.ts.map +1 -0
  44. package/dist/components/landscape/LandscapeMesh.js +278 -0
  45. package/dist/components/landscape/index.d.ts +3 -0
  46. package/dist/components/landscape/index.d.ts.map +1 -0
  47. package/dist/components/landscape/index.js +1 -0
  48. package/dist/components/landscape/types.d.ts +13 -0
  49. package/dist/components/landscape/types.d.ts.map +1 -0
  50. package/dist/components/landscape/types.js +1 -0
  51. package/dist/components/terrain/Canyon.d.ts +23 -0
  52. package/dist/components/terrain/Canyon.d.ts.map +1 -0
  53. package/dist/components/terrain/Canyon.js +29 -0
  54. package/dist/components/terrain/Dunes.d.ts +22 -0
  55. package/dist/components/terrain/Dunes.d.ts.map +1 -0
  56. package/dist/components/terrain/Dunes.js +25 -0
  57. package/dist/components/terrain/Hills.d.ts +17 -0
  58. package/dist/components/terrain/Hills.d.ts.map +1 -0
  59. package/dist/components/terrain/Hills.js +20 -0
  60. package/dist/components/terrain/Mountains.d.ts +19 -0
  61. package/dist/components/terrain/Mountains.d.ts.map +1 -0
  62. package/dist/components/terrain/Mountains.js +22 -0
  63. package/dist/components/terrain/TerrainSurface.d.ts +22 -0
  64. package/dist/components/terrain/TerrainSurface.d.ts.map +1 -0
  65. package/dist/components/terrain/TerrainSurface.js +94 -0
  66. package/dist/components/terrain/index.d.ts +7 -0
  67. package/dist/components/terrain/index.d.ts.map +1 -0
  68. package/dist/components/terrain/index.js +7 -0
  69. package/dist/components/terrain/types.d.ts +17 -0
  70. package/dist/components/terrain/types.d.ts.map +1 -0
  71. package/dist/components/terrain/types.js +1 -0
  72. package/dist/components/volumetricClouds/Cirrus.d.ts +14 -0
  73. package/dist/components/volumetricClouds/Cirrus.d.ts.map +1 -0
  74. package/dist/components/volumetricClouds/Cirrus.js +13 -0
  75. package/dist/components/volumetricClouds/Cumulonimbus.d.ts +17 -0
  76. package/dist/components/volumetricClouds/Cumulonimbus.d.ts.map +1 -0
  77. package/dist/components/volumetricClouds/Cumulonimbus.js +20 -0
  78. package/dist/components/volumetricClouds/Cumulus.d.ts +14 -0
  79. package/dist/components/volumetricClouds/Cumulus.d.ts.map +1 -0
  80. package/dist/components/volumetricClouds/Cumulus.js +17 -0
  81. package/dist/components/volumetricClouds/Stratocumulus.d.ts +12 -0
  82. package/dist/components/volumetricClouds/Stratocumulus.d.ts.map +1 -0
  83. package/dist/components/volumetricClouds/Stratocumulus.js +16 -0
  84. package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts +42 -0
  85. package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts.map +1 -0
  86. package/dist/components/volumetricClouds/VolumetricCloudsPass.js +298 -0
  87. package/dist/components/volumetricClouds/index.d.ts +8 -0
  88. package/dist/components/volumetricClouds/index.d.ts.map +1 -0
  89. package/dist/components/volumetricClouds/index.js +7 -0
  90. package/dist/components/volumetricClouds/types.d.ts +17 -0
  91. package/dist/components/volumetricClouds/types.d.ts.map +1 -0
  92. package/dist/components/volumetricClouds/types.js +1 -0
  93. package/dist/components/water/Host.d.ts +12 -0
  94. package/dist/components/water/Host.d.ts.map +1 -0
  95. package/dist/components/water/Host.js +12 -0
  96. package/dist/components/water/Lake.d.ts +18 -0
  97. package/dist/components/water/Lake.d.ts.map +1 -0
  98. package/dist/components/water/Lake.js +21 -0
  99. package/dist/components/water/OceanSurface.d.ts +27 -0
  100. package/dist/components/water/OceanSurface.d.ts.map +1 -0
  101. package/dist/components/water/OceanSurface.js +60 -0
  102. package/dist/components/water/Sea.d.ts +18 -0
  103. package/dist/components/water/Sea.d.ts.map +1 -0
  104. package/dist/components/water/Sea.js +17 -0
  105. package/dist/components/water/Shore.d.ts +16 -0
  106. package/dist/components/water/Shore.d.ts.map +1 -0
  107. package/dist/components/water/Shore.js +15 -0
  108. package/dist/components/water/Storm.d.ts +14 -0
  109. package/dist/components/water/Storm.d.ts.map +1 -0
  110. package/dist/components/water/Storm.js +13 -0
  111. package/dist/components/water/WaterBody.d.ts +31 -0
  112. package/dist/components/water/WaterBody.d.ts.map +1 -0
  113. package/dist/components/water/WaterBody.js +76 -0
  114. package/dist/components/water/index.d.ts +9 -0
  115. package/dist/components/water/index.d.ts.map +1 -0
  116. package/dist/components/water/index.js +11 -0
  117. package/dist/components/water/types.d.ts +20 -0
  118. package/dist/components/water/types.d.ts.map +1 -0
  119. package/dist/components/water/types.js +1 -0
  120. package/dist/constants.d.ts +1 -0
  121. package/dist/constants.d.ts.map +1 -1
  122. package/dist/constants.js +15 -0
  123. package/dist/hooks/index.d.ts +24 -1
  124. package/dist/hooks/index.d.ts.map +1 -1
  125. package/dist/hooks/index.js +12 -0
  126. package/dist/hooks/useClipmap.d.ts +71 -0
  127. package/dist/hooks/useClipmap.d.ts.map +1 -0
  128. package/dist/hooks/useClipmap.js +81 -0
  129. package/dist/hooks/useCloudLive.d.ts +29 -0
  130. package/dist/hooks/useCloudLive.d.ts.map +1 -0
  131. package/dist/hooks/useCloudLive.js +43 -0
  132. package/dist/hooks/useCloudNoise.d.ts +28 -0
  133. package/dist/hooks/useCloudNoise.d.ts.map +1 -0
  134. package/dist/hooks/useCloudNoise.js +128 -0
  135. package/dist/hooks/useCloudSun.d.ts +49 -0
  136. package/dist/hooks/useCloudSun.d.ts.map +1 -0
  137. package/dist/hooks/useCloudSun.js +84 -0
  138. package/dist/hooks/useCubeCamera.d.ts +27 -9
  139. package/dist/hooks/useCubeCamera.d.ts.map +1 -1
  140. package/dist/hooks/useCubeCamera.js +28 -70
  141. package/dist/hooks/useEnvironmentProbe.d.ts +32 -0
  142. package/dist/hooks/useEnvironmentProbe.d.ts.map +1 -0
  143. package/dist/hooks/useEnvironmentProbe.js +35 -0
  144. package/dist/hooks/useFrameRenderer.d.ts +15 -0
  145. package/dist/hooks/useFrameRenderer.d.ts.map +1 -0
  146. package/dist/hooks/useFrameRenderer.js +23 -0
  147. package/dist/hooks/useLandscapeLive.d.ts +29 -0
  148. package/dist/hooks/useLandscapeLive.d.ts.map +1 -0
  149. package/dist/hooks/useLandscapeLive.js +41 -0
  150. package/dist/hooks/useLandscapeMaps.d.ts +32 -0
  151. package/dist/hooks/useLandscapeMaps.d.ts.map +1 -0
  152. package/dist/hooks/useLandscapeMaps.js +138 -0
  153. package/dist/hooks/useMaterialApplication.d.ts.map +1 -1
  154. package/dist/hooks/useMaterialApplication.js +2 -3
  155. package/dist/hooks/useMaterialOptions.d.ts +2 -2
  156. package/dist/hooks/useMaterialOptions.d.ts.map +1 -1
  157. package/dist/hooks/useMaterialOptions.js +13 -31
  158. package/dist/hooks/useObjectEdges.d.ts.map +1 -1
  159. package/dist/hooks/useObjectEdges.js +3 -49
  160. package/dist/hooks/useOceanLive.d.ts +29 -0
  161. package/dist/hooks/useOceanLive.d.ts.map +1 -0
  162. package/dist/hooks/useOceanLive.js +38 -0
  163. package/dist/hooks/useOceanMaterial.d.ts +39 -0
  164. package/dist/hooks/useOceanMaterial.d.ts.map +1 -0
  165. package/dist/hooks/useOceanMaterial.js +65 -0
  166. package/dist/hooks/useTerrainClipmap.d.ts +12 -0
  167. package/dist/hooks/useTerrainClipmap.d.ts.map +1 -0
  168. package/dist/hooks/useTerrainClipmap.js +10 -0
  169. package/dist/hooks/useTerrainLive.d.ts +34 -0
  170. package/dist/hooks/useTerrainLive.d.ts.map +1 -0
  171. package/dist/hooks/useTerrainLive.js +45 -0
  172. package/dist/index.d.ts +26 -0
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/index.js +16 -0
  175. package/dist/presets/landscapePresets.d.ts +60 -0
  176. package/dist/presets/landscapePresets.d.ts.map +1 -0
  177. package/dist/presets/landscapePresets.js +194 -0
  178. package/dist/presets/landscapeTypes.d.ts +195 -0
  179. package/dist/presets/landscapeTypes.d.ts.map +1 -0
  180. package/dist/presets/landscapeTypes.js +315 -0
  181. package/dist/presets/oceanSurfacePresets.d.ts +33 -0
  182. package/dist/presets/oceanSurfacePresets.d.ts.map +1 -0
  183. package/dist/presets/oceanSurfacePresets.js +245 -0
  184. package/dist/presets/terrainPresets.d.ts +28 -0
  185. package/dist/presets/terrainPresets.d.ts.map +1 -0
  186. package/dist/presets/terrainPresets.js +293 -0
  187. package/dist/presets/volumetricCloudPresets.d.ts +38 -0
  188. package/dist/presets/volumetricCloudPresets.d.ts.map +1 -0
  189. package/dist/presets/volumetricCloudPresets.js +480 -0
  190. package/dist/shaders/clouds/CirrusMaterial.d.ts +84 -0
  191. package/dist/shaders/clouds/CirrusMaterial.d.ts.map +1 -0
  192. package/dist/shaders/clouds/CirrusMaterial.js +167 -0
  193. package/dist/shaders/clouds/CloudCompositeMaterial.d.ts +20 -0
  194. package/dist/shaders/clouds/CloudCompositeMaterial.d.ts.map +1 -0
  195. package/dist/shaders/clouds/CloudCompositeMaterial.js +82 -0
  196. package/dist/shaders/clouds/CloudResolveMaterial.d.ts +22 -0
  197. package/dist/shaders/clouds/CloudResolveMaterial.d.ts.map +1 -0
  198. package/dist/shaders/clouds/CloudResolveMaterial.js +77 -0
  199. package/dist/shaders/clouds/CloudVolumeMaterial.d.ts +143 -0
  200. package/dist/shaders/clouds/CloudVolumeMaterial.d.ts.map +1 -0
  201. package/dist/shaders/clouds/CloudVolumeMaterial.js +249 -0
  202. package/dist/shaders/clouds/SkyBackgroundMaterial.d.ts +21 -0
  203. package/dist/shaders/clouds/SkyBackgroundMaterial.d.ts.map +1 -0
  204. package/dist/shaders/clouds/SkyBackgroundMaterial.js +42 -0
  205. package/dist/shaders/glsl/cloudCore.glsl.d.ts +24 -0
  206. package/dist/shaders/glsl/cloudCore.glsl.d.ts.map +1 -0
  207. package/dist/shaders/glsl/cloudCore.glsl.js +348 -0
  208. package/dist/shaders/glsl/landscapeCore.glsl.d.ts +49 -0
  209. package/dist/shaders/glsl/landscapeCore.glsl.d.ts.map +1 -0
  210. package/dist/shaders/glsl/landscapeCore.glsl.js +518 -0
  211. package/dist/shaders/glsl/noiseBake.glsl.d.ts +30 -0
  212. package/dist/shaders/glsl/noiseBake.glsl.d.ts.map +1 -0
  213. package/dist/shaders/glsl/noiseBake.glsl.js +164 -0
  214. package/dist/shaders/glsl/oceanCore.glsl.d.ts +52 -0
  215. package/dist/shaders/glsl/oceanCore.glsl.d.ts.map +1 -0
  216. package/dist/shaders/glsl/oceanCore.glsl.js +421 -0
  217. package/dist/shaders/glsl/terrainCore.glsl.d.ts +78 -0
  218. package/dist/shaders/glsl/terrainCore.glsl.d.ts.map +1 -0
  219. package/dist/shaders/glsl/terrainCore.glsl.js +625 -0
  220. package/dist/shaders/landscape/LandscapeMaterial.d.ts +73 -0
  221. package/dist/shaders/landscape/LandscapeMaterial.d.ts.map +1 -0
  222. package/dist/shaders/landscape/LandscapeMaterial.js +136 -0
  223. package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts +57 -0
  224. package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts.map +1 -0
  225. package/dist/shaders/ocean/OceanSurfaceMaterial.js +268 -0
  226. package/dist/shaders/terrain/TerrainMaterial.d.ts +80 -0
  227. package/dist/shaders/terrain/TerrainMaterial.d.ts.map +1 -0
  228. package/dist/shaders/terrain/TerrainMaterial.js +291 -0
  229. package/dist/types/cloudConfigs.d.ts +244 -0
  230. package/dist/types/cloudConfigs.d.ts.map +1 -0
  231. package/dist/types/cloudConfigs.js +282 -0
  232. package/dist/types/index.d.ts +8 -0
  233. package/dist/types/index.d.ts.map +1 -1
  234. package/dist/types/index.js +4 -1
  235. package/dist/types/landscapeConfigs.d.ts +218 -0
  236. package/dist/types/landscapeConfigs.d.ts.map +1 -0
  237. package/dist/types/landscapeConfigs.js +256 -0
  238. package/dist/types/oceanConfigs.d.ts +290 -0
  239. package/dist/types/oceanConfigs.d.ts.map +1 -0
  240. package/dist/types/oceanConfigs.js +380 -0
  241. package/dist/types/terrainConfigs.d.ts +251 -0
  242. package/dist/types/terrainConfigs.d.ts.map +1 -0
  243. package/dist/types/terrainConfigs.js +292 -0
  244. package/dist/utils/cloudUniforms.d.ts +14 -0
  245. package/dist/utils/cloudUniforms.d.ts.map +1 -0
  246. package/dist/utils/cloudUniforms.js +122 -0
  247. package/dist/utils/frameRenderer.d.ts +33 -0
  248. package/dist/utils/frameRenderer.d.ts.map +1 -0
  249. package/dist/utils/frameRenderer.js +24 -0
  250. package/dist/utils/handleCloudsAnimations.d.ts +16 -0
  251. package/dist/utils/handleCloudsAnimations.d.ts.map +1 -0
  252. package/dist/utils/handleCloudsAnimations.js +64 -0
  253. package/dist/utils/landscapeGeometry.d.ts +61 -0
  254. package/dist/utils/landscapeGeometry.d.ts.map +1 -0
  255. package/dist/utils/landscapeGeometry.js +126 -0
  256. package/dist/utils/landscapeHeight.d.ts +44 -0
  257. package/dist/utils/landscapeHeight.d.ts.map +1 -0
  258. package/dist/utils/landscapeHeight.js +65 -0
  259. package/dist/utils/landscapeTextures.d.ts +94 -0
  260. package/dist/utils/landscapeTextures.d.ts.map +1 -0
  261. package/dist/utils/landscapeTextures.js +290 -0
  262. package/dist/utils/landscapeUniforms.d.ts +20 -0
  263. package/dist/utils/landscapeUniforms.d.ts.map +1 -0
  264. package/dist/utils/landscapeUniforms.js +61 -0
  265. package/dist/utils/oceanUniforms.d.ts +47 -0
  266. package/dist/utils/oceanUniforms.d.ts.map +1 -0
  267. package/dist/utils/oceanUniforms.js +216 -0
  268. package/dist/utils/pointsMaterialOwnership.d.ts +9 -0
  269. package/dist/utils/pointsMaterialOwnership.d.ts.map +1 -0
  270. package/dist/utils/pointsMaterialOwnership.js +14 -0
  271. package/dist/utils/rtin.d.ts +63 -0
  272. package/dist/utils/rtin.d.ts.map +1 -0
  273. package/dist/utils/rtin.js +202 -0
  274. package/dist/utils/terrainUniforms.d.ts +14 -0
  275. package/dist/utils/terrainUniforms.d.ts.map +1 -0
  276. package/dist/utils/terrainUniforms.js +196 -0
  277. package/package.json +2 -2
@@ -0,0 +1,94 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef } from 'react';
3
+ import { useFrame } from '@react-three/fiber';
4
+ import { useObjectReady } from '../../hooks/useObjectReady.js';
5
+ import { useTerrainClipmap } from '../../hooks/useTerrainClipmap.js';
6
+ import { useCloudSun } from '../../hooks/useCloudSun.js';
7
+ import { TERRAIN_LIVE_KEY } from '../../hooks/useTerrainLive.js';
8
+ import { TerrainMaterial } from '../../shaders/terrain/TerrainMaterial.js';
9
+ import { applySunUniforms } from '../../utils/cloudUniforms.js';
10
+ import { applyTerrainAmbient, applyTerrainUniforms } from '../../utils/terrainUniforms.js';
11
+ /**
12
+ * The terrain renderer. One clipmap, one material, four landforms — they differ
13
+ * by the shaping operator the height field applies, not by having separate
14
+ * renderers, the same way three of the four cloud types share one marcher.
15
+ *
16
+ * Unlike the cloud pass this does NOT take over the render loop, and that is the
17
+ * entire reason to build terrain as geometry: it depth-sorts against scene
18
+ * objects, it receives the sun's shadow map so anything standing on it casts onto
19
+ * it, scene fog applies, and the cloud pass composites over it correctly because
20
+ * it is simply in the scene when that pass renders. A raymarched terrain would
21
+ * have none of that.
22
+ *
23
+ * Per frame it does three things: resolves the sun (from the same
24
+ * `skySystemManager` the sun rig and the clouds read, so a time slider moves all
25
+ * three together), pushes the config into uniforms, and re-snaps the clipmap to
26
+ * the camera. Nothing here re-renders React.
27
+ */
28
+ const TerrainSurface = ({ id = '3drise-terrain', config, quality, skySettings, timeSettings, sunIntensity = 1, onObjectReady, onObjectDispose, }) => {
29
+ const clipmap = useTerrainClipmap(quality.levels, quality.segments, quality.cellSize);
30
+ const { state: sun, update: updateSun } = useCloudSun({ skySettings, timeSettings });
31
+ const material = useMemo(() => new TerrainMaterial(), []);
32
+ useEffect(() => () => { material.dispose(); }, [material]);
33
+ // Live controller overrides. A slider drag writes here every pointer-move and
34
+ // the frame loop reads it on top of the config, so the terrain responds without
35
+ // a dispatch per frame — which for `quality` also means without rebuilding
36
+ // every clipmap ring sixty times a second. Cleared when the config identity
37
+ // changes, which is exactly slider-end committing the same value.
38
+ const liveConfig = useRef({});
39
+ const liveQuality = useRef({});
40
+ useEffect(() => { liveConfig.current = {}; }, [config]);
41
+ useEffect(() => { liveQuality.current = {}; }, [quality]);
42
+ const rootRef = useRef(null);
43
+ const meshRefs = useRef([]);
44
+ // The handle a controller reaches through findById(id).userData. Set in an
45
+ // effect, never as a userData JSX prop: R3F replaces the whole userData object
46
+ // on every render, which would drop the handle whenever anything above
47
+ // re-rendered.
48
+ const liveHandle = useMemo(() => ({
49
+ config: (key, value) => { liveConfig.current[key] = value; },
50
+ quality: (key, value) => { liveQuality.current[key] = value; },
51
+ }), []);
52
+ useEffect(() => {
53
+ const root = rootRef.current;
54
+ if (!root)
55
+ return;
56
+ root.userData.id = id;
57
+ root.userData[TERRAIN_LIVE_KEY] = liveHandle;
58
+ return () => { delete root.userData[TERRAIN_LIVE_KEY]; };
59
+ }, [id, liveHandle]);
60
+ useObjectReady(rootRef, { id, type: 'terrain', config, quality }, onObjectReady, onObjectDispose);
61
+ useFrame((state) => {
62
+ const camera = state.camera;
63
+ updateSun();
64
+ applySunUniforms(material, sun, sunIntensity);
65
+ // Overwrites the two ambient colours applySunUniforms just set. Those are
66
+ // the cloud curve, tuned for cloud interiors; the ground has to be lit by
67
+ // the sky that is actually being drawn behind it or a sunset landscape
68
+ // comes out black under a blazing sky.
69
+ applyTerrainAmbient(material, sun);
70
+ const cfg = { ...config, ...liveConfig.current };
71
+ const qual = { ...quality, ...liveQuality.current };
72
+ applyTerrainUniforms(material, cfg, qual);
73
+ // ONE origin for the whole pyramid, snapped to twice the finest cell.
74
+ //
75
+ // Snapping each ring to its own cell grid is the textbook clipmap and it is
76
+ // what tears the seams open: neighbouring rings then sit on origins up to a
77
+ // cell apart, and boundaries that match exactly in local space miss each
78
+ // other in the world by that offset. Sharing the origin makes them coincide
79
+ // by construction. Twice the cell, so index parity — and with it the
80
+ // geomorph — never flips as the camera crosses a cell boundary.
81
+ const snap = clipmap.sharedSnap;
82
+ const ox = Math.round(camera.position.x / snap) * snap;
83
+ const oz = Math.round(camera.position.z / snap) * snap;
84
+ for (let i = 0; i < clipmap.levels.length; i++) {
85
+ meshRefs.current[i]?.position.set(ox, 0, oz);
86
+ }
87
+ });
88
+ return (_jsx("group", { ref: rootRef, children: clipmap.levels.map((ring, i) => (_jsx("mesh", { ref: (m) => { meshRefs.current[i] = m; }, geometry: ring.geometry, material: material, receiveShadow: quality.receiveShadows,
89
+ // Displacement happens in the vertex shader, so the object-space
90
+ // bounds describe a flat plane. Culling against them hides the
91
+ // terrain the moment the camera tilts up.
92
+ frustumCulled: false }, ring.level))) }));
93
+ };
94
+ export default TerrainSurface;
@@ -0,0 +1,7 @@
1
+ export type { TerrainLayerProps } from './types';
2
+ export { default as TerrainSurface } from './TerrainSurface';
3
+ export { default as Mountains } from './Mountains';
4
+ export { default as Dunes } from './Dunes';
5
+ export { default as Hills } from './Hills';
6
+ export { default as Canyon } from './Canyon';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/terrain/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,7 @@
1
+ // Terrain landforms. Each renders the shared clipmap surface with the coercions
2
+ // its type needs; exactly one is mounted at a time, by TerrainBuilder.
3
+ export { default as TerrainSurface } from './TerrainSurface.js';
4
+ export { default as Mountains } from './Mountains.js';
5
+ export { default as Dunes } from './Dunes.js';
6
+ export { default as Hills } from './Hills.js';
7
+ export { default as Canyon } from './Canyon.js';
@@ -0,0 +1,17 @@
1
+ import type { SkySettings, TimeSettings } from '@3driseai/3drise-core';
2
+ import type { ObjectReadyProps } from '../../types/objectReady';
3
+ import type { TerrainConfig, TerrainQualityConfig } from '../../types/terrainConfigs';
4
+ /** Shared by every landform component. The builder hands all four the same shape. */
5
+ export interface TerrainLayerProps extends ObjectReadyProps {
6
+ /** Scene id, so a controller can findById() the terrain and drive it live. */
7
+ id?: string;
8
+ config: TerrainConfig;
9
+ quality: TerrainQualityConfig;
10
+ /** The four Preetham parameters, so the ground is lit by the same atmosphere
11
+ * the sky is drawn with and the clouds are lit by. */
12
+ skySettings?: SkySettings;
13
+ timeSettings?: TimeSettings;
14
+ /** Matches SkySystem's sunIntensity, so terrain brightens with the scene. */
15
+ sunIntensity?: number;
16
+ }
17
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/terrain/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEtF,qFAAqF;AACrF,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACvD,8EAA8E;IAC9E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,oBAAoB,CAAC;IAC9B;2DACuD;IACvD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import type { CloudLayerProps } from './types';
3
+ /**
4
+ * The cirrus-only sky: high ice with no deck beneath it.
5
+ *
6
+ * Cirrus normally rides along with whatever deck is enabled — the pass composites
7
+ * it above, which is correct while the camera is below both. This component is the
8
+ * case where the deck is switched off entirely, so the deck march is skipped and
9
+ * only the three-sample ice pass runs. That is a fraction of the cost of any
10
+ * volumetric type, which is why a clear-with-cirrus sky is close to free.
11
+ */
12
+ declare const Cirrus: React.FC<CloudLayerProps>;
13
+ export default Cirrus;
14
+ //# sourceMappingURL=Cirrus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Cirrus.d.ts","sourceRoot":"","sources":["../../../src/components/volumetricClouds/Cirrus.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;GAQG;AACH,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAErC,CAAC;AACF,eAAe,MAAM,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import VolumetricCloudsPass from './VolumetricCloudsPass.js';
3
+ /**
4
+ * The cirrus-only sky: high ice with no deck beneath it.
5
+ *
6
+ * Cirrus normally rides along with whatever deck is enabled — the pass composites
7
+ * it above, which is correct while the camera is below both. This component is the
8
+ * case where the deck is switched off entirely, so the deck march is skipped and
9
+ * only the three-sample ice pass runs. That is a fraction of the cost of any
10
+ * volumetric type, which is why a clear-with-cirrus sky is close to free.
11
+ */
12
+ const Cirrus = ({ cirrus, quality, skySettings, timeSettings, sunIntensity, animations, mouseMove, id, onObjectReady, onObjectDispose }) => (_jsx(VolumetricCloudsPass, { deck: null, cirrus: cirrus, quality: quality, skySettings: skySettings, timeSettings: timeSettings, sunIntensity: sunIntensity, animations: animations, mouseMove: mouseMove, id: id, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose }));
13
+ export default Cirrus;
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import type { CloudLayerProps } from './types';
3
+ /**
4
+ * A storm cell — the deepest and most expensive type.
5
+ *
6
+ * Three config values carry its identity, and with any of them at zero it reads as
7
+ * an oversized cumulus: `shear` leans the tower and drags the anvil downwind,
8
+ * `anvil` flattens the top at the tropopause instead of rounding it, and
9
+ * `precipitation` drops scattering albedo at the base, which is the only thing that
10
+ * makes it charcoal — extinction alone will not.
11
+ *
12
+ * Cost scales with depth, since the fine step is derived from it, so a shallow step
13
+ * budget on a 9 km tower gives kilometre steps and loses the structure. Floored here.
14
+ */
15
+ declare const Cumulonimbus: React.FC<CloudLayerProps>;
16
+ export default Cumulonimbus;
17
+ //# sourceMappingURL=Cumulonimbus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Cumulonimbus.d.ts","sourceRoot":"","sources":["../../../src/components/volumetricClouds/Cumulonimbus.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAG3C,CAAC;AACF,eAAe,YAAY,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import VolumetricCloudsPass from './VolumetricCloudsPass.js';
4
+ /**
5
+ * A storm cell — the deepest and most expensive type.
6
+ *
7
+ * Three config values carry its identity, and with any of them at zero it reads as
8
+ * an oversized cumulus: `shear` leans the tower and drags the anvil downwind,
9
+ * `anvil` flattens the top at the tropopause instead of rounding it, and
10
+ * `precipitation` drops scattering albedo at the base, which is the only thing that
11
+ * makes it charcoal — extinction alone will not.
12
+ *
13
+ * Cost scales with depth, since the fine step is derived from it, so a shallow step
14
+ * budget on a 9 km tower gives kilometre steps and loses the structure. Floored here.
15
+ */
16
+ const Cumulonimbus = ({ deck, cirrus, quality, skySettings, timeSettings, sunIntensity, animations, mouseMove, id, onObjectReady, onObjectDispose }) => {
17
+ const resolvedQuality = useMemo(() => ({ ...quality, steps: Math.max(quality.steps, 96) }), [quality]);
18
+ return (_jsx(VolumetricCloudsPass, { deck: deck, cirrus: cirrus, quality: resolvedQuality, skySettings: skySettings, timeSettings: timeSettings, sunIntensity: sunIntensity, animations: animations, mouseMove: mouseMove, id: id, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose }));
19
+ };
20
+ export default Cumulonimbus;
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import type { CloudLayerProps } from './types';
3
+ /**
4
+ * Fair-weather cumulus — flat condensation base, cauliflower top, sky between the
5
+ * cells. The reference case for the scattering model, because its silhouette shows
6
+ * every term at once: lit flank, powder-dark base, silver rim, blue underside.
7
+ *
8
+ * Anvil and precipitation are forced off. Both are gated on the vertical profile
9
+ * inside the shader anyway, so leaving a stale value in the config would do nothing
10
+ * visible while making the panel read as though it should.
11
+ */
12
+ declare const Cumulus: React.FC<CloudLayerProps>;
13
+ export default Cumulus;
14
+ //# sourceMappingURL=Cumulus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Cumulus.d.ts","sourceRoot":"","sources":["../../../src/components/volumetricClouds/Cumulus.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;GAQG;AACH,QAAA,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAGtC,CAAC;AACF,eAAe,OAAO,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import VolumetricCloudsPass from './VolumetricCloudsPass.js';
4
+ /**
5
+ * Fair-weather cumulus — flat condensation base, cauliflower top, sky between the
6
+ * cells. The reference case for the scattering model, because its silhouette shows
7
+ * every term at once: lit flank, powder-dark base, silver rim, blue underside.
8
+ *
9
+ * Anvil and precipitation are forced off. Both are gated on the vertical profile
10
+ * inside the shader anyway, so leaving a stale value in the config would do nothing
11
+ * visible while making the panel read as though it should.
12
+ */
13
+ const Cumulus = ({ deck, cirrus, quality, skySettings, timeSettings, sunIntensity, animations, mouseMove, id, onObjectReady, onObjectDispose }) => {
14
+ const resolved = useMemo(() => ({ ...deck, anvil: 0, precipitation: 0 }), [deck]);
15
+ return (_jsx(VolumetricCloudsPass, { deck: resolved, cirrus: cirrus, quality: quality, skySettings: skySettings, timeSettings: timeSettings, sunIntensity: sunIntensity, animations: animations, mouseMove: mouseMove, id: id, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose }));
16
+ };
17
+ export default Cumulus;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import type { CloudLayerProps } from './types';
3
+ /**
4
+ * A low overcast deck — rolls, lumpy undersides, little vertical development.
5
+ *
6
+ * The deck is shallow, so the march's fine step (derived from depth) is already
7
+ * small and a large step budget is wasted; it is capped here. Anvil and
8
+ * precipitation are off for the same reason as cumulus.
9
+ */
10
+ declare const Stratocumulus: React.FC<CloudLayerProps>;
11
+ export default Stratocumulus;
12
+ //# sourceMappingURL=Stratocumulus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Stratocumulus.d.ts","sourceRoot":"","sources":["../../../src/components/volumetricClouds/Stratocumulus.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;GAMG;AACH,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAI5C,CAAC;AACF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import VolumetricCloudsPass from './VolumetricCloudsPass.js';
4
+ /**
5
+ * A low overcast deck — rolls, lumpy undersides, little vertical development.
6
+ *
7
+ * The deck is shallow, so the march's fine step (derived from depth) is already
8
+ * small and a large step budget is wasted; it is capped here. Anvil and
9
+ * precipitation are off for the same reason as cumulus.
10
+ */
11
+ const Stratocumulus = ({ deck, cirrus, quality, skySettings, timeSettings, sunIntensity, animations, mouseMove, id, onObjectReady, onObjectDispose }) => {
12
+ const resolved = useMemo(() => ({ ...deck, anvil: 0, precipitation: 0 }), [deck]);
13
+ const resolvedQuality = useMemo(() => ({ ...quality, steps: Math.min(quality.steps, 112) }), [quality]);
14
+ return (_jsx(VolumetricCloudsPass, { deck: resolved, cirrus: cirrus, quality: resolvedQuality, skySettings: skySettings, timeSettings: timeSettings, sunIntensity: sunIntensity, animations: animations, mouseMove: mouseMove, id: id, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose }));
15
+ };
16
+ export default Stratocumulus;
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import type { ObjectReadyProps } from '../../types/objectReady';
3
+ import type { SkySettings, TimeSettings } from '@3driseai/3drise-core';
4
+ import type { CirrusLayerConfig, CloudDeckConfig, CloudsQualityConfig } from '../../types/cloudConfigs';
5
+ import type { ObjectAnimations, MouseMoveInteractions } from '@3driseai/3drise-core';
6
+ /**
7
+ * The cloud render pipeline. The only component in this package that takes over
8
+ * rendering, and there must be exactly one.
9
+ *
10
+ * Per frame:
11
+ * 1. scene, into an HDR target with a depth texture
12
+ * 2. deck march at `resolutionScale`, jittered a sub-pixel per frame
13
+ * 3. temporal resolve against a reprojected history, neighbourhood-clamped
14
+ * 4. cirrus at the same scale — smooth enough to need no temporal pass
15
+ * 5. depth-aware upsample, composite, tonemap
16
+ *
17
+ * Half resolution plus a temporal resolve buys roughly four times the march steps
18
+ * for the same cost, which is the difference between a cloud with structure and a
19
+ * cloud made of fog. The cost is one frame of latency and this component owning
20
+ * the loop.
21
+ *
22
+ * If it unmounts, R3F resumes its own auto-render and the scene draws normally
23
+ * without clouds — so hiding it is safe.
24
+ */
25
+ export interface VolumetricCloudsPassProps extends ObjectReadyProps {
26
+ /** Scene id, so a controller can findById() this layer and drive it live. */
27
+ id?: string;
28
+ deck?: CloudDeckConfig | null;
29
+ cirrus?: CirrusLayerConfig | null;
30
+ quality: CloudsQualityConfig;
31
+ skySettings?: SkySettings;
32
+ timeSettings?: TimeSettings;
33
+ /** Multiplies sun radiance for the clouds only. Matches SkySystem's sunIntensity. */
34
+ sunIntensity?: number;
35
+ /** Per-domain keyframes; the `clouds` domain drives deck properties. */
36
+ animations?: ObjectAnimations;
37
+ /** Per-domain pointer interactions; same `clouds` domain. */
38
+ mouseMove?: MouseMoveInteractions;
39
+ }
40
+ declare const VolumetricCloudsPass: React.FC<VolumetricCloudsPassProps>;
41
+ export default VolumetricCloudsPass;
42
+ //# sourceMappingURL=VolumetricCloudsPass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VolumetricCloudsPass.d.ts","sourceRoot":"","sources":["../../../src/components/volumetricClouds/VolumetricCloudsPass.tsx"],"names":[],"mappings":"AACA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAIvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAQvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAExG,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAErF;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IAC/D,6EAA6E;IAC7E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,6DAA6D;IAC7D,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAkBD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA0S7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,298 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as THREE from 'three';
3
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
4
+ import { useFrame, useThree } from '@react-three/fiber';
5
+ import { useFrameRenderer } from '../../hooks/useFrameRenderer.js';
6
+ import { useObjectReady } from '../../hooks/useObjectReady.js';
7
+ import { CLOUD_LIVE_KEY } from '../../hooks/useCloudLive.js';
8
+ import { useCloudNoise } from '../../hooks/useCloudNoise.js';
9
+ import { useCloudSun } from '../../hooks/useCloudSun.js';
10
+ import { CloudVolumeMaterial } from '../../shaders/clouds/CloudVolumeMaterial.js';
11
+ import { CirrusMaterial } from '../../shaders/clouds/CirrusMaterial.js';
12
+ import { CloudResolveMaterial } from '../../shaders/clouds/CloudResolveMaterial.js';
13
+ import { CloudCompositeMaterial } from '../../shaders/clouds/CloudCompositeMaterial.js';
14
+ import { applyCameraUniforms, applyCirrusUniforms, applyDeckUniforms, applySunUniforms, } from '../../utils/cloudUniforms.js';
15
+ import { applyCloudsKeyframeAnimations, applyCloudsMouseMove, resolveDeckConfig, } from '../../utils/handleCloudsAnimations.js';
16
+ const quad = new THREE.PlaneGeometry(2, 2);
17
+ const orthoCam = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
18
+ function passScene(material) {
19
+ const scene = new THREE.Scene();
20
+ scene.add(new THREE.Mesh(quad, material));
21
+ return scene;
22
+ }
23
+ /** rgb 0, alpha 1 — composites as "nothing here", so a disabled layer costs one texel. */
24
+ function emptyTexture() {
25
+ const tex = new THREE.DataTexture(Uint8Array.from([0, 0, 0, 255]), 1, 1, THREE.RGBAFormat, THREE.UnsignedByteType);
26
+ tex.needsUpdate = true;
27
+ return tex;
28
+ }
29
+ const VolumetricCloudsPass = ({ id = '3drise-clouds', deck, cirrus, quality, skySettings, timeSettings, sunIntensity = 1, animations, mouseMove, onObjectReady, onObjectDispose, }) => {
30
+ const gl = useThree((s) => s.gl);
31
+ const scene = useThree((s) => s.scene);
32
+ const size = useThree((s) => s.size);
33
+ const dpr = useThree((s) => s.viewport.dpr);
34
+ const noise = useCloudNoise();
35
+ const { state: sun, update: updateSun } = useCloudSun({ skySettings, timeSettings });
36
+ const deckOn = !!deck?.enabled;
37
+ const cirrusOn = !!cirrus?.enabled;
38
+ const width = Math.max(2, Math.floor(size.width * dpr));
39
+ const height = Math.max(2, Math.floor(size.height * dpr));
40
+ const lowW = Math.max(2, Math.floor(width * quality.resolutionScale));
41
+ const lowH = Math.max(2, Math.floor(height * quality.resolutionScale));
42
+ const targets = useMemo(() => {
43
+ const makeSceneTarget = () => {
44
+ const depthTexture = new THREE.DepthTexture(width, height);
45
+ depthTexture.type = THREE.UnsignedIntType;
46
+ depthTexture.minFilter = THREE.NearestFilter;
47
+ depthTexture.magFilter = THREE.NearestFilter;
48
+ return new THREE.WebGLRenderTarget(width, height, {
49
+ minFilter: THREE.LinearFilter,
50
+ magFilter: THREE.LinearFilter,
51
+ type: THREE.HalfFloatType,
52
+ depthBuffer: true,
53
+ depthTexture,
54
+ });
55
+ };
56
+ const lowOpts = {
57
+ minFilter: THREE.LinearFilter,
58
+ magFilter: THREE.LinearFilter,
59
+ type: THREE.HalfFloatType,
60
+ depthBuffer: false,
61
+ stencilBuffer: false,
62
+ };
63
+ return {
64
+ // Ping-ponged so nothing ever samples the depth attachment it is writing.
65
+ sceneA: makeSceneTarget(),
66
+ sceneB: makeSceneTarget(),
67
+ deck: new THREE.WebGLRenderTarget(lowW, lowH, lowOpts),
68
+ cirrus: new THREE.WebGLRenderTarget(lowW, lowH, lowOpts),
69
+ histA: new THREE.WebGLRenderTarget(lowW, lowH, lowOpts),
70
+ histB: new THREE.WebGLRenderTarget(lowW, lowH, lowOpts),
71
+ };
72
+ }, [width, height, lowW, lowH]);
73
+ useEffect(() => () => { Object.values(targets).forEach((t) => t.dispose()); }, [targets]);
74
+ const passes = useMemo(() => {
75
+ const deckMat = new CloudVolumeMaterial();
76
+ const cirrusMat = new CirrusMaterial();
77
+ const resolveMat = new CloudResolveMaterial();
78
+ const compositeMat = new CloudCompositeMaterial();
79
+ for (const m of [deckMat, cirrusMat, resolveMat, compositeMat]) {
80
+ m.glslVersion = THREE.GLSL3;
81
+ m.depthTest = false;
82
+ m.depthWrite = false;
83
+ }
84
+ return {
85
+ deckMat, cirrusMat, resolveMat, compositeMat,
86
+ deckScene: passScene(deckMat),
87
+ cirrusScene: passScene(cirrusMat),
88
+ resolveScene: passScene(resolveMat),
89
+ compositeScene: passScene(compositeMat),
90
+ };
91
+ }, []);
92
+ useEffect(() => () => {
93
+ [passes.deckMat, passes.cirrusMat, passes.resolveMat, passes.compositeMat]
94
+ .forEach((m) => m.dispose());
95
+ }, [passes]);
96
+ const empty = useMemo(emptyTexture, []);
97
+ const prevViewProj = useRef(new THREE.Matrix4());
98
+ const jitProj = useRef(new THREE.Matrix4());
99
+ const jitInv = useRef(new THREE.Matrix4());
100
+ const frame = useRef(0);
101
+ const validHistory = useRef(0);
102
+ const time = useRef(0);
103
+ const histFlip = useRef(0);
104
+ const prevCamPos = useRef(new THREE.Vector3());
105
+ const prevCamQuat = useRef(new THREE.Quaternion());
106
+ // Animation scratch. Kept in refs so a keyframed coverage does not re-render the
107
+ // React tree sixty times a second.
108
+ const override = useRef({});
109
+ const startValues = useRef({});
110
+ // Live controller overrides. A slider drag writes here every pointer-move and the
111
+ // frame loop reads it on top of the config, so the deck responds without a Redux
112
+ // dispatch per frame. Cleared when the config identity changes, which is exactly
113
+ // slider-end committing the same value.
114
+ const liveDeck = useRef({});
115
+ const liveCirrus = useRef({});
116
+ const liveQuality = useRef({});
117
+ useEffect(() => { liveDeck.current = {}; }, [deck]);
118
+ useEffect(() => { liveCirrus.current = {}; }, [cirrus]);
119
+ useEffect(() => { liveQuality.current = {}; }, [quality]);
120
+ // The handle a controller reaches through findById(id).userData. Set in an effect,
121
+ // never as a userData JSX prop: R3F replaces the whole userData object on every
122
+ // render, which would drop the handle the moment anything above re-renders.
123
+ const rootRef = useRef(null);
124
+ const liveHandle = useMemo(() => ({
125
+ deck: (key, value) => { liveDeck.current[key] = value; },
126
+ cirrus: (key, value) => { liveCirrus.current[key] = value; },
127
+ quality: (key, value) => { liveQuality.current[key] = value; },
128
+ }), []);
129
+ useEffect(() => {
130
+ const root = rootRef.current;
131
+ if (!root)
132
+ return;
133
+ root.userData.id = id;
134
+ root.userData[CLOUD_LIVE_KEY] = liveHandle;
135
+ return () => {
136
+ delete root.userData[CLOUD_LIVE_KEY];
137
+ };
138
+ }, [id, liveHandle]);
139
+ // ObjectReadySource's union is widened by Record<string, any>, but spelling the
140
+ // cast out keeps the intent: this reports the layer's settings, not a mesh's.
141
+ useObjectReady(rootRef, { id, type: 'clouds', deck, cirrus, quality }, onObjectReady, onObjectDispose);
142
+ // Only a resolution change genuinely invalidates the history — the buffers are
143
+ // reallocated and nothing in them maps to the new grid.
144
+ //
145
+ // Config edits deliberately do NOT reset it. Dragging a slider dispatches a new
146
+ // config object every pointer-move, so resetting here would restart the temporal
147
+ // accumulation on every frame of the drag and the deck would boil while you tune
148
+ // it. The neighbourhood clamp in the resolve is exactly what absorbs a field that
149
+ // changes under the history.
150
+ useEffect(() => { validHistory.current = 0; }, [width, height, lowW, lowH]);
151
+ // The frame body, extracted so it can be driven from two places: the render loop,
152
+ // and an off-loop caller that needs a finished image (see useFrameRenderer). It is
153
+ // held in a ref rather than a useCallback because it closes over a dozen pieces of
154
+ // per-frame state, and a dependency array over all of them would be re-created
155
+ // every render anyway — which would re-register the frame renderer every render.
156
+ const drawFrame = (camera, pointer, delta) => {
157
+ if (!noise)
158
+ return;
159
+ frame.current += 1;
160
+ time.current += Math.min(delta, 0.05);
161
+ updateSun();
162
+ // ---- 1. scene ----
163
+ const sceneRT = (frame.current & 1) ? targets.sceneA : targets.sceneB;
164
+ const prevTarget = gl.getRenderTarget();
165
+ const prevAutoClear = gl.autoClear;
166
+ gl.autoClear = true;
167
+ gl.setRenderTarget(sceneRT);
168
+ // No sky pass. SkyController / Skybox already drew the background into this
169
+ // target; the clouds only borrow the atmosphere function for ambient and
170
+ // aerial perspective, so a second sky would just fight the first.
171
+ gl.render(scene, camera);
172
+ // Sub-pixel rotation, applied ONLY to the cloud ray basis. Jittering
173
+ // camera.projectionMatrix would shimmer the scene pass, which nothing resolves.
174
+ const jx = ((frame.current & 1) ? 0.5 : -0.5) / lowW;
175
+ const jy = ((frame.current & 2) ? 0.5 : -0.5) / lowH;
176
+ jitProj.current.copy(camera.projectionMatrix);
177
+ jitProj.current.elements[8] = jx * 2;
178
+ jitProj.current.elements[9] = jy * 2;
179
+ jitInv.current.copy(jitProj.current).invert();
180
+ const bindShared = (m) => {
181
+ applySunUniforms(m, sun, sunIntensity);
182
+ applyCameraUniforms(m, camera, jitInv.current, lowW, lowH);
183
+ m.uniforms.u_time.value = time.current;
184
+ if (m.uniforms.u_frame)
185
+ m.uniforms.u_frame.value = frame.current;
186
+ m.uniforms.u_depth.value = sceneRT.depthTexture;
187
+ m.uniforms.u_hasDepth.value = 1;
188
+ m.uniforms.u_screenSpace.value = 1;
189
+ };
190
+ // ---- 2 & 3. deck march, then temporal resolve ----
191
+ let deckTex = empty;
192
+ if (deckOn && deck) {
193
+ // Keyframes first, then pointer on top, so the two compose rather than
194
+ // the pointer snapping back to the stored config every frame.
195
+ for (const k in override.current)
196
+ delete override.current[k];
197
+ applyCloudsKeyframeAnimations(animations?.clouds?.animations, startValues.current, deck, override.current, time.current);
198
+ if (mouseMove?.enabled && mouseMove.clouds?.enabled) {
199
+ applyCloudsMouseMove(mouseMove.clouds.properties, deck, override.current, pointer);
200
+ }
201
+ // config -> keyframe/pointer domain -> live controller drag, in that order.
202
+ const animated = resolveDeckConfig(deck, override.current);
203
+ const live = resolveDeckConfig(animated, liveDeck.current);
204
+ const liveQual = { ...quality, ...liveQuality.current };
205
+ const m = passes.deckMat;
206
+ applyDeckUniforms(m, live, liveQual);
207
+ m.uniforms.u_base.value = noise.base;
208
+ m.uniforms.u_detail.value = noise.detail;
209
+ m.uniforms.u_weather.value = noise.weather;
210
+ m.uniforms.u_curl.value = noise.curl;
211
+ m.uniforms.u_host.value = 1;
212
+ bindShared(m);
213
+ gl.setRenderTarget(targets.deck);
214
+ gl.render(passes.deckScene, orthoCam);
215
+ const history = histFlip.current ? targets.histB : targets.histA;
216
+ const destination = histFlip.current ? targets.histA : targets.histB;
217
+ const r = passes.resolveMat.uniforms;
218
+ r.u_current.value = targets.deck.texture;
219
+ r.u_history.value = history.texture;
220
+ r.u_prevViewProj.value.copy(prevViewProj.current);
221
+ // UNJITTERED, deliberately. The march samples along jittered rays, but the
222
+ // reprojection maps pixel CENTRES between frames. Feeding it the jittered
223
+ // inverse offsets every history fetch by the current sub-pixel jitter, and
224
+ // since that cycles through four offsets the resolved image shifts about a
225
+ // full-res pixel every frame even with the camera locked. That reads as jitter.
226
+ r.u_invProj.value.copy(camera.projectionMatrixInverse);
227
+ r.u_camWorld.value.copy(camera.matrixWorld);
228
+ r.u_camPos.value.copy(camera.position);
229
+ r.u_texel.value.set(1 / lowW, 1 / lowH);
230
+ // Reprojection proxy: the deck's mid-height. Cheaper than storing
231
+ // per-pixel cloud depth and, for a layer far relative to per-frame
232
+ // camera motion, indistinguishable.
233
+ r.u_proxyHeight.value = (live.baseAltitude + live.topAltitude) * 0.5;
234
+ r.u_valid.value = validHistory.current > 1 ? 1 : 0;
235
+ // While the camera moves, the proxy-plane reprojection is only approximate
236
+ // and a high history weight smears. Ease off, and restore it once still.
237
+ const moved = camera.position.distanceToSquared(prevCamPos.current) > 1e-8
238
+ || Math.abs(camera.quaternion.dot(prevCamQuat.current)) < 0.9999995;
239
+ r.u_blend.value = moved ? Math.min(quality.temporalBlend, 0.7) : quality.temporalBlend;
240
+ gl.setRenderTarget(destination);
241
+ gl.render(passes.resolveScene, orthoCam);
242
+ histFlip.current ^= 1;
243
+ validHistory.current += 1;
244
+ deckTex = destination.texture;
245
+ }
246
+ else {
247
+ validHistory.current = 0;
248
+ }
249
+ // ---- 4. cirrus ----
250
+ let cirrusTex = empty;
251
+ if (cirrusOn && cirrus) {
252
+ const m = passes.cirrusMat;
253
+ applyCirrusUniforms(m, { ...cirrus, ...liveCirrus.current }, quality);
254
+ m.uniforms.u_weather.value = noise.weather;
255
+ bindShared(m);
256
+ gl.setRenderTarget(targets.cirrus);
257
+ gl.render(passes.cirrusScene, orthoCam);
258
+ cirrusTex = targets.cirrus.texture;
259
+ }
260
+ // ---- 5. composite ----
261
+ const c = passes.compositeMat.uniforms;
262
+ c.u_scene.value = sceneRT.texture;
263
+ c.u_clouds.value = deckTex;
264
+ c.u_cirrus.value = cirrusTex;
265
+ c.u_depth.value = sceneRT.depthTexture;
266
+ c.u_lowTexel.value.set(1 / lowW, 1 / lowH);
267
+ c.u_exposure.value = quality.exposure;
268
+ c.u_tonemap.value = quality.tonemap ? 1 : 0;
269
+ c.u_hasCirrus.value = cirrusOn ? 1 : 0;
270
+ gl.setRenderTarget(prevTarget);
271
+ gl.render(passes.compositeScene, orthoCam);
272
+ gl.autoClear = prevAutoClear;
273
+ prevViewProj.current.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
274
+ prevCamPos.current.copy(camera.position);
275
+ prevCamQuat.current.copy(camera.quaternion);
276
+ };
277
+ // Keep the latest body and pointer reachable from the off-loop path without
278
+ // making either a dependency of the registration.
279
+ const drawRef = useRef(drawFrame);
280
+ drawRef.current = drawFrame;
281
+ const pointerRef = useRef(new THREE.Vector2());
282
+ useFrame((state, delta) => {
283
+ pointerRef.current.copy(state.pointer);
284
+ drawRef.current(state.camera, state.pointer, delta);
285
+ }, 1);
286
+ // This pass composites to the canvas itself, so gl.render(scene, camera) is not a
287
+ // complete frame while it is mounted — it draws the scene graph and drops the
288
+ // clouds, which is what made screenshots come back with everything but the sky.
289
+ // delta 0: an off-loop frame must not advance cloud time or the animation jumps.
290
+ const renderCompleteFrame = useCallback((camera) => {
291
+ drawRef.current(camera, pointerRef.current, 0);
292
+ }, []);
293
+ useFrameRenderer(renderCompleteFrame);
294
+ // An empty group, but a real one: it carries userData.id so findById() can reach
295
+ // this layer, and the live handle so a controller can drive it without Redux.
296
+ return _jsx("group", { ref: rootRef });
297
+ };
298
+ export default VolumetricCloudsPass;