fix(infrastructure): sample visible ground below the ellipsoid

This commit is contained in:
German Khokhlov committed 2026-11-03 15:10:15 +04:00
1 parent 52b609480c
commit 65f0467ad8
3 files changed
+18 -10

No files matched your search

+2 -2
View File
@@ -2048,8 +2048,8 @@ Unchanged overlay entries do not request another frame; moved positions,
changed membership and re-enabling still publish. Ground sampling waits for changed membership and re-enabling still publish. Ground sampling waits for
visible globe tiles to settle, and skips markers whose ground the camera visible globe tiles to settle, and skips markers whose ground the camera
cannot see (unseen ground reads through the Earth and would retry every 2 s). cannot see (unseen ground reads through the Earth and would retry every 2 s).
Ground is tested at loaded globe terrain, or across 0-9,000 m when the globe Ground is tested at loaded globe terrain, or across -9,000 to 9,000 m when the
is hidden, so photoreal views skip only ground well out of view. Until it is globe is hidden, so photoreal views skip only ground well out of view. Until it is
seen, a stem keeps its unsampled height, which can show for a stem whose base seen, a stem keeps its unsampled height, which can show for a stem whose base
is just off screen. Sampling uses valid loaded terrain as a floor while is just off screen. Sampling uses valid loaded terrain as a floor while
retaining rooftop and below-sea-level elevations. A hidden globe does not retaining rooftop and below-sea-level elevations. A hidden globe does not
+7 -4
View File
@@ -797,7 +797,7 @@ test('an unseen base is not sampled and arms no retry until it is in view', asyn
* `cameraM` metres above the ellipsoid, looking at the base's ground at * `cameraM` metres above the ellipsoid, looking at the base's ground at
* `groundM` (or directly away from it). * `groundM` (or directly away from it).
*/ */
function aimRealCamera(env, { groundM, cameraM, southM, away = false }) { function aimRealCamera(env, { groundM, cameraM, southM, away = false, fov = Math.PI / 3 }) {
const entity = env.dataSources[0].entities.values[0]; const entity = env.dataSources[0].entities.values[0];
const base = Cesium.Cartographic.fromCartesian(entity.__localBaseCartesian); const base = Cesium.Cartographic.fromCartesian(entity.__localBaseCartesian);
const ground = Cesium.Cartesian3.fromRadians(base.longitude, base.latitude, groundM); const ground = Cesium.Cartesian3.fromRadians(base.longitude, base.latitude, groundM);
@@ -822,7 +822,7 @@ function aimRealCamera(env, { groundM, cameraM, southM, away = false }) {
); );
const up = Cesium.Cartesian3.cross(right, direction, new Cesium.Cartesian3()); const up = Cesium.Cartesian3.cross(right, direction, new Cesium.Cartesian3());
const frustum = new Cesium.PerspectiveFrustum({ const frustum = new Cesium.PerspectiveFrustum({
fov: Math.PI / 3, fov,
aspectRatio: 800 / 600, aspectRatio: 800 / 600,
near: 1, near: 1,
far: 1e8, far: 1e8,
@@ -841,7 +841,10 @@ function aimRealCamera(env, { groundM, cameraM, southM, away = false }) {
const loadedTerrain = (height) => ({ show: true, tilesLoaded: true, getHeight: () => height }); const loadedTerrain = (height) => ({ show: true, tilesLoaded: true, getHeight: () => height });
const hiddenGlobe = { show: false, tilesLoaded: true, getHeight: () => undefined }; const hiddenGlobe = { show: false, tilesLoaded: true, getHeight: () => undefined };
for (const { label, globe, groundM, cameraM, southM, baseOffScreen } of [ for (const { label, globe, groundM, cameraM, southM, fov, baseOffScreen } of [
// The Dead Sea (about -430 m) under a hidden globe, seen steeply down
// through a narrow view: both 0 m and 9,000 m above it are off screen.
{ label: 'hidden-globe depression', globe: hiddenGlobe, groundM: -430, cameraM: 20, southM: 180, fov: Math.PI / 12, baseOffScreen: true },
// 2,500 m terrain from 3 km away at a shallow angle: the ellipsoid-height // 2,500 m terrain from 3 km away at a shallow angle: the ellipsoid-height
// base is far below the bottom of the screen. // base is far below the bottom of the screen.
{ label: 'high ground with loaded terrain', globe: loadedTerrain(2_500), groundM: 2_500, cameraM: 3_500, southM: 3_000, baseOffScreen: true }, { label: 'high ground with loaded terrain', globe: loadedTerrain(2_500), groundM: 2_500, cameraM: 3_500, southM: 3_000, baseOffScreen: true },
@@ -857,7 +860,7 @@ for (const { label, globe, groundM, cameraM, southM, baseOffScreen } of [
const clock = installFakeClock(t); const clock = installFakeClock(t);
t.after(() => { env.layer.destroy(env.viewer); env.cleanup(); }); t.after(() => { env.layer.destroy(env.viewer); env.cleanup(); });
env.viewer.scene.globe = globe; env.viewer.scene.globe = globe;
const baseVisibility = aimRealCamera(env, { groundM, cameraM, southM }); const baseVisibility = aimRealCamera(env, { groundM, cameraM, southM, fov });
if (baseOffScreen) if (baseOffScreen)
assert.equal(baseVisibility, Cesium.Intersect.OUTSIDE, 'precondition: the ellipsoid-height base is off screen'); assert.equal(baseVisibility, Cesium.Intersect.OUTSIDE, 'precondition: the ellipsoid-height base is off screen');
+9 -4
View File
@@ -1159,9 +1159,10 @@ function groundSamplePoint(record, height, result) {
* retry, each one a full offscreen render; 110 unseen data centres there held * retry, each one a full offscreen render; 110 unseen data centres there held
* rest frames at 58-589 ms. Unsampled bases sit on the ellipsoid, so the test * rest frames at 58-589 ms. Unsampled bases sit on the ellipsoid, so the test
* uses loaded globe terrain when there is some. Otherwise (a hidden globe) it * uses loaded globe terrain when there is some. Otherwise (a hidden globe) it
* tests the vertical from 0 to GROUND_SAMPLE_MAX_ABS_HEIGHT_M, every height a * tests the vertical across +/-GROUND_SAMPLE_MAX_ABS_HEIGHT_M, every height a
* sample can accept, so high ground in view is never mistaken for an unseen * sample can accept, so high or below-ellipsoid ground in view is never
* base. Unseen ground is sampled on the first walk that sees it. * mistaken for an unseen base. Unseen ground is sampled on the first walk that
* sees it.
*/ */
function groundSampleInView(viewer, record) { function groundSampleInView(viewer, record) {
const camera = viewer.camera; const camera = viewer.camera;
@@ -1170,7 +1171,11 @@ function groundSampleInView(viewer, record) {
const known = const known =
Number.isFinite(terrain) && Number.isFinite(terrain) &&
Math.abs(terrain) <= GROUND_SAMPLE_MAX_ABS_HEIGHT_M; Math.abs(terrain) <= GROUND_SAMPLE_MAX_ABS_HEIGHT_M;
const low = groundSamplePoint(record, known ? terrain : 0, groundSampleLow); const low = groundSamplePoint(
record,
known ? terrain : -GROUND_SAMPLE_MAX_ABS_HEIGHT_M,
groundSampleLow,
);
const high = known const high = known
? low ? low
: groundSamplePoint( : groundSamplePoint(