From 92dd812072680a8bec4ea4537e8b481853120343 Mon Sep 17 00:00:00 2001 From: Richard Zink Date: Mon, 12 Oct 2026 23:06:53 +0200 Subject: [PATCH] weather: add wet road glow and light halos --- config.js | 8 ++++++++ vendor/clouds3d.js | 49 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/config.js b/config.js index 12aa538..58abc46 100644 --- a/config.js +++ b/config.js @@ -143,10 +143,18 @@ window.CONFIG = { minDelayS: 8, // minimum delay between storm flashes maxDelayS: 45, // maximum delay between storm flashes doubleFlash: true // some flashes flicker twice + }, + wetRoads: { + enabled: true, + dryMinutes: 30, // roads dry out after this many dry minutes + roadGain: 0.8, // extra road-ribbon glow when wet + lightGain: 0.35 // extra halo on vehicle lights when wet } // weatherEffects: { enabled: false } // weatherEffects: { rain: { maxAlpha: 0.55, speed: 1.4 }, snow: { enabled: false } } // weatherEffects: { lightning: { gain: 2.0, minDelayS: 3, maxDelayS: 12 } } + // weatherEffects: { wetRoads: { enabled: false } } + // weatherEffects: { wetRoads: { dryMinutes: 10, roadGain: 1.4, lightGain: 0.7 } } }, // ------------------------------------------------------------------ diff --git a/vendor/clouds3d.js b/vendor/clouds3d.js index cf6a709..e402b68 100644 --- a/vendor/clouds3d.js +++ b/vendor/clouds3d.js @@ -3870,6 +3870,8 @@ var CAR_FRAG_SRC = [ 'uniform float uBright;', // band brightness (CONFIG.carLights.bandBrightness)', 'uniform float uFadeA;', // distance where the fade starts', 'uniform float uFadeB;', // distance where it is fully faded', + 'uniform float uWet;', // wet-road factor 0..1', + 'uniform float uWetGain;', // extra road glow when wet', 'varying float vLen;', 'varying float vSide;', 'varying float vDens;', @@ -3886,9 +3888,13 @@ var CAR_FRAG_SRC = [ // what a road looks like when the individual vehicle points are', // closer together than one light - the "bar" along the A-net.', 'float body = 1.0 - smoothstep(0.0, 1.0, abs(vSide));', - 'float a = dens * body * uNight * df * uBright;', + 'float wet = clamp(uWet, 0.0, 1.0);', + 'float a = dens * body * uNight * df * uBright * (1.0 + wet * uWetGain);', 'if (a <= 0.002) discard;', - 'gl_FragColor = vec4(vec3(1.0, 0.82, 0.58) * a, 1.0);', + 'vec3 dryCol = vec3(1.0, 0.82, 0.58);', + 'vec3 wetCol = vec3(0.72, 0.82, 1.0);', + 'vec3 col = mix(dryCol, wetCol, wet * 0.55);', + 'gl_FragColor = vec4(col * a, 1.0);', '}' ].join('\n'); @@ -3975,6 +3981,8 @@ var CARP_FRAG_SRC = [ 'uniform float uBright;', // overall light intensity', 'uniform float uFadeA;', // distance where the fade starts', 'uniform float uFadeB;', // distance where it is fully faded', + 'uniform float uWet;', // wet-road factor 0..1', + 'uniform float uWetGain;', // extra halo on lights when wet', 'varying float vApproach;', 'varying float vDist;', 'varying float vSeed;', @@ -3989,8 +3997,9 @@ var CARP_FRAG_SRC = [ 'vec2 pc = gl_PointCoord - 0.5;', 'float d = length(pc) * 2.0;', // hot core plus a wide faint halo: a round light at any size', + 'float wet = clamp(uWet, 0.0, 1.0);', 'float core = exp(-d * d * 4.0);', - 'float halo = (1.0 - smoothstep(0.0, 1.0, d)) * 0.30;', + 'float halo = (1.0 - smoothstep(0.0, 1.0, d)) * (0.30 + wet * uWetGain);', 'float g = max(core, halo);', 'if (g <= 0.004) discard;', // soft occupancy + per-vehicle life cycle: a car fades in as the', @@ -4894,7 +4903,9 @@ function applyCarBuild(gl, res) { halfW: gl.getUniformLocation(carProg, 'uHalfW'), bright: gl.getUniformLocation(carProg, 'uBright'), fadeA: gl.getUniformLocation(carProg, 'uFadeA'), - fadeB: gl.getUniformLocation(carProg, 'uFadeB') + fadeB: gl.getUniformLocation(carProg, 'uFadeB'), + wet: gl.getUniformLocation(carProg, 'uWet'), + wetGain: gl.getUniformLocation(carProg, 'uWetGain') }; } } @@ -6041,6 +6052,26 @@ var WEATHER_LIGHTNING_MIN_DELAY = (typeof WEATHER_LIGHTNING_CFG.minDelayS === 'n var WEATHER_LIGHTNING_MAX_DELAY = (typeof WEATHER_LIGHTNING_CFG.maxDelayS === 'number') ? Math.max(WEATHER_LIGHTNING_MIN_DELAY, WEATHER_LIGHTNING_CFG.maxDelayS) : 45; var WEATHER_LIGHTNING_DOUBLE = WEATHER_LIGHTNING_CFG.doubleFlash !== false; var lightningNextT = 0, lightningSeq = 0, lightningSeqT = 0, lightningFlash = 0; +var WET_ROADS_CFG = WEATHER_FX_CFG.wetRoads || {}; +var WET_ROADS_ON = WET_ROADS_CFG.enabled !== false; +var WET_ROADS_DRY_MINUTES = (typeof WET_ROADS_CFG.dryMinutes === 'number') ? Math.max(1, WET_ROADS_CFG.dryMinutes) : 30; +var WET_ROADS_GAIN = (typeof WET_ROADS_CFG.roadGain === 'number') ? Math.max(0, WET_ROADS_CFG.roadGain) : 0.8; +var WET_LIGHT_GAIN = (typeof WET_ROADS_CFG.lightGain === 'number') ? Math.max(0, WET_ROADS_CFG.lightGain) : 0.35; +var roadWetness = 0, lastWetT = null; + +function updateRoadWetness(now) { + if (!WET_ROADS_ON) { roadWetness = 0; lastWetT = null; return 0; } + if (lastWetT === null) { lastWetT = now; return roadWetness; } + var dt = Math.max(0, Math.min(5, now - lastWetT)); + lastWetT = now; + var wx = window.__currentWeather || {}; + var precip = Number(wx.precipMm || 0); + var snow = Number(wx.snowMm || 0); + var input = Math.min(1.0, (precip + snow * 0.55) * 0.75); + var dry = dt / (WET_ROADS_DRY_MINUTES * 60.0); + roadWetness = Math.max(0, Math.min(1, roadWetness + input * dt * 0.25 - dry)); + return roadWetness; +} function updateLightning(now) { if (!WEATHER_LIGHTNING_ON) { @@ -6795,6 +6826,7 @@ window.clouds3dRender = function(target) { // Aggregate artificial-light level (road ribbon, density curves, // aircraft): the mean over the per-light thresholds. var carNight = lightFactor(window.__currentSunAlt || 0); + var _roadWet = updateRoadWetness(now); if (carProg && (carVboM || carVboB) && carNight > 0.01) { var carPxScale = Math.max(0.45, Math.min(1.25, gl.drawingBufferHeight / 1080)); var tCurve = Math.max(0, Math.min(1, (carNight - 0.10) / 0.40)); @@ -6821,6 +6853,8 @@ window.clouds3dRender = function(target) { gl.uniform1f(carUniforms.bright, CAR_BAND_BRIGHT); gl.uniform1f(carUniforms.fadeA, CAR_FADE_A); gl.uniform1f(carUniforms.fadeB, CAR_FADE_B); + gl.uniform1f(carUniforms.wet, _roadWet); + gl.uniform1f(carUniforms.wetGain, WET_ROADS_GAIN); gl.enable(gl.DEPTH_TEST); gl.depthMask(false); gl.enable(gl.BLEND); @@ -6876,7 +6910,9 @@ window.clouds3dRender = function(target) { sunAlt: gl.getUniformLocation(cpp, 'uSunAlt'), lightHi: gl.getUniformLocation(cpp, 'uLightHi'), lightLo: gl.getUniformLocation(cpp, 'uLightLo'), - lightSoft: gl.getUniformLocation(cpp, 'uLightSoft') + lightSoft: gl.getUniformLocation(cpp, 'uLightSoft'), + wet: gl.getUniformLocation(cpp, 'uWet'), + wetGain: gl.getUniformLocation(cpp, 'uWetGain') }; } } @@ -6907,6 +6943,8 @@ window.clouds3dRender = function(target) { gl.uniform1f(carPtsUniforms.bright, CAR_BRIGHT); gl.uniform1f(carPtsUniforms.fadeA, CAR_FADE_A); gl.uniform1f(carPtsUniforms.fadeB, CAR_FADE_B); + gl.uniform1f(carPtsUniforms.wet, _roadWet); + gl.uniform1f(carPtsUniforms.wetGain, WET_LIGHT_GAIN); var carPtsGroup = function (vbo, count, dash, speed, maxDens) { if (!vbo || !count) return; gl.bindBuffer(gl.ARRAY_BUFFER, vbo); @@ -7318,6 +7356,7 @@ window.clouds3dRender = function(target) { rain: +_weather[0].toFixed(2), snow: +_weather[1].toFixed(2), lightning: +lightningFlash.toFixed(2), + wet: +roadWetness.toFixed(2), code: Number((window.__currentWeather || {}).code || 0) }; var _rainI = _weather[0];