Merge pull request #1642 from bilawalsidhu/sameh/transit-floor-fix

Keep vehicles off rooftops and bridge decks by reading the 3D-tile surface where each vehicle is
This commit is contained in:
Sameh Khamis authored and GitHub committed 2026-11-09 15:12:25 -08:00
commit da134328ef
14 files changed
+521 -34

No files matched your search

@@ -0,0 +1,7 @@
- Keep buses and trains off rooftops and out of bridge decks on the photoreal
3D tiles: the ground floor probes the rendered mesh where each vehicle is,
keeps each reading at the point it was taken (up to sixteen a cell) and
answers from the reading nearest the vehicle, instead of one probe at each
110 m cell's centre that lifted every vehicle in the cell onto whatever lay
there. Transit probes each vehicle's latest point, and grounded flights read
the floor at their own position (Sameh Khamis, @samehkhamis, #1642).
+25 -1
View File
@@ -1347,6 +1347,30 @@ and snapshot source. The standalone adapter keeps the existing USGS daily feed,
M2.5+ filtering, static discs, magnitude labels and analyst records. Disabling or
destroying the layer cancels pending work and ignores late results.
## Rendered-mesh floor readings — November 9, 2026
On the photoreal stack the shared floor (`cachedGroundFloor`) answers from
rendered-mesh readings per coarse cell (0.001°, about 110 × 80 m), sampled by
`src/services/meshFloorSampler.js` and gated against the DEM by
`reportValidatedMeshFloorCell` in `src/services/groundFloor.js`. The probe goes
where the caller's point is (a vehicle or contact), not to the cell's centre;
the sampler reads a cell at up to eight spots about 12–17 m or more apart, and
each reading that passes the gate is kept at its point (ground snaps add theirs
the same way; a cell keeps at most sixteen). A read answers from the cell's
reading nearest the asked point, so a viaduct and the street beside it in one
cell keep their own heights; two readings within about 5 m of each other keep
the lower. A spot refused only because the cell's DEM prior was cold is tried
again later. Transit offers the sampler the latest point of each vehicle's path
in a cell (its newest fix in its own cell), and the grounded-flight display
clamp reads at the contact, held inside its sticky cell. Before this, one probe
at the cell's centre latched for the session, so a centre on a roof lifted
everything in the cell (a Haymarket bus drew 23.5 m above the street, on the
garage roof) and a centre in the river put Red Line trains on the Longfellow
Bridge 6–9 m under the deck. Residual: the probe is vertical, so a vehicle
directly under a deck or a roof overhang reads the structure above it, and a
vehicle between readings takes the nearest one, so its floor steps where the
nearest reading changes.
## Vessel components and sources
`src/data/aisLiveVessels.js` assembles `createVesselLayer` from the
@@ -3148,7 +3172,7 @@ This is the current runtime/source-of-truth snapshot for the project.
> `HELD_SNAP_CONTRADICTION_M` (5 m) ABOVE the held value drops the hold and the
> contact is COLD again, back to the floored 2D billboard. The rule is
> ONE-SIDED on purpose — it targets BURIAL. A cell reading BELOW a real sample
> is the floor chain's expected under-read (one-shot latch over ~111 m,
> is the floor chain's expected under-read (a few point readings over ~111 m,
> neighbours lean lowest, `displayFloorHeightM` only ever raises), not evidence
> of anything, and a two-sided cut was disproved on the track rig by a planted
> cell 66.7 m below a real sample at the same spot. Accepted residual, stated
+35
View File
@@ -20,9 +20,11 @@ import { setMilitaryLayerActive } from './militaryRegistry.js';
import {
GROUND_FLOOR_LIFT_M,
reportMeshFloorCell,
reportValidatedMeshFloorCell,
setMeshFloorPreferred,
_clearMeshFloorCellsForTest,
} from './groundFloor.js';
import { resolveEllipsoidalGround } from './terrainHeights.js';
import {
_setTrackedOverlayHostForTest,
destroyTrackedReadout,
@@ -1181,6 +1183,39 @@ test('display floor: a grounded contact that drifted onto a higher cell is lifte
assert.ok(Math.abs(Cesium.Math.toDegrees(c.longitude) + 97.6604) < 1e-6);
});
test('display floor: reads the reading nearest the contact, not the cell centre', async () => {
_clearDisplayFloorStateForTest();
_clearMeshFloorCellsForTest();
setMeshFloorPreferred(true);
const realFetch = globalThis.fetch;
globalThis.fetch = async () =>
new Response(JSON.stringify({ results: [{ ellipsoid: 120 }] }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
try {
await resolveEllipsoidalGround([{ lat: 30.317, lon: -97.717 }]);
} finally {
globalThis.fetch = realFetch;
}
// A hangar roof over the cell's centre; the apron where the contact is.
assert.equal(reportValidatedMeshFloorCell(30.317, -97.717, 142), true);
assert.equal(reportValidatedMeshFloorCell(30.3173, -97.7173, 121), true);
const pos = Cesium.Cartesian3.fromDegrees(-97.7173, 30.3173, 100);
const out = _floorGroundedDisplayPositionForTest(
{ onGround: true },
pos,
false,
);
const c = _floorCarto(out);
assert.ok(
Math.abs(c.height - (121 + GROUND_FLOOR_LIFT_M)) < 0.05,
`height ${c.height} should be the apron's 121 + lift`,
);
_clearMeshFloorCellsForTest();
_clearDisplayFloorStateForTest();
});
test('display floor: an AIRBORNE contact over the same cell is never lifted', () => {
_clearDisplayFloorStateForTest();
_clearMeshFloorCellsForTest();
+1
View File
@@ -22,6 +22,7 @@ export const {
reportMeshFloorCell,
meshFloorSampleWithinPrior,
reportValidatedMeshFloorCell,
meshFloorPriorReady,
cachedMeshFloor,
_clearMeshFloorCellsForTest,
cachedGroundFloor,
+178 -1
View File
@@ -24,7 +24,7 @@ import { sampleMeshFloorCells } from './meshFloorSampler.js';
import { resolveEllipsoidalGround } from './terrainHeights.js';
import {
cachedGroundFloor, cachedMeshFloor, setMeshFloorPreferred,
_clearMeshFloorCellsForTest,
_clearMeshFloorCellsForTest, reportValidatedMeshFloorCell,
} from './groundFloor.js';
/** Distinct site per test — terrainHeights' cache is module-global and warm
@@ -137,3 +137,180 @@ test('sampler: a cell just across the antimeridian is still near the viewer', ()
sampleMeshFloorCells(scene, [{ lat: -16.81, lon: 179.999 }], { viewerLat: -16.81, viewerLon: -179.999 });
assert.equal(calls, 2, 'about 200 m apart, so both cells are probed');
});
test('sampler: the probe goes where the point is, not to the cell centre', async () => {
reset();
const cell = nextSite();
await seedDem(cell, 120.0);
const asked = [];
const scene = fakeScene((carto) => {
asked.push(Cesium.Math.toDegrees(carto.longitude));
return 122.0;
});
const point = { lat: cell.lat + 0.0003, lon: cell.lon + 0.0004 };
sampleMeshFloorCells(scene, [point], { viewerLat: cell.lat, viewerLon: cell.lon });
assert.equal(asked.length, 1);
assert.ok(Math.abs(asked[0] - point.lon) < 1e-9, `probed at ${asked[0]}, not ${point.lon}`);
});
test('sampler: a viaduct and the street beside it in one cell keep their own heights', async () => {
reset();
const cell = nextSite();
await seedDem(cell, 120.0);
// A viaduct deck 9 m up along one line of the cell, the street elsewhere.
const deckLat = cell.lat + 0.0003;
let calls = 0;
const scene = fakeScene((carto) => {
calls += 1;
return Math.abs(Cesium.Math.toDegrees(carto.latitude) - deckLat) < 1e-7 ? 129.0 : 121.0;
});
const opts = { viewerLat: cell.lat, viewerLon: cell.lon };
const onDeck = { lat: deckLat, lon: cell.lon };
const street = { lat: cell.lat - 0.0003, lon: cell.lon };
sampleMeshFloorCells(scene, [onDeck], opts);
sampleMeshFloorCells(scene, [street], opts);
assert.equal(calls, 2);
assert.equal(cachedMeshFloor(onDeck.lat, onDeck.lon + 0.0001), 129.0, 'the train stays on the deck');
assert.equal(cachedMeshFloor(street.lat, street.lon - 0.0001), 121.0, 'the bus stays on the street');
assert.equal(cachedGroundFloor(street.lat, street.lon), 121.0);
sampleMeshFloorCells(scene, [{ lat: deckLat, lon: cell.lon + 0.00001 }], opts);
assert.equal(calls, 2, 'a point next to one already probed is not probed again');
});
test('sampler: a roof over the cell centre does not lift a bus on the street', async () => {
reset();
const cell = nextSite();
await seedDem(cell, 120.0);
// A roof 22 m up covering the cell's centre; the street 30 m west of it.
const scene = fakeScene((carto) =>
Math.abs(Cesium.Math.toDegrees(carto.longitude) - cell.lon) < 0.0001 ? 142.0 : 121.0);
const bus = { lat: cell.lat, lon: cell.lon - 0.0003 };
sampleMeshFloorCells(scene, [bus], { viewerLat: cell.lat, viewerLon: cell.lon });
assert.equal(cachedGroundFloor(bus.lat, bus.lon), 121.0);
});
test('sampler: a second reading at the same spot keeps the lower one', async () => {
reset();
const cell = nextSite();
await seedDem(cell, 120.0);
assert.equal(reportValidatedMeshFloorCell(cell.lat, cell.lon, 131.0), true);
assert.equal(reportValidatedMeshFloorCell(cell.lat + 0.00002, cell.lon, 122.0), true);
assert.equal(cachedMeshFloor(cell.lat, cell.lon), 122.0);
assert.equal(reportValidatedMeshFloorCell(cell.lat, cell.lon + 0.00002, 140.0), true);
assert.equal(cachedMeshFloor(cell.lat, cell.lon), 122.0, 'a higher one never raises it');
});
test('sampler: a cell with a reading is probed a few times at most', async () => {
reset();
const cell = nextSite();
await seedDem(cell, 120.0);
let calls = 0;
const scene = fakeScene(() => {
calls += 1;
return 121.0;
});
const opts = { viewerLat: cell.lat, viewerLon: cell.lon };
for (let i = 0; i < 20; i++)
sampleMeshFloorCells(
scene,
[{ lat: cell.lat - 0.0004 + (i % 5) * 0.0002, lon: cell.lon - 0.00045 + Math.floor(i / 5) * 0.0003 }],
opts,
);
assert.equal(calls, 8);
});
test('sampler: a cell with no accepted reading is probed again on every call', async () => {
reset();
const cell = nextSite();
let calls = 0;
const scene = fakeScene(() => {
calls += 1;
return 138.0;
});
const opts = { viewerLat: cell.lat, viewerLon: cell.lon };
for (let i = 0; i < 3; i++) sampleMeshFloorCells(scene, [cell], opts);
assert.equal(calls, 3, 'no DEM prior yet, so nothing is kept and the cell is retried');
await seedDem(cell, 120.0);
sampleMeshFloorCells(scene, [cell], opts);
assert.equal(cachedMeshFloor(cell.lat, cell.lon), 138.0, 'and lands once the prior is warm');
});
test('sampler: a spot whose reading is refused does not use up the cell', async () => {
reset();
const cell = nextSite();
await seedDem(cell, 120.0);
// A tower 260 m up where the first vehicle waits; the street elsewhere and
// a deck 9 m up along one line of the cell.
const towerLon = cell.lon + 0.0004;
const deckLat = cell.lat + 0.0003;
const scene = fakeScene((carto) => {
if (Math.abs(Cesium.Math.toDegrees(carto.longitude) - towerLon) < 1e-7) return 380.0;
return Math.abs(Cesium.Math.toDegrees(carto.latitude) - deckLat) < 1e-7 ? 129.0 : 121.0;
});
const opts = { viewerLat: cell.lat, viewerLon: cell.lon };
for (let i = 0; i < 10; i++) sampleMeshFloorCells(scene, [{ lat: cell.lat, lon: towerLon }], opts);
assert.equal(cachedMeshFloor(cell.lat, cell.lon), null, 'the tower is refused every time');
sampleMeshFloorCells(scene, [{ lat: cell.lat - 0.0003, lon: cell.lon }], opts);
sampleMeshFloorCells(scene, [{ lat: deckLat, lon: cell.lon - 0.0003 }], opts);
assert.equal(cachedMeshFloor(deckLat, cell.lon - 0.0003), 129.0, 'the deck is still probed');
});
test('ground floor: a cell keeps at most sixteen readings', async () => {
reset();
const cell = nextSite();
await seedDem(cell, 120.0);
for (let i = 0; i < 40; i++) {
const lat = cell.lat - 0.00045 + (i % 8) * 0.0001;
const lon = cell.lon - 0.00045 + Math.floor(i / 8) * 0.0002;
reportValidatedMeshFloorCell(lat, lon, 121.0 + i);
}
// Readings 0..15 are kept; reading 39 (at the far corner) is not, so that
// spot answers from the nearest kept one.
assert.equal(cachedMeshFloor(cell.lat - 0.00045, cell.lon - 0.00045), 121.0);
assert.equal(cachedMeshFloor(cell.lat + 0.00025, cell.lon + 0.00035), 121.0 + 15);
});
test('sampler: a spot refused while the DEM was cold is probed again once it is warm', async () => {
reset();
const cell = nextSite();
const deckLat = cell.lat + 0.0003;
let calls = 0;
const scene = fakeScene((carto) => {
calls += 1;
return Math.abs(Cesium.Math.toDegrees(carto.latitude) - deckLat) < 1e-7 ? 129.0 : 121.0;
});
const opts = { viewerLat: cell.lat, viewerLon: cell.lon };
const onDeck = { lat: deckLat, lon: cell.lon };
sampleMeshFloorCells(scene, [onDeck], opts);
assert.equal(cachedMeshFloor(onDeck.lat, onDeck.lon), null, 'no prior, nothing kept');
await seedDem(cell, 120.0);
sampleMeshFloorCells(scene, [{ lat: cell.lat - 0.0003, lon: cell.lon }], opts);
sampleMeshFloorCells(scene, [onDeck], opts);
assert.equal(calls, 3);
assert.equal(cachedMeshFloor(onDeck.lat, onDeck.lon), 129.0);
});
test('sampler: a spot refused while the DEM prior was evicted is probed again', async () => {
const { createGroundFloor } = await import('../services/groundFloor.js');
const { createMeshFloorSampler } = await import('../services/meshFloorSampler.js');
let prior = 120.0;
const terrain = {
cachedRealEllipsoidalGround: () => prior,
resolveEllipsoidalGround: async () => {},
};
const groundFloor = createGroundFloor({ terrain });
const sampler = createMeshFloorSampler({ groundFloor });
const cell = { lat: 41.5, lon: -97.66 };
const deckLat = cell.lat + 0.0003;
const scene = fakeScene((carto) =>
Math.abs(Cesium.Math.toDegrees(carto.latitude) - deckLat) < 1e-7 ? 129.0 : 121.0);
const opts = { viewerLat: cell.lat, viewerLon: cell.lon };
const onDeck = { lat: deckLat, lon: cell.lon };
sampler.sampleMeshFloorCells(scene, [{ lat: cell.lat - 0.0003, lon: cell.lon }], opts);
prior = null; // the cell's DEM entry is evicted; the street reading stays
sampler.sampleMeshFloorCells(scene, [onDeck], opts);
assert.equal(groundFloor.cachedMeshFloor(onDeck.lat, onDeck.lon), 121.0);
prior = 120.0;
sampler.sampleMeshFloorCells(scene, [onDeck], opts);
assert.equal(groundFloor.cachedMeshFloor(onDeck.lat, onDeck.lon), 129.0);
});
+1 -1
View File
@@ -448,7 +448,7 @@ export function createGeometry({ state: layerState, services, parts, source }) {
}
// Photoreal regime. Sampling is delegated to the shared coarse-cell
// sampler. It remains event-driven, one-shot per cell, and keeps its
// sampler. It remains event-driven, a few probes per cell, and keeps its
// existing acceptance window; CCTV adds no rooftop rejection policy.
if (sampleGround && parts.model.projectionTilesReady()) {
record.groundMeshSampleRequestCount =
+24 -5
View File
@@ -37,6 +37,22 @@ import {
DISPLAY_CORRIDOR_FAIR_SHARE,
} from './policy.js';
/** Half a coarse floor cell (0.001°), less a margin so rounding keeps a
* clamped coordinate inside the cell. */
const HALF_CELL_DEG = 0.00049;
/**
* A coordinate held within half a cell of the cell's centre (the short way
* round for longitude), so a read lands in the given cell.
* @param {number} deg @param {number} centreDeg
* @returns {number}
*/
function clampToCell(deg, centreDeg) {
let d = deg - centreDeg;
d -= 360 * Math.round(d / 360);
return centreDeg + Math.max(-HALF_CELL_DEG, Math.min(HALF_CELL_DEG, d));
}
export function createMotion({
flightState,
services,
@@ -865,12 +881,15 @@ export function createMotion({
);
// Boundary hysteresis: a position jittering across a cell edge would flip
// floors at fleet-tick rate (see stickyFloorCell).
const cell = stickyFloorCell(
Cesium.Math.toDegrees(carto.latitude),
Cesium.Math.toDegrees(carto.longitude),
state?.cell,
const latDeg = Cesium.Math.toDegrees(carto.latitude);
const lonDeg = Cesium.Math.toDegrees(carto.longitude);
const cell = stickyFloorCell(latDeg, lonDeg, state?.cell);
// A cell holds rendered-surface readings at the points they were taken,
// so the floor is read where the contact is, held inside its sticky cell.
const floor = cachedGroundFloor(
clampToCell(latDeg, cell.lat),
clampToCell(lonDeg, cell.lon),
);
const floor = cachedGroundFloor(cell.lat, cell.lon);
const next = state || {
cell,
in: new Cesium.Cartesian3(),
+2 -2
View File
@@ -300,8 +300,8 @@ export function createFlightSnapshotRenderer({
// floor cells collected in the loop (fire-and-forget, single-flight;
// read synchronously by NEXT poll's clamp — the military-layer pattern).
warmGroundFloor(floorWarmPoints);
// Round 4: sample the RENDERED mesh for those same cells (one-shot per
// cell, budget-capped, viewer-proximate, google-3d regime only). Own
// Round 4: sample the RENDERED mesh for those same cells (a few probes
// per cell, budget-capped, viewer-proximate, google-3d regime only). Own
// billboards/models are excluded so a vertical probe can't land on an
// aircraft instead of the pavement.
sampleMeshFloorCells(flightState._viewer?.scene, floorWarmPoints, {
+2 -2
View File
@@ -228,8 +228,8 @@ export function createMilitarySnapshotRenderer({
const bbStale = flightState._billboards.get(icao24);
if (bbStale) bbStale.position = liftedPos;
}
// Round 4: sample the RENDERED mesh for those cells (one-shot per cell,
// budget-capped, viewer-proximate, google-3d regime only), excluding
// Round 4: sample the RENDERED mesh for those cells (a few probes per
// cell, budget-capped, viewer-proximate, google-3d regime only), excluding
// this layer's own billboards/models from the probe.
{
const viewerCarto =
+15 -1
View File
@@ -124,7 +124,14 @@ export function createHeight({ state, services, parts }) {
const cells = new Map();
const collect = (point, answer) => {
if (!onGround) return;
if (!cells.has(answer.cell))
// A cell keeps the last point offered in it: the path is walked
// oldest first, so that is where the vehicle is now or about to be,
// and the mesh sampler reads the surface there.
const held = cells.get(answer.cell);
if (held) {
held.lat = point.lat;
held.lon = point.lon;
} else
cells.set(answer.cell, {
lat: point.lat,
lon: point.lon,
@@ -136,6 +143,13 @@ export function createHeight({ state, services, parts }) {
const newest = entry.fixes.at(-1);
const answer = requestHeight(newest.lat, newest.lon, entry);
entry.heightCell = answer.cell;
// The selected vehicle's path also offers its history, newest segment
// first; the vehicle's own cell is read where its newest fix is.
const own = cells.get(answer.cell);
if (own) {
own.lat = newest.lat;
own.lon = newest.lon;
}
if (answer.height !== null) {
entry.heightM = answer.height;
entry.heightResolved = true;
+108
View File
@@ -2938,6 +2938,114 @@ test('mesh refinement reaches all 100 retained DEM cells within five poll budget
assert.equal(probed.size, 100);
});
test('mesh refinement probes the latest point the path offers in a cell', async (t) => {
const { createHeight } = await import('./height.js');
// Three points along one cell's stretch of path, oldest first.
const path = [
{ lat: 42.3611, lon: -71.0751 },
{ lat: 42.3612, lon: -71.0753 },
{ lat: 42.3614, lon: -71.0754 },
];
const entry = { key: 'train', to: path[0], fixes: [path.at(-1)] };
const probed = [];
const state = {
_enabled: true,
_selectedKey: null,
_visible: new Set([entry]),
_vehicles: new Map([[entry.key, entry]]),
_floorCursor: 0,
_heightDirty: new Set(),
_viewer: { camera: { positionCartographic: { height: 100 } } },
};
const height = createHeight({
state,
services: {
render: { governorRequestRender() {} },
ground: {
coarseFloorCoord: (lat, lon) => ({
lat: Number(lat.toFixed(3)),
lon: Number(lon.toFixed(3)),
}),
cachedGroundFloor: () => 10,
GROUND_FLOOR_LIFT_M: 1.5,
},
mesh: {
sampleMeshFloorCells(scene, points) {
probed.push(...points.map((p) => [p.lat, p.lon]));
},
},
},
parts: {
trails: {
prepareEntry(e, collect) {
if (collect)
for (const point of path)
collect(point, height.requestHeight(point.lat, point.lon));
},
},
rendering: { requestVisibility() {} },
},
});
t.after(() => height.clear());
height.anchorFloors();
assert.deepEqual(probed, [[42.3614, -71.0754]]);
});
test('the selected vehicle is probed at its newest fix in its own cell', async (t) => {
const { createHeight } = await import('./height.js');
// The selected path offers its active corridor first, then its history
// newest segment first, so the last offer in the cell is an older point.
const newest = { lat: 42.3614, lon: -71.0754 };
const offers = [
newest,
{ lat: 42.3612, lon: -71.0753 },
{ lat: 42.3611, lon: -71.0751 },
];
const entry = { key: 'train', to: offers[0], fixes: [newest] };
const probed = [];
const state = {
_enabled: true,
_selectedKey: entry.key,
_visible: new Set([entry]),
_vehicles: new Map([[entry.key, entry]]),
_floorCursor: 0,
_heightDirty: new Set(),
_viewer: { camera: { positionCartographic: { height: 100 } } },
};
const height = createHeight({
state,
services: {
render: { governorRequestRender() {} },
ground: {
coarseFloorCoord: (lat, lon) => ({
lat: Number(lat.toFixed(3)),
lon: Number(lon.toFixed(3)),
}),
cachedGroundFloor: () => 10,
GROUND_FLOOR_LIFT_M: 1.5,
},
mesh: {
sampleMeshFloorCells(scene, points) {
probed.push(...points.map((p) => [p.lat, p.lon]));
},
},
},
parts: {
trails: {
prepareEntry(e, collect) {
if (collect)
for (const point of offers)
collect(point, height.requestHeight(point.lat, point.lon));
},
},
rendering: { requestVisibility() {} },
},
});
t.after(() => height.clear());
height.anchorFloors();
assert.deepEqual(probed, [[42.3614, -71.0754]]);
});
test('selection churn releases past geometry on every retained vehicle', async (t) => {
const app = harness(t, { floorAt: () => 10 });
const { mergeHistory, seek } = await import('../../data/contactPlayback.js');
+69 -12
View File
@@ -418,13 +418,26 @@ export function createGroundFloor({ terrain, signal }) {
// is the photogrammetric MESH, which sits above it (measured ~17 m at the
// Austin airport apron). DEM-flooring therefore still buried sprites/trails
// inside the mesh. These cells hold the RENDERED surface height, sampled
// one-shot per coarse cell near the viewer (scene.sampleHeight — the same
// at the callers' points, a few per coarse cell near the viewer, each kept
// where it was taken and read back from the nearest (scene.sampleHeight — the same
// source groundSnap uses, which is why 3D models always looked right) and
// reported here by meshFloorSampler.js. Design principle (owner): NEVER
// below the visible surface; slightly above is always fine. The DEM stays
// the instant global prior/fallback and the sanity gate for samples.
/** @type {Map<string, number>} coarse cell key -> rendered-surface height (m, ellipsoidal). */
/** @type {Map<string, Array<{lat: number, lon: number, h: number}>>}
* coarse cell key -> rendered-surface readings (m, ellipsoidal), each kept
* at the point it was probed. A read answers from the reading nearest the
* asked point, so a viaduct and the street beside it in one cell keep their
* own heights. */
const _meshCells = new Map();
/** @constant {number} Readings closer than this (degrees, about 5 m) are
* one spot: the lower one is kept there. */
const MESH_READING_SAME_SPOT_DEG = 0.00005;
/** @constant {number} Readings kept per cell. The sampler probes a cell at
* most eight times, but ground snaps report at each aircraft's position;
* past this many, a reading away from the others is not kept, so a busy
* apron cell stays a short list to scan. */
const MESH_READINGS_PER_CELL = 16;
/** @type {boolean} Whether mesh cells apply — true only in the google-3d
* regime (photoreal stack). On globe stacks the rendered surface IS the
* terrain provider (Re:Earth on keyless), so the DEM is already correct
@@ -444,7 +457,8 @@ export function createGroundFloor({ terrain, signal }) {
/**
* Records a sampled rendered-surface height for the cell containing the
* coordinate (one-shot latch — first accepted sample wins).
* coordinate, unchecked, when the cell has no reading yet. The sampler goes
* through `reportValidatedMeshFloorCell`, which keeps a reading per point.
* @param {number} lat @param {number} lon @param {number} heightM
*/
function reportMeshFloorCell(lat, lon, heightM) {
@@ -456,7 +470,7 @@ export function createGroundFloor({ terrain, signal }) {
return;
const c = coarseFloorCoord(lat, lon);
const key = `${c.lat},${c.lon}`;
if (!_meshCells.has(key)) _meshCells.set(key, heightM);
if (!_meshCells.has(key)) _meshCells.set(key, [{ lat, lon, h: heightM }]);
}
/** Return whether a rendered-mesh sample is plausible against a real DEM prior. */
@@ -471,9 +485,11 @@ export function createGroundFloor({ terrain, signal }) {
/**
* Validate and record a rendered-mesh floor through the single shared gate.
* Callers must not bypass this with raw `reportMeshFloorCell`: a coarse-LOD,
* rooftop, or aircraft hit would otherwise win the session-long first-write
* latch and poison every floor consumer in the cell.
* Callers must not bypass this with raw `reportMeshFloorCell`: a coarse-LOD
* or aircraft hit would otherwise poison every floor consumer in the cell.
* The reading is kept at the point it was taken; a second reading at the
* same spot (within about 5 m) keeps the lower of the two. A cell keeps at
* most MESH_READINGS_PER_CELL readings.
* @returns {boolean} Whether the sample passed and was reported.
*/
function reportValidatedMeshFloorCell(lat, lon, heightM) {
@@ -481,13 +497,41 @@ export function createGroundFloor({ terrain, signal }) {
const cell = coarseFloorCoord(lat, lon);
const prior = cachedRealEllipsoidalGround(cell.lat, cell.lon);
if (!meshFloorSampleWithinPrior(heightM, prior)) return false;
reportMeshFloorCell(cell.lat, cell.lon, heightM);
const key = `${cell.lat},${cell.lon}`;
const readings = _meshCells.get(key);
if (!readings) {
_meshCells.set(key, [{ lat, lon, h: heightM }]);
return true;
}
const same = readings.find(
(r) =>
Math.abs(r.lat - lat) < MESH_READING_SAME_SPOT_DEG &&
Math.abs(r.lon - lon) < MESH_READING_SAME_SPOT_DEG,
);
if (same) {
if (heightM < same.h) same.h = heightM;
} else if (readings.length < MESH_READINGS_PER_CELL) {
readings.push({ lat, lon, h: heightM });
}
return true;
}
/**
* Synchronous mesh-cell read — null when the cell is unsampled OR the
* google-3d regime is inactive (globe stacks must floor on the DEM).
* Whether the cell holding the point has the real DEM prior that
* `reportValidatedMeshFloorCell` judges a reading against.
* @param {number} lat @param {number} lon
* @returns {boolean}
*/
function meshFloorPriorReady(lat, lon) {
if (!Number.isFinite(lat) || !Number.isFinite(lon)) return false;
const c = coarseFloorCoord(lat, lon);
return cachedRealEllipsoidalGround(c.lat, c.lon) != null;
}
/**
* Synchronous mesh-cell read — the cell's reading nearest the point, or
* null when the cell is unsampled OR the google-3d regime is inactive (globe
* stacks must floor on the DEM).
* @param {number} lat @param {number} lon
* @returns {number|null}
*/
@@ -495,8 +539,20 @@ export function createGroundFloor({ terrain, signal }) {
if (!_meshPreferred) return null;
if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;
const c = coarseFloorCoord(lat, lon);
const h = _meshCells.get(`${c.lat},${c.lon}`);
return h != null ? h : null;
const readings = _meshCells.get(`${c.lat},${c.lon}`);
if (!readings) return null;
if (readings.length === 1) return readings[0].h;
const k = Math.cos((lat * Math.PI) / 180) ** 2;
let best = readings[0],
bestD = Infinity;
for (const r of readings) {
const d = (r.lat - lat) ** 2 + k * (r.lon - lon) ** 2;
if (d < bestD) {
bestD = d;
best = r;
}
}
return best.h;
}
/** Test hook: clears mesh cells (never called at runtime — cells are valid all session). */
@@ -662,6 +718,7 @@ export function createGroundFloor({ terrain, signal }) {
reportMeshFloorCell,
meshFloorSampleWithinPrior,
reportValidatedMeshFloorCell,
meshFloorPriorReady,
cachedMeshFloor,
_clearMeshFloorCellsForTest,
cachedGroundFloor,
+1 -1
View File
@@ -101,7 +101,7 @@ const HELD_SNAP_MAX_DRIFT_M = 250;
* alone, and the asymmetry is not tidiness:
*
* - A cell reading LOW against a real sample is the EXPECTED error here, not a
* contradiction. Cells are a one-shot first-write latch over ~111 m, the
* contradiction. Cells hold a few point readings over ~111 m, the
* borrowed-neighbour path leans to the LOWEST on purpose (`neighborFloorM`),
* and `displayFloorHeightM` only ever RAISES — the whole floor chain is
* built to under-read rather than to invent. Releasing on that sign would
+53 -8
View File
@@ -11,6 +11,7 @@ export function createMeshFloorSampler({
coarseFloorCoord,
cachedMeshFloor,
reportValidatedMeshFloorCell,
meshFloorPriorReady,
setMeshFloorPreferred,
meshFloorPreferred,
cachedGroundFloor,
@@ -23,13 +24,16 @@ export function createMeshFloorSampler({
// 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.
// This module samples the mesh height at the callers' points, a few probes
// per coarse cell, and reports them to groundFloor.js, which keeps each
// reading that passes its gate at its own point; cachedGroundFloor() then
// answers mesh-first, from the reading nearest the asked point.
//
// 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,
// visible at all, (b) capped per call, (c) at most MAX_PROBES_PER_CELL
// readings per cell for the session, each at a point apart from the others,
// (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.
//
@@ -49,6 +53,16 @@ export function createMeshFloorSampler({
* streamed LOD under a high camera is coarse everywhere, so every probe
* would latch junk. */
const MAX_CAMERA_HEIGHT_M = 25_000;
/** @constant {number} Readings per cell: a cell is probed at most this
* many times, each at a point apart from the others, and groundFloor keeps
* each reading at its point (a read answers from the nearest one). */
const MAX_PROBES_PER_CELL = 8;
/** @constant {number} How far apart (degrees, about 17 m) a new probe must
* be from the cell's earlier ones to be worth taking. */
const PROBE_APART_DEG = 0.00015;
/** @type {Map<string, Array<{lat: number, lon: number}>>} Points probed
* per cell that returned a reading. */
const _probedPoints = new Map();
/** @type {Cesium.Cartographic} Scratch for probe coordinates. */
const _scratchProbe = new Cesium.Cartographic();
@@ -82,7 +96,7 @@ export function createMeshFloorSampler({
/**
* Samples the rendered mesh height for the coarse cells containing `points`,
* one-shot per cell, budget-capped, viewer-proximity-gated. Call ONCE per
* a few probes 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.
*
@@ -119,8 +133,21 @@ export function createMeshFloorSampler({
const cell = coarseFloorCoord(p.lat, p.lon);
const key = `${cell.lat},${cell.lon}`;
if (attempted.has(key)) continue;
// A cell with a reading is probed again only at a point well apart
// from the ones already probed, a few times at most, so the cell's
// readings follow where its vehicles actually are.
const probes = _probedPoints.get(key);
if (
cachedMeshFloor(cell.lat, cell.lon) != null &&
(probes?.length >= MAX_PROBES_PER_CELL ||
probes?.some(
(q) =>
Math.abs(q.lat - p.lat) < PROBE_APART_DEG &&
Math.abs(q.lon - p.lon) < PROBE_APART_DEG,
))
)
continue;
attempted.add(key);
if (cachedMeshFloor(cell.lat, cell.lon) != null) continue; // one-shot latch
if (
Number.isFinite(viewerLat) &&
Number.isFinite(viewerLon) &&
@@ -131,9 +158,11 @@ export function createMeshFloorSampler({
}
let height;
try {
// Probe where the caller's point is, not at the cell's centre: a
// centre that falls on a roof would lift everything in the cell.
const carto = Cesium.Cartographic.fromDegrees(
cell.lon,
cell.lat,
p.lon,
p.lat,
0,
_scratchProbe,
);
@@ -171,7 +200,23 @@ export function createMeshFloorSampler({
// 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);
const accepted = reportValidatedMeshFloorCell(p.lat, p.lon, height);
// A spot counts as probed once the gate has judged it against a real
// prior: accepted, or refused with the cell's DEM warm. A spot refused
// only because the DEM was still cold is tried again later. Each spot is
// recorded once, so a cell retried while it has no reading does not fill
// its probe allowance with the same spot.
if (!accepted && !meshFloorPriorReady(cell.lat, cell.lon)) continue;
if (!probes) _probedPoints.set(key, [{ lat: p.lat, lon: p.lon }]);
else if (
probes.length < MAX_PROBES_PER_CELL &&
!probes.some(
(q) =>
Math.abs(q.lat - p.lat) < PROBE_APART_DEG &&
Math.abs(q.lon - p.lon) < PROBE_APART_DEG,
)
)
probes.push({ lat: p.lat, lon: p.lon });
}
}