mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
fix(infrastructure): test ground below 0 m and unknown terrain correctly
The ellipsoidal horizon test hid ground below the ellipsoid (low-geoid coasts, the Dead Sea) from a camera near it, so it is now tested at 0 m. With the globe hidden the ground is tested as the vertical from 0 to 9,000 m against the frustum planes, in place of a 4.5 km sphere that let in much unseen ground. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ecc0ced47c
commit
f392eecbd9
3 files changed
+130
-86
No files matched your search
@@ -1959,11 +1959,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, samples only markers whose ground is in the
|
visible globe tiles to settle, and skips markers whose ground the camera
|
||||||
camera's view and above the horizon (unseen ground reads through the Earth
|
cannot see (unseen ground reads through the Earth and would retry every 2 s).
|
||||||
and would retry every 2 s; until it is seen, a stem keeps its unsampled
|
Ground is tested at loaded globe terrain, or across 0-9,000 m when the globe
|
||||||
height, which can show for a stem whose base is just off screen), and uses
|
is hidden, so photoreal views skip only ground well out of view. Until it is
|
||||||
valid loaded terrain as a floor while
|
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
|
||||||
|
|||||||
@@ -113,8 +113,13 @@ async function createRealLocalLayerHarness({
|
|||||||
upWC: new Cesium.Cartesian3(),
|
upWC: new Cesium.Cartesian3(),
|
||||||
frustum: {
|
frustum: {
|
||||||
fov: Math.PI / 3,
|
fov: Math.PI / 3,
|
||||||
|
// No planes: everything is in view. One plane no point satisfies:
|
||||||
|
// nothing is.
|
||||||
computeCullingVolume: () => ({
|
computeCullingVolume: () => ({
|
||||||
computeVisibility: () => baseVisibility,
|
planes:
|
||||||
|
baseVisibility === Cesium.Intersect.OUTSIDE
|
||||||
|
? [new Cesium.Cartesian4(0, 0, 0, -1)]
|
||||||
|
: [],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
moveEnd,
|
moveEnd,
|
||||||
@@ -787,64 +792,95 @@ test('an unseen base is not sampled and arms no retry until it is in view', asyn
|
|||||||
assert.ok(Math.abs(baseHeightM(env) - 117) < 0.01);
|
assert.ok(Math.abs(baseHeightM(env) - 117) < 0.01);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const [label, globe] of [
|
/**
|
||||||
['loaded terrain', { show: true, tilesLoaded: true, getHeight: () => 2_500 }],
|
* Give the harness a real Cesium camera `southM` metres south of the base at
|
||||||
['a hidden globe', { show: false, tilesLoaded: true, getHeight: () => undefined }],
|
* `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 }) {
|
||||||
|
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: Math.PI / 3,
|
||||||
|
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, baseOffScreen } of [
|
||||||
|
// 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(`high ground in view is sampled with ${label} although its unsampled base projects off screen`, async (t) => {
|
test(`${label} in view is sampled`, async (t) => {
|
||||||
// A base on 2,500 m terrain, seen from 3 km away at a shallow angle: its
|
|
||||||
// ellipsoid-height point is far below the bottom of the screen.
|
|
||||||
const env = await createRealLocalLayerHarness({
|
const env = await createRealLocalLayerHarness({
|
||||||
sampleHeightSupported: true, sampleHeight: () => 2_500,
|
sampleHeightSupported: true, sampleHeight: () => groundM,
|
||||||
});
|
});
|
||||||
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 entity = env.dataSources[0].entities.values[0];
|
const baseVisibility = aimRealCamera(env, { groundM, cameraM, southM });
|
||||||
const base = Cesium.Cartographic.fromCartesian(entity.__localBaseCartesian);
|
if (baseOffScreen)
|
||||||
const ground = Cesium.Cartesian3.fromRadians(base.longitude, base.latitude, 2_500);
|
assert.equal(baseVisibility, Cesium.Intersect.OUTSIDE, 'precondition: the ellipsoid-height base is off screen');
|
||||||
const position = Cesium.Cartesian3.fromRadians(
|
|
||||||
base.longitude,
|
|
||||||
base.latitude - 3_000 / 6_371_000,
|
|
||||||
2_500 + 1_000,
|
|
||||||
);
|
|
||||||
const direction = Cesium.Cartesian3.normalize(
|
|
||||||
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: Math.PI / 3,
|
|
||||||
aspectRatio: 800 / 600,
|
|
||||||
near: 1,
|
|
||||||
far: 1e8,
|
|
||||||
});
|
|
||||||
Object.assign(env.viewer.camera, {
|
|
||||||
positionWC: position,
|
|
||||||
directionWC: direction,
|
|
||||||
upWC: up,
|
|
||||||
frustum,
|
|
||||||
});
|
|
||||||
const offScreen = frustum
|
|
||||||
.computeCullingVolume(position, direction, up)
|
|
||||||
.computeVisibility(new Cesium.BoundingSphere(entity.__localBaseCartesian, 1));
|
|
||||||
assert.equal(offScreen, Cesium.Intersect.OUTSIDE, 'precondition: the ellipsoid-height base is off screen');
|
|
||||||
|
|
||||||
clock.advance(2_100);
|
clock.advance(2_100);
|
||||||
env.preRender.raise();
|
env.preRender.raise();
|
||||||
assert.equal(env.sampleCalls.count, 1, 'the ground in view is sampled');
|
assert.equal(env.sampleCalls.count, 1, 'the ground in view is sampled');
|
||||||
assert.ok(Math.abs(baseHeightM(env) - 2_500) < 0.01);
|
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,
|
||||||
|
|||||||
@@ -1131,17 +1131,25 @@ function refreshLocalTerrainFloor(viewer, record) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const groundSampleSphere = new Cesium.BoundingSphere(
|
|
||||||
new Cesium.Cartesian3(),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
|
|
||||||
const groundSampleOccluder = new Cesium.EllipsoidalOccluder(
|
const groundSampleOccluder = new Cesium.EllipsoidalOccluder(
|
||||||
Cesium.Ellipsoid.WGS84,
|
Cesium.Ellipsoid.WGS84,
|
||||||
);
|
);
|
||||||
|
|
||||||
const groundSampleCarto = new Cesium.Cartographic();
|
const groundSampleCarto = new Cesium.Cartographic();
|
||||||
const groundSampleTop = new Cesium.Cartesian3();
|
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
|
* Whether the camera can see the ground under the record: inside the view
|
||||||
@@ -1150,10 +1158,10 @@ const groundSampleTop = new Cesium.Cartesian3();
|
|||||||
* the Earth (about -6,344 km near Ashburn on Esri) and fails on every 2 s
|
* 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
|
* 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, and otherwise every height a
|
* uses loaded globe terrain when there is some. Otherwise (a hidden globe) it
|
||||||
* sample can accept (0 to GROUND_SAMPLE_MAX_ABS_HEIGHT_M): high ground in
|
* tests the vertical from 0 to GROUND_SAMPLE_MAX_ABS_HEIGHT_M, every height a
|
||||||
* view is never mistaken for an unseen base. Unseen ground is sampled on the
|
* sample can accept, so high ground in view is never mistaken for an unseen
|
||||||
* first walk that sees it.
|
* 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;
|
||||||
@@ -1162,37 +1170,36 @@ 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 span = GROUND_SAMPLE_MAX_ABS_HEIGHT_M / 2;
|
const low = groundSamplePoint(record, known ? terrain : 0, groundSampleLow);
|
||||||
groundSampleCarto.longitude = record.carto.longitude;
|
const high = known
|
||||||
groundSampleCarto.latitude = record.carto.latitude;
|
? low
|
||||||
groundSampleCarto.height = known ? terrain : span;
|
: groundSamplePoint(
|
||||||
Cesium.Cartographic.toCartesian(
|
record,
|
||||||
groundSampleCarto,
|
GROUND_SAMPLE_MAX_ABS_HEIGHT_M,
|
||||||
Cesium.Ellipsoid.WGS84,
|
groundSampleHigh,
|
||||||
groundSampleSphere.center,
|
);
|
||||||
);
|
// The vertical is unseen only when both ends are outside the same plane.
|
||||||
groundSampleSphere.radius = known ? 1 : span;
|
const { planes } = camera.frustum.computeCullingVolume(
|
||||||
const culling = camera.frustum.computeCullingVolume(
|
|
||||||
camera.positionWC,
|
camera.positionWC,
|
||||||
camera.directionWC,
|
camera.directionWC,
|
||||||
camera.upWC,
|
camera.upWC,
|
||||||
);
|
);
|
||||||
if (
|
for (const plane of planes) {
|
||||||
culling.computeVisibility(groundSampleSphere) === Cesium.Intersect.OUTSIDE
|
if (
|
||||||
)
|
Cesium.Cartesian3.dot(plane, low) + plane.w < 0 &&
|
||||||
return false;
|
Cesium.Cartesian3.dot(plane, high) + plane.w < 0
|
||||||
// The highest ground the sample could find is the last to drop below the
|
)
|
||||||
// horizon.
|
return false;
|
||||||
if (!known) groundSampleCarto.height = GROUND_SAMPLE_MAX_ABS_HEIGHT_M;
|
}
|
||||||
|
// 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;
|
groundSampleOccluder.cameraPosition = camera.positionWC;
|
||||||
return groundSampleOccluder.isPointVisible(
|
return groundSampleOccluder.isPointVisible(
|
||||||
known
|
groundSamplePoint(record, horizonHeight, groundSampleHigh),
|
||||||
? groundSampleSphere.center
|
|
||||||
: Cesium.Cartographic.toCartesian(
|
|
||||||
groundSampleCarto,
|
|
||||||
Cesium.Ellipsoid.WGS84,
|
|
||||||
groundSampleTop,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user