lighting: remove the palette terrain model - physics is the only lighting, themes tint it through THEMES[theme].lighting; debug panel shows the active palette and its tint/gain; credits no longer claim VIIRS
This commit is contained in:
1 parent
f26ac6c0ce
commit
4369afd3ef
3 files changed
+379
-452
No files matched your search
+18
-74
@@ -9,9 +9,10 @@
|
|||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var theme = (window.CONFIG && window.CONFIG.theme) || 'ash';
|
var theme = (window.CONFIG && window.CONFIG.theme) || 'ash';
|
||||||
var cssFile = window.THEMES && window.THEMES[theme];
|
var entry = window.THEMES && window.THEMES[theme];
|
||||||
|
var cssFile = (entry && entry.css) || (window.THEMES && window.THEMES.ash && window.THEMES.ash.css) || 'ash.css';
|
||||||
var css = document.getElementById('theme-css');
|
var css = document.getElementById('theme-css');
|
||||||
if (css) css.href = cssFile || (window.THEMES && window.THEMES.ash) || 'ash.css';
|
if (css) css.href = cssFile;
|
||||||
// standalone build (build.py): themes are embedded in
|
// standalone build (build.py): themes are embedded in
|
||||||
// window.__inlineThemeCss and the external link is disabled
|
// window.__inlineThemeCss and the external link is disabled
|
||||||
if (window.__inlineThemeCss && window.__inlineThemeCss[theme]) {
|
if (window.__inlineThemeCss && window.__inlineThemeCss[theme]) {
|
||||||
@@ -928,13 +929,6 @@ window.sunPalettes = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustColor(color, factor) {
|
|
||||||
var r = Math.min(255, Math.floor(((color >> 16) & 255) * factor));
|
|
||||||
var g = Math.min(255, Math.floor(((color >> 8) & 255) * factor));
|
|
||||||
var b = Math.min(255, Math.floor((color & 255) * factor));
|
|
||||||
return (r << 16) | (g << 8) | b;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shiftColor(color, delta) {
|
function shiftColor(color, delta) {
|
||||||
var r = Math.max(0, Math.min(255, ((color >> 16) & 255) + delta));
|
var r = Math.max(0, Math.min(255, ((color >> 16) & 255) + delta));
|
||||||
var g = Math.max(0, Math.min(255, ((color >> 8) & 255) + delta));
|
var g = Math.max(0, Math.min(255, ((color >> 8) & 255) + delta));
|
||||||
@@ -942,50 +936,8 @@ window.sunPalettes = [
|
|||||||
return (r << 16) | (g << 8) | b;
|
return (r << 16) | (g << 8) | b;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWeatherModifiers(code) {
|
|
||||||
// Returns {cloudDark: factor, cloudLight: factor, skyShift: shift, bgShift: shift}
|
|
||||||
// code is WMO weather code from open-meteo
|
|
||||||
if (code === 0) {
|
|
||||||
// Clear sky - lighter clouds, brighter sky
|
|
||||||
return { cloudDark: 0.7, cloudLight: 1.3, skyShift: 15, bgShift: 15 };
|
|
||||||
} else if (code >= 1 && code <= 3) {
|
|
||||||
// Mainly clear to overcast - neutral
|
|
||||||
return { cloudDark: 0.95, cloudLight: 1.0, skyShift: 0, bgShift: -5 };
|
|
||||||
} else if (code === 45 || code === 48) {
|
|
||||||
// Fog - cool, washed out
|
|
||||||
return { cloudDark: 1.1, cloudLight: 0.85, skyShift: 10, bgShift: 10 };
|
|
||||||
} else if ((code >= 51 && code <= 57) || (code >= 61 && code <= 67)) {
|
|
||||||
// Drizzle / Rain - darker, slightly cooler
|
|
||||||
return { cloudDark: 1.3, cloudLight: 0.7, skyShift: -15, bgShift: -20 };
|
|
||||||
} else if ((code >= 71 && code <= 77) || (code >= 85 && code <= 86)) {
|
|
||||||
// Snow - brighter (light reflection)
|
|
||||||
return { cloudDark: 0.7, cloudLight: 1.5, skyShift: 25, bgShift: 25 };
|
|
||||||
} else if (code >= 80 && code <= 82) {
|
|
||||||
// Rain showers - darker
|
|
||||||
return { cloudDark: 1.4, cloudLight: 0.6, skyShift: -20, bgShift: -25 };
|
|
||||||
} else if (code >= 95) {
|
|
||||||
// Thunderstorm - very dark, dramatic
|
|
||||||
return { cloudDark: 1.6, cloudLight: 0.4, skyShift: -30, bgShift: -35 };
|
|
||||||
}
|
|
||||||
return { cloudDark: 1.0, cloudLight: 1.0, skyShift: 0, bgShift: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentWeatherCode = -1;
|
var currentWeatherCode = -1;
|
||||||
|
|
||||||
function applyWeatherModifiers(c, code) {
|
|
||||||
var m = getWeatherModifiers(code);
|
|
||||||
return {
|
|
||||||
sky: c.sky,
|
|
||||||
cloud: adjustColor(c.cloud, m.cloudLight),
|
|
||||||
cloudShadow: adjustColor(c.cloudShadow, m.cloudDark),
|
|
||||||
sun: adjustColor(c.sun, m.cloudLight),
|
|
||||||
sunGlare: adjustColor(c.sunGlare, m.cloudLight),
|
|
||||||
sunLight: adjustColor(c.sunLight, m.cloudLight),
|
|
||||||
bg: shiftColor(c.bg, m.bgShift),
|
|
||||||
sunVis: c.sunVis
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============ Clock & Sun/Moon Calculations ============
|
// ============ Clock & Sun/Moon Calculations ============
|
||||||
|
|
||||||
var hourHand = document.getElementById('hour-hand');
|
var hourHand = document.getElementById('hour-hand');
|
||||||
@@ -1038,10 +990,6 @@ window.sunPalettes = [
|
|||||||
return (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m;
|
return (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTimeHMS(h, m, s) {
|
|
||||||
return (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pad2(n) { return n < 10 ? '0' + n : '' + n; }
|
function pad2(n) { return n < 10 ? '0' + n : '' + n; }
|
||||||
function pad3(n) { return n < 10 ? '00' + n : n < 100 ? '0' + n : '' + n; }
|
function pad3(n) { return n < 10 ? '00' + n : n < 100 ? '0' + n : '' + n; }
|
||||||
|
|
||||||
@@ -1145,12 +1093,6 @@ window.sunPalettes = [
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDateShort(date) {
|
|
||||||
var d = date.getDate();
|
|
||||||
var mo = date.getMonth() + 1;
|
|
||||||
return (d < 10 ? '0' : '') + d + '.' + (mo < 10 ? '0' : '') + mo + '.';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDateLong(date) {
|
function formatDateLong(date) {
|
||||||
var days = ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'];
|
var days = ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'];
|
||||||
var d = date.getDate();
|
var d = date.getDate();
|
||||||
@@ -1679,11 +1621,11 @@ window.weatherThemes = {
|
|||||||
var t = cfg.terrain || {};
|
var t = cfg.terrain || {};
|
||||||
if (t.enabled !== false) {
|
if (t.enabled !== false) {
|
||||||
lines.push('Terrain: Copernicus DEM via Mapzen Terrarium');
|
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)');
|
if (t.carLights !== false || t.cityLights !== false) lines.push('Roads: \u00a9 OpenStreetMap contributors (ODbL)');
|
||||||
|
}
|
||||||
var fc = cfg.flights;
|
var fc = cfg.flights;
|
||||||
if (!fc || fc.enabled !== false) lines.push('Airliner model: Grab3D (CC0)');
|
if (!fc || fc.enabled !== false) lines.push('Airliner model: Grab3D (CC0)');
|
||||||
}
|
lines.push('Stars: Yale Bright Star Catalogue');
|
||||||
lines.push('Weather: Open-Meteo');
|
lines.push('Weather: Open-Meteo');
|
||||||
el.innerHTML = lines.join('<br>');
|
el.innerHTML = lines.join('<br>');
|
||||||
})();
|
})();
|
||||||
@@ -1713,12 +1655,6 @@ window.weatherThemes = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentDayProgress() {
|
|
||||||
var now = new Date();
|
|
||||||
var startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
|
|
||||||
return (now.getTime() - startOfDay.getTime()) / 86400000;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleDebugPanel() {
|
function toggleDebugPanel() {
|
||||||
var content = document.getElementById('debug-content');
|
var content = document.getElementById('debug-content');
|
||||||
var title = document.querySelector('.debug-title');
|
var title = document.querySelector('.debug-title');
|
||||||
@@ -1814,7 +1750,8 @@ window.weatherThemes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setDebugTheme(theme) {
|
function setDebugTheme(theme) {
|
||||||
var cssFile = window.THEMES && window.THEMES[theme];
|
var entry = window.THEMES && window.THEMES[theme];
|
||||||
|
var cssFile = (entry && entry.css) || 'ash.css';
|
||||||
var css = document.getElementById('theme-css');
|
var css = document.getElementById('theme-css');
|
||||||
if (window.__inlineThemeCss && window.__inlineThemeCss[theme]) {
|
if (window.__inlineThemeCss && window.__inlineThemeCss[theme]) {
|
||||||
if (css) css.media = 'not all';
|
if (css) css.media = 'not all';
|
||||||
@@ -1939,7 +1876,17 @@ window.weatherThemes = {
|
|||||||
'orange', 'yellow', 'red', 'brown', 'pink', 'green', 'blue', 'blue-light', 'purple'
|
'orange', 'yellow', 'red', 'brown', 'pink', 'green', 'blue', 'blue-light', 'purple'
|
||||||
];
|
];
|
||||||
var dbgRoot = getComputedStyle(document.documentElement);
|
var dbgRoot = getComputedStyle(document.documentElement);
|
||||||
var colorHtml = '<div class="theme-table-header">Palette</div>';
|
// Header names the active theme and its lighting block, so the
|
||||||
|
// palette table also shows how the theme tints the physical model.
|
||||||
|
var __thName = (window.CONFIG && window.CONFIG.theme) || 'ash';
|
||||||
|
var __th = (window.THEMES && window.THEMES[__thName]) || {};
|
||||||
|
var __lt = __th.lighting || null;
|
||||||
|
var colorHtml = '<div class="theme-table-header">Palette (' + __thName + ')</div>';
|
||||||
|
colorHtml += '<div class="palette-table-row">' +
|
||||||
|
'<span class="theme-table-name">' +
|
||||||
|
(__lt ? ('lighting tint ' + __lt.tint + ' · gain ' + __lt.gain +
|
||||||
|
'/' + __lt.nightGain) : 'lighting: physical (no tint)') +
|
||||||
|
'</span></div>';
|
||||||
for (var k = 0; k < colorKeys.length; k++) {
|
for (var k = 0; k < colorKeys.length; k++) {
|
||||||
var ck = colorKeys[k];
|
var ck = colorKeys[k];
|
||||||
var cv = ASH[ck] || dbgRoot.getPropertyValue('--' + ck).trim();
|
var cv = ASH[ck] || dbgRoot.getPropertyValue('--' + ck).trim();
|
||||||
@@ -2205,9 +2152,6 @@ window.weatherThemes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get timezone offset based on user's local time
|
// Get timezone offset based on user's local time
|
||||||
function getTimezoneOffsetMinutes() {
|
|
||||||
return new Date().getTimezoneOffset();
|
|
||||||
}
|
|
||||||
var weatherData = null;
|
var weatherData = null;
|
||||||
|
|
||||||
var WMO_ICONS = {
|
var WMO_ICONS = {
|
||||||
|
|||||||
@@ -1,347 +1,376 @@
|
|||||||
/*
|
/*
|
||||||
* Wallpaper background config.
|
* animated_wallpaper - configuration
|
||||||
*
|
*
|
||||||
* Properties:
|
* Everything the scene reads at runtime lives here. Values are plain
|
||||||
* theme - Active palette theme. One of the keys in window.THEMES
|
* JavaScript so the file can also be edited while the page is open
|
||||||
* (default 'ash'). The matching CSS file is loaded at
|
* (window.CONFIG is read every frame for most knobs).
|
||||||
* 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):
|
* TOP LEVEL
|
||||||
* YYYY 4-digit year (2026)
|
* theme active palette, a key of window.THEMES (default 'ash')
|
||||||
* MMMM full month name (October)
|
* language UI language code for i18n.js ('en', 'de', ...)
|
||||||
* MM month with leading zero (10)
|
* timeFormat clock format string, token list at the bottom
|
||||||
* DD day with leading zero (07)
|
* debug 1 = show the debug panel (bottom left: weather, shader
|
||||||
* HH 24-hour hour with zero (18)
|
* counters, theme palette). 0 (default) = hidden
|
||||||
* mm minute with leading zero (14)
|
* credits true = attribution line at the bottom of the info box
|
||||||
* ss second with leading zero (25)
|
* units { temperature: 'C' | 'F' }
|
||||||
* a am/pm lowercase (pm)
|
* location { lat, lon, label? } - sun/moon geometry, traffic side,
|
||||||
* A AM/PM uppercase (PM)
|
* tile/DEM/Overpass origin, figure-eight centre
|
||||||
* dddd full weekday name (lowercase!)
|
* renderScale internal 3D resolution (0.2..1) with blocky upscale
|
||||||
* Any other character is emitted verbatim.
|
* maxFps scene frame cap (0 = free running)
|
||||||
|
* weatherSim ignore Open-Meteo and cycle the weather themes
|
||||||
|
* tiles basemap/elevation cache behaviour + debug overlays
|
||||||
|
* ground basemap style
|
||||||
|
* orbit flight path (figure eight or straight line)
|
||||||
|
* fog distance haze
|
||||||
|
* clouds cloud deck height + per-weather coverage
|
||||||
|
* 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
|
||||||
|
* 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
|
||||||
|
* 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)
|
||||||
|
* YYYY 4-digit year MMMM full month name MM month 01-12
|
||||||
|
* DD day 01-31 HH hour 00-23 mm minute
|
||||||
|
* ss second a am/pm A AM/PM
|
||||||
|
* dddd full weekday name DDD short weekday name
|
||||||
|
* any other character is copied verbatim
|
||||||
*/
|
*/
|
||||||
window.CONFIG = {
|
window.CONFIG = {
|
||||||
theme: 'ash',
|
|
||||||
language: 'en',
|
// ------------------------------------------------------------------
|
||||||
|
// Identity and UI
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
theme: 'ash', // 'ash' | 'nord' | 'gruvbox' (see window.THEMES)
|
||||||
|
language: 'en', // 'en', 'de' - keys of the i18n.js dictionaries
|
||||||
timeFormat: 'DD. MMMM, YYYY ‒ HH:mm:SS',
|
timeFormat: 'DD. MMMM, YYYY ‒ HH:mm:SS',
|
||||||
debug: 0,
|
debug: 0, // 1 = debug panel with the theme palette
|
||||||
units: {
|
credits: true, // false = hide the attribution line
|
||||||
temperature: 'C'
|
units: { temperature: 'C' }, // 'F' for Fahrenheit
|
||||||
},
|
|
||||||
// Data attribution at the bottom of the info box (automatic text
|
// ------------------------------------------------------------------
|
||||||
// based on the enabled layers). false = hidden.
|
// Rendering budget
|
||||||
credits: true,
|
// ------------------------------------------------------------------
|
||||||
// Internal 3D resolution scale (0.3..1, default 1). Lower values
|
// Internal 3D resolution. The value is snapped to an exact integer
|
||||||
// render the clouds/map shader at reduced resolution and upscale
|
// divisor of the CSS size (0.5 -> 2x blocks, 0.33 -> 3x, 0.25 -> 4x)
|
||||||
// via CSS - use it if fps is low (GPU-bound) despite a quiet CPU.
|
// and upscaled with image-rendering: pixelated, so it reads as pixel
|
||||||
// 1 = native. 0.5 = a quarter of the pixels (half width, half height),
|
// art instead of a blurry rescale. The DOM UI stays crisp at any value.
|
||||||
// upscaled 2x as hard blocks; the DOM UI on top stays crisp.
|
// 1 = native resolution (sharpest, most expensive).
|
||||||
renderScale: 0.5,
|
renderScale: 0.5,
|
||||||
// WebGL scene frame cap (default 30; 0 = free-running/vsync). All
|
// Scene frame cap. All motion is time-based, so this only changes
|
||||||
// scene motion is time-based, so the cap only changes smoothness.
|
// smoothness, never speed. 0 = free running (vsync).
|
||||||
maxFps: 30,
|
maxFps: 30,
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Location
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Drives sun/moon positions, the traffic side lookup, the tile/DEM
|
||||||
|
// origin, the Overpass grid and the figure-eight centre.
|
||||||
location: {
|
location: {
|
||||||
// Latitude and longitude for sun/moon calculations
|
|
||||||
lat: 48.5667,
|
lat: 48.5667,
|
||||||
lon: 13.4319
|
lon: 13.4319
|
||||||
|
// label: 'Passau' // overrides the reverse-geocoded name
|
||||||
},
|
},
|
||||||
// weatherSim: true = Szenario-Wetter statt Open-Meteo (A/B-Durchlauf
|
|
||||||
// aller Wetterstufen alle weatherSimInterval ms). false = echtes
|
// ------------------------------------------------------------------
|
||||||
// Wetter von Open-Meteo.
|
// Weather
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// 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,
|
||||||
weatherSimInterval: 30000,
|
weatherSimInterval: 30000, // ms per weather step when weatherSim is true
|
||||||
// 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 and caches
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Basemap tiles, the elevation grid, Overpass cells, the star catalogue
|
||||||
|
// and the last weather payload live permanently in IndexedDB
|
||||||
|
// ('background-tiles' / store 'tiles') and are never re-fetched once
|
||||||
|
// cached: a warm cache renders fully offline.
|
||||||
tiles: {
|
tiles: {
|
||||||
expireDays: null,
|
expireDays: null, // e.g. 90 = re-fetch basemap tiles every 90 days
|
||||||
// true = draw tile borders + N/S + x/y coords into the ground
|
debug: false // true = tile borders + N/S + x/y labels baked
|
||||||
// texture (debug orientation). Off = clean map.
|
// into the ground texture (orientation debug)
|
||||||
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: {
|
ground: {
|
||||||
style: 'streets'
|
// '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'
|
||||||
},
|
},
|
||||||
// Figure-eight course around location (lat/lon); the crossing point
|
|
||||||
// sits exactly on the coordinate. null/removed = straight flight.
|
// ------------------------------------------------------------------
|
||||||
|
// Flight path
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Figure eight (Bernoulli lemniscate) whose crossing sits exactly on
|
||||||
|
// CONFIG.location. At every crossing a new bearing is picked and blended
|
||||||
|
// over one lobe. Set orbit: null for a straight drift.
|
||||||
orbit: {
|
orbit: {
|
||||||
radiusKm: 60, // lobe tip: max distance from the centre
|
radiusKm: 60, // lobe tip reach (clamped ~190 km so the
|
||||||
clockwise: true,
|
// tips plus the fog dome fit the mosaic)
|
||||||
reorient: true, // random heading at every crossing pass
|
clockwise: true, // travel direction along the eight
|
||||||
// Slow vertical bobbing inside the 15-35 km flight band:
|
reorient: true, // false keeps the bearing across crossings
|
||||||
// centre 25 km (~1.93 world units), amp = half the band
|
// Vertical bobbing inside the 15-35 km band: centre 25 km, amp is
|
||||||
// (10 km ~ 0.77); hard clamp at the band limits.
|
// half the band (10 km ~ 0.77 world units), tilt is how much the
|
||||||
// periodS = seconds per cycle, tilt = camera nose reaction to
|
// camera nose follows the climb/descent rate.
|
||||||
// climb/descent rate. enabled: false = constant 25 km.
|
|
||||||
altBob: { enabled: true, amp: 0.77, periodS: 90, tilt: 0.3 }
|
altBob: { enabled: true, amp: 0.77, periodS: 90, tilt: 0.3 }
|
||||||
|
// altBob: { enabled: false } // constant 25 km, no bobbing
|
||||||
},
|
},
|
||||||
// Ground fog: distance haze. distanceKm = visibility until fully
|
|
||||||
// saturated haze (false/0 = off).
|
// ------------------------------------------------------------------
|
||||||
|
// Distance haze
|
||||||
|
// ------------------------------------------------------------------
|
||||||
fog: {
|
fog: {
|
||||||
distanceKm: 120
|
distanceKm: 120 // fully saturated haze at this range; 0/false = off
|
||||||
},
|
},
|
||||||
// ---- 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
|
// Cloud deck
|
||||||
// via wHub/wField/wStrip). Paths are gentle Bezier curves resampled to
|
// ------------------------------------------------------------------
|
||||||
// arc length (constant ground speed) flown as a full profile: take-off
|
// World units (1 unit ~ 12.9 km). Keep 'top' below the lowest flight
|
||||||
// and climb, cruise, descent and landing, a short ground hold, then a
|
// altitude so aircraft fly above the deck.
|
||||||
// fresh destination. By day sun-lit silhouettes; by night white
|
clouds: {
|
||||||
// strobing position lights with a red beacon. Contrails form above
|
top: 0.9, // ~11.6 km
|
||||||
// contrailStartKm on contrailChance of the aircraft and stay visible
|
bottom: -0.3, // ~-3.9 km (deck base below the terrain)
|
||||||
// for contrailLifeS. All distances in km (world scale: 1 unit = one
|
// Coverage per weather level, 0 = clear, 1 = overcast. Only the
|
||||||
// z11 tile ~ 12.9 km at 48.6 deg N).
|
// levels listed here override the built-in defaults
|
||||||
|
// (sunny 0.20, cloudy 0.70, stormy 0.80, night 0.45).
|
||||||
|
density: { stormy: 0.80 }
|
||||||
|
// density: { sunny: 0.05, cloudy: 0.9, stormy: 1.0, night: 0.2 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Civil aviation
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Low-poly jets (CC0 model in vendor/plane3d.js) with a destination
|
||||||
|
// rolled at every take-off (hubs weighted by wHub/wField/wStrip).
|
||||||
|
// Paths are Bezier curves resampled to arc length, flown as a full
|
||||||
|
// profile: climb, cruise, descent, landing, ground hold, new leg.
|
||||||
flights: {
|
flights: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
altitudeKm: 14, // cruise altitude
|
altitudeKm: 14, // cruise altitude
|
||||||
climbKm: 100, // climb/descent run to/from cruise altitude
|
climbKm: 100, // climb/descent run to and from cruise
|
||||||
count: 21, // max simultaneous aircraft
|
count: 21, // simultaneous aircraft (max 240)
|
||||||
speedKmh: 850, // mean ground speed
|
speedKmh: 850, // mean ground speed
|
||||||
speedVar: 0.18, // +/- deviation (0..0.5)
|
speedVar: 0.18, // +/- speed deviation (0..0.5)
|
||||||
minLegKm: 60, // shortest leg (no taxi hops)
|
minLegKm: 60, // shortest leg (no taxi hops)
|
||||||
holdMinS: 50, // ground hold at destination (min..max)
|
holdMinS: 50, // ground hold at the destination
|
||||||
holdMaxS: 150,
|
holdMaxS: 150,
|
||||||
wHub: 4, // destination weights: hub / airfield / strip
|
wHub: 4, // destination weights
|
||||||
wField: 2,
|
wField: 2,
|
||||||
wStrip: 1,
|
wStrip: 1,
|
||||||
contrailChance: 0.85, // share of aircraft with a persistent trail
|
// Contrails: contrailChance of the aircraft leaves a trail once
|
||||||
contrailStartKm: 6, // trail formation altitude
|
// above contrailStartKm, visible for contrailLifeS seconds.
|
||||||
contrailLifeS: 300, // trail visibility duration
|
contrailChance: 0.85,
|
||||||
|
contrailStartKm: 6,
|
||||||
|
contrailLifeS: 300,
|
||||||
contrailWidthM: 620, // full trail width
|
contrailWidthM: 620, // full trail width
|
||||||
// trails fade out completely by this distance (start is 35% of it)
|
contrailFadeKm: 100, // fully faded at this distance (ramp starts at 35 %)
|
||||||
contrailFadeKm: 100,
|
// Night strobes. Point size shrinks with distance but never below
|
||||||
// Night strobes. Size shrinks with distance but never below
|
// lightPx and never above lightMaxPx for a jet directly overhead.
|
||||||
// lightPx, and never grows past lightMaxPx for a jet right above.
|
// The white strobe is a double flash: two flashes blinkFlashS long,
|
||||||
// The white strobe is a double flash: two equal flashes
|
// blinkGapS apart, then the rest of blinkPeriodS is dark.
|
||||||
// blinkGapS apart, then blinkPeriodS - blinkGapS - blinkFlashS
|
|
||||||
// of darkness (1.4 - 0.16 - 0.08 = 1.16 s pause).
|
|
||||||
lightPx: 7,
|
lightPx: 7,
|
||||||
lightMaxPx: 16,
|
lightMaxPx: 16,
|
||||||
lightBoost: 9,
|
lightBoost: 9,
|
||||||
blinkPeriodS: 1.4,
|
blinkPeriodS: 1.4,
|
||||||
blinkGapS: 0.16,
|
blinkGapS: 0.16,
|
||||||
blinkFlashS: 0.08
|
blinkFlashS: 0.08
|
||||||
|
// flights: false // remove the aviation layer completely
|
||||||
},
|
},
|
||||||
// 3D ground from real elevation data (Copernicus DEM, terrarium
|
|
||||||
// tiles). enabled: false = flat plane. exaggeration: vertical
|
// ------------------------------------------------------------------
|
||||||
// elevation scale (1 = real, nearly invisible; default 8).
|
// 3D ground
|
||||||
// wireframe: true = overlay mesh edges as orange lines (debug).
|
// ------------------------------------------------------------------
|
||||||
// carLights: moving lights on the real road network (OSM/Overpass
|
// Copernicus DEM via terrarium tiles (z10), mesh built from the same
|
||||||
// curves, additive over the terrain at night). JSON once, cached.
|
// height field the terrain shadows are cast against.
|
||||||
// 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: {
|
terrain: {
|
||||||
// soft rim at the mosaic border, in tiles (1 tile ~ 13 km)
|
enabled: true, // false = flat plane (no DEM, no terrain shadows)
|
||||||
edgeFadeTiles: 4,
|
exaggeration: 8, // vertical scale (1 = real, nearly invisible here)
|
||||||
enabled: true,
|
edgeFadeTiles: 4, // soft rim at the mosaic border, in tiles (~13 km)
|
||||||
exaggeration: 8,
|
carLights: true, // moving vehicle lights on the real road network
|
||||||
wireframe: false,
|
cityLights: true, // settlement lights from OSM place nodes
|
||||||
// How far the day map is dimmed at full night: 0 = pitch black,
|
wireframe: false // true = draw the mesh edges (orientation debug)
|
||||||
// 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
|
// Vehicle lights
|
||||||
// dots' gl_PointSize floor. laneM is the real distance between the two
|
// ------------------------------------------------------------------
|
||||||
// traffic directions, so they split close to the camera and merge into
|
// Roads come from Overpass (motorway/trunk for the A-net, primary/
|
||||||
// one line far away instead of drifting apart with the camera distance.
|
// secondary for the side layer), cached per grid cell.
|
||||||
// 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: {
|
carLights: {
|
||||||
roadWidthM: 24, // real carriageway width of the road line
|
roadWidthM: 24, // real carriageway width of the road line
|
||||||
bandPx: 1.2, // pixel floor for the road line
|
bandPx: 1.2, // pixel floor of the road line
|
||||||
bandBrightness: 0.10, // road line glow (points are the main layer)
|
bandBrightness: 0.10, // road line glow (the points are the main layer)
|
||||||
minPx: 3.6, // guaranteed vehicle light diameter
|
minPx: 3.6, // guaranteed vehicle light diameter
|
||||||
maxPx: 10, // ceiling for a light right above the camera
|
maxPx: 10, // ceiling for a light right above the camera
|
||||||
boost: 5, // extra light size close to the camera
|
boost: 5, // extra light size close to the camera
|
||||||
laneM: 8, // real distance between the two directions
|
laneM: 8, // real distance between the two directions
|
||||||
lanePx: 2.6, // screen floor for that distance, so the two
|
lanePx: 2.6, // screen floor for that distance, so the two
|
||||||
// directions never collapse into one stream
|
// streams never collapse into one line
|
||||||
// Traffic lives on a disc around the configured location: roads
|
// Traffic lives on a disc around CONFIG.location. Roads outside
|
||||||
// outside rangeKm carry no vehicles. fadeKm / fadeSpanKm are the
|
// rangeKm carry no vehicles; fadeKm/fadeSpanKm are the matching
|
||||||
// distance fade belonging to that disc (start, and how far past it
|
// distance fade (start, and how far past it nothing is left) - keep
|
||||||
// until nothing is left) - keep them at or beyond rangeKm so the
|
// them at or beyond rangeKm so the disc never ends in a hard cut.
|
||||||
// region never ends in a hard cut.
|
|
||||||
rangeKm: 200,
|
rangeKm: 200,
|
||||||
fadeKm: 150,
|
fadeKm: 150,
|
||||||
fadeSpanKm: 60,
|
fadeSpanKm: 60,
|
||||||
// 'auto' = side of the road taken from location (lat/lon): left in
|
// 'auto' derives the driving side from the location (left in the UK,
|
||||||
// the UK, Japan, Australia, India, Southern Africa, ... and right
|
// Japan, Australia, India, southern Africa, ...), 'left'/'right' forces it.
|
||||||
// everywhere else. Set 'left' or 'right' to force it.
|
|
||||||
trafficSide: 'auto',
|
trafficSide: 'auto',
|
||||||
dashM: 620, // mean vehicle spacing on motorways
|
dashM: 620, // mean vehicle spacing on motorways
|
||||||
// Traffic spread around the motorway network (gaussian): peak on the
|
// (side roads use dashM * 2.8)
|
||||||
// motorway itself, thinning with distance to the nearest one.
|
spreadKm: 45, // gaussian sigma around the motorway network
|
||||||
// spreadKm = sigma, spreadFloor = share that far side roads keep.
|
spreadFloor: 0.30, // share that far side roads keep
|
||||||
spreadKm: 45,
|
|
||||||
spreadFloor: 0.30,
|
|
||||||
speed: 1.0, // flow speed multiplier
|
speed: 1.0, // flow speed multiplier
|
||||||
density: 1.0, // vehicle density multiplier
|
density: 1.0, // vehicle density multiplier
|
||||||
brightness: 1.3 // vehicle light intensity
|
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.
|
// Artificial light switching
|
||||||
// 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
|
// Every single light (vehicle, city dot, ground block) gets its own
|
||||||
// off again as the sun climbs.
|
// switch altitude inside this window, so a city does not ignite as one
|
||||||
|
// 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: {
|
lights: {
|
||||||
offDeg: 5,
|
offDeg: 5,
|
||||||
onDeg: -6,
|
onDeg: -6,
|
||||||
softDeg: 1.2
|
softDeg: 1.2 // width of the individual transition
|
||||||
},
|
},
|
||||||
// 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
|
// Physical light model
|
||||||
// old hand-painted hillshade. turbidity scales the aerosol load,
|
// ------------------------------------------------------------------
|
||||||
// moonGain the moonlight strength, nightFloor the darkest skylight
|
// Direct beam through a Kasten-Young air mass with Rayleigh + ozone +
|
||||||
// (airglow + starlight), albedoTint is the theme's share on surfaces.
|
// aerosol extinction; diffuse skylight, moonlight, starlight floor and
|
||||||
|
// aerial perspective all come from the same model, and sky, clouds,
|
||||||
|
// terrain, contrails and aircraft share one light state per frame.
|
||||||
atmosphere: {
|
atmosphere: {
|
||||||
terrainLighting: 'physical',
|
turbidity: 1.0, // aerosol load of the extinction
|
||||||
turbidity: 1.0,
|
moonGain: 0.60, // moonlight, scaled by the illuminated fraction
|
||||||
// moonlight strength; scales the direct term AND the diffuse sky
|
starGain: 0.18, // stylised starlight + airglow (real integrated
|
||||||
// lift, both with the illuminated fraction (new moon -> none)
|
// starlight would be invisible)
|
||||||
moonGain: 0.60,
|
starGainGround: 0.4, // extra share on the ground; the product must
|
||||||
// stylised starlight + airglow floor on a dark night (real
|
// stay below the skylight at sunset
|
||||||
// integrated starlight is ~1e-6 of daylight and would be invisible)
|
nightFloor: 0.10, // darkest skylight before starlight takes over
|
||||||
starGain: 0.18,
|
albedoGain: 1.0, // theme share on surface colours
|
||||||
// extra share on the ground. The product is the starlit floor and
|
skyGain: 0.45, // diffuse skylight share on surfaces
|
||||||
// must stay below the skylight at sunset, otherwise the night would
|
airGain: 0.55, // Rayleigh airlight strength (blue-biased)
|
||||||
// be brighter than the moment of sunset. Moonlight (moonGain) is
|
hazeResidual: 0.30, // surface colour surviving at the full fog distance
|
||||||
// what lifts a night above that floor.
|
cloudBrightness: 1.0, // cloud albedo (theme hue, brightness from the model)
|
||||||
starGainGround: 0.4,
|
cloudHaze: 0.22, // share of the ground haze applied to the deck
|
||||||
nightFloor: 0.10,
|
// Cloud shadows: the terrain samples the same cloud field as the sky pass.
|
||||||
// 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,
|
cloudShadows: true,
|
||||||
cloudShadowDensity: 1.8, // optical depth through the deck
|
cloudShadowDensity: 1.8, // optical depth through the deck
|
||||||
cloudShadowFloor: 0.06, // darkest shade under a thick cloud
|
cloudShadowFloor: 0.06, // darkest shade under a thick cloud
|
||||||
cloudShadowAmbient: 0.65, // share of the skylight a cloud also blocks
|
cloudShadowAmbient: 0.65, // share of the skylight a deck also blocks
|
||||||
// ridges occlude the sun for the ground behind them (march over the
|
// Terrain self-shadowing over the DEM height field.
|
||||||
// same height field the mesh was built from)
|
|
||||||
terrainShadows: true,
|
terrainShadows: true,
|
||||||
// brightness a fully shadowed slope keeps (0 = pitch black, 1 = off)
|
terrainShadowFloor: 0.42, // brightness a fully shadowed slope keeps (0 = black)
|
||||||
terrainShadowFloor: 0.42,
|
// Optional variants (leave commented out to use the defaults):
|
||||||
// how much of the ground haze applies to the cloud deck (clouds sit
|
// turbidity: 3.0 // hazy, warm, strongly reddened low sun
|
||||||
// above most of the air that causes it)
|
// moonGain: 1.2 // bright full-moon nights
|
||||||
cloudHaze: 0.22,
|
// starGain: 0.30 // stronger starcast on clouds and contrails
|
||||||
albedoGain: 1.0,
|
// hazeResidual: 0.10 // flatter, milkier distance haze
|
||||||
// share of the daylight column that the distance fog / horizon takes,
|
// cloudShadows: false // no moving cloud shade on the ground
|
||||||
// and the diffuse skylight share on surfaces
|
// terrainShadows: false // no ridge shadows
|
||||||
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.
|
// ------------------------------------------------------------------
|
||||||
|
// Star catalogue
|
||||||
|
// ------------------------------------------------------------------
|
||||||
stars: {
|
stars: {
|
||||||
tint: 0.10, // 90 % white + 10 % of the star's real colour
|
tint: 0.10, // 0 = pure white points, 1 = full blackbody colour
|
||||||
brightness: 1.6 // overall glow of the catalogue points
|
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.
|
// Day cycle
|
||||||
// fixedTime: 'HH:MM' (or a number 0-24) freezes the sim clock at
|
// ------------------------------------------------------------------
|
||||||
// that time of day (moving objects, clouds, cars and the weather
|
// simulation: true runs a full 24 h cycle in cycleSeconds (sun rises
|
||||||
// sim keep running unaffected); null/omitted = the clock advances.
|
// east, culminates south, sets west) and the clock plus the sun/moon
|
||||||
|
// tables follow the simulated date. false uses the real SunCalc positions.
|
||||||
day: {
|
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,
|
simulation: true,
|
||||||
cycleSeconds: 60,
|
cycleSeconds: 60, // 60 s = one simulated day per minute
|
||||||
fixedTime: null
|
fixedTime: null // 'HH:MM' or 0-24 freezes the sim clock
|
||||||
},
|
// (clouds, traffic and flights keep moving)
|
||||||
// Cloud deck in world units (1 unit ~ 12.9 km at zoom 11; flight
|
// day: { simulation: false } // real sun and moon
|
||||||
// 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 }
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Debug helpers (off by default, documented so they are findable)
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// debug: 1 // show the debug panel (weather, shader
|
||||||
|
// counters, theme palette, theme lighting)
|
||||||
|
// tiles: { debug: true } // bake tile borders and x/y labels into the map
|
||||||
|
// terrain: { wireframe: true } // draw the mesh edges
|
||||||
|
// ground: { style: 'streets' } // labelled street map instead of satellite
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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
|
* Themes.
|
||||||
// and dropping the corresponding CSS file in the same directory.
|
*
|
||||||
|
* Each entry is an object:
|
||||||
|
* css stylesheet loaded for this theme (also the palette source:
|
||||||
|
* the scene reads the CSS custom properties of the active theme)
|
||||||
|
* lighting optional tint of the physical light model. The physics stays
|
||||||
|
* the base; the theme only pulls the colours toward its own hue
|
||||||
|
* by 'tint' (0 = pure physics, 1 = theme colour) and scales the
|
||||||
|
* exposure with gain (day) and nightGain (night).
|
||||||
|
* sun/moon/air/sky accept '#rgb' or '#rrggbb'.
|
||||||
|
* Omit 'lighting' entirely for a purely physical theme.
|
||||||
|
*
|
||||||
|
* Add a theme by adding an entry here and dropping the CSS file next to
|
||||||
|
* config.js - build.py inlines every stylesheet referenced here.
|
||||||
|
*/
|
||||||
window.THEMES = {
|
window.THEMES = {
|
||||||
'ash': 'ash.css',
|
'ash': {
|
||||||
'gruvbox': 'gruvbox.css',
|
css: 'ash.css',
|
||||||
'nord': 'nord.css'
|
lighting: {
|
||||||
|
tint: 0.28,
|
||||||
|
sun: '#ffd9a6', // warm amber beam
|
||||||
|
sky: '#5b6f96', // cool slate skylight
|
||||||
|
air: '#7d93bd', // haze and horizon colour
|
||||||
|
moon: '#c9d6ea',
|
||||||
|
gain: 1.00,
|
||||||
|
nightGain: 1.00
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'nord': {
|
||||||
|
css: 'nord.css',
|
||||||
|
lighting: {
|
||||||
|
tint: 0.45,
|
||||||
|
sun: '#f2e3c0',
|
||||||
|
sky: '#5e81ac',
|
||||||
|
air: '#81a1c1',
|
||||||
|
moon: '#d8e3ee',
|
||||||
|
gain: 0.95,
|
||||||
|
nightGain: 1.12 // nord nights read a little brighter
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'gruvbox': {
|
||||||
|
css: 'gruvbox.css',
|
||||||
|
lighting: {
|
||||||
|
tint: 0.50,
|
||||||
|
sun: '#fabd7f',
|
||||||
|
sky: '#83a598',
|
||||||
|
air: '#b8a27f',
|
||||||
|
moon: '#e6d5ae',
|
||||||
|
gain: 1.05,
|
||||||
|
nightGain: 1.05
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A theme without a lighting block keeps the pure physical colours:
|
||||||
|
// 'mono': { css: 'mono.css' }
|
||||||
};
|
};
|
||||||
Vendored
+61
-107
@@ -67,7 +67,6 @@ var FRAG_SRC = [
|
|||||||
'uniform float uDensity;',
|
'uniform float uDensity;',
|
||||||
'uniform float uDayProgress;',
|
'uniform float uDayProgress;',
|
||||||
'uniform float uSunAlt;',
|
'uniform float uSunAlt;',
|
||||||
'uniform float uFogDist;', // world units (=tiles) where ground fog saturates
|
|
||||||
'uniform float uCloudTop;', // cloud deck upper edge (world Y)
|
'uniform float uCloudTop;', // cloud deck upper edge (world Y)
|
||||||
'uniform float uCloudBot;', // cloud deck lower edge (world Y)
|
'uniform float uCloudBot;', // cloud deck lower edge (world Y)
|
||||||
'uniform sampler2D uElev;', // terrarium RGB height grid, same UV as uGround
|
'uniform sampler2D uElev;', // terrarium RGB height grid, same UV as uGround
|
||||||
@@ -501,9 +500,6 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
'uniform vec2 uGroundSize;',
|
'uniform vec2 uGroundSize;',
|
||||||
'uniform vec2 uCorridorRot;',
|
'uniform vec2 uCorridorRot;',
|
||||||
'uniform vec3 uSunDir;',
|
'uniform vec3 uSunDir;',
|
||||||
'uniform vec3 uLight;',
|
|
||||||
'uniform vec3 uBg;',
|
|
||||||
'uniform vec3 uSky;',
|
|
||||||
'uniform vec3 uMoonDir;', // unit vector towards the moon (world)
|
'uniform vec3 uMoonDir;', // unit vector towards the moon (world)
|
||||||
'uniform vec3 uSunColor;', // direct beam after atmospheric extinction
|
'uniform vec3 uSunColor;', // direct beam after atmospheric extinction
|
||||||
'uniform float uSunInt;', // direct beam strength (1 = zenith noon)
|
'uniform float uSunInt;', // direct beam strength (1 = zenith noon)
|
||||||
@@ -512,7 +508,6 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
'uniform float uSkyAmb;', // diffuse skylight
|
'uniform float uSkyAmb;', // diffuse skylight
|
||||||
'uniform vec3 uAlbedoTint;', // theme fine-tuning of the surface colour
|
'uniform vec3 uAlbedoTint;', // theme fine-tuning of the surface colour
|
||||||
'uniform vec3 uHorizonColor;', // fog colour taken from the same light model
|
'uniform vec3 uHorizonColor;', // fog colour taken from the same light model
|
||||||
'uniform float uPhysical;', // 1 = physical lighting, 0 = palette hillshade
|
|
||||||
'uniform float uStarAmb;', // diffuse starlight + airglow (night only)
|
'uniform float uStarAmb;', // diffuse starlight + airglow (night only)
|
||||||
'uniform float uTime;', // must match the sky pass: same cloud field
|
'uniform float uTime;', // must match the sky pass: same cloud field
|
||||||
'uniform float uCloudBot;',
|
'uniform float uCloudBot;',
|
||||||
@@ -526,17 +521,10 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
'uniform float uElevScale;', // metres -> world units (incl. exaggeration)
|
'uniform float uElevScale;', // metres -> world units (incl. exaggeration)
|
||||||
'uniform float uTerrainShadowOn;',
|
'uniform float uTerrainShadowOn;',
|
||||||
'uniform float uTerrainShadowFloor;', // brightness a fully shadowed slope keeps
|
'uniform float uTerrainShadowFloor;', // brightness a fully shadowed slope keeps
|
||||||
'uniform float uSunAlt;', // sun altitude, for the light switching
|
|
||||||
'uniform float uLightHi;', // above this altitude no house light is on
|
|
||||||
'uniform float uLightLo;', // below this altitude every house light is on
|
|
||||||
'uniform float uLightSoft;',
|
|
||||||
'uniform vec3 uAirColor;', // Rayleigh airlight (sun-tinted, blue-biased)
|
'uniform vec3 uAirColor;', // Rayleigh airlight (sun-tinted, blue-biased)
|
||||||
'uniform float uAirDensity;', // slant optical depth per world unit
|
'uniform float uAirDensity;', // slant optical depth per world unit
|
||||||
'uniform vec3 uCamPos;', // world-space camera, for the view direction
|
'uniform vec3 uCamPos;', // world-space camera, for the view direction
|
||||||
'uniform float uEdgeFadeTiles;',
|
'uniform float uEdgeFadeTiles;',
|
||||||
'uniform float uFogDist;',
|
|
||||||
'uniform float uNightFactor;',
|
|
||||||
'uniform float uNightDim;', // how far the day map is dimmed at night
|
|
||||||
'uniform vec3 uWireColor;', // non-zero -> debug wireframe overlay (flat colour)
|
'uniform vec3 uWireColor;', // non-zero -> debug wireframe overlay (flat colour)
|
||||||
'varying vec3 vNormal;',
|
'varying vec3 vNormal;',
|
||||||
'varying vec2 vUV;',
|
'varying vec2 vUV;',
|
||||||
@@ -547,7 +535,7 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
'void main() {',
|
'void main() {',
|
||||||
'vec2 gUv = vUV;',
|
'vec2 gUv = vUV;',
|
||||||
// Edge fade: beyond the canvas the texture is clamped to the
|
// Edge fade: beyond the canvas the texture is clamped to the
|
||||||
// edge pixel, so we fade toward uBg over the last 5%.
|
// edge pixel, so the rim dissolves into the airlight instead.
|
||||||
// Distance from the mosaic border in TILES, so the fade band has a
|
// Distance from the mosaic border in TILES, so the fade band has a
|
||||||
// real width (uEdgeFadeTiles) instead of a fixed 5 % slice - the
|
// real width (uEdgeFadeTiles) instead of a fixed 5 % slice - the
|
||||||
// rim dissolves into the airlight over tens of kilometres.
|
// rim dissolves into the airlight over tens of kilometres.
|
||||||
@@ -564,7 +552,6 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
// Direct beam (extinction-coloured by the JS air-mass model) +
|
// Direct beam (extinction-coloured by the JS air-mass model) +
|
||||||
// hemispherical skylight + moonlight. No hand-picked day/night
|
// hemispherical skylight + moonlight. No hand-picked day/night
|
||||||
// ramp: the sun altitude drives colour and strength continuously.
|
// ramp: the sun altitude drives colour and strength continuously.
|
||||||
'if (uPhysical > 0.5) {',
|
|
||||||
'vec3 alb = texture2D(uGround, gUv).rgb * uAlbedoTint;',
|
'vec3 alb = texture2D(uGround, gUv).rgb * uAlbedoTint;',
|
||||||
'float ndl = max(0.0, dot(n, uSunDir));',
|
'float ndl = max(0.0, dot(n, uSunDir));',
|
||||||
// Cloud shadow: march from the surface point towards the sun
|
// Cloud shadow: march from the surface point towards the sun
|
||||||
@@ -608,42 +595,10 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
' * ambShadow * mix(1.0, tShadow, 0.18);',
|
' * ambShadow * mix(1.0, tShadow, 0.18);',
|
||||||
'vec3 moon = alb * uMoonColor * max(0.0, dot(n, uMoonDir)) * uMoonInt * ambShadow;',
|
'vec3 moon = alb * uMoonColor * max(0.0, dot(n, uMoonDir)) * uMoonInt * ambShadow;',
|
||||||
'col = direct + sky + moon;',
|
'col = direct + sky + moon;',
|
||||||
'} else {',
|
|
||||||
// ---- Hillshade (palette model) ---------------------------------
|
|
||||||
// Classic shaded-relief: strong lambert term for sun-facing vs
|
|
||||||
// shadow-facing slopes, plus a sky-ambient term (upward normals
|
|
||||||
// collect more skylight), plus a subtle warm/cool slope tint.
|
|
||||||
'float lam = dot(n, uSunDir);',
|
|
||||||
'float relief = 0.62 + 0.58 * lam;',
|
|
||||||
// shadowed slopes keep a little bounce light instead of going black
|
|
||||||
'relief += 0.16 * max(0.0, n.y);',
|
|
||||||
'relief = clamp(relief, 0.28, 1.5);',
|
|
||||||
'float warm = clamp(lam, 0.0, 1.0);',
|
|
||||||
'float cool = clamp(-lam, 0.0, 1.0);',
|
|
||||||
// sun-facing slopes get a warm cast, shaded slopes a cool cast
|
|
||||||
'vec3 tint = mix(vec3(1.0), vec3(1.09, 1.03, 0.9), warm * 0.8);',
|
|
||||||
'tint = mix(tint, vec3(0.82, 0.88, 1.06), cool * 0.6);',
|
|
||||||
'col *= relief * tint;',
|
|
||||||
// night: lambert relief against a sun BELOW the horizon would
|
|
||||||
// crush the map; fade shading back toward the neutral texture
|
|
||||||
'col = mix(col, texture2D(uGround, gUv).rgb, uNightFactor * 0.7);',
|
|
||||||
// ---- Day/night cross-fade --------------------------------------
|
|
||||||
// Starts exactly at sunset and blends over ~the following hour of
|
|
||||||
// simulated time (uNightFactor: 0 at alt 0, 1 at -0.4 rad, which
|
|
||||||
// the sun covers in ~1 h at 48 deg N).
|
|
||||||
'if (uNightFactor > 0.002) {',
|
|
||||||
// uNightDim is CONFIG.terrain.nightDim - how far to dim the day
|
|
||||||
// map at full night (0 = pitch black, 1 = unchanged).
|
|
||||||
'vec3 nightBase = col * uNightDim;',
|
|
||||||
'col = mix(col, nightBase, uNightFactor);',
|
|
||||||
'}',
|
|
||||||
'}',
|
|
||||||
// ---- Distance fog ----------------------------------------------
|
// ---- Distance fog ----------------------------------------------
|
||||||
// Real view-space distance (uFogDist is in tiles, matching the
|
// Real view-space distance, not a height proxy.
|
||||||
// world-unit scale of the mesh), not a height proxy.
|
|
||||||
'float dist = length(vViewPos);',
|
'float dist = length(vViewPos);',
|
||||||
'vec3 horizonCol = (uPhysical > 0.5) ? uHorizonColor',
|
'vec3 horizonCol = uHorizonColor;',
|
||||||
' : mix(uBg, uSky, 0.5) * (1.0 - uNightFactor * 0.55);',
|
|
||||||
// ---- Aerial perspective --------------------------------------
|
// ---- Aerial perspective --------------------------------------
|
||||||
// Physical mode: Beer-Lambert along the view ray. The surface is
|
// Physical mode: Beer-Lambert along the view ray. The surface is
|
||||||
// attenuated by exp(-tau) and the light scattered in the same air
|
// attenuated by exp(-tau) and the light scattered in the same air
|
||||||
@@ -651,19 +606,12 @@ var TERRAIN_FRAG_SRC = [
|
|||||||
// veil but into the airlight itself - blue-biased (Rayleigh,
|
// veil but into the airlight itself - blue-biased (Rayleigh,
|
||||||
// 1/lambda^4) and brighter towards the sun through the (1+cos^2)/2
|
// 1/lambda^4) and brighter towards the sun through the (1+cos^2)/2
|
||||||
// phase function.
|
// phase function.
|
||||||
'if (uPhysical > 0.5) {',
|
|
||||||
'float tau = uAirDensity * dist * (0.75 + 0.25 * smoothstep(0.4, 0.0, vY));',
|
'float tau = uAirDensity * dist * (0.75 + 0.25 * smoothstep(0.4, 0.0, vY));',
|
||||||
'float trans = exp(-tau);',
|
'float trans = exp(-tau);',
|
||||||
'vec3 vd = normalize(vWorld - uCamPos);',
|
'vec3 vd = normalize(vWorld - uCamPos);',
|
||||||
'float cosT = dot(vd, uSunDir);',
|
'float cosT = dot(vd, uSunDir);',
|
||||||
'float rphase = 0.5 + 0.5 * cosT * cosT;',
|
'float rphase = 0.5 + 0.5 * cosT * cosT;',
|
||||||
'col = col * trans + uAirColor * rphase * (1.0 - trans);',
|
'col = col * trans + uAirColor * rphase * (1.0 - trans);',
|
||||||
'} else {',
|
|
||||||
'float fogAmount = clamp(dist / max(uFogDist, 0.001), 0.0, 1.0);',
|
|
||||||
'fogAmount = pow(fogAmount, 0.8);',
|
|
||||||
'fogAmount *= 0.75 + 0.25 * smoothstep(0.4, 0.0, vY);',
|
|
||||||
'col = mix(col, horizonCol, fogAmount);',
|
|
||||||
'}',
|
|
||||||
// Edge fade to the same horizon colour
|
// Edge fade to the same horizon colour
|
||||||
'col = mix(horizonCol, col, edgeFade);',
|
'col = mix(horizonCol, col, edgeFade);',
|
||||||
// Wireframe overlay: when CONFIG.terrain.wireframe is set the
|
// Wireframe overlay: when CONFIG.terrain.wireframe is set the
|
||||||
@@ -719,7 +667,6 @@ function init(canvas) {
|
|||||||
var uDensity = gl.getUniformLocation(prog, 'uDensity');
|
var uDensity = gl.getUniformLocation(prog, 'uDensity');
|
||||||
var uDayProgress = gl.getUniformLocation(prog, 'uDayProgress');
|
var uDayProgress = gl.getUniformLocation(prog, 'uDayProgress');
|
||||||
var uSunAlt = gl.getUniformLocation(prog, 'uSunAlt');
|
var uSunAlt = gl.getUniformLocation(prog, 'uSunAlt');
|
||||||
var uFogDist = gl.getUniformLocation(prog, 'uFogDist');
|
|
||||||
var uCloudTop = gl.getUniformLocation(prog, 'uCloudTop');
|
var uCloudTop = gl.getUniformLocation(prog, 'uCloudTop');
|
||||||
var uCloudBot = gl.getUniformLocation(prog, 'uCloudBot');
|
var uCloudBot = gl.getUniformLocation(prog, 'uCloudBot');
|
||||||
var uElev = gl.getUniformLocation(prog, 'uElev');
|
var uElev = gl.getUniformLocation(prog, 'uElev');
|
||||||
@@ -768,13 +715,6 @@ function init(canvas) {
|
|||||||
var terrainUproj = terrainProg ? gl.getUniformLocation(terrainProg, 'uProj') : null;
|
var terrainUproj = terrainProg ? gl.getUniformLocation(terrainProg, 'uProj') : null;
|
||||||
var terrainUground = terrainProg ? gl.getUniformLocation(terrainProg, 'uGround') : null;
|
var terrainUground = terrainProg ? gl.getUniformLocation(terrainProg, 'uGround') : null;
|
||||||
var terrainUsundir = terrainProg ? gl.getUniformLocation(terrainProg, 'uSunDir') : null;
|
var terrainUsundir = terrainProg ? gl.getUniformLocation(terrainProg, 'uSunDir') : null;
|
||||||
var terrainUlight = terrainProg ? gl.getUniformLocation(terrainProg, 'uLight') : null;
|
|
||||||
var terrainUbg = terrainProg ? gl.getUniformLocation(terrainProg, 'uBg') : null;
|
|
||||||
var terrainUsky = terrainProg ? gl.getUniformLocation(terrainProg, 'uSky') : null;
|
|
||||||
var terrainUfogdist = terrainProg ? gl.getUniformLocation(terrainProg, 'uFogDist') : null;
|
|
||||||
var terrainUnight = terrainProg ? gl.getUniformLocation(terrainProg, 'uNightFactor') : null;
|
|
||||||
|
|
||||||
var terrainUnightdim = terrainProg ? gl.getUniformLocation(terrainProg, 'uNightDim') : null;
|
|
||||||
var terrainUgroundcenter = terrainProg ? gl.getUniformLocation(terrainProg, 'uGroundCenter') : null;
|
var terrainUgroundcenter = terrainProg ? gl.getUniformLocation(terrainProg, 'uGroundCenter') : null;
|
||||||
var terrainUgroundsize = terrainProg ? gl.getUniformLocation(terrainProg, 'uGroundSize') : null;
|
var terrainUgroundsize = terrainProg ? gl.getUniformLocation(terrainProg, 'uGroundSize') : null;
|
||||||
var terrainUcorridor = terrainProg ? gl.getUniformLocation(terrainProg, 'uCorridorRot') : null;
|
var terrainUcorridor = terrainProg ? gl.getUniformLocation(terrainProg, 'uCorridorRot') : null;
|
||||||
@@ -788,11 +728,6 @@ function init(canvas) {
|
|||||||
var terrainUskyAmb = terrainProg ? gl.getUniformLocation(terrainProg, 'uSkyAmb') : null;
|
var terrainUskyAmb = terrainProg ? gl.getUniformLocation(terrainProg, 'uSkyAmb') : null;
|
||||||
var terrainUalbedoTint = terrainProg ? gl.getUniformLocation(terrainProg, 'uAlbedoTint') : null;
|
var terrainUalbedoTint = terrainProg ? gl.getUniformLocation(terrainProg, 'uAlbedoTint') : null;
|
||||||
var terrainUhorizon = terrainProg ? gl.getUniformLocation(terrainProg, 'uHorizonColor') : null;
|
var terrainUhorizon = terrainProg ? gl.getUniformLocation(terrainProg, 'uHorizonColor') : null;
|
||||||
var terrainUphysical = terrainProg ? gl.getUniformLocation(terrainProg, 'uPhysical') : null;
|
|
||||||
var terrainUsunAlt = terrainProg ? gl.getUniformLocation(terrainProg, 'uSunAlt') : null;
|
|
||||||
var terrainUlightHi = terrainProg ? gl.getUniformLocation(terrainProg, 'uLightHi') : null;
|
|
||||||
var terrainUlightLo = terrainProg ? gl.getUniformLocation(terrainProg, 'uLightLo') : null;
|
|
||||||
var terrainUlightSoft = terrainProg ? gl.getUniformLocation(terrainProg, 'uLightSoft') : null;
|
|
||||||
var terrainUairColor = terrainProg ? gl.getUniformLocation(terrainProg, 'uAirColor') : null;
|
var terrainUairColor = terrainProg ? gl.getUniformLocation(terrainProg, 'uAirColor') : null;
|
||||||
var terrainUairDensity = terrainProg ? gl.getUniformLocation(terrainProg, 'uAirDensity') : null;
|
var terrainUairDensity = terrainProg ? gl.getUniformLocation(terrainProg, 'uAirDensity') : null;
|
||||||
var terrainUcampos = terrainProg ? gl.getUniformLocation(terrainProg, 'uCamPos') : null;
|
var terrainUcampos = terrainProg ? gl.getUniformLocation(terrainProg, 'uCamPos') : null;
|
||||||
@@ -811,8 +746,8 @@ function init(canvas) {
|
|||||||
var terrainUterrainShadowFloor = terrainProg ? gl.getUniformLocation(terrainProg, 'uTerrainShadowFloor') : null;
|
var terrainUterrainShadowFloor = terrainProg ? gl.getUniformLocation(terrainProg, 'uTerrainShadowFloor') : null;
|
||||||
|
|
||||||
// 1x1 black texture: bound wherever a real layer does not exist yet
|
// 1x1 black texture: bound wherever a real layer does not exist yet
|
||||||
// (e.g. night lights before the first tile, or nightLights: false)
|
// (e.g. the basemap before the first tile, or terrain disabled) so the
|
||||||
// so the shaders always sample a valid black.
|
// shaders always sample a valid black.
|
||||||
var blackTex = gl.createTexture();
|
var blackTex = gl.createTexture();
|
||||||
gl.bindTexture(gl.TEXTURE_2D, blackTex);
|
gl.bindTexture(gl.TEXTURE_2D, blackTex);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA,
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA,
|
||||||
@@ -833,7 +768,7 @@ function init(canvas) {
|
|||||||
uSky: uSky, uCloud: uCloud,
|
uSky: uSky, uCloud: uCloud,
|
||||||
uCloudShadow: uCloudShadow, uLight: uLight, uBg: uBg,
|
uCloudShadow: uCloudShadow, uLight: uLight, uBg: uBg,
|
||||||
uSpeed: uSpeed, uDensity: uDensity, uDayProgress: uDayProgress,
|
uSpeed: uSpeed, uDensity: uDensity, uDayProgress: uDayProgress,
|
||||||
uSunAlt: uSunAlt, uFogDist: uFogDist,
|
uSunAlt: uSunAlt,
|
||||||
uCloudTop: uCloudTop, uCloudBot: uCloudBot,
|
uCloudTop: uCloudTop, uCloudBot: uCloudBot,
|
||||||
uElev: uElev, uElevOn: uElevOn, uElevScale: uElevScale,
|
uElev: uElev, uElevOn: uElevOn, uElevScale: uElevScale,
|
||||||
uSunDir: uSunDir, uMoonDir: uMoonDir, uMode: uMode,
|
uSunDir: uSunDir, uMoonDir: uMoonDir, uMode: uMode,
|
||||||
@@ -847,20 +782,15 @@ function init(canvas) {
|
|||||||
prog: terrainProg,
|
prog: terrainProg,
|
||||||
aPos: terrainApos, aNormal: terrainAnormal, aUV: terrainAuv,
|
aPos: terrainApos, aNormal: terrainAnormal, aUV: terrainAuv,
|
||||||
uView: terrainUview, uProj: terrainUproj,
|
uView: terrainUview, uProj: terrainUproj,
|
||||||
uGround: terrainUground, uSunDir: terrainUsundir, uLight: terrainUlight,
|
uGround: terrainUground, uSunDir: terrainUsundir,
|
||||||
uBg: terrainUbg, uSky: terrainUsky, uFogDist: terrainUfogdist, uNightFactor: terrainUnight,
|
|
||||||
uGroundCenter: terrainUgroundcenter, uGroundSize: terrainUgroundsize,
|
uGroundCenter: terrainUgroundcenter, uGroundSize: terrainUgroundsize,
|
||||||
uCorridorRot: terrainUcorridor, uCloudsOn: terrainUcloudsOn,
|
uCorridorRot: terrainUcorridor, uCloudsOn: terrainUcloudsOn,
|
||||||
|
|
||||||
uNightDim: terrainUnightdim,
|
|
||||||
uElevScale: terrainUelevScale,
|
uElevScale: terrainUelevScale,
|
||||||
uWireColor: terrainUwireColor,
|
uWireColor: terrainUwireColor,
|
||||||
uSunColor: terrainUsunColor, uSunInt: terrainUsunInt,
|
uSunColor: terrainUsunColor, uSunInt: terrainUsunInt,
|
||||||
uMoonColor: terrainUmoonColor, uMoonInt: terrainUmoonInt,
|
uMoonColor: terrainUmoonColor, uMoonInt: terrainUmoonInt,
|
||||||
uSkyAmb: terrainUskyAmb, uAlbedoTint: terrainUalbedoTint,
|
uSkyAmb: terrainUskyAmb, uAlbedoTint: terrainUalbedoTint,
|
||||||
uHorizonColor: terrainUhorizon, uPhysical: terrainUphysical,
|
uHorizonColor: terrainUhorizon,
|
||||||
uSunAlt: terrainUsunAlt, uLightHi: terrainUlightHi,
|
|
||||||
uLightLo: terrainUlightLo, uLightSoft: terrainUlightSoft,
|
|
||||||
uAirColor: terrainUairColor, uAirDensity: terrainUairDensity,
|
uAirColor: terrainUairColor, uAirDensity: terrainUairDensity,
|
||||||
uCamPos: terrainUcampos, uEdgeFadeTiles: terrainUedgeFade,
|
uCamPos: terrainUcampos, uEdgeFadeTiles: terrainUedgeFade,
|
||||||
uStarAmb: terrainUstarAmb,
|
uStarAmb: terrainUstarAmb,
|
||||||
@@ -1654,13 +1584,6 @@ function buildElevationSystem(glCtx) {
|
|||||||
// ---- 3D math helpers (used by the terrain mesh + view-proj matrices) ----
|
// ---- 3D math helpers (used by the terrain mesh + view-proj matrices) ----
|
||||||
// 4x4 matrix laid out column-major (the same layout gl.uniformMatrix4fv
|
// 4x4 matrix laid out column-major (the same layout gl.uniformMatrix4fv
|
||||||
// expects) as a flat Float32Array of 16 entries.
|
// expects) as a flat Float32Array of 16 entries.
|
||||||
function mat4Identity(out) {
|
|
||||||
out[0]=1;out[1]=0;out[2]=0;out[3]=0;
|
|
||||||
out[4]=0;out[5]=1;out[6]=0;out[7]=0;
|
|
||||||
out[8]=0;out[9]=0;out[10]=1;out[11]=0;
|
|
||||||
out[12]=0;out[13]=0;out[14]=0;out[15]=1;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
function mat4LookAt(eye, target, up) {
|
function mat4LookAt(eye, target, up) {
|
||||||
// z axis = (eye - target) normalized (camera looks down -z)
|
// z axis = (eye - target) normalized (camera looks down -z)
|
||||||
var zx = eye[0]-target[0], zy = eye[1]-target[1], zz = eye[2]-target[2];
|
var zx = eye[0]-target[0], zy = eye[1]-target[1], zz = eye[2]-target[2];
|
||||||
@@ -1922,9 +1845,6 @@ function buildTerrainMesh(gl, state) {
|
|||||||
// How far the day map is dimmed at full night (0 = pitch black, 1 =
|
// How far the day map is dimmed at full night (0 = pitch black, 1 =
|
||||||
// unchanged). The NASA VIIRS city-lights layer was removed: settlement
|
// unchanged). The NASA VIIRS city-lights layer was removed: settlement
|
||||||
// lights come from OSM place nodes (see the settlement lights section).
|
// lights come from OSM place nodes (see the settlement lights section).
|
||||||
var NIGHT_CFG = (window.CONFIG && window.CONFIG.terrain) || {};
|
|
||||||
var NIGHT_DIM = (typeof NIGHT_CFG.nightDim === 'number') ?
|
|
||||||
Math.max(0, Math.min(1, NIGHT_CFG.nightDim)) : 0.22;
|
|
||||||
|
|
||||||
// ---- Motorway car-lights layer ----------------------------------------
|
// ---- Motorway car-lights layer ----------------------------------------
|
||||||
// Motorway/trunk roads (Autobahnen) from OSM via Overpass API, fetched at
|
// Motorway/trunk roads (Autobahnen) from OSM via Overpass API, fetched at
|
||||||
@@ -1945,7 +1865,6 @@ var ATMO = (window.CONFIG && window.CONFIG.atmosphere) || {};
|
|||||||
var ATMO_TURBIDITY = (typeof ATMO.turbidity === 'number') ? Math.max(0.2, ATMO.turbidity) : 1.0;
|
var ATMO_TURBIDITY = (typeof ATMO.turbidity === 'number') ? Math.max(0.2, ATMO.turbidity) : 1.0;
|
||||||
var ATMO_MOON_GAIN = (typeof ATMO.moonGain === 'number') ? Math.max(0, ATMO.moonGain) : 0.16;
|
var ATMO_MOON_GAIN = (typeof ATMO.moonGain === 'number') ? Math.max(0, ATMO.moonGain) : 0.16;
|
||||||
var ATMO_NIGHT_FLOOR = (typeof ATMO.nightFloor === 'number') ? Math.max(0.01, ATMO.nightFloor) : 0.05;
|
var ATMO_NIGHT_FLOOR = (typeof ATMO.nightFloor === 'number') ? Math.max(0.01, ATMO.nightFloor) : 0.05;
|
||||||
var ATMO_TERRAIN_PHYS = ATMO.terrainLighting !== 'palette';
|
|
||||||
var ATMO_ALBEDO_GAIN = (typeof ATMO.albedoGain === 'number') ? Math.max(0.1, ATMO.albedoGain) : 1.0;
|
var ATMO_ALBEDO_GAIN = (typeof ATMO.albedoGain === 'number') ? Math.max(0.1, ATMO.albedoGain) : 1.0;
|
||||||
var ATMO_SKY_GAIN = (typeof ATMO.skyGain === 'number') ? Math.max(0.05, ATMO.skyGain) : 0.55;
|
var ATMO_SKY_GAIN = (typeof ATMO.skyGain === 'number') ? Math.max(0.05, ATMO.skyGain) : 0.55;
|
||||||
|
|
||||||
@@ -2003,6 +1922,43 @@ function airlightColor(altRad, moonAlt, moonFrac) {
|
|||||||
RAYLEIGH[1] * (beam[1] * kDay + mBeam[1] * kMoon),
|
RAYLEIGH[1] * (beam[1] * kDay + mBeam[1] * kMoon),
|
||||||
RAYLEIGH[2] * (beam[2] * kDay + mBeam[2] * kMoon)];
|
RAYLEIGH[2] * (beam[2] * kDay + mBeam[2] * kMoon)];
|
||||||
}
|
}
|
||||||
|
// ---- Theme lighting -----------------------------------------------------
|
||||||
|
// The physical model stays the base in every theme: the theme only pulls
|
||||||
|
// the beam, the airlight and the moonlight toward its own hue by 'tint'
|
||||||
|
// (0 = pure physics, 1 = the theme colour), and gain / nightGain scale the
|
||||||
|
// day and night exposure. Configured per theme in window.THEMES[theme].
|
||||||
|
// .lighting; a theme without that block is pure physics.
|
||||||
|
function themeColor(v, fallback) {
|
||||||
|
if (typeof v === 'number') return hex(v);
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
var t = v.replace('#', '');
|
||||||
|
if (t.length === 3) t = t[0] + t[0] + t[1] + t[1] + t[2] + t[2];
|
||||||
|
if (t.length !== 6 || isNaN(parseInt(t, 16))) return fallback;
|
||||||
|
var n = parseInt(t, 16);
|
||||||
|
return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
function mixRgb(a, b, k) {
|
||||||
|
return [a[0] + (b[0] - a[0]) * k, a[1] + (b[1] - a[1]) * k, a[2] + (b[2] - a[2]) * k];
|
||||||
|
}
|
||||||
|
function themeLighting() {
|
||||||
|
var name = (window.CONFIG && window.CONFIG.theme) || 'ash';
|
||||||
|
var th = (window.THEMES && window.THEMES[name]) || null;
|
||||||
|
var L = th && th.lighting;
|
||||||
|
if (!L) return null;
|
||||||
|
var tint = (typeof L.tint === 'number') ? Math.max(0, Math.min(1, L.tint)) : 0.30;
|
||||||
|
return {
|
||||||
|
tint: tint,
|
||||||
|
sun: themeColor(L.sun, [1, 1, 1]),
|
||||||
|
sky: themeColor(L.sky, [0.55, 0.65, 0.85]),
|
||||||
|
air: themeColor(L.air, [0.45, 0.60, 0.85]),
|
||||||
|
moon: themeColor(L.moon, [0.80, 0.85, 0.95]),
|
||||||
|
gain: (typeof L.gain === 'number') ? Math.max(0.2, L.gain) : 1,
|
||||||
|
nightGain: (typeof L.nightGain === 'number') ? Math.max(0.2, L.nightGain) : 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function lightFactor(altRad) {
|
function lightFactor(altRad) {
|
||||||
var span = LIGHT_HI - LIGHT_LO;
|
var span = LIGHT_HI - LIGHT_LO;
|
||||||
if (span <= 0) return 0;
|
if (span <= 0) return 0;
|
||||||
@@ -2048,10 +2004,6 @@ function skyAmbient(altRad) {
|
|||||||
var tw = s > 0 ? 1 : Math.max(ATMO_NIGHT_FLOOR, Math.pow(2, altRad * 57.2957795 / 8.0));
|
var tw = s > 0 ? 1 : Math.max(ATMO_NIGHT_FLOOR, Math.pow(2, altRad * 57.2957795 / 8.0));
|
||||||
return day * tw;
|
return day * tw;
|
||||||
}
|
}
|
||||||
function skyTwilight(altRad) {
|
|
||||||
if (altRad > 0) return 1;
|
|
||||||
return Math.max(ATMO_NIGHT_FLOOR, Math.pow(2, altRad * 57.2957795 / 8.0));
|
|
||||||
}
|
|
||||||
var CAR_CFG = (window.CONFIG && window.CONFIG.terrain) || {};
|
var CAR_CFG = (window.CONFIG && window.CONFIG.terrain) || {};
|
||||||
var CAR_ON = CAR_CFG.carLights !== false;
|
var CAR_ON = CAR_CFG.carLights !== false;
|
||||||
var CITY_ON = CAR_CFG.cityLights !== false;
|
var CITY_ON = CAR_CFG.cityLights !== false;
|
||||||
@@ -4824,7 +4776,8 @@ window.clouds3dRender = function(target) {
|
|||||||
gl.uniform3f(s.uSunDir, sd[0], sd[1], sd[2]);
|
gl.uniform3f(s.uSunDir, sd[0], sd[1], sd[2]);
|
||||||
var md = window.__currentMoonDir || sd;
|
var md = window.__currentMoonDir || sd;
|
||||||
gl.uniform3f(s.uMoonDir, md[0], md[1], md[2]);
|
gl.uniform3f(s.uMoonDir, md[0], md[1], md[2]);
|
||||||
gl.uniform1f(s.uPhysical, ATMO_TERRAIN_PHYS ? 1.0 : 0.0);
|
// the physical model is the only lighting model now
|
||||||
|
gl.uniform1f(s.uPhysical, 1.0);
|
||||||
|
|
||||||
// Star catalogue sky texture on texture unit 4
|
// Star catalogue sky texture on texture unit 4
|
||||||
gl.activeTexture(gl.TEXTURE4);
|
gl.activeTexture(gl.TEXTURE4);
|
||||||
@@ -4890,6 +4843,20 @@ window.clouds3dRender = function(target) {
|
|||||||
var _moonDiff = ATMO_MOON_GAIN * _moonFrac * _moonUp * 0.9;
|
var _moonDiff = ATMO_MOON_GAIN * _moonFrac * _moonUp * 0.9;
|
||||||
var _amb = skyAmbient(sunAlt) + _moonDiff;
|
var _amb = skyAmbient(sunAlt) + _moonDiff;
|
||||||
var _air = airlightColor(sunAlt, _moonAlt, _moonFrac);
|
var _air = airlightColor(sunAlt, _moonAlt, _moonFrac);
|
||||||
|
// Theme tint on top of the physics (see themeLighting above). The
|
||||||
|
// exposure factor ramps from gain (day) to nightGain (night) with the
|
||||||
|
// same altitude ramp the terrain uses for its night cross-fade.
|
||||||
|
var _tl = themeLighting();
|
||||||
|
if (_tl) {
|
||||||
|
var _nf = Math.max(0, Math.min(1, -sunAlt / 0.4));
|
||||||
|
var _exp = _tl.gain * (1 - _nf) + _tl.nightGain * _nf;
|
||||||
|
_beam = mixRgb(_beam, _tl.sun, _tl.tint);
|
||||||
|
_mBeam = mixRgb(_mBeam, _tl.moon, _tl.tint);
|
||||||
|
_air = mixRgb(mixRgb(_air, _tl.air, _tl.tint), _tl.sky, _tl.tint * 0.45);
|
||||||
|
_beamL *= _exp;
|
||||||
|
_moonI *= _exp;
|
||||||
|
_amb *= _exp;
|
||||||
|
}
|
||||||
gl.uniform3f(s.uAirColor, _air[0], _air[1], _air[2]);
|
gl.uniform3f(s.uAirColor, _air[0], _air[1], _air[2]);
|
||||||
gl.uniform3f(s.uSunColor, _beam[0], _beam[1], _beam[2]);
|
gl.uniform3f(s.uSunColor, _beam[0], _beam[1], _beam[2]);
|
||||||
gl.uniform1f(s.uSunInt, _beamL);
|
gl.uniform1f(s.uSunInt, _beamL);
|
||||||
@@ -4911,7 +4878,6 @@ window.clouds3dRender = function(target) {
|
|||||||
var speed = (typeof themeSpeed === 'number')
|
var speed = (typeof themeSpeed === 'number')
|
||||||
? Math.max(0.1, Math.min(3, themeSpeed)) : 1.0;
|
? Math.max(0.1, Math.min(3, themeSpeed)) : 1.0;
|
||||||
gl.uniform1f(s.uSpeed, speed);
|
gl.uniform1f(s.uSpeed, speed);
|
||||||
gl.uniform1f(s.uFogDist, FOG_TILES);
|
|
||||||
gl.uniform1f(s.uCloudTop, CLOUD_TOP);
|
gl.uniform1f(s.uCloudTop, CLOUD_TOP);
|
||||||
gl.uniform1f(s.uCloudBot, CLOUD_BOT);
|
gl.uniform1f(s.uCloudBot, CLOUD_BOT);
|
||||||
|
|
||||||
@@ -5095,7 +5061,6 @@ window.clouds3dRender = function(target) {
|
|||||||
gl.uniformMatrix4fv(terr.uView, false, view);
|
gl.uniformMatrix4fv(terr.uView, false, view);
|
||||||
gl.uniformMatrix4fv(terr.uProj, false, proj);
|
gl.uniformMatrix4fv(terr.uProj, false, proj);
|
||||||
gl.uniform1i(terr.uGround, 1);
|
gl.uniform1i(terr.uGround, 1);
|
||||||
gl.uniform1f(terr.uNightDim, NIGHT_DIM);
|
|
||||||
gl.uniform2f(terr.uGroundCenter, centerWorld.x, centerWorld.y);
|
gl.uniform2f(terr.uGroundCenter, centerWorld.x, centerWorld.y);
|
||||||
gl.uniform2f(terr.uGroundSize, groundTilesX(), groundTilesY());
|
gl.uniform2f(terr.uGroundSize, groundTilesX(), groundTilesY());
|
||||||
gl.uniform2f(terr.uCorridorRot, GROUND_TRAVEL_COS, GROUND_TRAVEL_SIN);
|
gl.uniform2f(terr.uCorridorRot, GROUND_TRAVEL_COS, GROUND_TRAVEL_SIN);
|
||||||
@@ -5135,21 +5100,10 @@ window.clouds3dRender = function(target) {
|
|||||||
// instead of a separate grey veil.
|
// instead of a separate grey veil.
|
||||||
var _hor = 0.75;
|
var _hor = 0.75;
|
||||||
gl.uniform3f(terr.uHorizonColor, _air[0] * _hor, _air[1] * _hor, _air[2] * _hor);
|
gl.uniform3f(terr.uHorizonColor, _air[0] * _hor, _air[1] * _hor, _air[2] * _hor);
|
||||||
gl.uniform1f(terr.uPhysical, ATMO_TERRAIN_PHYS ? 1.0 : 0.0);
|
|
||||||
gl.uniform1f(terr.uSunAlt, sunAlt);
|
|
||||||
gl.uniform1f(terr.uLightHi, LIGHT_HI);
|
|
||||||
gl.uniform1f(terr.uLightLo, LIGHT_LO);
|
|
||||||
gl.uniform1f(terr.uLightSoft, LIGHT_SOFT);
|
|
||||||
gl.uniform3f(terr.uAirColor, _air[0], _air[1], _air[2]);
|
gl.uniform3f(terr.uAirColor, _air[0], _air[1], _air[2]);
|
||||||
gl.uniform1f(terr.uAirDensity, -Math.log(ATMO_HAZE_RESIDUAL) / Math.max(FOG_TILES, 0.001));
|
gl.uniform1f(terr.uAirDensity, -Math.log(ATMO_HAZE_RESIDUAL) / Math.max(FOG_TILES, 0.001));
|
||||||
gl.uniform3f(terr.uCamPos, camX, camY, camZ);
|
gl.uniform3f(terr.uCamPos, camX, camY, camZ);
|
||||||
gl.uniform1f(terr.uEdgeFadeTiles, EDGE_FADE_TILES);
|
gl.uniform1f(terr.uEdgeFadeTiles, EDGE_FADE_TILES);
|
||||||
gl.uniform3fv(terr.uLight, hex(pal.light));
|
|
||||||
gl.uniform3fv(terr.uBg, hex(pal.bg));
|
|
||||||
gl.uniform3fv(terr.uSky, hex(pal.sky));
|
|
||||||
gl.uniform1f(terr.uFogDist, FOG_TILES);
|
|
||||||
var nightFac = Math.max(0, Math.min(1, -sunAlt / 0.4));
|
|
||||||
gl.uniform1f(terr.uNightFactor, nightFac);
|
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, terr.vbo);
|
gl.bindBuffer(gl.ARRAY_BUFFER, terr.vbo);
|
||||||
var STRIDE = 8 * 4; // 8 floats per vertex
|
var STRIDE = 8 * 4; // 8 floats per vertex
|
||||||
|
|||||||
Reference in new issue
Block a user