wip(mobile): checkpoint polish round in progress

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VVYRAqiQj9Y1V9ZfG1RcDH
This commit is contained in:
Claude committed 2026-10-23 09:59:48 +00:00
1 parent 4e1e838771
commit 90b962745e
29 files changed
+9600 -205

No files matched your search

+137 -1
View File
@@ -9,6 +9,7 @@ import * as Cesium from 'cesium';
import { createEsriImagery } from '../../src/maps/imagery.js';
import { createKeylessTerrain } from '../../src/maps/terrain.js';
import { loadPhotorealisticTileset } from '../../src/maps/google3d.js';
import { subsolarPoint } from './geo.js';
/** NASA GIBS VIIRS Black Marble (2016), Web Mercator, keyless, CORS-enabled. */
export const BLACK_MARBLE_URL =
@@ -20,6 +21,28 @@ const LIGHT_FULL_ABOVE_M = 2_500_000;
/** Cesium's night side keeps 0.3 of the lit colour (GlobeFS diffuse floor). */
const NIGHT_DIFFUSE_FLOOR = 0.3;
const NIGHT_LIGHTS_GAIN = 1.3;
/**
* Close-in night grade. Below ~400 km Cesium's lighting is faded out and the
* 500 m Black Marble mosaic is only a blur, so a night-side close-up shows the
* Esri day mosaic graded toward night (darker, desaturated, cooler) with a
* soft glow of the real city lights over it: the city stays readable at any
* hour. The sun is taken from the scene clock, so a Moment keeps the lighting
* of its own time.
*/
const GRADE = Object.freeze({
brightness: 0.5,
contrast: 1.18,
saturation: 0.38,
gamma: 0.9,
hue: 0.04,
glow: 0.3,
});
/**
* Terrain watchdog: when the globe has had nothing to draw for this long
* while it is on screen (the keyless terrain host throttles under load, and
* Cesium then shows only the star field), fall back to the flat ellipsoid.
*/
const TERRAIN_STALL_MS = 5000;
export const BASE_CREDITS = Object.freeze([
{
@@ -104,15 +127,45 @@ export function createImagery(viewer, { terrain = true, google = {} } = {}) {
),
};
let stallTimer = 0;
if (terrain) {
createKeylessTerrain().then(({ provider, fallback }) => {
if (viewer.isDestroyed()) return;
viewer.terrainProvider = provider;
state.terrainCredit = !fallback;
scene.requestRender();
if (!fallback) watchTerrain(provider);
});
}
function watchTerrain(provider) {
let emptySince = 0;
stallTimer = setInterval(() => {
if (viewer.isDestroyed() || viewer.terrainProvider !== provider)
return clearInterval(stallTimer);
// Only while the globe is on screen (a paused globe proves nothing).
if (document.hidden || !globe.show || !viewer.useDefaultRenderLoop) {
emptySince = 0;
return;
}
// Private, read defensively: how many tiles the globe could draw.
const n = globe._surface?._tilesToRender?.length;
if (n == null || n > 0) {
emptySince = 0;
return;
}
scene.requestRender();
if (!emptySince) emptySince = performance.now();
else if (performance.now() - emptySince > TERRAIN_STALL_MS) {
clearInterval(stallTimer);
console.warn('[globe] terrain stalled; using the flat ellipsoid');
viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider();
state.terrainCredit = false;
scene.requestRender();
}
}, 1500);
}
async function ensureGoogle() {
if (!state.googleWanted || state.google || state.googleLoading) return;
state.googleLoading = loadPhotorealisticTileset(Cesium, {
@@ -162,10 +215,74 @@ export function createImagery(viewer, { terrain = true, google = {} } = {}) {
night.nightAlpha = Math.min(1, Math.max(0, (h - 300_000) / 700_000));
}
/**
* Night grade for close-in views (see GRADE): strength follows the sun's
* elevation under the camera and fades out as Cesium's own lighting fades
* in with altitude.
*/
let gradeK = -1;
let gradeStrength = 0;
function grade() {
if (state.nightMode) return;
// The camera's own height, every frame (the view handler is throttled).
const h = scene.camera.positionCartographic.height;
const close =
1 -
Math.min(
1,
Math.max(
0,
(h - LIGHT_FULL_BELOW_M) / (LIGHT_FULL_ABOVE_M - LIGHT_FULL_BELOW_M),
),
);
let k = 0;
if (close > 0) {
const c = scene.camera.positionCartographic;
const ms = Cesium.JulianDate.toDate(viewer.clock.currentTime).getTime();
const sun = subsolarPoint(ms);
const D = Math.PI / 180;
const lat = c.latitude;
const dLon = c.longitude - sun.lon * D;
const sinEl =
Math.sin(lat) * Math.sin(sun.lat * D) +
Math.cos(lat) * Math.cos(sun.lat * D) * Math.cos(dLon);
const el = Math.asin(Math.max(-1, Math.min(1, sinEl))) / D;
// Civil twilight: full grade once the sun is 8° down, none at +4°.
const night = Math.min(1, Math.max(0, (4 - el) / 12));
k = Math.round(night * close * 50) / 50;
}
const glow = GRADE.glow * k * Math.min(1, Math.max(0, (h - 40e3) / 160e3));
const key = k * 1000 + Math.round(glow * 50);
if (key === gradeK) return;
gradeK = key;
gradeStrength = k;
const mix = (a, b) => a + (b - a) * k;
day.brightness = mix(1, GRADE.brightness);
day.contrast = mix(1, GRADE.contrast);
day.saturation = mix(1, GRADE.saturation);
day.gamma = mix(1, GRADE.gamma);
day.hue = mix(0, GRADE.hue);
if (k > 0) {
// Lighting is faded out this close: one glow alpha for both sides.
// Below ~150 km a whole lit city is one 500 m blur, so the glow fades
// out and the graded day mosaic carries the night alone.
night.dayAlpha = glow;
night.nightAlpha = glow;
night.brightness = 1.6;
} else {
night.dayAlpha = 0;
tuneNight();
}
scene.requestRender();
}
const removeGradeListener = scene.preRender.addEventListener(grade);
/** Swap globe ↔ photorealistic tiles by camera height. */
function onHeight(heightM) {
state.heightM = heightM;
gradeK = -1;
tuneNight();
grade();
if (!state.googleWanted) return;
const close = heightM < GOOGLE_3D_MAX_HEIGHT_M;
if (close) ensureGoogle();
@@ -180,11 +297,22 @@ export function createImagery(viewer, { terrain = true, google = {} } = {}) {
/** Night mode: Black Marble everywhere, unshaded (lighting off). */
function setNightMode(on) {
state.nightMode = Boolean(on);
if (on) {
day.brightness = 1;
day.contrast = 1;
day.saturation = 1;
day.gamma = 1;
day.hue = 0;
}
gradeK = -1;
globe.enableLighting = !on;
night.dayAlpha = on ? 1 : 0;
night.nightAlpha = 1;
night.brightness = on ? 1.25 : night.brightness;
if (!on) tuneNight();
if (!on) {
tuneNight();
grade();
}
scene.requestRender();
}
@@ -208,6 +336,14 @@ export function createImagery(viewer, { terrain = true, google = {} } = {}) {
onHeight,
setNightMode,
credits,
/** Night-grade strength 0..1 (0 by day or from orbit). */
get nightGrade() {
return gradeStrength;
},
destroy() {
removeGradeListener();
clearInterval(stallTimer);
},
get googleActive() {
return Boolean(state.google?.show);
},