mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-10 05:12:23 +00:00
Keep vehicles off rooftops and bridge decks: probe the mesh where the vehicle is
On the photoreal stack the rendered-mesh floor took one probe at each coarse cell's centre (0.001 degrees) and kept it for the session. A centre on a roof passed the +80 m DEM window and lifted every vehicle in the cell to roof height (a Haymarket bus drew 23.5 m above the street, on the garage roof); a centre in the river put Red Line trains on the Longfellow Bridge 6-9 m under the deck. The sampler now probes at the caller's point. A cell with a reading is probed again only at spots about 17 m apart from earlier ones, eight at most, and a spot refused only because the cell's DEM prior was cold is tried again later. The floor keeps each reading that passes the gate at the point it was taken (two within about 5 m keep the lower; sixteen at most per cell) and answers a read from the reading nearest the asked point, so a viaduct and the street beside it in one cell keep their own heights.
This commit is contained in:
1 parent
e5da073d33
commit
04b8c856ad
9 files changed
+329
-28
No files matched your search
+22
-1
@@ -1347,6 +1347,27 @@ 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. 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 +3169,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
|
||||
|
||||
@@ -22,6 +22,7 @@ export const {
|
||||
reportMeshFloorCell,
|
||||
meshFloorSampleWithinPrior,
|
||||
reportValidatedMeshFloorCell,
|
||||
meshFloorPriorReady,
|
||||
cachedMeshFloor,
|
||||
_clearMeshFloorCellsForTest,
|
||||
cachedGroundFloor,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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 =
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
+69
-12
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user