mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
Let the shell's share link, HUD, orbit and navigation follow another globe engine
The shell reads its viewer, engine and map stack from the attached scene. ShareLinkManager, IntelHUD and OrbitController gain attachEngine, NavigationController setScene and LocationNavigation attachScene/detachScene, so a switch can keep them and move only their camera listeners; between engines they write and read nothing. The voice runner and session voice modules are owned by their packages, which check:boundaries needs since the voice commit.
This commit is contained in:
1 parent
741b6f67eb
commit
d1fdb9eb45
9 files changed
+243
-17
No files matched your search
@@ -2660,6 +2660,7 @@
|
|||||||
"src/app/stateChannel.js",
|
"src/app/stateChannel.js",
|
||||||
"src/app/surfaceServices.js",
|
"src/app/surfaceServices.js",
|
||||||
"src/app/tools.js",
|
"src/app/tools.js",
|
||||||
|
"src/app/sessionVoice.js",
|
||||||
"src/app/viewer.js",
|
"src/app/viewer.js",
|
||||||
"src/sky/starrySky.js",
|
"src/sky/starrySky.js",
|
||||||
"src/sky/stars.js",
|
"src/sky/stars.js",
|
||||||
@@ -3203,6 +3204,7 @@
|
|||||||
"src/voice/voiceCardPresentation.js",
|
"src/voice/voiceCardPresentation.js",
|
||||||
"src/voice/resultDisplay.js",
|
"src/voice/resultDisplay.js",
|
||||||
"src/voice/gevRealtime.js",
|
"src/voice/gevRealtime.js",
|
||||||
|
"src/voice/sceneRunner.js",
|
||||||
"src/voice/layerSummary.js",
|
"src/voice/layerSummary.js",
|
||||||
"src/voice/cloudVoiceAuth.js",
|
"src/voice/cloudVoiceAuth.js",
|
||||||
"src/voice/realtimeBackend.js",
|
"src/voice/realtimeBackend.js",
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ test('newer navigation, reset, Cockpit, and teardown share one generation', () =
|
|||||||
'this._navigation.destroy(',
|
'this._navigation.destroy(',
|
||||||
'await this._contextControls.restoreForDisposal();',
|
'await this._contextControls.restoreForDisposal();',
|
||||||
], 'dispose invalidation');
|
], 'dispose invalidation');
|
||||||
assert.match(shellMethod('destroy').toString(), /this\._stampNavigation\(\{cancelPendingSelection: !retainScene\}\)/);
|
assert.match(shellMethod('destroy').toString(), /this\._stampNavigation\(\{\s*cancelPendingSelection: !retainScene\s*\}\)/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('teardown synchronously closes immediate camera entry points', () => {
|
test('teardown synchronously closes immediate camera entry points', () => {
|
||||||
|
|||||||
+29
-2
@@ -511,6 +511,8 @@ export class IntelHUD {
|
|||||||
* collection timestamp. Stores results in {@link _latestMetrics}.
|
* collection timestamp. Stores results in {@link _latestMetrics}.
|
||||||
*/
|
*/
|
||||||
_updateCameraData() {
|
_updateCameraData() {
|
||||||
|
// Between two globe engines there is no camera to read.
|
||||||
|
if (!this.engine) return;
|
||||||
const pose = this.engine.camera.pose();
|
const pose = this.engine.camera.pose();
|
||||||
const cartographic = pose.cartographic;
|
const cartographic = pose.cartographic;
|
||||||
if (!cartographic) return;
|
if (!cartographic) return;
|
||||||
@@ -563,7 +565,7 @@ export class IntelHUD {
|
|||||||
const sunEl = this._estimateSunElevation(latDeg, lonDeg);
|
const sunEl = this._estimateSunElevation(latDeg, lonDeg);
|
||||||
// i18n-ignore: sensor codes
|
// i18n-ignore: sensor codes
|
||||||
readouts.alt = `ALT: ${Math.round(altMslM)}m SUN: ${sunEl.toFixed(1)}° EL`;
|
readouts.alt = `ALT: ${Math.round(altMslM)}m SUN: ${sunEl.toFixed(1)}° EL`;
|
||||||
if (this.engine.host.container?.hasAttribute?.('data-terrain-daylight'))
|
if (this.engine?.host.container?.hasAttribute?.('data-terrain-daylight'))
|
||||||
readouts.alt += ' · DAYLIGHT VIEW';
|
readouts.alt += ' · DAYLIGHT VIEW';
|
||||||
|
|
||||||
// Collection timestamp
|
// Collection timestamp
|
||||||
@@ -740,7 +742,7 @@ export class IntelHUD {
|
|||||||
* dimensions, or null if the view rectangle cannot be computed.
|
* dimensions, or null if the view rectangle cannot be computed.
|
||||||
*/
|
*/
|
||||||
_viewWindowKm(latDeg) {
|
_viewWindowKm(latDeg) {
|
||||||
const rect = this.engine.camera.viewRectangle();
|
const rect = this.engine?.camera.viewRectangle();
|
||||||
if (!rect) return null;
|
if (!rect) return null;
|
||||||
const north = geo.math.toDegrees(rect.north);
|
const north = geo.math.toDegrees(rect.north);
|
||||||
const south = geo.math.toDegrees(rect.south);
|
const south = geo.math.toDegrees(rect.south);
|
||||||
@@ -1040,6 +1042,8 @@ export class IntelHUD {
|
|||||||
|
|
||||||
async _summaryContext() {
|
async _summaryContext() {
|
||||||
const revision = this._summaryRevision;
|
const revision = this._summaryRevision;
|
||||||
|
// Between two globe engines the summary keeps its fallback line.
|
||||||
|
if (!this.engine) throw new Error('No globe camera to describe');
|
||||||
const labels = await getBasemapLabelContext(this.engine, this.placeSearch, {
|
const labels = await getBasemapLabelContext(this.engine, this.placeSearch, {
|
||||||
...this.basemapContext,
|
...this.basemapContext,
|
||||||
previous: this._placeLabels,
|
previous: this._placeLabels,
|
||||||
@@ -1224,6 +1228,29 @@ export class IntelHUD {
|
|||||||
this._markSummaryDirty();
|
this._markSummaryDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read another globe engine's camera (an engine switch), or none (null)
|
||||||
|
* between two engines. Telemetry, summaries and the camera listeners
|
||||||
|
* follow it; the view, timers and data manager stay.
|
||||||
|
*/
|
||||||
|
attachEngine(engine, { viewer = null } = {}) {
|
||||||
|
this._removeCameraMoveEnd?.();
|
||||||
|
this._removeCameraMoveEnd = null;
|
||||||
|
this._removeCameraMoveStart?.();
|
||||||
|
this._removeCameraMoveStart = null;
|
||||||
|
this._summaryRequest?.abort();
|
||||||
|
this.engine = engine ? engineFromViewer(engine) : null;
|
||||||
|
this.viewer = viewer;
|
||||||
|
if (!this.engine) return;
|
||||||
|
this._removeCameraMoveStart =
|
||||||
|
this.engine.camera.moveStart?.addEventListener(this._onCameraMoveStart);
|
||||||
|
this._removeCameraMoveEnd = this.engine.camera.moveEnd.addEventListener(
|
||||||
|
this._onCameraMoveEnd,
|
||||||
|
);
|
||||||
|
this._markSummaryDirty();
|
||||||
|
if (this._visible) this._updateCameraData();
|
||||||
|
}
|
||||||
|
|
||||||
/** Tear down all running intervals. Call when discarding the HUD instance. */
|
/** Tear down all running intervals. Call when discarding the HUD instance. */
|
||||||
destroy() {
|
destroy() {
|
||||||
clearInterval(this._updateInterval);
|
clearInterval(this._updateInterval);
|
||||||
|
|||||||
@@ -82,6 +82,20 @@ export class OrbitController {
|
|||||||
this.engine?.camera.release();
|
this.engine?.camera.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orbit on another globe engine's camera (an engine switch), or none
|
||||||
|
* (null). A running orbit stops: its frames belonged to the outgoing one.
|
||||||
|
*/
|
||||||
|
attachEngine(engine) {
|
||||||
|
if (this.active || this._removeListener) {
|
||||||
|
this.active = false;
|
||||||
|
releaseContinuousRender('camera-orbit');
|
||||||
|
this._removeListener?.();
|
||||||
|
this._removeListener = null;
|
||||||
|
}
|
||||||
|
this.engine = engine ? engineFromViewer(engine) : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle orbit on/off.
|
* Toggle orbit on/off.
|
||||||
* @param {{x: number, y: number, z: number}} targetCartesian - Required when starting
|
* @param {{x: number, y: number, z: number}} targetCartesian - Required when starting
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// The session objects an engine switch keeps follow the next globe's camera
|
||||||
|
// (attachEngine / setScene) instead of being rebuilt, and hold no listener on
|
||||||
|
// the globe they left.
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { createFakeEngine } from './globe/testing/fakeEngine.js';
|
||||||
|
import { ShareLinkManager } from './sharelink.js';
|
||||||
|
import { OrbitController } from './orbit.js';
|
||||||
|
import { NavigationController } from './ui/navigationController.js';
|
||||||
|
|
||||||
|
const listeners = (event) => event.numberOfListeners;
|
||||||
|
|
||||||
|
test('the share link follows the attached camera and writes nothing between engines', (t) => {
|
||||||
|
const prior = { window: globalThis.window, history: globalThis.history };
|
||||||
|
const writes = [];
|
||||||
|
globalThis.window = Object.assign(new EventTarget(), {
|
||||||
|
location: new URL('http://localhost/'),
|
||||||
|
});
|
||||||
|
globalThis.history = { replaceState: (...args) => writes.push(args) };
|
||||||
|
t.after(() => Object.assign(globalThis, prior));
|
||||||
|
const first = createFakeEngine();
|
||||||
|
const second = createFakeEngine();
|
||||||
|
const manager = new ShareLinkManager(first);
|
||||||
|
assert.equal(listeners(first.camera.changed), 1);
|
||||||
|
manager.attachEngine(null);
|
||||||
|
assert.equal(listeners(first.camera.changed), 0);
|
||||||
|
assert.equal(manager.engine, null);
|
||||||
|
manager._updateHash();
|
||||||
|
assert.equal(writes.length, 0, 'no camera, no address write');
|
||||||
|
assert.equal(manager.buildLink(), null);
|
||||||
|
manager.attachEngine(second);
|
||||||
|
assert.equal(listeners(second.camera.changed), 1);
|
||||||
|
assert.equal(manager.engine, second);
|
||||||
|
manager.destroy();
|
||||||
|
assert.equal(listeners(second.camera.changed), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('an orbit stops when its globe leaves and orbits the next one', () => {
|
||||||
|
const first = createFakeEngine();
|
||||||
|
const second = createFakeEngine();
|
||||||
|
const orbit = new OrbitController(first);
|
||||||
|
orbit.start({ x: 6378137, y: 0, z: 0 });
|
||||||
|
assert.equal(orbit.active, true);
|
||||||
|
assert.equal(listeners(first.frames.preRender), 1);
|
||||||
|
orbit.attachEngine(second);
|
||||||
|
assert.equal(orbit.active, false);
|
||||||
|
assert.equal(listeners(first.frames.preRender), 0);
|
||||||
|
orbit.start({ x: 6378137, y: 0, z: 0 });
|
||||||
|
assert.equal(listeners(second.frames.preRender), 1);
|
||||||
|
orbit.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('navigation authority retires deferred flights when the globe changes', () => {
|
||||||
|
const first = createFakeEngine();
|
||||||
|
const second = createFakeEngine();
|
||||||
|
const none = {
|
||||||
|
cancelPendingTrackingRestore() {},
|
||||||
|
getTrackedInfo: () => null,
|
||||||
|
};
|
||||||
|
const navigation = new NavigationController({
|
||||||
|
engine: first,
|
||||||
|
tracking: {
|
||||||
|
flightsLayer: none,
|
||||||
|
militaryFlightsLayer: none,
|
||||||
|
satellitesLayer: none,
|
||||||
|
},
|
||||||
|
interruptCameraMotion() {},
|
||||||
|
isCockpitActive: () => false,
|
||||||
|
clearLocation() {},
|
||||||
|
cancelShareSelection: () => false,
|
||||||
|
getDataManager: () => null,
|
||||||
|
stopOrbit() {},
|
||||||
|
showToast() {},
|
||||||
|
});
|
||||||
|
const generation = navigation._beginDeferredNavigation('shared view');
|
||||||
|
assert.equal(Number.isInteger(generation), true);
|
||||||
|
navigation.setScene({ engine: null });
|
||||||
|
assert.equal(navigation.engine, null);
|
||||||
|
assert.equal(
|
||||||
|
navigation._reassertNavigationHandoff(generation),
|
||||||
|
false,
|
||||||
|
'a flight deferred on the outgoing globe cannot land on the next one',
|
||||||
|
);
|
||||||
|
navigation.setScene({ engine: second });
|
||||||
|
assert.equal(navigation.engine, second);
|
||||||
|
const next = navigation._beginDeferredNavigation('location');
|
||||||
|
assert.equal(navigation._reassertNavigationHandoff(next), true);
|
||||||
|
});
|
||||||
+30
-3
@@ -150,6 +150,28 @@ export class ShareLinkManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Follow another globe engine's camera (an engine switch), or none (null)
|
||||||
|
* between two engines. The link's state, listeners and holds stay; a
|
||||||
|
* restore flight still owned on the outgoing camera is settled.
|
||||||
|
*/
|
||||||
|
attachEngine(engine) {
|
||||||
|
if (this._destroyed) return;
|
||||||
|
this._removeCameraChanged?.();
|
||||||
|
this._removeCameraChanged = null;
|
||||||
|
if (this._activeCameraFlight) {
|
||||||
|
const flight = this._activeCameraFlight;
|
||||||
|
this._restoreGeneration += 1;
|
||||||
|
this._activeCameraFlight = null;
|
||||||
|
flight.settle?.('superseded');
|
||||||
|
}
|
||||||
|
this.engine = engine ? engineFromViewer(engine) : null;
|
||||||
|
if (!this.engine) return;
|
||||||
|
this._removeCameraChanged = this.engine.camera.changed.addEventListener(
|
||||||
|
() => this._scheduleUpdate(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the share this page opened with: the host's `initialShareCode`, a
|
* Parse the share this page opened with: the host's `initialShareCode`, a
|
||||||
* `/share/<code>` address, or a long-form hash. Returns the parsed state, or
|
* `/share/<code>` address, or a long-form hash. Returns the parsed state, or
|
||||||
@@ -364,7 +386,7 @@ export class ShareLinkManager {
|
|||||||
state,
|
state,
|
||||||
{ applyCamera = true, instant = false, navigationToken = null } = {},
|
{ applyCamera = true, instant = false, navigationToken = null } = {},
|
||||||
) {
|
) {
|
||||||
if (this._destroyed || !state)
|
if (this._destroyed || !state || !this.engine)
|
||||||
return { succeeded: false, reason: 'unavailable' };
|
return { succeeded: false, reason: 'unavailable' };
|
||||||
const view = {
|
const view = {
|
||||||
destination: geo.cartesian3.fromDegrees(state.lon, state.lat, state.alt),
|
destination: geo.cartesian3.fromDegrees(state.lon, state.lat, state.alt),
|
||||||
@@ -682,7 +704,12 @@ export class ShareLinkManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_updateHash() {
|
_updateHash() {
|
||||||
if (this._destroyed || this._initialRestorePending || this._navigationHeld)
|
if (
|
||||||
|
this._destroyed ||
|
||||||
|
!this.engine ||
|
||||||
|
this._initialRestorePending ||
|
||||||
|
this._navigationHeld
|
||||||
|
)
|
||||||
return;
|
return;
|
||||||
const params = this._buildHashParams();
|
const params = this._buildHashParams();
|
||||||
if (!params) return;
|
if (!params) return;
|
||||||
@@ -703,7 +730,7 @@ export class ShareLinkManager {
|
|||||||
|
|
||||||
/** Build a deterministic snapshot without mutating history. */
|
/** Build a deterministic snapshot without mutating history. */
|
||||||
_buildHashParams() {
|
_buildHashParams() {
|
||||||
if (this._destroyed) return null;
|
if (this._destroyed || !this.engine) return null;
|
||||||
const pose = this.engine.camera.pose();
|
const pose = this.engine.camera.pose();
|
||||||
const carto = pose.cartographic;
|
const carto = pose.cartographic;
|
||||||
if (!carto) return null;
|
if (!carto) return null;
|
||||||
|
|||||||
@@ -168,9 +168,9 @@ export class StyleManager extends ShellFacade {
|
|||||||
this._feedback = new ShellFeedback({
|
this._feedback = new ShellFeedback({
|
||||||
readLayers: () => this._dataManager?.getAll?.() || [],
|
readLayers: () => this._dataManager?.getAll?.() || [],
|
||||||
});
|
});
|
||||||
this.viewer = viewer;
|
// The attached globe: its viewer (null on Navara), engine and map
|
||||||
this.engine = engine;
|
// stack. An engine switch replaces it (attachScene/detachScene).
|
||||||
this.mapStackController = mapStackController;
|
this._scene = { viewer, engine, mapStackController };
|
||||||
this.placeSearch = placeSearch;
|
this.placeSearch = placeSearch;
|
||||||
|
|
||||||
this._navigation = new NavigationController({
|
this._navigation = new NavigationController({
|
||||||
@@ -458,7 +458,7 @@ export class StyleManager extends ShellFacade {
|
|||||||
},
|
},
|
||||||
isNavigationCurrent: (generation) =>
|
isNavigationCurrent: (generation) =>
|
||||||
generation === this._navigationGeneration,
|
generation === this._navigationGeneration,
|
||||||
cancelOwnedNavigation: () => this.engine.camera.cancelFlight(),
|
cancelOwnedNavigation: () => this.engine?.camera.cancelFlight(),
|
||||||
// A host may open a share code without a `/share/<code>` address.
|
// A host may open a share code without a `/share/<code>` address.
|
||||||
initialShareCode,
|
initialShareCode,
|
||||||
});
|
});
|
||||||
@@ -625,7 +625,10 @@ export class StyleManager extends ShellFacade {
|
|||||||
this._publishActiveStyle();
|
this._publishActiveStyle();
|
||||||
this._updateLocationMiniStatus();
|
this._updateLocationMiniStatus();
|
||||||
|
|
||||||
this._shareRestoration.start({ engineSwitch, retainScene: Boolean(this._layerSession?.handoffLayers) });
|
this._shareRestoration.start({
|
||||||
|
engineSwitch,
|
||||||
|
retainScene: Boolean(this._layerSession?.handoffLayers),
|
||||||
|
});
|
||||||
|
|
||||||
// Keep the parameter panel from overlapping toggle controls.
|
// Keep the parameter panel from overlapping toggle controls.
|
||||||
this._layoutRightPanels();
|
this._layoutRightPanels();
|
||||||
@@ -644,6 +647,21 @@ export class StyleManager extends ShellFacade {
|
|||||||
this._layerBindings.observeCamera();
|
this._layerBindings.observeCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The attached globe's Cesium viewer; null on Navara or between engines. */
|
||||||
|
get viewer() {
|
||||||
|
return this._scene?.viewer ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The attached globe engine; null between engines. */
|
||||||
|
get engine() {
|
||||||
|
return this._scene?.engine ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The attached globe's map stack; null between engines. */
|
||||||
|
get mapStackController() {
|
||||||
|
return this._scene?.mapStackController ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
// Compatibility reads for existing controls, scene snapshots and Cockpit.
|
// Compatibility reads for existing controls, scene snapshots and Cockpit.
|
||||||
|
|
||||||
/** Advance camera authority and settle any older search UI immediately. */
|
/** Advance camera authority and settle any older search UI immediately. */
|
||||||
@@ -1475,7 +1493,7 @@ export class StyleManager extends ShellFacade {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
applyCameraState(cameraState, duration = 2.8) {
|
applyCameraState(cameraState, duration = 2.8) {
|
||||||
if (!cameraState) return;
|
if (!cameraState || !this.engine) return;
|
||||||
this.engine.camera.flyTo({
|
this.engine.camera.flyTo({
|
||||||
destination: geo.cartesian3.fromDegrees(
|
destination: geo.cartesian3.fromDegrees(
|
||||||
cameraState.lon,
|
cameraState.lon,
|
||||||
@@ -1856,12 +1874,17 @@ export class StyleManager extends ShellFacade {
|
|||||||
// re-enable a mode's entry layer and republish `_contextMode` while the
|
// re-enable a mode's entry layer and republish `_contextMode` while the
|
||||||
// rest of teardown is tearing those very layers down.
|
// rest of teardown is tearing those very layers down.
|
||||||
this._contextControls.stop();
|
this._contextControls.stop();
|
||||||
this._navigation.destroy({ retainScene: Boolean(this._layerSession?.handoffLayers) });
|
this._navigation.destroy({
|
||||||
|
retainScene: Boolean(this._layerSession?.handoffLayers),
|
||||||
|
});
|
||||||
// Close camera-entry seams synchronously. Context restoration may await
|
// Close camera-entry seams synchronously. Context restoration may await
|
||||||
// layer work, so leaving these listeners attached until afterward lets a
|
// layer work, so leaving these listeners attached until afterward lets a
|
||||||
// focus event release tracking or start a flight during teardown.
|
// focus event release tracking or start a flight during teardown.
|
||||||
if (this._layerSession && !this._layerSession.lifetime.signal.aborted &&
|
if (
|
||||||
this._contextControls.canRetainScene()) {
|
this._layerSession &&
|
||||||
|
!this._layerSession.lifetime.signal.aborted &&
|
||||||
|
this._contextControls.canRetainScene()
|
||||||
|
) {
|
||||||
this._layerSession.contextScene = this._contextControls.captureScene();
|
this._layerSession.contextScene = this._contextControls.captureScene();
|
||||||
} else {
|
} else {
|
||||||
if (this._layerSession) this._layerSession.contextScene = null;
|
if (this._layerSession) this._layerSession.contextScene = null;
|
||||||
|
|||||||
@@ -443,6 +443,30 @@ export class LocationNavigation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Revoke callbacks and settle owned camera work before the viewer is released. */
|
/** Revoke callbacks and settle owned camera work before the viewer is released. */
|
||||||
|
/**
|
||||||
|
* Leave the globe (an engine switch): its reset flight, world-jump
|
||||||
|
* transition and orbit end; the search, its text and the readout stay.
|
||||||
|
*/
|
||||||
|
detachScene() {
|
||||||
|
this._cancelGlobeReset?.();
|
||||||
|
this._cancelGlobeReset = null;
|
||||||
|
if (this._worldJumpActive) this._endWorldJumpTransition();
|
||||||
|
clearTimeout(this._trafficTransitionTimer);
|
||||||
|
this._trafficTransitionTimer = null;
|
||||||
|
this._stopOrbit();
|
||||||
|
this.orbitController?.attachEngine(null);
|
||||||
|
this.viewer = null;
|
||||||
|
this.engine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Navigate on another globe (an engine switch). */
|
||||||
|
attachScene({ viewer = null, engine = engineFromViewer(viewer) } = {}) {
|
||||||
|
if (this._disposed) return;
|
||||||
|
this.viewer = viewer;
|
||||||
|
this.engine = engine;
|
||||||
|
this.orbitController?.attachEngine(engine);
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this._disposed) return;
|
if (this._disposed) return;
|
||||||
this._disposed = true;
|
this._disposed = true;
|
||||||
|
|||||||
@@ -254,7 +254,10 @@ export class NavigationController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_reassertNavigationHandoff(generation, { trackingOrigin = 'tool', keepTracking = false } = {}) {
|
_reassertNavigationHandoff(
|
||||||
|
generation,
|
||||||
|
{ trackingOrigin = 'tool', keepTracking = false } = {},
|
||||||
|
) {
|
||||||
return reassertNavigationHandoff({
|
return reassertNavigationHandoff({
|
||||||
generation,
|
generation,
|
||||||
currentGeneration: this._navigationGeneration,
|
currentGeneration: this._navigationGeneration,
|
||||||
@@ -270,6 +273,24 @@ export class NavigationController {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The globe the camera authority applies to (an engine switch), or none.
|
||||||
|
* Leaving a globe retires every deferred navigation started on it: its
|
||||||
|
* flight cannot land on the next one. `retainScene` keeps the selection a
|
||||||
|
* handoff carries across.
|
||||||
|
*/
|
||||||
|
setScene({ viewer = null, engine = null } = {}, { retainScene = true } = {}) {
|
||||||
|
if (this._disposed) return;
|
||||||
|
if (this.engine && engine !== this.engine) {
|
||||||
|
cancelCameraArrival(this.engine);
|
||||||
|
this._stampNavigation({
|
||||||
|
cancelPendingSelection: !retainScene,
|
||||||
|
clearSearchedLocation: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.viewer = viewer;
|
||||||
|
this.engine = engine ?? engineFromViewer(viewer);
|
||||||
|
}
|
||||||
/** Subscribe to ownership changes without claiming the camera or exposing mutable state. */
|
/** Subscribe to ownership changes without claiming the camera or exposing mutable state. */
|
||||||
subscribeCameraHandoff(listener) {
|
subscribeCameraHandoff(listener) {
|
||||||
return this._cameraHandoffs.subscribe(listener, { emitCurrent: false });
|
return this._cameraHandoffs.subscribe(listener, { emitCurrent: false });
|
||||||
@@ -282,6 +303,6 @@ export class NavigationController {
|
|||||||
}
|
}
|
||||||
destroy({ retainScene = false } = {}) {
|
destroy({ retainScene = false } = {}) {
|
||||||
this.stop();
|
this.stop();
|
||||||
this._stampNavigation({cancelPendingSelection: !retainScene});
|
this._stampNavigation({ cancelPendingSelection: !retainScene });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user