mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
- Photo framing and FOLLOW claim the camera through the application's
navigation, injected with attachNavigation({ run, subscribeHandoff }):
aircraft and satellite tracking is released first, the cockpit refuses,
and FOLLOW stops when another feature takes the camera. No UI imports
in the layer.
- The mesh sampler follows the mesh floor's rules: it waits for the
visible tileset to finish streaming (visibleTilesetLoaded, now exposed
by the mesh floor service), needs a real bare-earth prior and keeps to
the shared meshFloorSampleWithinPrior window, which cameraFollow now
uses too. A cell is probed again once the camera is half as far from
it (down to 40 m), so a coarse early sample is replaced and redrawn.
- The MCP globe panel's CSP allows the Mapillary Graph API and its image
CDN (https://*.xx.fbcdn.net).
- The tile proxy reads bodies with the shared readResponseBytesCapped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
204 lines
9.3 KiB
JavaScript
204 lines
9.3 KiB
JavaScript
import * as Cesium from 'cesium';
|
||
|
||
/** Construct an instance-owned meshFloorSampler service with explicit dependencies. */
|
||
export function createMeshFloorSampler({
|
||
groundFloor,
|
||
signal,
|
||
eventTarget = null,
|
||
}) {
|
||
const {
|
||
coarseFloorCoord,
|
||
cachedMeshFloor,
|
||
reportValidatedMeshFloorCell,
|
||
setMeshFloorPreferred,
|
||
meshFloorPreferred,
|
||
cachedGroundFloor,
|
||
} = groundFloor;
|
||
signal?.throwIfAborted();
|
||
// src/data/meshFloorSampler.js — rendered-surface (mesh) floor sampling
|
||
// (field-test round 4, 2026-07-06).
|
||
//
|
||
// The visible world in the google-3d regime is the PHOTOGRAMMETRIC MESH,
|
||
// which sits above the Re:Earth bare-earth DEM (measured ~17 m at the Austin
|
||
// airport apron) — so DEM-floored sprites/trails still buried themselves in
|
||
// the mesh while 3D models (which groundSnap against the mesh) looked right.
|
||
// This module samples the mesh height ONE-SHOT per coarse cell and reports it
|
||
// to groundFloor.js, whose cachedGroundFloor() then answers mesh-first.
|
||
//
|
||
// Rationing (the CCTV loading-jitter lesson): scene.sampleHeight forces
|
||
// tile-load prioritization at the probe point, so sampling is (a) gated to
|
||
// cells NEAR the viewer — where tiles are streamed and where an error is
|
||
// visible at all, (b) capped per call, (c) one-shot per cell for the session,
|
||
// (d) skipped entirely outside the google-3d regime. A returned-undefined
|
||
// probe (tiles not streamed yet) is NOT latched — it retries on a later call.
|
||
//
|
||
// Sample acceptance is sanity-gated against the DEM prior when warm (a
|
||
// vertical probe can hit a rooftop or another aircraft instead of pavement):
|
||
// the mesh legitimately sits ABOVE bare earth, so the window is asymmetric.
|
||
|
||
/** @constant {number} Max scene samples per call (one call per layer poll). */
|
||
const MAX_SAMPLES_PER_CALL = 40;
|
||
/** @constant {number} Only cells within this range of the viewer subpoint are
|
||
* sampled. Round-5 hardening: tightened 40 → 15 km — beyond the streamed
|
||
* fine-LOD area a probe returns COARSE-tile heights (not undefined), and the
|
||
* one-shot latch made those permanent (owner round 5: grounded planes stuck
|
||
* at a uniform wrong height). */
|
||
const MAX_SAMPLE_DIST_KM = 15;
|
||
/** @constant {number} No sampling when the camera is above this height — the
|
||
* streamed LOD under a high camera is coarse everywhere, so every probe
|
||
* would latch junk. */
|
||
const MAX_CAMERA_HEIGHT_M = 25_000;
|
||
/** @type {Cesium.Cartographic} Scratch for probe coordinates. */
|
||
const _scratchProbe = new Cesium.Cartographic();
|
||
|
||
// Regime tracking: mesh cells only apply while the photoreal (google-3d)
|
||
// stack renders. main.js re-dispatches MapStackController.onChange as this
|
||
// CustomEvent; the boot default is photoreal, matching groundFloor's initial
|
||
// preference. Module-scope listener: the module only loads in the browser
|
||
// bundle, and the subscription is idempotent for the app's lifetime.
|
||
|
||
/**
|
||
* True when a VISIBLE 3D tileset in the scene reports its streaming queue
|
||
* drained (tilesLoaded) — the mirror of cctv.js's projectionTilesReady.
|
||
* Walks top-level primitives only (a handful; once per poll).
|
||
* @param {Cesium.Scene} scene
|
||
* @returns {boolean}
|
||
*/
|
||
function _visibleTilesetLoaded(scene) {
|
||
try {
|
||
const prims = scene.primitives;
|
||
for (let i = 0; i < prims.length; i++) {
|
||
const p = prims.get(i);
|
||
if (p instanceof Cesium.Cesium3DTileset && p.show) {
|
||
return !!p.tilesLoaded;
|
||
}
|
||
}
|
||
} catch {
|
||
/* mid-teardown */
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Equirectangular distance (km) — same approximation the flights clamp uses.
|
||
*/
|
||
function _approxKm(lat1, lon1, lat2, lon2) {
|
||
const dLat = (lat2 - lat1) * 111.32;
|
||
const dLon =
|
||
(lon2 - lon1) * 111.32 * Math.cos((((lat1 + lat2) / 2) * Math.PI) / 180);
|
||
return Math.hypot(dLat, dLon);
|
||
}
|
||
|
||
/**
|
||
* Samples the rendered mesh height for the coarse cells containing `points`,
|
||
* one-shot per cell, budget-capped, viewer-proximity-gated. Call ONCE per
|
||
* layer poll (never per contact, never per frame). Synchronous — Cesium's
|
||
* sampleHeight is a CPU-side query against loaded tiles.
|
||
*
|
||
* @param {Cesium.Scene|undefined} scene - The scene (skipped when absent/torn down).
|
||
* @param {Array<{lat: number, lon: number}>} points - Contact/waypoint coords.
|
||
* @param {object} [options]
|
||
* @param {Array<object>} [options.excludeObjects] - Own billboards/models to
|
||
* exclude from the probe (Cesium matches instances/.primitive/.id — pass
|
||
* Billboard and Model instances, NOT collections).
|
||
* @param {number} [options.viewerLat] @param {number} [options.viewerLon]
|
||
* Viewer subpoint (computed once by the caller's poll).
|
||
*/
|
||
function sampleMeshFloorCells(
|
||
scene,
|
||
points,
|
||
{ excludeObjects = [], viewerLat, viewerLon } = {},
|
||
) {
|
||
if (!meshFloorPreferred()) return;
|
||
if (!scene || typeof scene.sampleHeight !== 'function') return;
|
||
if (!Array.isArray(points) || !points.length) return;
|
||
// Round-5 hardening (the CCTV projectionTilesReady lesson, ported late):
|
||
// while tiles are MID-STREAM a probe returns coarse-LOD heights — real
|
||
// numbers, wildly wrong — and the one-shot latch made them permanent.
|
||
// Only sample when the visible Google tileset reports tilesLoaded AND the
|
||
// camera is low enough that the streamed LOD near it is fine-grained.
|
||
const camH = scene.camera?.positionCartographic?.height;
|
||
if (!Number.isFinite(camH) || camH > MAX_CAMERA_HEIGHT_M) return;
|
||
if (!_visibleTilesetLoaded(scene)) return;
|
||
let sampled = 0;
|
||
const attempted = new Set();
|
||
for (const p of points) {
|
||
if (sampled >= MAX_SAMPLES_PER_CALL) break;
|
||
if (!Number.isFinite(p?.lat) || !Number.isFinite(p?.lon)) continue;
|
||
const cell = coarseFloorCoord(p.lat, p.lon);
|
||
const key = `${cell.lat},${cell.lon}`;
|
||
if (attempted.has(key)) continue;
|
||
attempted.add(key);
|
||
if (cachedMeshFloor(cell.lat, cell.lon) != null) continue; // one-shot latch
|
||
if (
|
||
Number.isFinite(viewerLat) &&
|
||
Number.isFinite(viewerLon) &&
|
||
_approxKm(viewerLat, viewerLon, cell.lat, cell.lon) > MAX_SAMPLE_DIST_KM
|
||
) {
|
||
continue; // too far: tiles not streamed there, probe would be a guaranteed miss
|
||
}
|
||
let height;
|
||
try {
|
||
const carto = Cesium.Cartographic.fromDegrees(
|
||
cell.lon,
|
||
cell.lat,
|
||
0,
|
||
_scratchProbe,
|
||
);
|
||
height = scene.sampleHeight(carto, excludeObjects);
|
||
sampled += 1;
|
||
} catch {
|
||
continue; // scene mid-teardown — try again next poll
|
||
}
|
||
if (!Number.isFinite(height)) continue; // tiles not loaded yet — no negative latch
|
||
// Round 5: a REAL Re:Earth prior is REQUIRED before any sample latches.
|
||
// - A geoid-fallback prior (cached while the proxy was failing) is a
|
||
// sea-level poison that rejected legitimate samples at elevated
|
||
// fields (AUS: mesh 138 m vs poisoned −27 m prior → dropped forever).
|
||
// - No prior at all means no way to tell a real surface from a
|
||
// coarse-LOD/rooftop/aircraft mis-hit — the old sanity-only branch
|
||
// latched a 37 m "surface" at Austin permanently. The DEM lands
|
||
// within a poll (the same warm batch that queued this cell), so
|
||
// waiting costs one poll, not correctness.
|
||
// 2026-08-21: a "provisional" tier that KEPT the reading when no prior
|
||
// existed was built here and then removed, because it was measured failing.
|
||
// Run twice against the real GPU at the same Austin apron with the proxy
|
||
// down, it recorded 122.1 m once and 20.6 m the next time — the second is a
|
||
// coarse-LOD read of ground that is really ~122 m, and it left the contact
|
||
// ~100 m under the mesh. The DEM requirement above is not a formality.
|
||
//
|
||
// Before reaching for the obvious gate: `tilesLoaded` / `allTilesLoaded` is
|
||
// NOT a resolution signal. It reports that the streaming queue has drained
|
||
// for the CURRENT camera's screen-space-error target, and a distant or high
|
||
// camera is entirely satisfied by coarse tiles — which is exactly the state
|
||
// that reported "loaded" during the measurement above. Gating harder on it
|
||
// buys nothing. A correct gate has to ask about the specific tile covering
|
||
// the sample point: walk the tileset tree to the tile containing the
|
||
// coordinate and require its `geometricError` to be below a threshold tied
|
||
// to the accuracy the caller needs, before trusting the sample. That is
|
||
// feasible and deliberately deferred — see the P2 in
|
||
// the project roadmap, which bundles it with the fly_route probe so
|
||
// one post-launch pass settles rendered-mesh sampling everywhere.
|
||
reportValidatedMeshFloorCell(cell.lat, cell.lon, height);
|
||
}
|
||
}
|
||
|
||
// re-export for callers that want one import site
|
||
|
||
const onStack = (event) => {
|
||
const id = event?.detail?.activeId;
|
||
if (id) setMeshFloorPreferred(id === 'photoreal');
|
||
};
|
||
eventTarget?.addEventListener('gev:map-stack-changed', onStack);
|
||
signal?.addEventListener(
|
||
'abort',
|
||
() => eventTarget?.removeEventListener('gev:map-stack-changed', onStack),
|
||
{ once: true },
|
||
);
|
||
return {
|
||
sampleMeshFloorCells,
|
||
cachedGroundFloor,
|
||
visibleTilesetLoaded: _visibleTilesetLoaded,
|
||
};
|
||
}
|