mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 19:34:01 +00:00
refactor(overlays): layer labels through the engine
The world overlay host already projects through engine.projector and tests the horizon with projector.horizonVisible; the last Cesium value on its path, the snapshot of an entry's cullPosition, is now a geo Cartesian3. Navara's horizonVisible is Cesium's EllipsoidalOccluder.isPointVisible: tested against Cesium's own on 8 000 eye/point pairs (600 m to 20 000 km, points under, on and over the ellipsoid; no disagreement) and at the limb (a label 40 m under the ellipsoid just past the horizon is hidden, with its cullPosition lifted 100 m it is shown). It ran per label per frame with three new vectors each call; it now uses scratch vectors. Micro-measure (500 labels per frame, no scavenge during the run): 7 941 B per frame before, 51 after. The W1 sources that projected with Cesium's SceneTransforms themselves - the icon orientation's exact projection and the launch replay overlay - go through the engine's projector when their scene or viewer has a registered engine. Satellites, vessel labels and aircraft recession project nothing themselves (the host does). scripts/qa-overlay-engines.mjs publishes the same labels on both engines at a city, an orbital and a limb pose and compares the painted rectangles: worst 0 / 2 / 0 px; on both, the far-side label is not painted, the lifted limb label is and the buried one is not.
This commit is contained in:
1 parent
311722e6de
commit
7d5a484796
9 files changed
+137
-35
No files matched your search
@@ -1095,5 +1095,6 @@
|
|||||||
"scripts/qa-surface-heights.mjs",
|
"scripts/qa-surface-heights.mjs",
|
||||||
"src/globe/navara/followGestures.js",
|
"src/globe/navara/followGestures.js",
|
||||||
"src/globe/navara/followGestures.test.mjs",
|
"src/globe/navara/followGestures.test.mjs",
|
||||||
"src/globe/cpuPicks.test.mjs"
|
"src/globe/cpuPicks.test.mjs",
|
||||||
|
"scripts/qa-overlay-engines.mjs"
|
||||||
]
|
]
|
||||||
@@ -893,7 +893,9 @@
|
|||||||
"src/geo/check.js",
|
"src/geo/check.js",
|
||||||
"src/geo/debug.js",
|
"src/geo/debug.js",
|
||||||
"src/geo/developerError.js",
|
"src/geo/developerError.js",
|
||||||
"src/time/clock.js"
|
"src/time/clock.js",
|
||||||
|
"src/globe/contract.js",
|
||||||
|
"src/globe/cesium/engineFromViewer.js"
|
||||||
],
|
],
|
||||||
"external": ["cesium"]
|
"external": ["cesium"]
|
||||||
},
|
},
|
||||||
@@ -947,7 +949,9 @@
|
|||||||
"src/geo/check.js",
|
"src/geo/check.js",
|
||||||
"src/geo/debug.js",
|
"src/geo/debug.js",
|
||||||
"src/geo/developerError.js",
|
"src/geo/developerError.js",
|
||||||
"src/time/clock.js"
|
"src/time/clock.js",
|
||||||
|
"src/globe/contract.js",
|
||||||
|
"src/globe/cesium/engineFromViewer.js"
|
||||||
],
|
],
|
||||||
"external": ["cesium"]
|
"external": ["cesium"]
|
||||||
},
|
},
|
||||||
@@ -988,7 +992,9 @@
|
|||||||
"src/geo/scaleToGeodeticSurface.js",
|
"src/geo/scaleToGeodeticSurface.js",
|
||||||
"src/geo/check.js",
|
"src/geo/check.js",
|
||||||
"src/geo/debug.js",
|
"src/geo/debug.js",
|
||||||
"src/geo/developerError.js"
|
"src/geo/developerError.js",
|
||||||
|
"src/globe/contract.js",
|
||||||
|
"src/globe/cesium/engineFromViewer.js"
|
||||||
],
|
],
|
||||||
"external": ["cesium"]
|
"external": ["cesium"]
|
||||||
},
|
},
|
||||||
@@ -1028,7 +1034,9 @@
|
|||||||
"src/geo/scaleToGeodeticSurface.js",
|
"src/geo/scaleToGeodeticSurface.js",
|
||||||
"src/geo/check.js",
|
"src/geo/check.js",
|
||||||
"src/geo/debug.js",
|
"src/geo/debug.js",
|
||||||
"src/geo/developerError.js"
|
"src/geo/developerError.js",
|
||||||
|
"src/globe/contract.js",
|
||||||
|
"src/globe/cesium/engineFromViewer.js"
|
||||||
],
|
],
|
||||||
"external": ["cesium"]
|
"external": ["cesium"]
|
||||||
},
|
},
|
||||||
@@ -1088,7 +1096,9 @@
|
|||||||
"src/geo/scaleToGeodeticSurface.js",
|
"src/geo/scaleToGeodeticSurface.js",
|
||||||
"src/geo/check.js",
|
"src/geo/check.js",
|
||||||
"src/geo/debug.js",
|
"src/geo/debug.js",
|
||||||
"src/geo/developerError.js"
|
"src/geo/developerError.js",
|
||||||
|
"src/globe/contract.js",
|
||||||
|
"src/globe/cesium/engineFromViewer.js"
|
||||||
],
|
],
|
||||||
"external": ["cesium"]
|
"external": ["cesium"]
|
||||||
},
|
},
|
||||||
@@ -1171,7 +1181,9 @@
|
|||||||
"src/geo/check.js",
|
"src/geo/check.js",
|
||||||
"src/geo/debug.js",
|
"src/geo/debug.js",
|
||||||
"src/geo/developerError.js",
|
"src/geo/developerError.js",
|
||||||
"src/time/clock.js"
|
"src/time/clock.js",
|
||||||
|
"src/globe/contract.js",
|
||||||
|
"src/globe/cesium/engineFromViewer.js"
|
||||||
],
|
],
|
||||||
"external": ["cesium"]
|
"external": ["cesium"]
|
||||||
},
|
},
|
||||||
@@ -1263,7 +1275,9 @@
|
|||||||
"src/geo/scaleToGeodeticSurface.js",
|
"src/geo/scaleToGeodeticSurface.js",
|
||||||
"src/geo/check.js",
|
"src/geo/check.js",
|
||||||
"src/geo/debug.js",
|
"src/geo/debug.js",
|
||||||
"src/geo/developerError.js"
|
"src/geo/developerError.js",
|
||||||
|
"src/globe/contract.js",
|
||||||
|
"src/globe/cesium/engineFromViewer.js"
|
||||||
],
|
],
|
||||||
"external": ["cesium"]
|
"external": ["cesium"]
|
||||||
},
|
},
|
||||||
@@ -1414,7 +1428,9 @@
|
|||||||
"src/geo/scaleToGeodeticSurface.js",
|
"src/geo/scaleToGeodeticSurface.js",
|
||||||
"src/geo/check.js",
|
"src/geo/check.js",
|
||||||
"src/geo/debug.js",
|
"src/geo/debug.js",
|
||||||
"src/geo/developerError.js"
|
"src/geo/developerError.js",
|
||||||
|
"src/globe/contract.js",
|
||||||
|
"src/globe/cesium/engineFromViewer.js"
|
||||||
],
|
],
|
||||||
"external": ["hls.js", "cesium"]
|
"external": ["hls.js", "cesium"]
|
||||||
},
|
},
|
||||||
@@ -1557,7 +1573,9 @@
|
|||||||
"src/geo/scaleToGeodeticSurface.js",
|
"src/geo/scaleToGeodeticSurface.js",
|
||||||
"src/geo/check.js",
|
"src/geo/check.js",
|
||||||
"src/geo/debug.js",
|
"src/geo/debug.js",
|
||||||
"src/geo/developerError.js"
|
"src/geo/developerError.js",
|
||||||
|
"src/globe/contract.js",
|
||||||
|
"src/globe/cesium/engineFromViewer.js"
|
||||||
],
|
],
|
||||||
"external": ["cesium"]
|
"external": ["cesium"]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -226,8 +226,10 @@ test('isOverlayPointVisible: horizon test prefers entry.cullPosition when presen
|
|||||||
|
|
||||||
test('normalizeOverlayEntry: carries a valid cullPosition, nulls a junk one', () => {
|
test('normalizeOverlayEntry: carries a valid cullPosition, nulls a junk one', () => {
|
||||||
const position = { x: 1, y: 2, z: 3 };
|
const position = { x: 1, y: 2, z: 3 };
|
||||||
const kept = normalizeOverlayEntry('firms', { id: 'a', position, cullPosition: { x: 4, y: 5, z: 6 } });
|
const source = { x: 4, y: 5, z: 6 };
|
||||||
assert.ok(kept.cullPosition instanceof Cesium.Cartesian3, 'stored as a host-owned Cartesian3');
|
const kept = normalizeOverlayEntry('firms', { id: 'a', position, cullPosition: source });
|
||||||
|
assert.notEqual(kept.cullPosition, source, 'stored as a host-owned copy (a geo Cartesian3)');
|
||||||
|
assert.ok(!(kept.cullPosition instanceof Cesium.Cartesian3), 'not a Cesium value');
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
[kept.cullPosition.x, kept.cullPosition.y, kept.cullPosition.z],
|
[kept.cullPosition.x, kept.cullPosition.y, kept.cullPosition.z],
|
||||||
[4, 5, 6],
|
[4, 5, 6],
|
||||||
|
|||||||
+12
-10
@@ -23,6 +23,16 @@
|
|||||||
*/
|
*/
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import * as geo from '../geo/index.js';
|
import * as geo from '../geo/index.js';
|
||||||
|
import { engineFromViewer } from '../globe/cesium/engineFromViewer.js';
|
||||||
|
|
||||||
|
// A window position through the engine's projector when `scene` has a
|
||||||
|
// registered engine (engineFromViewer), else Cesium's SceneTransforms.
|
||||||
|
function worldToWindow(scene, position, result) {
|
||||||
|
const projector = engineFromViewer(scene)?.projector;
|
||||||
|
return projector
|
||||||
|
? projector.worldToWindow(position, result)
|
||||||
|
: Cesium.SceneTransforms.worldToWindowCoordinates(scene, position, result);
|
||||||
|
}
|
||||||
|
|
||||||
/** Meters used to give the course vector a stable projection magnitude. */
|
/** Meters used to give the course vector a stable projection magnitude. */
|
||||||
const FORWARD_PROBE_M = 2000;
|
const FORWARD_PROBE_M = 2000;
|
||||||
@@ -331,16 +341,8 @@ export function perspectiveProjectedRotation(
|
|||||||
);
|
);
|
||||||
geo.cartesian3.add(position, _scratchWorldForward, _scratchProbe);
|
geo.cartesian3.add(position, _scratchWorldForward, _scratchProbe);
|
||||||
|
|
||||||
const at = Cesium.SceneTransforms.worldToWindowCoordinates(
|
const at = worldToWindow(scene, position, _scratchWindowAt);
|
||||||
scene,
|
const ahead = worldToWindow(scene, _scratchProbe, _scratchWindowAhead);
|
||||||
position,
|
|
||||||
_scratchWindowAt,
|
|
||||||
);
|
|
||||||
const ahead = Cesium.SceneTransforms.worldToWindowCoordinates(
|
|
||||||
scene,
|
|
||||||
_scratchProbe,
|
|
||||||
_scratchWindowAhead,
|
|
||||||
);
|
|
||||||
if (!at || !ahead) {
|
if (!at || !ahead) {
|
||||||
return screenProjectedRotation(scene, position, courseDeg, previous);
|
return screenProjectedRotation(scene, position, courseDeg, previous);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -745,3 +745,27 @@ test('the perspective helper is continuous through the sub-pixel band, not a 23
|
|||||||
`exact alone at ${s.separation.toFixed(2)} px`,
|
`exact alone at ${s.separation.toFixed(2)} px`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the exact projection asks the engine projector of a registered scene', async () => {
|
||||||
|
const geo = await import('../geo/index.js');
|
||||||
|
const { registerViewerEngine } = await import('../globe/cesium/engineFromViewer.js');
|
||||||
|
const { perspectiveProjectedRotation: rotation } = await import('./iconOrientation.js');
|
||||||
|
const scene = { camera: { rightWC: { x: 0, y: 1, z: 0 }, upWC: { x: 0, y: 0, z: 1 } } };
|
||||||
|
const asked = [];
|
||||||
|
const unregister = registerViewerEngine({ scene }, {
|
||||||
|
projector: {
|
||||||
|
// Screen-right for every point further east (the probe), so 90° on screen.
|
||||||
|
worldToWindow(position, result = geo.cartesian2.create()) {
|
||||||
|
asked.push(position);
|
||||||
|
return Object.assign(result, { x: asked.length === 1 ? 100 : 120, y: 100 });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const value = rotation(scene, geo.cartesian3.fromDegrees(0, 0, 0), 90, null);
|
||||||
|
assert.equal(asked.length, 2, 'the anchor and the probe');
|
||||||
|
assert.ok(Math.abs(value - -Math.PI / 2) < 1e-9, `rotation ${value}`);
|
||||||
|
} finally {
|
||||||
|
unregister();
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -221,20 +221,27 @@ export function pickEllipsoid(camera, screen) {
|
|||||||
return intersectEllipsoid(camera.position, pickRay(camera, screen).direction);
|
return intersectEllipsoid(camera.position, pickRay(camera, screen).direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// horizonVisible runs per label per frame (worldOverlay): no allocation.
|
||||||
|
const horizonCamera = cartesian3.create();
|
||||||
|
const horizonTarget = cartesian3.create();
|
||||||
|
const horizonVt = cartesian3.create();
|
||||||
|
|
||||||
/** Cesium's EllipsoidalOccluder.isPointVisible from `eye`. */
|
/** Cesium's EllipsoidalOccluder.isPointVisible from `eye`. */
|
||||||
export function horizonVisible(eye, position) {
|
export function horizonVisible(eye, position) {
|
||||||
const camera = cartesian3.create(
|
const camera = cartesian3.fromElements(
|
||||||
eye.x / RADII.x,
|
eye.x / RADII.x,
|
||||||
eye.y / RADII.y,
|
eye.y / RADII.y,
|
||||||
eye.z / RADII.z,
|
eye.z / RADII.z,
|
||||||
|
horizonCamera,
|
||||||
);
|
);
|
||||||
const limbSquared = cartesian3.magnitudeSquared(camera) - 1;
|
const limbSquared = cartesian3.magnitudeSquared(camera) - 1;
|
||||||
const target = cartesian3.create(
|
const target = cartesian3.fromElements(
|
||||||
position.x / RADII.x,
|
position.x / RADII.x,
|
||||||
position.y / RADII.y,
|
position.y / RADII.y,
|
||||||
position.z / RADII.z,
|
position.z / RADII.z,
|
||||||
|
horizonTarget,
|
||||||
);
|
);
|
||||||
const vt = cartesian3.subtract(target, camera, cartesian3.create());
|
const vt = cartesian3.subtract(target, camera, horizonVt);
|
||||||
const vtDotVc = -cartesian3.dot(vt, camera);
|
const vtDotVc = -cartesian3.dot(vt, camera);
|
||||||
const occluded =
|
const occluded =
|
||||||
limbSquared < 0
|
limbSquared < 0
|
||||||
|
|||||||
@@ -313,3 +313,48 @@ test('cartographic heights of the math agree with src/geo', () => {
|
|||||||
'height',
|
'height',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("horizonVisible agrees with Cesium's EllipsoidalOccluder, also at the limb", async () => {
|
||||||
|
const Cesium = await import('cesium');
|
||||||
|
const occluder = new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84);
|
||||||
|
let seed = 7;
|
||||||
|
const random = () => {
|
||||||
|
seed = (seed * 16807) % 2147483647;
|
||||||
|
return seed / 2147483647;
|
||||||
|
};
|
||||||
|
let disagreements = 0;
|
||||||
|
let checked = 0;
|
||||||
|
for (let i = 0; i < 400; i++) {
|
||||||
|
const eye = cartesian3.fromDegrees(
|
||||||
|
random() * 360 - 180,
|
||||||
|
random() * 160 - 80,
|
||||||
|
[600, 15_000, 400_000, 20_000_000][i % 4],
|
||||||
|
);
|
||||||
|
occluder.cameraPosition = new Cesium.Cartesian3(eye.x, eye.y, eye.z);
|
||||||
|
for (let j = 0; j < 20; j++) {
|
||||||
|
// Points on the ground and above it, many near the horizon of the eye.
|
||||||
|
const point = cartesian3.fromDegrees(
|
||||||
|
random() * 360 - 180,
|
||||||
|
random() * 160 - 80,
|
||||||
|
[0, -40, 50, 10_000][j % 4],
|
||||||
|
);
|
||||||
|
checked++;
|
||||||
|
if (
|
||||||
|
horizonVisible(eye, point) !==
|
||||||
|
occluder.isPointVisible(
|
||||||
|
new Cesium.Cartesian3(point.x, point.y, point.z),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
disagreements++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.equal(disagreements, 0, `${disagreements}/${checked} disagree`);
|
||||||
|
// At the limb: a point 40 m under the ellipsoid just past the horizon is
|
||||||
|
// hidden, its cullPosition lifted 100 m is visible (worldOverlay's rule).
|
||||||
|
const eye = cartesian3.fromDegrees(0, 0, 10_000);
|
||||||
|
const horizonDeg = math.toDegrees(Math.acos(6378137 / (6378137 + 10_000)));
|
||||||
|
const buried = cartesian3.fromDegrees(horizonDeg + 0.02, 0, -40);
|
||||||
|
const lifted = cartesian3.fromDegrees(horizonDeg + 0.02, 0, 100);
|
||||||
|
assert.equal(horizonVisible(eye, buried), false);
|
||||||
|
assert.equal(horizonVisible(eye, lifted), true);
|
||||||
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { engineFromViewer } from '../../globe/cesium/engineFromViewer.js';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import {
|
import {
|
||||||
createCyberSonarSampler,
|
createCyberSonarSampler,
|
||||||
@@ -483,10 +484,16 @@ export function createOverlays({ state: layerState, services, parts, source }) {
|
|||||||
hideReplayVehicleOverlay();
|
hideReplayVehicleOverlay();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const windowPosition = Cesium.SceneTransforms.worldToWindowCoordinates(
|
// The engine's projector (engineFromViewer), as the world overlay.
|
||||||
|
const projector = engineFromViewer(layerState._viewer)?.projector;
|
||||||
|
const project = (point) =>
|
||||||
|
projector
|
||||||
|
? projector.worldToWindow(point)
|
||||||
|
: Cesium.SceneTransforms.worldToWindowCoordinates(
|
||||||
layerState._viewer.scene,
|
layerState._viewer.scene,
|
||||||
position,
|
point,
|
||||||
);
|
);
|
||||||
|
const windowPosition = project(position);
|
||||||
if (!windowPosition) {
|
if (!windowPosition) {
|
||||||
hideReplayVehicleOverlay();
|
hideReplayVehicleOverlay();
|
||||||
return null;
|
return null;
|
||||||
@@ -548,10 +555,7 @@ export function createOverlays({ state: layerState, services, parts, source }) {
|
|||||||
useForward ? forwardProgress : backwardProgress,
|
useForward ? forwardProgress : backwardProgress,
|
||||||
);
|
);
|
||||||
const tangentWindowPosition = tangentPosition
|
const tangentWindowPosition = tangentPosition
|
||||||
? Cesium.SceneTransforms.worldToWindowCoordinates(
|
? project(tangentPosition)
|
||||||
layerState._viewer.scene,
|
|
||||||
tangentPosition,
|
|
||||||
)
|
|
||||||
: null;
|
: null;
|
||||||
if (tangentWindowPosition) {
|
if (tangentWindowPosition) {
|
||||||
const screenDelta = Math.hypot(
|
const screenDelta = Math.hypot(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Cesium from 'cesium';
|
|
||||||
import * as geo from '../geo/index.js';
|
import * as geo from '../geo/index.js';
|
||||||
import { isGlobeEngine } from '../globe/contract.js';
|
import { isGlobeEngine } from '../globe/contract.js';
|
||||||
import {
|
import {
|
||||||
@@ -416,7 +415,8 @@ export function paintLaneForOverlayEntry(entry = {}) {
|
|||||||
* point) to the per-frame occluder.
|
* point) to the per-frame occluder.
|
||||||
*
|
*
|
||||||
* @param {object} entry - Raw source entry.
|
* @param {object} entry - Raw source entry.
|
||||||
* @returns {?Cesium.Cartesian3} Host-owned lifted anchor, or null.
|
* @returns {?{x: number, y: number, z: number}} Host-owned lifted anchor
|
||||||
|
* (a geo Cartesian3), or null.
|
||||||
*/
|
*/
|
||||||
function snapshotCullPosition(entry) {
|
function snapshotCullPosition(entry) {
|
||||||
let candidate;
|
let candidate;
|
||||||
@@ -429,8 +429,7 @@ function snapshotCullPosition(entry) {
|
|||||||
const { x, y, z } = candidate;
|
const { x, y, z } = candidate;
|
||||||
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z))
|
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z))
|
||||||
return null;
|
return null;
|
||||||
// Still the sources' own vector type until they move off Cesium (phase 1c).
|
return geo.cartesian3.create(x, y, z);
|
||||||
return new Cesium.Cartesian3(x, y, z);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeOverlayEntry(sourceId, entry) {
|
export function normalizeOverlayEntry(sourceId, entry) {
|
||||||
|
|||||||
Reference in new issue
Block a user