diff --git a/config.js b/config.js index 7bbb885..773096c 100644 --- a/config.js +++ b/config.js @@ -118,6 +118,29 @@ window.CONFIG = { // weatherSimProfile: { rain: { code: 80, tempC: 9, precipMm: 2.0, windKmh: 28 } } }, + // ------------------------------------------------------------------ + // Weather effects + // ------------------------------------------------------------------ + // Visual precipitation overlays driven by window.__currentWeather. + // intensity multiplies Open-Meteo values; maxAlpha caps the overlay. + weatherEffects: { + enabled: true, + rain: { + enabled: true, + intensity: 1.0, // 0.5 subtle, 2.0 heavy + maxAlpha: 0.35, // maximum screen overlay opacity + speed: 1.0 // fall speed multiplier + }, + snow: { + enabled: true, + intensity: 1.0, + maxAlpha: 0.45, + speed: 0.35 // slow drifting flakes + } + // weatherEffects: { enabled: false } + // weatherEffects: { rain: { maxAlpha: 0.55, speed: 1.4 }, snow: { enabled: false } } + }, + // ------------------------------------------------------------------ // Weather data // ------------------------------------------------------------------ diff --git a/vendor/clouds3d.js b/vendor/clouds3d.js index 1b0e245..5df65e0 100644 --- a/vendor/clouds3d.js +++ b/vendor/clouds3d.js @@ -6021,6 +6021,114 @@ function loadMoonTexture(gl) { img.src = MOON_TEX_SRC; } +// ---- Weather overlays: rain and snow ----------------------------------- +var WEATHER_FX_CFG = (window.CONFIG && window.CONFIG.weatherEffects) || {}; +var WEATHER_FX_ON = WEATHER_FX_CFG.enabled !== false; +var WEATHER_RAIN_CFG = WEATHER_FX_CFG.rain || {}; +var WEATHER_SNOW_CFG = WEATHER_FX_CFG.snow || {}; +var WEATHER_RAIN_ON = WEATHER_RAIN_CFG.enabled !== false; +var WEATHER_SNOW_ON = WEATHER_SNOW_CFG.enabled !== false; +var WEATHER_RAIN_INTENSITY = (typeof WEATHER_RAIN_CFG.intensity === 'number') ? Math.max(0, WEATHER_RAIN_CFG.intensity) : 1.0; +var WEATHER_SNOW_INTENSITY = (typeof WEATHER_SNOW_CFG.intensity === 'number') ? Math.max(0, WEATHER_SNOW_CFG.intensity) : 1.0; +var WEATHER_RAIN_ALPHA = (typeof WEATHER_RAIN_CFG.maxAlpha === 'number') ? Math.max(0, Math.min(1, WEATHER_RAIN_CFG.maxAlpha)) : 0.35; +var WEATHER_SNOW_ALPHA = (typeof WEATHER_SNOW_CFG.maxAlpha === 'number') ? Math.max(0, Math.min(1, WEATHER_SNOW_CFG.maxAlpha)) : 0.45; +var WEATHER_RAIN_SPEED = (typeof WEATHER_RAIN_CFG.speed === 'number') ? Math.max(0.1, WEATHER_RAIN_CFG.speed) : 1.0; +var WEATHER_SNOW_SPEED = (typeof WEATHER_SNOW_CFG.speed === 'number') ? Math.max(0.05, WEATHER_SNOW_CFG.speed) : 0.35; +var weatherProg = null, weatherU = null; + +var WEATHER_VERT_SRC = [ + 'attribute vec2 aPos;', + 'varying vec2 vUv;', + 'void main() {', + 'vUv = aPos * 0.5 + 0.5;', + 'gl_Position = vec4(aPos, 0.0, 1.0);', + '}' +].join('\n'); + +var WEATHER_FRAG_SRC = [ + 'precision mediump float;', + 'uniform vec2 uResolution;', + 'uniform float uTime;', + 'uniform float uRain;', + 'uniform float uSnow;', + 'uniform vec2 uWind;', + 'uniform float uRainSpeed;', + 'uniform float uSnowSpeed;', + 'uniform float uRainAlpha;', + 'uniform float uSnowAlpha;', + 'uniform float uDay;', + 'uniform float uNight;', + 'varying vec2 vUv;', + 'float hash2(vec2 p) {', + 'return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);', + '}', + 'float rainLayer(vec2 uv, float scale, float speed, float seed) {', + 'vec2 p = uv * vec2(scale * 1.7, scale);', + 'p += uWind * uTime * 0.035;', + 'p.y -= uTime * speed;', + 'vec2 ip = floor(p);', + 'vec2 fp = fract(p);', + 'float h = hash2(ip + seed);', + 'float x = fp.x - (0.15 + 0.70 * h);', + 'float y = fract(fp.y + h * 7.31);', + 'float width = 0.018 + 0.022 * hash2(ip + seed + 1.7);', + 'float len = 0.12 + 0.34 * hash2(ip + seed + 3.1);', + 'float line = 1.0 - smoothstep(0.0, width, abs(x));', + 'line *= smoothstep(0.0, 0.06, y) * (1.0 - smoothstep(len, len + 0.14, y));', + 'return line;', + '}', + 'float snowLayer(vec2 uv, float scale, float speed, float seed) {', + 'vec2 p = uv * vec2(scale * 1.25, scale);', + 'p.x += uWind.x * uTime * 0.018 + sin(uTime * 0.35 + uv.y * 4.0 + seed) * 0.18;', + 'p.y += uWind.y * uTime * 0.012 - uTime * speed;', + 'vec2 ip = floor(p);', + 'vec2 fp = fract(p);', + 'float h = hash2(ip + seed);', + 'vec2 c = vec2(0.20 + 0.60 * h, 0.20 + 0.60 * hash2(ip + seed + 5.3));', + 'float d = length(fp - c);', + 'float r = 0.055 + 0.055 * hash2(ip + seed + 8.1);', + 'return 1.0 - smoothstep(0.0, r, d);', + '}', + 'void main() {', + 'float vis = clamp(uDay + uNight, 0.18, 1.0);', + 'float rain = 0.0;', + 'if (uRain > 0.001) {', + 'rain += rainLayer(vUv, 18.0, 1.35 * uRainSpeed, 0.0) * 0.75;', + 'rain += rainLayer(vUv, 28.0, 1.05 * uRainSpeed, 11.0) * 0.55;', + 'rain += rainLayer(vUv, 44.0, 0.75 * uRainSpeed, 27.0) * 0.35;', + 'rain *= uRain * uRainAlpha * vis;', + '}', + 'float snow = 0.0;', + 'if (uSnow > 0.001) {', + 'snow += snowLayer(vUv, 12.0, 0.22 * uSnowSpeed, 0.0) * 0.70;', + 'snow += snowLayer(vUv, 20.0, 0.16 * uSnowSpeed, 17.0) * 0.50;', + 'snow += snowLayer(vUv, 34.0, 0.11 * uSnowSpeed, 39.0) * 0.30;', + 'snow *= uSnow * uSnowAlpha * vis;', + '}', + 'float a = clamp(rain + snow, 0.0, 0.95);', + 'if (a <= 0.002) discard;', + 'vec3 rainCol = vec3(0.62, 0.74, 0.92);', + 'vec3 snowCol = vec3(0.92, 0.96, 1.0);', + 'vec3 col = mix(snowCol, rainCol, clamp(rain / max(a, 0.001), 0.0, 1.0));', + 'gl_FragColor = vec4(col * a, a);', + '}' +].join('\n'); + +function weatherOverlayIntensities() { + var wx = window.__currentWeather || {}; + var precip = Number(wx.precipMm || 0); + var snow = Number(wx.snowMm || 0); + var temp = Number(wx.tempC !== undefined ? wx.tempC : 10); + var rain = 0, snowI = 0; + if (WEATHER_RAIN_ON) rain = Math.max(0, Math.min(1, precip * 0.55 * WEATHER_RAIN_INTENSITY)); + if (WEATHER_SNOW_ON) snowI = Math.max(0, Math.min(1, snow * 1.25 * WEATHER_SNOW_INTENSITY)); + if (temp < 1.0 && precip > 0 && snowI < 0.18) { + snowI = Math.max(snowI, Math.max(0, Math.min(1, precip * 0.85 * WEATHER_SNOW_INTENSITY))); + rain = 0; + } + return [rain, snowI]; +} + window.clouds3dRender = function(target) { if (!target || typeof target !== 'object') { console.error('clouds3d: invalid target'); @@ -7156,6 +7264,66 @@ window.clouds3dRender = function(target) { } } + // ---- Weather overlay: rain/snow ------------------------------------ + if (WEATHER_FX_ON) { + var _weather = weatherOverlayIntensities(); + var _rainI = _weather[0]; + var _snowI = _weather[1]; + if (_rainI > 0.001 || _snowI > 0.001) { + if (!weatherProg) { + var wp = makeProgram(gl, WEATHER_VERT_SRC, WEATHER_FRAG_SRC); + if (wp) { + weatherProg = wp; + weatherU = { + aPos: gl.getAttribLocation(wp, 'aPos'), + res: gl.getUniformLocation(wp, 'uResolution'), + time: gl.getUniformLocation(wp, 'uTime'), + rain: gl.getUniformLocation(wp, 'uRain'), + snow: gl.getUniformLocation(wp, 'uSnow'), + wind: gl.getUniformLocation(wp, 'uWind'), + rainSpeed: gl.getUniformLocation(wp, 'uRainSpeed'), + snowSpeed: gl.getUniformLocation(wp, 'uSnowSpeed'), + rainAlpha: gl.getUniformLocation(wp, 'uRainAlpha'), + snowAlpha: gl.getUniformLocation(wp, 'uSnowAlpha'), + day: gl.getUniformLocation(wp, 'uDay'), + night: gl.getUniformLocation(wp, 'uNight') + }; + } + } + if (weatherProg) { + var _wx = (_cloudWind && _cloudWind[0]) || 0; + var _wz = (_cloudWind && _cloudWind[2]) || 0; + var _screenWindX = _wx * hdZ - _wz * hdX; + var _screenWindY = _wx * hdX + _wz * hdZ; + var _weatherDay = 1 - Math.min(1, Math.max(0, -sunAlt / 0.30)); + var _weatherNight = lightFactor(sunAlt); + gl.useProgram(weatherProg); + gl.bindBuffer(gl.ARRAY_BUFFER, s.buf); + gl.enableVertexAttribArray(weatherU.aPos); + gl.vertexAttribPointer(weatherU.aPos, 2, gl.FLOAT, false, 0, 0); + gl.uniform2f(weatherU.res, canvas.width, canvas.height); + gl.uniform1f(weatherU.time, now); + gl.uniform1f(weatherU.rain, _rainI); + gl.uniform1f(weatherU.snow, _snowI); + gl.uniform2f(weatherU.wind, _screenWindX, _screenWindY); + gl.uniform1f(weatherU.rainSpeed, WEATHER_RAIN_SPEED); + gl.uniform1f(weatherU.snowSpeed, WEATHER_SNOW_SPEED); + gl.uniform1f(weatherU.rainAlpha, WEATHER_RAIN_ALPHA); + gl.uniform1f(weatherU.snowAlpha, WEATHER_SNOW_ALPHA); + gl.uniform1f(weatherU.day, _weatherDay); + gl.uniform1f(weatherU.night, _weatherNight); + gl.disable(gl.DEPTH_TEST); + gl.depthMask(false); + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE); + gl.drawArrays(gl.TRIANGLES, 0, 3); + gl.disable(gl.BLEND); + gl.depthMask(true); + gl.enable(gl.DEPTH_TEST); + } + } + } + // Restore default state for the next frame. gl.disable(gl.BLEND); gl.enable(gl.DEPTH_TEST);