flights: full CONFIG.flights parameterization (altitude, count, speed, holds, destination weights, contrail chance/alt/life/width) + curved random-destination routing

This commit is contained in:
Richard Zink committed 2026-10-10 11:02:58 +02:00
1 parent fbac24773a
commit d0aa86ce9b
10 files changed
+1230 -209

No files matched your search

+1
View File
@@ -0,0 +1 @@
background-standalone.html
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2026 zink
Copyright (c) 2026 Richard Zink
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+160 -82
View File
@@ -1,36 +1,46 @@
# Animated Background
An animated desktop background that runs in a browser. Features:
A browser-based animated desktop background: a WebGL flight scene over
real terrain, driven by the real sun, moon, stars, and live weather.
- Time-of-day simulation with realistic color gradients driven by the
actual sun position (computed from latitude/longitude via SunCalc).
- WebGL flight scene (`vendor/clouds3d.js`): raymarched cloud deck, 3D
terrain from Copernicus DEM, real star sky (Yale BSC + Milky Way),
moon with phases, animated highway car lights and settlement lights
from OpenStreetMap/Overpass at night.
- Night brightness coupled to the moon phase (new moon = minimum, full
moon = maximum), with a configurable minimum so the night is never
pitch-black.
- Live weather widget (Open-Meteo) showing current conditions and a
4-day forecast (morning, evening, full) at every timestamp.
- Configurable units (metric °C+km/h or imperial °F+mph).
- Configurable language (with English as the base; translations for
German, Italian, Spanish, French, and Russian).
- Configurable color theme (Ash, Gruvbox, Nord) - drop a new
`*.css` file in the same directory and add an entry to `window.THEMES`
to add a new theme.
- Customisable location: set `CONFIG.location.lat/lon`, or let the
browser's geolocation API pick one up automatically (falls back to
the built-in default Passau).
- Optional custom location label (`CONFIG.location.label`) that
overrides the OpenStreetMap reverse-geocoding lookup.
- Configurable digital clock format string using ISO-8601-ish tokens
(YYYY, MM, DD, HH, mm, ss, DDD, DDDD, MMM, MMMM, a, A, sss, T, escape
sequences \n \t \\, and full weekday/month names via the i18n dict).
- Configurable debug panel (toggle collapse/expand with theme, units,
and language dropdowns that apply at runtime).
## Features
## Run with Docker
- **WebGL flight scene** (`vendor/clouds3d.js`): raymarched cloud deck,
3D terrain mesh from real elevation data (Copernicus DEM), satellite
or street basemap, figure-eight flight path around the configured
location with a vertical bob between **15 and 35 km** flight altitude.
- **Real day/night cycle** driven by the actual sun and moon position
(SunCalc) for the configured latitude/longitude — or an accelerated
24 h simulation (`CONFIG.day`) for wall-decoration purposes.
- **Realistic night sky**: ~8700 stars from the Yale Bright Star
Catalogue (crisp GL points, size by magnitude, colour by temperature),
rotated with the sidereal rate for the site's latitude, plus a Milky
Way band and a phase-correct moon.
- **Aircraft**: low-poly jets (CC0 model) cruising the region on curved
routes between real airfields, rolling a random destination at every
take-off - sun-lit silhouettes with contrails by day, strobing position
lights by night (all parameters in `CONFIG.flights`).
- **Night lights**: animated car lights along the real motorway/road
network and pale-yellow settlement lights (OSM place nodes; bigger
towns get more and brighter dots), optionally a NASA VIIRS night-lights
satellite overlay. All faded in from dusk, density shaped by a
rush-hour curve.
- **Live weather** (Open-Meteo): current conditions + 4-day forecast
widget; the weather code selects the scene's cloud density and colour
mood (sunny / cloudy / stormy / night).
- **Fully cached, offline-capable**: basemap tiles, elevation grid,
Overpass road/place data, star catalogue and the last weather payload
live permanently in IndexedDB. Once warm, the scene renders fully
offline; a configurable expiry (`CONFIG.tiles.expireDays`) can refresh
basemap tiles periodically.
- **Location-aware**: set `CONFIG.location.lat/lon`; everything
(flight anchor, terrain tiles, sun/moon, weather, star rotation) is
computed from it. If the location is omitted, browser geolocation is
tried, falling back to the built-in default (London).
- Units (°C/°F), six UI languages, CSS-swappable colour themes
(Ash/Gruvbox/Nord), configurable clock format, optional debug panel.
## Run with Docker (recommended)
```sh
docker compose up --build
@@ -38,41 +48,131 @@ docker compose up --build
Then open <http://localhost:58881/>.
## Run locally
The bundled nginx config (`default.conf`) is more than a file server: it
provides CORS proxies for the tile sources (`/esri/`, `/carto/`,
`/terrarium/`) that the scene needs for basemap and elevation data.
A plain static server without these proxies (e.g.
`python3 -m http.server 58881`) will run the UI but terrain and map
tiles will fail to load.
The app is a single HTML page. With any static file server:
## Single-file build
`build.py` bundles everything into one self-contained HTML file
(`background-standalone.html`) — all scripts, the star catalogue and all
theme CSS are inlined, and theme switching keeps working:
```sh
python3 -m http.server 58881
python3 build.py # -> background-standalone.html (no sibling files needed)
```
Open <http://localhost:58881/>.
Runtime *data* is still fetched at load (tiles, Overpass, weather): serve
the file from the Docker image for the full experience; opened via
`file://` or a proxy-less server it renders sky, clouds and UI with an
"OFFLINE"-labelled ground grid instead of basemap tiles.
## Configuration
Edit `config.js` and reload the page:
Edit `config.js` and reload. All keys (with defaults):
```javascript
window.CONFIG = {
debug: 0, // 0 = hide debug panel, 1 = show
theme: 'ash', // 'ash' | 'gruvbox' | 'nord'
units: 'metric', // 'metric' | 'imperial'
language: 'de', // 'en' | 'de' | 'it' | 'es' | 'fr' | 'ru'
language: 'en', // 'en' | 'de' | 'it' | 'es' | 'fr' | 'ru'
timeFormat: 'DD. MMMM, YYYY \u2012 HH:mm:SS',
location: {
lat: 48.5667, // latitude, -90 to +90, north positive
lon: 13.4319, // longitude, -180 to +180, east positive
label: '' // optional custom place name;
// if set, skips reverse-geocoding
debug: 0, // 1 = show right-side debug panel
units: { temperature: 'C' }, // 'C' | 'F'
credits: true, // data-attribution block in info box
renderScale: 1, // 0.3..1 internal 3D resolution
location: { // drives scene anchor, sun/moon, weather
lat: 51.5074, // -90..+90, north positive
lon: -0.1278, // -180..+180, east positive
label: '' // optional name, skips reverse geocoding
},
orbit: { // omit/null = straight-line flight
radiusKm: 15, // circular flight around location;
// clamped to ~50 km at zoom 11
clockwise: true // turn direction
day: {
simulation: false, // true: run 24 h in cycleSeconds;
cycleSeconds: 20, // false: real sun/moon positions
fixedTime: null // 'HH:MM' freezes the sim clock at
// that time of day (cars, clouds,
// weather keep moving); null = flow
},
weatherSim: false, // true: cycle weather themes, ignore API
weatherSimInterval: 30000, // ms per sim step
clouds: { top: 0.9, bottom: -0.3 },// cloud deck, world units
// (1 unit = ~12.9 km at z11 / 48.6 deg N)
orbit: { // null/omit = straight flight
radiusKm: 60, // lemniscate lobe tip (clamped ~190 km)
clockwise: true,
reorient: true, // random heading at each crossing
altBob: { // vertical bob, flight band 15-35 km
enabled: true, // false = constant 25 km
amp: 0.77, // half-band in world units
periodS: 90, // seconds per bob cycle
tilt: 0.3 // pitch reaction to climb/descent
}
},
fog: { distanceKm: 120 }, // ground haze; 0/false = off
terrain: {
enabled: true, // false = flat plane
exaggeration: 8, // vertical DEM scale factor
wireframe: false, // debug: orange mesh edges
nightLights: false, // NASA VIIRS city-lights overlay
carLights: true, // moving lights on real roads
cityLights: true // settlement glow dots
},
flights: { // see section above; all distances km
enabled: true,
altitudeKm: 14, climbKm: 100, // cruise / climb-descent run
count: 84, speedKmh: 850, speedVar: 0.18, minLegKm: 60,
holdMinS: 50, holdMaxS: 150, // ground hold at destination
wHub: 4, wField: 2, wStrip: 1, // destination roll weights
contrailChance: 0.6, // share of aircraft with a trail
contrailStartKm: 6, // formation altitude
contrailLifeS: 300, // visibility duration
contrailWidthM: 310 // full trail width
},
ground: { style: 'satellite' }, // 'satellite' | 'streets'
tiles: {
expireDays: null, // >0: re-fetch basemap tiles after n days
debug: false // tile borders + coords on the ground
}
};
```
## URL hash parameters
`http://host:58881/#flight=N&fetch=S`
- `flight=N|NE|E|SE|S|SW|W|NW` — heading of the straight flight corridor
(used when `CONFIG.orbit` is disabled)
- `fetch=N|NE|E|SE|S|SW|W|NW` — direction the tile mosaic is prefetched
ahead of the camera
## Time format tokens
```
YYYY 4-digit year (2026) MM month 01-12 (10)
DD day 01-31 (07) HH hour 00-23 (14)
mm minute 00-59 (35) ss second 00-59 (22)
sss millisecond 000-999 (456) a am/pm (pm)
A AM/PM (PM)
MMM month short name (Oct, Okt, Ott, ...)
MMMM month full name (October, Oktober, ...)
DDD weekday short name (Mon, Mo, Lu, ...)
dddd weekday full name (Monday, Montag, ...) lowercase!
Any other character is emitted verbatim (so a literal T in ISO-8601
needs no special token).
\n newline \t tab \\ literal backslash
```
## Adding a new language
Edit `i18n.js` and add entries for each existing key under your new
@@ -85,65 +185,43 @@ window.TRANSLATIONS['Sunrise']['xx'] = 'MyTranslation';
## Adding a new theme
Drop a new `*.css` file next to the others and add an entry to
`window.THEMES`:
`window.THEMES` in `config.js`:
```javascript
window.THEMES['mytheme'] = 'mytheme.css';
```
## Time format tokens
```
YYYY 4-digit year (2026)
MM month 01-12 (10)
DD day 01-31 (07)
HH hour 00-23 (24h) (14)
mm minute 00-59 (35) lowercase!
ss second 00-59 (22) lowercase!
sss millisecond 000-999 (456)
a am/pm (lowercase) (pm)
A AM/PM (uppercase) (PM)
T literal 'T'
MMM month short name (Oct, Okt, Ott, ...)
MMMM month full name (October, Oktober, ...)
DDD weekday short name (Mon, Mo, Lu, ...)
DDDD weekday full name (Monday, Montag, ...)
\n newline
\t tab
\\ literal backslash
```
## Files
```
.
├── background.html the page (formerly index.html)
├── background.html the page (UI, weather, day/night driver)
├── config.js user-facing configuration
├── i18n.js translation strings (en/de/it/es/fr/ru)
├── ash.css default color theme
├── gruvbox.css alternative color theme
├── nord.css alternative color theme
├── vendor/ vendored third-party files (no network needed)
│ ├── clouds3d.js WebGL scene: raymarched clouds, 3D terrain,
│ │ real sun/moon/stars, car + city lights
├── vendor/
│ ├── clouds3d.js WebGL scene renderer (clouds, terrain, sky, lights)
│ ├── suncalc.js ISC (SunCalc by mourner)
│ ├── stars.json Yale Bright Star Catalogue subset
│ └── LICENSE per-library license attributions
├── default.conf nginx server config (port 58881, tile CORS proxies)
├── default.conf nginx config: port 58881 + tile CORS proxies
├── build.py single-file HTML bundler (see "Single-file build")
├── Dockerfile builds nginx:alpine image
└── docker-compose.yml one-service Compose file
├── docker-compose.yml one-service Compose file
└── LICENSE MIT (application code)
```
## Licenses
## Licenses & data attribution
- **Background application code**: MIT
- **Background application code**: MIT (see `LICENSE`)
- **SunCalc** (vendored): ISC - <https://github.com/mourner/suncalc>
- **Open-Meteo** (weather API): CC BY 4.0 - <https://open-meteo.com/>
- **OpenStreetMap** (basemap tiles, Overpass road/place data): ODbL -
<https://www.openstreetmap.org/copyright>
- **Copernicus DEM** (terrain via terrarium tiles): open data
- **OpenStreetMap** (basemap tiles via ESRI/Carto, Overpass road and
place data): ODbL - <https://www.openstreetmap.org/copyright>
- **Copernicus DEM** (terrain via Mapzen terrarium tiles): open data
- **NASA GIBS VIIRS** (optional night-lights overlay): public domain
- **Yale Bright Star Catalogue** (star positions, via `vendor/stars.json`)
- **OpenStreetMap Nominatim** (reverse geocoding): ODbL -
<https://nominatim.org/release/>
+1 -2
View File
@@ -1,5 +1,4 @@
/* Ash palette by zink (this project)
* Original: https://git.familie-zink.org/zink/omarchy-ash-theme
/* Ash palette by Richard Zink (this project)
* License: See repository LICENSE
*
* Generic color system - simple to customize for other themes.
+53 -5
View File
@@ -12,6 +12,15 @@
var cssFile = window.THEMES && window.THEMES[theme];
var css = document.getElementById('theme-css');
if (css) css.href = cssFile || (window.THEMES && window.THEMES.ash) || 'ash.css';
// standalone build (build.py): themes are embedded in
// window.__inlineThemeCss and the external link is disabled
if (window.__inlineThemeCss && window.__inlineThemeCss[theme]) {
if (css) css.media = 'not all';
var s = document.createElement('style');
s.id = 'theme-css-inline';
s.textContent = window.__inlineThemeCss[theme];
document.head.appendChild(s);
}
})();
</script>
<link id="theme-css" rel="stylesheet" href="ash.css">
@@ -519,6 +528,7 @@
<div id="weather-forecast" class="weather-forecast"></div>
<div id="credits" class="credits"></div>
</div>
<script src="vendor/plane3d.js"></script>
<script src="vendor/clouds3d.js"></script>
<script>
window.__cloudsStarted = true;
@@ -594,6 +604,24 @@ window.addEventListener('resize', sceneRender);
// west). Off = real SunCalc positions.
var DAY_SIM = !!(window.CONFIG && window.CONFIG.day && window.CONFIG.day.simulation);
var CYCLE = ((window.CONFIG && window.CONFIG.day && window.CONFIG.day.cycleSeconds) || 20) * 1000;
// CONFIG.day.fixedTime freezes the simulated clock at one time of
// day: 'HH:MM', 'HH:MM:SS' or a number (0..24). null/unset = the
// clock advances as before. Only the TIME OF DAY stops - cloud
// drift, car flow and the bobbing camera run on wall time, and
// the weather sim has its own timer, so all of that keeps moving.
var FIXED_T = (function () {
var v = window.CONFIG && window.CONFIG.day && window.CONFIG.day.fixedTime;
if (v == null || v === '') return null;
var h;
if (typeof v === 'number') h = v;
else {
var p = String(v).split(':');
h = parseInt(p[0], 10) || 0;
if (p.length > 1) h += parseInt(p[1], 10) / 60;
if (p.length > 2) h += parseInt(p[2], 10) / 3600;
}
return (h >= 0 && h < 24) ? h / 24 : null;
})();
// simulated sun altitude (rad): t=0 midnight, 0.25 sunrise, 0.5 noon
function simSunAlt(t) {
return Math.sin((t - 0.25) * Math.PI * 2) * 1.45;
@@ -603,12 +631,17 @@ window.addEventListener('resize', sceneRender);
// simulated cycle. The visible clock hands/digital stay real.
function simNow() {
if (!DAY_SIM) return new Date();
if (FIXED_T !== null) {
var f = new Date();
f.setHours(Math.floor(FIXED_T * 24), Math.round((FIXED_T * 1440) % 60), 0, 0);
return f;
}
return new Date(startTime + (Date.now() - startTime) * (86400000 / CYCLE));
}
var __simT = 0;
var SITE_LAT = (window.CONFIG && window.CONFIG.location && window.CONFIG.location.lat) || 48.5667;
var SITE_LON = (window.CONFIG && window.CONFIG.location && window.CONFIG.location.lon) || 13.4319;
var SITE_LAT = (window.CONFIG && window.CONFIG.location && window.CONFIG.location.lat) || 51.5074;
var SITE_LON = (window.CONFIG && window.CONFIG.location && window.CONFIG.location.lon) || -0.1278;
var realDayProgress = 0;
@@ -1598,6 +1631,8 @@ window.weatherThemes = {
lines.push('Terrain: Copernicus DEM via Mapzen Terrarium');
if (t.nightLights !== false) lines.push('Night lights: NASA GIBS VIIRS');
if (t.carLights !== false || t.cityLights !== false) lines.push('Roads: \u00a9 OpenStreetMap contributors (ODbL)');
var fc = cfg.flights;
if (!fc || fc.enabled !== false) lines.push('Airliner model: Grab3D (CC0)');
}
lines.push('Weather: Open-Meteo');
el.innerHTML = lines.join('<br>');
@@ -1731,7 +1766,18 @@ window.weatherThemes = {
function setDebugTheme(theme) {
var cssFile = window.THEMES && window.THEMES[theme];
var css = document.getElementById('theme-css');
if (css && cssFile) css.href = cssFile;
if (window.__inlineThemeCss && window.__inlineThemeCss[theme]) {
if (css) css.media = 'not all';
var s = document.getElementById('theme-css-inline');
if (!s) {
s = document.createElement('style');
s.id = 'theme-css-inline';
document.head.appendChild(s);
}
s.textContent = window.__inlineThemeCss[theme];
} else if (css && cssFile) {
css.href = cssFile;
}
setTimeout(function() {
rebuildFromColors();
var pal = document.getElementById('dbg-palette-name');
@@ -1862,7 +1908,9 @@ window.weatherThemes = {
var __fpsCount = 0, __fpsT0 = performance.now(), __dbgT0 = 0;
function animate() {
var t = (Date.now() - startTime) / CYCLE % 1;
var t = (DAY_SIM && FIXED_T !== null)
? FIXED_T
: (Date.now() - startTime) / CYCLE % 1;
var c = getColor(t);
c = applyWeatherTheme(c);
// Expose current palette + weather info for the WebGL scene
@@ -1916,7 +1964,7 @@ window.weatherThemes = {
window.__moonElong0 = SunCalc.getMoonIllumination(new Date()).phase * Math.PI * 2;
window.__moonSimStartMs = Date.now();
}
var _simDays = DAY_SIM ? (Date.now() - window.__moonSimStartMs) / CYCLE : 0;
var _simDays = (DAY_SIM && FIXED_T === null) ? (Date.now() - window.__moonSimStartMs) / CYCLE : 0;
var _elong = window.__moonElong0 + _simDays * (Math.PI * 2 / 29.53);
var _sd = window.__currentSunDir;
var _cp = SITE_LAT * Math.PI / 180;
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""
Build a single self-contained HTML file from the source files.
Usage:
python3 build.py [output.html]
If output is not given, defaults to 'background-standalone.html'.
What gets inlined:
- config.js, i18n.js (plain <script> swap)
- vendor/suncalc.js, vendor/clouds3d.js (plain <script> swap)
- vendor/stars.json (window.__starsData)
- every theme CSS referenced by window.THEMES
(window.__inlineThemeCss,
so theme switching works
without any files)
The result needs NO sibling files. Note that runtime DATA is still
fetched when the page loads: basemap/elevation tiles (via the nginx
CORS proxies of the Docker image), Overpass road + place data and the
Open-Meteo weather API. Serve the standalone file from the same nginx
(or a warm IndexedDB cache = fully offline) for the full experience;
opened via file:// it still runs sky, clouds and the whole UI, just
without the textured ground.
"""
import json
import os
import re
import sys
ROOT = os.path.dirname(os.path.abspath(__file__))
def read(path):
with open(os.path.join(ROOT, path), 'r', encoding='utf-8') as f:
return f.read()
def guard(s):
# a literal </script> inside an inline block would end it early
return s.replace('</', '<\\/')
def inline_script(html, tag, body):
old = '<script src="%s"></script>' % tag
new = '<script>\n%s\n</script>' % body
if old not in html:
raise SystemExit('build.py: expected tag not found: ' + tag)
return html.replace(old, new, 1)
def parse_themes(config_src):
m = re.search(r'window\.THEMES\s*=\s*\{(.*?)\}', config_src, re.S)
if not m:
raise SystemExit('build.py: window.THEMES not found in config.js')
return dict(re.findall(r"'([^']+)'\s*:\s*'([^']+\.css)'", m.group(1)))
def main():
out = sys.argv[1] if len(sys.argv) > 1 else 'background-standalone.html'
html = read('background.html')
config_src = read('config.js')
themes = parse_themes(config_src)
# embedded data preamble (before clouds3d's own scripts run)
stars = json.loads(read('vendor/stars.json'))
theme_css = {name: read(fname) for name, fname in themes.items()}
embed = (
'<script>\n'
'window.__starsData = %s;\n'
'window.__inlineThemeCss = %s;\n'
'</script>\n'
% (json.dumps(stars, separators=(',', ':')),
json.dumps(theme_css))
)
anchor = '<script src="vendor/suncalc.js"></script>'
html = html.replace(anchor, embed + anchor, 1)
html = inline_script(html, 'config.js', read('config.js'))
html = inline_script(html, 'i18n.js', read('i18n.js'))
html = inline_script(html, 'vendor/suncalc.js', read('vendor/suncalc.js'))
html = inline_script(html, 'vendor/plane3d.js', read('vendor/plane3d.js'))
html = inline_script(html, 'vendor/clouds3d.js', read('vendor/clouds3d.js'))
# the default <link id="theme-css" href="ash.css"> stays but is
# disabled by the inline-theme loader; keep a guarded fallback anyway
html = html.replace('<link id="theme-css"', '<!-- standalone: themes embedded -->\n <link id="theme-css"', 1)
with open(os.path.join(ROOT, out), 'w', encoding='utf-8') as f:
f.write(html)
print('Wrote %s (%d bytes)' % (out, len(html)))
print('Inlined: config.js, i18n.js, suncalc.js, clouds3d.js,')
print(' stars.json (%d entries), themes: %s'
% (len(stars), ', '.join(themes)))
if __name__ == '__main__':
main()
+51 -17
View File
@@ -6,7 +6,8 @@
* (default 'ash'). The matching CSS file is loaded at
* startup.
* language - UI / i18n language code (default 'en').
* timeFormat - Moment.js-compatible format string for the clock.
* 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
@@ -16,9 +17,9 @@
* Optionally add 'label' (string) to override the
* reverse-geocoded city name.
* Note: the height grid is axis-aligned (N up); terrain
* matches the 8-mode square mosaic and straight flights
* with fetch=N. Heights: Copernicus DEM via terrarium
* tiles (z9, ~200 m), fetched once per session.
* 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
@@ -48,9 +49,9 @@
* (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 = ~13 km at zoom 11; camera flies at y=2.2).
* Object with 'top' and 'bottom'. Default keeps the
* deck a bit below flight altitude.
* (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
@@ -60,16 +61,16 @@
*
* Time format tokens (subset):
* YYYY 4-digit year (2026)
* YY 2-digit year (26)
* MMMM full month name (October)
* MM month with leading zero (10)
* DD day with leading zero (07)
* HH 24-hour hour with zero (18)
* hh 12-hour hour with zero (06)
* mm minute with leading zero (14)
* ss second with leading zero (25)
* a am/pm lowercase (pm) - long form
* A AM/PM uppercase (PM) - long form
* 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',
@@ -143,11 +144,39 @@ window.CONFIG = {
// hamlet) als leicht gelbliche Glow-Punkte, ab Daemmerung statisch;
// je groesser die Siedlung (place-Klasse + population-Tag), desto
// mehr und hellere Punkte. Eine Overpass-Abfrage, persistent gecacht.
// Daten (c) OpenStreetMap-Mitwirkende, ODbL.
// ---- 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: 84, // 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.6, // share of aircraft with a persistent trail
contrailStartKm: 6, // trail formation altitude
contrailLifeS: 300, // trail visibility duration
contrailWidthM: 310 // full trail width
},
terrain: {
enabled: true,
exaggeration: 10,
wireframe: false,
exaggeration: 8,
wireframe: true,
nightLights: false,
carLights: true,
cityLights: true
@@ -155,12 +184,17 @@ window.CONFIG = {
// Tagessimulation: true = 24h-Zyklus wird in cycleSeconds durchlaufen
// (Sonne steigt im Osten, Mittag im Sueden, Untergang im Westen).
// false = echte Sonnen-/Mondposition via SunCalc.
// fixedTime: 'HH:MM' (oder Zahl 0-24) = Sim-Uhr steht still auf
// dieser Tageszeit (bewegte Objekte, Wolken, Autos und Wetter-Sim
// laufen unaffected weiter); null/auskommentiert = Uhr laeuft.
// 20:00 = maximum Verkehrsaufkommen (Plateau 18:30-23:00, Dichte=1).
day: {
simulation: false,
cycleSeconds: 20
cycleSeconds: 20,
fixedTime: null
},
// Wolkendecke in Welt-Einheiten (1 unit ~ 13 km bei Zoom 11;
// Kamera fliegt auf y=2.2). Decke etwas unter Flughohe.
// Wolkendecke in Welt-Einheiten (1 unit ~ 12.9 km bei Zoom 11;
// Flugband 15-35 km = y ~ 1.16-2.70). Oberkante unter dem Tiefpunkt.
clouds: {
top: 0.9,
bottom: -0.3
+5
View File
@@ -8,6 +8,11 @@ stars.json - star catalogue derived from the Yale Bright Star Catalogue
(5th ed., public catalogue data). Generated for this project; see the
real-sky notes in clouds3d.js.
plane3d.js - low-poly airliner mesh (599 triangles, flattened, no textures)
Derived from 'Passenger Airplane' by Grab3D (https://grab3d.com), CC0 1.0
(public domain dedication). Decimated and repacked for this project; the
derived mesh is offered under CC0 as well.
clouds3d.js - original code for this project (same license as the
repository root LICENSE).
+852 -102
View File
File diff suppressed because it is too large. Load diff
+4
View File
@@ -0,0 +1,4 @@
/* Airliner low-poly mesh (599 tris). Derived from 'Passenger Airplane' by Grab3D,
* CC0 (https://grab3d.com) - decimated and flattened for distant viewing.
* Non-indexed interleaved: pos(3) nrm(3) colIdx(1); unit extent 1.0. */
window.__planeVerts = '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 Line truncated