physical lighting, volumetric clouds, terrain shadows, pixel-art render scale

- 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)
This commit is contained in:
Richard Zink committed 2026-10-11 15:04:21 +02:00
1 parent cd460ec100
commit 6eb25bdd03
6 files changed
+2579 -795

No files matched your search

+150 -16
View File
@@ -86,7 +86,9 @@ window.CONFIG = {
// 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.
renderScale: 1,
// 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,
@@ -95,7 +97,10 @@ window.CONFIG = {
lat: 48.5667,
lon: 13.4319
},
weatherSim: true,
// 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
@@ -104,13 +109,13 @@ window.CONFIG = {
expireDays: null,
// true = draw tile borders + N/S + x/y coords into the ground
// texture (debug orientation). Off = clean map.
debug: false
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: 'satellite'
style: 'streets'
},
// Figure-eight course around location (lat/lon); the crossing point
// sits exactly on the coordinate. null/removed = straight flight.
@@ -145,7 +150,7 @@ window.CONFIG = {
enabled: true,
altitudeKm: 14, // cruise altitude
climbKm: 100, // climb/descent run to/from cruise altitude
count: 84, // max simultaneous aircraft
count: 21, // max simultaneous aircraft
speedKmh: 850, // mean ground speed
speedVar: 0.18, // +/- deviation (0..0.5)
minLegKm: 60, // shortest leg (no taxi hops)
@@ -154,20 +159,28 @@ window.CONFIG = {
wHub: 4, // destination weights: hub / airfield / strip
wField: 2,
wStrip: 1,
contrailChance: 0.6, // share of aircraft with a persistent trail
contrailChance: 0.85, // share of aircraft with a persistent trail
contrailStartKm: 6, // trail formation altitude
contrailLifeS: 300, // trail visibility duration
contrailWidthM: 310 // full trail width
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).
// nightLights: NASA VIIRS city-lights layer, cross-faded into the
// day map over ~1 h after sunset (dark ground, glowing cities).
// One-time fetch (~49 tiles), cached permanently. false = no light
// overlay; at night the normal texture stays much brighter (only
// gently dimmed).
// 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/
@@ -176,13 +189,127 @@ window.CONFIG = {
// 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,
nightLights: 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.
@@ -190,16 +317,23 @@ window.CONFIG = {
// 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: 20,
fixedTime: '20:30'
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
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 }
},
};