diff --git a/src/overlays/worldOverlayAllocation.test.mjs b/src/overlays/worldOverlayAllocation.test.mjs index bbaa4f168..bf5df538b 100644 --- a/src/overlays/worldOverlayAllocation.test.mjs +++ b/src/overlays/worldOverlayAllocation.test.mjs @@ -126,6 +126,18 @@ const WORKLOADS = [ maxBytesPerFrame: 13_000, saturated: true, }, + { + // The Navara engine's projector (cameraMath) with the horizon test on, + // over a real WGS84 camera: measured on Node 24.14 at 10 905 B/frame + // (generic 250: 9 681) — the per-frame matrix allocates a little, the + // per-label horizon test nothing. + name: 'through the Navara projector with the horizon test on', + profile: 'navara-projector', + entries: 250, + candidates: 250, + maxBytesPerFrame: 13_000, + saturated: true, + }, { name: 'with both local infrastructure sources live', profile: 'local-infrastructure', diff --git a/src/overlays/worldOverlayAllocation.worker.mjs b/src/overlays/worldOverlayAllocation.worker.mjs index 3ac5792c5..112052f38 100644 --- a/src/overlays/worldOverlayAllocation.worker.mjs +++ b/src/overlays/worldOverlayAllocation.worker.mjs @@ -1,5 +1,11 @@ import * as Cesium from 'cesium'; import { createCesiumEngine } from '../globe/cesium/createCesiumEngine.js'; +import * as geo from '../geo/index.js'; +import { + horizonVisible as navaraHorizonVisible, + projectToWindow as navaraProjectToWindow, + viewProjection as navaraViewProjection, +} from '../globe/navara/cameraMath.js'; import { AMBIENT_CARD_COLLISION_CAPACITY, destroyWorldOverlay, @@ -1157,6 +1163,63 @@ function buildPhase6DetectionWorkload(count) { }; } +/** + * The Navara engine's projector (its camera maths, src/globe/navara/ + * cameraMath.js, as createNavaraEngine wires it) over a real WGS84 camera + * 2 000 km over (0°, 0°), and labels on a lon/lat grid under it with the + * horizon test ON: the per-label horizon test and the per-frame matrix of + * the engine-neutral host path, not the Cesium mock's. + */ +const NAVARA_CAMERA = (() => { + const position = geo.cartesian3.fromDegrees(0, 0, 2_000_000); + const direction = geo.cartesian3.normalize( + geo.cartesian3.negate(position, geo.cartesian3.create()), + geo.cartesian3.create(), + ); + return { + position, + direction, + up: geo.cartesian3.create(0, 0, 1), + fovy: Math.PI / 3, + width: VIEWPORT_WIDTH, + height: VIEWPORT_HEIGHT, + }; +})(); + +function navaraProjector() { + return { + worldToWindow: (position, result) => + navaraProjectToWindow(NAVARA_CAMERA, position, result), + viewProjection: (result) => navaraViewProjection(NAVARA_CAMERA, result), + horizonVisible: (position) => + navaraHorizonVisible(NAVARA_CAMERA.position, position), + }; +} + +function buildNavaraProjectorWorkload(count) { + const workload = buildWorkload(count); + const columns = Math.max(1, Math.ceil(Math.sqrt((count * 16) / 9))); + for (let index = 0; index < count; index++) { + const column = index % columns; + const row = Math.floor(index / columns); + // ±12° east-west, ±8° north-south under the camera (on screen). + const place = geo.cartesian3.fromDegrees( + -12 + (24 * column) / Math.max(1, columns - 1), + -8 + (16 * row) / Math.max(1, Math.ceil(count / columns) - 1), + 0, + ); + workload.positions[index].x = place.x; + workload.positions[index].y = place.y; + workload.positions[index].z = place.z; + workload.drifts[index].baseX = place.x; + workload.drifts[index].baseY = place.y; + // Moving by up to 3 km each way. + workload.drifts[index].amplitude = 3_000; + workload.entries[index].horizonCull = true; + } + return workload; +} + function advanceWorkload(positions, drifts, frame) { for (let index = 0; index < positions.length; index++) { const drift = drifts[index]; @@ -1177,6 +1240,9 @@ function main() { height: VIEWPORT_HEIGHT, dpr: 2, }); + // The Navara projector profile swaps the engine's projector before the + // host reads it. + if (PROFILE === 'navara-projector') env.engine.projector = navaraProjector(); initWorldOverlay(env.engine); const workload = PROFILE === 'phase6-detection' @@ -1211,7 +1277,11 @@ function main() { ? buildAllLiveRadioWorkload(ENTRY_COUNT) : PROFILE === 'submarine-cables' ? buildSubmarineCablesWorkload(ENTRY_COUNT) - : buildWorkload(ENTRY_COUNT); + : PROFILE === 'navara-projector' + ? buildNavaraProjectorWorkload( + ENTRY_COUNT, + ) + : buildWorkload(ENTRY_COUNT); const { entries, positions, drifts } = workload; const solveIntervalMs = Number(process.env.GEV_ALLOC_SOLVE_MS) || 125; const detectionActive = !!workload.detectionLayer;