mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
fix(ui): north-up and tilt keep an engine's own follow (Navara)
Inside a Navara follow the orientation controls frame the follow's target and retarget its viewFrom instead of moving the camera, and the release cancels only a running flight (a cancel is a camera command, which ends the follow). Cesium's trackedEntity path is unchanged. Updated test: cameraOrientationControls.test.mjs (orientation navigation starts a flight before expecting its cancel; no flight, no cancel).
This commit is contained in:
1 parent
dcfb1219d5
commit
2da7b6d0d9
3 files changed
+85
-2
No files matched your search
@@ -24,6 +24,18 @@ function followedEntity(engine, viewer) {
|
|||||||
return handle ? (handle.native ?? null) : (viewer?.trackedEntity ?? null);
|
return handle ? (handle.native ?? null) : (viewer?.trackedEntity ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An engine's own follow (no tracked entity: Navara): its handle, which
|
||||||
|
// knows its target and takes a new offset without ending the follow.
|
||||||
|
function engineFollow(engine, viewer) {
|
||||||
|
const handle = engine?.camera.following?.();
|
||||||
|
return handle &&
|
||||||
|
!followedEntity(engine, viewer) &&
|
||||||
|
typeof handle.target === 'function' &&
|
||||||
|
typeof handle.retarget === 'function'
|
||||||
|
? handle
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
function trackedTarget(entity, viewer) {
|
function trackedTarget(entity, viewer) {
|
||||||
// Read the visual's sample first; sampling a flight CallbackProperty twice
|
// Read the visual's sample first; sampling a flight CallbackProperty twice
|
||||||
// can advance dead reckoning ahead of the displayed aircraft.
|
// can advance dead reckoning ahead of the displayed aircraft.
|
||||||
@@ -89,9 +101,10 @@ export function readCameraTargetFrame(target, tracker) {
|
|||||||
if (!engine) return null;
|
if (!engine) return null;
|
||||||
const viewer = trackerOf(target, tracker);
|
const viewer = trackerOf(target, tracker);
|
||||||
const entity = followedEntity(engine, viewer);
|
const entity = followedEntity(engine, viewer);
|
||||||
|
const follow = engineFollow(engine, viewer);
|
||||||
const center = entity
|
const center = entity
|
||||||
? trackedTarget(entity, viewer)
|
? trackedTarget(entity, viewer)
|
||||||
: pickViewTarget(engine);
|
: (follow?.target() ?? pickViewTarget(engine));
|
||||||
const pose = engine.camera.pose();
|
const pose = engine.camera.pose();
|
||||||
if (!center || !isPickedWorldPosition(pose.position)) return null;
|
if (!center || !isPickedWorldPosition(pose.position)) return null;
|
||||||
|
|
||||||
@@ -133,6 +146,23 @@ export function setCameraTargetFrame(target, frame, tracker) {
|
|||||||
if (!engine || !frame?.target || !Number.isFinite(frame.range)) return false;
|
if (!engine || !frame?.target || !Number.isFinite(frame.range)) return false;
|
||||||
const viewer = trackerOf(target, tracker);
|
const viewer = trackerOf(target, tracker);
|
||||||
const { camera } = engine;
|
const { camera } = engine;
|
||||||
|
// Inside an engine's own follow: the new frame is the follow's offset (a
|
||||||
|
// camera command would end the follow; Cesium's tracked entity keeps its
|
||||||
|
// follow through lookAt below, so both engines keep following).
|
||||||
|
const follow = engineFollow(engine, viewer);
|
||||||
|
if (follow) {
|
||||||
|
const heading = normalizedHeading(frame.heading);
|
||||||
|
const flat = Math.cos(frame.pitch);
|
||||||
|
follow.retarget({
|
||||||
|
viewFrom: geo.cartesian3.create(
|
||||||
|
-frame.range * Math.sin(heading) * flat,
|
||||||
|
-frame.range * Math.cos(heading) * flat,
|
||||||
|
-frame.range * Math.sin(frame.pitch),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
engine.render.request('camera-orientation');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const trackedTransform = followedEntity(engine, viewer)
|
const trackedTransform = followedEntity(engine, viewer)
|
||||||
? camera.transform()
|
? camera.transform()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from './cameraOrientationControls.js';
|
} from './cameraOrientationControls.js';
|
||||||
import { NavigationController } from './navigationController.js';
|
import { NavigationController } from './navigationController.js';
|
||||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||||
|
import * as geo from '../geo/index.js';
|
||||||
import { createCesiumEngine } from '../globe/cesium/createCesiumEngine.js';
|
import { createCesiumEngine } from '../globe/cesium/createCesiumEngine.js';
|
||||||
import { registerViewerEngine } from '../globe/cesium/engineFromViewer.js';
|
import { registerViewerEngine } from '../globe/cesium/engineFromViewer.js';
|
||||||
|
|
||||||
@@ -651,6 +652,10 @@ test('orientation navigation preserves follow and selection; new navigation canc
|
|||||||
cancelFlight();
|
cancelFlight();
|
||||||
};
|
};
|
||||||
let cockpit = false;
|
let cockpit = false;
|
||||||
|
engine.camera.flyTo({
|
||||||
|
destination: geo.cartesian3.fromDegrees(30, 40, 5000),
|
||||||
|
duration: 1,
|
||||||
|
});
|
||||||
const navigation = new NavigationController({
|
const navigation = new NavigationController({
|
||||||
viewer,
|
viewer,
|
||||||
engine,
|
engine,
|
||||||
@@ -675,6 +680,15 @@ test('orientation navigation preserves follow and selection; new navigation canc
|
|||||||
'cancel-flight',
|
'cancel-flight',
|
||||||
]);
|
]);
|
||||||
calls.length = 0;
|
calls.length = 0;
|
||||||
|
// No running flight: a cancel would be a camera command, ending an engine's
|
||||||
|
// own follow, so the orientation leaves the camera alone.
|
||||||
|
navigation.runOrientation('camera', () => 'animated');
|
||||||
|
assert.deepEqual(calls, [
|
||||||
|
'cancel-orientation',
|
||||||
|
'interrupt-motion',
|
||||||
|
'stop-orbit',
|
||||||
|
]);
|
||||||
|
calls.length = 0;
|
||||||
navigation._stampNavigation({ cancelPendingSelection: false });
|
navigation._stampNavigation({ cancelPendingSelection: false });
|
||||||
assert.deepEqual(calls, ['cancel-orientation', 'clear-location']);
|
assert.deepEqual(calls, ['cancel-orientation', 'clear-location']);
|
||||||
calls.length = 0;
|
calls.length = 0;
|
||||||
@@ -761,3 +775,39 @@ test('the controls run on a globe engine alone', (t) => {
|
|||||||
assert.equal(engine.frames.preUpdate.numberOfListeners, 0);
|
assert.equal(engine.frames.preUpdate.numberOfListeners, 0);
|
||||||
assert.equal(engine.camera.moveEnd.numberOfListeners, 0);
|
assert.equal(engine.camera.moveEnd.numberOfListeners, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("north-up and tilt inside an engine's own follow keep following (Navara)", () => {
|
||||||
|
const engine = createFakeEngine();
|
||||||
|
const target = Cesium.Cartesian3.fromDegrees(-73.78, 40.64, 3000);
|
||||||
|
const handle = engine.camera.follow({
|
||||||
|
position: () => target,
|
||||||
|
// 2 km south-east of the target, 1 km up: heading about 315°.
|
||||||
|
viewFrom: { x: 1400, y: -1400, z: 1000 },
|
||||||
|
});
|
||||||
|
engine.fake.frame();
|
||||||
|
const before = readCameraTargetFrame(engine);
|
||||||
|
assert.ok(before, 'a frame around the followed target');
|
||||||
|
sameXYZ(before.target, target, 'the frame centres the follow target');
|
||||||
|
assert.equal(resetCameraNorth(engine), true);
|
||||||
|
engine.fake.frame();
|
||||||
|
assert.equal(engine.camera.following(), handle, 'north-up keeps the follow');
|
||||||
|
const north = readCameraTargetFrame(engine);
|
||||||
|
const headingDeg = Cesium.Math.toDegrees(north.heading);
|
||||||
|
assert.ok(
|
||||||
|
Math.min(headingDeg, 360 - headingDeg) < 0.01,
|
||||||
|
`north-up left the follow at ${headingDeg.toFixed(2)} deg`,
|
||||||
|
);
|
||||||
|
assert.ok(Math.abs(north.range - before.range) < 1, 'same range');
|
||||||
|
assert.ok(Math.abs(north.pitch - before.pitch) < 1e-6, 'same pitch');
|
||||||
|
const tilt = toggleCameraTilt(engine);
|
||||||
|
assert.ok(tilt, 'tilt applied');
|
||||||
|
engine.fake.frame();
|
||||||
|
assert.equal(engine.camera.following(), handle, 'tilt keeps the follow');
|
||||||
|
const tilted = readCameraTargetFrame(engine);
|
||||||
|
assert.ok(
|
||||||
|
Math.abs(tilted.pitch - tilt.pitch) < 1e-6,
|
||||||
|
'the follow took the tilted pitch',
|
||||||
|
);
|
||||||
|
handle.stop();
|
||||||
|
engine.destroy();
|
||||||
|
});
|
||||||
@@ -220,7 +220,10 @@ export class NavigationController {
|
|||||||
release: () => {
|
release: () => {
|
||||||
this.interruptCameraMotion('camera-orientation');
|
this.interruptCameraMotion('camera-orientation');
|
||||||
this.stopOrbit();
|
this.stopOrbit();
|
||||||
this.engine?.camera.cancelFlight();
|
// Only a running flight: a cancel is a camera command, which ends an
|
||||||
|
// engine's own follow (Navara) that the orientation keeps.
|
||||||
|
if (this.engine?.camera.flightActive?.() !== false)
|
||||||
|
this.engine?.camera.cancelFlight();
|
||||||
},
|
},
|
||||||
navigate,
|
navigate,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user