mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
Stop zooming out while the globe is still in view
Cesium sets no limit, so zooming out left the globe a speck among the stars. On the globe a hand now stops zooming out where the globe spans 30% of the screen's height, which follows the screen's shape as the app's own whole-globe view does. A camera already past it, after a flight or a turned screen, stops there rather than jumping in at the next zoom. Flat maps keep zooming out unlimited.
This commit is contained in:
1 parent
0aa54dfa97
commit
e7973e81cf
2 files changed
+116
No files matched your search
@@ -2,6 +2,48 @@ import * as Cesium from 'cesium';
|
||||
import { applyModelAtmosphereWorkaround } from './atmosphereCompat.js';
|
||||
import { StarrySky } from '../sky/starrySky.js';
|
||||
|
||||
/**
|
||||
* At the farthest a hand can zoom out, the globe is this share of the
|
||||
* screen's height across. Cesium sets no limit, so zooming on left the globe
|
||||
* a speck among the stars.
|
||||
*/
|
||||
const MIN_GLOBE_SHARE = 0.3;
|
||||
|
||||
/**
|
||||
* The farthest a hand can zoom out, in metres above the ellipsoid, for a
|
||||
* vertical field of view. It follows the screen's shape, as the app's own
|
||||
* whole-globe view does, so that view always lies within it.
|
||||
* @param {number} fovy Vertical field of view, radians.
|
||||
*/
|
||||
export function zoomOutLimit(fovy) {
|
||||
const radius = Cesium.Ellipsoid.WGS84.maximumRadius;
|
||||
const angular = Math.atan(MIN_GLOBE_SHARE * Math.tan(fovy / 2));
|
||||
return radius / Math.sin(angular) - radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hold a hand's zoom out to `zoomOutLimit` on the globe, each frame, since the
|
||||
* field of view follows the screen's shape. The limit never sits below the
|
||||
* camera: one already past it (a flight, a screen turned) stops there rather
|
||||
* than jumping in at the next zoom. Flat maps keep Cesium's own, unlimited.
|
||||
* @returns {() => void} Stop limiting.
|
||||
*/
|
||||
export function limitZoomOut(viewer) {
|
||||
const { scene, camera } = viewer;
|
||||
return scene.preRender.addEventListener(() => {
|
||||
const controller = scene.screenSpaceCameraController;
|
||||
const fovy = camera.frustum.fovy;
|
||||
if (scene.mode !== Cesium.SceneMode.SCENE3D || !Number.isFinite(fovy)) {
|
||||
controller.maximumZoomDistance = Number.POSITIVE_INFINITY;
|
||||
return;
|
||||
}
|
||||
controller.maximumZoomDistance = Math.max(
|
||||
zoomOutLimit(fovy),
|
||||
camera.positionCartographic?.height ?? 0,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const PINCH_ZOOM_MULTIPLIER = 8;
|
||||
const MAX_PINCH_PIXEL_DELTA = 120;
|
||||
|
||||
@@ -142,6 +184,7 @@ export function createApplicationViewer({ container, creditContainer }) {
|
||||
// required on touch-first screens. Mouse/trackpad sensitivity stays native.
|
||||
if (globalThis.matchMedia?.('(pointer: coarse)').matches)
|
||||
viewer.scene.screenSpaceCameraController.zoomFactor *= 3;
|
||||
limitZoomOut(viewer);
|
||||
// Before any tile builds a draw command: Cesium's per-vertex model
|
||||
// atmosphere fails to LINK on Apple's Metal backend and kills the
|
||||
// render loop. See app/atmosphereCompat.js.
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// A hand zooming out stops while the globe is still a fair share of the
|
||||
// screen, on any screen's shape, rather than leaving it a speck.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as Cesium from 'cesium';
|
||||
import { limitZoomOut, zoomOutLimit } from './viewer.js';
|
||||
|
||||
// The share of the screen's height the globe spans from `altitude`.
|
||||
function globeShare(altitude, fovy) {
|
||||
const radius = Cesium.Ellipsoid.WGS84.maximumRadius;
|
||||
const angular = Math.asin(radius / (radius + altitude));
|
||||
return Math.tan(angular) / Math.tan(fovy / 2);
|
||||
}
|
||||
|
||||
test("zooming out stops with the globe 30% of the screen's height across", () => {
|
||||
for (const fovy of [Math.PI / 3, 0.4, 0.2]) {
|
||||
const limit = zoomOutLimit(fovy);
|
||||
assert.ok(Math.abs(globeShare(limit, fovy) - 0.3) < 1e-9, `fovy ${fovy}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('a wide screen, whose view is narrower, may zoom out farther', () => {
|
||||
assert.ok(zoomOutLimit(0.4) > zoomOutLimit(Math.PI / 3));
|
||||
assert.ok(zoomOutLimit(Math.PI / 3) > 20_000_000, 'past the views a link opens');
|
||||
});
|
||||
|
||||
function fakeViewer({ fovy = Math.PI / 3, height = 10_000_000 } = {}) {
|
||||
const scene = {
|
||||
mode: Cesium.SceneMode.SCENE3D,
|
||||
preRender: new Cesium.Event(),
|
||||
screenSpaceCameraController: { maximumZoomDistance: Infinity },
|
||||
};
|
||||
const camera = {
|
||||
frustum: { fovy },
|
||||
positionCartographic: { height },
|
||||
};
|
||||
return { scene, camera };
|
||||
}
|
||||
|
||||
test('the limit follows the screen, and holds a camera already past it', () => {
|
||||
const viewer = fakeViewer();
|
||||
const controller = viewer.scene.screenSpaceCameraController;
|
||||
const stop = limitZoomOut(viewer);
|
||||
viewer.scene.preRender.raiseEvent();
|
||||
assert.equal(controller.maximumZoomDistance, zoomOutLimit(Math.PI / 3));
|
||||
|
||||
// A wider screen narrows the view, and the globe may go farther.
|
||||
viewer.camera.frustum.fovy = 0.4;
|
||||
viewer.scene.preRender.raiseEvent();
|
||||
assert.equal(controller.maximumZoomDistance, zoomOutLimit(0.4));
|
||||
|
||||
// Flown past it, the next zoom out stops there instead of jumping in.
|
||||
viewer.camera.positionCartographic.height = zoomOutLimit(0.4) * 2;
|
||||
viewer.scene.preRender.raiseEvent();
|
||||
assert.equal(controller.maximumZoomDistance, zoomOutLimit(0.4) * 2);
|
||||
|
||||
stop();
|
||||
viewer.camera.positionCartographic.height = 0;
|
||||
viewer.scene.preRender.raiseEvent();
|
||||
assert.equal(controller.maximumZoomDistance, zoomOutLimit(0.4) * 2);
|
||||
});
|
||||
|
||||
test('flat maps keep zooming out unlimited', () => {
|
||||
const viewer = fakeViewer();
|
||||
const controller = viewer.scene.screenSpaceCameraController;
|
||||
limitZoomOut(viewer);
|
||||
viewer.scene.preRender.raiseEvent();
|
||||
assert.ok(Number.isFinite(controller.maximumZoomDistance));
|
||||
viewer.scene.mode = Cesium.SceneMode.SCENE2D;
|
||||
viewer.camera.frustum = {};
|
||||
viewer.scene.preRender.raiseEvent();
|
||||
assert.equal(controller.maximumZoomDistance, Infinity);
|
||||
});
|
||||
Reference in new issue
Block a user