mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
Merge pull request #1464 from quanticsoul4772/upstream/datacenter-ground-sampling
fix(infrastructure): ground-sample only stem bases the camera sees
This commit is contained in:
4 files changed
+242
-6
No files matched your search
@@ -0,0 +1,4 @@
|
|||||||
|
- Ground-sample Data Centers and Dams markers only when the camera sees the
|
||||||
|
ground under them. Unseen ground read through the Earth and retried every
|
||||||
|
2 s; over Ashburn, Virginia on the Esri map, slow frames reached 2 s
|
||||||
|
(quanticsoul4772, #1464).
|
||||||
@@ -2191,7 +2191,12 @@ during continuous motion. Disable/re-enable resets selection cleanly.
|
|||||||
Close-up stem sizing uses camera-to-feature distance without a 5 km minimum.
|
Close-up stem sizing uses camera-to-feature distance without a 5 km minimum.
|
||||||
Unchanged overlay entries do not request another frame; moved positions,
|
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 uses valid loaded terrain as a floor while
|
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).
|
||||||
|
Ground is tested at loaded globe terrain, or across -9,000 to 9,000 m when the
|
||||||
|
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
|
||||||
|
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
|
||||||
constrain photoreal geometry, and retry work remains bounded. Already sampled
|
constrain photoreal geometry, and retry work remains bounded. Already sampled
|
||||||
nearby markers follow higher settled terrain on existing bounded walks, without
|
nearby markers follow higher settled terrain on existing bounded walks, without
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ async function createRealLocalLayerHarness({
|
|||||||
globalThis.window = { dispatchEvent() {} };
|
globalThis.window = { dispatchEvent() {} };
|
||||||
let sampleHeightImpl = sampleHeight;
|
let sampleHeightImpl = sampleHeight;
|
||||||
const sampleCalls = { count: 0 };
|
const sampleCalls = { count: 0 };
|
||||||
|
// The camera frustum, reduced to the one answer ground sampling asks of it.
|
||||||
|
let baseVisibility = Cesium.Intersect.INSIDE;
|
||||||
const overlayHost = {
|
const overlayHost = {
|
||||||
setVisible: (...args) => hostCalls.push(['visible', ...args]),
|
setVisible: (...args) => hostCalls.push(['visible', ...args]),
|
||||||
setEntries: (...args) => hostCalls.push(['entries', ...args]),
|
setEntries: (...args) => hostCalls.push(['entries', ...args]),
|
||||||
@@ -107,7 +109,19 @@ async function createRealLocalLayerHarness({
|
|||||||
},
|
},
|
||||||
camera: {
|
camera: {
|
||||||
positionWC: Cesium.Cartesian3.fromDegrees(-97.695, 30.205, 100_000),
|
positionWC: Cesium.Cartesian3.fromDegrees(-97.695, 30.205, 100_000),
|
||||||
frustum: { fov: Math.PI / 3 },
|
directionWC: new Cesium.Cartesian3(),
|
||||||
|
upWC: new Cesium.Cartesian3(),
|
||||||
|
frustum: {
|
||||||
|
fov: Math.PI / 3,
|
||||||
|
// No planes: everything is in view. One plane no point satisfies:
|
||||||
|
// nothing is.
|
||||||
|
computeCullingVolume: () => ({
|
||||||
|
planes:
|
||||||
|
baseVisibility === Cesium.Intersect.OUTSIDE
|
||||||
|
? [new Cesium.Cartesian4(0, 0, 0, -1)]
|
||||||
|
: [],
|
||||||
|
}),
|
||||||
|
},
|
||||||
moveEnd,
|
moveEnd,
|
||||||
flyTo() {},
|
flyTo() {},
|
||||||
},
|
},
|
||||||
@@ -151,6 +165,10 @@ async function createRealLocalLayerHarness({
|
|||||||
sampleCalls,
|
sampleCalls,
|
||||||
/** Swap scene.sampleHeight mid-test (e.g. tiles finally arrive). */
|
/** Swap scene.sampleHeight mid-test (e.g. tiles finally arrive). */
|
||||||
setSampleHeight(next) { sampleHeightImpl = next; },
|
setSampleHeight(next) { sampleHeightImpl = next; },
|
||||||
|
/** Put the feature's base inside (true) or outside the camera frustum. */
|
||||||
|
setBaseInView(inView) {
|
||||||
|
baseVisibility = inView ? Cesium.Intersect.INSIDE : Cesium.Intersect.OUTSIDE;
|
||||||
|
},
|
||||||
cleanup() {
|
cleanup() {
|
||||||
if (originalWindow === undefined) delete globalThis.window;
|
if (originalWindow === undefined) delete globalThis.window;
|
||||||
else globalThis.window = originalWindow;
|
else globalThis.window = originalWindow;
|
||||||
@@ -736,6 +754,136 @@ test('ground sampling waits for visible globe tiles before caching a height', as
|
|||||||
assert.ok(Math.abs(baseHeightM(env) - 117) < 0.01);
|
assert.ok(Math.abs(baseHeightM(env) - 117) < 0.01);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('an unseen base is not sampled and arms no retry until it is in view', async (t) => {
|
||||||
|
// Out of view the scene has no surface under the base, so the depth sample
|
||||||
|
// would pass through the Earth (about -6,344 km) and fail on every retry.
|
||||||
|
const env = await createRealLocalLayerHarness({
|
||||||
|
sampleHeightSupported: true, sampleHeight: () => 117,
|
||||||
|
});
|
||||||
|
_resetRenderGovernorForTest();
|
||||||
|
const governorScene = { renders: 0, requestRender() { this.renders += 1; } };
|
||||||
|
installRenderGovernor({ scene: governorScene });
|
||||||
|
governorScene.renders = 0; // discard the governor's own install settling frame
|
||||||
|
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||||
|
const clock = installFakeClock(t);
|
||||||
|
t.after(() => {
|
||||||
|
env.layer.destroy(env.viewer);
|
||||||
|
env.cleanup();
|
||||||
|
_resetRenderGovernorForTest();
|
||||||
|
});
|
||||||
|
env.viewer.scene.globe = { show: true, tilesLoaded: true, getHeight: () => 117 };
|
||||||
|
setCameraAltitude(env, 20_000);
|
||||||
|
env.setBaseInView(false);
|
||||||
|
runArmedRetryChain(env, t, clock, 5);
|
||||||
|
assert.equal(env.sampleCalls.count, 0, 'an off-screen base must not be sampled');
|
||||||
|
assert.equal(governorScene.renders, 0, 'an off-screen base must not keep a parked camera rendering');
|
||||||
|
assert.ok(Math.abs(baseHeightM(env)) < 0.01);
|
||||||
|
|
||||||
|
// In the frustum but over the horizon: 50 m up, about 67 km away.
|
||||||
|
env.setBaseInView(true);
|
||||||
|
env.viewer.camera.positionWC = Cesium.Cartesian3.fromDegrees(-97.0, 30.205, 50);
|
||||||
|
runArmedRetryChain(env, t, clock, 5);
|
||||||
|
assert.equal(env.sampleCalls.count, 0, 'a base over the horizon must not be sampled');
|
||||||
|
assert.equal(governorScene.renders, 0);
|
||||||
|
|
||||||
|
setCameraAltitude(env, 20_000);
|
||||||
|
env.preRender.raise();
|
||||||
|
assert.equal(env.sampleCalls.count, 1, 'the base is sampled on the first walk that sees it');
|
||||||
|
assert.ok(Math.abs(baseHeightM(env) - 117) < 0.01);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the harness a real Cesium camera `southM` metres south of the base at
|
||||||
|
* `cameraM` metres above the ellipsoid, looking at the base's ground at
|
||||||
|
* `groundM` (or directly away from it).
|
||||||
|
*/
|
||||||
|
function aimRealCamera(env, { groundM, cameraM, southM, away = false, fov = Math.PI / 3 }) {
|
||||||
|
const entity = env.dataSources[0].entities.values[0];
|
||||||
|
const base = Cesium.Cartographic.fromCartesian(entity.__localBaseCartesian);
|
||||||
|
const ground = Cesium.Cartesian3.fromRadians(base.longitude, base.latitude, groundM);
|
||||||
|
const position = Cesium.Cartesian3.fromRadians(
|
||||||
|
base.longitude,
|
||||||
|
base.latitude - southM / 6_371_000,
|
||||||
|
cameraM,
|
||||||
|
);
|
||||||
|
const direction = Cesium.Cartesian3.normalize(
|
||||||
|
away
|
||||||
|
? Cesium.Cartesian3.subtract(position, ground, new Cesium.Cartesian3())
|
||||||
|
: Cesium.Cartesian3.subtract(ground, position, new Cesium.Cartesian3()),
|
||||||
|
new Cesium.Cartesian3(),
|
||||||
|
);
|
||||||
|
const right = Cesium.Cartesian3.normalize(
|
||||||
|
Cesium.Cartesian3.cross(
|
||||||
|
direction,
|
||||||
|
Cesium.Ellipsoid.WGS84.geodeticSurfaceNormal(position),
|
||||||
|
new Cesium.Cartesian3(),
|
||||||
|
),
|
||||||
|
new Cesium.Cartesian3(),
|
||||||
|
);
|
||||||
|
const up = Cesium.Cartesian3.cross(right, direction, new Cesium.Cartesian3());
|
||||||
|
const frustum = new Cesium.PerspectiveFrustum({
|
||||||
|
fov,
|
||||||
|
aspectRatio: 800 / 600,
|
||||||
|
near: 1,
|
||||||
|
far: 1e8,
|
||||||
|
});
|
||||||
|
Object.assign(env.viewer.camera, {
|
||||||
|
positionWC: position,
|
||||||
|
directionWC: direction,
|
||||||
|
upWC: up,
|
||||||
|
frustum,
|
||||||
|
});
|
||||||
|
return frustum
|
||||||
|
.computeCullingVolume(position, direction, up)
|
||||||
|
.computeVisibility(new Cesium.BoundingSphere(entity.__localBaseCartesian, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadedTerrain = (height) => ({ show: true, tilesLoaded: true, getHeight: () => height });
|
||||||
|
const hiddenGlobe = { show: false, tilesLoaded: true, getHeight: () => undefined };
|
||||||
|
|
||||||
|
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
|
||||||
|
// 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 under a hidden globe', globe: hiddenGlobe, groundM: 2_500, cameraM: 3_500, southM: 3_000, baseOffScreen: true },
|
||||||
|
// A low-geoid coast (about -63 m near Mumbai) from a camera 50 m above the
|
||||||
|
// ellipsoid: the ellipsoidal horizon would hide the ground itself.
|
||||||
|
{ label: 'ground below the ellipsoid', globe: loadedTerrain(-63), groundM: -63, cameraM: 50, southM: 2_000, baseOffScreen: false },
|
||||||
|
]) {
|
||||||
|
test(`${label} in view is sampled`, async (t) => {
|
||||||
|
const env = await createRealLocalLayerHarness({
|
||||||
|
sampleHeightSupported: true, sampleHeight: () => groundM,
|
||||||
|
});
|
||||||
|
const clock = installFakeClock(t);
|
||||||
|
t.after(() => { env.layer.destroy(env.viewer); env.cleanup(); });
|
||||||
|
env.viewer.scene.globe = globe;
|
||||||
|
const baseVisibility = aimRealCamera(env, { groundM, cameraM, southM, fov });
|
||||||
|
if (baseOffScreen)
|
||||||
|
assert.equal(baseVisibility, Cesium.Intersect.OUTSIDE, 'precondition: the ellipsoid-height base is off screen');
|
||||||
|
|
||||||
|
clock.advance(2_100);
|
||||||
|
env.preRender.raise();
|
||||||
|
assert.equal(env.sampleCalls.count, 1, 'the ground in view is sampled');
|
||||||
|
assert.ok(Math.abs(baseHeightM(env) - groundM) < 0.01);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('under a hidden globe, ground behind the camera is not sampled', async (t) => {
|
||||||
|
const env = await createRealLocalLayerHarness({
|
||||||
|
sampleHeightSupported: true, sampleHeight: () => 2_500,
|
||||||
|
});
|
||||||
|
const clock = installFakeClock(t);
|
||||||
|
t.after(() => { env.layer.destroy(env.viewer); env.cleanup(); });
|
||||||
|
env.viewer.scene.globe = hiddenGlobe;
|
||||||
|
aimRealCamera(env, { groundM: 2_500, cameraM: 3_500, southM: 3_000, away: true });
|
||||||
|
clock.advance(2_100);
|
||||||
|
env.preRender.raise();
|
||||||
|
assert.equal(env.sampleCalls.count, 0);
|
||||||
|
});
|
||||||
|
|
||||||
test('settled terrain refinement lifts an already sampled stem without another GPU sample', async (t) => {
|
test('settled terrain refinement lifts an already sampled stem without another GPU sample', async (t) => {
|
||||||
const env = await createRealLocalLayerHarness({
|
const env = await createRealLocalLayerHarness({
|
||||||
sampleHeightSupported: true, sampleHeight: () => 117,
|
sampleHeightSupported: true, sampleHeight: () => 117,
|
||||||
|
|||||||
@@ -1000,9 +1000,9 @@ export function createLocalGeoJsonLayer(
|
|||||||
// governor the retry's preRender never arrives on a parked camera
|
// governor the retry's preRender never arrives on a parked camera
|
||||||
// and the stem stays at ellipsoid height (buried/floating) until
|
// and the stem stays at ellipsoid height (buried/floating) until
|
||||||
// the user happens to move. Schedule the frame the retry needs.
|
// the user happens to move. Schedule the frame the retry needs.
|
||||||
// Gated on a sampleable scene and in-range records only, so a far
|
// Gated on a sampleable scene and in-range, in-view records only,
|
||||||
// camera (or a keyless scene) stays fully idle; the distance is
|
// so a far camera (or a keyless scene) stays fully idle; the
|
||||||
// only computed for still-unsampled stems.
|
// distance is only computed for still-unsampled stems.
|
||||||
if (
|
if (
|
||||||
canSampleGround &&
|
canSampleGround &&
|
||||||
!record.groundSampled &&
|
!record.groundSampled &&
|
||||||
@@ -1010,7 +1010,8 @@ export function createLocalGeoJsonLayer(
|
|||||||
Cesium.Cartesian3.distance(
|
Cesium.Cartesian3.distance(
|
||||||
viewer.camera.positionWC,
|
viewer.camera.positionWC,
|
||||||
record.base,
|
record.base,
|
||||||
) < GROUND_SAMPLE_MAX_DISTANCE_M
|
) < GROUND_SAMPLE_MAX_DISTANCE_M &&
|
||||||
|
groundSampleInView(viewer, record)
|
||||||
) {
|
) {
|
||||||
groundRetryPending = true;
|
groundRetryPending = true;
|
||||||
}
|
}
|
||||||
@@ -1130,9 +1131,87 @@ function refreshLocalTerrainFloor(viewer, record) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groundSampleOccluder = new Cesium.EllipsoidalOccluder(
|
||||||
|
Cesium.Ellipsoid.WGS84,
|
||||||
|
);
|
||||||
|
|
||||||
|
const groundSampleCarto = new Cesium.Cartographic();
|
||||||
|
const groundSampleLow = new Cesium.Cartesian3();
|
||||||
|
const groundSampleHigh = new Cesium.Cartesian3();
|
||||||
|
|
||||||
|
/** The record's vertical at `height` metres above the ellipsoid. */
|
||||||
|
function groundSamplePoint(record, height, result) {
|
||||||
|
groundSampleCarto.longitude = record.carto.longitude;
|
||||||
|
groundSampleCarto.latitude = record.carto.latitude;
|
||||||
|
groundSampleCarto.height = height;
|
||||||
|
return Cesium.Cartographic.toCartesian(
|
||||||
|
groundSampleCarto,
|
||||||
|
Cesium.Ellipsoid.WGS84,
|
||||||
|
result,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the camera can see the ground under the record: inside the view
|
||||||
|
* frustum and above the horizon. scene.sampleHeight reads only surface
|
||||||
|
* rendered for the current view, so a sample of unseen ground passes through
|
||||||
|
* the Earth (about -6,344 km near Ashburn on Esri) and fails on every 2 s
|
||||||
|
* 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
|
||||||
|
* uses loaded globe terrain when there is some. Otherwise (a hidden globe) it
|
||||||
|
* tests the vertical across +/-GROUND_SAMPLE_MAX_ABS_HEIGHT_M, every height a
|
||||||
|
* sample can accept, so high or below-ellipsoid ground in view is never
|
||||||
|
* mistaken for an unseen base. Unseen ground is sampled on the first walk that
|
||||||
|
* sees it.
|
||||||
|
*/
|
||||||
|
function groundSampleInView(viewer, record) {
|
||||||
|
const camera = viewer.camera;
|
||||||
|
const globe = viewer.scene.globe;
|
||||||
|
const terrain = globe?.show ? globe.getHeight?.(record.carto) : undefined;
|
||||||
|
const known =
|
||||||
|
Number.isFinite(terrain) &&
|
||||||
|
Math.abs(terrain) <= GROUND_SAMPLE_MAX_ABS_HEIGHT_M;
|
||||||
|
const low = groundSamplePoint(
|
||||||
|
record,
|
||||||
|
known ? terrain : -GROUND_SAMPLE_MAX_ABS_HEIGHT_M,
|
||||||
|
groundSampleLow,
|
||||||
|
);
|
||||||
|
const high = known
|
||||||
|
? low
|
||||||
|
: groundSamplePoint(
|
||||||
|
record,
|
||||||
|
GROUND_SAMPLE_MAX_ABS_HEIGHT_M,
|
||||||
|
groundSampleHigh,
|
||||||
|
);
|
||||||
|
// The vertical is unseen only when both ends are outside the same plane.
|
||||||
|
const { planes } = camera.frustum.computeCullingVolume(
|
||||||
|
camera.positionWC,
|
||||||
|
camera.directionWC,
|
||||||
|
camera.upWC,
|
||||||
|
);
|
||||||
|
for (const plane of planes) {
|
||||||
|
if (
|
||||||
|
Cesium.Cartesian3.dot(plane, low) + plane.w < 0 &&
|
||||||
|
Cesium.Cartesian3.dot(plane, high) + plane.w < 0
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// The ellipsoidal occluder hides every point below the ellipsoid from a
|
||||||
|
// camera near it, so ground below 0 m (low-geoid coasts, the Dead Sea) is
|
||||||
|
// tested at 0 m; unknown ground at the top of the vertical.
|
||||||
|
const horizonHeight = known
|
||||||
|
? Math.max(terrain, 0)
|
||||||
|
: GROUND_SAMPLE_MAX_ABS_HEIGHT_M;
|
||||||
|
groundSampleOccluder.cameraPosition = camera.positionWC;
|
||||||
|
return groundSampleOccluder.isPointVisible(
|
||||||
|
groundSamplePoint(record, horizonHeight, groundSampleHigh),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function sampleLocalGroundHeight(viewer, record, now) {
|
function sampleLocalGroundHeight(viewer, record, now) {
|
||||||
if (record.groundSampled || !viewer.scene.sampleHeightSupported) return false;
|
if (record.groundSampled || !viewer.scene.sampleHeightSupported) return false;
|
||||||
if (now - record.lastGroundSampleMs < GROUND_SAMPLE_RETRY_MS) return false;
|
if (now - record.lastGroundSampleMs < GROUND_SAMPLE_RETRY_MS) return false;
|
||||||
|
if (!groundSampleInView(viewer, record)) return false;
|
||||||
record.lastGroundSampleMs = now;
|
record.lastGroundSampleMs = now;
|
||||||
const globe = viewer.scene.globe;
|
const globe = viewer.scene.globe;
|
||||||
if (globe?.show && globe.tilesLoaded === false) return false;
|
if (globe?.show && globe.tilesLoaded === false) return false;
|
||||||
|
|||||||
Reference in new issue
Block a user