@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,61 @@
1
+ import * as THREE from 'three';
2
+ /**
3
+ * Config units to shader units, once per frame.
4
+ *
5
+ * Everything the panel exposes in human units — metres, degrees, world sizes —
6
+ * is converted here, so the shader only ever sees reciprocals and normalised
7
+ * values and never divides by a uniform it could have been handed pre-divided.
8
+ * The same split the clouds and terrain use.
9
+ */
10
+ const tmpColor = new THREE.Color();
11
+ export function applyLandscapeMaps(uniforms, maps) {
12
+ uniforms.u_lsHeight.value = maps.height;
13
+ uniforms.u_lsHeightSize.value.copy(maps.heightSize);
14
+ uniforms.u_lsField.value = maps.field;
15
+ uniforms.u_lsGradScale.value = maps.gradScale;
16
+ uniforms.u_lsFieldTexel.value.set(1 / Math.max(maps.fieldSize.x, 1), 1 / Math.max(maps.fieldSize.y, 1));
17
+ uniforms.u_lsDiffuse.value = maps.diffuse;
18
+ uniforms.u_lsNormal.value = maps.normal;
19
+ uniforms.u_lsSplat0.value = maps.splat0;
20
+ uniforms.u_lsSplat1.value = maps.splat1;
21
+ uniforms.u_lsAo.value = maps.ao;
22
+ const means = uniforms.u_lsSurfMean.value;
23
+ for (let i = 0; i < means.length; i++) {
24
+ const mean = maps.means[i];
25
+ if (mean)
26
+ means[i].copy(mean);
27
+ }
28
+ }
29
+ export function applyLandscapeUniforms(uniforms, config, quality) {
30
+ uniforms.u_lsSmoothness.value = config.smoothness;
31
+ uniforms.u_lsElevation.value = config.elevationScale;
32
+ uniforms.u_lsElevationBias.value = config.elevationBias;
33
+ uniforms.u_lsSize.value = config.size;
34
+ uniforms.u_lsMacro.value = config.macroVariation;
35
+ uniforms.u_lsMacroScale.value = 1 / Math.max(config.macroSize, 1);
36
+ uniforms.u_lsAoIntensity.value = config.aoIntensity;
37
+ const auto = config.autoSplat;
38
+ uniforms.u_lsAuto.value.set(auto.bandWidth, auto.slopeStart, auto.slopeEnd, auto.noise);
39
+ uniforms.u_lsNoiseScale.value = 1 / Math.max(auto.noiseSize, 1);
40
+ const bands = uniforms.u_lsAutoBands.value;
41
+ for (let i = 0; i < bands.length; i++) {
42
+ // A surface with no band of its own is spread evenly through the range,
43
+ // so adding a layer to a preset does not stack it on top of layer zero.
44
+ bands[i] = auto.bands[i] ?? (bands.length > 1 ? i / (bands.length - 1) : 0);
45
+ }
46
+ const a = uniforms.u_lsSurfA.value;
47
+ const b = uniforms.u_lsSurfB.value;
48
+ const c = uniforms.u_lsSurfC.value;
49
+ for (let i = 0; i < a.length; i++) {
50
+ const surface = config.surfaces[i];
51
+ if (!surface)
52
+ continue;
53
+ a[i].set(Math.max(surface.repeat, 0.001), surface.normalStrength, THREE.MathUtils.clamp(surface.roughness, 0.04, 1), THREE.MathUtils.clamp(surface.heightBlend, 0, 1));
54
+ // Tints are authored in sRGB like every other colour in the panel, and
55
+ // the albedo they multiply has already been converted, so the tint has
56
+ // to be too — otherwise a mid grey tint darkens the ground by a stop.
57
+ tmpColor.set(surface.tint || '#ffffff').convertSRGBToLinear();
58
+ b[i].set(tmpColor.r, tmpColor.g, tmpColor.b, surface.saturation);
59
+ c[i].set(quality.triplanar && surface.triplanar ? 1 : 0, surface.flipNormalY ? -1 : 1, THREE.MathUtils.clamp(surface.metalness, 0, 1), quality.stochastic && surface.aperiodic ? 1 : 0);
60
+ }
61
+ }
@@ -0,0 +1,47 @@
1
+ import * as THREE from 'three';
2
+ import type { OceanConfig, OceanQualityConfig } from '../types/oceanConfigs';
3
+ export interface WaveTable {
4
+ /** (dirX, dirZ, amplitude, wavenumber) */
5
+ a: THREE.Vector4[];
6
+ /** (angular frequency, phase, steepness Q, wavelength) */
7
+ b: THREE.Vector4[];
8
+ count: number;
9
+ }
10
+ /**
11
+ * The sea state, from wind and fetch.
12
+ *
13
+ * Fully developed Pierson-Moskowitz gives peak wavelength ~0.88 U^2 and
14
+ * significant height ~0.0246 U^2 for wind speed U in m/s. Fetch scales both
15
+ * down: the `fetchFactor` here is a fit, not a derivation — the real
16
+ * fetch-limited JONSWAP relations need dimensionless fetch and a peak
17
+ * enhancement, and none of that survives contact with a slider labelled "km".
18
+ * What matters is the behaviour it produces, which is the one thing everybody
19
+ * gets wrong about lakes: a lake in a gale has small, short, steep waves, not
20
+ * small oceans.
21
+ */
22
+ export declare function seaState(windSpeed: number, fetchKm: number): {
23
+ peakLength: number;
24
+ significantHeight: number;
25
+ };
26
+ /**
27
+ * Build the trochoid table for a config.
28
+ *
29
+ * Components are spread geometrically from long to short about the spectral
30
+ * peak, weighted by Pierson-Moskowitz, then renormalised so the significant
31
+ * wave height is exactly what `seaState` says whatever the component count. That
32
+ * last step is what stops the sea changing size when somebody drags the wave
33
+ * count — the discretisation should cost detail, not amplitude.
34
+ */
35
+ export declare function buildWaveTable(config: OceanConfig, out?: WaveTable): WaveTable;
36
+ /**
37
+ * Push a config into the material.
38
+ *
39
+ * Called every frame with the live overrides merged in, exactly as
40
+ * `applyTerrainUniforms` is — it is a few dozen assignments and it is what lets
41
+ * a slider drag reach the renderer without a Redux dispatch per pointer-move.
42
+ * The wave table is the one thing that is not recomputed blindly: it is rebuilt
43
+ * only when one of the eight values it depends on changes.
44
+ */
45
+ export declare function applyOceanUniforms(material: THREE.ShaderMaterial, config: OceanConfig, quality: OceanQualityConfig): void;
46
+ export default applyOceanUniforms;
47
+ //# sourceMappingURL=oceanUniforms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oceanUniforms.d.ts","sourceRoot":"","sources":["../../src/utils/oceanUniforms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AA+B7E,MAAM,WAAW,SAAS;IACtB,0CAA0C;IAC1C,CAAC,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IACnB,0DAA0D;IAC1D,CAAC,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACjB;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,CAQ9G;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,GAAE,SAAwB,GAAG,SAAS,CA6F5F;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAC9B,QAAQ,EAAE,KAAK,CAAC,cAAc,EAC9B,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,kBAAkB,GAC5B,IAAI,CAqDN;AAED,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,216 @@
1
+ import * as THREE from 'three';
2
+ import { MAX_WAVES } from '../shaders/glsl/oceanCore.glsl.js';
3
+ /**
4
+ * Config units to shader units for the ocean, and the wave table itself.
5
+ *
6
+ * The counterpart to `terrainUniforms.ts`, with one extra job: the spectrum.
7
+ * Everything the panel exposes is meteorology — wind speed, fetch, spread — and
8
+ * what the shader needs is a table of trochoids. The fit between them runs here,
9
+ * on the CPU, once per change rather than once per vertex.
10
+ *
11
+ * ## Why the spectrum is not evaluated in the shader
12
+ *
13
+ * Pierson-Moskowitz is an `exp` and two `pow` per component. Evaluated in the
14
+ * vertex loop that is 60 transcendentals per vertex before a single sine has
15
+ * been summed, and it produces the same numbers for every vertex in the scene,
16
+ * every frame, until somebody moves a slider. Baking it is not an optimisation
17
+ * so much as putting it where it belongs — and it is the shape the FFT phase
18
+ * needs anyway, where the spectrum is baked into a texture and only the phase
19
+ * advances per frame.
20
+ */
21
+ const GRAVITY = 9.81;
22
+ /** Deterministic per-component jitter. Same table every load, so a saved scene
23
+ * looks the same tomorrow — a random one re-rolls the sea on every refresh. */
24
+ function hash1(n) {
25
+ const s = Math.sin(n * 127.1 + 311.7) * 43758.5453123;
26
+ return s - Math.floor(s);
27
+ }
28
+ function emptyTable() {
29
+ return {
30
+ a: Array.from({ length: MAX_WAVES }, () => new THREE.Vector4()),
31
+ b: Array.from({ length: MAX_WAVES }, () => new THREE.Vector4()),
32
+ count: 0,
33
+ };
34
+ }
35
+ /**
36
+ * The sea state, from wind and fetch.
37
+ *
38
+ * Fully developed Pierson-Moskowitz gives peak wavelength ~0.88 U^2 and
39
+ * significant height ~0.0246 U^2 for wind speed U in m/s. Fetch scales both
40
+ * down: the `fetchFactor` here is a fit, not a derivation — the real
41
+ * fetch-limited JONSWAP relations need dimensionless fetch and a peak
42
+ * enhancement, and none of that survives contact with a slider labelled "km".
43
+ * What matters is the behaviour it produces, which is the one thing everybody
44
+ * gets wrong about lakes: a lake in a gale has small, short, steep waves, not
45
+ * small oceans.
46
+ */
47
+ export function seaState(windSpeed, fetchKm) {
48
+ const U = Math.max(windSpeed, 0.01);
49
+ const fullyDevelopedKm = 0.05 * U * U + 1;
50
+ const fetchFactor = Math.min(Math.max(Math.pow(Math.max(fetchKm, 0.001) / fullyDevelopedKm, 0.4), 0.08), 1);
51
+ return {
52
+ peakLength: Math.max(0.88 * U * U * fetchFactor, 0.12),
53
+ significantHeight: Math.max(0.0246 * U * U * fetchFactor, 0.0015),
54
+ };
55
+ }
56
+ /**
57
+ * Build the trochoid table for a config.
58
+ *
59
+ * Components are spread geometrically from long to short about the spectral
60
+ * peak, weighted by Pierson-Moskowitz, then renormalised so the significant
61
+ * wave height is exactly what `seaState` says whatever the component count. That
62
+ * last step is what stops the sea changing size when somebody drags the wave
63
+ * count — the discretisation should cost detail, not amplitude.
64
+ */
65
+ export function buildWaveTable(config, out = emptyTable()) {
66
+ const count = Math.max(1, Math.min(Math.round(config.waveComponents), MAX_WAVES));
67
+ const { peakLength, significantHeight } = seaState(config.windSpeed, config.fetch);
68
+ // Swell is energy that has outrun the weather that made it: longer, lower,
69
+ // and narrower in direction. It extends the long end of the band and
70
+ // reweights toward it.
71
+ const swell = Math.min(Math.max(config.swell, 0), 1);
72
+ const lengthMax = peakLength * (1 + swell * 2.2);
73
+ const lengthMin = Math.max(peakLength / 24, 0.08);
74
+ const omegaPeak = Math.sqrt((GRAVITY * 2 * Math.PI) / peakLength);
75
+ const bearing = (config.windBearing * Math.PI) / 180;
76
+ const spread = (config.directionalSpread * Math.PI) / 180;
77
+ const lengths = [];
78
+ const amps = [];
79
+ for (let i = 0; i < count; i++) {
80
+ const t = count === 1 ? 0 : i / (count - 1);
81
+ const wl = lengthMax * Math.pow(lengthMin / lengthMax, t);
82
+ const k = (2 * Math.PI) / wl;
83
+ const omega = Math.sqrt(GRAVITY * k);
84
+ // Pierson-Moskowitz density, and the band each component stands for.
85
+ const density = (0.0081 * GRAVITY * GRAVITY) / Math.pow(omega, 5)
86
+ * Math.exp(-1.25 * Math.pow(omegaPeak / omega, 4));
87
+ const dOmega = omega * Math.log(lengthMax / lengthMin) / Math.max(count - 1, 1) * 0.5;
88
+ let amp = Math.sqrt(Math.max(2 * density * dOmega, 0));
89
+ // Reweight toward the long end for swell.
90
+ amp *= 1 + swell * 1.6 * Math.pow(wl / lengthMax, 1.5);
91
+ lengths.push(wl);
92
+ amps.push(amp);
93
+ }
94
+ // Renormalise to the significant height. Hs = 4 * sqrt(sum(A^2 / 2)) for a
95
+ // sum of sinusoids, which is the definition that makes the number mean what
96
+ // an oceanographer thinks it means.
97
+ let variance = 0;
98
+ for (const a of amps)
99
+ variance += (a * a) / 2;
100
+ const sigma = Math.sqrt(Math.max(variance, 1e-12));
101
+ const scale = (significantHeight / 4) / sigma;
102
+ const steepness = Math.min(Math.max(config.steepness, 0), 1);
103
+ const choppiness = Math.max(config.choppiness, 0);
104
+ for (let i = 0; i < count; i++) {
105
+ const wl = lengths[i];
106
+ const amp = amps[i] * scale;
107
+ const k = (2 * Math.PI) / wl;
108
+ const omega = Math.sqrt(GRAVITY * k);
109
+ // Stratified across the spread, so the components never clump the way
110
+ // independent random draws do, plus a deterministic jitter so the strata
111
+ // are not visible as a fan of evenly spaced crests.
112
+ const t = count === 1 ? 0.5 : i / (count - 1);
113
+ const stratified = count === 1 ? 0 : ((i + 0.5) / count) * 2 - 1;
114
+ const jitter = (hash1(i * 3.17 + 1.7) * 2 - 1) / count;
115
+ const shaped = Math.sign(stratified) * Math.pow(Math.abs(stratified), 1.5) + jitter;
116
+ // Long waves are more directional than short ones — swell arrives as a
117
+ // train, chop comes from everywhere.
118
+ const spreadScale = 0.35 + 0.65 * t;
119
+ const angle = bearing + shaped * spread * spreadScale;
120
+ // Gerstner folds through itself past Q*A*k = 1, and the budget has to be
121
+ // shared across the components. Dividing by the count is the safe answer
122
+ // and it is too safe: the components have independent phases, so their
123
+ // gradients add like a random walk rather than in lockstep, and the sum
124
+ // of N of them grows as sqrt(N). Dividing by N therefore leaves the
125
+ // surface an order of magnitude below the folding limit and no amount of
126
+ // choppiness ever produces a breaking crest - which means no whitecaps,
127
+ // because the foam is read off exactly that folding.
128
+ //
129
+ // count^0.7 is measured rather than derived: across the four types at
130
+ // their defaults it lands the minimum Jacobian just above zero, so the
131
+ // sea is as choppy as it can be without folding, and pushing choppiness
132
+ // past 1 tips it over into genuine breaking. See the calibration note in
133
+ // claude/ocean-flow-plan.md.
134
+ const qLimit = 1 / Math.max(k * amp * Math.pow(count, 0.7), 1e-6);
135
+ const q = choppiness * qLimit * (0.65 + 0.35 * steepness);
136
+ out.a[i].set(Math.sin(angle), Math.cos(angle), amp, k);
137
+ out.b[i].set(omega, hash1(i * 7.31 + 4.2) * Math.PI * 2, Math.min(q, qLimit * 1.6), wl);
138
+ }
139
+ for (let i = count; i < MAX_WAVES; i++) {
140
+ out.a[i].set(0, 0, 0, 1);
141
+ out.b[i].set(0, 0, 0, 1);
142
+ }
143
+ out.count = count;
144
+ return out;
145
+ }
146
+ /** Everything the table depends on. Rebuilding it costs a few hundred flops, so
147
+ * it is cheap — but it also has to be stable, or a live drag on an unrelated
148
+ * slider would re-roll every phase and the sea would jump. */
149
+ function tableSignature(c) {
150
+ return [
151
+ c.windSpeed, c.windBearing, c.fetch, c.directionalSpread,
152
+ c.waveComponents, c.choppiness, c.swell, c.steepness,
153
+ ].join(',');
154
+ }
155
+ const tableCache = new WeakMap();
156
+ const _color = new THREE.Color();
157
+ function setColor(u, key, hex) {
158
+ if (!u[key])
159
+ return;
160
+ u[key].value.copy(_color.set(hex));
161
+ }
162
+ /**
163
+ * Push a config into the material.
164
+ *
165
+ * Called every frame with the live overrides merged in, exactly as
166
+ * `applyTerrainUniforms` is — it is a few dozen assignments and it is what lets
167
+ * a slider drag reach the renderer without a Redux dispatch per pointer-move.
168
+ * The wave table is the one thing that is not recomputed blindly: it is rebuilt
169
+ * only when one of the eight values it depends on changes.
170
+ */
171
+ export function applyOceanUniforms(material, config, quality) {
172
+ const u = material.uniforms;
173
+ if (!u)
174
+ return;
175
+ const signature = tableSignature(config);
176
+ let cached = tableCache.get(material);
177
+ if (!cached) {
178
+ cached = { table: emptyTable(), signature: '' };
179
+ tableCache.set(material, cached);
180
+ }
181
+ if (cached.signature !== signature) {
182
+ buildWaveTable(config, cached.table);
183
+ cached.signature = signature;
184
+ u.u_waveA.value = cached.table.a;
185
+ u.u_waveB.value = cached.table.b;
186
+ u.u_waveCount.value = cached.table.count;
187
+ }
188
+ u.u_waterLevel.value = config.seaLevel;
189
+ u.u_choppiness.value = config.choppiness;
190
+ u.u_amplitudeScale.value = Math.max(config.amplitudeScale, 0);
191
+ const isBody = config.host === 'object';
192
+ u.u_bodyHalf.value.set(isBody ? Math.max(config.size[0], 0.01) / 2 : 0, isBody ? Math.max(config.size[1], 0.01) / 2 : 0);
193
+ u.u_edgeFade.value = config.edgeFade;
194
+ setColor(u, 'u_deepColor', config.deepColor);
195
+ setColor(u, 'u_shallowColor', config.shallowColor);
196
+ setColor(u, 'u_foamColor', config.foamColor);
197
+ setColor(u, 'u_sssColor', config.sssColor);
198
+ u.u_absorption.value = config.absorption;
199
+ u.u_clarity.value = config.clarity;
200
+ u.u_roughness.value = config.roughness;
201
+ u.u_specular.value = config.specular;
202
+ u.u_sssStrength.value = config.sssStrength;
203
+ u.u_detailStrength.value = config.detailStrength;
204
+ u.u_detailScale.value = 1 / Math.max(config.detailSize, 0.01);
205
+ u.u_foamAmount.value = config.foamAmount;
206
+ u.u_foamDecay.value = config.foamDecay;
207
+ u.u_foamScale.value = 1 / Math.max(config.foamSize, 0.05);
208
+ u.u_ambientScale.value = config.ambient;
209
+ u.u_fogDensity.value = config.aerialPerspective;
210
+ u.u_exposure.value = config.exposure;
211
+ u.u_detailFade.value = 1 / Math.max(quality.detailFadeDistance, 1);
212
+ u.u_curvature.value = quality.curvature;
213
+ u.u_receiveShadows.value = quality.receiveShadows ? 1 : 0;
214
+ material.wireframe = !!quality.wireframe;
215
+ }
216
+ export default applyOceanUniforms;
@@ -0,0 +1,9 @@
1
+ import type { MaterialSettings } from '@3driseai/3drise-core';
2
+ import type { ShaderEffect } from '@3driseai/3drise-core';
3
+ /** The one particles variant that is built dynamically rather than by the registry. */
4
+ export declare const SHADER_EFFECTS_VARIANT = "3dRiseShader";
5
+ /** True when at least one shader effect is switched on. */
6
+ export declare function hasActiveShaderEffects(effects?: ShaderEffect[] | null): boolean;
7
+ export declare function ownsShaderEffectsMaterial(materialSettings?: MaterialSettings | null, effects?: ShaderEffect[] | null): boolean;
8
+ export default ownsShaderEffectsMaterial;
9
+ //# sourceMappingURL=pointsMaterialOwnership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pointsMaterialOwnership.d.ts","sourceRoot":"","sources":["../../src/utils/pointsMaterialOwnership.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,uFAAuF;AACvF,eAAO,MAAM,sBAAsB,iBAAiB,CAAC;AAErD,2DAA2D;AAC3D,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,OAAO,CAE/E;AAED,wBAAgB,yBAAyB,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,OAAO,CAM9H;AAED,eAAe,yBAAyB,CAAC"}
@@ -0,0 +1,14 @@
1
+ /** The one particles variant that is built dynamically rather than by the registry. */
2
+ export const SHADER_EFFECTS_VARIANT = '3dRiseShader';
3
+ /** True when at least one shader effect is switched on. */
4
+ export function hasActiveShaderEffects(effects) {
5
+ return Array.isArray(effects) && effects.some((effect) => effect?.enabled);
6
+ }
7
+ export function ownsShaderEffectsMaterial(materialSettings, effects) {
8
+ // Effects only exist inside the shell, so their presence decides ownership.
9
+ if (hasActiveShaderEffects(effects))
10
+ return true;
11
+ const variant = materialSettings?.materialVariant;
12
+ return !variant || variant === SHADER_EFFECTS_VARIANT;
13
+ }
14
+ export default ownsShaderEffectsMaterial;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * RTIN — Right-Triangulated Irregular Networks.
3
+ *
4
+ * Vendored from Mapbox's MARTINI (https://github.com/mapbox/martini, ISC), which
5
+ * implements Evans, Kirkpatrick & Townsend, *Right-triangulated irregular
6
+ * networks* (2001). Rewritten in TypeScript with the two bugs the reference
7
+ * project's copy carries fixed, and with the winding flipped for a Y-up mesh.
8
+ *
9
+ * ## Why vendored rather than depended on
10
+ *
11
+ * `packages/*` reach the client as a hand-copied `dist`, and a fresh `npm i`
12
+ * wipes that copy — so every runtime dependency of this package is one more
13
+ * thing that has to exist in the client's `node_modules` at the right version.
14
+ * 250 lines with the source named is cheaper than that.
15
+ *
16
+ * ## What it does
17
+ *
18
+ * Given a square heightfield of size 2^n+1, it builds an error map over the
19
+ * implicit binary tree of right triangles: for every triangle, the height error
20
+ * introduced by *not* splitting it. `getMesh(maxError)` then walks that tree and
21
+ * emits only the triangles whose error exceeds the budget — dense over cliffs,
22
+ * nearly empty over flats, and crack-free by construction because a split is
23
+ * always a split of both children.
24
+ *
25
+ * The error map is the expensive half and depends only on the heightfield, so a
26
+ * `Tile` is built once per heightmap and `getMesh` is re-run whenever the error
27
+ * budget changes.
28
+ */
29
+ export interface RtinMesh {
30
+ /** Grid coordinates, 2 per vertex, in [0, size-1]. */
31
+ vertices: Uint16Array;
32
+ /** Triangle indices, 3 per face. */
33
+ triangles: Uint32Array;
34
+ }
35
+ export declare class Rtin {
36
+ readonly gridSize: number;
37
+ readonly numTriangles: number;
38
+ readonly numParentTriangles: number;
39
+ readonly indices: Uint32Array;
40
+ readonly coords: Uint16Array;
41
+ constructor(gridSize?: number);
42
+ createTile(terrain: Float32Array): RtinTile;
43
+ }
44
+ export declare class RtinTile {
45
+ readonly terrain: Float32Array;
46
+ readonly rtin: Rtin;
47
+ readonly errors: Float32Array;
48
+ constructor(terrain: Float32Array, rtin: Rtin);
49
+ /** Build the error map, smallest triangles first, so a parent can take the
50
+ * max of its own error and its two children's. */
51
+ private update;
52
+ /**
53
+ * Emit the mesh for an error budget, in the same units as the heightfield.
54
+ *
55
+ * Two passes over the same traversal: the first counts vertices and
56
+ * triangles so both arrays can be allocated exactly (the reference
57
+ * over-allocates from the maximum-resolution mesh and leaves the tail full
58
+ * of zeros); the second fills them.
59
+ */
60
+ getMesh(maxError?: number): RtinMesh;
61
+ }
62
+ export default Rtin;
63
+ //# sourceMappingURL=rtin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rtin.d.ts","sourceRoot":"","sources":["../../src/utils/rtin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,WAAW,QAAQ;IACrB,sDAAsD;IACtD,QAAQ,EAAE,WAAW,CAAA;IACrB,oCAAoC;IACpC,SAAS,EAAE,WAAW,CAAA;CACzB;AAED,qBAAa,IAAI;IACb,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;gBAEhB,QAAQ,SAAM;IAoD1B,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,QAAQ;CAG9C;AAED,qBAAa,QAAQ;IACjB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAC9B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;gBAEjB,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI;IAW7C;uDACmD;IACnD,OAAO,CAAC,MAAM;IAmCd;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,SAAI,GAAG,QAAQ;CAkElC;AAED,eAAe,IAAI,CAAA"}
@@ -0,0 +1,202 @@
1
+ /**
2
+ * RTIN — Right-Triangulated Irregular Networks.
3
+ *
4
+ * Vendored from Mapbox's MARTINI (https://github.com/mapbox/martini, ISC), which
5
+ * implements Evans, Kirkpatrick & Townsend, *Right-triangulated irregular
6
+ * networks* (2001). Rewritten in TypeScript with the two bugs the reference
7
+ * project's copy carries fixed, and with the winding flipped for a Y-up mesh.
8
+ *
9
+ * ## Why vendored rather than depended on
10
+ *
11
+ * `packages/*` reach the client as a hand-copied `dist`, and a fresh `npm i`
12
+ * wipes that copy — so every runtime dependency of this package is one more
13
+ * thing that has to exist in the client's `node_modules` at the right version.
14
+ * 250 lines with the source named is cheaper than that.
15
+ *
16
+ * ## What it does
17
+ *
18
+ * Given a square heightfield of size 2^n+1, it builds an error map over the
19
+ * implicit binary tree of right triangles: for every triangle, the height error
20
+ * introduced by *not* splitting it. `getMesh(maxError)` then walks that tree and
21
+ * emits only the triangles whose error exceeds the budget — dense over cliffs,
22
+ * nearly empty over flats, and crack-free by construction because a split is
23
+ * always a split of both children.
24
+ *
25
+ * The error map is the expensive half and depends only on the heightfield, so a
26
+ * `Tile` is built once per heightmap and `getMesh` is re-run whenever the error
27
+ * budget changes.
28
+ */
29
+ export class Rtin {
30
+ gridSize;
31
+ numTriangles;
32
+ numParentTriangles;
33
+ indices;
34
+ coords;
35
+ constructor(gridSize = 257) {
36
+ this.gridSize = gridSize;
37
+ const tileSize = gridSize - 1;
38
+ if (tileSize & (tileSize - 1)) {
39
+ throw new Error(`Expected grid size to be 2^n+1, got ${gridSize}.`);
40
+ }
41
+ this.numTriangles = tileSize * tileSize * 2 - 2;
42
+ this.numParentTriangles = this.numTriangles - tileSize * tileSize;
43
+ this.indices = new Uint32Array(gridSize * gridSize);
44
+ this.coords = new Uint16Array(this.numTriangles * 4);
45
+ // Triangle coordinates recovered from the index in the implicit binary
46
+ // tree: descend from the root pair, halving the hypotenuse each step.
47
+ for (let i = 0; i < this.numTriangles; i++) {
48
+ let id = i + 2;
49
+ let ax = 0;
50
+ let ay = 0;
51
+ let bx = 0;
52
+ let by = 0;
53
+ let cx = 0;
54
+ let cy = 0;
55
+ if (id & 1) {
56
+ bx = by = cx = tileSize;
57
+ }
58
+ else {
59
+ ax = ay = cy = tileSize;
60
+ }
61
+ while ((id >>= 1) > 1) {
62
+ const mx = (ax + bx) >> 1;
63
+ const my = (ay + by) >> 1;
64
+ if (id & 1) {
65
+ bx = ax;
66
+ by = ay;
67
+ ax = cx;
68
+ ay = cy;
69
+ }
70
+ else {
71
+ ax = bx;
72
+ ay = by;
73
+ bx = cx;
74
+ by = cy;
75
+ }
76
+ cx = mx;
77
+ cy = my;
78
+ }
79
+ const k = i * 4;
80
+ this.coords[k + 0] = ax;
81
+ this.coords[k + 1] = ay;
82
+ this.coords[k + 2] = bx;
83
+ this.coords[k + 3] = by;
84
+ }
85
+ }
86
+ createTile(terrain) {
87
+ return new RtinTile(terrain, this);
88
+ }
89
+ }
90
+ export class RtinTile {
91
+ terrain;
92
+ rtin;
93
+ errors;
94
+ constructor(terrain, rtin) {
95
+ const size = rtin.gridSize;
96
+ if (terrain.length !== size * size) {
97
+ throw new Error(`Expected terrain data of length ${size * size}, got ${terrain.length}.`);
98
+ }
99
+ this.terrain = terrain;
100
+ this.rtin = rtin;
101
+ this.errors = new Float32Array(terrain.length);
102
+ this.update();
103
+ }
104
+ /** Build the error map, smallest triangles first, so a parent can take the
105
+ * max of its own error and its two children's. */
106
+ update() {
107
+ const { numTriangles, numParentTriangles, coords, gridSize: size } = this.rtin;
108
+ const { terrain, errors } = this;
109
+ for (let i = numTriangles - 1; i >= 0; i--) {
110
+ const k = i * 4;
111
+ const ax = coords[k + 0];
112
+ const ay = coords[k + 1];
113
+ const bx = coords[k + 2];
114
+ const by = coords[k + 3];
115
+ const mx = (ax + bx) >> 1;
116
+ const my = (ay + by) >> 1;
117
+ const cx = mx + my - ay;
118
+ const cy = my + ax - mx;
119
+ // Error at the midpoint of the hypotenuse: what interpolating across
120
+ // it would get wrong.
121
+ const interpolated = (terrain[ay * size + ax] + terrain[by * size + bx]) / 2;
122
+ const middleIndex = my * size + mx;
123
+ const middleError = Math.abs(interpolated - terrain[middleIndex]);
124
+ errors[middleIndex] = Math.max(errors[middleIndex], middleError);
125
+ if (i < numParentTriangles) {
126
+ const leftChild = ((ay + cy) >> 1) * size + ((ax + cx) >> 1);
127
+ const rightChild = ((by + cy) >> 1) * size + ((bx + cx) >> 1);
128
+ errors[middleIndex] = Math.max(errors[middleIndex], errors[leftChild], errors[rightChild]);
129
+ }
130
+ }
131
+ }
132
+ /**
133
+ * Emit the mesh for an error budget, in the same units as the heightfield.
134
+ *
135
+ * Two passes over the same traversal: the first counts vertices and
136
+ * triangles so both arrays can be allocated exactly (the reference
137
+ * over-allocates from the maximum-resolution mesh and leaves the tail full
138
+ * of zeros); the second fills them.
139
+ */
140
+ getMesh(maxError = 0) {
141
+ const { gridSize: size, indices } = this.rtin;
142
+ const { errors } = this;
143
+ const max = size - 1;
144
+ let numVertices = 0;
145
+ let numTriangles = 0;
146
+ indices.fill(0);
147
+ const countElements = (ax, ay, bx, by, cx, cy) => {
148
+ const mx = (ax + bx) >> 1;
149
+ const my = (ay + by) >> 1;
150
+ if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) {
151
+ countElements(cx, cy, ax, ay, mx, my);
152
+ countElements(bx, by, cx, cy, mx, my);
153
+ }
154
+ else {
155
+ indices[ay * size + ax] = indices[ay * size + ax] || ++numVertices;
156
+ indices[by * size + bx] = indices[by * size + bx] || ++numVertices;
157
+ indices[cy * size + cx] = indices[cy * size + cx] || ++numVertices;
158
+ numTriangles++;
159
+ }
160
+ };
161
+ countElements(0, 0, max, max, max, 0);
162
+ countElements(max, max, 0, 0, 0, max);
163
+ const vertices = new Uint16Array(numVertices * 2);
164
+ const triangles = new Uint32Array(numTriangles * 3);
165
+ let triIndex = 0;
166
+ const processTriangle = (ax, ay, bx, by, cx, cy) => {
167
+ const mx = (ax + bx) >> 1;
168
+ const my = (ay + by) >> 1;
169
+ if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) {
170
+ processTriangle(cx, cy, ax, ay, mx, my);
171
+ processTriangle(bx, by, cx, cy, mx, my);
172
+ }
173
+ else {
174
+ const a = indices[ay * size + ax] - 1;
175
+ const b = indices[by * size + bx] - 1;
176
+ const c = indices[cy * size + cx] - 1;
177
+ vertices[2 * a] = ax;
178
+ vertices[2 * a + 1] = ay;
179
+ vertices[2 * b] = bx;
180
+ vertices[2 * b + 1] = by;
181
+ vertices[2 * c] = cx;
182
+ vertices[2 * c + 1] = cy;
183
+ // Winding is emitted as RTIN produces it, and that is correct
184
+ // for grid Y mapped to world +Z. Reasoning about the handedness
185
+ // flip on paper gives the opposite answer and it is wrong: the
186
+ // first version reversed each triangle here, and the tile was
187
+ // then visible only from BELOW because every face was a
188
+ // backface. Found by rendering it, not by reading it. The
189
+ // reference project reverses the whole index array in place
190
+ // instead, which mutates the tile's own buffer and flips back
191
+ // the second time it runs.
192
+ triangles[triIndex++] = a;
193
+ triangles[triIndex++] = b;
194
+ triangles[triIndex++] = c;
195
+ }
196
+ };
197
+ processTriangle(0, 0, max, max, max, 0);
198
+ processTriangle(max, max, 0, 0, 0, max);
199
+ return { vertices, triangles };
200
+ }
201
+ }
202
+ export default Rtin;
@@ -0,0 +1,14 @@
1
+ import * as THREE from 'three';
2
+ import type { TerrainConfig, TerrainQualityConfig } from '../types/terrainConfigs';
3
+ import type { CloudSunState } from '../hooks/useCloudSun';
4
+ export declare function applyTerrainUniforms(material: THREE.ShaderMaterial, config: TerrainConfig, quality: TerrainQualityConfig): void;
5
+ export default applyTerrainUniforms;
6
+ /** skyRadiance(rd), in the units the shader returns. */
7
+ export declare function skyRadianceAt(rd: THREE.Vector3, sun: CloudSunState, out: THREE.Color): THREE.Color;
8
+ /**
9
+ * Sets the two ambient uniforms. Call after `applySunUniforms`, which fills them
10
+ * from the cloud curve — this overwrites that with the sky the terrain is actually
11
+ * standing under.
12
+ */
13
+ export declare function applyTerrainAmbient(material: THREE.ShaderMaterial, sun: CloudSunState): void;
14
+ //# sourceMappingURL=terrainUniforms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terrainUniforms.d.ts","sourceRoot":"","sources":["../../src/utils/terrainUniforms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAe,MAAM,yBAAyB,CAAC;AAChG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAoD1D,wBAAgB,oBAAoB,CAChC,QAAQ,EAAE,KAAK,CAAC,cAAc,EAC9B,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,oBAAoB,GAC9B,IAAI,CAwDN;AAED,eAAe,oBAAoB,CAAC;AAiCpC,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CA4BlG;AAMD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,cAAc,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CAwC5F"}