mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 19:34:01 +00:00
Preserve satellite tracking, orbit solutions and mission replay across globes
This commit is contained in:
1 parent
133ca14119
commit
a49671a391
21 files changed
+424
-49
No files matched your search
@@ -0,0 +1,208 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import puppeteer from 'puppeteer';
|
||||||
|
import { launchFixture } from '../src/bench/launchFixture.js';
|
||||||
|
import {
|
||||||
|
satelliteFixture,
|
||||||
|
SATELLITE_FIXTURE_EPOCH,
|
||||||
|
} from '../src/bench/satelliteFixture.js';
|
||||||
|
import { blockPaidQaMaps } from './qa-map-policy.mjs';
|
||||||
|
import { switchGlobe } from './qa-switch-globe.mjs';
|
||||||
|
const url = new URL(process.argv[2]),
|
||||||
|
missions = process.argv.includes('--missions');
|
||||||
|
if (
|
||||||
|
!['localhost', '127.0.0.1'].includes(url.hostname) ||
|
||||||
|
['', '4173', '4185', '4186', '4187', '4188', '4189', '4194'].includes(
|
||||||
|
url.port,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw Error('Supply local keyless preview URL');
|
||||||
|
url.search = '?engine=navara&welcome=0';
|
||||||
|
url.hash = 'v=2&lat=5&lon=5&alt=18000000&pitch=-90&heading=0&map=sentinel2&l=';
|
||||||
|
const b = await puppeteer.launch({
|
||||||
|
headless: true,
|
||||||
|
args: ['--use-angle=metal', '--enable-gpu'],
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const p = await b.newPage();
|
||||||
|
await blockPaidQaMaps(p);
|
||||||
|
const errors = [];
|
||||||
|
p.on('pageerror', (e) => {
|
||||||
|
errors.push(e.message);
|
||||||
|
console.log('error', e.message);
|
||||||
|
});
|
||||||
|
const groups = Object.fromEntries(
|
||||||
|
[
|
||||||
|
'stations',
|
||||||
|
'visual',
|
||||||
|
'gps-ops',
|
||||||
|
'glo-ops',
|
||||||
|
'galileo',
|
||||||
|
'geo',
|
||||||
|
'starlink',
|
||||||
|
].map((g) => [g, satelliteFixture(g)]),
|
||||||
|
);
|
||||||
|
await p.evaluateOnNewDocument(
|
||||||
|
(groups, launches, epoch) => {
|
||||||
|
const fetch = window.fetch.bind(window);
|
||||||
|
window.spaceReads = {};
|
||||||
|
const mono = performance.now();
|
||||||
|
Date.now = () => epoch + performance.now() - mono;
|
||||||
|
window.fetch = (input, opt) => {
|
||||||
|
const path = new URL(input.url ?? input, location.href).pathname;
|
||||||
|
if (path.startsWith('/api/celestrak/') || path === '/api/launches') {
|
||||||
|
window.spaceReads[path] = (window.spaceReads[path] || 0) + 1;
|
||||||
|
const g = path.split('/').pop();
|
||||||
|
return Promise.resolve(
|
||||||
|
g === 'launches'
|
||||||
|
? Response.json(launches)
|
||||||
|
: new Response(groups[g] ?? groups.stations),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return fetch(input, opt);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
groups,
|
||||||
|
launchFixture(),
|
||||||
|
SATELLITE_FIXTURE_EPOCH,
|
||||||
|
);
|
||||||
|
await p.goto(url.href);
|
||||||
|
await p.waitForFunction(
|
||||||
|
() =>
|
||||||
|
window.__godsEyeView?.engine.kind === 'navara' &&
|
||||||
|
document.querySelector('#loading-screen.hidden'),
|
||||||
|
);
|
||||||
|
await p.evaluate(async (missions) => {
|
||||||
|
const { dataManager, styleManager, tileset } = window.__godsEyeView;
|
||||||
|
if (tileset || window.__GOOGLE_MAPS_API_KEY__)
|
||||||
|
throw Error('Expected keyless preview');
|
||||||
|
await styleManager.initialRestorePromise;
|
||||||
|
window.sa = dataManager.layers.get('satellites').module;
|
||||||
|
window.la = dataManager.layers.get('rocket-launches').module;
|
||||||
|
if (missions) {
|
||||||
|
const result = await styleManager.setContextMode('space-missions');
|
||||||
|
if (!result.ok) throw Error('Missions mode unavailable: ' + result.error);
|
||||||
|
} else await dataManager.setEnabled('satellites', true, { origin: 'user' });
|
||||||
|
}, missions);
|
||||||
|
if (!missions) {
|
||||||
|
await p.waitForFunction(() => window.sa.getStats().count === 839, {
|
||||||
|
timeout: 40000,
|
||||||
|
});
|
||||||
|
console.log('core catalog ready');
|
||||||
|
await p.evaluate(() => window.sa.setParams({ catalog: 'dense' }));
|
||||||
|
await p.waitForFunction(() => window.sa.getStats().count === 6839, {
|
||||||
|
timeout: 60000,
|
||||||
|
});
|
||||||
|
console.log('dense catalog ready');
|
||||||
|
assert.ok(
|
||||||
|
await p.evaluate(() => window.sa.trackById(25544, { origin: 'user' })),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await p.waitForFunction(() => window.la.getStats().count === 3, {
|
||||||
|
timeout: 40000,
|
||||||
|
});
|
||||||
|
await p.evaluate(() => {
|
||||||
|
window.la.selectMissionAt(0);
|
||||||
|
window.la.startSelectedReplay();
|
||||||
|
window.la.setReplaySpeed(4);
|
||||||
|
});
|
||||||
|
await p.waitForFunction(() => window.la.qaDrawn()?.replay?.active, {
|
||||||
|
timeout: 20000,
|
||||||
|
});
|
||||||
|
await p.waitForFunction(
|
||||||
|
() => window.la.qaDrawn()?.replay?.phaseProgress > 0.001,
|
||||||
|
{ timeout: 35000 },
|
||||||
|
);
|
||||||
|
await p.evaluate(() => window.la.toggleReplayPause());
|
||||||
|
}
|
||||||
|
const before = await p.evaluate(() => ({
|
||||||
|
reads: window.spaceReads,
|
||||||
|
stamp: (window.la.getStats().count ? window.la : window.sa).getStats()
|
||||||
|
.lastUpdate,
|
||||||
|
mission: window.la.qaDrawn()?.selected,
|
||||||
|
replay: window.la.qaDrawn()?.replay,
|
||||||
|
}));
|
||||||
|
console.log(
|
||||||
|
'before switch',
|
||||||
|
before.reads,
|
||||||
|
before.mission,
|
||||||
|
before.replay?.active,
|
||||||
|
);
|
||||||
|
for (const kind of ['cesium', 'navara']) {
|
||||||
|
await switchGlobe(p, kind);
|
||||||
|
const state = await p.evaluate(() => {
|
||||||
|
const { dataManager, engine, styleManager } = window.__godsEyeView;
|
||||||
|
return {
|
||||||
|
ids: [
|
||||||
|
dataManager.layers.get('satellites').module === window.sa,
|
||||||
|
dataManager.layers.get('rocket-launches').module === window.la,
|
||||||
|
],
|
||||||
|
reads: window.spaceReads,
|
||||||
|
sat: window.sa.getStats(),
|
||||||
|
params: window.sa.getParams(),
|
||||||
|
follow: !!engine.camera.following(),
|
||||||
|
ring: window.sa.qaDrawn()?.rings.length,
|
||||||
|
mission: window.la.qaDrawn(),
|
||||||
|
mode: styleManager.getContextModeState().mode,
|
||||||
|
missionStats: window.la.getStats(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
kind,
|
||||||
|
state.ids,
|
||||||
|
state.sat.count,
|
||||||
|
state.params.selectedSatTrackingId,
|
||||||
|
state.follow,
|
||||||
|
state.mode,
|
||||||
|
state.mission?.selected,
|
||||||
|
state.mission?.replay?.phaseProgress,
|
||||||
|
);
|
||||||
|
assert.ok(state.ids.every(Boolean));
|
||||||
|
assert.deepEqual(state.reads, before.reads);
|
||||||
|
if (missions) {
|
||||||
|
assert.equal(state.mode, 'space-missions');
|
||||||
|
assert.equal(state.mission.selected, before.mission);
|
||||||
|
assert.ok(state.mission.paused && state.mission.replay.active);
|
||||||
|
assert.equal(state.mission.speed, 4);
|
||||||
|
assert.equal(state.missionStats.lastUpdate, before.stamp);
|
||||||
|
assert.equal(
|
||||||
|
state.mission.replay.phaseProgress,
|
||||||
|
before.replay.phaseProgress,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert.equal(state.sat.count, 6839);
|
||||||
|
assert.equal(state.params.selectedSatTrackingId, 25544);
|
||||||
|
assert.ok(state.follow && state.ring > 0);
|
||||||
|
assert.equal(state.sat.lastUpdate, before.stamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (missions) {
|
||||||
|
await p.evaluate(() => window.la.toggleReplayPause());
|
||||||
|
await p.waitForFunction(() => window.la.qaDrawn()?.paused === false);
|
||||||
|
await p.waitForFunction(
|
||||||
|
(progress) => window.la.qaDrawn()?.replay?.phaseProgress > progress,
|
||||||
|
{},
|
||||||
|
before.replay.phaseProgress,
|
||||||
|
);
|
||||||
|
await switchGlobe(p, 'cesium');
|
||||||
|
const moving = await p.evaluate(() => window.la.qaDrawn());
|
||||||
|
assert.ok(
|
||||||
|
moving.replay?.active &&
|
||||||
|
!moving.paused &&
|
||||||
|
moving.replay.phaseProgress > before.replay.phaseProgress,
|
||||||
|
);
|
||||||
|
const left = await p.evaluate(() =>
|
||||||
|
window.__godsEyeView.styleManager.setContextMode(null),
|
||||||
|
);
|
||||||
|
assert.ok(left.ok);
|
||||||
|
assert.equal(
|
||||||
|
await p.evaluate(() =>
|
||||||
|
window.__godsEyeView.dataManager.isEnabled('rocket-launches'),
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert.deepEqual(errors, []);
|
||||||
|
console.log('PASS retained space data and active camera across both planets');
|
||||||
|
} finally {
|
||||||
|
await b.close();
|
||||||
|
}
|
||||||
@@ -153,9 +153,9 @@ export function createApplicationCatalog({
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
const satellites = once(() =>
|
const satellites = once(() =>
|
||||||
createApplicationSatellites({
|
sessionModel('satellites', () => createApplicationSatellites({
|
||||||
source: sources.satellites,
|
source: sources.satellites,
|
||||||
}),
|
})),
|
||||||
);
|
);
|
||||||
// Catalog order: [layer id(s), construction]. Only the layers the
|
// Catalog order: [layer id(s), construction]. Only the layers the
|
||||||
// engine can show are constructed (renderers.js).
|
// engine can show are constructed (renderers.js).
|
||||||
@@ -235,10 +235,10 @@ export function createApplicationCatalog({
|
|||||||
[
|
[
|
||||||
'rocket-launches',
|
'rocket-launches',
|
||||||
() =>
|
() =>
|
||||||
createApplicationLaunches({
|
sessionModel('rocket-launches', () => createApplicationLaunches({
|
||||||
source: sources.launches,
|
source: sources.launches,
|
||||||
satellites: satellites(),
|
satellites: satellites(),
|
||||||
}),
|
})),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'traffic',
|
'traffic',
|
||||||
|
|||||||
@@ -491,7 +491,7 @@ export class LayerStateCoordinator {
|
|||||||
const model = this.dataManager.layers.get(selected.layerId)?.module;
|
const model = this.dataManager.layers.get(selected.layerId)?.module;
|
||||||
if (
|
if (
|
||||||
model?.attachRenderer &&
|
model?.attachRenderer &&
|
||||||
model.getParams?.()[selected.optionKey] === selected.targetId &&
|
String(model.getParams?.()[selected.optionKey]) === String(selected.targetId) &&
|
||||||
model.hasContact?.(selected.targetId) === true
|
model.hasContact?.(selected.targetId) === true
|
||||||
) return selected;
|
) return selected;
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ export function createIngestion({
|
|||||||
stale: false,
|
stale: false,
|
||||||
};
|
};
|
||||||
const launches = parts.model.normalizeRocketLaunches(payload);
|
const launches = parts.model.normalizeRocketLaunches(payload);
|
||||||
|
if (layerState._waitForRenderer) await layerState._waitForRenderer;
|
||||||
if (
|
if (
|
||||||
!layerState._enabled ||
|
!layerState._enabled ||
|
||||||
token !== layerState._lifecycleToken ||
|
token !== layerState._lifecycleToken ||
|
||||||
|
|||||||
@@ -8,20 +8,22 @@ export function createLifecycle({
|
|||||||
const { holdContinuousRender, releaseContinuousRender } = services.render;
|
const { holdContinuousRender, releaseContinuousRender } = services.render;
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
init(viewer, { engine = null } = {}) {
|
init(viewer, options) {
|
||||||
layerState._engine = engine;
|
|
||||||
layerState._renderer = services.renderer.create({ viewer, engine });
|
|
||||||
layerState._viewer = layerState._renderer.view;
|
|
||||||
layerState._declutterOccluder = getRenderer().occluder();
|
|
||||||
layerState._enabled = false;
|
|
||||||
parts.panel.createMissionPanel();
|
|
||||||
parts.overlays.createReplayVehicleOverlay();
|
|
||||||
layerState._dataSource = getRenderer().createDataSource();
|
|
||||||
layerState._count = 0;
|
layerState._count = 0;
|
||||||
layerState._lastUpdate = null;
|
layerState._lastUpdate = null;
|
||||||
layerState._stale = false;
|
layerState._stale = false;
|
||||||
layerState._lastError = null;
|
layerState._lastError = null;
|
||||||
layerState._orbitMatches = 0;
|
layerState._orbitMatches = 0;
|
||||||
|
this.attachRenderer(viewer, options);
|
||||||
|
},
|
||||||
|
attachRenderer(viewer, { engine = null, waitForCameraRestore = false } = {}) {
|
||||||
|
layerState._engine = engine;
|
||||||
|
layerState._renderer = services.renderer.create({ viewer, engine });
|
||||||
|
layerState._viewer = layerState._renderer.view;
|
||||||
|
layerState._declutterOccluder = getRenderer().occluder();
|
||||||
|
parts.panel.createMissionPanel();
|
||||||
|
parts.overlays.createReplayVehicleOverlay();
|
||||||
|
layerState._dataSource = getRenderer().createDataSource();
|
||||||
parts.overlays.clearMissionOverlaySources();
|
parts.overlays.clearMissionOverlaySources();
|
||||||
const enabled = () => layerState._enabled && layerState._dataSource?.show;
|
const enabled = () => layerState._enabled && layerState._dataSource?.show;
|
||||||
layerState._clickHandler = getRenderer().listenClick(
|
layerState._clickHandler = getRenderer().listenClick(
|
||||||
@@ -53,6 +55,52 @@ export function createLifecycle({
|
|||||||
parts.rendering.updateMissionFrame,
|
parts.rendering.updateMissionFrame,
|
||||||
);
|
);
|
||||||
parts.launchPad.initLaunchPadZonePrimitive();
|
parts.launchPad.initLaunchPadZonePrimitive();
|
||||||
|
if (layerState._enabled) {
|
||||||
|
holdContinuousRender('rocket-launches');
|
||||||
|
layerState._dataSource.show = true;
|
||||||
|
layerState._orbitMatches = 0;
|
||||||
|
for (const launch of layerState._launches) parts.rendering.addLaunchEntity(launch, layerState._renderedTleText);
|
||||||
|
parts.selection.showSelectedMission();
|
||||||
|
}
|
||||||
|
layerState._restoringViewport = true;
|
||||||
|
if (!waitForCameraRestore) this.resumeViewport();
|
||||||
|
},
|
||||||
|
|
||||||
|
resumeViewport() {
|
||||||
|
if (!layerState._restoringViewport) return;
|
||||||
|
layerState._restoringViewport = false;
|
||||||
|
if (layerState._enabled && layerState._replayCameraLaunchId) {
|
||||||
|
parts.replay.startMissionReplay(layerState._replayCameraLaunchId, { resuming: true });
|
||||||
|
} else if (layerState._enabled && layerState._missionZoomAnchorId) {
|
||||||
|
const launch = layerState._launches.find(item => item.id === layerState._missionZoomAnchorId);
|
||||||
|
if (launch) parts.selection.startMissionZoomAnchor(launch, layerState._missionZoomRange);
|
||||||
|
}
|
||||||
|
layerState._resumeRenderer?.();
|
||||||
|
layerState._waitForRenderer = layerState._resumeRenderer = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
detachRenderer() {
|
||||||
|
if (!layerState._renderer) return;
|
||||||
|
layerState._waitForRenderer ??= new Promise(resolve => { layerState._resumeRenderer = resolve; });
|
||||||
|
layerState._replayCameraToken++;
|
||||||
|
layerState._replayCameraRemover?.();
|
||||||
|
layerState._missionZoomAnchorRemover?.();
|
||||||
|
layerState._replayCameraRemover = layerState._missionZoomAnchorRemover = null;
|
||||||
|
layerState._clickHandler?.destroy();
|
||||||
|
layerState._moveHandler?.destroy();
|
||||||
|
layerState._declutterHandler?.();
|
||||||
|
layerState._clickHandler = layerState._moveHandler = layerState._declutterHandler = null;
|
||||||
|
parts.launchPad.destroyLaunchPadZonePrimitive();
|
||||||
|
parts.orbitRendering.removeMissionOrbitPrimitives();
|
||||||
|
parts.overlays.clearMissionOverlaySources();
|
||||||
|
parts.overlays.destroyReplayVehicleOverlay();
|
||||||
|
layerState._missionOverlayRecords.clear();
|
||||||
|
layerState._satelliteTelemetry.clear();
|
||||||
|
layerState._replayTracks.clear();
|
||||||
|
layerState._renderer.removeDataSource(layerState._dataSource);
|
||||||
|
layerState._renderer.destroy();
|
||||||
|
layerState._dataSource = layerState._renderer = layerState._engine = layerState._viewer = null;
|
||||||
|
if (layerState._enabled) releaseContinuousRender('rocket-launches');
|
||||||
},
|
},
|
||||||
|
|
||||||
async enable() {
|
async enable() {
|
||||||
@@ -116,6 +164,8 @@ export function createLifecycle({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async destroy(viewer) {
|
async destroy(viewer) {
|
||||||
|
layerState._resumeRenderer?.();
|
||||||
|
layerState._waitForRenderer = layerState._resumeRenderer = null;
|
||||||
layerState._sourceController.abort();
|
layerState._sourceController.abort();
|
||||||
releaseContinuousRender('rocket-launches'); // direct-destroy path (perf wave 2 fix)
|
releaseContinuousRender('rocket-launches'); // direct-destroy path (perf wave 2 fix)
|
||||||
layerState._enabled = false;
|
layerState._enabled = false;
|
||||||
|
|||||||
@@ -76,18 +76,12 @@ export function createRendering({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLaunchEntity(launch, activeTleText = layerState._activeTleText) {
|
// The source snapshot owns its ephemeris epoch. A renderer attached later
|
||||||
const position = geo.cartesian3.fromDegrees(launch.lon, launch.lat);
|
// must reconstruct the same ascent and orbit, including a paused replay.
|
||||||
const overlayRecord = {
|
const orbitSolutions = new WeakMap();
|
||||||
launch,
|
function orbitForLaunch(launch, activeTleText, orbitAllowed) {
|
||||||
anchorPosition: position,
|
const known = orbitSolutions.get(launch);
|
||||||
elementEntryFactories: [],
|
if (known && known.activeTleText === activeTleText) return known;
|
||||||
liveEventTime: null,
|
|
||||||
};
|
|
||||||
layerState._missionOverlayRecords.set(launch.id, overlayRecord);
|
|
||||||
const orbitAllowed = parts.policyHelpers.launchStatusAllowsOrbit(
|
|
||||||
launch.status,
|
|
||||||
);
|
|
||||||
const coreTrack =
|
const coreTrack =
|
||||||
orbitAllowed && launch.satelliteQuery
|
orbitAllowed && launch.satelliteQuery
|
||||||
? getSatelliteOrbitTrack(launch.satelliteQuery, {
|
? getSatelliteOrbitTrack(launch.satelliteQuery, {
|
||||||
@@ -106,6 +100,24 @@ export function createRendering({
|
|||||||
: satelliteTrack?.orbitPath?.length > 1
|
: satelliteTrack?.orbitPath?.length > 1
|
||||||
? satelliteTrack.orbitPath
|
? satelliteTrack.orbitPath
|
||||||
: parts.paths.approximateOrbitPath(launch);
|
: parts.paths.approximateOrbitPath(launch);
|
||||||
|
const solution = {activeTleText, satelliteTrack, orbitPath};
|
||||||
|
orbitSolutions.set(launch, solution);
|
||||||
|
return solution;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLaunchEntity(launch, activeTleText = layerState._activeTleText) {
|
||||||
|
const position = geo.cartesian3.fromDegrees(launch.lon, launch.lat);
|
||||||
|
const overlayRecord = {
|
||||||
|
launch,
|
||||||
|
anchorPosition: position,
|
||||||
|
elementEntryFactories: [],
|
||||||
|
liveEventTime: null,
|
||||||
|
};
|
||||||
|
layerState._missionOverlayRecords.set(launch.id, overlayRecord);
|
||||||
|
const orbitAllowed = parts.policyHelpers.launchStatusAllowsOrbit(
|
||||||
|
launch.status,
|
||||||
|
);
|
||||||
|
const { satelliteTrack, orbitPath } = orbitForLaunch(launch, activeTleText, orbitAllowed);
|
||||||
launch.recoveryStages.forEach((stage) => {
|
launch.recoveryStages.forEach((stage) => {
|
||||||
stage.endpoint = parts.paths.landingEndpoint(
|
stage.endpoint = parts.paths.landingEndpoint(
|
||||||
stage,
|
stage,
|
||||||
|
|||||||
@@ -280,6 +280,7 @@ export function createReplay({
|
|||||||
return false;
|
return false;
|
||||||
layerState._replayPausedAtMs = appClock.now();
|
layerState._replayPausedAtMs = appClock.now();
|
||||||
layerState._replayPaused = true;
|
layerState._replayPaused = true;
|
||||||
|
layerState._replayTracks.get(layerState._replayCameraLaunchId)?.beginReplayFrame?.();
|
||||||
syncReplayButton();
|
syncReplayButton();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -329,20 +330,22 @@ export function createReplay({
|
|||||||
parts.overlays.syncMissionOverlayEntries();
|
parts.overlays.syncMissionOverlayEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
function startMissionReplay(launchId) {
|
function startMissionReplay(launchId, {resuming = false} = {}) {
|
||||||
const launch = layerState._launches.find((item) => item.id === launchId);
|
const launch = layerState._launches.find((item) => item.id === launchId);
|
||||||
const track = layerState._replayTracks.get(launchId);
|
const track = layerState._replayTracks.get(launchId);
|
||||||
if (!layerState._viewer || !launch || !track) return false;
|
if (!layerState._viewer || !launch || !track) return false;
|
||||||
if (layerState._replayCameraLaunchId === launchId) {
|
if (!resuming && layerState._replayCameraLaunchId === launchId) {
|
||||||
stopMissionReplay();
|
stopMissionReplay();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!resuming) {
|
||||||
parts.selection.stopMissionZoomAnchor();
|
parts.selection.stopMissionZoomAnchor();
|
||||||
stopMissionReplay();
|
stopMissionReplay();
|
||||||
layerState._replayCameraLaunchId = launchId;
|
layerState._replayCameraLaunchId = launchId;
|
||||||
layerState._replayPaused = false;
|
layerState._replayPaused = false;
|
||||||
layerState._replayPausedAtMs = null;
|
layerState._replayPausedAtMs = null;
|
||||||
|
}
|
||||||
const token = ++layerState._replayCameraToken;
|
const token = ++layerState._replayCameraToken;
|
||||||
const ascentDurationSec = track.ascentDurationSec;
|
const ascentDurationSec = track.ascentDurationSec;
|
||||||
// Start broadside to the ascent/orbit direction so the launch profile is
|
// Start broadside to the ascent/orbit direction so the launch profile is
|
||||||
@@ -354,6 +357,7 @@ export function createReplay({
|
|||||||
track.lastCameraHeading = initialHeading;
|
track.lastCameraHeading = initialHeading;
|
||||||
syncReplayButton();
|
syncReplayButton();
|
||||||
parts.overlays.syncMissionOverlayEntries();
|
parts.overlays.syncMissionOverlayEntries();
|
||||||
|
if (!resuming) {
|
||||||
parts.policyHelpers.releaseAircraftTracking(layerState._dataManager);
|
parts.policyHelpers.releaseAircraftTracking(layerState._dataManager);
|
||||||
layerState._viewer.trackedEntity = undefined;
|
layerState._viewer.trackedEntity = undefined;
|
||||||
layerState._viewer.camera.cancelFlight();
|
layerState._viewer.camera.cancelFlight();
|
||||||
@@ -362,7 +366,8 @@ export function createReplay({
|
|||||||
appClock.now() +
|
appClock.now() +
|
||||||
(REPLAY_TILE_SETTLE_DELAY_SEC + REPLAY_COUNTDOWN_DURATION_SEC) * 1000,
|
(REPLAY_TILE_SETTLE_DELAY_SEC + REPLAY_COUNTDOWN_DURATION_SEC) * 1000,
|
||||||
);
|
);
|
||||||
let cameraReady = false;
|
}
|
||||||
|
let cameraReady = resuming;
|
||||||
let lastCameraUpdateMs = null;
|
let lastCameraUpdateMs = null;
|
||||||
|
|
||||||
// Move the chase camera before scene traversal. Mutating it in preRender
|
// Move the chase camera before scene traversal. Mutating it in preRender
|
||||||
@@ -486,6 +491,7 @@ export function createReplay({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (resuming) return true;
|
||||||
layerState._viewer.camera.flyToBoundingSphere(
|
layerState._viewer.camera.flyToBoundingSphere(
|
||||||
geo.boundingSphere.create(track.ascentPath[0], 0),
|
geo.boundingSphere.create(track.ascentPath[0], 0),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -75,7 +75,12 @@ export function createSelection({
|
|||||||
stopMissionZoomAnchor();
|
stopMissionZoomAnchor();
|
||||||
layerState._viewer.selectedEntity = undefined;
|
layerState._viewer.selectedEntity = undefined;
|
||||||
}
|
}
|
||||||
|
showSelectedMission();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSelectedMission() {
|
||||||
if (!layerState._dataSource) return;
|
if (!layerState._dataSource) return;
|
||||||
|
const launchId = layerState._selectedLaunchId;
|
||||||
for (const entity of layerState._dataSource.entities.values) {
|
for (const entity of layerState._dataSource.entities.values) {
|
||||||
const relatedId = entityLaunchId(entity);
|
const relatedId = entityLaunchId(entity);
|
||||||
entity.show = launchId
|
entity.show = launchId
|
||||||
@@ -107,6 +112,7 @@ export function createSelection({
|
|||||||
Number(initialRange) || MISSION_GLOBE_VIEW_RANGE_M,
|
Number(initialRange) || MISSION_GLOBE_VIEW_RANGE_M,
|
||||||
);
|
);
|
||||||
layerState._missionZoomAnchorId = launch.id;
|
layerState._missionZoomAnchorId = launch.id;
|
||||||
|
layerState._missionZoomRange = range;
|
||||||
layerState._viewer.camera.lookAt(
|
layerState._viewer.camera.lookAt(
|
||||||
target,
|
target,
|
||||||
getRenderer().range(0, parts.camera.missionZoomPitch(range), range),
|
getRenderer().range(0, parts.camera.missionZoomPitch(range), range),
|
||||||
@@ -219,6 +225,7 @@ export function createSelection({
|
|||||||
focusFullGlobe,
|
focusFullGlobe,
|
||||||
entityLaunchId,
|
entityLaunchId,
|
||||||
setSelectedMission,
|
setSelectedMission,
|
||||||
|
showSelectedMission,
|
||||||
stopMissionZoomAnchor,
|
stopMissionZoomAnchor,
|
||||||
startMissionZoomAnchor,
|
startMissionZoomAnchor,
|
||||||
focusMission,
|
focusMission,
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export function createCatalog({ state: layerState, services, parts, source }) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
async function _loadDenseCatalog({ signal = null } = {}) {
|
async function _loadDenseCatalog({ signal = null } = {}) {
|
||||||
|
if (layerState._waitForRenderer) await layerState._waitForRenderer;
|
||||||
if (!layerState._viewer || !layerState._pointCollection)
|
if (!layerState._viewer || !layerState._pointCollection)
|
||||||
return { status: 'source-unavailable', reason: 'layer-unavailable' };
|
return { status: 'source-unavailable', reason: 'layer-unavailable' };
|
||||||
layerState._denseLoadController?.abort();
|
layerState._denseLoadController?.abort();
|
||||||
@@ -103,6 +104,7 @@ export function createCatalog({ state: layerState, services, parts, source }) {
|
|||||||
let added = 0;
|
let added = 0;
|
||||||
|
|
||||||
for (let start = 0; start < entries.length; start += DENSE_CREATE_CHUNK) {
|
for (let start = 0; start < entries.length; start += DENSE_CREATE_CHUNK) {
|
||||||
|
if (layerState._waitForRenderer) await layerState._waitForRenderer;
|
||||||
loadSignal.throwIfAborted();
|
loadSignal.throwIfAborted();
|
||||||
if (
|
if (
|
||||||
token !== layerState._denseLoadToken ||
|
token !== layerState._denseLoadToken ||
|
||||||
|
|||||||
Vendored
+5
@@ -253,6 +253,10 @@ export function createControls({ state: layerState, services, parts, source }) {
|
|||||||
* @param {string|number} noradId NORAD catalog number.
|
* @param {string|number} noradId NORAD catalog number.
|
||||||
* @returns {boolean} True if tracking started.
|
* @returns {boolean} True if tracking started.
|
||||||
*/
|
*/
|
||||||
|
hasContact(id) {
|
||||||
|
return layerState._points.has(Number(id));
|
||||||
|
},
|
||||||
|
|
||||||
trackById(noradId, { origin = 'programmatic' } = {}) {
|
trackById(noradId, { origin = 'programmatic' } = {}) {
|
||||||
const id = Number(noradId);
|
const id = Number(noradId);
|
||||||
if (
|
if (
|
||||||
@@ -262,6 +266,7 @@ export function createControls({ state: layerState, services, parts, source }) {
|
|||||||
!layerState._points.has(id)
|
!layerState._points.has(id)
|
||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
|
if (layerState._restoringViewport && layerState._trackedNorad === id) return true;
|
||||||
parts.tracking._cancelPendingTrackingRestore();
|
parts.tracking._cancelPendingTrackingRestore();
|
||||||
parts.tracking._trackSatellite(id, { origin });
|
parts.tracking._trackSatellite(id, { origin });
|
||||||
return layerState._trackedNorad === id;
|
return layerState._trackedNorad === id;
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ export function createIngestion({
|
|||||||
? `${failed.length} CelesTrak group${failed.length === 1 ? '' : 's'} unavailable`
|
? `${failed.length} CelesTrak group${failed.length === 1 ? '' : 's'} unavailable`
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
if (layerState._waitForRenderer) {
|
||||||
|
await layerState._waitForRenderer;
|
||||||
|
updateSignal.throwIfAborted();
|
||||||
|
}
|
||||||
// Clear existing
|
// Clear existing
|
||||||
layerState._pointCollection.removeAll();
|
layerState._pointCollection.removeAll();
|
||||||
layerState._points.clear();
|
layerState._points.clear();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ISS_OVERLAY_SOURCE_ID } from './policy.js';
|
import { ISS_NORAD, ISS_OVERLAY_SOURCE_ID } from './policy.js';
|
||||||
|
|
||||||
export function createLifecycle({
|
export function createLifecycle({
|
||||||
state: layerState,
|
state: layerState,
|
||||||
@@ -64,6 +64,78 @@ export function createLifecycle({
|
|||||||
console.log('[Data:Satellites] Initialized');
|
console.log('[Data:Satellites] Initialized');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
suspendCamera() {
|
||||||
|
// Outgoing tools release their cameras during teardown. That release
|
||||||
|
// must not classify a retained follow as a user deselection.
|
||||||
|
layerState._trackedEntityChangedRemove?.();
|
||||||
|
layerState._trackedEntityChangedRemove = null;
|
||||||
|
},
|
||||||
|
detachRenderer() {
|
||||||
|
const renderer = layerState._renderer;
|
||||||
|
if (!renderer) return;
|
||||||
|
layerState._waitForRenderer ??= new Promise(resolve => { layerState._resumeRenderer = resolve; });
|
||||||
|
layerState._clickHandler?.destroy();
|
||||||
|
layerState._trackedEntityChangedRemove?.();
|
||||||
|
layerState._preRenderListener?.();
|
||||||
|
layerState._clickHandler = layerState._trackedEntityChangedRemove = layerState._preRenderListener = null;
|
||||||
|
document.removeEventListener('keydown', parts.interaction._onKeyDown);
|
||||||
|
unregisterPickOwner('satellites');
|
||||||
|
if (layerState._trackedEntity) {
|
||||||
|
layerState._handoffOrigin = layerState._trackedEntity.gevSelectionOrigin;
|
||||||
|
renderer.follow.release();
|
||||||
|
renderer.removeTracked(layerState._trackedEntity);
|
||||||
|
layerState._trackedEntity = null;
|
||||||
|
}
|
||||||
|
for (const [id, point] of layerState._points) {
|
||||||
|
const {position, show, pixelSize, color, outlineColor, outlineWidth, scaleByDistance, disableDepthTestDistance} = point;
|
||||||
|
layerState._points.set(id, {id, position: {...position}, show, pixelSize, color, outlineColor, outlineWidth, scaleByDistance, disableDepthTestDistance});
|
||||||
|
}
|
||||||
|
for (const path of layerState._orbitPaths.values()) {
|
||||||
|
renderer.removeOrbit(path.primitive);
|
||||||
|
path.primitive = null;
|
||||||
|
}
|
||||||
|
layerState._detectionObjects.clear();
|
||||||
|
renderer.removePoints(layerState._pointCollection);
|
||||||
|
renderer.destroy();
|
||||||
|
layerState._engine = layerState._viewer = layerState._renderer = layerState._pointCollection = null;
|
||||||
|
layerState._overlayHost.clearSource(ISS_OVERLAY_SOURCE_ID);
|
||||||
|
if (layerState._enabled) releaseContinuousRender('satellites');
|
||||||
|
},
|
||||||
|
|
||||||
|
attachRenderer(viewer, {engine = null, waitForCameraRestore = false} = {}) {
|
||||||
|
layerState._engine = engine;
|
||||||
|
const renderer = services.renderer.create({viewer,engine});
|
||||||
|
layerState._renderer = renderer;
|
||||||
|
layerState._viewer = renderer.view;
|
||||||
|
layerState._pointCollection = renderer.createPoints();
|
||||||
|
for (const [id, spec] of layerState._points)
|
||||||
|
layerState._points.set(id, layerState._pointCollection.add(spec));
|
||||||
|
layerState._pointCollection.show = layerState._enabled && layerState._params.showPoints;
|
||||||
|
for (const [id, path] of layerState._orbitPaths) {
|
||||||
|
path.primitive = renderer.createOrbit({ positions: path.basePositions, width: id === ISS_NORAD ? 2.5 : 2, color: path.color });
|
||||||
|
path.primitive.show = layerState._enabled && layerState._params.showOrbits;
|
||||||
|
}
|
||||||
|
// Source work can publish as soon as GPU collections exist. Restoring
|
||||||
|
// share-selected camera state may itself wait for that publication.
|
||||||
|
layerState._resumeRenderer?.();
|
||||||
|
layerState._waitForRenderer = layerState._resumeRenderer = null;
|
||||||
|
// The restored camera runs before a retained follow gets its new native target.
|
||||||
|
layerState._restoringViewport = true;
|
||||||
|
if (!waitForCameraRestore) this.resumeViewport();
|
||||||
|
},
|
||||||
|
|
||||||
|
resumeViewport() {
|
||||||
|
if (!layerState._restoringViewport || !layerState._renderer) return;
|
||||||
|
layerState._restoringViewport = false;
|
||||||
|
if (layerState._enabled) {
|
||||||
|
if (layerState._trackedNorad != null && !layerState._trackedEntity)
|
||||||
|
parts.tracking._trackSatellite(layerState._trackedNorad, {origin: layerState._handoffOrigin, resuming: true});
|
||||||
|
this.enable(layerState._viewer);
|
||||||
|
}
|
||||||
|
layerState._resumeRenderer?.();
|
||||||
|
layerState._waitForRenderer = layerState._resumeRenderer = null;
|
||||||
|
},
|
||||||
|
|
||||||
enable(viewer) {
|
enable(viewer) {
|
||||||
layerState._enabled = true;
|
layerState._enabled = true;
|
||||||
holdContinuousRender('satellites'); // per-frame animator (perf wave 2)
|
holdContinuousRender('satellites'); // per-frame animator (perf wave 2)
|
||||||
@@ -124,6 +196,8 @@ export function createLifecycle({
|
|||||||
},
|
},
|
||||||
|
|
||||||
destroy(viewer) {
|
destroy(viewer) {
|
||||||
|
layerState._resumeRenderer?.();
|
||||||
|
layerState._waitForRenderer = layerState._resumeRenderer = null;
|
||||||
parts.catalog._abortActiveUpdates();
|
parts.catalog._abortActiveUpdates();
|
||||||
releaseContinuousRender('satellites'); // direct-destroy path (perf wave 2 fix)
|
releaseContinuousRender('satellites'); // direct-destroy path (perf wave 2 fix)
|
||||||
layerState._enabled = false;
|
layerState._enabled = false;
|
||||||
@@ -151,7 +225,7 @@ export function createLifecycle({
|
|||||||
}
|
}
|
||||||
// Orbit ring primitives are removed (and destroyed) here
|
// Orbit ring primitives are removed (and destroyed) here
|
||||||
for (const path of layerState._orbitPaths.values()) {
|
for (const path of layerState._orbitPaths.values()) {
|
||||||
getRenderer().removeOrbit(path.primitive);
|
if (path.primitive) getRenderer().removeOrbit(path.primitive);
|
||||||
}
|
}
|
||||||
layerState._points.clear();
|
layerState._points.clear();
|
||||||
layerState._detectionObjects.clear();
|
layerState._detectionObjects.clear();
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export function createRendering({
|
|||||||
layerState._orbitPaths.set(noradId, {
|
layerState._orbitPaths.set(noradId, {
|
||||||
primitive,
|
primitive,
|
||||||
basePositions,
|
basePositions,
|
||||||
|
color: pathColor,
|
||||||
// Same Date object as computeOrbitPath's internal fixedGmst → identical
|
// Same Date object as computeOrbitPath's internal fixedGmst → identical
|
||||||
// GMST value (gstime is pure), so delta starts at exactly 0 and the
|
// GMST value (gstime is pure), so delta starts at exactly 0 and the
|
||||||
// initial identity modelMatrix is correct until the first ring tick.
|
// initial identity modelMatrix is correct until the first ring tick.
|
||||||
|
|||||||
@@ -412,8 +412,8 @@ export function createTracking({
|
|||||||
refreshTrackedReadout(layerState._trackedEntity);
|
refreshTrackedReadout(layerState._trackedEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _trackSatellite(noradId, { origin = 'programmatic' } = {}) {
|
function _trackSatellite(noradId, { origin = 'programmatic', resuming = false } = {}) {
|
||||||
_clearTracking(false, { origin });
|
if (!resuming) _clearTracking(false, { origin });
|
||||||
|
|
||||||
const point = layerState._points.get(noradId);
|
const point = layerState._points.get(noradId);
|
||||||
const sat = layerState._catalog.get(noradId);
|
const sat = layerState._catalog.get(noradId);
|
||||||
@@ -470,6 +470,7 @@ export function createTracking({
|
|||||||
layerState._trackedEntity.gevDisplayPosition = _trackedDisplayCached;
|
layerState._trackedEntity.gevDisplayPosition = _trackedDisplayCached;
|
||||||
_updateTrackedSatelliteLabelModel(initialPos?.altitude ?? null);
|
_updateTrackedSatelliteLabelModel(initialPos?.altitude ?? null);
|
||||||
|
|
||||||
|
if (!resuming) {
|
||||||
_emitAwarenessEvent('gev:awareness-subject-selected', {
|
_emitAwarenessEvent('gev:awareness-subject-selected', {
|
||||||
layerId: 'satellites',
|
layerId: 'satellites',
|
||||||
id: noradId,
|
id: noradId,
|
||||||
@@ -479,6 +480,7 @@ export function createTracking({
|
|||||||
});
|
});
|
||||||
selectTrackedSubjectContext(_contextSubjectMetadata(noradId, initialPos));
|
selectTrackedSubjectContext(_contextSubjectMetadata(noradId, initialPos));
|
||||||
layerState._contextRefreshedAtMs = Date.now();
|
layerState._contextRefreshedAtMs = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
getRenderer().follow.start(layerState._trackedEntity);
|
getRenderer().follow.start(layerState._trackedEntity);
|
||||||
console.log(`[Data:Satellites] Tracking ${name} (NORAD ${noradId})`);
|
console.log(`[Data:Satellites] Tracking ${name} (NORAD ${noradId})`);
|
||||||
|
|||||||
+4
-1
@@ -141,7 +141,10 @@ async function reconcile(remember = false) {
|
|||||||
id, { entry: entry.module.attachRenderer ? entry : null, enabled: manager.isEffectivelyEnabled(id) },
|
id, { entry: entry.module.attachRenderer ? entry : null, enabled: manager.isEffectivelyEnabled(id) },
|
||||||
]))
|
]))
|
||||||
: null;
|
: null;
|
||||||
if (layerSession.handoffLayers) manager.layers.get('directions')?.module.suspendCamera?.();
|
if (layerSession.handoffLayers) {
|
||||||
|
manager.layers.get('directions')?.module.suspendCamera?.();
|
||||||
|
manager.layers.get('satellites')?.module.suspendCamera?.();
|
||||||
|
}
|
||||||
renderers.retain(kind);
|
renderers.retain(kind);
|
||||||
await application.destroy();
|
await application.destroy();
|
||||||
await start(pageEngineKind(), true, cockpit);
|
await start(pageEngineKind(), true, cockpit);
|
||||||
|
|||||||
@@ -625,7 +625,7 @@ export class StyleManager extends ShellFacade {
|
|||||||
this._publishActiveStyle();
|
this._publishActiveStyle();
|
||||||
this._updateLocationMiniStatus();
|
this._updateLocationMiniStatus();
|
||||||
|
|
||||||
this._shareRestoration.start({ engineSwitch });
|
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();
|
||||||
@@ -976,7 +976,7 @@ export class StyleManager extends ShellFacade {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (this._layerSession?.handoffLayers)
|
if (this._layerSession?.handoffLayers)
|
||||||
this._contextControls.resumeScene(this._layerSession.contacts);
|
this._contextControls.resumeScene(this._layerSession.contextScene);
|
||||||
// The Contacts layer publishes its panel; the view calls its navigation.
|
// The Contacts layer publishes its panel; the view calls its navigation.
|
||||||
contextStore.connectAwareness(militaryAwarenessLayer);
|
contextStore.connectAwareness(militaryAwarenessLayer);
|
||||||
// A panel left by an earlier shell must not outlive its layer.
|
// A panel left by an earlier shell must not outlive its layer.
|
||||||
@@ -1856,15 +1856,15 @@ 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();
|
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._layerSession && !this._layerSession.lifetime.signal.aborted &&
|
||||||
this._contextControls.canRetainContacts()) {
|
this._contextControls.canRetainScene()) {
|
||||||
this._layerSession.contacts = this._contextControls.captureScene();
|
this._layerSession.contextScene = this._contextControls.captureScene();
|
||||||
} else {
|
} else {
|
||||||
if (this._layerSession) this._layerSession.contacts = null;
|
if (this._layerSession) this._layerSession.contextScene = null;
|
||||||
await this._contextControls.restoreForDisposal();
|
await this._contextControls.restoreForDisposal();
|
||||||
}
|
}
|
||||||
// IR boost teardown BEFORE detaching the data manager: restore fog and
|
// IR boost teardown BEFORE detaching the data manager: restore fog and
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ export class ContextControls {
|
|||||||
this[field] = null;
|
this[field] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
canRetainContacts() {
|
canRetainScene() {
|
||||||
return this._contextMode === 'flights' && !this._contextModeChanging &&
|
return ['flights', 'space-missions'].includes(this._contextMode) && !this._contextModeChanging &&
|
||||||
!this._contextRestoreState && !this._clearSelectedLayersPromise &&
|
!this._contextRestoreState && !this._clearSelectedLayersPromise &&
|
||||||
!this._contextLayerReactionPromises.size;
|
!this._contextLayerReactionPromises.size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,8 +280,8 @@ export class NavigationController {
|
|||||||
this._cameraHandoffs?.publish();
|
this._cameraHandoffs?.publish();
|
||||||
this._cameraHandoffs?.destroy();
|
this._cameraHandoffs?.destroy();
|
||||||
}
|
}
|
||||||
destroy() {
|
destroy({ retainScene = false } = {}) {
|
||||||
this.stop();
|
this.stop();
|
||||||
this._stampNavigation();
|
this._stampNavigation({cancelPendingSelection: !retainScene});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,7 +155,7 @@ export class ShareRestoration {
|
|||||||
this.shareLinkManager = shareLinkManager;
|
this.shareLinkManager = shareLinkManager;
|
||||||
this._initialShareState = shareLinkManager.parseInitialHash();
|
this._initialShareState = shareLinkManager.parseInitialHash();
|
||||||
}
|
}
|
||||||
start({ engineSwitch = false } = {}) {
|
start({ engineSwitch = false, retainScene = false } = {}) {
|
||||||
// Restore from URL hash if present
|
// Restore from URL hash if present
|
||||||
const savedState = this._initialShareState;
|
const savedState = this._initialShareState;
|
||||||
this._initialShareRestorePromise = savedState
|
this._initialShareRestorePromise = savedState
|
||||||
@@ -181,12 +181,13 @@ export class ShareRestoration {
|
|||||||
Number.isInteger(generation) &&
|
Number.isInteger(generation) &&
|
||||||
this.navigation._reassertNavigationHandoff(generation, {
|
this.navigation._reassertNavigationHandoff(generation, {
|
||||||
trackingOrigin: engineSwitch ? 'share-restore' : 'tool',
|
trackingOrigin: engineSwitch ? 'share-restore' : 'tool',
|
||||||
keepTracking: Boolean(retained),
|
keepTracking: retainScene || Boolean(retained),
|
||||||
});
|
});
|
||||||
void this._runShareRestore(savedState, {
|
void this._runShareRestore(savedState, {
|
||||||
applyCamera,
|
applyCamera,
|
||||||
instant: engineSwitch,
|
instant: engineSwitch,
|
||||||
retained,
|
retained,
|
||||||
|
retainScene,
|
||||||
navigationToken: generation,
|
navigationToken: generation,
|
||||||
layersPromise: this._layerStateRestorePromise,
|
layersPromise: this._layerStateRestorePromise,
|
||||||
settle: (result) => this._settleInitialShareRestore(result),
|
settle: (result) => this._settleInitialShareRestore(result),
|
||||||
@@ -245,7 +246,7 @@ export class ShareRestoration {
|
|||||||
*/
|
*/
|
||||||
async _runShareRestore(
|
async _runShareRestore(
|
||||||
state,
|
state,
|
||||||
{ applyCamera, instant = false, retained, navigationToken, layersPromise, settle },
|
{ applyCamera, instant = false, retained, retainScene = false, navigationToken, layersPromise, settle },
|
||||||
) {
|
) {
|
||||||
const generation = this._shareRestoreGeneration;
|
const generation = this._shareRestoreGeneration;
|
||||||
const current = () =>
|
const current = () =>
|
||||||
@@ -258,6 +259,7 @@ export class ShareRestoration {
|
|||||||
});
|
});
|
||||||
const layers = await (layersPromise || Promise.resolve([]));
|
const layers = await (layersPromise || Promise.resolve([]));
|
||||||
if (!current()) return;
|
if (!current()) return;
|
||||||
|
if (retainScene && !retained) retained = this._layerStateCoordinator?.retainedTrackingTarget();
|
||||||
const tracking =
|
const tracking =
|
||||||
share.camera === 'applied'
|
share.camera === 'applied'
|
||||||
? (retained && this._layerStateCoordinator.resumeRetainedTracking(retained)) ||
|
? (retained && this._layerStateCoordinator.resumeRetainedTracking(retained)) ||
|
||||||
|
|||||||
@@ -236,7 +236,6 @@ export const VIEWER_ONLY_ACTIONS = Object.freeze([
|
|||||||
'clear_annotations',
|
'clear_annotations',
|
||||||
'fly_route',
|
'fly_route',
|
||||||
'move_camera',
|
'move_camera',
|
||||||
'set_context_mode',
|
|
||||||
]);
|
]);
|
||||||
/** The refusal code of a viewer-only tool on an engine without a viewer. */
|
/** The refusal code of a viewer-only tool on an engine without a viewer. */
|
||||||
export const UNAVAILABLE_ON_ENGINE = 'UNAVAILABLE_ON_ENGINE';
|
export const UNAVAILABLE_ON_ENGINE = 'UNAVAILABLE_ON_ENGINE';
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ test('the viewer-only actions are one exported list of real tools', () => {
|
|||||||
'clear_annotations',
|
'clear_annotations',
|
||||||
'fly_route',
|
'fly_route',
|
||||||
'move_camera',
|
'move_camera',
|
||||||
'set_context_mode',
|
|
||||||
]);
|
]);
|
||||||
assert.ok(Object.isFrozen(VIEWER_ONLY_ACTIONS));
|
assert.ok(Object.isFrozen(VIEWER_ONLY_ACTIONS));
|
||||||
const tools = new Set(GEV_ACTION_SCHEMAS.map((schema) => schema.name));
|
const tools = new Set(GEV_ACTION_SCHEMAS.map((schema) => schema.name));
|
||||||
|
|||||||
Reference in new issue
Block a user