@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.
- package/dist/components/Clouds.d.ts.map +1 -1
- package/dist/components/Clouds.js +0 -1
- package/dist/components/CloudsGenerator.d.ts +32 -0
- package/dist/components/CloudsGenerator.d.ts.map +1 -0
- package/dist/components/CloudsGenerator.js +58 -0
- package/dist/components/CustomPrimitive.js +1 -1
- package/dist/components/EnvironmentManager.d.ts +47 -5
- package/dist/components/EnvironmentManager.d.ts.map +1 -1
- package/dist/components/EnvironmentManager.js +35 -66
- package/dist/components/GeometryShape.d.ts.map +1 -1
- package/dist/components/GeometryShape.js +19 -3
- package/dist/components/HdrEnvironment.d.ts +20 -0
- package/dist/components/HdrEnvironment.d.ts.map +1 -0
- package/dist/components/HdrEnvironment.js +44 -0
- package/dist/components/LandscapeBuilder.d.ts +23 -0
- package/dist/components/LandscapeBuilder.d.ts.map +1 -0
- package/dist/components/LandscapeBuilder.js +25 -0
- package/dist/components/LandscapeObject.d.ts +24 -0
- package/dist/components/LandscapeObject.d.ts.map +1 -0
- package/dist/components/LandscapeObject.js +27 -0
- package/dist/components/ObjectNode.d.ts.map +1 -1
- package/dist/components/ObjectNode.js +11 -9
- package/dist/components/OceanBuilder.d.ts +35 -0
- package/dist/components/OceanBuilder.d.ts.map +1 -0
- package/dist/components/OceanBuilder.js +53 -0
- package/dist/components/ParticlesR3f.d.ts.map +1 -1
- package/dist/components/ParticlesR3f.js +63 -12
- package/dist/components/PointCloud.d.ts.map +1 -1
- package/dist/components/PointCloud.js +50 -10
- package/dist/components/SceneBuilder.d.ts.map +1 -1
- package/dist/components/SkySystem.d.ts +27 -6
- package/dist/components/SkySystem.d.ts.map +1 -1
- package/dist/components/SkySystem.js +6 -5
- package/dist/components/SunLight.d.ts.map +1 -1
- package/dist/components/SunLight.js +4 -1
- package/dist/components/TerrainBuilder.d.ts +30 -0
- package/dist/components/TerrainBuilder.d.ts.map +1 -0
- package/dist/components/TerrainBuilder.js +44 -0
- package/dist/components/index.d.ts +17 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +10 -0
- package/dist/components/landscape/LandscapeMesh.d.ts +18 -0
- package/dist/components/landscape/LandscapeMesh.d.ts.map +1 -0
- package/dist/components/landscape/LandscapeMesh.js +278 -0
- package/dist/components/landscape/index.d.ts +3 -0
- package/dist/components/landscape/index.d.ts.map +1 -0
- package/dist/components/landscape/index.js +1 -0
- package/dist/components/landscape/types.d.ts +13 -0
- package/dist/components/landscape/types.d.ts.map +1 -0
- package/dist/components/landscape/types.js +1 -0
- package/dist/components/terrain/Canyon.d.ts +23 -0
- package/dist/components/terrain/Canyon.d.ts.map +1 -0
- package/dist/components/terrain/Canyon.js +29 -0
- package/dist/components/terrain/Dunes.d.ts +22 -0
- package/dist/components/terrain/Dunes.d.ts.map +1 -0
- package/dist/components/terrain/Dunes.js +25 -0
- package/dist/components/terrain/Hills.d.ts +17 -0
- package/dist/components/terrain/Hills.d.ts.map +1 -0
- package/dist/components/terrain/Hills.js +20 -0
- package/dist/components/terrain/Mountains.d.ts +19 -0
- package/dist/components/terrain/Mountains.d.ts.map +1 -0
- package/dist/components/terrain/Mountains.js +22 -0
- package/dist/components/terrain/TerrainSurface.d.ts +22 -0
- package/dist/components/terrain/TerrainSurface.d.ts.map +1 -0
- package/dist/components/terrain/TerrainSurface.js +94 -0
- package/dist/components/terrain/index.d.ts +7 -0
- package/dist/components/terrain/index.d.ts.map +1 -0
- package/dist/components/terrain/index.js +7 -0
- package/dist/components/terrain/types.d.ts +17 -0
- package/dist/components/terrain/types.d.ts.map +1 -0
- package/dist/components/terrain/types.js +1 -0
- package/dist/components/volumetricClouds/Cirrus.d.ts +14 -0
- package/dist/components/volumetricClouds/Cirrus.d.ts.map +1 -0
- package/dist/components/volumetricClouds/Cirrus.js +13 -0
- package/dist/components/volumetricClouds/Cumulonimbus.d.ts +17 -0
- package/dist/components/volumetricClouds/Cumulonimbus.d.ts.map +1 -0
- package/dist/components/volumetricClouds/Cumulonimbus.js +20 -0
- package/dist/components/volumetricClouds/Cumulus.d.ts +14 -0
- package/dist/components/volumetricClouds/Cumulus.d.ts.map +1 -0
- package/dist/components/volumetricClouds/Cumulus.js +17 -0
- package/dist/components/volumetricClouds/Stratocumulus.d.ts +12 -0
- package/dist/components/volumetricClouds/Stratocumulus.d.ts.map +1 -0
- package/dist/components/volumetricClouds/Stratocumulus.js +16 -0
- package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts +42 -0
- package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts.map +1 -0
- package/dist/components/volumetricClouds/VolumetricCloudsPass.js +298 -0
- package/dist/components/volumetricClouds/index.d.ts +8 -0
- package/dist/components/volumetricClouds/index.d.ts.map +1 -0
- package/dist/components/volumetricClouds/index.js +7 -0
- package/dist/components/volumetricClouds/types.d.ts +17 -0
- package/dist/components/volumetricClouds/types.d.ts.map +1 -0
- package/dist/components/volumetricClouds/types.js +1 -0
- package/dist/components/water/Host.d.ts +12 -0
- package/dist/components/water/Host.d.ts.map +1 -0
- package/dist/components/water/Host.js +12 -0
- package/dist/components/water/Lake.d.ts +18 -0
- package/dist/components/water/Lake.d.ts.map +1 -0
- package/dist/components/water/Lake.js +21 -0
- package/dist/components/water/OceanSurface.d.ts +27 -0
- package/dist/components/water/OceanSurface.d.ts.map +1 -0
- package/dist/components/water/OceanSurface.js +60 -0
- package/dist/components/water/Sea.d.ts +18 -0
- package/dist/components/water/Sea.d.ts.map +1 -0
- package/dist/components/water/Sea.js +17 -0
- package/dist/components/water/Shore.d.ts +16 -0
- package/dist/components/water/Shore.d.ts.map +1 -0
- package/dist/components/water/Shore.js +15 -0
- package/dist/components/water/Storm.d.ts +14 -0
- package/dist/components/water/Storm.d.ts.map +1 -0
- package/dist/components/water/Storm.js +13 -0
- package/dist/components/water/WaterBody.d.ts +31 -0
- package/dist/components/water/WaterBody.d.ts.map +1 -0
- package/dist/components/water/WaterBody.js +76 -0
- package/dist/components/water/index.d.ts +9 -0
- package/dist/components/water/index.d.ts.map +1 -0
- package/dist/components/water/index.js +11 -0
- package/dist/components/water/types.d.ts +20 -0
- package/dist/components/water/types.d.ts.map +1 -0
- package/dist/components/water/types.js +1 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +15 -0
- package/dist/hooks/index.d.ts +24 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +12 -0
- package/dist/hooks/useClipmap.d.ts +71 -0
- package/dist/hooks/useClipmap.d.ts.map +1 -0
- package/dist/hooks/useClipmap.js +81 -0
- package/dist/hooks/useCloudLive.d.ts +29 -0
- package/dist/hooks/useCloudLive.d.ts.map +1 -0
- package/dist/hooks/useCloudLive.js +43 -0
- package/dist/hooks/useCloudNoise.d.ts +28 -0
- package/dist/hooks/useCloudNoise.d.ts.map +1 -0
- package/dist/hooks/useCloudNoise.js +128 -0
- package/dist/hooks/useCloudSun.d.ts +49 -0
- package/dist/hooks/useCloudSun.d.ts.map +1 -0
- package/dist/hooks/useCloudSun.js +84 -0
- package/dist/hooks/useCubeCamera.d.ts +27 -9
- package/dist/hooks/useCubeCamera.d.ts.map +1 -1
- package/dist/hooks/useCubeCamera.js +28 -70
- package/dist/hooks/useEnvironmentProbe.d.ts +32 -0
- package/dist/hooks/useEnvironmentProbe.d.ts.map +1 -0
- package/dist/hooks/useEnvironmentProbe.js +35 -0
- package/dist/hooks/useFrameRenderer.d.ts +15 -0
- package/dist/hooks/useFrameRenderer.d.ts.map +1 -0
- package/dist/hooks/useFrameRenderer.js +23 -0
- package/dist/hooks/useLandscapeLive.d.ts +29 -0
- package/dist/hooks/useLandscapeLive.d.ts.map +1 -0
- package/dist/hooks/useLandscapeLive.js +41 -0
- package/dist/hooks/useLandscapeMaps.d.ts +32 -0
- package/dist/hooks/useLandscapeMaps.d.ts.map +1 -0
- package/dist/hooks/useLandscapeMaps.js +138 -0
- package/dist/hooks/useMaterialApplication.d.ts.map +1 -1
- package/dist/hooks/useMaterialApplication.js +2 -3
- package/dist/hooks/useMaterialOptions.d.ts +2 -2
- package/dist/hooks/useMaterialOptions.d.ts.map +1 -1
- package/dist/hooks/useMaterialOptions.js +13 -31
- package/dist/hooks/useObjectEdges.d.ts.map +1 -1
- package/dist/hooks/useObjectEdges.js +3 -49
- package/dist/hooks/useOceanLive.d.ts +29 -0
- package/dist/hooks/useOceanLive.d.ts.map +1 -0
- package/dist/hooks/useOceanLive.js +38 -0
- package/dist/hooks/useOceanMaterial.d.ts +39 -0
- package/dist/hooks/useOceanMaterial.d.ts.map +1 -0
- package/dist/hooks/useOceanMaterial.js +65 -0
- package/dist/hooks/useTerrainClipmap.d.ts +12 -0
- package/dist/hooks/useTerrainClipmap.d.ts.map +1 -0
- package/dist/hooks/useTerrainClipmap.js +10 -0
- package/dist/hooks/useTerrainLive.d.ts +34 -0
- package/dist/hooks/useTerrainLive.d.ts.map +1 -0
- package/dist/hooks/useTerrainLive.js +45 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/presets/landscapePresets.d.ts +60 -0
- package/dist/presets/landscapePresets.d.ts.map +1 -0
- package/dist/presets/landscapePresets.js +194 -0
- package/dist/presets/landscapeTypes.d.ts +195 -0
- package/dist/presets/landscapeTypes.d.ts.map +1 -0
- package/dist/presets/landscapeTypes.js +315 -0
- package/dist/presets/oceanSurfacePresets.d.ts +33 -0
- package/dist/presets/oceanSurfacePresets.d.ts.map +1 -0
- package/dist/presets/oceanSurfacePresets.js +245 -0
- package/dist/presets/terrainPresets.d.ts +28 -0
- package/dist/presets/terrainPresets.d.ts.map +1 -0
- package/dist/presets/terrainPresets.js +293 -0
- package/dist/presets/volumetricCloudPresets.d.ts +38 -0
- package/dist/presets/volumetricCloudPresets.d.ts.map +1 -0
- package/dist/presets/volumetricCloudPresets.js +480 -0
- package/dist/shaders/clouds/CirrusMaterial.d.ts +84 -0
- package/dist/shaders/clouds/CirrusMaterial.d.ts.map +1 -0
- package/dist/shaders/clouds/CirrusMaterial.js +167 -0
- package/dist/shaders/clouds/CloudCompositeMaterial.d.ts +20 -0
- package/dist/shaders/clouds/CloudCompositeMaterial.d.ts.map +1 -0
- package/dist/shaders/clouds/CloudCompositeMaterial.js +82 -0
- package/dist/shaders/clouds/CloudResolveMaterial.d.ts +22 -0
- package/dist/shaders/clouds/CloudResolveMaterial.d.ts.map +1 -0
- package/dist/shaders/clouds/CloudResolveMaterial.js +77 -0
- package/dist/shaders/clouds/CloudVolumeMaterial.d.ts +143 -0
- package/dist/shaders/clouds/CloudVolumeMaterial.d.ts.map +1 -0
- package/dist/shaders/clouds/CloudVolumeMaterial.js +249 -0
- package/dist/shaders/clouds/SkyBackgroundMaterial.d.ts +21 -0
- package/dist/shaders/clouds/SkyBackgroundMaterial.d.ts.map +1 -0
- package/dist/shaders/clouds/SkyBackgroundMaterial.js +42 -0
- package/dist/shaders/glsl/cloudCore.glsl.d.ts +24 -0
- package/dist/shaders/glsl/cloudCore.glsl.d.ts.map +1 -0
- package/dist/shaders/glsl/cloudCore.glsl.js +348 -0
- package/dist/shaders/glsl/landscapeCore.glsl.d.ts +49 -0
- package/dist/shaders/glsl/landscapeCore.glsl.d.ts.map +1 -0
- package/dist/shaders/glsl/landscapeCore.glsl.js +518 -0
- package/dist/shaders/glsl/noiseBake.glsl.d.ts +30 -0
- package/dist/shaders/glsl/noiseBake.glsl.d.ts.map +1 -0
- package/dist/shaders/glsl/noiseBake.glsl.js +164 -0
- package/dist/shaders/glsl/oceanCore.glsl.d.ts +52 -0
- package/dist/shaders/glsl/oceanCore.glsl.d.ts.map +1 -0
- package/dist/shaders/glsl/oceanCore.glsl.js +421 -0
- package/dist/shaders/glsl/terrainCore.glsl.d.ts +78 -0
- package/dist/shaders/glsl/terrainCore.glsl.d.ts.map +1 -0
- package/dist/shaders/glsl/terrainCore.glsl.js +625 -0
- package/dist/shaders/landscape/LandscapeMaterial.d.ts +73 -0
- package/dist/shaders/landscape/LandscapeMaterial.d.ts.map +1 -0
- package/dist/shaders/landscape/LandscapeMaterial.js +136 -0
- package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts +57 -0
- package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts.map +1 -0
- package/dist/shaders/ocean/OceanSurfaceMaterial.js +268 -0
- package/dist/shaders/terrain/TerrainMaterial.d.ts +80 -0
- package/dist/shaders/terrain/TerrainMaterial.d.ts.map +1 -0
- package/dist/shaders/terrain/TerrainMaterial.js +291 -0
- package/dist/types/cloudConfigs.d.ts +244 -0
- package/dist/types/cloudConfigs.d.ts.map +1 -0
- package/dist/types/cloudConfigs.js +282 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -1
- package/dist/types/landscapeConfigs.d.ts +218 -0
- package/dist/types/landscapeConfigs.d.ts.map +1 -0
- package/dist/types/landscapeConfigs.js +256 -0
- package/dist/types/oceanConfigs.d.ts +290 -0
- package/dist/types/oceanConfigs.d.ts.map +1 -0
- package/dist/types/oceanConfigs.js +380 -0
- package/dist/types/terrainConfigs.d.ts +251 -0
- package/dist/types/terrainConfigs.d.ts.map +1 -0
- package/dist/types/terrainConfigs.js +292 -0
- package/dist/utils/cloudUniforms.d.ts +14 -0
- package/dist/utils/cloudUniforms.d.ts.map +1 -0
- package/dist/utils/cloudUniforms.js +122 -0
- package/dist/utils/frameRenderer.d.ts +33 -0
- package/dist/utils/frameRenderer.d.ts.map +1 -0
- package/dist/utils/frameRenderer.js +24 -0
- package/dist/utils/handleCloudsAnimations.d.ts +16 -0
- package/dist/utils/handleCloudsAnimations.d.ts.map +1 -0
- package/dist/utils/handleCloudsAnimations.js +64 -0
- package/dist/utils/landscapeGeometry.d.ts +61 -0
- package/dist/utils/landscapeGeometry.d.ts.map +1 -0
- package/dist/utils/landscapeGeometry.js +126 -0
- package/dist/utils/landscapeHeight.d.ts +44 -0
- package/dist/utils/landscapeHeight.d.ts.map +1 -0
- package/dist/utils/landscapeHeight.js +65 -0
- package/dist/utils/landscapeTextures.d.ts +94 -0
- package/dist/utils/landscapeTextures.d.ts.map +1 -0
- package/dist/utils/landscapeTextures.js +290 -0
- package/dist/utils/landscapeUniforms.d.ts +20 -0
- package/dist/utils/landscapeUniforms.d.ts.map +1 -0
- package/dist/utils/landscapeUniforms.js +61 -0
- package/dist/utils/oceanUniforms.d.ts +47 -0
- package/dist/utils/oceanUniforms.d.ts.map +1 -0
- package/dist/utils/oceanUniforms.js +216 -0
- package/dist/utils/pointsMaterialOwnership.d.ts +9 -0
- package/dist/utils/pointsMaterialOwnership.d.ts.map +1 -0
- package/dist/utils/pointsMaterialOwnership.js +14 -0
- package/dist/utils/rtin.d.ts +63 -0
- package/dist/utils/rtin.d.ts.map +1 -0
- package/dist/utils/rtin.js +202 -0
- package/dist/utils/terrainUniforms.d.ts +14 -0
- package/dist/utils/terrainUniforms.d.ts.map +1 -0
- package/dist/utils/terrainUniforms.js +196 -0
- 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"}
|