@3driseai/3drise-viewer 0.1.7 → 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 (259) 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/EnvironmentManager.d.ts +47 -5
  7. package/dist/components/EnvironmentManager.d.ts.map +1 -1
  8. package/dist/components/EnvironmentManager.js +35 -66
  9. package/dist/components/HdrEnvironment.d.ts +20 -0
  10. package/dist/components/HdrEnvironment.d.ts.map +1 -0
  11. package/dist/components/HdrEnvironment.js +44 -0
  12. package/dist/components/LandscapeBuilder.d.ts +23 -0
  13. package/dist/components/LandscapeBuilder.d.ts.map +1 -0
  14. package/dist/components/LandscapeBuilder.js +25 -0
  15. package/dist/components/LandscapeObject.d.ts +24 -0
  16. package/dist/components/LandscapeObject.d.ts.map +1 -0
  17. package/dist/components/LandscapeObject.js +27 -0
  18. package/dist/components/ObjectNode.d.ts.map +1 -1
  19. package/dist/components/ObjectNode.js +10 -0
  20. package/dist/components/OceanBuilder.d.ts +35 -0
  21. package/dist/components/OceanBuilder.d.ts.map +1 -0
  22. package/dist/components/OceanBuilder.js +53 -0
  23. package/dist/components/SceneBuilder.d.ts.map +1 -1
  24. package/dist/components/SkySystem.d.ts +27 -6
  25. package/dist/components/SkySystem.d.ts.map +1 -1
  26. package/dist/components/SkySystem.js +6 -5
  27. package/dist/components/SunLight.d.ts.map +1 -1
  28. package/dist/components/SunLight.js +4 -1
  29. package/dist/components/TerrainBuilder.d.ts +30 -0
  30. package/dist/components/TerrainBuilder.d.ts.map +1 -0
  31. package/dist/components/TerrainBuilder.js +44 -0
  32. package/dist/components/index.d.ts +17 -1
  33. package/dist/components/index.d.ts.map +1 -1
  34. package/dist/components/index.js +10 -0
  35. package/dist/components/landscape/LandscapeMesh.d.ts +18 -0
  36. package/dist/components/landscape/LandscapeMesh.d.ts.map +1 -0
  37. package/dist/components/landscape/LandscapeMesh.js +278 -0
  38. package/dist/components/landscape/index.d.ts +3 -0
  39. package/dist/components/landscape/index.d.ts.map +1 -0
  40. package/dist/components/landscape/index.js +1 -0
  41. package/dist/components/landscape/types.d.ts +13 -0
  42. package/dist/components/landscape/types.d.ts.map +1 -0
  43. package/dist/components/landscape/types.js +1 -0
  44. package/dist/components/terrain/Canyon.d.ts +23 -0
  45. package/dist/components/terrain/Canyon.d.ts.map +1 -0
  46. package/dist/components/terrain/Canyon.js +29 -0
  47. package/dist/components/terrain/Dunes.d.ts +22 -0
  48. package/dist/components/terrain/Dunes.d.ts.map +1 -0
  49. package/dist/components/terrain/Dunes.js +25 -0
  50. package/dist/components/terrain/Hills.d.ts +17 -0
  51. package/dist/components/terrain/Hills.d.ts.map +1 -0
  52. package/dist/components/terrain/Hills.js +20 -0
  53. package/dist/components/terrain/Mountains.d.ts +19 -0
  54. package/dist/components/terrain/Mountains.d.ts.map +1 -0
  55. package/dist/components/terrain/Mountains.js +22 -0
  56. package/dist/components/terrain/TerrainSurface.d.ts +22 -0
  57. package/dist/components/terrain/TerrainSurface.d.ts.map +1 -0
  58. package/dist/components/terrain/TerrainSurface.js +94 -0
  59. package/dist/components/terrain/index.d.ts +7 -0
  60. package/dist/components/terrain/index.d.ts.map +1 -0
  61. package/dist/components/terrain/index.js +7 -0
  62. package/dist/components/terrain/types.d.ts +17 -0
  63. package/dist/components/terrain/types.d.ts.map +1 -0
  64. package/dist/components/terrain/types.js +1 -0
  65. package/dist/components/volumetricClouds/Cirrus.d.ts +14 -0
  66. package/dist/components/volumetricClouds/Cirrus.d.ts.map +1 -0
  67. package/dist/components/volumetricClouds/Cirrus.js +13 -0
  68. package/dist/components/volumetricClouds/Cumulonimbus.d.ts +17 -0
  69. package/dist/components/volumetricClouds/Cumulonimbus.d.ts.map +1 -0
  70. package/dist/components/volumetricClouds/Cumulonimbus.js +20 -0
  71. package/dist/components/volumetricClouds/Cumulus.d.ts +14 -0
  72. package/dist/components/volumetricClouds/Cumulus.d.ts.map +1 -0
  73. package/dist/components/volumetricClouds/Cumulus.js +17 -0
  74. package/dist/components/volumetricClouds/Stratocumulus.d.ts +12 -0
  75. package/dist/components/volumetricClouds/Stratocumulus.d.ts.map +1 -0
  76. package/dist/components/volumetricClouds/Stratocumulus.js +16 -0
  77. package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts +42 -0
  78. package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts.map +1 -0
  79. package/dist/components/volumetricClouds/VolumetricCloudsPass.js +298 -0
  80. package/dist/components/volumetricClouds/index.d.ts +8 -0
  81. package/dist/components/volumetricClouds/index.d.ts.map +1 -0
  82. package/dist/components/volumetricClouds/index.js +7 -0
  83. package/dist/components/volumetricClouds/types.d.ts +17 -0
  84. package/dist/components/volumetricClouds/types.d.ts.map +1 -0
  85. package/dist/components/volumetricClouds/types.js +1 -0
  86. package/dist/components/water/Host.d.ts +12 -0
  87. package/dist/components/water/Host.d.ts.map +1 -0
  88. package/dist/components/water/Host.js +12 -0
  89. package/dist/components/water/Lake.d.ts +18 -0
  90. package/dist/components/water/Lake.d.ts.map +1 -0
  91. package/dist/components/water/Lake.js +21 -0
  92. package/dist/components/water/OceanSurface.d.ts +27 -0
  93. package/dist/components/water/OceanSurface.d.ts.map +1 -0
  94. package/dist/components/water/OceanSurface.js +60 -0
  95. package/dist/components/water/Sea.d.ts +18 -0
  96. package/dist/components/water/Sea.d.ts.map +1 -0
  97. package/dist/components/water/Sea.js +17 -0
  98. package/dist/components/water/Shore.d.ts +16 -0
  99. package/dist/components/water/Shore.d.ts.map +1 -0
  100. package/dist/components/water/Shore.js +15 -0
  101. package/dist/components/water/Storm.d.ts +14 -0
  102. package/dist/components/water/Storm.d.ts.map +1 -0
  103. package/dist/components/water/Storm.js +13 -0
  104. package/dist/components/water/WaterBody.d.ts +31 -0
  105. package/dist/components/water/WaterBody.d.ts.map +1 -0
  106. package/dist/components/water/WaterBody.js +76 -0
  107. package/dist/components/water/index.d.ts +9 -0
  108. package/dist/components/water/index.d.ts.map +1 -0
  109. package/dist/components/water/index.js +11 -0
  110. package/dist/components/water/types.d.ts +20 -0
  111. package/dist/components/water/types.d.ts.map +1 -0
  112. package/dist/components/water/types.js +1 -0
  113. package/dist/constants.d.ts.map +1 -1
  114. package/dist/constants.js +9 -0
  115. package/dist/hooks/index.d.ts +24 -1
  116. package/dist/hooks/index.d.ts.map +1 -1
  117. package/dist/hooks/index.js +12 -0
  118. package/dist/hooks/useClipmap.d.ts +71 -0
  119. package/dist/hooks/useClipmap.d.ts.map +1 -0
  120. package/dist/hooks/useClipmap.js +81 -0
  121. package/dist/hooks/useCloudLive.d.ts +29 -0
  122. package/dist/hooks/useCloudLive.d.ts.map +1 -0
  123. package/dist/hooks/useCloudLive.js +43 -0
  124. package/dist/hooks/useCloudNoise.d.ts +28 -0
  125. package/dist/hooks/useCloudNoise.d.ts.map +1 -0
  126. package/dist/hooks/useCloudNoise.js +128 -0
  127. package/dist/hooks/useCloudSun.d.ts +49 -0
  128. package/dist/hooks/useCloudSun.d.ts.map +1 -0
  129. package/dist/hooks/useCloudSun.js +84 -0
  130. package/dist/hooks/useCubeCamera.d.ts +27 -9
  131. package/dist/hooks/useCubeCamera.d.ts.map +1 -1
  132. package/dist/hooks/useCubeCamera.js +28 -70
  133. package/dist/hooks/useEnvironmentProbe.d.ts +32 -0
  134. package/dist/hooks/useEnvironmentProbe.d.ts.map +1 -0
  135. package/dist/hooks/useEnvironmentProbe.js +35 -0
  136. package/dist/hooks/useFrameRenderer.d.ts +15 -0
  137. package/dist/hooks/useFrameRenderer.d.ts.map +1 -0
  138. package/dist/hooks/useFrameRenderer.js +23 -0
  139. package/dist/hooks/useLandscapeLive.d.ts +29 -0
  140. package/dist/hooks/useLandscapeLive.d.ts.map +1 -0
  141. package/dist/hooks/useLandscapeLive.js +41 -0
  142. package/dist/hooks/useLandscapeMaps.d.ts +32 -0
  143. package/dist/hooks/useLandscapeMaps.d.ts.map +1 -0
  144. package/dist/hooks/useLandscapeMaps.js +138 -0
  145. package/dist/hooks/useOceanLive.d.ts +29 -0
  146. package/dist/hooks/useOceanLive.d.ts.map +1 -0
  147. package/dist/hooks/useOceanLive.js +38 -0
  148. package/dist/hooks/useOceanMaterial.d.ts +39 -0
  149. package/dist/hooks/useOceanMaterial.d.ts.map +1 -0
  150. package/dist/hooks/useOceanMaterial.js +65 -0
  151. package/dist/hooks/useTerrainClipmap.d.ts +12 -0
  152. package/dist/hooks/useTerrainClipmap.d.ts.map +1 -0
  153. package/dist/hooks/useTerrainClipmap.js +10 -0
  154. package/dist/hooks/useTerrainLive.d.ts +34 -0
  155. package/dist/hooks/useTerrainLive.d.ts.map +1 -0
  156. package/dist/hooks/useTerrainLive.js +45 -0
  157. package/dist/index.d.ts +26 -0
  158. package/dist/index.d.ts.map +1 -1
  159. package/dist/index.js +16 -0
  160. package/dist/presets/landscapePresets.d.ts +60 -0
  161. package/dist/presets/landscapePresets.d.ts.map +1 -0
  162. package/dist/presets/landscapePresets.js +194 -0
  163. package/dist/presets/landscapeTypes.d.ts +195 -0
  164. package/dist/presets/landscapeTypes.d.ts.map +1 -0
  165. package/dist/presets/landscapeTypes.js +315 -0
  166. package/dist/presets/oceanSurfacePresets.d.ts +33 -0
  167. package/dist/presets/oceanSurfacePresets.d.ts.map +1 -0
  168. package/dist/presets/oceanSurfacePresets.js +245 -0
  169. package/dist/presets/terrainPresets.d.ts +28 -0
  170. package/dist/presets/terrainPresets.d.ts.map +1 -0
  171. package/dist/presets/terrainPresets.js +293 -0
  172. package/dist/presets/volumetricCloudPresets.d.ts +38 -0
  173. package/dist/presets/volumetricCloudPresets.d.ts.map +1 -0
  174. package/dist/presets/volumetricCloudPresets.js +480 -0
  175. package/dist/shaders/clouds/CirrusMaterial.d.ts +84 -0
  176. package/dist/shaders/clouds/CirrusMaterial.d.ts.map +1 -0
  177. package/dist/shaders/clouds/CirrusMaterial.js +167 -0
  178. package/dist/shaders/clouds/CloudCompositeMaterial.d.ts +20 -0
  179. package/dist/shaders/clouds/CloudCompositeMaterial.d.ts.map +1 -0
  180. package/dist/shaders/clouds/CloudCompositeMaterial.js +82 -0
  181. package/dist/shaders/clouds/CloudResolveMaterial.d.ts +22 -0
  182. package/dist/shaders/clouds/CloudResolveMaterial.d.ts.map +1 -0
  183. package/dist/shaders/clouds/CloudResolveMaterial.js +77 -0
  184. package/dist/shaders/clouds/CloudVolumeMaterial.d.ts +143 -0
  185. package/dist/shaders/clouds/CloudVolumeMaterial.d.ts.map +1 -0
  186. package/dist/shaders/clouds/CloudVolumeMaterial.js +249 -0
  187. package/dist/shaders/clouds/SkyBackgroundMaterial.d.ts +21 -0
  188. package/dist/shaders/clouds/SkyBackgroundMaterial.d.ts.map +1 -0
  189. package/dist/shaders/clouds/SkyBackgroundMaterial.js +42 -0
  190. package/dist/shaders/glsl/cloudCore.glsl.d.ts +24 -0
  191. package/dist/shaders/glsl/cloudCore.glsl.d.ts.map +1 -0
  192. package/dist/shaders/glsl/cloudCore.glsl.js +348 -0
  193. package/dist/shaders/glsl/landscapeCore.glsl.d.ts +49 -0
  194. package/dist/shaders/glsl/landscapeCore.glsl.d.ts.map +1 -0
  195. package/dist/shaders/glsl/landscapeCore.glsl.js +518 -0
  196. package/dist/shaders/glsl/noiseBake.glsl.d.ts +30 -0
  197. package/dist/shaders/glsl/noiseBake.glsl.d.ts.map +1 -0
  198. package/dist/shaders/glsl/noiseBake.glsl.js +164 -0
  199. package/dist/shaders/glsl/oceanCore.glsl.d.ts +52 -0
  200. package/dist/shaders/glsl/oceanCore.glsl.d.ts.map +1 -0
  201. package/dist/shaders/glsl/oceanCore.glsl.js +421 -0
  202. package/dist/shaders/glsl/terrainCore.glsl.d.ts +78 -0
  203. package/dist/shaders/glsl/terrainCore.glsl.d.ts.map +1 -0
  204. package/dist/shaders/glsl/terrainCore.glsl.js +625 -0
  205. package/dist/shaders/landscape/LandscapeMaterial.d.ts +73 -0
  206. package/dist/shaders/landscape/LandscapeMaterial.d.ts.map +1 -0
  207. package/dist/shaders/landscape/LandscapeMaterial.js +136 -0
  208. package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts +57 -0
  209. package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts.map +1 -0
  210. package/dist/shaders/ocean/OceanSurfaceMaterial.js +268 -0
  211. package/dist/shaders/terrain/TerrainMaterial.d.ts +80 -0
  212. package/dist/shaders/terrain/TerrainMaterial.d.ts.map +1 -0
  213. package/dist/shaders/terrain/TerrainMaterial.js +291 -0
  214. package/dist/types/cloudConfigs.d.ts +244 -0
  215. package/dist/types/cloudConfigs.d.ts.map +1 -0
  216. package/dist/types/cloudConfigs.js +282 -0
  217. package/dist/types/index.d.ts +8 -0
  218. package/dist/types/index.d.ts.map +1 -1
  219. package/dist/types/index.js +4 -1
  220. package/dist/types/landscapeConfigs.d.ts +218 -0
  221. package/dist/types/landscapeConfigs.d.ts.map +1 -0
  222. package/dist/types/landscapeConfigs.js +256 -0
  223. package/dist/types/oceanConfigs.d.ts +290 -0
  224. package/dist/types/oceanConfigs.d.ts.map +1 -0
  225. package/dist/types/oceanConfigs.js +380 -0
  226. package/dist/types/terrainConfigs.d.ts +251 -0
  227. package/dist/types/terrainConfigs.d.ts.map +1 -0
  228. package/dist/types/terrainConfigs.js +292 -0
  229. package/dist/utils/cloudUniforms.d.ts +14 -0
  230. package/dist/utils/cloudUniforms.d.ts.map +1 -0
  231. package/dist/utils/cloudUniforms.js +122 -0
  232. package/dist/utils/frameRenderer.d.ts +33 -0
  233. package/dist/utils/frameRenderer.d.ts.map +1 -0
  234. package/dist/utils/frameRenderer.js +24 -0
  235. package/dist/utils/handleCloudsAnimations.d.ts +16 -0
  236. package/dist/utils/handleCloudsAnimations.d.ts.map +1 -0
  237. package/dist/utils/handleCloudsAnimations.js +64 -0
  238. package/dist/utils/landscapeGeometry.d.ts +61 -0
  239. package/dist/utils/landscapeGeometry.d.ts.map +1 -0
  240. package/dist/utils/landscapeGeometry.js +126 -0
  241. package/dist/utils/landscapeHeight.d.ts +44 -0
  242. package/dist/utils/landscapeHeight.d.ts.map +1 -0
  243. package/dist/utils/landscapeHeight.js +65 -0
  244. package/dist/utils/landscapeTextures.d.ts +94 -0
  245. package/dist/utils/landscapeTextures.d.ts.map +1 -0
  246. package/dist/utils/landscapeTextures.js +290 -0
  247. package/dist/utils/landscapeUniforms.d.ts +20 -0
  248. package/dist/utils/landscapeUniforms.d.ts.map +1 -0
  249. package/dist/utils/landscapeUniforms.js +61 -0
  250. package/dist/utils/oceanUniforms.d.ts +47 -0
  251. package/dist/utils/oceanUniforms.d.ts.map +1 -0
  252. package/dist/utils/oceanUniforms.js +216 -0
  253. package/dist/utils/rtin.d.ts +63 -0
  254. package/dist/utils/rtin.d.ts.map +1 -0
  255. package/dist/utils/rtin.js +202 -0
  256. package/dist/utils/terrainUniforms.d.ts +14 -0
  257. package/dist/utils/terrainUniforms.d.ts.map +1 -0
  258. package/dist/utils/terrainUniforms.js +196 -0
  259. package/package.json +2 -2
@@ -0,0 +1,126 @@
1
+ import * as THREE from 'three';
2
+ import { Rtin } from './rtin.js';
3
+ const tileCache = new WeakMap();
4
+ /** The error map depends only on the heightfield, so it is built once per
5
+ * decoded heightmap and reused for every error budget. */
6
+ export function rtinTileFor(height) {
7
+ const cached = tileCache.get(height);
8
+ if (cached)
9
+ return cached;
10
+ const tile = new Rtin(height.gridSize).createTile(height.data);
11
+ tileCache.set(height, tile);
12
+ return tile;
13
+ }
14
+ export function buildLandscapeGeometry(height, options) {
15
+ const { size, maxError } = options;
16
+ const chunksPerSide = Math.max(1, Math.min(16, Math.round(options.chunks)));
17
+ const tile = rtinTileFor(height);
18
+ const mesh = tile.getMesh(Math.max(0, maxError));
19
+ const grid = height.gridSize;
20
+ const tileSize = height.tileSize;
21
+ const vertexCount = mesh.vertices.length / 2;
22
+ const positions = new Float32Array(vertexCount * 3);
23
+ const uvs = new Float32Array(vertexCount * 2);
24
+ for (let i = 0; i < vertexCount; i++) {
25
+ const gx = mesh.vertices[i * 2];
26
+ const gy = mesh.vertices[i * 2 + 1];
27
+ const u = gx / tileSize;
28
+ const v = gy / tileSize;
29
+ positions[i * 3 + 0] = (u - 0.5) * size;
30
+ positions[i * 3 + 1] = 0;
31
+ positions[i * 3 + 2] = (v - 0.5) * size;
32
+ uvs[i * 2 + 0] = u;
33
+ uvs[i * 2 + 1] = v;
34
+ }
35
+ const position = new THREE.BufferAttribute(positions, 3);
36
+ const uv = new THREE.BufferAttribute(uvs, 2);
37
+ // Bucket triangles by the chunk their centroid falls in.
38
+ const buckets = Array.from({ length: chunksPerSide * chunksPerSide }, () => []);
39
+ const triangleCount = mesh.triangles.length / 3;
40
+ for (let t = 0; t < triangleCount; t++) {
41
+ const a = mesh.triangles[t * 3];
42
+ const b = mesh.triangles[t * 3 + 1];
43
+ const c = mesh.triangles[t * 3 + 2];
44
+ const cx = (mesh.vertices[a * 2] + mesh.vertices[b * 2] + mesh.vertices[c * 2]) / 3;
45
+ const cy = (mesh.vertices[a * 2 + 1] + mesh.vertices[b * 2 + 1] + mesh.vertices[c * 2 + 1]) / 3;
46
+ const ix = Math.min(chunksPerSide - 1, Math.floor((cx / tileSize) * chunksPerSide));
47
+ const iy = Math.min(chunksPerSide - 1, Math.floor((cy / tileSize) * chunksPerSide));
48
+ buckets[iy * chunksPerSide + ix].push(a, b, c);
49
+ }
50
+ const chunks = [];
51
+ for (const indices of buckets) {
52
+ if (indices.length === 0)
53
+ continue;
54
+ const geometry = new THREE.BufferGeometry();
55
+ geometry.setAttribute('position', position);
56
+ geometry.setAttribute('uv', uv);
57
+ geometry.setIndex(new THREE.BufferAttribute(new Uint32Array(indices), 1));
58
+ let minH01 = Infinity;
59
+ let maxH01 = -Infinity;
60
+ let minX = Infinity;
61
+ let maxX = -Infinity;
62
+ let minZ = Infinity;
63
+ let maxZ = -Infinity;
64
+ for (const index of indices) {
65
+ const gx = mesh.vertices[index * 2];
66
+ const gy = mesh.vertices[index * 2 + 1];
67
+ const h = height.data[gy * grid + gx];
68
+ if (h < minH01)
69
+ minH01 = h;
70
+ if (h > maxH01)
71
+ maxH01 = h;
72
+ const x = positions[index * 3];
73
+ const z = positions[index * 3 + 2];
74
+ if (x < minX)
75
+ minX = x;
76
+ if (x > maxX)
77
+ maxX = x;
78
+ if (z < minZ)
79
+ minZ = z;
80
+ if (z > maxZ)
81
+ maxZ = z;
82
+ }
83
+ // Placeholder Y; setLandscapeBounds writes the real range once the
84
+ // elevation scale is known, and again whenever it changes.
85
+ geometry.boundingBox = new THREE.Box3(new THREE.Vector3(minX, 0, minZ), new THREE.Vector3(maxX, 1, maxZ));
86
+ geometry.boundingSphere = new THREE.Sphere();
87
+ geometry.boundingBox.getBoundingSphere(geometry.boundingSphere);
88
+ chunks.push({ geometry, minH01, maxH01 });
89
+ }
90
+ return {
91
+ chunks,
92
+ position,
93
+ uv,
94
+ vertexCount,
95
+ triangleCount,
96
+ size,
97
+ dispose() {
98
+ for (const chunk of chunks) {
99
+ // Drop the shared attributes first so disposing the geometry
100
+ // cannot free them once per chunk.
101
+ chunk.geometry.deleteAttribute('position');
102
+ chunk.geometry.deleteAttribute('uv');
103
+ chunk.geometry.dispose();
104
+ }
105
+ },
106
+ };
107
+ }
108
+ /** Rewrite the vertical extent of every chunk's bounds. Called whenever the
109
+ * elevation scale or bias changes; without it a raised landscape culls against
110
+ * the flat plate it was built as and disappears when the camera tilts. */
111
+ export function setLandscapeBounds(geometry, scale, bias) {
112
+ for (const chunk of geometry.chunks) {
113
+ const box = chunk.geometry.boundingBox;
114
+ if (!box)
115
+ continue;
116
+ box.min.y = chunk.minH01 * scale + bias;
117
+ box.max.y = chunk.maxH01 * scale + bias;
118
+ // A degenerate box makes the sphere radius 0 and culls the chunk at
119
+ // grazing angles.
120
+ if (box.max.y - box.min.y < 1e-3)
121
+ box.max.y = box.min.y + 1e-3;
122
+ if (!chunk.geometry.boundingSphere)
123
+ chunk.geometry.boundingSphere = new THREE.Sphere();
124
+ box.getBoundingSphere(chunk.geometry.boundingSphere);
125
+ }
126
+ }
@@ -0,0 +1,44 @@
1
+ import * as THREE from 'three';
2
+ /**
3
+ * CPU height queries against a landscape.
4
+ *
5
+ * The procedural terrain has never had this — its height field exists only in
6
+ * the shader, so nothing can be placed *on* it programmatically (terrain doc,
7
+ * open item #5). A landscape gets it for nothing, because RTIN already decoded
8
+ * the heightmap into a `Float32Array` on the way to building the mesh.
9
+ *
10
+ * What it is for: snapping a placed object to the ground, keeping a camera above
11
+ * it, and later letting the water read a landscape's shoreline the way the ocean
12
+ * plan reads the terrain's.
13
+ */
14
+ export interface LandscapeHeightField {
15
+ /** Normalised 0..1 samples, `gridSize * gridSize`, row-major. */
16
+ data: Float32Array;
17
+ gridSize: number;
18
+ tileSize: number;
19
+ /** World units across the tile. */
20
+ size: number;
21
+ /** World centre of the tile. */
22
+ origin: THREE.Vector3;
23
+ /** Y rotation applied to the tile, in radians. */
24
+ rotationY: number;
25
+ elevationScale: number;
26
+ elevationBias: number;
27
+ }
28
+ export declare function registerLandscapeHeightField(id: string, field: LandscapeHeightField): void;
29
+ export declare function unregisterLandscapeHeightField(id: string): void;
30
+ /**
31
+ * World height at a world XZ, or null when the point is off the tile.
32
+ *
33
+ * Bilinear against the heightmap itself rather than against the mesh, so the
34
+ * answer does not change when the error budget does — the mesh is an
35
+ * approximation of this, not the other way round.
36
+ */
37
+ export declare function sampleLandscapeHeight(field: LandscapeHeightField, x: number, z: number): number | null;
38
+ /** Highest landscape under a world XZ, across every registered landscape.
39
+ * Returns null when no landscape covers the point. */
40
+ export declare function getLandscapeHeightAt(x: number, z: number): number | null;
41
+ /** Drop an object onto the ground, keeping its XZ. No-op when nothing is under
42
+ * it, so calling this in a scene with no landscape is safe. */
43
+ export declare function snapToLandscape(object: THREE.Object3D, offset?: number): boolean;
44
+ //# sourceMappingURL=landscapeHeight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landscapeHeight.d.ts","sourceRoot":"","sources":["../../src/utils/landscapeHeight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,oBAAoB;IACjC,iEAAiE;IACjE,IAAI,EAAE,YAAY,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,gCAAgC;IAChC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAA;IACrB,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;CACxB;AAID,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAE1F;AAED,wBAAgB,8BAA8B,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE/D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,oBAAoB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgCtG;AAED;uDACuD;AACvD,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOxE;AAED;gEACgE;AAChE,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE,MAAM,SAAI,GAAG,OAAO,CAK3E"}
@@ -0,0 +1,65 @@
1
+ const fields = new Map();
2
+ export function registerLandscapeHeightField(id, field) {
3
+ fields.set(id, field);
4
+ }
5
+ export function unregisterLandscapeHeightField(id) {
6
+ fields.delete(id);
7
+ }
8
+ /**
9
+ * World height at a world XZ, or null when the point is off the tile.
10
+ *
11
+ * Bilinear against the heightmap itself rather than against the mesh, so the
12
+ * answer does not change when the error budget does — the mesh is an
13
+ * approximation of this, not the other way round.
14
+ */
15
+ export function sampleLandscapeHeight(field, x, z) {
16
+ let dx = x - field.origin.x;
17
+ let dz = z - field.origin.z;
18
+ if (field.rotationY !== 0) {
19
+ const c = Math.cos(-field.rotationY);
20
+ const s = Math.sin(-field.rotationY);
21
+ const rx = dx * c - dz * s;
22
+ const rz = dx * s + dz * c;
23
+ dx = rx;
24
+ dz = rz;
25
+ }
26
+ const u = dx / field.size + 0.5;
27
+ const v = dz / field.size + 0.5;
28
+ if (u < 0 || u > 1 || v < 0 || v > 1)
29
+ return null;
30
+ const fx = u * field.tileSize;
31
+ const fy = v * field.tileSize;
32
+ const x0 = Math.min(field.tileSize, Math.floor(fx));
33
+ const y0 = Math.min(field.tileSize, Math.floor(fy));
34
+ const x1 = Math.min(field.gridSize - 1, x0 + 1);
35
+ const y1 = Math.min(field.gridSize - 1, y0 + 1);
36
+ const tx = fx - x0;
37
+ const ty = fy - y0;
38
+ const g = field.gridSize;
39
+ const h00 = field.data[y0 * g + x0];
40
+ const h10 = field.data[y0 * g + x1];
41
+ const h01 = field.data[y1 * g + x0];
42
+ const h11 = field.data[y1 * g + x1];
43
+ const h = (h00 * (1 - tx) + h10 * tx) * (1 - ty) + (h01 * (1 - tx) + h11 * tx) * ty;
44
+ return h * field.elevationScale + field.elevationBias + field.origin.y;
45
+ }
46
+ /** Highest landscape under a world XZ, across every registered landscape.
47
+ * Returns null when no landscape covers the point. */
48
+ export function getLandscapeHeightAt(x, z) {
49
+ let best = null;
50
+ for (const field of fields.values()) {
51
+ const h = sampleLandscapeHeight(field, x, z);
52
+ if (h !== null && (best === null || h > best))
53
+ best = h;
54
+ }
55
+ return best;
56
+ }
57
+ /** Drop an object onto the ground, keeping its XZ. No-op when nothing is under
58
+ * it, so calling this in a scene with no landscape is safe. */
59
+ export function snapToLandscape(object, offset = 0) {
60
+ const h = getLandscapeHeightAt(object.position.x, object.position.z);
61
+ if (h === null)
62
+ return false;
63
+ object.position.y = h + offset;
64
+ return true;
65
+ }
@@ -0,0 +1,94 @@
1
+ import * as THREE from 'three';
2
+ export declare function loadImage(url: string): Promise<HTMLImageElement>;
3
+ /** Largest power of two at or below n, clamped to something RTIN can chew. */
4
+ export declare function floorPowerOfTwo(n: number): number;
5
+ export type HeightEncodingName = 'grayscale' | 'terrain-rgb' | 'rg16';
6
+ export interface DecodedHeightmap {
7
+ /** (size+1)^2 samples, normalised to 0..1, row-major, right and bottom edges
8
+ * backfilled so RTIN gets its 2^n+1 grid. */
9
+ data: Float32Array;
10
+ /** RTIN grid size — `tileSize + 1`. */
11
+ gridSize: number;
12
+ /** Heightmap resolution the grid was built from, a power of two. */
13
+ tileSize: number;
14
+ /** Source-unit range, before normalisation. Metres for terrain-rgb. */
15
+ min: number;
16
+ max: number;
17
+ }
18
+ /**
19
+ * Heightmap pixels to a normalised RTIN grid.
20
+ *
21
+ * Normalising to 0..1 here rather than baking `elevationScale` in is what lets
22
+ * the error map be built once: RTIN's error scales linearly with the height
23
+ * scale, so a change of scale is a change of error budget, not a new error map.
24
+ */
25
+ export declare function decodeHeightmap(image: HTMLImageElement, encoding: HeightEncodingName): DecodedHeightmap;
26
+ export interface TextureArrayResult {
27
+ texture: THREE.DataArrayTexture;
28
+ /** Mean colour per layer, in the array's own encoding. Used by the
29
+ * variance-preserving stochastic blend, which needs the texture's mean to
30
+ * restore contrast the blend would otherwise wash out. Free to compute here
31
+ * because the pixels are already in hand. */
32
+ means: THREE.Vector3[];
33
+ }
34
+ /**
35
+ * Pack images into one `DataArrayTexture`.
36
+ *
37
+ * Every layer must be the same size, and user uploads never are, so each image
38
+ * is drawn into a canvas at `size` first. `alphaFrom` supplies a second image
39
+ * per layer whose red channel becomes the layer's alpha — used to carry each
40
+ * surface's height map in the normal array's spare channel rather than paying
41
+ * for a whole third array.
42
+ */
43
+ export declare function buildTextureArray(images: (HTMLImageElement | null)[], size: number, colorSpace: THREE.ColorSpace, alphaFrom?: (HTMLImageElement | null)[], fallback?: [number, number, number, number]): TextureArrayResult;
44
+ /** A plain 2D texture from an already-loaded image, without a round trip through
45
+ * TextureLoader — the image is in the cache either way. */
46
+ export declare function textureFromImage(image: HTMLImageElement, colorSpace: THREE.ColorSpace): THREE.Texture;
47
+ /**
48
+ * Light separable binomial blur over the decoded field.
49
+ *
50
+ * This is not cosmetic. An 8-bit heightmap has 256 levels, which over 340 m of
51
+ * relief is a 1.33 m step — and RTIN's error metric measures exactly that step.
52
+ * Measured on the highlands map: a 1.7 m budget sits above the quantisation
53
+ * noise and returns 68k triangles (visibly polygonal), while 0.4 m sits below it
54
+ * and returns 1.2M, the whole grid. There is no useful setting in between,
55
+ * because the staircase, not the terrain, is what the metric is seeing.
56
+ *
57
+ * Smoothing first removes the staircase and gives the error map a real signal,
58
+ * so the budget means what it says. The smoothed field is what RTIN, the field
59
+ * texture and the CPU height query all use; the vertex shader keeps sampling the
60
+ * raw texture, where its own bicubic filter does the same job on the GPU.
61
+ */
62
+ export declare function smoothHeightfield(height: DecodedHeightmap, passes?: number): DecodedHeightmap;
63
+ export interface FieldTextureResult {
64
+ texture: THREE.DataTexture;
65
+ /** Largest absolute one-texel gradient in the field, used to unpack RG. */
66
+ gradScale: number;
67
+ }
68
+ /**
69
+ * The height field as something the FRAGMENT stage can read: one texture, one
70
+ * fetch, carrying both the surface gradient and the height.
71
+ *
72
+ * R, G the one-texel gradient in x and z, biased to 0..1 by `gradScale`
73
+ * B the height, 0..1
74
+ *
75
+ * ## Why this exists
76
+ *
77
+ * Because splatting and shading off the interpolated vertex height is wrong, and
78
+ * that is exactly what the first version did. A linearly interpolated quantity
79
+ * has straight iso-lines within a triangle that kink at its edges, so every
80
+ * splat threshold is drawn along the triangulation — big polygonal patches of
81
+ * flat colour — and the shading normal is faceted for the same reason. The
82
+ * terrain material's own comment says so: *shade against the evaluated surface,
83
+ * not the interpolated mesh vertex*.
84
+ *
85
+ * Sampling this in the fragment shader costs one texture fetch and makes the
86
+ * mesh's own resolution invisible: the ground shades and splats smoothly however
87
+ * coarse the triangles under it are.
88
+ *
89
+ * Eight bits per channel is enough here. The gradient is a normal map by another
90
+ * name, and the height only drives auto-splat band thresholds that are already
91
+ * noise-jittered and smoothstepped.
92
+ */
93
+ export declare function buildFieldTexture(height: DecodedHeightmap): FieldTextureResult;
94
+ //# sourceMappingURL=landscapeTextures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landscapeTextures.d.ts","sourceRoot":"","sources":["../../src/utils/landscapeTextures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAe9B,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAYhE;AAwBD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,aAAa,GAAG,MAAM,CAAA;AAErE,MAAM,WAAW,gBAAgB;IAC7B;kDAC8C;IAC9C,IAAI,EAAE,YAAY,CAAA;IAClB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAA;IAChB,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,kBAAkB,GAAG,gBAAgB,CA4CvG;AAED,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,KAAK,CAAC,gBAAgB,CAAA;IAC/B;;;kDAG8C;IAC9C,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC7B,MAAM,EAAE,CAAC,gBAAgB,GAAG,IAAI,CAAC,EAAE,EACnC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,KAAK,CAAC,UAAU,EAC5B,SAAS,CAAC,EAAE,CAAC,gBAAgB,GAAG,IAAI,CAAC,EAAE,EACvC,QAAQ,GAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAwB,GAClE,kBAAkB,CA2DpB;AAED;4DAC4D;AAC5D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,OAAO,CAUrG;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,SAAI,GAAG,gBAAgB,CA2BxF;AAED,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,KAAK,CAAC,WAAW,CAAA;IAC1B,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,GAAG,kBAAkB,CA0C9E"}
@@ -0,0 +1,290 @@
1
+ import * as THREE from 'three';
2
+ /**
3
+ * Image decoding and texture-array packing for the landscape.
4
+ *
5
+ * Everything here reads pixels through a 2D canvas, which means every image must
6
+ * be CORS-clean: a cross-origin image served without `Access-Control-Allow-Origin`
7
+ * taints the canvas and `getImageData` throws. The failure is confusing when it
8
+ * happens — the texture itself renders fine and only the landscape fails to
9
+ * build — so `loadImage` sets `crossOrigin` and the callers report the origin in
10
+ * the error.
11
+ */
12
+ const imageCache = new Map();
13
+ export function loadImage(url) {
14
+ const cached = imageCache.get(url);
15
+ if (cached)
16
+ return cached;
17
+ const promise = new Promise((resolve, reject) => {
18
+ const image = new window.Image();
19
+ image.crossOrigin = 'anonymous';
20
+ image.onload = () => resolve(image);
21
+ image.onerror = () => reject(new Error(`landscape: failed to load ${url}`));
22
+ image.src = url;
23
+ });
24
+ imageCache.set(url, promise);
25
+ return promise;
26
+ }
27
+ function canvasOf(width, height) {
28
+ const canvas = document.createElement('canvas');
29
+ canvas.width = width;
30
+ canvas.height = height;
31
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
32
+ if (!ctx)
33
+ throw new Error('landscape: no 2d context');
34
+ return { canvas, ctx };
35
+ }
36
+ function readPixels(image, size) {
37
+ const { ctx } = canvasOf(size, size);
38
+ ctx.drawImage(image, 0, 0, size, size);
39
+ try {
40
+ return ctx.getImageData(0, 0, size, size).data;
41
+ }
42
+ catch (error) {
43
+ throw new Error(`landscape: cannot read pixels from ${image.src} — the canvas is tainted. `
44
+ + 'The asset host must send Access-Control-Allow-Origin.');
45
+ }
46
+ }
47
+ /** Largest power of two at or below n, clamped to something RTIN can chew. */
48
+ export function floorPowerOfTwo(n) {
49
+ return Math.max(16, Math.min(4096, 2 ** Math.floor(Math.log2(n))));
50
+ }
51
+ /**
52
+ * Heightmap pixels to a normalised RTIN grid.
53
+ *
54
+ * Normalising to 0..1 here rather than baking `elevationScale` in is what lets
55
+ * the error map be built once: RTIN's error scales linearly with the height
56
+ * scale, so a change of scale is a change of error budget, not a new error map.
57
+ */
58
+ export function decodeHeightmap(image, encoding) {
59
+ const tileSize = floorPowerOfTwo(Math.min(image.width, image.height));
60
+ const gridSize = tileSize + 1;
61
+ const pixels = readPixels(image, tileSize);
62
+ const raw = new Float32Array(gridSize * gridSize);
63
+ let min = Infinity;
64
+ let max = -Infinity;
65
+ for (let y = 0; y < tileSize; y++) {
66
+ for (let x = 0; x < tileSize; x++) {
67
+ const k = (y * tileSize + x) * 4;
68
+ const r = pixels[k];
69
+ const g = pixels[k + 1];
70
+ const b = pixels[k + 2];
71
+ let h;
72
+ if (encoding === 'terrain-rgb') {
73
+ h = (r * 65536 + g * 256 + b) / 10 - 10000;
74
+ }
75
+ else if (encoding === 'rg16') {
76
+ h = (r * 256 + g) / 65535;
77
+ }
78
+ else {
79
+ h = r / 255;
80
+ }
81
+ raw[y * gridSize + x] = h;
82
+ if (h < min)
83
+ min = h;
84
+ if (h > max)
85
+ max = h;
86
+ }
87
+ }
88
+ // RTIN wants 2^n+1 samples; the last row and column are copies of their
89
+ // neighbours so the extra edge does not invent a cliff.
90
+ for (let x = 0; x < gridSize - 1; x++) {
91
+ raw[gridSize * (gridSize - 1) + x] = raw[gridSize * (gridSize - 2) + x];
92
+ }
93
+ for (let y = 0; y < gridSize; y++) {
94
+ raw[gridSize * y + gridSize - 1] = raw[gridSize * y + gridSize - 2];
95
+ }
96
+ const range = max - min;
97
+ if (range > 1e-9 && (encoding !== 'grayscale' || min !== 0 || max !== 1)) {
98
+ for (let i = 0; i < raw.length; i++)
99
+ raw[i] = (raw[i] - min) / range;
100
+ }
101
+ return { data: raw, gridSize, tileSize, min, max };
102
+ }
103
+ /**
104
+ * Pack images into one `DataArrayTexture`.
105
+ *
106
+ * Every layer must be the same size, and user uploads never are, so each image
107
+ * is drawn into a canvas at `size` first. `alphaFrom` supplies a second image
108
+ * per layer whose red channel becomes the layer's alpha — used to carry each
109
+ * surface's height map in the normal array's spare channel rather than paying
110
+ * for a whole third array.
111
+ */
112
+ export function buildTextureArray(images, size, colorSpace, alphaFrom, fallback = [255, 255, 255, 255]) {
113
+ const layers = images.length;
114
+ const stride = size * size * 4;
115
+ const data = new Uint8Array(stride * layers);
116
+ const means = [];
117
+ for (let i = 0; i < layers; i++) {
118
+ const image = images[i];
119
+ const offset = i * stride;
120
+ let sumR = 0;
121
+ let sumG = 0;
122
+ let sumB = 0;
123
+ if (image) {
124
+ const pixels = readPixels(image, size);
125
+ data.set(pixels, offset);
126
+ for (let p = 0; p < stride; p += 4) {
127
+ sumR += pixels[p];
128
+ sumG += pixels[p + 1];
129
+ sumB += pixels[p + 2];
130
+ }
131
+ }
132
+ else {
133
+ for (let p = 0; p < stride; p += 4) {
134
+ data[offset + p] = fallback[0];
135
+ data[offset + p + 1] = fallback[1];
136
+ data[offset + p + 2] = fallback[2];
137
+ data[offset + p + 3] = fallback[3];
138
+ }
139
+ sumR = (fallback[0] * stride) / 4;
140
+ sumG = (fallback[1] * stride) / 4;
141
+ sumB = (fallback[2] * stride) / 4;
142
+ }
143
+ const alphaImage = alphaFrom?.[i];
144
+ if (alphaImage) {
145
+ const alpha = readPixels(alphaImage, size);
146
+ for (let p = 0; p < stride; p += 4)
147
+ data[offset + p + 3] = alpha[p];
148
+ }
149
+ else if (alphaFrom) {
150
+ // No height map for this layer: a flat mid height blends linearly,
151
+ // which is what every layer does in the reference implementation.
152
+ for (let p = 0; p < stride; p += 4)
153
+ data[offset + p + 3] = 128;
154
+ }
155
+ const texels = (size * size) || 1;
156
+ means.push(new THREE.Vector3(sumR / texels / 255, sumG / texels / 255, sumB / texels / 255));
157
+ }
158
+ const texture = new THREE.DataArrayTexture(data, size, size, layers);
159
+ texture.format = THREE.RGBAFormat;
160
+ texture.type = THREE.UnsignedByteType;
161
+ texture.colorSpace = colorSpace;
162
+ texture.minFilter = THREE.LinearMipMapLinearFilter;
163
+ texture.magFilter = THREE.LinearFilter;
164
+ texture.wrapS = THREE.RepeatWrapping;
165
+ texture.wrapT = THREE.RepeatWrapping;
166
+ texture.generateMipmaps = true;
167
+ texture.needsUpdate = true;
168
+ return { texture, means };
169
+ }
170
+ /** A plain 2D texture from an already-loaded image, without a round trip through
171
+ * TextureLoader — the image is in the cache either way. */
172
+ export function textureFromImage(image, colorSpace) {
173
+ const texture = new THREE.Texture(image);
174
+ texture.colorSpace = colorSpace;
175
+ texture.wrapS = THREE.ClampToEdgeWrapping;
176
+ texture.wrapT = THREE.ClampToEdgeWrapping;
177
+ texture.minFilter = THREE.LinearMipMapLinearFilter;
178
+ texture.magFilter = THREE.LinearFilter;
179
+ texture.generateMipmaps = true;
180
+ texture.needsUpdate = true;
181
+ return texture;
182
+ }
183
+ /**
184
+ * Light separable binomial blur over the decoded field.
185
+ *
186
+ * This is not cosmetic. An 8-bit heightmap has 256 levels, which over 340 m of
187
+ * relief is a 1.33 m step — and RTIN's error metric measures exactly that step.
188
+ * Measured on the highlands map: a 1.7 m budget sits above the quantisation
189
+ * noise and returns 68k triangles (visibly polygonal), while 0.4 m sits below it
190
+ * and returns 1.2M, the whole grid. There is no useful setting in between,
191
+ * because the staircase, not the terrain, is what the metric is seeing.
192
+ *
193
+ * Smoothing first removes the staircase and gives the error map a real signal,
194
+ * so the budget means what it says. The smoothed field is what RTIN, the field
195
+ * texture and the CPU height query all use; the vertex shader keeps sampling the
196
+ * raw texture, where its own bicubic filter does the same job on the GPU.
197
+ */
198
+ export function smoothHeightfield(height, passes = 1) {
199
+ const { gridSize } = height;
200
+ let src = height.data;
201
+ for (let pass = 0; pass < passes; pass++) {
202
+ const tmp = new Float32Array(src.length);
203
+ const out = new Float32Array(src.length);
204
+ // horizontal, then vertical: [1 2 1] / 4
205
+ for (let y = 0; y < gridSize; y++) {
206
+ const row = y * gridSize;
207
+ for (let x = 0; x < gridSize; x++) {
208
+ const l = src[row + Math.max(x - 1, 0)];
209
+ const c = src[row + x];
210
+ const r = src[row + Math.min(x + 1, gridSize - 1)];
211
+ tmp[row + x] = (l + 2 * c + r) * 0.25;
212
+ }
213
+ }
214
+ for (let y = 0; y < gridSize; y++) {
215
+ const up = Math.max(y - 1, 0) * gridSize;
216
+ const dn = Math.min(y + 1, gridSize - 1) * gridSize;
217
+ const row = y * gridSize;
218
+ for (let x = 0; x < gridSize; x++) {
219
+ out[row + x] = (tmp[up + x] + 2 * tmp[row + x] + tmp[dn + x]) * 0.25;
220
+ }
221
+ }
222
+ src = out;
223
+ }
224
+ return { ...height, data: src };
225
+ }
226
+ /**
227
+ * The height field as something the FRAGMENT stage can read: one texture, one
228
+ * fetch, carrying both the surface gradient and the height.
229
+ *
230
+ * R, G the one-texel gradient in x and z, biased to 0..1 by `gradScale`
231
+ * B the height, 0..1
232
+ *
233
+ * ## Why this exists
234
+ *
235
+ * Because splatting and shading off the interpolated vertex height is wrong, and
236
+ * that is exactly what the first version did. A linearly interpolated quantity
237
+ * has straight iso-lines within a triangle that kink at its edges, so every
238
+ * splat threshold is drawn along the triangulation — big polygonal patches of
239
+ * flat colour — and the shading normal is faceted for the same reason. The
240
+ * terrain material's own comment says so: *shade against the evaluated surface,
241
+ * not the interpolated mesh vertex*.
242
+ *
243
+ * Sampling this in the fragment shader costs one texture fetch and makes the
244
+ * mesh's own resolution invisible: the ground shades and splats smoothly however
245
+ * coarse the triangles under it are.
246
+ *
247
+ * Eight bits per channel is enough here. The gradient is a normal map by another
248
+ * name, and the height only drives auto-splat band thresholds that are already
249
+ * noise-jittered and smoothstepped.
250
+ */
251
+ export function buildFieldTexture(height) {
252
+ const { data, gridSize, tileSize } = height;
253
+ const size = tileSize;
254
+ const gx = new Float32Array(size * size);
255
+ const gy = new Float32Array(size * size);
256
+ let maxGrad = 1e-6;
257
+ for (let y = 0; y < size; y++) {
258
+ for (let x = 0; x < size; x++) {
259
+ const xl = Math.max(x - 1, 0);
260
+ const xr = Math.min(x + 1, size - 1);
261
+ const yu = Math.max(y - 1, 0);
262
+ const yd = Math.min(y + 1, size - 1);
263
+ const dx = (data[y * gridSize + xr] - data[y * gridSize + xl]) * 0.5;
264
+ const dy = (data[yd * gridSize + x] - data[yu * gridSize + x]) * 0.5;
265
+ gx[y * size + x] = dx;
266
+ gy[y * size + x] = dy;
267
+ const m = Math.max(Math.abs(dx), Math.abs(dy));
268
+ if (m > maxGrad)
269
+ maxGrad = m;
270
+ }
271
+ }
272
+ const pixels = new Uint8Array(size * size * 4);
273
+ for (let i = 0; i < size * size; i++) {
274
+ const x = i % size;
275
+ const y = (i / size) | 0;
276
+ pixels[i * 4 + 0] = Math.round(THREE.MathUtils.clamp(gx[i] / maxGrad * 0.5 + 0.5, 0, 1) * 255);
277
+ pixels[i * 4 + 1] = Math.round(THREE.MathUtils.clamp(gy[i] / maxGrad * 0.5 + 0.5, 0, 1) * 255);
278
+ pixels[i * 4 + 2] = Math.round(THREE.MathUtils.clamp(data[y * gridSize + x], 0, 1) * 255);
279
+ pixels[i * 4 + 3] = 255;
280
+ }
281
+ const texture = new THREE.DataTexture(pixels, size, size, THREE.RGBAFormat, THREE.UnsignedByteType);
282
+ texture.colorSpace = THREE.NoColorSpace;
283
+ texture.wrapS = THREE.ClampToEdgeWrapping;
284
+ texture.wrapT = THREE.ClampToEdgeWrapping;
285
+ texture.minFilter = THREE.LinearMipMapLinearFilter;
286
+ texture.magFilter = THREE.LinearFilter;
287
+ texture.generateMipmaps = true;
288
+ texture.needsUpdate = true;
289
+ return { texture, gradScale: maxGrad };
290
+ }
@@ -0,0 +1,20 @@
1
+ import * as THREE from 'three';
2
+ import type { LandscapeConfig, LandscapeQualityConfig } from '../types/landscapeConfigs';
3
+ import type { LandscapeUniforms } from '../shaders/landscape/LandscapeMaterial';
4
+ export interface LandscapeMapUniforms {
5
+ height: THREE.Texture | null;
6
+ heightSize: THREE.Vector2;
7
+ /** RG gradient + B height, sampled per fragment. */
8
+ field: THREE.Texture | null;
9
+ fieldSize: THREE.Vector2;
10
+ gradScale: number;
11
+ diffuse: THREE.DataArrayTexture | null;
12
+ normal: THREE.DataArrayTexture | null;
13
+ splat0: THREE.Texture | null;
14
+ splat1: THREE.Texture | null;
15
+ ao: THREE.Texture | null;
16
+ means: THREE.Vector3[];
17
+ }
18
+ export declare function applyLandscapeMaps(uniforms: LandscapeUniforms, maps: LandscapeMapUniforms): void;
19
+ export declare function applyLandscapeUniforms(uniforms: LandscapeUniforms, config: LandscapeConfig, quality: LandscapeQualityConfig): void;
20
+ //# sourceMappingURL=landscapeUniforms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landscapeUniforms.d.ts","sourceRoot":"","sources":["../../src/utils/landscapeUniforms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAA;AACxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AAa/E,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,CAAA;IAC5B,UAAU,EAAE,KAAK,CAAC,OAAO,CAAA;IACzB,oDAAoD;IACpD,KAAK,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,CAAA;IAC3B,SAAS,EAAE,KAAK,CAAC,OAAO,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAA;IACtC,MAAM,EAAE,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAA;IACrC,MAAM,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,CAAA;IAC5B,MAAM,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,CAAA;IAC5B,EAAE,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,CAAA;IACxB,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;CACzB;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAoBhG;AAED,wBAAgB,sBAAsB,CAClC,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,sBAAsB,GAChC,IAAI,CAmDN"}