curvature: bend roads, city lights, flights and contrails

This commit is contained in:
Richard Zink committed 2026-10-12 19:08:50 +02:00
1 parent 60758b1168
commit 4748adc0a1
1 file changed
+80 -11
+80 -11
View File
@@ -3736,6 +3736,8 @@ var CAR_VERT_SRC = [
'uniform mat4 uProj;',
'uniform mat4 uView;',
'uniform vec3 uCamPos;',
'uniform float uEarthRadius;',
'uniform float uCurvatureOn;',
'uniform float uGrow;', // half width needed per unit distance (px floor)',
'uniform float uHalfW;', // ribbon half width baked into the geometry',
'varying float vLen;',
@@ -3748,7 +3750,12 @@ var CAR_VERT_SRC = [
'vLen = aMeta.x;',
'vSide = aMeta.y;',
'vDens = aDens;',
'vec3 toCam = uCamPos - aPos;',
'vec3 pos = aPos;',
'if (uCurvatureOn > 0.5) {',
'vec2 d = pos.xz - uCamPos.xz;',
'pos.y -= dot(d, d) / (2.0 * max(1.0, uEarthRadius));',
'}',
'vec3 toCam = uCamPos - pos;',
'vDist = length(toCam);',
// Travel direction of a ribbon half is -tangent (see the flow sign
// note in the fragment shader). The tangent is IDENTICAL on both
@@ -3764,7 +3771,7 @@ var CAR_VERT_SRC = [
// A1 into the Attersee because a 3.6 px floor at 70 km is ~640 m.
'vHw = max(uHalfW, uGrow * vDist);',
'vec3 perp = vec3(-aMeta.w, 0.0, aMeta.z) * aMeta.y;',
'vec3 wp = aPos + perp * (vHw - uHalfW);',
'vec3 wp = pos + perp * (vHw - uHalfW);',
'gl_Position = uProj * uView * vec4(wp, 1.0);',
'}'
].join('\n');
@@ -3813,6 +3820,8 @@ var CARP_VERT_SRC = [
'uniform mat4 uProj;',
'uniform mat4 uView;',
'uniform vec3 uCamPos;',
'uniform float uEarthRadius;',
'uniform float uCurvatureOn;',
'uniform float uTime;',
'uniform float uSpeed;', // slots per second (frame flow)',
'uniform float uDash;', // vehicle spacing (world units)',
@@ -3843,7 +3852,12 @@ var CARP_VERT_SRC = [
'float span = min(uDash, aLen);',
'float off = fract(uTime * uSpeed + aMeta.w) * span + (aSeed - 0.5) * span * 0.12;',
'vec3 base = aPos - vec3(aMeta.x, 0.0, aMeta.y) * dir * off;',
'vec3 toCam = uCamPos - base;',
'vec3 pos = base;',
'if (uCurvatureOn > 0.5) {',
'vec2 d = pos.xz - uCamPos.xz;',
'pos.y -= dot(d, d) / (2.0 * max(1.0, uEarthRadius));',
'}',
'vec3 toCam = uCamPos - pos;',
'vDist = length(toCam);',
'vApproach = dot(vec3(aMeta.x, 0.0, aMeta.y), normalize(toCam));',
// perp is the RIGHT of the digitisation tangent. This point travels',
@@ -3858,7 +3872,7 @@ var CARP_VERT_SRC = [
'vec3 perp = vec3(-aMeta.y, 0.0, aMeta.x) * (-dir * uTraffic);',
'float gap = max(uLaneW, uLanePx * uPxW * vDist);',
'gl_PointSize = clamp(uMinPx + uBoost / max(vDist, 0.35), uMinPx, uMaxPx);',
'gl_Position = uProj * uView * vec4(base + perp * gap, 1.0);',
'gl_Position = uProj * uView * vec4(pos + perp * gap, 1.0);',
'}'
].join('\n');
@@ -4784,6 +4798,8 @@ function applyCarBuild(gl, res) {
proj: gl.getUniformLocation(carProg, 'uProj'),
view: gl.getUniformLocation(carProg, 'uView'),
camPos: gl.getUniformLocation(carProg, 'uCamPos'),
earthRadius: gl.getUniformLocation(carProg, 'uEarthRadius'),
curvatureOn: gl.getUniformLocation(carProg, 'uCurvatureOn'),
time: gl.getUniformLocation(carProg, 'uTime'),
night: gl.getUniformLocation(carProg, 'uNight'),
dens: gl.getUniformLocation(carProg, 'uDens'),
@@ -4860,6 +4876,8 @@ function applyCityBuild(gl, res) {
proj: gl.getUniformLocation(cityProg, 'uProj'),
view: gl.getUniformLocation(cityProg, 'uView'),
camPos: gl.getUniformLocation(cityProg, 'uCamPos'),
earthRadius: gl.getUniformLocation(cityProg, 'uEarthRadius'),
curvatureOn: gl.getUniformLocation(cityProg, 'uCurvatureOn'),
scale: gl.getUniformLocation(cityProg, 'uScale'),
night: gl.getUniformLocation(cityProg, 'uNight'),
sunAlt: gl.getUniformLocation(cityProg, 'uSunAlt'),
@@ -4880,17 +4898,24 @@ var CITY_VERT_SRC = [
'uniform mat4 uProj;',
'uniform mat4 uView;',
'uniform vec3 uCamPos;',
'uniform float uEarthRadius;',
'uniform float uCurvatureOn;',
'uniform float uScale;', // px scale (viewport height / 600)',
'varying float vBri;',
'varying float vDist;',
'varying float vSeed;',
'void main() {',
'vDist = length(uCamPos - aPos);',
'vec3 pos = aPos;',
'if (uCurvatureOn > 0.5) {',
'vec2 d = pos.xz - uCamPos.xz;',
'pos.y -= dot(d, d) / (2.0 * max(1.0, uEarthRadius));',
'}',
'vDist = length(uCamPos - pos);',
'vBri = aBri;',
// stable per-dot threshold seed: derived from the dot's own position
'vSeed = fract(sin(dot(aPos.xz, vec2(12.9898, 78.233))) * 43758.5453);',
'gl_PointSize = clamp(aSize * uScale * 750.0 / max(vDist, 0.1), 1.6, 18.0);',
'gl_Position = uProj * uView * vec4(aPos, 1.0);',
'gl_Position = uProj * uView * vec4(pos, 1.0);',
'}'
].join('\n');
@@ -5227,6 +5252,8 @@ var FLT_VERT_SRC = [
'uniform mat4 uProj;',
'uniform mat4 uView;',
'uniform vec3 uCamPos;',
'uniform float uEarthRadius;',
'uniform float uCurvatureOn;',
'uniform float uScale;',
'uniform float uMinPx;', // hard pixel floor for the light',
'uniform float uMaxPx;', // ceiling so a near jet is not a blob',
@@ -5235,14 +5262,19 @@ var FLT_VERT_SRC = [
'varying float vPhase;',
'varying float vDist;',
'void main() {',
'vDist = length(uCamPos - aPos);',
'vec3 pos = aPos;',
'if (uCurvatureOn > 0.5) {',
'vec2 d = pos.xz - uCamPos.xz;',
'pos.y -= dot(d, d) / (2.0 * max(1.0, uEarthRadius));',
'}',
'vDist = length(uCamPos - pos);',
'vBri = aBlink.x;',
'vPhase = aBlink.y;',
// distance aware, but never below the pixel floor - the city dots
// use the same clamp; aBlink.z carries the destination rank
'gl_PointSize = clamp(uMinPx + uBoost * uScale * aBlink.z / max(vDist, 0.35),',
'uMinPx, uMaxPx);',
'gl_Position = uProj * uView * vec4(aPos, 1.0);',
'gl_Position = uProj * uView * vec4(pos, 1.0);',
'}'
].join('\n');
@@ -5292,6 +5324,9 @@ var PLANE_VERT_SRC = [
'attribute float aCol;', // 0 body, 1 glass
'uniform mat4 uProj;',
'uniform mat4 uView;',
'uniform vec3 uCamPos;',
'uniform float uEarthRadius;',
'uniform float uCurvatureOn;',
'uniform vec3 uOrigin;', // world anchor (cruise position)
'uniform vec4 uOrit;', // cos(yaw), sin(yaw), sin(pitch), fade
'uniform float uS;', // model radius in world units
@@ -5306,7 +5341,12 @@ var PLANE_VERT_SRC = [
'n = vec3(n.x, n.y * cp2 + n.z * sp, -n.y * sp + n.z * cp2);',
'n = vec3(n.x * cy + n.z * sy, n.y, -n.x * sy + n.z * cy);',
'vN = n; vCol = aCol;',
'gl_Position = uProj * uView * vec4(uOrigin + p, 1.0);',
'vec3 origin = uOrigin;',
'if (uCurvatureOn > 0.5) {',
'vec2 d = origin.xz - uCamPos.xz;',
'origin.y -= dot(d, d) / (2.0 * max(1.0, uEarthRadius));',
'}',
'gl_Position = uProj * uView * vec4(origin + p, 1.0);',
'}'
].join('\n');
@@ -5366,6 +5406,8 @@ var CT_VERT_SRC = [
'uniform mat4 uProj;',
'uniform mat4 uView;',
'uniform vec3 uCamPos;',
'uniform float uEarthRadius;',
'uniform float uCurvatureOn;',
'varying float vU;',
'varying float vV;',
'varying float vS;',
@@ -5373,8 +5415,13 @@ var CT_VERT_SRC = [
'varying vec3 vWorld;',
'void main() {',
'vU = aU; vV = aV; vS = aS; vP = aP;',
'vWorld = aPos;',
'gl_Position = uProj * uView * vec4(aPos, 1.0);',
'vec3 pos = aPos;',
'if (uCurvatureOn > 0.5) {',
'vec2 d = pos.xz - uCamPos.xz;',
'pos.y -= dot(d, d) / (2.0 * max(1.0, uEarthRadius));',
'}',
'vWorld = pos;',
'gl_Position = uProj * uView * vec4(pos, 1.0);',
'}'
].join('\n');
@@ -6370,6 +6417,8 @@ window.clouds3dRender = function(target) {
gl.uniformMatrix4fv(carUniforms.proj, false, proj);
gl.uniformMatrix4fv(carUniforms.view, false, view);
gl.uniform3f(carUniforms.camPos, camX, camY, camZ);
gl.uniform1f(carUniforms.earthRadius, EARTH_RADIUS_UNITS);
gl.uniform1f(carUniforms.curvatureOn, CURVATURE_ON ? 1.0 : 0.0);
gl.uniform1f(carUniforms.time, now);
gl.uniform1f(carUniforms.night, carNight);
// dot radius the light needs per unit distance to still reach
@@ -6414,6 +6463,8 @@ window.clouds3dRender = function(target) {
proj: gl.getUniformLocation(cpp, 'uProj'),
view: gl.getUniformLocation(cpp, 'uView'),
camPos: gl.getUniformLocation(cpp, 'uCamPos'),
earthRadius: gl.getUniformLocation(cpp, 'uEarthRadius'),
curvatureOn: gl.getUniformLocation(cpp, 'uCurvatureOn'),
time: gl.getUniformLocation(cpp, 'uTime'),
night: gl.getUniformLocation(cpp, 'uNight'),
speed: gl.getUniformLocation(cpp, 'uSpeed'),
@@ -6441,6 +6492,8 @@ window.clouds3dRender = function(target) {
gl.uniformMatrix4fv(carPtsUniforms.proj, false, proj);
gl.uniformMatrix4fv(carPtsUniforms.view, false, view);
gl.uniform3f(carPtsUniforms.camPos, camX, camY, camZ);
gl.uniform1f(carPtsUniforms.earthRadius, EARTH_RADIUS_UNITS);
gl.uniform1f(carPtsUniforms.curvatureOn, CURVATURE_ON ? 1.0 : 0.0);
gl.uniform1f(carPtsUniforms.time, now);
gl.uniform1f(carPtsUniforms.night, carNight);
gl.uniform1f(carPtsUniforms.sunAlt, window.__currentSunAlt || 0);
@@ -6496,6 +6549,8 @@ window.clouds3dRender = function(target) {
gl.uniformMatrix4fv(cityUniforms.proj, false, proj);
gl.uniformMatrix4fv(cityUniforms.view, false, view);
gl.uniform3f(cityUniforms.camPos, camX, camY, camZ);
gl.uniform1f(cityUniforms.earthRadius, EARTH_RADIUS_UNITS);
gl.uniform1f(cityUniforms.curvatureOn, CURVATURE_ON ? 1.0 : 0.0);
gl.uniform1f(cityUniforms.night, Math.min(1, carNight * 1.35));
gl.uniform1f(cityUniforms.sunAlt, window.__currentSunAlt || 0);
gl.uniform1f(cityUniforms.lightHi, LIGHT_HI);
@@ -6560,6 +6615,8 @@ window.clouds3dRender = function(target) {
proj: gl.getUniformLocation(fltProg, 'uProj'),
view: gl.getUniformLocation(fltProg, 'uView'),
camPos: gl.getUniformLocation(fltProg, 'uCamPos'),
earthRadius: gl.getUniformLocation(fltProg, 'uEarthRadius'),
curvatureOn: gl.getUniformLocation(fltProg, 'uCurvatureOn'),
scale: gl.getUniformLocation(fltProg, 'uScale'),
time: gl.getUniformLocation(fltProg, 'uTime'),
night: gl.getUniformLocation(fltProg, 'uNight'),
@@ -6577,6 +6634,8 @@ window.clouds3dRender = function(target) {
gl.uniformMatrix4fv(fltUniforms.proj, false, proj);
gl.uniformMatrix4fv(fltUniforms.view, false, view);
gl.uniform3f(fltUniforms.camPos, camX, camY, camZ);
gl.uniform1f(fltUniforms.earthRadius, EARTH_RADIUS_UNITS);
gl.uniform1f(fltUniforms.curvatureOn, CURVATURE_ON ? 1.0 : 0.0);
gl.uniform1f(fltUniforms.scale, gl.drawingBufferHeight / 600);
gl.uniform1f(fltUniforms.time, now);
gl.uniform1f(fltUniforms.night, Math.min(1, carNight * 1.2));
@@ -6621,6 +6680,9 @@ window.clouds3dRender = function(target) {
planeU = {
proj: gl.getUniformLocation(pp, 'uProj'),
view: gl.getUniformLocation(pp, 'uView'),
camPos: gl.getUniformLocation(pp, 'uCamPos'),
earthRadius: gl.getUniformLocation(pp, 'uEarthRadius'),
curvatureOn: gl.getUniformLocation(pp, 'uCurvatureOn'),
origin: gl.getUniformLocation(pp, 'uOrigin'),
orit: gl.getUniformLocation(pp, 'uOrit'),
s: gl.getUniformLocation(pp, 'uS'),
@@ -6636,6 +6698,9 @@ window.clouds3dRender = function(target) {
gl.useProgram(planeProg);
gl.uniformMatrix4fv(planeU.proj, false, proj);
gl.uniformMatrix4fv(planeU.view, false, view);
gl.uniform3f(planeU.camPos, camX, camY, camZ);
gl.uniform1f(planeU.earthRadius, EARTH_RADIUS_UNITS);
gl.uniform1f(planeU.curvatureOn, CURVATURE_ON ? 1.0 : 0.0);
gl.uniform3f(planeU.sun, sunv[0], sunv[1], sunv[2]);
gl.uniform1f(planeU.light, dayf);
var moonv = window.__currentMoonDir || [0, 1, 0];
@@ -6765,6 +6830,8 @@ window.clouds3dRender = function(target) {
ctU = { proj: gl.getUniformLocation(cpm, 'uProj'),
view: gl.getUniformLocation(cpm, 'uView'),
camPos: gl.getUniformLocation(cpm, 'uCamPos'),
earthRadius: gl.getUniformLocation(cpm, 'uEarthRadius'),
curvatureOn: gl.getUniformLocation(cpm, 'uCurvatureOn'),
sunDir: gl.getUniformLocation(cpm, 'uSunDir'),
sunColor: gl.getUniformLocation(cpm, 'uSunColor'),
sunInt: gl.getUniformLocation(cpm, 'uSunInt'),
@@ -6780,6 +6847,8 @@ window.clouds3dRender = function(target) {
gl.uniformMatrix4fv(ctU.proj, false, proj);
gl.uniformMatrix4fv(ctU.view, false, view);
gl.uniform3f(ctU.camPos, camX, camY, camZ);
gl.uniform1f(ctU.earthRadius, EARTH_RADIUS_UNITS);
gl.uniform1f(ctU.curvatureOn, CURVATURE_ON ? 1.0 : 0.0);
gl.uniform3f(ctU.sunDir, sd[0], sd[1], sd[2]);
gl.uniform3f(ctU.sunColor, _beam[0], _beam[1], _beam[2]);
gl.uniform1f(ctU.sunInt, _beamL);