mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +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
+139
-37
No files matched your search
@@ -1095,5 +1095,6 @@
|
||||
"scripts/qa-surface-heights.mjs",
|
||||
"src/globe/navara/followGestures.js",
|
||||
"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/debug.js",
|
||||
"src/geo/developerError.js",
|
||||
"src/time/clock.js"
|
||||
"src/time/clock.js",
|
||||
"src/globe/contract.js",
|
||||
"src/globe/cesium/engineFromViewer.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -947,7 +949,9 @@
|
||||
"src/geo/check.js",
|
||||
"src/geo/debug.js",
|
||||
"src/geo/developerError.js",
|
||||
"src/time/clock.js"
|
||||
"src/time/clock.js",
|
||||
"src/globe/contract.js",
|
||||
"src/globe/cesium/engineFromViewer.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -988,7 +992,9 @@
|
||||
"src/geo/scaleToGeodeticSurface.js",
|
||||
"src/geo/check.js",
|
||||
"src/geo/debug.js",
|
||||
"src/geo/developerError.js"
|
||||
"src/geo/developerError.js",
|
||||
"src/globe/contract.js",
|
||||
"src/globe/cesium/engineFromViewer.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -1028,7 +1034,9 @@
|
||||
"src/geo/scaleToGeodeticSurface.js",
|
||||
"src/geo/check.js",
|
||||
"src/geo/debug.js",
|
||||
"src/geo/developerError.js"
|
||||
"src/geo/developerError.js",
|
||||
"src/globe/contract.js",
|
||||
"src/globe/cesium/engineFromViewer.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -1088,7 +1096,9 @@
|
||||
"src/geo/scaleToGeodeticSurface.js",
|
||||
"src/geo/check.js",
|
||||
"src/geo/debug.js",
|
||||
"src/geo/developerError.js"
|
||||
"src/geo/developerError.js",
|
||||
"src/globe/contract.js",
|
||||
"src/globe/cesium/engineFromViewer.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -1171,7 +1181,9 @@
|
||||
"src/geo/check.js",
|
||||
"src/geo/debug.js",
|
||||
"src/geo/developerError.js",
|
||||
"src/time/clock.js"
|
||||
"src/time/clock.js",
|
||||
"src/globe/contract.js",
|
||||
"src/globe/cesium/engineFromViewer.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -1263,7 +1275,9 @@
|
||||
"src/geo/scaleToGeodeticSurface.js",
|
||||
"src/geo/check.js",
|
||||
"src/geo/debug.js",
|
||||
"src/geo/developerError.js"
|
||||
"src/geo/developerError.js",
|
||||
"src/globe/contract.js",
|
||||
"src/globe/cesium/engineFromViewer.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -1414,7 +1428,9 @@
|
||||
"src/geo/scaleToGeodeticSurface.js",
|
||||
"src/geo/check.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"]
|
||||
},
|
||||
@@ -1557,7 +1573,9 @@
|
||||
"src/geo/scaleToGeodeticSurface.js",
|
||||
"src/geo/check.js",
|
||||
"src/geo/debug.js",
|
||||
"src/geo/developerError.js"
|
||||
"src/geo/developerError.js",
|
||||
"src/globe/contract.js",
|
||||
"src/globe/cesium/engineFromViewer.js"
|
||||
],
|
||||
"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', () => {
|
||||
const position = { x: 1, y: 2, z: 3 };
|
||||
const kept = normalizeOverlayEntry('firms', { id: 'a', position, cullPosition: { x: 4, y: 5, z: 6 } });
|
||||
assert.ok(kept.cullPosition instanceof Cesium.Cartesian3, 'stored as a host-owned Cartesian3');
|
||||
const source = { x: 4, y: 5, z: 6 };
|
||||
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(
|
||||
[kept.cullPosition.x, kept.cullPosition.y, kept.cullPosition.z],
|
||||
[4, 5, 6],
|
||||
|
||||
+12
-10
@@ -23,6 +23,16 @@
|
||||
*/
|
||||
import * as Cesium from 'cesium';
|
||||
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. */
|
||||
const FORWARD_PROBE_M = 2000;
|
||||
@@ -331,16 +341,8 @@ export function perspectiveProjectedRotation(
|
||||
);
|
||||
geo.cartesian3.add(position, _scratchWorldForward, _scratchProbe);
|
||||
|
||||
const at = Cesium.SceneTransforms.worldToWindowCoordinates(
|
||||
scene,
|
||||
position,
|
||||
_scratchWindowAt,
|
||||
);
|
||||
const ahead = Cesium.SceneTransforms.worldToWindowCoordinates(
|
||||
scene,
|
||||
_scratchProbe,
|
||||
_scratchWindowAhead,
|
||||
);
|
||||
const at = worldToWindow(scene, position, _scratchWindowAt);
|
||||
const ahead = worldToWindow(scene, _scratchProbe, _scratchWindowAhead);
|
||||
if (!at || !ahead) {
|
||||
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`,
|
||||
);
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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`. */
|
||||
export function horizonVisible(eye, position) {
|
||||
const camera = cartesian3.create(
|
||||
const camera = cartesian3.fromElements(
|
||||
eye.x / RADII.x,
|
||||
eye.y / RADII.y,
|
||||
eye.z / RADII.z,
|
||||
horizonCamera,
|
||||
);
|
||||
const limbSquared = cartesian3.magnitudeSquared(camera) - 1;
|
||||
const target = cartesian3.create(
|
||||
const target = cartesian3.fromElements(
|
||||
position.x / RADII.x,
|
||||
position.y / RADII.y,
|
||||
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 occluded =
|
||||
limbSquared < 0
|
||||
|
||||
@@ -313,3 +313,48 @@ test('cartographic heights of the math agree with src/geo', () => {
|
||||
'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 {
|
||||
createCyberSonarSampler,
|
||||
@@ -483,10 +484,16 @@ export function createOverlays({ state: layerState, services, parts, source }) {
|
||||
hideReplayVehicleOverlay();
|
||||
return null;
|
||||
}
|
||||
const windowPosition = Cesium.SceneTransforms.worldToWindowCoordinates(
|
||||
layerState._viewer.scene,
|
||||
position,
|
||||
);
|
||||
// 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,
|
||||
point,
|
||||
);
|
||||
const windowPosition = project(position);
|
||||
if (!windowPosition) {
|
||||
hideReplayVehicleOverlay();
|
||||
return null;
|
||||
@@ -548,10 +555,7 @@ export function createOverlays({ state: layerState, services, parts, source }) {
|
||||
useForward ? forwardProgress : backwardProgress,
|
||||
);
|
||||
const tangentWindowPosition = tangentPosition
|
||||
? Cesium.SceneTransforms.worldToWindowCoordinates(
|
||||
layerState._viewer.scene,
|
||||
tangentPosition,
|
||||
)
|
||||
? project(tangentPosition)
|
||||
: null;
|
||||
if (tangentWindowPosition) {
|
||||
const screenDelta = Math.hypot(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import * as geo from '../geo/index.js';
|
||||
import { isGlobeEngine } from '../globe/contract.js';
|
||||
import {
|
||||
@@ -416,7 +415,8 @@ export function paintLaneForOverlayEntry(entry = {}) {
|
||||
* point) to the per-frame occluder.
|
||||
*
|
||||
* @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) {
|
||||
let candidate;
|
||||
@@ -429,8 +429,7 @@ function snapshotCullPosition(entry) {
|
||||
const { x, y, z } = candidate;
|
||||
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z))
|
||||
return null;
|
||||
// Still the sources' own vector type until they move off Cesium (phase 1c).
|
||||
return new Cesium.Cartesian3(x, y, z);
|
||||
return geo.cartesian3.create(x, y, z);
|
||||
}
|
||||
|
||||
export function normalizeOverlayEntry(sourceId, entry) {
|
||||
|
||||
Reference in new issue
Block a user