mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
fix(camera): leave flights and scenes to their framing
Review follow-ups for the globe-in-view recovery and voice limits. - Leave a camera flight in progress and a playing Director scene, holds included, to their own framing. The recovery is now installed with the scene tools, which own the Director, and reads its running state. - Reuse a voice turn's half-degree proof only from the position and direction it was proven at, so a release that moves the camera checks the turn again. - Share one free-globe-camera check between the recovery, the voice turn limit and the globe rotation. - Keep an enableLook setting other code makes after the free-look limit is installed, instead of writing back the install-time value. - Name the nudging test for what it asserts, the 1% floor, and document that a view losing its surface without a camera move waits for the next one.
This commit is contained in:
1 parent
d36b28f352
commit
64377db91a
8 files changed
+137
-44
No files matched your search
@@ -100,8 +100,11 @@ away, such as a saved or shared view that did, or a drag
|
|||||||
that Cesium turns into an in-place look below its 7,500 km trackball height (one
|
that Cesium turns into an in-place look below its 7,500 km trackball height (one
|
||||||
that starts on the stars, or a tilt drag with the screen centre off the planet).
|
that starts on the stars, or a tilt drag with the screen centre off the planet).
|
||||||
Views with more surface than that, such as the oblique orbital Director shots,
|
Views with more surface than that, such as the oblique orbital Director shots,
|
||||||
are left as they are, and so are tracked or locked cameras and the 2D and
|
are left as they are, and so are tracked or locked cameras, a camera flight in
|
||||||
Columbus modes. A voice turn requested while the camera is tracked is judged
|
progress, a Director scene while it plays (holds included) and the 2D and
|
||||||
|
Columbus modes. A view that loses its surface without a camera move (a resize,
|
||||||
|
or turning the scope on) is left as it is until the next move. A voice turn
|
||||||
|
requested while the camera is tracked is judged
|
||||||
by the pose voice's release of the follow camera will leave, before that
|
by the pose voice's release of the follow camera will leave, before that
|
||||||
release, so a refused turn keeps the tracking.
|
release, so a refused turn keeps the tracking.
|
||||||
|
|
||||||
|
|||||||
@@ -399,6 +399,26 @@ test('free look follows camera height and is restored on dispose', () => {
|
|||||||
assert.equal(f.listeners.size, 0);
|
assert.equal(f.listeners.size, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('free look keeps a setting other code makes after it is installed', () => {
|
||||||
|
const f = freeLookFixture({ height: 500 });
|
||||||
|
const dispose = installFreeLookHeightLimit(f.viewer);
|
||||||
|
f.controller.enableLook = false;
|
||||||
|
f.frame(10);
|
||||||
|
assert.equal(f.controller.enableLook, false, 'turned off stays off');
|
||||||
|
f.frame(FREE_LOOK_MAX_HEIGHT_METERS * 20);
|
||||||
|
f.frame(10);
|
||||||
|
assert.equal(f.controller.enableLook, false, 'and stays off after orbit');
|
||||||
|
f.controller.enableLook = true;
|
||||||
|
f.frame(10);
|
||||||
|
assert.equal(f.controller.enableLook, true, 'turned back on');
|
||||||
|
f.frame(FREE_LOOK_MAX_HEIGHT_METERS * 20);
|
||||||
|
assert.equal(f.controller.enableLook, false, 'still limited from orbit');
|
||||||
|
f.frame(10);
|
||||||
|
f.controller.enableLook = false;
|
||||||
|
dispose();
|
||||||
|
assert.equal(f.controller.enableLook, false, 'dispose keeps a later setting');
|
||||||
|
});
|
||||||
|
|
||||||
test('free look stays off when the viewer had it disabled', () => {
|
test('free look stays off when the viewer had it disabled', () => {
|
||||||
const f = freeLookFixture({ enableLook: false, height: 500 });
|
const f = freeLookFixture({ enableLook: false, height: 500 });
|
||||||
installFreeLookHeightLimit(f.viewer);
|
installFreeLookHeightLimit(f.viewer);
|
||||||
@@ -418,6 +438,7 @@ function realCamera({ width = 1600, height = 900, mode } = {}) {
|
|||||||
pixelRatio: 1,
|
pixelRatio: 1,
|
||||||
ellipsoid: Cesium.Ellipsoid.WGS84,
|
ellipsoid: Cesium.Ellipsoid.WGS84,
|
||||||
mapMode2D: Cesium.MapMode2D.INFINITE_SCROLL,
|
mapMode2D: Cesium.MapMode2D.INFINITE_SCROLL,
|
||||||
|
tweens: new Cesium.TweenCollection(),
|
||||||
renders: 0,
|
renders: 0,
|
||||||
requestRender() {
|
requestRender() {
|
||||||
this.renders += 1;
|
this.renders += 1;
|
||||||
@@ -674,6 +695,29 @@ test('the recovery follows the scope: corner-only views are turned back only beh
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the recovery leaves a camera flight and an authored scene alone', () => {
|
||||||
|
let playing = true;
|
||||||
|
const { camera, scene, viewer } = realCamera({ width: 1225, height: 923 });
|
||||||
|
scene.camera = camera;
|
||||||
|
scene.screenSpaceCameraController = { enableInputs: true };
|
||||||
|
installGlobeInViewRecovery(viewer, { paused: () => playing });
|
||||||
|
aim(camera, reportedStuckView);
|
||||||
|
camera.moveEnd.raiseEvent();
|
||||||
|
assert.equal(globeOutOfFrame(camera), true, 'a playing scene is left alone');
|
||||||
|
playing = false;
|
||||||
|
|
||||||
|
// A flight that pauses looking away is left alone until it ends.
|
||||||
|
camera.flyTo({
|
||||||
|
destination: Cesium.Cartesian3.fromDegrees(10, 20, 18_000_000),
|
||||||
|
duration: 5,
|
||||||
|
});
|
||||||
|
camera.moveEnd.raiseEvent();
|
||||||
|
assert.equal(globeOutOfFrame(camera), true, 'a flight is left alone');
|
||||||
|
camera.cancelFlight();
|
||||||
|
camera.moveEnd.raiseEvent();
|
||||||
|
assert.ok(Cesium.Math.equalsEpsilon(camera.pitch, -Math.PI / 2, 1e-6));
|
||||||
|
});
|
||||||
|
|
||||||
function fakeTimers() {
|
function fakeTimers() {
|
||||||
let next = 1;
|
let next = 1;
|
||||||
const pending = new Map();
|
const pending = new Map();
|
||||||
|
|||||||
@@ -3,12 +3,9 @@ import * as Cesium from 'cesium';
|
|||||||
import {
|
import {
|
||||||
createApplicationViewer,
|
createApplicationViewer,
|
||||||
installFreeLookHeightLimit,
|
installFreeLookHeightLimit,
|
||||||
installGlobeInViewRecovery,
|
|
||||||
installTrackpadPinchZoom,
|
installTrackpadPinchZoom,
|
||||||
} from '../app/viewer.js';
|
} from '../app/viewer.js';
|
||||||
import { registerDataCredits } from '../data/dataCredits.js';
|
import { registerDataCredits } from '../data/dataCredits.js';
|
||||||
import { KEYHOLE_OUTER_RADIUS } from '../celestialRing.js';
|
|
||||||
import { isScopeMaskEnabled } from '../scopeMask.js';
|
|
||||||
import { configureCreditKeyboardAccess } from '../creditKeyboard.js';
|
import { configureCreditKeyboardAccess } from '../creditKeyboard.js';
|
||||||
import { MapStackController } from '../mapStackController.js';
|
import { MapStackController } from '../mapStackController.js';
|
||||||
import { loadPhotorealisticTileset } from '../mapStartup.js';
|
import { loadPhotorealisticTileset } from '../mapStartup.js';
|
||||||
@@ -63,12 +60,6 @@ export async function createApplicationScene({
|
|||||||
});
|
});
|
||||||
defer(installTrackpadPinchZoom(viewer));
|
defer(installTrackpadPinchZoom(viewer));
|
||||||
defer(installFreeLookHeightLimit(viewer));
|
defer(installFreeLookHeightLimit(viewer));
|
||||||
defer(
|
|
||||||
installGlobeInViewRecovery(viewer, {
|
|
||||||
// The scope mask hides everything outside its keyhole.
|
|
||||||
keyholeRadius: () => (isScopeMaskEnabled() ? KEYHOLE_OUTER_RADIUS : 0),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
registerDataCredits(viewer, credits);
|
registerDataCredits(viewer, credits);
|
||||||
configureCreditKeyboardAccess(document);
|
configureCreditKeyboardAccess(document);
|
||||||
defer(onLocaleChange(() => configureCreditKeyboardAccess(document)));
|
defer(onLocaleChange(() => configureCreditKeyboardAccess(document)));
|
||||||
|
|||||||
+15
-1
@@ -8,7 +8,13 @@ import { initImageryBoxTool } from '../ui/imageryBoxTool.js';
|
|||||||
import { createRecentImageryPanel } from '../ui/recentImagery.js';
|
import { createRecentImageryPanel } from '../ui/recentImagery.js';
|
||||||
import { initGevVoiceCommands } from '../voice/gevRealtime.js';
|
import { initGevVoiceCommands } from '../voice/gevRealtime.js';
|
||||||
import { installViews, isEmbeddedInline } from './embed.js';
|
import { installViews, isEmbeddedInline } from './embed.js';
|
||||||
import { installScopeMask, destroyScopeMask } from '../scopeMask.js';
|
import {
|
||||||
|
installScopeMask,
|
||||||
|
destroyScopeMask,
|
||||||
|
isScopeMaskEnabled,
|
||||||
|
} from '../scopeMask.js';
|
||||||
|
import { KEYHOLE_OUTER_RADIUS } from '../celestialRing.js';
|
||||||
|
import { installGlobeInViewRecovery } from './viewer.js';
|
||||||
import { installLayerReleaseTrim } from '../sceneCommandRelease.js';
|
import { installLayerReleaseTrim } from '../sceneCommandRelease.js';
|
||||||
import {
|
import {
|
||||||
installRenderGovernor,
|
installRenderGovernor,
|
||||||
@@ -44,6 +50,14 @@ export function createApplicationTools({
|
|||||||
.get('bhote-koshi-2026')
|
.get('bhote-koshi-2026')
|
||||||
?.module.attachSceneController(sceneDirector);
|
?.module.attachSceneController(sceneDirector);
|
||||||
defer(() => sceneDirector.destroy());
|
defer(() => sceneDirector.destroy());
|
||||||
|
defer(
|
||||||
|
installGlobeInViewRecovery(viewer, {
|
||||||
|
// The scope mask hides everything outside its keyhole.
|
||||||
|
keyholeRadius: () => (isScopeMaskEnabled() ? KEYHOLE_OUTER_RADIUS : 0),
|
||||||
|
// An authored scene frames its own shots, holds included.
|
||||||
|
paused: () => sceneDirector.running,
|
||||||
|
}),
|
||||||
|
);
|
||||||
onSceneDirector?.(sceneDirector);
|
onSceneDirector?.(sceneDirector);
|
||||||
const annotations = initAnnotations({
|
const annotations = initAnnotations({
|
||||||
viewer,
|
viewer,
|
||||||
|
|||||||
+18
-9
@@ -4,6 +4,7 @@ import { StarrySky } from '../sky/starrySky.js';
|
|||||||
import {
|
import {
|
||||||
FREE_LOOK_MAX_HEIGHT_METERS,
|
FREE_LOOK_MAX_HEIGHT_METERS,
|
||||||
globeOutOfFrame,
|
globeOutOfFrame,
|
||||||
|
isFreeGlobeCamera,
|
||||||
} from '../globeInView.js';
|
} from '../globeInView.js';
|
||||||
|
|
||||||
const PINCH_ZOOM_MULTIPLIER = 8;
|
const PINCH_ZOOM_MULTIPLIER = 8;
|
||||||
@@ -109,7 +110,9 @@ export function installTrackpadPinchZoom(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow Cesium's free look only below `maxHeight` and return its disposer.
|
* Allow Cesium's free look only below `maxHeight` and return its disposer.
|
||||||
* Orbit, tilt and zoom stay available at every height. `enableLook` gates the
|
* Orbit, tilt and zoom stay available at every height. A value other code
|
||||||
|
* sets later is kept as the one to allow below that height, and restored on
|
||||||
|
* dispose. `enableLook` gates the
|
||||||
* Shift+drag binding only; a drag that starts on the stars below Cesium's
|
* Shift+drag binding only; a drag that starts on the stars below Cesium's
|
||||||
* trackball height still turns the camera in place, which
|
* trackball height still turns the camera in place, which
|
||||||
* `installGlobeInViewRecovery` catches.
|
* `installGlobeInViewRecovery` catches.
|
||||||
@@ -124,11 +127,14 @@ export function installFreeLookHeightLimit(
|
|||||||
if (!controller || !camera || !scene?.preUpdate)
|
if (!controller || !camera || !scene?.preUpdate)
|
||||||
throw new TypeError('A complete Cesium viewer is required');
|
throw new TypeError('A complete Cesium viewer is required');
|
||||||
|
|
||||||
const originalEnableLook = controller.enableLook;
|
let wanted = controller.enableLook;
|
||||||
|
let written;
|
||||||
const sync = () => {
|
const sync = () => {
|
||||||
|
if (written !== undefined && controller.enableLook !== written)
|
||||||
|
wanted = controller.enableLook;
|
||||||
const height = camera.positionCartographic?.height;
|
const height = camera.positionCartographic?.height;
|
||||||
controller.enableLook =
|
written = wanted && !(Number.isFinite(height) && height > maxHeight);
|
||||||
originalEnableLook && !(Number.isFinite(height) && height > maxHeight);
|
controller.enableLook = written;
|
||||||
};
|
};
|
||||||
sync();
|
sync();
|
||||||
const removeListener = scene.preUpdate.addEventListener(sync);
|
const removeListener = scene.preUpdate.addEventListener(sync);
|
||||||
@@ -138,7 +144,7 @@ export function installFreeLookHeightLimit(
|
|||||||
if (!active) return;
|
if (!active) return;
|
||||||
active = false;
|
active = false;
|
||||||
removeListener();
|
removeListener();
|
||||||
controller.enableLook = originalEnableLook;
|
if (controller.enableLook === written) controller.enableLook = wanted;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +165,9 @@ const SETTLE_GIVE_UP_MS = 5 * 60_000;
|
|||||||
* catches a saved or shared view that looked away, and a drag that Cesium turns
|
* catches a saved or shared view that looked away, and a drag that Cesium turns
|
||||||
* into an in-place look below its trackball height whatever `enableLook` says.
|
* into an in-place look below its trackball height whatever `enableLook` says.
|
||||||
* Heading and position are kept. Tracked cameras, locked ones (non-identity
|
* Heading and position are kept. Tracked cameras, locked ones (non-identity
|
||||||
* transform) and the 2D and Columbus modes are left alone. `keyholeRadius`
|
* transform), a camera flight in progress, the 2D and Columbus modes, and any
|
||||||
|
* time `paused` returns true (an authored scene playing) are left alone.
|
||||||
|
* `keyholeRadius`
|
||||||
* returns the radius of the visible circle when a scope mask hides the rest of
|
* returns the radius of the visible circle when a scope mask hides the rest of
|
||||||
* the frame (see `globeFrameShare`), or 0; it is read at every check.
|
* the frame (see `globeFrameShare`), or 0; it is read at every check.
|
||||||
*
|
*
|
||||||
@@ -179,6 +187,7 @@ export function installGlobeInViewRecovery(
|
|||||||
{
|
{
|
||||||
minHeight = FREE_LOOK_MAX_HEIGHT_METERS,
|
minHeight = FREE_LOOK_MAX_HEIGHT_METERS,
|
||||||
keyholeRadius = () => 0,
|
keyholeRadius = () => 0,
|
||||||
|
paused = () => false,
|
||||||
setTimeout: schedule = globalThis.setTimeout,
|
setTimeout: schedule = globalThis.setTimeout,
|
||||||
clearTimeout: unschedule = globalThis.clearTimeout,
|
clearTimeout: unschedule = globalThis.clearTimeout,
|
||||||
} = {},
|
} = {},
|
||||||
@@ -189,9 +198,9 @@ export function installGlobeInViewRecovery(
|
|||||||
throw new TypeError('A complete Cesium viewer is required');
|
throw new TypeError('A complete Cesium viewer is required');
|
||||||
|
|
||||||
const recover = () => {
|
const recover = () => {
|
||||||
if (scene.mode !== Cesium.SceneMode.SCENE3D || viewer.trackedEntity) return;
|
if (!isFreeGlobeCamera(viewer)) return;
|
||||||
if (!Cesium.Matrix4.equals(camera.transform, Cesium.Matrix4.IDENTITY))
|
// Cesium has no public running-flight flag.
|
||||||
return;
|
if (camera._currentFlight || paused()) return;
|
||||||
const height = camera.positionCartographic?.height;
|
const height = camera.positionCartographic?.height;
|
||||||
if (
|
if (
|
||||||
!(height > minHeight) ||
|
!(height > minHeight) ||
|
||||||
|
|||||||
@@ -1526,7 +1526,7 @@ test('a voice turn after something else turned the camera starts over', (t) => {
|
|||||||
assert.ok(globeFrameShare(camera) >= start - 2 / (32 * 18) - 1e-9);
|
assert.ok(globeFrameShare(camera) >= start - 2 / (32 * 18) - 1e-9);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('nudging the camera between voice turns cannot slide the planet away', (t) => {
|
test('nudging the camera between voice turns cannot take the planet below the 1% floor', (t) => {
|
||||||
withScope(t, false); // the whole frame is visible
|
withScope(t, false); // the whole frame is visible
|
||||||
// A wheel notch or an orbit drag between requests starts each turn afresh;
|
// A wheel notch or an orbit drag between requests starts each turn afresh;
|
||||||
// the 1% floor still holds, and below it a turn may only keep what it has.
|
// the 1% floor still holds, and below it a turn may only keep what it has.
|
||||||
@@ -1810,6 +1810,32 @@ test('a refused voice turn leaves a tracked camera tracking', (t) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a turn proven at one position is checked again after a release moves the camera', (t) => {
|
||||||
|
withScope(t, false);
|
||||||
|
const { camera, tick } = harness(t, {
|
||||||
|
pitch: -80,
|
||||||
|
viewportWidth: 1600,
|
||||||
|
viewportHeight: 900,
|
||||||
|
});
|
||||||
|
// Tilt up to the limit, then check from 4,000 km closer along the same view
|
||||||
|
// direction, where the next tilt fits; the release moves the camera back.
|
||||||
|
for (let i = 0; i < 40; i += 1) {
|
||||||
|
if (!moveCamera({ motion: 'tilt', direction: 'up', mode: 'once' }).ok)
|
||||||
|
break;
|
||||||
|
for (let j = 0; j < 600 && getActiveCameraMotion(); j += 1) tick(1 / 60);
|
||||||
|
}
|
||||||
|
camera.moveForward(4_000_000);
|
||||||
|
const result = moveCamera(
|
||||||
|
{ motion: 'tilt', direction: 'up', mode: 'once' },
|
||||||
|
(start) => {
|
||||||
|
camera.moveBackward(4_000_000);
|
||||||
|
return start();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert.equal(result.ok, false);
|
||||||
|
assert.match(result.error, /globe out of view/);
|
||||||
|
});
|
||||||
|
|
||||||
test('the voice frame limit leaves Columbus view alone', (t) => {
|
test('the voice frame limit leaves Columbus view alone', (t) => {
|
||||||
// At its limit in 3D a further tilt up is refused; the same tilt in Columbus
|
// At its limit in 3D a further tilt up is refused; the same tilt in Columbus
|
||||||
// view is not limited by the frame.
|
// view is not limited by the frame.
|
||||||
|
|||||||
+16
-22
@@ -22,6 +22,7 @@ import * as Cesium from 'cesium';
|
|||||||
import { GLOBE_VIEW } from './locations.js';
|
import { GLOBE_VIEW } from './locations.js';
|
||||||
import {
|
import {
|
||||||
globeFrameShare,
|
globeFrameShare,
|
||||||
|
isFreeGlobeCamera,
|
||||||
lookAngleWithinFrame,
|
lookAngleWithinFrame,
|
||||||
lookStepLosesGlobe,
|
lookStepLosesGlobe,
|
||||||
} from './globeInView.js';
|
} from './globeInView.js';
|
||||||
@@ -1015,18 +1016,6 @@ export function interruptCameraMotionIfActive(
|
|||||||
return interruptCameraMotion(reason);
|
return interruptCameraMotion(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A free globe camera: 3D, not tracking, not locked to a frame. The frame
|
|
||||||
* limit for in-place turns applies to it alone, as the move-end recovery does.
|
|
||||||
*/
|
|
||||||
function isFreeGlobeCamera(cam) {
|
|
||||||
return (
|
|
||||||
_viewer.scene.mode === Cesium.SceneMode.SCENE3D &&
|
|
||||||
!_viewer.trackedEntity &&
|
|
||||||
Cesium.Matrix4.equals(cam.transform, Cesium.Matrix4.IDENTITY)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The frame share that in-place voice turns measure their limit against, with
|
// The frame share that in-place voice turns measure their limit against, with
|
||||||
// the pose the last voice turn left the camera in. A turn that starts where
|
// the pose the last voice turn left the camera in. A turn that starts where
|
||||||
// the last one left off keeps that baseline (raised if the planet now fills
|
// the last one left off keeps that baseline (raised if the planet now fills
|
||||||
@@ -1036,7 +1025,7 @@ function isFreeGlobeCamera(cam) {
|
|||||||
let _lookBaseline = null;
|
let _lookBaseline = null;
|
||||||
|
|
||||||
// The pre-check's proof that a half-degree turn from this pose keeps the planet
|
// The pre-check's proof that a half-degree turn from this pose keeps the planet
|
||||||
// in view: which way, from which direction. A motion that starts from that
|
// in view: which way, from which position and direction. A motion that starts from that
|
||||||
// pose turns at least that far, so a request reported as done really moves
|
// pose turns at least that far, so a request reported as done really moves
|
||||||
// even when a smaller first step lands in a dip of the sampled share.
|
// even when a smaller first step lands in a dip of the sampled share.
|
||||||
const PROVEN_TURN_RAD = Cesium.Math.toRadians(0.5);
|
const PROVEN_TURN_RAD = Cesium.Math.toRadians(0.5);
|
||||||
@@ -1079,7 +1068,7 @@ function lookBaselineShare(cam) {
|
|||||||
*/
|
*/
|
||||||
function lookWithinFrame(cam, m, look, angle) {
|
function lookWithinFrame(cam, m, look, angle) {
|
||||||
let allowed = angle;
|
let allowed = angle;
|
||||||
if (isFreeGlobeCamera(cam)) {
|
if (isFreeGlobeCamera(_viewer)) {
|
||||||
if (m.globeShareAtStart === undefined) {
|
if (m.globeShareAtStart === undefined) {
|
||||||
m.globeShareAtStart = lookBaselineShare(cam);
|
m.globeShareAtStart = lookBaselineShare(cam);
|
||||||
m.provenTurnRad = lookProven(cam, look) ? PROVEN_TURN_RAD : 0;
|
m.provenTurnRad = lookProven(cam, look) ? PROVEN_TURN_RAD : 0;
|
||||||
@@ -1105,6 +1094,12 @@ function lookWithinFrame(cam, m, look, angle) {
|
|||||||
function lookProven(cam, look) {
|
function lookProven(cam, look) {
|
||||||
return (
|
return (
|
||||||
_provenLook?.look === look &&
|
_provenLook?.look === look &&
|
||||||
|
Cesium.Cartesian3.equalsEpsilon(
|
||||||
|
_provenLook.position,
|
||||||
|
cam.positionWC,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
) &&
|
||||||
Cesium.Cartesian3.equalsEpsilon(
|
Cesium.Cartesian3.equalsEpsilon(
|
||||||
_provenLook.direction,
|
_provenLook.direction,
|
||||||
cam.directionWC,
|
cam.directionWC,
|
||||||
@@ -1129,7 +1124,7 @@ function checkLookFromOrbit(look, { released = false } = {}) {
|
|||||||
_provenLook = null;
|
_provenLook = null;
|
||||||
if (_viewer.scene.mode !== Cesium.SceneMode.SCENE3D) return null;
|
if (_viewer.scene.mode !== Cesium.SceneMode.SCENE3D) return null;
|
||||||
const cam = _viewer.camera;
|
const cam = _viewer.camera;
|
||||||
if (!released && !isFreeGlobeCamera(cam)) return null;
|
if (!released && !isFreeGlobeCamera(_viewer)) return null;
|
||||||
if (
|
if (
|
||||||
lookStepLosesGlobe(cam, look, PROVEN_TURN_RAD, {
|
lookStepLosesGlobe(cam, look, PROVEN_TURN_RAD, {
|
||||||
...visibleFrame(),
|
...visibleFrame(),
|
||||||
@@ -1142,7 +1137,11 @@ function checkLookFromOrbit(look, { released = false } = {}) {
|
|||||||
error:
|
error:
|
||||||
'From this height that would turn the globe out of view — zoom in first, or turn the other way.',
|
'From this height that would turn the globe out of view — zoom in first, or turn the other way.',
|
||||||
};
|
};
|
||||||
_provenLook = { look, direction: Cesium.Cartesian3.clone(cam.directionWC) };
|
_provenLook = {
|
||||||
|
look,
|
||||||
|
position: Cesium.Cartesian3.clone(cam.positionWC),
|
||||||
|
direction: Cesium.Cartesian3.clone(cam.directionWC),
|
||||||
|
};
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1329,12 +1328,7 @@ function onTick() {
|
|||||||
// Tracking can acquire the camera without going through navigation.
|
// Tracking can acquire the camera without going through navigation.
|
||||||
// Yield without resetting its pose/frame: the world-space arc proof
|
// Yield without resetting its pose/frame: the world-space arc proof
|
||||||
// only describes Cesium's rotation while the frame is identity.
|
// only describes Cesium's rotation while the frame is identity.
|
||||||
if (
|
if (!isFreeGlobeCamera(_viewer) || _isCockpitActive?.()) {
|
||||||
_viewer.scene.mode !== Cesium.SceneMode.SCENE3D ||
|
|
||||||
_viewer.trackedEntity ||
|
|
||||||
_isCockpitActive?.() ||
|
|
||||||
!Cesium.Matrix4.equals(cam.transform, Cesium.Matrix4.IDENTITY)
|
|
||||||
) {
|
|
||||||
interruptCameraMotion('globe-camera-owner-changed');
|
interruptCameraMotion('globe-camera-owner-changed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,18 @@ export const FREE_LOOK_MAX_HEIGHT_METERS = 1_000_000;
|
|||||||
// the planet's surface.
|
// the planet's surface.
|
||||||
export const MIN_GLOBE_FRAME_SHARE = 0.1;
|
export const MIN_GLOBE_FRAME_SHARE = 0.1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A free globe camera: the viewer is in 3D, follows no tracked entity, and its
|
||||||
|
* camera is not locked to a reference frame.
|
||||||
|
*/
|
||||||
|
export function isFreeGlobeCamera(viewer) {
|
||||||
|
return (
|
||||||
|
viewer.scene.mode === Cesium.SceneMode.SCENE3D &&
|
||||||
|
!viewer.trackedEntity &&
|
||||||
|
Cesium.Matrix4.equals(viewer.camera.transform, Cesium.Matrix4.IDENTITY)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const GRID_COLUMNS = 32;
|
const GRID_COLUMNS = 32;
|
||||||
const GRID_ROWS = 18;
|
const GRID_ROWS = 18;
|
||||||
// A turn across a sliver of planet moves its edge through sample points, so
|
// A turn across a sliver of planet moves its edge through sample points, so
|
||||||
|
|||||||
Reference in new issue
Block a user