docs: add render diagrams and complete config examples

This commit is contained in:
Richard Zink committed 2026-10-12 21:15:24 +02:00
1 parent b9a77fbaa4
commit d5fb1f12c4
3 files changed
+246 -139

No files matched your search

+146 -10
View File
@@ -8,8 +8,8 @@ dropped into an iframe or kiosk browser and left running.
- **WebGL flight scene** (`vendor/clouds3d.js`): a 3D terrain mesh built from
real elevation data (Copernicus DEM), a raymarched volumetric cloud deck,
and a figure-eight flight path around the configured location with a
vertical bob inside the 15-35 km flight band.
optional round-world curvature, and a figure-eight flight path around the
configured location with a vertical bob inside the 15-35 km flight band.
- **One physical light model** shared by sky, clouds, contrails, aircraft and
ground: direct beam through a Kasten-Young air mass with Rayleigh, ozone and
aerosol extinction, diffuse skylight, moonlight, a starlight floor and aerial
@@ -20,11 +20,12 @@ dropped into an iframe or kiosk browser and left running.
- **Theme-tinted lighting**: each theme may pull the physical colours toward
its own palette and set its own day/night exposure, without replacing the
physics (`window.THEMES[...].lighting`).
- **Shadows**: moving cloud shadows sampled from the same cloud field the sky
renders, plus terrain self-shadowing over the DEM height field.
- **Shadows**: moving cloud shadows sampled from the same wind-advected cloud
field the sky renders, plus terrain self-shadowing over the DEM height field.
- **Realistic night sky**: ~8700 stars from the Yale Bright Star Catalogue
(points sized by magnitude, coloured by temperature), rotated at the sidereal
rate for the site's latitude, a Milky Way band and a moon disc with a real
rate for the site's latitude, a Milky Way band, approximate Keplerian planet
points for Mercury..Pluto with planet colours, and a moon disc with a real
lunar surface map: the terminator comes from the sun direction, so crescent,
gibbous and full fall out of the geometry (sun and moon discs are drawn
enlarged - `CONFIG.sun`/`CONFIG.moon` - while the lighting stays physical).
@@ -37,7 +38,8 @@ dropped into an iframe or kiosk browser and left running.
nodes (bigger towns get more and brighter dots). Both fade in light by light
across twilight instead of switching as one block.
- **Live weather** (Open-Meteo): current conditions plus a 4-day forecast
widget; the weather code selects the cloud coverage and the colour mood.
widget; the weather code selects the cloud coverage and the colour mood, and
wind speed/direction advect the cloud field.
- **Fully cached, offline-capable**: basemap tiles, the elevation grid, Overpass
road/place cells, the merged road chains and the decoded settlement list live
permanently in IndexedDB; the star catalogue is inlined. Once warm, the scene
@@ -106,6 +108,138 @@ cache, which is the gentlest option for the upstreams. If a source is
unreachable, the scene degrades instead of failing: an "OFFLINE"-labelled
ground grid in place of basemap tiles.
## Render flow diagrams
The scene is a sequence of full-screen and geometry passes sharing one light
state. The diagrams below describe the current pipeline.
### Frame pipeline
```mermaid
flowchart TD
A[background.html] --> B[CONFIG + THEMES]
A --> C[Open-Meteo weather]
A --> D[SunCalc clock: sun, moon, sidereal time]
A --> E[IndexedDB cache: tiles, DEM, Overpass]
B --> F[clouds3dRender]
C --> F
D --> F
E --> F
F --> G[Sky pass: gradient, stars, Milky Way, sun, moon]
G --> H[Star catalogue points]
H --> I[Planet points]
G --> J[Terrain distance pass]
J --> K[Terrain mesh pass]
K --> L[City lights]
L --> M[Car lights]
M --> N[Optional atmosphere model]
N --> O[Front haze]
O --> P[Cloud raymarch]
P --> Q[Aircraft]
Q --> R[Contrails]
```
### Weather and cloud calculation
```mermaid
flowchart LR
A[Open-Meteo current/hourly] --> B[weather_code]
A --> C[wind_speed_10m]
A --> D[wind_direction_10m]
B --> E[weather theme]
B --> F[cloud coverage density]
C --> G[wind speed in world units/s]
D --> H[advection vector toward opposite bearing]
F --> I[cloudDensity fBm]
G --> I
H --> I
I --> J[raymarch cloud deck]
J --> K[cloud colour and alpha]
K --> L[sky/cloud pixels]
K --> M[terrain cloud shadows]
```
### Cloud shadows on terrain
```mermaid
sequenceDiagram
participant Sky as Sky/Cloud pass
participant Cloud as cloudDensity()
participant Wind as wind vector
participant Terrain as Terrain shader
participant Sun as Sun direction
Wind->>Cloud: offset cloud field over time
Terrain->>Cloud: sample from ground point toward sun
Cloud-->>Terrain: optical depth through cloud deck
Terrain->>Terrain: darken direct sunlight by cloud optical depth
Terrain->>Terrain: reduce skylight by cloudShadowAmbient
Terrain-->>Screen: shaded ground patch under moving cloud
```
### Terrain self-shadowing
```mermaid
sequenceDiagram
participant T as Terrain fragment
participant H as DEM height field
participant Sun as Sun direction
T->>H: march from surface point toward sun
H-->>T: terrain height samples
T->>T: compare surface height with sampled terrain height
T->>T: shadow = 0 if blocked, 1 if lit
T-->>Screen: ridge/valley shading with terrainShadowFloor
```
### Aircraft and contrails
```mermaid
flowchart TD
A[OSM aerodromes] --> B[airport graph]
B --> C[destination selection]
C --> D[Bezier route: climb, cruise, descent]
D --> E[aircraft mesh + night strobes]
D --> F[eligible for contrail]
F --> G[contrail ribbon geometry]
G --> H[world-space fBm shader]
H --> I[contrail pixels]
E --> I
```
### Roads, car lights and city lights
```mermaid
flowchart TD
A[Overpass road cells] --> B[road chains]
B --> C[road slots]
C --> D[traffic side lookup]
D --> E[vehicle points]
E --> F[distance fade + night ramp]
F --> G[car light pixels]
H[Overpass place nodes] --> I[settlement list]
I --> J[rank/population weighting]
J --> K[city light clusters]
K --> L[city light pixels]
```
### Planets and stars
```mermaid
flowchart LR
A[scene date] --> B[Keplerian planet positions]
B --> C[geocentric ecliptic vector]
C --> D[RA/Dec]
D --> E[local sky direction]
E --> F[planet points]
G[Yale star catalogue] --> H[RA/Dec]
H --> I[sidereal rotation]
I --> J[star points]
```
## Configuration
`config.js` is the reference: every key is documented in place, and unused
@@ -125,13 +259,15 @@ Groups:
| `ground.style` | `'satellite'` (ESRI World_Imagery) or `'streets'` (ESRI World_Street_Map) |
| `orbit` | figure-eight path, `radiusKm`, direction, reorientation, `altBob` |
| `fog.distanceKm` | distance haze (0/false = off) |
| `clouds` | deck height (`top`/`bottom` in world units) and `density` per weather level |
| `clouds` | deck height (`top`/`bottom` in world units), `density` per weather level, and `wind` motion from Open-Meteo |
| `flights` | aircraft count, altitudes, speeds, destination weights, contrails, night strobes |
| `terrain` | DEM mesh, `exaggeration`, `edgeFadeTiles`, `carLights`, `cityLights`, `wireframe` |
| `terrain` | DEM mesh, `exaggeration`, edge fade/skirt, `outerDem`, `carLights`, `cityLights`, `wireframe` |
| `carLights` | road width, light sizes, lane separation, traffic range/fade, `trafficSide`, spacing, speed, density, brightness |
| `lights` | twilight switching window of every artificial light (`offDeg`, `onDeg`, `softDeg`) |
| `atmosphere` | turbidity, moon/star gains, night floor, albedo/sky/air gains, haze residual, cloud albedo and cloud haze, cloud shadows, terrain shadows |
| `atmosphere` | turbidity, moon/star gains, night floor, albedo/sky/air gains, haze blocks, cloud albedo/haze, cloud shadows, terrain shadows |
| `stars` | star colour `tint` and `brightness` |
| `planets` | planet points: `enabled`, `brightness`, `sizeScale`, `showPluto` |
| `curvature` | round-world curvature: `enabled`, `earthRadiusKm`, `exaggeration` |
| `sun` | `angularRadius` (disc size only, 12x physical by default) |
| `moon` | disc size (`angularRadius`, 12x physical by default), `texture`, `lightGain`, `earthshine`, `limbDarkening`, `brightness`, `halo`, `flipU`/`flipV` |
| `camera` | `aim` (`'off'` or `'moon'`), `aimEase`, `aimBob` |
@@ -140,7 +276,7 @@ Groups:
### World scale
1 world unit = one zoom-11 tile ≈ 12.9 km at 48.6°N. World axes are
`+X = west`, `+Y = up`, `+Z = north`; the camera flies the 15-35 km band
`+X = east`, `+Y = up`, `+Z = north`; the camera flies the 15-35 km band
(`y ≈ 1.16-2.70`). Cloud deck heights, fog distance and orbit radius are all
expressed in these units or in km where noted.
+90 -34
View File
@@ -23,18 +23,20 @@
* ground basemap style
* orbit flight path (figure eight or straight line)
* fog distance haze
* clouds cloud deck height + per-weather coverage
* clouds cloud deck height, coverage and wind motion
* flights civil aviation layer (aircraft + contrails)
* terrain 3D ground, car lights, settlement lights
* carLights tuning of the vehicle light layer
* lights twilight switching of artificial lights
* atmosphere physical light model + shadow/haze knobs
* stars star catalogue tint and brightness
* planets Keplerian planet points in the night sky
* curvature round-world curvature for terrain, roads, lights and sky paths
* day simulated day cycle or the real sun
*
* WORLD SCALE
* 1 world unit = 1 zoom-11 tile ~ 12.9 km at 48.6 deg N.
* World axes: +X = west, +Y = up, +Z = north (verified against the
* World axes: +X = east, +Y = up, +Z = north (verified against the
* basemap and the star frame). Camera flies a 15-35 km band (y ~ 1.16-2.70).
*
* TIME FORMAT TOKENS (own mini formatter, Moment.js-like names)
@@ -50,11 +52,19 @@ window.CONFIG = {
// Identity and UI
// ------------------------------------------------------------------
theme: 'ash', // 'ash' | 'nord' | 'gruvbox' (see window.THEMES)
language: 'en', // 'en', 'de' - keys of the i18n.js dictionaries
// theme: 'nord'
// theme: 'gruvbox'
language: 'en', // 'en', 'de', 'it', 'es', 'fr', 'ru'
// language: 'de'
timeFormat: 'DD. MMMM, YYYY ‒ HH:mm:SS',
// timeFormat: 'YYYY-MM-DD HH:mm:ss'
// timeFormat: 'dddd, DD. MMMM YYYY HH:mm'
debug: 0, // 1 = debug panel + progress logs in the console
// debug: 1
credits: true, // false = hide the attribution line
// credits: false
units: { temperature: 'C' }, // 'F' for Fahrenheit
// units: { temperature: 'F' }
// ------------------------------------------------------------------
// Rendering budget
@@ -64,14 +74,14 @@ window.CONFIG = {
// and upscaled with image-rendering: pixelated, so it reads as pixel
// art instead of a blurry rescale. The DOM UI stays crisp at any value.
// 1 = native resolution (sharpest, most expensive).
renderScale: 0.5,
renderScale: 0.5, // 1 native, 0.5 pixel-art 2x, 0.25 4x
// Scene frame cap. All motion is time-based, so this only changes
// smoothness, never speed. 0 = free running (vsync).
maxFps: 30,
maxFps: 30, // 0 = free running
// MSAA on the WebGL framebuffer. Costs fill rate, and with renderScale
// below 1 the pixel blocks dominate anyway - set false if the scene is
// GPU bound. Read once at context creation, so a reload is needed.
antialias: true,
antialias: true, // false saves GPU fill rate
// ------------------------------------------------------------------
// Location
@@ -82,6 +92,7 @@ window.CONFIG = {
lat: 48.5667,
lon: 13.4319
// label: 'Passau' // overrides the reverse-geocoded name
// lat: 51.5074, lon: -0.1278 // London example
},
// ------------------------------------------------------------------
@@ -90,8 +101,9 @@ window.CONFIG = {
// false = live Open-Meteo conditions (also used for the theme choice).
// true = cycle sunny -> cloudy -> stormy -> night, useful to compare
// all weather looks without waiting on the API.
weatherSim: false,
weatherSim: false, // true cycles sunny/cloudy/stormy/night
weatherSimInterval: 30000, // ms per weather step when weatherSim is true
// weatherSim: true, weatherSimInterval: 15000
// ------------------------------------------------------------------
// Tiles and caches
@@ -101,15 +113,17 @@ window.CONFIG = {
// ('background-tiles' / store 'tiles') and are never re-fetched once
// cached: a warm cache renders fully offline.
tiles: {
expireDays: null, // e.g. 90 = re-fetch basemap tiles every 90 days
expireDays: null, // null = never expire; e.g. 90 = re-fetch every 90 days
debug: false // true = tile borders + N/S + x/y labels baked
// into the ground texture (orientation debug)
// tiles: { expireDays: 30, debug: true }
},
ground: {
// 'satellite' = ESRI World_Imagery (aerial, no labels)
// 'streets' = ESRI World_Street_Map (with place names)
// Both are cached separately (cache id prefix 'sat/' vs plain).
style: 'satellite'
// style: 'streets'
},
// ------------------------------------------------------------------
@@ -121,6 +135,8 @@ window.CONFIG = {
orbit: {
radiusKm: 60, // lobe tip reach (clamped ~190 km so the
// tips plus the fog dome fit the mosaic)
// radiusKm: 35 // tighter flight
// radiusKm: 120 // wider flight, more terrain visible
clockwise: true, // travel direction along the eight
reorient: true, // false keeps the bearing across crossings
// Vertical bobbing inside the 15-35 km band: centre 25 km, amp is
@@ -135,6 +151,8 @@ window.CONFIG = {
// ------------------------------------------------------------------
fog: {
distanceKm: 120 // fully saturated haze at this range; 0/false = off
// distanceKm: 0 // no distance fog
// distanceKm: 220 // deeper aerial perspective
},
// ------------------------------------------------------------------
@@ -143,7 +161,7 @@ window.CONFIG = {
// World units (1 unit ~ 12.9 km). Keep 'top' below the lowest flight
// altitude so aircraft fly above the deck.
clouds: {
enabled: true,
enabled: true, // false removes the volumetric cloud deck
top: 1.0, // ~12.9 km
bottom: -0.2, // ~-2.6 km (deck base slightly below terrain)
// Coverage per weather level, 0 = clear, 1 = overcast. Only the
@@ -158,9 +176,11 @@ window.CONFIG = {
// bearing. speedScale exaggerates real advection so the motion is
// visible at the world scale (1 unit ~ 12.9 km).
wind: {
speedScale: 80,
minSpeed: 0.004,
maxSpeed: 0.08
speedScale: 80, // 0 = no wind motion, 40 slow, 120 fast
minSpeed: 0.004, // world units/s floor when API wind is calm
maxSpeed: 0.08 // world units/s ceiling for storm winds
// wind: { speedScale: 0 } // static clouds
// wind: { speedScale: 140, maxSpeed: 0.12 } // very visible motion
}
// density: { sunny: 0.05, cloudy: 0.9, stormy: 1.0, night: 0.2 }
},
@@ -199,7 +219,7 @@ window.CONFIG = {
// Paths are Bezier curves resampled to arc length, flown as a full
// profile: climb, cruise, descent, landing, ground hold, new leg.
flights: {
enabled: true,
enabled: true, // false removes aircraft and contrails
altitudeKm: 14, // cruise altitude
climbKm: 100, // climb/descent run to and from cruise
count: 21, // simultaneous aircraft (max 240)
@@ -213,11 +233,12 @@ window.CONFIG = {
wStrip: 1,
// Contrails: contrailChance of the aircraft leaves a trail once
// above contrailStartKm, visible for contrailLifeS seconds.
contrailChance: 1.00,
contrailStartKm: 4,
contrailLifeS: 300,
contrailChance: 1.00, // 0 = no contrails, 1 = every eligible jet
contrailStartKm: 4, // start after this distance from the airport
contrailLifeS: 300, // seconds before a trail disappears
contrailWidthM: 1400, // full trail width
contrailFadeKm: 300, // fully faded at this distance (ramp starts at 35 %)
// contrailChance: 0.35, contrailLifeS: 120 // short sparse trails
// Night contrast: a physically scaled contrail is a faint grey thread
// that disappears at 4x pixel scale, so this is the night share of its
// opacity (0.34 was the old value) and it also drives the gain on the
@@ -243,15 +264,17 @@ window.CONFIG = {
// height field the terrain shadows are cast against.
terrain: {
enabled: true, // false = flat plane (no DEM, no terrain shadows)
// terrain: false // completely disable the 3D ground
exaggeration: 8, // vertical scale (1 = real, nearly invisible here)
edgeFadeTiles: 4, // soft rim at the mosaic border, in tiles (~13 km)
edgeSkirtKm: 800, // coarse outer terrain pulled past the horizon
edgeSkirtRings: 24, // coarse rings in the outer skirt
edgeSkirtNoise: 0.28, // fallback height variation if outer DEM is off
outerDem: {
enabled: true,
enabled: true, // false uses procedural fallback heights
zoom: 7, // reduced-resolution terrarium zoom for the skirt
maxMeters: 2500 // height normalization for skirt colors
// outerDem: { enabled: false }
},
carLights: true, // moving vehicle lights on the real road network
cityLights: true, // settlement lights from OSM place nodes
@@ -264,6 +287,7 @@ window.CONFIG = {
// Roads come from Overpass (motorway/trunk for the A-net, primary/
// secondary for the side layer), cached per grid cell.
carLights: {
// All distances are real metres/km unless the name says Px.
roadWidthM: 24, // real carriageway width of the road line
bandPx: 1.2, // pixel floor of the road line
bandBrightness: 0.10, // road line glow (the points are the main layer)
@@ -284,12 +308,15 @@ window.CONFIG = {
// Japan, Australia, India, southern Africa, ...), 'left'/'right' forces it.
trafficSide: 'auto',
dashM: 620, // mean vehicle spacing on motorways
// (side roads use dashM * 2.8)
// side roads use dashM * 2.4
// dashM: 350 // denser traffic
// dashM: 1200 // sparse traffic
spreadKm: 45, // gaussian sigma around the motorway network
spreadFloor: 0.30, // share that far side roads keep
speed: 1.0, // flow speed multiplier
density: 1.0, // vehicle density multiplier
brightness: 1.3 // vehicle light intensity
// speed: 2.0, density: 0.6, brightness: 1.0
},
// ------------------------------------------------------------------
@@ -300,9 +327,10 @@ window.CONFIG = {
// piece. offDeg +5 deg is about 30 min before sunset here, onDeg -6 deg
// is civil twilight. In the morning the same thresholds switch off again.
lights: {
offDeg: 5,
onDeg: -6,
offDeg: 5, // sun altitude where lights start switching off
onDeg: -6, // sun altitude where lights are fully on
softDeg: 1.2 // width of the individual transition
// offDeg: 8, onDeg: -4, softDeg: 2.0 // earlier, softer twilight
},
// ------------------------------------------------------------------
@@ -325,7 +353,6 @@ window.CONFIG = {
airGain: 0.55, // Rayleigh airlight strength (blue-biased)
physicalSky: true, // Rayleigh sky scattering instead of pure theme gradient
rayleighGain: 8.0, // exposure for the physical sky term
distanceFog: false, // legacy aerial perspective is off
haze: {
enabled: false, // full-screen haze pass causes horizon/sky flicker
heightKm: 100,
@@ -336,12 +363,14 @@ window.CONFIG = {
maxAlpha: 0.55
},
terrainHaze: {
enabled: true,
scaleHeightKm: 8.5,
density: 0.22,
gain: 1.0,
maxAlpha: 0.65,
color: [0.58, 0.64, 0.72]
enabled: true, // terrain-distance based haze on the ground
scaleHeightKm: 8.5, // exponential vertical falloff
density: 0.22, // optical depth per world unit
gain: 1.0, // overall multiplier
maxAlpha: 0.65, // maximum haze opacity
color: [0.58, 0.64, 0.72] // fallback colour; dynamic night tint overrides
// terrainHaze: { enabled: false }
// terrainHaze: { density: 0.35, maxAlpha: 0.80 }
},
airHaze: {
enabled: false, // replaced by stable front haze using terrain distance
@@ -352,27 +381,37 @@ window.CONFIG = {
color: [0.58, 0.64, 0.72]
},
frontHaze: {
enabled: true,
enabled: true, // stable front haze pass using terrain distance
scaleHeightKm: 8.5,
density: 0.22,
gain: 1.0,
maxAlpha: 0.65,
color: [0.58, 0.64, 0.72]
// frontHaze: { enabled: false }
// frontHaze: { density: 0.30, maxAlpha: 0.75 }
},
model: {
enabled: false // legacy full atmosphere model remains off
enabled: false // legacy full-screen atmosphere model
// model: { enabled: true, heightKm: 80, density: 0.25 }
},
hazeResidual: 0.30, // surface colour surviving at the full fog distance
cloudBrightness: 1.0, // cloud albedo (theme hue, brightness from the model)
cloudHaze: 0.22, // share of the ground haze applied to the deck
// cloudBrightness: 1.35 // brighter cloud tops
// cloudHaze: 0.10 // clouds stay cleaner against the haze
// Cloud shadows: the terrain samples the same cloud field as the sky pass.
cloudShadows: true,
cloudShadowDensity: 1.8, // optical depth through the deck
cloudShadowFloor: 0.06, // darkest shade under a thick cloud
cloudShadowAmbient: 0.65, // share of the skylight a deck also blocks
// cloudShadows: false
// cloudShadowDensity: 2.6 // stronger moving cloud shade
// cloudShadowFloor: 0.18 // softer, less contrasty shade
// Terrain self-shadowing over the DEM height field.
terrainShadows: true,
terrainShadowFloor: 0.42, // brightness a fully shadowed slope keeps (0 = black)
// terrainShadows: false
// terrainShadowFloor: 0.25 // deeper ridge shadows
// Optional variants (leave commented out to use the defaults):
// turbidity: 3.0 // hazy, warm, strongly reddened low sun
// moonGain: 1.2 // bright full-moon nights
@@ -388,6 +427,22 @@ window.CONFIG = {
stars: {
tint: 0.10, // 0 = pure white points, 1 = full blackbody colour
brightness: 1.6 // overall glow of the catalogue points
// stars: { tint: 0.35, brightness: 2.2 } // stronger colour and glow
},
// ------------------------------------------------------------------
// Planets
// ------------------------------------------------------------------
// Approximate Keplerian positions for Mercury..Pluto, rendered as
// larger coloured points in the night sky. Positions are geocentric and
// use the same simulated clock as sun/moon/stars.
planets: {
enabled: true,
brightness: 1.0, // 0.5 = subtle, 2.0 = clearly readable
sizeScale: 1.0, // 0.6 = small, 1.4 = prominent
showPluto: true // Pluto is faint; false removes it
// planets: { enabled: false }
// planets: { brightness: 1.6, sizeScale: 1.25, showPluto: false }
},
// ------------------------------------------------------------------
@@ -395,8 +450,10 @@ window.CONFIG = {
// ------------------------------------------------------------------
curvature: {
enabled: true,
earthRadiusKm: 6371,
exaggeration: 4.0
earthRadiusKm: 6371, // physical Earth radius used for the bend
exaggeration: 4.0 // 1 = physically true, 4 = visually readable
// curvature: { enabled: false } // flat world
// curvature: { exaggeration: 2.0 } // gentler horizon drop
},
// ------------------------------------------------------------------
@@ -419,6 +476,7 @@ window.CONFIG = {
// Below the horizon the view holds the moon's azimuth at the horizon.
camera: {
aim: 'off', // 'off' | 'moon'
// aim: 'moon' // look at the moon while flying the path
aimEase: 0.94, // 0 = instant turn, 0.99 = very slow
aimBob: 0.25 // share of the altitude bob kept while aiming
// camera: { aim: 'moon' } // look at the moon
@@ -435,9 +493,7 @@ window.CONFIG = {
day: {
startAt: null, // null = strictly real time
speed: 1 // virtual seconds per real second
// day: { startAt: '2026-10-29T20:56', speed: 1 } // start at a chosen
// // moment, real pace
// day: { startAt: '2026-10-29T20:56', speed: 1440 } // accelerated
// startAt: '2026-10-29T20:56', speed: 1440 // one simulated day per minute
},
// ------------------------------------------------------------------
+10 -95
View File
@@ -2783,7 +2783,6 @@ var RAYLEIGH = [0.192, 0.451, 1.0];
var ATMO_AIR_GAIN = (typeof ATMO.airGain === 'number') ? Math.max(0.05, ATMO.airGain) : 0.55;
var ATMO_HAZE_RESIDUAL = (typeof ATMO.hazeResidual === 'number')
? Math.max(0.02, Math.min(0.95, ATMO.hazeResidual)) : 0.30;
var ATMO_DISTANCE_FOG = ATMO.distanceFog !== false;
var ATMO_MODEL = ATMO.model || {};
var HAZE_CFG = ATMO.haze || {};
var ATMO_MODEL_ON = (typeof HAZE_CFG.enabled === 'boolean')
@@ -5416,9 +5415,6 @@ var planeV = null, planeVbo = null, planeProg = null, planeU = null, planeA = nu
// noise phase), alpha ramping up toward the head. By day the stripes are
// sun-lit white; by night they stay visible as dimmed moonlit streaks
// (reusing the car-lights night ramp), around 40 % strength.
var ctBuf = null, ctVbo = null, ctVerts = 0;
var ctProg = null, ctA = null, ctU = null;
var CT2_SEG = 18;
var ct2Buf = null, ct2Vbo = null, ct2Verts = 0;
var ct2Prog = null, ct2A = null, ct2U = null;
@@ -5518,95 +5514,6 @@ var CT2_FRAG_SRC = [
'}'
].join('\n');
function _ctVert(x, y, z, u, v, s, p) {
var i = ctVerts * 7;
ctBuf[i] = x; ctBuf[i + 1] = y; ctBuf[i + 2] = z;
ctBuf[i + 3] = u; ctBuf[i + 4] = v; ctBuf[i + 5] = s; ctBuf[i + 6] = p;
ctVerts++;
}
var CT_VERT_SRC = [
'attribute vec3 aPos;',
'attribute float aU;', // 0 tail .. 1 head
'attribute float aV;', // -1 .. +1 across the ribbon
'attribute float aS;', // per-trail strength (gate x day x fade)',
'attribute float aP;', // per-trail noise phase
'uniform mat4 uProj;',
'uniform mat4 uView;',
'uniform vec3 uCamPos;',
'uniform float uEarthRadius;',
'uniform float uCurvatureOn;',
'varying float vU;',
'varying float vV;',
'varying float vS;',
'varying float vP;',
'varying vec3 vWorld;',
'void main() {',
'vU = aU; vV = aV; vS = aS; vP = aP;',
'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');
var CT_FRAG_SRC = [
'precision mediump float;',
'uniform vec3 uSunColor;',
'uniform float uSunInt;',
'uniform vec3 uMoonColor;',
'uniform float uMoonInt;',
'uniform float uSkyAmb;',
'uniform float uStarAmb;',
'uniform float uNightBoost;', // extra gain on the moon/star terms
'uniform vec3 uSunDir;',
'uniform vec3 uCamPos;',
'varying float vU;',
'varying float vV;',
'varying float vS;',
'varying float vP;',
'varying vec3 vWorld;',
'void main() {',
// Procedural mip: a contrail far away is a handful of pixels wide, so
// its along-stripe density and its edge wobble are higher frequency
// than the pixel grid can carry - that is the shimmer. Both fade to
// their mean with distance, which leaves a steady faint stripe.
'float cDist = length(vWorld - uCamPos);',
'float cSoft = smoothstep(9.0, 22.0, cDist);',
// density patches drifting along the stripe
'float along = vU * 26.0 + vP * 31.0;',
'float dens = 0.62 + 0.38 * sin(along) * sin(along * 2.7 + 1.3);',
'dens = mix(dens, 0.62, cSoft);',
'if (dens < 0.0) dens = 0.0;',
// eroded, uneven edges: the stripe narrows and widens along u
'float wob = 0.55 + 0.45 * (0.5 + 0.5 * sin(along * 3.1 + 7.0));',
'wob = mix(wob, 0.78, cSoft);',
'float side = abs(vV);',
'float edge = 1.0 - smoothstep(wob * 0.22, wob, side);',
// older tail also thins out across its width
'edge *= 0.45 + 0.55 * pow(vU, 0.6);',
'if (edge <= 0.0) discard;',
'float a = vS * pow(vU, 1.15) * 1.15 * dens * edge;',
'if (a <= 0.004) discard;',
// A contrail is a cloud: sunlit head-on, moonlit at night, and on a
// dark starlit night the diffuse starlight keeps it faintly visible
// instead of a hard white stripe on black.
'vec3 vd = normalize(vWorld - uCamPos);',
'float cosT = dot(vd, -uSunDir);',
'float phase = 0.35 + 0.65 * pow(max(0.0, cosT), 2.0);',
// Same multipliers as the cloud deck, so the contrail/cloud
// brightness ratio at night is the same as in daylight.
'vec3 light = uSunColor * uSunInt * phase * 1.25',
' + (uMoonColor * uMoonInt * 3.0',
' + max(vec3(uSkyAmb), uStarAmb * vec3(0.75, 0.85, 1.0)) * 1.7)',
' * uNightBoost;',
'gl_FragColor = vec4(vec3(0.95, 0.96, 1.0) * light, a);',
'}'
].join('\n');
function decodePlaneVerts() {
if (planeV || !window.__planeVerts) return;
try {
@@ -5672,6 +5579,12 @@ function planetHeliocentric(p, d) {
var z = r * Math.sin(u) * Math.sin(i);
return [x, y, z];
}
var PLANET_CFG = (window.CONFIG && window.CONFIG.planets) || {};
var PLANETS_ON = PLANET_CFG.enabled !== false;
var PLANET_BRIGHT = (typeof PLANET_CFG.brightness === 'number') ? Math.max(0, PLANET_CFG.brightness) : 1.0;
var PLANET_SIZE_SCALE = (typeof PLANET_CFG.sizeScale === 'number') ? Math.max(0.2, Math.min(2.0, PLANET_CFG.sizeScale)) : 1.0;
var PLANET_SHOW_PLUTO = PLANET_CFG.showPluto !== false;
function updatePlanetPoints(gl, date) {
if (!gl || !date) return;
var d = (date.getTime() - Date.UTC(2000, 0, 1, 12, 0, 0)) / 86400000;
@@ -5683,6 +5596,8 @@ function updatePlanetPoints(gl, date) {
var ceps = Math.cos(eps), seps = Math.sin(eps);
for (var pi = 0; pi < PLANET_ELEMENTS.length; pi++) {
var p = PLANET_ELEMENTS[pi];
if (!PLANETS_ON) continue;
if (!PLANET_SHOW_PLUTO && p.name === 'Pluto') continue;
var helio = planetHeliocentric(p, d);
var gx = helio[0] - earth[0];
var gy = helio[1] - earth[1];
@@ -5708,9 +5623,9 @@ function updatePlanetPoints(gl, date) {
mag = mag + 5 * Math.log10(Math.max(1e-6, rGeo * rSun));
mag = Math.max(-5.0, Math.min(16.0, mag));
var bright = p.bright * Math.max(0.12, Math.min(1.25, Math.pow(2.512, -mag * 0.35)));
data[di++] = dx; data[di++] = dy; data[di++] = dz; data[di++] = p.sizeCode;
data[di++] = dx; data[di++] = dy; data[di++] = dz; data[di++] = p.sizeCode * PLANET_SIZE_SCALE;
data[di++] = p.color[0]; data[di++] = p.color[1]; data[di++] = p.color[2];
data[di++] = bright;
data[di++] = bright * PLANET_BRIGHT;
count++;
}
if (!planetVbo) planetVbo = gl.createBuffer();