mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +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);
|
setMeshFloorPreferred(true);
|
||||||
assert.equal(cachedMeshFloor(cell.lat, cell.lon), null);
|
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 * 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
|
/** 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
|
* 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.
|
* Create a per-layer ground-snap cache.
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* heightFor: (viewer: Cesium.Viewer, icao: string, pos: Cesium.Cartesian3,
|
* heightFor: (target: object, icao: string, pos: Cesium.Cartesian3,
|
||||||
* getExclusions?: () => Array<unknown>) => number|null,
|
* getExclusions?: () => Array<unknown>) => number|null,
|
||||||
* forget: (icao: string) => void,
|
* forget: (icao: string) => void,
|
||||||
* clear: () => void,
|
* clear: () => void,
|
||||||
@@ -281,7 +282,9 @@ export function createGroundSnap({ groundFloor }) {
|
|||||||
* A contact that HAS resolved once keeps that answer through a resample outage;
|
* A contact that HAS resolved once keeps that answer through a resample outage;
|
||||||
* see `heldSnapM`.
|
* 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 {string} icao - Cache key (per-layer icao space).
|
||||||
* @param {Cesium.Cartesian3} pos - Current display position. Cache movement
|
* @param {Cesium.Cartesian3} pos - Current display position. Cache movement
|
||||||
* is measured after projecting this input to the WGS84 ellipsoid, so a
|
* 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.
|
* otherwise). Only invoked when a sample actually fires.
|
||||||
* @returns {number|null}
|
* @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(
|
const surfacePos = geo.ellipsoid.WGS84.scaleToGeodeticSurface(
|
||||||
pos,
|
pos,
|
||||||
scratchSurfacePos,
|
scratchSurfacePos,
|
||||||
@@ -344,7 +348,8 @@ export function createGroundSnap({ groundFloor }) {
|
|||||||
entries.set(icao, next);
|
entries.set(icao, next);
|
||||||
return heldSnapM(next, surfacePos);
|
return heldSnapM(next, surfacePos);
|
||||||
};
|
};
|
||||||
if (!_tilesReady(viewer)) return miss();
|
if (!(engine ? engine.surface.tilesLoaded() : _tilesReady(target)))
|
||||||
|
return miss();
|
||||||
windowCount += 1;
|
windowCount += 1;
|
||||||
let sampled;
|
let sampled;
|
||||||
try {
|
try {
|
||||||
@@ -354,10 +359,10 @@ export function createGroundSnap({ groundFloor }) {
|
|||||||
scratchCarto,
|
scratchCarto,
|
||||||
);
|
);
|
||||||
// sampleHeight throws when unsupported (no depth textures) — that's a miss.
|
// sampleHeight throws when unsupported (no depth textures) — that's a miss.
|
||||||
sampled = viewer.scene.sampleHeight(
|
const exclusions = getExclusions ? getExclusions() : undefined;
|
||||||
carto,
|
sampled = engine
|
||||||
getExclusions ? getExclusions() : undefined,
|
? engine.surface.sampleHeight(carto, exclusions)
|
||||||
);
|
: target.scene.sampleHeight(carto, exclusions);
|
||||||
} catch {
|
} catch {
|
||||||
sampled = undefined;
|
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 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. */
|
/** Construct an instance-owned meshFloorSampler service with explicit dependencies. */
|
||||||
export function createMeshFloorSampler({
|
export function createMeshFloorSampler({
|
||||||
@@ -50,6 +52,7 @@ export function createMeshFloorSampler({
|
|||||||
const MAX_CAMERA_HEIGHT_M = 25_000;
|
const MAX_CAMERA_HEIGHT_M = 25_000;
|
||||||
/** @type {Cesium.Cartographic} Scratch for probe coordinates. */
|
/** @type {Cesium.Cartographic} Scratch for probe coordinates. */
|
||||||
const _scratchProbe = new Cesium.Cartographic();
|
const _scratchProbe = new Cesium.Cartographic();
|
||||||
|
const _scratchEngineProbe = geo.cartographic.create();
|
||||||
|
|
||||||
// Regime tracking: mesh cells only apply while the photoreal (google-3d)
|
// Regime tracking: mesh cells only apply while the photoreal (google-3d)
|
||||||
// stack renders. main.js re-dispatches MapStackController.onChange as this
|
// 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
|
* layer poll (never per contact, never per frame). Synchronous — Cesium's
|
||||||
* sampleHeight is a CPU-side query against loaded tiles.
|
* 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 {Array<{lat: number, lon: number}>} points - Contact/waypoint coords.
|
||||||
* @param {object} [options]
|
* @param {object} [options]
|
||||||
* @param {Array<object>} [options.excludeObjects] - Own billboards/models to
|
* @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).
|
* Viewer subpoint (computed once by the caller's poll).
|
||||||
*/
|
*/
|
||||||
function sampleMeshFloorCells(
|
function sampleMeshFloorCells(
|
||||||
scene,
|
target,
|
||||||
points,
|
points,
|
||||||
{ excludeObjects = [], viewerLat, viewerLon } = {},
|
{ excludeObjects = [], viewerLat, viewerLon } = {},
|
||||||
) {
|
) {
|
||||||
if (!meshFloorPreferred()) return;
|
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;
|
if (!Array.isArray(points) || !points.length) return;
|
||||||
// Round-5 hardening (the CCTV projectionTilesReady lesson, ported late):
|
// Round-5 hardening (the CCTV projectionTilesReady lesson, ported late):
|
||||||
// while tiles are MID-STREAM a probe returns coarse-LOD heights — real
|
// while tiles are MID-STREAM a probe returns coarse-LOD heights — real
|
||||||
// numbers, wildly wrong — and the one-shot latch made them permanent.
|
// numbers, wildly wrong — and the one-shot latch made them permanent.
|
||||||
// Only sample when the visible Google tileset reports tilesLoaded AND the
|
// Only sample when the visible Google tileset reports tilesLoaded AND the
|
||||||
// camera is low enough that the streamed LOD near it is fine-grained.
|
// 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 (!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;
|
let sampled = 0;
|
||||||
const attempted = new Set();
|
const attempted = new Set();
|
||||||
for (const p of points) {
|
for (const p of points) {
|
||||||
@@ -139,13 +154,23 @@ export function createMeshFloorSampler({
|
|||||||
}
|
}
|
||||||
let height;
|
let height;
|
||||||
try {
|
try {
|
||||||
const carto = Cesium.Cartographic.fromDegrees(
|
if (engine) {
|
||||||
cell.lon,
|
const carto = geo.cartographic.fromDegrees(
|
||||||
cell.lat,
|
cell.lon,
|
||||||
0,
|
cell.lat,
|
||||||
_scratchProbe,
|
0,
|
||||||
);
|
_scratchEngineProbe,
|
||||||
height = scene.sampleHeight(carto, excludeObjects);
|
);
|
||||||
|
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;
|
sampled += 1;
|
||||||
} catch {
|
} catch {
|
||||||
continue; // scene mid-teardown — try again next poll
|
continue; // scene mid-teardown — try again next poll
|
||||||
|
|||||||
Reference in new issue
Block a user