Anchor Navara inspection sunlight to the restored view

This commit is contained in:
German Khokhlov committed 2026-11-02 17:26:34 +04:00
1 parent 0d15e80e28
commit 3a2a53aef9
5 files changed
+80 -12

No files matched your search

+5
View File
@@ -199,6 +199,7 @@ export async function createNavaraScene({
const { engine } = globe;
navaraViews.set(engine, {
view: globe.view,
alignDaylightToRestoredView: globe.alignDaylightToRestoredView,
MeshDesc: globe.MeshDesc,
EffectDesc: globe.EffectDesc,
NavaraEffect: globe.NavaraEffect,
@@ -264,6 +265,10 @@ export function createViewerlessTools({
defer,
}) {
const { dataManager } = data;
void styleManager.initialRestorePromise?.then(() => {
if (!signal.aborted)
navaraViews.get(engine)?.alignDaylightToRestoredView?.();
});
const recentImagery = dataManager.layers.get('recent-imagery')?.module;
if (recentImagery) {
const tool = initNavaraImageryBoxTool({
+9 -7
View File
@@ -109,6 +109,7 @@ export async function createNavaraGlobe({
let view;
let engine;
let overlay = null;
let look;
let removeLook = null;
let removePostSampling = null;
try {
@@ -123,13 +124,9 @@ export async function createNavaraGlobe({
});
// The sun follows the engine clock (one time source for layers and the
// look); the look only runs in preRender, after the engine exists.
const look = buildNavaraLook(
view,
plugin,
plan,
() => engine.clock.frameTime(),
{ forEachLuminanceUniform },
);
look = buildNavaraLook(view, plugin, plan, () => engine.clock.frameTime(), {
forEachLuminanceUniform,
});
// The look's shadowed sun leaked a light per frame (internals.js).
keepCascadedLightIds(view);
removePostSampling = createPostSampling(view);
@@ -213,6 +210,11 @@ export async function createNavaraGlobe({
NavaraEffect,
DrapedMesh,
setupMaterialForDrape,
// Startup can fly from the engine's placeholder camera into a shared view.
alignDaylightToRestoredView() {
look.resetDaylight();
engine.render.request('restored-daylight');
},
setRenderLoop: (enabled) => setRenderLoopEnabled(view, enabled),
destroy() {
if (destroyed) return;
+34 -3
View File
@@ -86,6 +86,9 @@ export function buildNavaraLook(
let cloudsShown = true;
let cloudsSunlit = true;
let landscape = false;
let daylightTime = null;
let fillLight;
let fill = 0;
const tone = effect('toneMapping');
let starsShown = true;
@@ -100,13 +103,35 @@ export function buildNavaraLook(
let changed = brighten(!landscapeRequested);
if (landscapeRequested !== landscape && tone) {
landscape = landscapeRequested;
daylightTime = null;
view.toneMappingExposure = landscape ? 10 : 1;
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;
const time =
landscape && place ? landscapeSunTime(now(), place.lng) : now();
// Choose inspection daylight once. Rotating the camera must not rotate
// the terminator across the geography.
if (landscape && place && daylightTime === null)
daylightTime = landscapeSunTime(now(), place.lng);
const time = daylightTime ?? now();
if (atmosphere && sunDateStale(atmosphere.date.getTime(), time)) {
atmosphere.date = new Date(time);
changed = true;
@@ -170,7 +195,13 @@ export function buildNavaraLook(
}
return changed;
};
return { handles, frame };
return {
handles,
frame,
resetDaylight() {
daylightTime = null;
},
};
}
const IRRADIANCE_ON = Object.freeze({
+30
View File
@@ -167,20 +167,50 @@ test('daylight inspection restores live exposure, tone mapping and time when lef
const tone = view.added.find(({ desc }) =>
Object.hasOwn(desc, 'toneMapping'),
);
const aerial = view.added.find(({ desc }) =>
Object.hasOwn(desc, 'aerialPerspective'),
);
const opacity = { value: 1 };
aerial.ref = { raw: { rawEffect: { blendMode: { opacity } } } };
const fuji = { lat: 35.2599, lng: 138.6574 };
look.frame(6508, false, 2600, fuji, true);
assert.equal(view.toneMappingExposure, 10);
assert.equal(opacity.value, 1);
assert.equal(tone.updates.at(-1).toneMapping.mode, 7);
assert.ok(
Math.abs(view.atmosphere.date.getTime() / 3_600_000 + fuji.lng / 15 - 13) <
1e-6,
);
look.frame(750_149, false, 0, fuji, true);
assert.ok(opacity.value < 1);
const ambient = view.added.find(({ desc }) => Object.hasOwn(desc, 'ambient'));
assert.equal(ambient.desc.ambient.intensity, 1);
look.frame(6508, false, 2600, fuji, false);
assert.equal(ambient.updates.at(-1).ambient.intensity, 0);
assert.equal(view.toneMappingExposure, 1);
assert.equal(opacity.value, 1);
assert.equal(tone.updates.at(-1).toneMapping.mode, 6);
assert.equal(view.atmosphere.date.getTime(), 0);
});
test('inspection sunlight stays fixed through orbit, zoom and move end', () => {
const view = fakeView(0);
const look = warmCinematic(view);
const origin = { lat: -45.9847, lng: 67.8795 };
const rotated = { lat: -20, lng: -158 };
look.frame(50_151_111, false, 0, origin, true);
const sunDate = view.atmosphere.date;
look.frame(50_151_111, true, 0, rotated, true);
look.frame(750_149, false, 0, rotated, true);
assert.equal(view.atmosphere.date, sunDate);
look.resetDaylight();
look.frame(750_149, false, 0, rotated, true);
assert.notEqual(view.atmosphere.date.getTime(), sunDate.getTime());
const restored = view.atmosphere.date;
look.frame(50_151_111, false, 0, origin, true);
assert.equal(view.atmosphere.date, restored);
});
test('the sun follows the wall clock, a minute at most behind, only on drawn frames', () => {
let now = 1_000_000;
const view = fakeView(now);
+2 -2
View File
@@ -52,8 +52,8 @@ export function resolveNavaraLook(search = '') {
export const ACES_FILMIC = 6;
export const AGX = 7;
// The Sentinel-2 landscape uses early afternoon at the camera longitude,
// independently of the telemetry clock. Only the displayed sunlight moves.
// Pick early afternoon at the starting landscape longitude. The look keeps
// this timestamp while the user explores; telemetry uses its own clock.
export function landscapeSunTime(now, longitude) {
const hour = 3_600_000;
const day = 24 * hour;