mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-10 13:22:18 +00:00
Read the mesh floor where transit vehicles and grounded flights actually are
With readings kept per point, the floor a vehicle gets depends on which point is probed and where it is read. Transit offered the sampler the first point of each vehicle's path in a cell, usually its oldest fix there, so a vehicle that stayed in one cell never had its current spot probed. It now offers the latest point of the path in each cell, and the newest fix in the vehicle's own cell (the selected vehicle's history is walked newest first). The grounded-flight display clamp read the floor at its sticky cell's centre, which now answers from the reading nearest the centre, not the contact. It reads at the contact's own position, held inside the sticky cell so boundary hysteresis still applies.
This commit is contained in:
1 parent
04b8c856ad
commit
9bce41d3c6
5 files changed
+192
-13
No files matched your search
+10
-7
@@ -1360,13 +1360,16 @@ 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.
|
||||
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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in new issue
Block a user