mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
refactor(services): use the globe engine
groundSnap.heightFor and meshFloorSampler.sampleMeshFloorCells take the globe engine: engine.surface.sampleHeight for the probe, engine.surface.tilesLoaded (and, for mesh cells, maps.photorealActive) for readiness, camera.height for the mesh sampler's altitude gate. Callers that still hold a viewer or scene (the layers until phase 1c) keep the unchanged Cesium path.
This commit is contained in:
1 parent
c2a0d20f3d
commit
be56144d75
4 files changed
+143
-20
No files matched your search
@@ -128,3 +128,43 @@ test('sampler: outside the google-3d regime nothing is sampled at all', () => {
|
||||
setMeshFloorPreferred(true);
|
||||
assert.equal(cachedMeshFloor(cell.lat, cell.lon), null);
|
||||
});
|
||||
|
||||
// --- On a globe engine (engine.surface / camera.height / maps.photorealActive)
|
||||
|
||||
async function fakeEngineAt(cell, { heightM = 900, tilesLoaded = true, photoreal = true, sample } = {}) {
|
||||
const { createFakeEngine } = await import('../globe/testing/fakeEngine.js');
|
||||
const engine = createFakeEngine({
|
||||
longitude: Cesium.Math.toRadians(cell.lon),
|
||||
latitude: Cesium.Math.toRadians(cell.lat),
|
||||
altitude: heightM,
|
||||
});
|
||||
if (photoreal) await engine.maps.setPhotorealTiles({ tileset: {} });
|
||||
engine.fake.script('tilesLoaded', tilesLoaded);
|
||||
engine.fake.script('sampleHeight', sample);
|
||||
return engine;
|
||||
}
|
||||
|
||||
test('sampler on a globe engine: samples engine.surface and latches against the prior', async () => {
|
||||
reset();
|
||||
const cell = nextSite();
|
||||
await seedDem(cell, 120.0);
|
||||
const engine = await fakeEngineAt(cell, { sample: () => 137.0 });
|
||||
sampleMeshFloorCells(engine, [cell], { viewerLat: cell.lat, viewerLon: cell.lon, excludeObjects: ['own'] });
|
||||
assert.equal(cachedMeshFloor(cell.lat, cell.lon), 137.0);
|
||||
const [call] = engine.fake.callsOf('surface.sampleHeight');
|
||||
assert.ok(Math.abs(Cesium.Math.toDegrees(call.args[0].latitude) - cell.lat) < 1e-9);
|
||||
assert.deepEqual(call.args[1], ['own']);
|
||||
});
|
||||
|
||||
test('sampler on a globe engine: a high camera, loading tiles or no photoreal tiles sample nothing', async () => {
|
||||
reset();
|
||||
const cell = nextSite();
|
||||
await seedDem(cell, 120.0);
|
||||
const opts = { viewerLat: cell.lat, viewerLon: cell.lon };
|
||||
for (const options of [{ heightM: 90_000 }, { tilesLoaded: false }, { photoreal: false }]) {
|
||||
const engine = await fakeEngineAt(cell, { ...options, sample: () => 138.0 });
|
||||
sampleMeshFloorCells(engine, [cell], opts);
|
||||
assert.equal(engine.fake.callsOf('surface.sampleHeight').length, 0, JSON.stringify(options));
|
||||
}
|
||||
assert.equal(cachedMeshFloor(cell.lat, cell.lon), null);
|
||||
});
|
||||
@@ -47,6 +47,7 @@
|
||||
*/
|
||||
|
||||
import * as geo from '../geo/index.js';
|
||||
import { isGlobeEngine } from '../globe/contract.js';
|
||||
|
||||
/** Taxi threshold: a cached snap answers directly for moves up to this far from
|
||||
* the sampled spot (m). Past it the value is demoted to a held last-known and a
|
||||
@@ -170,7 +171,7 @@ export function firstVisibleTileset(primitives) {
|
||||
/**
|
||||
* Create a per-layer ground-snap cache.
|
||||
* @returns {{
|
||||
* heightFor: (viewer: Cesium.Viewer, icao: string, pos: Cesium.Cartesian3,
|
||||
* heightFor: (target: object, icao: string, pos: Cesium.Cartesian3,
|
||||
* getExclusions?: () => Array<unknown>) => number|null,
|
||||
* forget: (icao: string) => void,
|
||||
* clear: () => void,
|
||||
@@ -281,7 +282,9 @@ export function createGroundSnap({ groundFloor }) {
|
||||
* A contact that HAS resolved once keeps that answer through a resample outage;
|
||||
* see `heldSnapM`.
|
||||
*
|
||||
* @param {Cesium.Viewer} viewer - Live viewer (sampling + tiles-ready check).
|
||||
* @param {object} target - The globe engine (`engine.surface`: sampleHeight
|
||||
* and tilesLoaded), or a live Cesium viewer from callers that still hold
|
||||
* one (scene.sampleHeight and the first visible tileset's tilesLoaded).
|
||||
* @param {string} icao - Cache key (per-layer icao space).
|
||||
* @param {Cesium.Cartesian3} pos - Current display position. Cache movement
|
||||
* is measured after projecting this input to the WGS84 ellipsoid, so a
|
||||
@@ -292,7 +295,8 @@ export function createGroundSnap({ groundFloor }) {
|
||||
* otherwise). Only invoked when a sample actually fires.
|
||||
* @returns {number|null}
|
||||
*/
|
||||
function heightFor(viewer, icao, pos, getExclusions) {
|
||||
function heightFor(target, icao, pos, getExclusions) {
|
||||
const engine = isGlobeEngine(target) ? target : null;
|
||||
const surfacePos = geo.ellipsoid.WGS84.scaleToGeodeticSurface(
|
||||
pos,
|
||||
scratchSurfacePos,
|
||||
@@ -344,7 +348,8 @@ export function createGroundSnap({ groundFloor }) {
|
||||
entries.set(icao, next);
|
||||
return heldSnapM(next, surfacePos);
|
||||
};
|
||||
if (!_tilesReady(viewer)) return miss();
|
||||
if (!(engine ? engine.surface.tilesLoaded() : _tilesReady(target)))
|
||||
return miss();
|
||||
windowCount += 1;
|
||||
let sampled;
|
||||
try {
|
||||
@@ -354,10 +359,10 @@ export function createGroundSnap({ groundFloor }) {
|
||||
scratchCarto,
|
||||
);
|
||||
// sampleHeight throws when unsupported (no depth textures) — that's a miss.
|
||||
sampled = viewer.scene.sampleHeight(
|
||||
carto,
|
||||
getExclusions ? getExclusions() : undefined,
|
||||
);
|
||||
const exclusions = getExclusions ? getExclusions() : undefined;
|
||||
sampled = engine
|
||||
? engine.surface.sampleHeight(carto, exclusions)
|
||||
: target.scene.sampleHeight(carto, exclusions);
|
||||
} catch {
|
||||
sampled = undefined;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as geo from '../geo/index.js';
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import { createGroundSnap } from './groundSnap.js';
|
||||
|
||||
const groundFloor = (reports = []) => ({
|
||||
cachedMeshFloor: () => null,
|
||||
meshFloorPreferred: () => true,
|
||||
reportValidatedMeshFloorCell: (...args) => reports.push(args),
|
||||
});
|
||||
const KAUS = geo.cartesian3.fromDegrees(-97.67, 30.19, 0);
|
||||
|
||||
test('on a globe engine a snap samples engine.surface once and caches it', () => {
|
||||
const reports = [];
|
||||
const snap = createGroundSnap({ groundFloor: groundFloor(reports) });
|
||||
const engine = createFakeEngine();
|
||||
engine.fake.script('sampleHeight', () => 138.5);
|
||||
let exclusions = 0;
|
||||
const exclude = () => {
|
||||
exclusions++;
|
||||
return ['own-model'];
|
||||
};
|
||||
assert.equal(snap.heightFor(engine, 'a1', KAUS, exclude), 138.5);
|
||||
assert.equal(snap.heightFor(engine, 'a1', KAUS, exclude), 138.5);
|
||||
const calls = engine.fake.callsOf('surface.sampleHeight');
|
||||
assert.equal(calls.length, 1, 'one sample, then the cache');
|
||||
assert.deepEqual(calls[0].args[1], ['own-model']);
|
||||
assert.equal(exclusions, 1);
|
||||
assert.ok(
|
||||
Math.abs(geo.math.toDegrees(calls[0].args[0].latitude) - 30.19) < 1e-9,
|
||||
);
|
||||
assert.equal(reports.length, 1, 'the sample feeds the mesh floor');
|
||||
});
|
||||
|
||||
test('on a globe engine loading tiles or a miss return null without a sample cache', () => {
|
||||
const snap = createGroundSnap({ groundFloor: groundFloor() });
|
||||
const engine = createFakeEngine();
|
||||
engine.fake.script('tilesLoaded', false);
|
||||
engine.fake.script('sampleHeight', () => 10);
|
||||
assert.equal(snap.heightFor(engine, 'b1', KAUS), null);
|
||||
assert.equal(engine.fake.callsOf('surface.sampleHeight').length, 0);
|
||||
engine.fake.script('tilesLoaded', true);
|
||||
engine.fake.script('sampleHeight', () => undefined);
|
||||
assert.equal(snap.heightFor(engine, 'c1', KAUS), null);
|
||||
assert.equal(engine.fake.callsOf('surface.sampleHeight').length, 1);
|
||||
assert.equal(
|
||||
snap.heightFor(engine, 'c1', KAUS),
|
||||
null,
|
||||
'backoff after a miss',
|
||||
);
|
||||
assert.equal(engine.fake.callsOf('surface.sampleHeight').length, 1);
|
||||
});
|
||||
@@ -1,4 +1,6 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import * as geo from '../geo/index.js';
|
||||
import { isGlobeEngine } from '../globe/contract.js';
|
||||
|
||||
/** Construct an instance-owned meshFloorSampler service with explicit dependencies. */
|
||||
export function createMeshFloorSampler({
|
||||
@@ -50,6 +52,7 @@ export function createMeshFloorSampler({
|
||||
const MAX_CAMERA_HEIGHT_M = 25_000;
|
||||
/** @type {Cesium.Cartographic} Scratch for probe coordinates. */
|
||||
const _scratchProbe = new Cesium.Cartographic();
|
||||
const _scratchEngineProbe = geo.cartographic.create();
|
||||
|
||||
// Regime tracking: mesh cells only apply while the photoreal (google-3d)
|
||||
// stack renders. main.js re-dispatches MapStackController.onChange as this
|
||||
@@ -95,7 +98,9 @@ export function createMeshFloorSampler({
|
||||
* 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 {object|undefined} target - The globe engine (engine.surface,
|
||||
* camera.height, maps.photorealActive), or the Cesium scene from callers
|
||||
* that still hold one (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
|
||||
@@ -105,21 +110,31 @@ export function createMeshFloorSampler({
|
||||
* Viewer subpoint (computed once by the caller's poll).
|
||||
*/
|
||||
function sampleMeshFloorCells(
|
||||
scene,
|
||||
target,
|
||||
points,
|
||||
{ excludeObjects = [], viewerLat, viewerLon } = {},
|
||||
) {
|
||||
if (!meshFloorPreferred()) return;
|
||||
if (!scene || typeof scene.sampleHeight !== 'function') return;
|
||||
const engine = isGlobeEngine(target) ? target : null;
|
||||
const scene = engine ? null : target;
|
||||
if (engine ? !engine.surface.supported : !scene) return;
|
||||
if (!engine && 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;
|
||||
const camH = engine
|
||||
? engine.camera.height()
|
||||
: scene.camera?.positionCartographic?.height;
|
||||
if (!Number.isFinite(camH) || camH > MAX_CAMERA_HEIGHT_M) return;
|
||||
if (!_visibleTilesetLoaded(scene)) return;
|
||||
if (
|
||||
!(engine
|
||||
? engine.maps.photorealActive() && engine.surface.tilesLoaded()
|
||||
: _visibleTilesetLoaded(scene))
|
||||
)
|
||||
return;
|
||||
let sampled = 0;
|
||||
const attempted = new Set();
|
||||
for (const p of points) {
|
||||
@@ -139,13 +154,23 @@ export function createMeshFloorSampler({
|
||||
}
|
||||
let height;
|
||||
try {
|
||||
const carto = Cesium.Cartographic.fromDegrees(
|
||||
cell.lon,
|
||||
cell.lat,
|
||||
0,
|
||||
_scratchProbe,
|
||||
);
|
||||
height = scene.sampleHeight(carto, excludeObjects);
|
||||
if (engine) {
|
||||
const carto = geo.cartographic.fromDegrees(
|
||||
cell.lon,
|
||||
cell.lat,
|
||||
0,
|
||||
_scratchEngineProbe,
|
||||
);
|
||||
height = engine.surface.sampleHeight(carto, excludeObjects);
|
||||
} else {
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user