From 96ce795f8c7ec4d77a602920143aee28f0b10951 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Mon, 2 Nov 2026 20:58:17 +0400 Subject: [PATCH] Keep Navara terrain visible on night-side descents --- src/globe/navara/look.js | 39 ++++++++++++++++++---------------- src/globe/navara/look.test.mjs | 35 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/globe/navara/look.js b/src/globe/navara/look.js index 97624cf23..8abae7133 100644 --- a/src/globe/navara/look.js +++ b/src/globe/navara/look.js @@ -108,24 +108,6 @@ export function buildNavaraLook( tone.update({ toneMapping: { mode: landscape ? AGX : ACES_FILMIC } }); changed = true; } - // In orbit, retain some of the underlying imagery in the atmospheric - // shadow. Fade up with altitude so ground views keep their original light. - const nextFill = - landscape && Number.isFinite(height) - ? Math.min(1, Math.max(0, (height - 50_000) / 250_000)) - : 0; - if (nextFill !== fill) { - fill = nextFill; - if (fillLight) fillLight.update({ ambient: { intensity: fill } }); - else fillLight = view.addLight({ ambient: { intensity: fill } }); - changed = true; - } - const opacity = aerial?.ref?.raw?.rawEffect?.blendMode.opacity; - const strength = 1 - 0.2 * fill; - if (opacity && opacity.value !== strength) { - opacity.value = strength; - changed = true; - } const atmosphere = view.atmosphere; // Choose inspection daylight once. Rotating the camera must not rotate // the terminator across the geography. @@ -140,6 +122,27 @@ export function buildNavaraLook( place && typeof atmosphere?.getSunElevation === 'function' ? atmosphere.getSunElevation(place) : Number.NaN; + // Keep the map legible from orbit and when descending into darkness. + // At low altitude the fill increases from sunset to civil dusk. + const orbitalFill = Number.isFinite(height) + ? Math.min(1, Math.max(0, (height - 50_000) / 250_000)) + : 0; + const nightFill = Number.isFinite(elevation) + ? Math.min(1, Math.max(0, -elevation / 6)) + : 0; + const nextFill = landscape ? Math.max(orbitalFill, nightFill) : 0; + if (nextFill !== fill) { + fill = nextFill; + if (fillLight) fillLight.update({ ambient: { intensity: fill } }); + else fillLight = view.addLight({ ambient: { intensity: fill } }); + changed = true; + } + const opacity = aerial?.ref?.raw?.rawEffect?.blendMode.opacity; + const strength = 1 - 0.2 * fill; + if (opacity && opacity.value !== strength) { + opacity.value = strength; + changed = true; + } if (warmUp) { warmUp -= 1; aerial.update(warmUp ? IRRADIANCE_ON : IRRADIANCE_OFF); diff --git a/src/globe/navara/look.test.mjs b/src/globe/navara/look.test.mjs index d751da061..2de6286fb 100644 --- a/src/globe/navara/look.test.mjs +++ b/src/globe/navara/look.test.mjs @@ -193,6 +193,41 @@ test('daylight inspection restores live exposure, tone mapping and time when lef assert.equal(view.atmosphere.date.getTime(), 0); }); +test('the dark side retains satellite imagery down to the surface', () => { + const view = fakeView(0); + const look = warmCinematic(view); + const aerial = view.added.find(({ desc }) => + Object.hasOwn(desc, 'aerialPerspective'), + ); + const opacity = { value: 1 }; + aerial.ref = { raw: { rawEffect: { blendMode: { opacity } } } }; + const origin = { lat: 21.975, lng: 20.9038 }; + const fuji = { lat: 35.2599, lng: 138.6574 }; + let elevation = 40; + view.atmosphere.getSunElevation = () => elevation; + look.frame(33_529_370, false, 0, origin, true); + const sun = view.atmosphere.date; + const ambient = view.added.find(({ desc }) => Object.hasOwn(desc, 'ambient')); + elevation = -30; + for (const height of [2_000_000, 100_000, 50_000, 6508, 600]) { + look.frame(height, true, 0, fuji, true); + assert.equal(opacity.value, 0.8); + assert.equal( + ambient.updates.length, + 0, + 'the orbital fill remains at full strength', + ); + assert.equal(view.atmosphere.date, sun); + } + elevation = -3; + look.frame(600, false, 0, fuji, true); + assert.equal(ambient.updates.at(-1).ambient.intensity, 0.5); + elevation = 10; + look.frame(600, false, 0, origin, true); + assert.equal(ambient.updates.at(-1).ambient.intensity, 0); + assert.equal(opacity.value, 1); +}); + test('inspection sunlight stays fixed through orbit, zoom and move end', () => { const view = fakeView(0); const look = warmCinematic(view);