- atmosphere: physical terrain lighting (air mass, Rayleigh beam colour, skylight, moon, starlight floor), aerial perspective for terrain and cloud deck, configurable haze/albedo/ambient gains - clouds: march the deck in height instead of ray distance, IGN dither (no per-frame flicker), per-step height jitter, 4-octave fbm renormalised, softer density threshold, distance softening as a procedural mip, grazing path clamp, cloud shadows on the ground (cloudShadowDensity/ Floor/Ambient) and cloud haze factor - terrain: self-shadows from the elevation field (penumbra + shadow floor), soft mosaic rim (edgeFadeTiles), east/west axis corrected (+X = west) - lights: per-object twilight switching for cars, city dots and ground blocks - flights: contrail width 620 m, fade to zero by contrailFadeKm, aircraft size decoupled from trail width, count halved - render: CONFIG.renderScale with integer upscale + image-rendering: pixelated - data: nginx overpass proxy + cache, per-mirror cooldown ladder, ring-based cell ordering (disc fills evenly), settlements fetched before roads, interim chain persistence, two worker lanes (car/city) with progress logs - removed: NASA VIIRS night-lights layer (fetch, canvas, texture, uniforms)
347 lines
18 KiB
JavaScript
347 lines
18 KiB
JavaScript
/*
|
||
* Wallpaper background config.
|
||
*
|
||
* Properties:
|
||
* theme - Active palette theme. One of the keys in window.THEMES
|
||
* (default 'ash'). The matching CSS file is loaded at
|
||
* startup.
|
||
* language - UI / i18n language code (default 'en').
|
||
* timeFormat - Format string for the clock using the token set below
|
||
* (Moment.js-like names, own mini-formatter).
|
||
* debug - If 1, the right-side debug panel is shown.
|
||
* units - Object with 'temperature' ('C' or 'F').
|
||
* location - Geographic coordinates for sun/moon calculations. Used
|
||
* by SunCalc (https://github.com/mourner/suncalc). Object
|
||
* with 'lat' (latitude, -90 to +90) and 'lon' (longitude,
|
||
* -180 to +180). Positive lat = north, positive lon = east.
|
||
* Optionally add 'label' (string) to override the
|
||
* reverse-geocoded city name.
|
||
* Note: the height grid is axis-aligned (N up); terrain
|
||
* matches the square ground mosaic and straight flights
|
||
* (prefetch direction via #fetch=N). Heights: Copernicus
|
||
* DEM via terrarium tiles (z9, ~200 m).
|
||
* terrain - 3D ground from real elevation data. Object:
|
||
* enabled - true (default) / false = flat plane.
|
||
* exaggeration - vertical scale factor on the DEM
|
||
* (default 8; 1 would be physically
|
||
* true and nearly invisible at this
|
||
* horizontal scale).
|
||
* weatherSim - If true, ignore open-meteo and cycle through
|
||
* ['sunny','cloudy','stormy','night'] automatically every
|
||
* weatherSimInterval ms. Useful for A/B testing every
|
||
* theme without waiting on the API.
|
||
* weatherSimInterval - Sim cycle period in ms (default 30000).
|
||
* orbit - Figure-eight flight (Bernoulli lemniscate) around
|
||
* CONFIG.location instead of a straight drift. The
|
||
* eight's self-crossing sits exactly on the configured
|
||
* coordinate. Each time the aircraft crosses that point
|
||
* a new random orientation is picked and blended in
|
||
* over one lobe (spline through the crossing). Object:
|
||
* radiusKm - lobe tip reach from the centre in km
|
||
* (~13 km per tile at z11/48deg N).
|
||
* Clamped (~190 km) so tips + fog horizon
|
||
* fit inside the 43x43 tile mosaic.
|
||
* clockwise - travel direction along the eight.
|
||
* reorient - random re-aim at every crossing
|
||
* (default true; false keeps the bearing).
|
||
* Set to null (or remove) for the straight-line flight.
|
||
* fog - Ground fog / distance haze. Object with 'distanceKm'
|
||
* (visibility range at which terrain fully blends into
|
||
* a milky haze; default 120). false/0 disables fog.
|
||
* clouds - Vertical extent of the cloud deck in world units
|
||
* (1 unit = ~12.9 km at zoom 11; camera band 15-35 km,
|
||
* i.e. y ~ 1.16-2.70). Default puts the deck top (~11.6
|
||
* km) below the lowest flight altitude.
|
||
* tiles - All downloads (street tiles, elevation grid, last
|
||
* weather payload, geo label) live permanently in
|
||
* IndexedDB and are never re-fetched once cached; a
|
||
* warm cache = fully offline rendering. Set
|
||
* expireDays (number > 0) to re-fetch street tiles
|
||
* periodically (e.g. 90).
|
||
*
|
||
* Time format tokens (subset):
|
||
* YYYY 4-digit year (2026)
|
||
* MMMM full month name (October)
|
||
* MM month with leading zero (10)
|
||
* DD day with leading zero (07)
|
||
* HH 24-hour hour with zero (18)
|
||
* mm minute with leading zero (14)
|
||
* ss second with leading zero (25)
|
||
* a am/pm lowercase (pm)
|
||
* A AM/PM uppercase (PM)
|
||
* dddd full weekday name (lowercase!)
|
||
* Any other character is emitted verbatim.
|
||
*/
|
||
window.CONFIG = {
|
||
theme: 'ash',
|
||
language: 'en',
|
||
timeFormat: 'DD. MMMM, YYYY ‒ HH:mm:SS',
|
||
debug: 0,
|
||
units: {
|
||
temperature: 'C'
|
||
},
|
||
// Data attribution at the bottom of the info box (automatic text
|
||
// based on the enabled layers). false = hidden.
|
||
credits: true,
|
||
// Internal 3D resolution scale (0.3..1, default 1). Lower values
|
||
// render the clouds/map shader at reduced resolution and upscale
|
||
// via CSS - use it if fps is low (GPU-bound) despite a quiet CPU.
|
||
// 1 = native. 0.5 = a quarter of the pixels (half width, half height),
|
||
// upscaled 2x as hard blocks; the DOM UI on top stays crisp.
|
||
renderScale: 0.5,
|
||
// WebGL scene frame cap (default 30; 0 = free-running/vsync). All
|
||
// scene motion is time-based, so the cap only changes smoothness.
|
||
maxFps: 30,
|
||
location: {
|
||
// Latitude and longitude for sun/moon calculations
|
||
lat: 48.5667,
|
||
lon: 13.4319
|
||
},
|
||
// weatherSim: true = Szenario-Wetter statt Open-Meteo (A/B-Durchlauf
|
||
// aller Wetterstufen alle weatherSimInterval ms). false = echtes
|
||
// Wetter von Open-Meteo.
|
||
weatherSim: false,
|
||
weatherSimInterval: 30000,
|
||
// All downloads (basemap tiles, elevation grid, weather JSON, geo
|
||
// label) stay in IndexedDB permanently and are never re-fetched once
|
||
// cached. expireDays (days, > 0) re-enables periodic basemap refresh.
|
||
tiles: {
|
||
expireDays: null,
|
||
// true = draw tile borders + N/S + x/y coords into the ground
|
||
// texture (debug orientation). Off = clean map.
|
||
debug: true
|
||
},
|
||
// Ground imagery: 'satellite' = ESRI World_Imagery (aerial, no
|
||
// labels), 'streets' = ESRI World_Street_Map (with place names).
|
||
// Both cached permanently (separate cache prefix per style).
|
||
ground: {
|
||
style: 'streets'
|
||
},
|
||
// Figure-eight course around location (lat/lon); the crossing point
|
||
// sits exactly on the coordinate. null/removed = straight flight.
|
||
orbit: {
|
||
radiusKm: 60, // lobe tip: max distance from the centre
|
||
clockwise: true,
|
||
reorient: true, // random heading at every crossing pass
|
||
// Slow vertical bobbing inside the 15-35 km flight band:
|
||
// centre 25 km (~1.93 world units), amp = half the band
|
||
// (10 km ~ 0.77); hard clamp at the band limits.
|
||
// periodS = seconds per cycle, tilt = camera nose reaction to
|
||
// climb/descent rate. enabled: false = constant 25 km.
|
||
altBob: { enabled: true, amp: 0.77, periodS: 90, tilt: 0.3 }
|
||
},
|
||
// Ground fog: distance haze. distanceKm = visibility until fully
|
||
// saturated haze (false/0 = off).
|
||
fog: {
|
||
distanceKm: 120
|
||
},
|
||
// ---- Civil aviation (CONFIG.flights) ---------------------------------
|
||
// Low-poly jets (CC0 model, vendor/plane3d.js) with randomized target
|
||
// airports: the destination is rolled at every take-off (hubs weighted
|
||
// via wHub/wField/wStrip). Paths are gentle Bezier curves resampled to
|
||
// arc length (constant ground speed) flown as a full profile: take-off
|
||
// and climb, cruise, descent and landing, a short ground hold, then a
|
||
// fresh destination. By day sun-lit silhouettes; by night white
|
||
// strobing position lights with a red beacon. Contrails form above
|
||
// contrailStartKm on contrailChance of the aircraft and stay visible
|
||
// for contrailLifeS. All distances in km (world scale: 1 unit = one
|
||
// z11 tile ~ 12.9 km at 48.6 deg N).
|
||
flights: {
|
||
enabled: true,
|
||
altitudeKm: 14, // cruise altitude
|
||
climbKm: 100, // climb/descent run to/from cruise altitude
|
||
count: 21, // max simultaneous aircraft
|
||
speedKmh: 850, // mean ground speed
|
||
speedVar: 0.18, // +/- deviation (0..0.5)
|
||
minLegKm: 60, // shortest leg (no taxi hops)
|
||
holdMinS: 50, // ground hold at destination (min..max)
|
||
holdMaxS: 150,
|
||
wHub: 4, // destination weights: hub / airfield / strip
|
||
wField: 2,
|
||
wStrip: 1,
|
||
contrailChance: 0.85, // share of aircraft with a persistent trail
|
||
contrailStartKm: 6, // trail formation altitude
|
||
contrailLifeS: 300, // trail visibility duration
|
||
contrailWidthM: 620, // full trail width
|
||
// trails fade out completely by this distance (start is 35% of it)
|
||
contrailFadeKm: 100,
|
||
// Night strobes. Size shrinks with distance but never below
|
||
// lightPx, and never grows past lightMaxPx for a jet right above.
|
||
// The white strobe is a double flash: two equal flashes
|
||
// blinkGapS apart, then blinkPeriodS - blinkGapS - blinkFlashS
|
||
// of darkness (1.4 - 0.16 - 0.08 = 1.16 s pause).
|
||
lightPx: 7,
|
||
lightMaxPx: 16,
|
||
lightBoost: 9,
|
||
blinkPeriodS: 1.4,
|
||
blinkGapS: 0.16,
|
||
blinkFlashS: 0.08
|
||
},
|
||
// 3D ground from real elevation data (Copernicus DEM, terrarium
|
||
// tiles). enabled: false = flat plane. exaggeration: vertical
|
||
// elevation scale (1 = real, nearly invisible; default 8).
|
||
// wireframe: true = overlay mesh edges as orange lines (debug).
|
||
// carLights: moving lights on the real road network (OSM/Overpass
|
||
// curves, additive over the terrain at night). JSON once, cached.
|
||
// cityLights: settlement lights - OSM place nodes (city/town/
|
||
// village/hamlet) as slightly yellow glow dots from dusk on;
|
||
// bigger settlements (place class + population tag) get more and
|
||
// brighter dots. One Overpass query, cached permanently.
|
||
// Data (c) OpenStreetMap contributors, ODbL.
|
||
terrain: {
|
||
// soft rim at the mosaic border, in tiles (1 tile ~ 13 km)
|
||
edgeFadeTiles: 4,
|
||
enabled: true,
|
||
exaggeration: 8,
|
||
wireframe: false,
|
||
// How far the day map is dimmed at full night: 0 = pitch black,
|
||
// 1 = unchanged. Only affects night; the day map is untouched.
|
||
// (The NASA VIIRS city-lights layer was removed; city lights come
|
||
// from OSM place nodes via cityLights.)
|
||
nightDim: 0.22,
|
||
carLights: true,
|
||
cityLights: true
|
||
},
|
||
// Car lights on the real road network (on/off stays in terrain.carLights).
|
||
// Two layers now: a thin road line (bandPx / bandBrightness) and one
|
||
// round point sprite per vehicle (minPx..maxPx), same idea as the city
|
||
// dots' gl_PointSize floor. laneM is the real distance between the two
|
||
// traffic directions, so they split close to the camera and merge into
|
||
// one line far away instead of drifting apart with the camera distance.
|
||
// dashM: mean vehicle spacing (secondary roads use dashM * 2.8).
|
||
// speed: multiplier on the real driving speed (motorway ~120 km/h,
|
||
// secondary ~70 km/h at dashM spacing).
|
||
carLights: {
|
||
roadWidthM: 24, // real carriageway width of the road line
|
||
bandPx: 1.2, // pixel floor for the road line
|
||
bandBrightness: 0.10, // road line glow (points are the main layer)
|
||
minPx: 3.6, // guaranteed vehicle light diameter
|
||
maxPx: 10, // ceiling for a light right above the camera
|
||
boost: 5, // extra light size close to the camera
|
||
laneM: 8, // real distance between the two directions
|
||
lanePx: 2.6, // screen floor for that distance, so the two
|
||
// directions never collapse into one stream
|
||
// Traffic lives on a disc around the configured location: roads
|
||
// outside rangeKm carry no vehicles. fadeKm / fadeSpanKm are the
|
||
// distance fade belonging to that disc (start, and how far past it
|
||
// until nothing is left) - keep them at or beyond rangeKm so the
|
||
// region never ends in a hard cut.
|
||
rangeKm: 200,
|
||
fadeKm: 150,
|
||
fadeSpanKm: 60,
|
||
// 'auto' = side of the road taken from location (lat/lon): left in
|
||
// the UK, Japan, Australia, India, Southern Africa, ... and right
|
||
// everywhere else. Set 'left' or 'right' to force it.
|
||
trafficSide: 'auto',
|
||
dashM: 620, // mean vehicle spacing on motorways
|
||
// Traffic spread around the motorway network (gaussian): peak on the
|
||
// motorway itself, thinning with distance to the nearest one.
|
||
// spreadKm = sigma, spreadFloor = share that far side roads keep.
|
||
spreadKm: 45,
|
||
spreadFloor: 0.30,
|
||
speed: 1.0, // flow speed multiplier
|
||
density: 1.0, // vehicle density multiplier
|
||
brightness: 1.3 // vehicle light intensity
|
||
},
|
||
// Headlights and house lights do not ignite at once: every single
|
||
// light gets its own sun-altitude threshold between offDeg (nothing
|
||
// lit) and onDeg (everything lit), drawn per vehicle / per city block.
|
||
// offDeg +5 deg is roughly 30 min before sunset here, onDeg -6 deg is
|
||
// civil twilight. In the morning the same thresholds switch the lights
|
||
// off again as the sun climbs.
|
||
lights: {
|
||
offDeg: 5,
|
||
onDeg: -6,
|
||
softDeg: 1.2
|
||
},
|
||
// Physical lighting: direct beam from a Kasten-Young air mass with
|
||
// Rayleigh + ozone + aerosol extinction, diffuse skylight and moonlight
|
||
// from the same model. terrainLighting: 'palette' switches back to the
|
||
// old hand-painted hillshade. turbidity scales the aerosol load,
|
||
// moonGain the moonlight strength, nightFloor the darkest skylight
|
||
// (airglow + starlight), albedoTint is the theme's share on surfaces.
|
||
atmosphere: {
|
||
terrainLighting: 'physical',
|
||
turbidity: 1.0,
|
||
// moonlight strength; scales the direct term AND the diffuse sky
|
||
// lift, both with the illuminated fraction (new moon -> none)
|
||
moonGain: 0.60,
|
||
// stylised starlight + airglow floor on a dark night (real
|
||
// integrated starlight is ~1e-6 of daylight and would be invisible)
|
||
starGain: 0.18,
|
||
// extra share on the ground. The product is the starlit floor and
|
||
// must stay below the skylight at sunset, otherwise the night would
|
||
// be brighter than the moment of sunset. Moonlight (moonGain) is
|
||
// what lifts a night above that floor.
|
||
starGainGround: 0.4,
|
||
nightFloor: 0.10,
|
||
// cloud albedo in physical mode: theme hue kept, brightness comes
|
||
// from the light model (the night palette alone would double-darken)
|
||
cloudBrightness: 1.0,
|
||
// moving cloud patches darken the ground beneath them (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 cloud also blocks
|
||
// ridges occlude the sun for the ground behind them (march over the
|
||
// same height field the mesh was built from)
|
||
terrainShadows: true,
|
||
// brightness a fully shadowed slope keeps (0 = pitch black, 1 = off)
|
||
terrainShadowFloor: 0.42,
|
||
// how much of the ground haze applies to the cloud deck (clouds sit
|
||
// above most of the air that causes it)
|
||
cloudHaze: 0.22,
|
||
albedoGain: 1.0,
|
||
// share of the daylight column that the distance fog / horizon takes,
|
||
// and the diffuse skylight share on surfaces
|
||
skyGain: 0.45,
|
||
// Aerial perspective in physical mode: the terrain is attenuated by
|
||
// exp(-tau) and the scattered light of the same air column is added.
|
||
// airGain scales the Rayleigh airlight (blue-biased), hazeResidual
|
||
// is how much of the surface survives at the full fog distance
|
||
// (0.3 = distant ground keeps 30 % of its own colour).
|
||
airGain: 0.55,
|
||
hazeResidual: 0.30
|
||
},
|
||
// Star colour: every catalogue star gets its real blackbody tint, mixed
|
||
// towards white by this factor. 0 = pure white points, 1 = full colour.
|
||
stars: {
|
||
tint: 0.10, // 90 % white + 10 % of the star's real colour
|
||
brightness: 1.6 // overall glow of the catalogue points
|
||
},
|
||
// Day simulation: true = a full 24 h cycle runs in cycleSeconds
|
||
// (sun rises east, culminates south, sets west).
|
||
// false = real sun/moon positions via SunCalc.
|
||
// fixedTime: 'HH:MM' (or a number 0-24) freezes the sim clock at
|
||
// that time of day (moving objects, clouds, cars and the weather
|
||
// sim keep running unaffected); null/omitted = the clock advances.
|
||
day: {
|
||
// simulation: true runs a full 24 h cycle in cycleSeconds.
|
||
// 60 s = one simulated day per minute of wall time (one simulated
|
||
// hour every 2.5 s). The analog clock and the sun/moon tables follow
|
||
// the simulated date.
|
||
simulation: true,
|
||
cycleSeconds: 60,
|
||
fixedTime: null
|
||
},
|
||
// Cloud deck in world units (1 unit ~ 12.9 km at zoom 11; flight
|
||
// band 15-35 km = y ~ 1.16-2.70). Keep the top below the lowest
|
||
// flight altitude.
|
||
clouds: {
|
||
top: 0.9,
|
||
bottom: -0.3,
|
||
// per weather level (0 = clear, 1 = overcast); overrides the built-in
|
||
// sunny 0.20 / cloudy 0.70 / stormy 0.80 / night 0.45
|
||
density: { stormy: 0.80 }
|
||
},
|
||
};
|
||
|
||
// List of available themes. The key is the theme id (used in config.theme),
|
||
// the value is the CSS file name. Add a new theme by adding a new entry here
|
||
// and dropping the corresponding CSS file in the same directory.
|
||
window.THEMES = {
|
||
'ash': 'ash.css',
|
||
'gruvbox': 'gruvbox.css',
|
||
'nord': 'nord.css'
|
||
}; |