Spaces:
Running
Running
Download atmos.js from CyberMax-tools/fellwing: direct link, hf CLI and curl.
- Browser
- Download file 7.55 kB
-
https://huggingface.co/spaces/CyberMax-tools/fellwing/resolve/main/atmos.js
- Command line
-
hf download hf://spaces/CyberMax-tools/fellwing/atmos.js
-
curl -L -o atmos.js https://huggingface.co/spaces/CyberMax-tools/fellwing/resolve/main/atmos.js
7.55 kB
| // Fellwing sky and air: one GLSL atmosphere shared by the sky dome, the terrain's aerial perspective, the sea and the | |
| // clouds, so far mountains fade into exactly the sky behind them. Analytic single-scattering look (Rayleigh blue, | |
| // Mie haze and sun glow, sunset reddening by air mass), Earth curvature for the horizon, and a time-of-day table. | |
| // (c) 2026 CyberMax. All rights reserved. | |
| export const ATMOS_GLSL = /* glsl */` | |
| uniform vec3 uSun; // unit vector to the sun (world) | |
| uniform float uHaze; // 0 = crystal clear, 1 = thick haze | |
| uniform float uOvercast; // 0..1 grey sky | |
| uniform float uNight; // 0 day .. 1 night | |
| uniform vec3 uSunCol; // sun colour * intensity (linear) | |
| const float R_EARTH = 6378137.0; | |
| vec3 skyColor(vec3 dir) { | |
| float sunH = uSun.y; | |
| float h = max(dir.y, -0.02); | |
| float mu = dot(dir, uSun); | |
| // air mass along the view (more air near the horizon) | |
| float am = 1.0 / (max(h, 0.0) + 0.05); | |
| vec3 rayleigh = vec3(0.18, 0.42, 1.0); | |
| vec3 zenith = mix(vec3(0.10, 0.32, 0.78), vec3(0.20, 0.44, 0.86), uHaze * 0.5); | |
| vec3 horizon = mix(vec3(0.66, 0.78, 0.92), vec3(0.80, 0.84, 0.88), uHaze); | |
| vec3 col = mix(horizon, zenith, pow(clamp(h, 0.0, 1.0), 0.45)); | |
| // sunset: horizon and the half of the sky around the sun go warm when the sun is low | |
| float low = 1.0 - smoothstep(-0.06, 0.32, sunH); | |
| vec3 warm = vec3(1.0, 0.48, 0.22); | |
| float side = 0.5 + 0.5 * mu; | |
| col = mix(col, mix(vec3(0.95, 0.62, 0.45), warm, side) * (0.55 + 0.45 * side), low * (1.0 - smoothstep(0.0, 0.5, h)) * 0.85); | |
| col = mix(col, col * vec3(0.55, 0.62, 0.95), low * smoothstep(0.1, 0.7, h) * 0.5); | |
| // Mie: haze glow around the sun | |
| float mie = pow(max(mu, 0.0), 8.0) * 0.35 + pow(max(mu, 0.0), 64.0) * 0.6; | |
| col += uSunCol * mie * (0.25 + uHaze * 0.6) * (1.0 - uOvercast * 0.8); | |
| // daylight level from the sun's height | |
| float day = smoothstep(-0.12, 0.18, sunH); | |
| col *= mix(0.04, 1.0, day); | |
| // overcast: flat grey that still brightens a little toward the sun | |
| vec3 grey = vec3(0.62, 0.65, 0.70) * mix(0.05, 1.0, day) * (0.85 + 0.15 * max(mu, 0.0)); | |
| col = mix(col, grey, uOvercast); | |
| // night: deep blue | |
| col = mix(col, vec3(0.006, 0.010, 0.026) + vec3(0.02, 0.03, 0.06) * pow(1.0 - h, 3.0), uNight); | |
| return col; | |
| } | |
| // Aerial perspective: blend a lit surface colour toward the sky along the view ray, with air density falling with height. | |
| vec3 aerial(vec3 col, vec3 camPos, vec3 wpos, out float fogAmt) { | |
| vec3 d = wpos - camPos; | |
| float dist = length(d); | |
| vec3 dir = d / max(dist, 1.0); | |
| float hAvg = max(0.0, 0.5 * (camPos.y + wpos.y)); | |
| float dens = exp(-hAvg / 7000.0); | |
| float beta = mix(1.0 / 95000.0, 1.0 / 16000.0, uHaze) * (1.0 + uOvercast * 0.8); | |
| fogAmt = 1.0 - exp(-dist * beta * dens); | |
| vec3 sky = skyColor(normalize(vec3(dir.x, max(dir.y, 0.0) * 0.6, dir.z))); | |
| // sunlight scattered toward the eye (in-scatter) warms the haze toward the sun | |
| float mu = max(dot(dir, uSun), 0.0); | |
| sky += uSunCol * pow(mu, 6.0) * 0.18 * (1.0 - uOvercast); | |
| // the haze is a little bluer and darker than the bright horizon itself | |
| return mix(col, sky * vec3(0.92, 0.96, 1.02), fogAmt); | |
| } | |
| // Earth curvature relative to the camera (visual only): far terrain drops below the horizon like the real thing. | |
| float curveDrop(vec3 wpos, vec3 camPos) { | |
| vec2 d = wpos.xz - camPos.xz; | |
| return dot(d, d) / (2.0 * R_EARTH); | |
| } | |
| `; | |
| /** | |
| * Times of day: sun elevation and azimuth (deg, azimuth 0 = north), sun colour/intensity, ambient, night amount. | |
| * 'day' is free; the rest come with the Fun Pass. | |
| */ | |
| export const TIMES = { | |
| day: { name: 'Midday', el: 38, az: 150, sun: [1.0, 0.97, 0.92], i: 3.0, amb: 1.0, night: 0, free: true }, | |
| morning: { name: 'Morning', el: 22, az: 110, sun: [1.0, 0.9, 0.78], i: 2.6, amb: 0.85, night: 0 }, | |
| golden: { name: 'Golden hour', el: 7, az: 255, sun: [1.0, 0.68, 0.40], i: 2.3, amb: 0.6, night: 0 }, | |
| dusk: { name: 'Dusk', el: -2.5, az: 275, sun: [1.0, 0.45, 0.25], i: 1.0, amb: 0.32, night: 0.25 }, | |
| night: { name: 'Night', el: -18, az: 300, sun: [0.55, 0.62, 0.85], i: 0.22, amb: 0.12, night: 1 }, | |
| live: { name: 'Live (local time)', el: 30, az: 180, sun: [1.0, 0.97, 0.92], i: 3.0, amb: 1.0, night: 0, live: true }, | |
| }; | |
| /** Light for any sun elevation: interpolates the presets above by elevation (so a real sun gets matching colours). */ | |
| export function lightFor(el) { | |
| const K = [TIMES.night, TIMES.dusk, TIMES.golden, TIMES.morning, TIMES.day]; | |
| if (el <= K[0].el) return K[0]; | |
| if (el >= K[4].el) return { ...K[4], el }; | |
| let i = 0; while (el > K[i + 1].el) i++; | |
| const a = K[i], b = K[i + 1], t = (el - a.el) / (b.el - a.el), m = (x, y) => x + (y - x) * t; | |
| return { el, sun: a.sun.map((v, j) => m(v, b.sun[j])), i: m(a.i, b.i), amb: m(a.amb, b.amb), night: m(a.night, b.night) }; | |
| } | |
| /** Weather presets. 'fair' is free. wind = [from deg, knots], turb 0..1. */ | |
| export const WEATHER = { | |
| fair: { name: 'Fair', haze: 0.08, overcast: 0, clouds: 0.32, cloudAlt: 2600, wind: [270, 6], turb: 0.05, free: true }, | |
| clear: { name: 'Crystal clear', haze: 0.0, overcast: 0, clouds: 0, cloudAlt: 3000, wind: [0, 0], turb: 0 }, | |
| hazy: { name: 'Summer haze', haze: 0.7, overcast: 0, clouds: 0.18, cloudAlt: 3200, wind: [200, 8], turb: 0.12 }, | |
| overcast: { name: 'Overcast', haze: 0.45, overcast: 0.85, clouds: 0.92, cloudAlt: 1900, wind: [240, 14], turb: 0.3 }, | |
| windy: { name: 'Strong wind', haze: 0.25, overcast: 0.15, clouds: 0.45, cloudAlt: 2400, wind: [300, 28], turb: 0.65 }, | |
| }; | |
| /** | |
| * The real sun for a place and moment (NOAA low-precision solar position; better than 0.5 deg until 2100). | |
| * Returns { el, az } in degrees (azimuth 0 = north, clockwise). date: a JS Date (UTC instant). | |
| */ | |
| export function sunPosition(lat, lon, date) { | |
| const D = Math.PI / 180; | |
| const jd = date.getTime() / 864e5 + 2440587.5, n = jd - 2451545.0; | |
| const L = (280.46 + 0.9856474 * n) % 360, g = ((357.528 + 0.9856003 * n) % 360) * D; | |
| const lam = (L + 1.915 * Math.sin(g) + 0.02 * Math.sin(2 * g)) * D, eps = (23.439 - 0.0000004 * n) * D; | |
| const ra = Math.atan2(Math.cos(eps) * Math.sin(lam), Math.cos(lam)), dec = Math.asin(Math.sin(eps) * Math.sin(lam)); | |
| const gmst = (18.697374558 + 24.06570982441908 * n) % 24; | |
| const ha = ((gmst * 15 + lon) * D - ra); | |
| const la = lat * D; | |
| const el = Math.asin(Math.sin(la) * Math.sin(dec) + Math.cos(la) * Math.cos(dec) * Math.cos(ha)); | |
| const az = Math.atan2(-Math.sin(ha), Math.tan(dec) * Math.cos(la) - Math.sin(la) * Math.cos(ha)); | |
| return { el: el / D, az: ((az / D) + 360) % 360 }; | |
| } | |
| /** Today's UTC instant at a local mean solar time (hours) for a longitude. */ | |
| export function solarTime(lon, hours, day = new Date()) { | |
| const d = Date.UTC(day.getUTCFullYear(), day.getUTCMonth(), day.getUTCDate()); | |
| return new Date(d + (hours - lon / 15) * 3600e3); | |
| } | |
| /** The sun's local solar times for each preset: noon, mid-morning, ~40 min before sunset, just after it, late night. */ | |
| export const SOLAR = { day: 12.5, morning: 8.75, golden: 'set-0.6', dusk: 'set+0.35', night: 23.5 }; | |
| /** Sunset solar hour for lat on a date (sun centre at -0.83 deg); 18 when the sun never sets or never rises. */ | |
| export function sunsetHour(lat, date = new Date()) { | |
| const D = Math.PI / 180; | |
| const doy = Math.floor((date - Date.UTC(date.getUTCFullYear(), 0, 0)) / 864e5); | |
| const dec = -23.44 * Math.cos((360 / 365) * (doy + 10) * D) * D; | |
| const c = (Math.sin(-0.83 * D) - Math.sin(lat * D) * Math.sin(dec)) / (Math.cos(lat * D) * Math.cos(dec)); | |
| if (c <= -1 || c >= 1) return 18; | |
| return 12 + Math.acos(c) / D / 15; | |
| } | |