From e78c6e86b6baa58f5af3572471dbae6f94e13819 Mon Sep 17 00:00:00 2001 From: Richard Zink Date: Mon, 12 Oct 2026 14:04:29 +0200 Subject: [PATCH] curvature: bend terrain vertices to a spherical Earth --- vendor/clouds3d.js | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/vendor/clouds3d.js b/vendor/clouds3d.js index e12556a..fa11db5 100644 --- a/vendor/clouds3d.js +++ b/vendor/clouds3d.js @@ -530,17 +530,30 @@ var TERRAIN_VERT_SRC = [ 'attribute vec2 aUV;', 'uniform mat4 uView;', 'uniform mat4 uProj;', + 'uniform vec3 uCamPos;', + 'uniform float uEarthRadius;', + 'uniform float uCurvatureOn;', 'varying vec3 vNormal;', 'varying vec2 vUV;', 'varying float vY;', 'varying vec3 vViewPos;', 'varying vec3 vWorld;', 'void main() {', - 'vNormal = aNormal;', + 'vec3 pos = aPos;', + 'vec3 n = aNormal;', + 'if (uCurvatureOn > 0.5) {', + 'vec2 d = pos.xz - uCamPos.xz;', + 'float r = max(1.0, uEarthRadius);', + 'pos.y -= dot(d, d) / (2.0 * r);', + 'n.x -= d.x / r;', + 'n.z -= d.y / r;', + 'n = normalize(n);', + '}', + 'vNormal = n;', 'vUV = aUV;', - 'vY = aPos.y;', - 'vWorld = aPos;', - 'vec4 vp = uView * vec4(aPos, 1.0);', + 'vY = pos.y;', + 'vWorld = pos;', + 'vec4 vp = uView * vec4(pos, 1.0);', 'vViewPos = vp.xyz;', 'gl_Position = uProj * vp;', '}' @@ -888,6 +901,8 @@ function init(canvas) { var terrainUairColor = terrainProg ? gl.getUniformLocation(terrainProg, 'uAirColor') : null; var terrainUairDensity = terrainProg ? gl.getUniformLocation(terrainProg, 'uAirDensity') : null; var terrainUcampos = terrainProg ? gl.getUniformLocation(terrainProg, 'uCamPos') : null; + var terrainUEarthRadius = terrainProg ? gl.getUniformLocation(terrainProg, 'uEarthRadius') : null; + var terrainUCurvatureOn = terrainProg ? gl.getUniformLocation(terrainProg, 'uCurvatureOn') : null; var terrainUedgeFade = terrainProg ? gl.getUniformLocation(terrainProg, 'uEdgeFadeTiles') : null; var terrainUstarAmb = terrainProg ? gl.getUniformLocation(terrainProg, 'uStarAmb') : null; var terrainUtime = terrainProg ? gl.getUniformLocation(terrainProg, 'uTime') : null; @@ -958,7 +973,9 @@ function init(canvas) { uSkyAmb: terrainUskyAmb, uAlbedoTint: terrainUalbedoTint, uHorizonColor: terrainUhorizon, uAirColor: terrainUairColor, uAirDensity: terrainUairDensity, - uCamPos: terrainUcampos, uEdgeFadeTiles: terrainUedgeFade, + uCamPos: terrainUcampos, + uEarthRadius: terrainUEarthRadius, uCurvatureOn: terrainUCurvatureOn, + uEdgeFadeTiles: terrainUedgeFade, uStarAmb: terrainUstarAmb, uTime: terrainUtime, uCloudBot: terrainUcloudbot, uCloudTop: terrainUcloudtop, uDensity: terrainUdensity, @@ -2137,7 +2154,12 @@ var ATMO_MODEL_GRAY_TAU = (typeof ATMO_MODEL.grayTau === 'number') ? Math.max(0.1, Math.min(10.0, ATMO_MODEL.grayTau)) : 1.5; var ATMO_MODEL_GAIN = (typeof ATMO_MODEL.gain === 'number') ? Math.max(0.1, Math.min(5.0, ATMO_MODEL.gain)) : 2.0; -var EARTH_RADIUS_UNITS = 6371.0 / KM_PER_TILE; +var CURV_CFG = (window.CONFIG && window.CONFIG.curvature) || {}; +var CURVATURE_ON = CURV_CFG.enabled !== false; +var CURV_EXAG = (typeof CURV_CFG.exaggeration === 'number') + ? Math.max(0.25, CURV_CFG.exaggeration) : 2.0; +var EARTH_RADIUS_UNITS = (typeof CURV_CFG.earthRadiusKm === 'number' + ? Math.max(1000, CURV_CFG.earthRadiusKm) : 6371) / KM_PER_TILE / CURV_EXAG; var ATMO_MODEL_BLUE_COLOR = new Float32Array([0.22, 0.42, 0.95]); var ATMO_MODEL_GRAY_COLOR = new Float32Array([0.72, 0.76, 0.82]); // Integrated starlight + airglow. Physically this is about a millionth of @@ -5490,6 +5512,8 @@ window.clouds3dRender = function(target) { gl.uniform3f(terr.uAirColor, _air[0], _air[1], _air[2]); gl.uniform1f(terr.uAirDensity, -Math.log(ATMO_HAZE_RESIDUAL) / Math.max(FOG_TILES, 0.001)); gl.uniform3f(terr.uCamPos, camX, camY, camZ); + gl.uniform1f(terr.uEarthRadius, EARTH_RADIUS_UNITS); + gl.uniform1f(terr.uCurvatureOn, CURVATURE_ON ? 1.0 : 0.0); gl.uniform1f(terr.uEdgeFadeTiles, EDGE_FADE_TILES); gl.bindBuffer(gl.ARRAY_BUFFER, terr.vbo);