physical lighting, volumetric clouds, terrain shadows, pixel-art render scale
- atmosphere: physical terrain lighting (air mass, Rayleigh beam colour, skylight, moon, starlight floor), aerial perspective for terrain and cloud deck, configurable haze/albedo/ambient gains - clouds: march the deck in height instead of ray distance, IGN dither (no per-frame flicker), per-step height jitter, 4-octave fbm renormalised, softer density threshold, distance softening as a procedural mip, grazing path clamp, cloud shadows on the ground (cloudShadowDensity/ Floor/Ambient) and cloud haze factor - terrain: self-shadows from the elevation field (penumbra + shadow floor), soft mosaic rim (edgeFadeTiles), east/west axis corrected (+X = west) - lights: per-object twilight switching for cars, city dots and ground blocks - flights: contrail width 620 m, fade to zero by contrailFadeKm, aircraft size decoupled from trail width, count halved - render: CONFIG.renderScale with integer upscale + image-rendering: pixelated - data: nginx overpass proxy + cache, per-mirror cooldown ladder, ring-based cell ordering (disc fills evenly), settlements fetched before roads, interim chain persistence, two worker lanes (car/city) with progress logs - removed: NASA VIIRS night-lights layer (fetch, canvas, texture, uniforms)
This commit is contained in:
1 parent
cd460ec100
commit
6eb25bdd03
6 files changed
+2579
-795
No files matched your search
+114
-21
@@ -191,21 +191,27 @@
|
||||
bottom: 2vh;
|
||||
color: var(--bone);
|
||||
background: rgba(28, 27, 31, 0.7);
|
||||
padding: 1vh 1vw;
|
||||
padding: 1vh 1em;
|
||||
border-radius: 0.6vh;
|
||||
z-index: 9999;
|
||||
font-size: clamp(10px, 1.2vh, 14px);
|
||||
/* Everything below is derived from vh (font-size) or em (which
|
||||
follows font-size), so the whole panel is one scale factor:
|
||||
grow the viewport and it grows, shrink it and it shrinks.
|
||||
No px floors - those were what made the content overflow the
|
||||
box in both directions on short screens. */
|
||||
font-size: 1.5vh;
|
||||
line-height: 1.4;
|
||||
width: clamp(260px, min(33vh, 28vw), 40vw);
|
||||
width: min(32vh, 44vw);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 700px) {
|
||||
#info-panel {
|
||||
width: 90vw;
|
||||
width: min(88vw, 46vh);
|
||||
}
|
||||
}
|
||||
#analog-clock {
|
||||
@@ -218,7 +224,7 @@
|
||||
#digital-clock {
|
||||
text-align: center;
|
||||
font-size: 1.3em;
|
||||
letter-spacing: 2px;
|
||||
letter-spacing: 0.12em;
|
||||
margin-bottom: 0.6vh;
|
||||
padding-bottom: 0.5vh;
|
||||
border-bottom: 1px solid rgba(230, 223, 209, 0.2);
|
||||
@@ -227,7 +233,7 @@
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6vw;
|
||||
gap: 0.5em;
|
||||
padding: 0.05vh 0;
|
||||
}
|
||||
.info-icon {
|
||||
@@ -246,7 +252,7 @@
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.6vw;
|
||||
gap: 0.5em;
|
||||
}
|
||||
.info-label {
|
||||
color: var(--icon);
|
||||
@@ -258,7 +264,7 @@
|
||||
.info-sub {
|
||||
color: var(--stone);
|
||||
font-size: 0.85em;
|
||||
margin-left: 4px;
|
||||
margin-left: 0.3em;
|
||||
}
|
||||
.weather-divider {
|
||||
height: 1px;
|
||||
@@ -268,7 +274,7 @@
|
||||
.weather-current {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8vw;
|
||||
gap: 0.7em;
|
||||
padding: 0.8vh 0 1vh 0;
|
||||
border-bottom: 1px solid rgba(230, 223, 209, 0.2);
|
||||
margin-bottom: 1vh;
|
||||
@@ -285,7 +291,7 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 1vw;
|
||||
gap: 0.8em;
|
||||
}
|
||||
.weather-location {
|
||||
font-size: 0.45em;
|
||||
@@ -329,7 +335,7 @@
|
||||
.weather-day-slots {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.3vw;
|
||||
gap: 0.3em;
|
||||
font-size: 0.9em;
|
||||
margin-top: 0.3vh;
|
||||
}
|
||||
@@ -583,8 +589,34 @@ var _maxFps = (window.CONFIG && typeof window.CONFIG.maxFps === 'number')
|
||||
? window.CONFIG.maxFps : 30;
|
||||
var _frameGap = _maxFps > 0 ? 1000 / _maxFps : 0;
|
||||
var _lastDraw = 0;
|
||||
|
||||
// ---- Info panel auto-fit -------------------------------------------------
|
||||
// The panel box itself is viewport-sized (top/bottom 2vh) and every size
|
||||
// inside it is em/vh based, so a single font size scales the whole content.
|
||||
// Binary search that font size until the content fills the box exactly:
|
||||
// no scrollbar at any viewport size, and the proportions never change.
|
||||
function fitInfoPanel() {
|
||||
var p = document.getElementById('info-panel');
|
||||
if (!p) return;
|
||||
var avail = p.clientHeight;
|
||||
if (!avail) return;
|
||||
var lo = 0.4, hi = 3.0;
|
||||
for (var i = 0; i < 9; i++) {
|
||||
var mid = (lo + hi) / 2;
|
||||
p.style.fontSize = mid + 'vh';
|
||||
if (p.scrollHeight <= avail + 1) lo = mid; else hi = mid;
|
||||
}
|
||||
p.style.fontSize = lo.toFixed(3) + 'vh';
|
||||
}
|
||||
var _fitTimer = null;
|
||||
function scheduleFitInfoPanel() {
|
||||
if (_fitTimer) clearTimeout(_fitTimer);
|
||||
_fitTimer = setTimeout(fitInfoPanel, 120);
|
||||
}
|
||||
|
||||
sceneRender();
|
||||
window.addEventListener('resize', sceneRender);
|
||||
window.addEventListener('resize', scheduleFitInfoPanel);
|
||||
(function sceneLoop() {
|
||||
window._sceneRaf = requestAnimationFrame(sceneLoop);
|
||||
var t = performance.now();
|
||||
@@ -1316,6 +1348,13 @@ window.sunPalettes = [
|
||||
|
||||
function updateClockAndEvents() {
|
||||
var now = new Date();
|
||||
// Simulated wall clock: with CONFIG.day.simulation the scene runs
|
||||
// a whole day in cycleSeconds, so the clock hands, the digital
|
||||
// readout and every sun/moon table follow the accelerated date
|
||||
// instead of the real one - the scene and the clock stay in step.
|
||||
// simNow() is the same accelerated date the weather/calendar
|
||||
// layer uses, so nothing drifts apart.
|
||||
if (DAY_SIM) now = simNow();
|
||||
|
||||
// Calculate real day progress based on sun position
|
||||
// 0% = midnight, 50% = noon, 100% = next midnight
|
||||
@@ -1805,6 +1844,7 @@ window.weatherThemes = {
|
||||
|
||||
function updateDebug(t, palette, sceneColors, themeName, realDayProgress) {
|
||||
var now = new Date();
|
||||
if (DAY_SIM) now = simNow();
|
||||
var hh = now.getHours(), mm = now.getMinutes(), ss = now.getSeconds();
|
||||
document.getElementById('dbg-time').textContent =
|
||||
(hh < 10 ? '0' : '') + hh + ':' + (mm < 10 ? '0' : '') + mm + ':' + (ss < 10 ? '0' : '') + ss;
|
||||
@@ -1948,6 +1988,12 @@ window.weatherThemes = {
|
||||
// bearing: 6:00 (t=0.25) = east (pi/2), 12:00 = south (pi),
|
||||
// 18:00 = west (3pi/2) -> bearing = 2*PI*t
|
||||
var _sunBearing = t * Math.PI * 2;
|
||||
// World frame (verified from the star frame and the terrain
|
||||
// mesh): +X points WEST, +Y up, +Z north. The star frame maps
|
||||
// RA=0 to +X and rotates it to due west after 6 h of sidereal
|
||||
// time, and the mesh samples the mosaic with u = 0.5 - x/size,
|
||||
// so east is -X. The east component therefore has to land on
|
||||
// -cos(alt)*sin(bearing).
|
||||
window.__currentSunDir = [
|
||||
-Math.cos(_alt) * Math.sin(_sunBearing),
|
||||
Math.sin(_alt),
|
||||
@@ -1957,9 +2003,10 @@ window.weatherThemes = {
|
||||
var _sunPos = SunCalc.getPosition(new Date(), SITE_LAT, SITE_LON);
|
||||
window.__currentSunAlt = _sunPos.altitude;
|
||||
window.__flightDow = new Date().getDay();
|
||||
// Unit vector towards the sun in world space (X east, Z north,
|
||||
// Y up). SunCalc azimuth is measured from south, clockwise ->
|
||||
// bearing from north = azimuth + PI.
|
||||
// Unit vector towards the sun in world space. SunCalc azimuth
|
||||
// is measured from the south, positive towards the west, so
|
||||
// the compass bearing (0 = north, clockwise) is azimuth + PI.
|
||||
// World frame: +X = west, +Z = north, +Y = up -> east is -X.
|
||||
var _sunBearingR = _sunPos.azimuth + Math.PI;
|
||||
window.__currentSunDir = [
|
||||
-Math.cos(_sunPos.altitude) * Math.sin(_sunBearingR),
|
||||
@@ -1967,6 +2014,26 @@ window.weatherThemes = {
|
||||
Math.cos(_sunPos.altitude) * Math.cos(_sunBearingR)
|
||||
];
|
||||
}
|
||||
// ---- Local sidereal time -------------------------------------
|
||||
// Same formula SunCalc uses internally (Meeus): GMST in degrees
|
||||
// plus the observer's east longitude. This is what the star
|
||||
// frame has to follow - a solar day fraction is 3 min 56 s per
|
||||
// day off and has no absolute phase at all.
|
||||
var _sidD = (Date.now() - Date.UTC(2000, 0, 1, 12, 0, 0)) / 86400000;
|
||||
var _sidReal = (280.16 + 360.9856235 * _sidD + SITE_LON) * Math.PI / 180;
|
||||
if (DAY_SIM) {
|
||||
if (typeof window.__sidStart !== 'number') {
|
||||
window.__sidStart = _sidReal;
|
||||
window.__sidStartMs = Date.now();
|
||||
}
|
||||
// simulated days elapsed * sidereal rate (1.0027379 solar days
|
||||
// per sidereal day)
|
||||
var _sidDays = (Date.now() - window.__sidStartMs) / CYCLE;
|
||||
window.__currentSiderealRad = window.__sidStart + _sidDays * 2 * Math.PI * 1.0027379;
|
||||
} else {
|
||||
window.__currentSiderealRad = _sidReal;
|
||||
}
|
||||
|
||||
// ---- Moon direction ------------------------------------------
|
||||
// Start elongation = real lunar phase (SunCalc: 0 new .. 0.5
|
||||
// full); in day simulation the elongation advances 360/29.53
|
||||
@@ -1985,11 +2052,30 @@ window.weatherThemes = {
|
||||
var _cx = _kp[1]*_sd[2] - _kp[2]*_sd[1];
|
||||
var _cy = _kp[2]*_sd[0] - _kp[0]*_sd[2];
|
||||
var _cz = _kp[0]*_sd[1] - _kp[1]*_sd[0];
|
||||
window.__currentMoonDir = [
|
||||
_sd[0]*_ecs + _cx*_esn + _kp[0]*_kdv*(1-_ecs),
|
||||
_sd[1]*_ecs + _cy*_esn + _kp[1]*_kdv*(1-_ecs),
|
||||
_sd[2]*_ecs + _cz*_esn + _kp[2]*_kdv*(1-_ecs)
|
||||
];
|
||||
if (DAY_SIM) {
|
||||
window.__currentMoonDir = [
|
||||
_sd[0]*_ecs + _cx*_esn + _kp[0]*_kdv*(1-_ecs),
|
||||
_sd[1]*_ecs + _cy*_esn + _kp[1]*_kdv*(1-_ecs),
|
||||
_sd[2]*_ecs + _cz*_esn + _kp[2]*_kdv*(1-_ecs)
|
||||
];
|
||||
// illuminated fraction straight from the elongation used above
|
||||
// (0 = new, 1 = full) so other layers can light themselves by it
|
||||
window.__currentMoonFrac = (1 - Math.cos(_elong)) * 0.5;
|
||||
} else {
|
||||
// Real mode: the geometric construction above keeps the moon on
|
||||
// the SUN's declination circle. The real moon leaves it by up to
|
||||
// 28.5 deg (obliquity 23.4 + lunar inclination 5.1), which puts
|
||||
// it in the wrong part of the sky. SunCalc solves the actual
|
||||
// lunar ephemeris, so use its altitude/azimuth directly.
|
||||
var _moonPos = SunCalc.getMoonPosition(new Date(), SITE_LAT, SITE_LON);
|
||||
var _moonBearingR = _moonPos.azimuth + Math.PI;
|
||||
window.__currentMoonDir = [
|
||||
-Math.cos(_moonPos.altitude) * Math.sin(_moonBearingR),
|
||||
Math.sin(_moonPos.altitude),
|
||||
Math.cos(_moonPos.altitude) * Math.cos(_moonBearingR)
|
||||
];
|
||||
window.__currentMoonFrac = SunCalc.getMoonIllumination(new Date()).fraction;
|
||||
}
|
||||
var __skyHex = '#' + ('00000' + c.sky.toString(16)).slice(-6);
|
||||
if (__skyHex !== window.__lastSkyHex) {
|
||||
window.__lastSkyHex = __skyHex;
|
||||
@@ -2104,6 +2190,7 @@ window.weatherThemes = {
|
||||
if (window.BackgroundCache) {
|
||||
window.BackgroundCache.put('meta/geolabel', { value: name });
|
||||
}
|
||||
scheduleFitInfoPanel();
|
||||
}
|
||||
})
|
||||
.catch(function() { /* ignore errors */ });
|
||||
@@ -2214,7 +2301,10 @@ window.weatherThemes = {
|
||||
window.BackgroundCache.put('meta/weather', { json: data });
|
||||
}
|
||||
currentWeatherCode = data.current.weather_code;
|
||||
setWeatherTheme(getWeatherThemeName(currentWeatherCode));
|
||||
// while the weather sim runs it owns the theme - a live
|
||||
// poll must not override the configured test weather
|
||||
if (!__weatherSimActive)
|
||||
setWeatherTheme(getWeatherThemeName(currentWeatherCode));
|
||||
updateWeatherDisplay();
|
||||
})
|
||||
.catch(function(err) {
|
||||
@@ -2227,7 +2317,8 @@ window.weatherThemes = {
|
||||
weatherData = rec.json;
|
||||
window.__weatherStale = true;
|
||||
currentWeatherCode = rec.json.current.weather_code;
|
||||
setWeatherTheme(getWeatherThemeName(currentWeatherCode));
|
||||
if (!__weatherSimActive)
|
||||
setWeatherTheme(getWeatherThemeName(currentWeatherCode));
|
||||
updateWeatherDisplay();
|
||||
}
|
||||
});
|
||||
@@ -2356,11 +2447,13 @@ window.weatherThemes = {
|
||||
'</div>';
|
||||
}
|
||||
document.getElementById('weather-forecast').innerHTML = forecastHtml;
|
||||
fitInfoPanel();
|
||||
}
|
||||
|
||||
// Update weather every 15 minutes
|
||||
applyI18n();
|
||||
populateDebugSelects();
|
||||
fitInfoPanel();
|
||||
// Apply debug flag from CONFIG
|
||||
var debugEl = document.getElementById('debug-panel');
|
||||
if (debugEl && !(window.CONFIG && window.CONFIG.debug)) {
|
||||
|
||||
Reference in new issue
Block a user