mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-08 04:14:02 +00:00
Carry the Nepal event images, playback and media across globes
This commit is contained in:
1 parent
811bcaff85
commit
4e09559337
19 files changed
+426
-84
No files matched your search
@@ -3680,6 +3680,7 @@
|
||||
"src/layers/submarineCables/render/navara.js",
|
||||
"src/layers/directions/render/navara.js",
|
||||
"src/layers/shared/render/navaraStory.js",
|
||||
"src/layers/bhoteKoshi/render/navara.js",
|
||||
"src/layers/weather/render/navara.js",
|
||||
"src/layers/weather/render/navaraSurface.js",
|
||||
"src/layers/wind/render/navara.js",
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import sharp from 'sharp';
|
||||
import puppeteer from 'puppeteer';
|
||||
import assert from 'node:assert/strict';
|
||||
import { blockPaidQaMaps } from './qa-map-policy.mjs';
|
||||
|
||||
const base = new URL(process.argv[2]);
|
||||
if (
|
||||
!['localhost', '127.0.0.1'].includes(base.hostname) ||
|
||||
['', '4173', '4185', '4186', '4187', '4188', '4189', '4194'].includes(
|
||||
base.port,
|
||||
)
|
||||
)
|
||||
throw new Error('Supply an unreserved local keyless preview URL');
|
||||
base.search = '?engine=navara&welcome=0&look=cinematic';
|
||||
base.hash =
|
||||
'v=2&lat=28.2110&lon=85.3380&alt=9000&heading=0&pitch=-90&roll=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);
|
||||
await p.setViewport({ width: 1024, height: 768 });
|
||||
const errors = [];
|
||||
p.on('pageerror', (e) => {
|
||||
errors.push(e.message);
|
||||
console.log('error', e.message.split('?')[0]);
|
||||
});
|
||||
p.on('console', (msg) => {
|
||||
if (msg.type() === 'error' && /Shader|WebGLProgram/i.test(msg.text()))
|
||||
errors.push(msg.text());
|
||||
});
|
||||
const assets = [];
|
||||
p.on('request', (req) => {
|
||||
const path = new URL(req.url()).pathname;
|
||||
if (
|
||||
/\/events\/bhote-koshi-2026\/(event\.json|pre\.webp|post\.webp)$/.test(
|
||||
path,
|
||||
)
|
||||
)
|
||||
assets.push(path);
|
||||
});
|
||||
await p.goto(base.href);
|
||||
const ready = async (kind) => {
|
||||
await p.waitForFunction(
|
||||
(k) =>
|
||||
window.__godsEyeView?.engine.kind === k &&
|
||||
!document.querySelector('#globe-engine')?.disabled &&
|
||||
!document.querySelector('.engine-switch-frame') &&
|
||||
document.querySelector('#loading-screen.hidden'),
|
||||
{ timeout: 90000 },
|
||||
kind,
|
||||
);
|
||||
await p.evaluate(
|
||||
() => window.__godsEyeView.styleManager.initialRestorePromise,
|
||||
);
|
||||
};
|
||||
await ready('navara');
|
||||
await p.evaluate(async () => {
|
||||
const { tileset, dataManager, sceneDirector } = window.__godsEyeView;
|
||||
if (!sceneDirector?.listScenes().length) throw Error('No scenes available on Navara');
|
||||
if (tileset || window.__GOOGLE_MAPS_API_KEY__)
|
||||
throw Error('Keyless preview required');
|
||||
const mod = dataManager.layers.get('bhote-koshi-2026').module;
|
||||
window.qaEvent = mod;
|
||||
await dataManager.setEnabled(mod.id, true, { origin: 'user' });
|
||||
mod.setParams({
|
||||
presentation: 'standalone',
|
||||
progress: 0.52,
|
||||
split: 0.27,
|
||||
autoPlay: false,
|
||||
});
|
||||
});
|
||||
const sample = async () => {
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
return sharp(await p.screenshot())
|
||||
.extract({ left: 490, top: 370, width: 25, height: 25 })
|
||||
.removeAlpha()
|
||||
.raw()
|
||||
.toBuffer();
|
||||
};
|
||||
for (const kind of ['navara', 'cesium', 'navara']) {
|
||||
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
|
||||
await p.select('#globe-engine', kind);
|
||||
await ready(kind);
|
||||
}
|
||||
const state = await p.evaluate(() => {
|
||||
const { dataManager } = window.__godsEyeView;
|
||||
return {
|
||||
same:
|
||||
window.qaEvent === dataManager.layers.get('bhote-koshi-2026').module,
|
||||
state: window.qaEvent.getPlaybackState(),
|
||||
player:
|
||||
document.querySelector('#bhote-koshi-embedded-media-root') ===
|
||||
window.qaPlayer,
|
||||
};
|
||||
});
|
||||
assert.equal(state.same, true);
|
||||
assert.equal(state.state.enabled, true);
|
||||
assert.equal(state.state.split, 0.27);
|
||||
assert.equal(state.state.playing, false);
|
||||
assert.equal(state.state.progress, 0.52);
|
||||
await p.evaluate(() => {
|
||||
window.qaPlayer ??= document.querySelector(
|
||||
'#bhote-koshi-embedded-media-root',
|
||||
);
|
||||
window.qaEvent.setParams({ split: 0.75 });
|
||||
});
|
||||
assert.ok(await p.evaluate(()=>Boolean(window.qaPlayer)), 'persistent embedded media root');
|
||||
const before = await sample();
|
||||
await p.evaluate(() => window.qaEvent.setParams({ split: 0.27 }));
|
||||
const after = await sample();
|
||||
const diff =
|
||||
before.reduce((n, v, i) => n + Math.abs(v - after[i]), 0) / before.length;
|
||||
console.log(kind, state.state, 'imagery difference', diff);
|
||||
assert.ok(diff > 6, 'divider changes the actual imagery in the viewport');
|
||||
assert.equal(
|
||||
await p.evaluate(
|
||||
() =>
|
||||
document.querySelector('#bhote-koshi-embedded-media-root') ===
|
||||
window.qaPlayer,
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
for (const suffix of ['/event.json', '/pre.webp', '/post.webp'])
|
||||
assert.equal(
|
||||
assets.filter((a) => a.endsWith(suffix)).length,
|
||||
1,
|
||||
JSON.stringify(assets),
|
||||
);
|
||||
await p.evaluate(() => window.qaEvent.setParams({ autoPlay: true }));
|
||||
await p.waitForFunction(
|
||||
() => window.qaEvent.getPlaybackState().progress > 0.53,
|
||||
);
|
||||
await p.select('#globe-engine', 'cesium');
|
||||
await ready('cesium');
|
||||
const live = await p.evaluate(() => window.qaEvent.getPlaybackState());
|
||||
assert.ok(live.playing && live.progress > 0.53);
|
||||
await p.evaluate(() =>
|
||||
window.__godsEyeView.dataManager.setEnabled('bhote-koshi-2026', false, {
|
||||
origin: 'user',
|
||||
}),
|
||||
);
|
||||
assert.deepEqual(errors, []);
|
||||
console.log(
|
||||
'PASS event timeline, paused and playing handoff, image pair, divider, player host and single asset acquisition',
|
||||
);
|
||||
} finally {
|
||||
await b.close();
|
||||
}
|
||||
@@ -160,7 +160,9 @@ export function createApplicationCatalog({
|
||||
// Catalog order: [layer id(s), construction]. Only the layers the
|
||||
// engine can show are constructed (renderers.js).
|
||||
const entries = [
|
||||
['bhote-koshi-2026', () => createBhoteKoshiEventLayer()],
|
||||
['bhote-koshi-2026', () => sessionModel('bhote-koshi-2026', () => createBhoteKoshiEventLayer({
|
||||
createNativeRenderer: (engine) => rendererFor('bhote-koshi-2026', engine),
|
||||
}))],
|
||||
[
|
||||
'bhote-koshi-locator',
|
||||
() =>
|
||||
|
||||
+2
-2
@@ -45,10 +45,10 @@ export function createApplicationData({
|
||||
onData?.(dataManager);
|
||||
if (!catalog?.layers || !catalog?.metadata)
|
||||
throw new TypeError('An application layer catalog is required');
|
||||
const attached = dataManager.bindCatalog(viewer, engine, catalog);
|
||||
for (const layer of catalog.layers) layer.attachDataManager?.(dataManager);
|
||||
for (const layer of catalog.layers)
|
||||
layer.attachMapStackController?.(mapStackController);
|
||||
const attached = dataManager.bindCatalog(viewer, engine, catalog);
|
||||
for (const layer of catalog.layers) layer.attachDataManager?.(dataManager);
|
||||
|
||||
if (allowQaRegistration) {
|
||||
window.__gevQaRegisterLayer = (targetManager, layerModule) => {
|
||||
|
||||
@@ -43,6 +43,7 @@ test('the Navara mode registers migrated renderers once', async () => {
|
||||
.available()
|
||||
.map(({ id }) => id),
|
||||
[
|
||||
'bhote-koshi-2026',
|
||||
'bhote-koshi-locator',
|
||||
'flights',
|
||||
'military',
|
||||
|
||||
@@ -38,9 +38,7 @@ export function navaraModeNotice(availability) {
|
||||
const names = availability.available().map(({ name }) => name);
|
||||
return Object.freeze({
|
||||
label: t('Experimental Navara engine'),
|
||||
detail: names.length
|
||||
? `Data layers: ${names.join(', ')}; the other layers and director are unavailable`
|
||||
: 'No data layers on this engine yet; cockpit and director are unavailable',
|
||||
detail: names.length ? `Data layers: ${names.join(', ')}` : 'No registered data layers',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,6 +118,10 @@ export async function registerNavaraLayerRenderers() {
|
||||
return (engine, options) => createWeatherNavaraRenderer(engine, navaraViews.get(engine), options);
|
||||
},
|
||||
])),
|
||||
'bhote-koshi-2026': async () => {
|
||||
const { createNepalEventDisplay } = await import('../layers/bhoteKoshi/render/navara.js');
|
||||
return (engine) => createNepalEventDisplay(engine, navaraViews.get(engine));
|
||||
},
|
||||
'bhote-koshi-locator': async () => {
|
||||
const { drawStorySource } = await import('../layers/shared/render/navaraStory.js');
|
||||
return (engine, { data }) => drawStorySource(engine, navaraViews.get(engine), data);
|
||||
@@ -331,6 +333,7 @@ export function createViewerlessTools({
|
||||
placeSearch,
|
||||
voice = {},
|
||||
startChrome,
|
||||
sceneDirector,
|
||||
signal,
|
||||
defer,
|
||||
}) {
|
||||
@@ -402,7 +405,7 @@ export function createViewerlessTools({
|
||||
styleManager,
|
||||
tileset: null,
|
||||
dataManager,
|
||||
sceneDirector: null,
|
||||
sceneDirector,
|
||||
mapStackController,
|
||||
annotations: null,
|
||||
weatherEffects: null,
|
||||
@@ -428,7 +431,7 @@ export function createViewerlessTools({
|
||||
engine,
|
||||
styleManager,
|
||||
dataManager,
|
||||
sceneDirector: null,
|
||||
sceneDirector,
|
||||
annotations: null,
|
||||
});
|
||||
defer(() => {
|
||||
@@ -437,5 +440,5 @@ export function createViewerlessTools({
|
||||
delete window.__gevVoiceCommands;
|
||||
});
|
||||
debug.voiceCommands = voiceCommands;
|
||||
return { sceneDirector: null, annotations: null, voiceCommands };
|
||||
return { sceneDirector, annotations: null, voiceCommands };
|
||||
}
|
||||
+17
-16
@@ -41,22 +41,6 @@ export function createApplicationTools({
|
||||
const { viewer, engine, tileset, mapStackController, operations } = scene;
|
||||
const { styleManager, weatherEffects, cockpitCloudEffects } = controls;
|
||||
const { dataManager } = data;
|
||||
// Without a viewer (the Navara mode) the tools load with that mode's code.
|
||||
if (!viewer) {
|
||||
return import('./navaraMode.js').then(({ createViewerlessTools }) =>
|
||||
createViewerlessTools({
|
||||
scene,
|
||||
controls,
|
||||
data,
|
||||
loadingScreen,
|
||||
placeSearch,
|
||||
voice,
|
||||
startChrome,
|
||||
signal,
|
||||
defer,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const sceneDirector = new SceneDirector(viewer, styleManager, dataManager, {
|
||||
engine,
|
||||
dataPacks: sceneDataPacks,
|
||||
@@ -68,6 +52,23 @@ export function createApplicationTools({
|
||||
?.module.attachSceneController(sceneDirector);
|
||||
defer(() => sceneDirector.destroy());
|
||||
onSceneDirector?.(sceneDirector);
|
||||
// Without a viewer (the Navara mode) the tools load with that mode's code.
|
||||
if (!viewer) {
|
||||
return import('./navaraMode.js').then(({ createViewerlessTools }) =>
|
||||
createViewerlessTools({
|
||||
scene,
|
||||
controls,
|
||||
data,
|
||||
loadingScreen,
|
||||
placeSearch,
|
||||
voice,
|
||||
sceneDirector,
|
||||
startChrome,
|
||||
signal,
|
||||
defer,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const annotations = initAnnotations({
|
||||
viewer,
|
||||
tileset,
|
||||
|
||||
@@ -17,11 +17,15 @@ import {
|
||||
// The tools without a viewer (the experimental Navara engine); they load
|
||||
// from src/app/navaraMode.js.
|
||||
test('without a viewer the tools run the governor on the engine, show the notice, create no viewer tools and run voice on the engine', async (t) => {
|
||||
const saved = { window: globalThis.window, document: globalThis.document };
|
||||
const saved = { window: globalThis.window, document: globalThis.document, localStorage:globalThis.localStorage };
|
||||
const listeners = new Map();
|
||||
globalThis.window = {};
|
||||
globalThis.localStorage = { getItem:()=>null,setItem(){},removeItem(){} };
|
||||
globalThis.document = {
|
||||
hidden: false,
|
||||
getElementById: () => null,
|
||||
querySelector: () => null,
|
||||
body: {classList:{add(){},remove(){},toggle(){},contains(){return false;}},appendChild(){}},
|
||||
addEventListener: (type, fn) => listeners.set(type, fn),
|
||||
removeEventListener: (type, fn) => {
|
||||
if (listeners.get(type) === fn) listeners.delete(type);
|
||||
@@ -84,7 +88,7 @@ test('without a viewer the tools run the governor on the engine, show the notice
|
||||
signal: new AbortController().signal,
|
||||
defer: (dispose) => cleanups.push(dispose),
|
||||
});
|
||||
assert.equal(tools.sceneDirector, null);
|
||||
assert.ok(tools.sceneDirector);
|
||||
assert.equal(tools.annotations, null);
|
||||
assert.ok(tools.voiceCommands, 'voice runs on the engine');
|
||||
assert.equal(window.__gevVoiceCommands, tools.voiceCommands);
|
||||
@@ -94,8 +98,7 @@ test('without a viewer the tools run the governor on the engine, show the notice
|
||||
const scene = await voice.runner('control_scene', {
|
||||
action: 'list',
|
||||
});
|
||||
assert.equal(scene.ok, false);
|
||||
assert.equal(scene.code, 'UNAVAILABLE_ON_ENGINE');
|
||||
assert.equal(scene.ok, true);
|
||||
assert.equal(chrome.length, 1);
|
||||
assert.equal(chrome[0].initializeWelcome, null, 'no first-run welcome');
|
||||
assert.equal(chrome[0].styleManager, styleManager);
|
||||
@@ -112,14 +115,14 @@ test('without a viewer the tools run the governor on the engine, show the notice
|
||||
]);
|
||||
assert.match(
|
||||
`${notice.label}: ${notice.detail}`,
|
||||
/^Experimental Navara engine: no data layers on this engine yet; cockpit and director are unavailable$/i,
|
||||
/^Experimental Navara engine: no registered data layers$/i,
|
||||
);
|
||||
// With Navara renderers, the notice names the layers it shows.
|
||||
const registry = createRendererRegistry();
|
||||
registry.register('navara', { satellites: () => ({}), flights: () => ({}) });
|
||||
assert.equal(
|
||||
navaraModeNotice(createLayerAvailability('navara', { registry })).detail,
|
||||
'Data layers: Live Flights, Satellites; the other layers and director are unavailable',
|
||||
'Data layers: Live Flights, Satellites',
|
||||
);
|
||||
// The governor drives the engine's continuous rendering.
|
||||
assert.equal(isRenderGovernorInstalled(engine), true);
|
||||
@@ -130,7 +133,7 @@ test('without a viewer the tools run the governor on the engine, show the notice
|
||||
const debug = window.__godsEyeView;
|
||||
assert.equal(debug.viewer, null);
|
||||
assert.equal(debug.engine, engine);
|
||||
assert.equal(debug.sceneDirector, null);
|
||||
assert.equal(debug.sceneDirector, tools.sceneDirector);
|
||||
assert.equal(debug.dataManager, dataManager);
|
||||
assert.equal(debug.voiceCommands, tools.voiceCommands);
|
||||
listeners.get('visibilitychange')();
|
||||
|
||||
@@ -538,9 +538,10 @@ function ensureProviderConnections(documentRef, provider) {
|
||||
}
|
||||
}
|
||||
|
||||
function anchorOnScreen(viewer, anchor, root) {
|
||||
if (!viewer?.scene || !anchor || !root) return null;
|
||||
const camera = viewer.camera;
|
||||
function anchorOnScreen(viewer, engine, anchor, root) {
|
||||
if ((!viewer?.scene && !engine) || !anchor || !root) return null;
|
||||
const camera = viewer?.camera;
|
||||
if (engine && !engine.projector.horizonVisible(anchor)) return null;
|
||||
if (camera?.positionWC && camera?.directionWC) {
|
||||
const delta = Cesium.Cartesian3.subtract(
|
||||
anchor,
|
||||
@@ -549,12 +550,12 @@ function anchorOnScreen(viewer, anchor, root) {
|
||||
);
|
||||
if (Cesium.Cartesian3.dot(delta, camera.directionWC) <= 0) return null;
|
||||
}
|
||||
const pixel = Cesium.SceneTransforms.worldToWindowCoordinates(
|
||||
const pixel = engine ? engine.projector.worldToWindow(anchor, {}) : Cesium.SceneTransforms.worldToWindowCoordinates(
|
||||
viewer.scene,
|
||||
anchor,
|
||||
);
|
||||
if (!pixel) return null;
|
||||
const canvasBounds = viewer.canvas?.getBoundingClientRect?.();
|
||||
const canvasBounds = (engine?.host.canvas ?? viewer?.canvas)?.getBoundingClientRect?.();
|
||||
const rootBounds = root.getBoundingClientRect?.();
|
||||
if (!canvasBounds || !rootBounds) return null;
|
||||
const x = pixel.x + canvasBounds.left - rootBounds.left;
|
||||
@@ -637,7 +638,7 @@ function createRoot(documentRef, viewer) {
|
||||
const root = documentRef.createElement('div');
|
||||
root.id = 'bhote-koshi-embedded-media-root';
|
||||
root.setAttribute('aria-live', 'polite');
|
||||
(viewer?.container || documentRef.body)?.append(root);
|
||||
(documentRef.getElementById?.('cesiumContainer') || viewer?.container || documentRef.body)?.append(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -648,6 +649,7 @@ function createRoot(documentRef, viewer) {
|
||||
*/
|
||||
export function createBhoteKoshiEmbeddedMedia({
|
||||
viewer,
|
||||
engine = null,
|
||||
documentRef = globalThis.document,
|
||||
globalRef = globalThis,
|
||||
xLoader = loadXWidgets,
|
||||
@@ -660,7 +662,7 @@ export function createBhoteKoshiEmbeddedMedia({
|
||||
const pinokioShell = /(?:^|\s)Pinokio\/[^\s]+/i.test(
|
||||
globalRef.navigator?.userAgent || '',
|
||||
);
|
||||
if (pinokioShell || !documentRef?.createElement || !viewer) {
|
||||
if (pinokioShell || !documentRef?.createElement || (!viewer && !engine)) {
|
||||
return {
|
||||
supportsPlayback: false,
|
||||
warm: () => false,
|
||||
@@ -671,7 +673,7 @@ export function createBhoteKoshiEmbeddedMedia({
|
||||
destroy: () => {},
|
||||
};
|
||||
}
|
||||
const root = createRoot(documentRef, viewer);
|
||||
const root = createRoot(documentRef, engine?.host ?? viewer);
|
||||
let record = null;
|
||||
let warmRecord = null;
|
||||
let generation = 0;
|
||||
@@ -848,7 +850,7 @@ export function createBhoteKoshiEmbeddedMedia({
|
||||
|
||||
const position = () => {
|
||||
if (!record) return;
|
||||
const projected = anchorOnScreen(viewer, record.anchor, root);
|
||||
const projected = anchorOnScreen(viewer, engine, record.anchor, root);
|
||||
record.card.hidden = !projected;
|
||||
if (projected) {
|
||||
placeCard(record.card, record.leader, projected);
|
||||
@@ -864,8 +866,16 @@ export function createBhoteKoshiEmbeddedMedia({
|
||||
}
|
||||
}
|
||||
};
|
||||
const removePostRender =
|
||||
viewer.scene?.postRender?.addEventListener?.(position);
|
||||
let removePostRender;
|
||||
function attachScene(nextViewer, nextEngine) {
|
||||
removePostRender?.();
|
||||
viewer = nextViewer;
|
||||
engine = nextEngine;
|
||||
const frames = engine?.frames.postRender ?? viewer?.scene?.postRender;
|
||||
removePostRender = frames?.addEventListener?.(position);
|
||||
position();
|
||||
}
|
||||
attachScene(viewer, engine);
|
||||
|
||||
function hide({ immediate = false, preserveWarm = false } = {}) {
|
||||
if (!preserveWarm) clearWarm();
|
||||
@@ -1223,6 +1233,7 @@ export function createBhoteKoshiEmbeddedMedia({
|
||||
}
|
||||
|
||||
return {
|
||||
attachScene,
|
||||
supportsPlayback: true,
|
||||
warm,
|
||||
show,
|
||||
|
||||
+111
-22
@@ -889,7 +889,7 @@ function openExternal(url) {
|
||||
if (target) target.opener = null;
|
||||
}
|
||||
|
||||
function focusView(viewer, viewpoint) {
|
||||
function focusView(viewer, viewpoint, engine) {
|
||||
announceNavigationAuthority('bhote-koshi-event-focus');
|
||||
const target = Cesium.Cartesian3.fromDegrees(
|
||||
viewpoint.lon,
|
||||
@@ -901,6 +901,17 @@ function focusView(viewer, viewpoint) {
|
||||
Cesium.Math.toRadians(viewpoint.pitchDeg),
|
||||
viewpoint.rangeM,
|
||||
);
|
||||
if (engine) {
|
||||
engine.camera.cancelFlight();
|
||||
engine.camera.flyToSphere({center: target, radius: 80}, {
|
||||
heading:offset.heading, pitch:offset.pitch, range:offset.range, duration:2.2,
|
||||
complete: () => {
|
||||
engine.camera.lookAt(target, offset);
|
||||
engine.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
viewer.camera.cancelFlight?.();
|
||||
viewer.camera.flyToBoundingSphere(new Cesium.BoundingSphere(target, 80), {
|
||||
offset,
|
||||
@@ -994,9 +1005,16 @@ export function createBhoteKoshiEventLayer({
|
||||
evidenceFrameUpdater = updateEvidenceFrame,
|
||||
videoFactory = DEFAULT_VIDEO_FACTORY,
|
||||
embeddedMediaFactory = createBhoteKoshiEmbeddedMedia,
|
||||
createNativeRenderer,
|
||||
panelStore = useBhoteKoshiPanelStore(applicationPinia()),
|
||||
} = {}) {
|
||||
let _viewer = null;
|
||||
let _engine = null;
|
||||
let _native = null;
|
||||
let _nativeFlood = null;
|
||||
let _providers = null;
|
||||
const cameraForScene = () => _engine?.camera ?? _viewer?.camera;
|
||||
const canvasForScene = () => _engine?.host.canvas ?? _viewer?.scene?.canvas;
|
||||
let _dataManager = null;
|
||||
let _mapStackController = null;
|
||||
let _event = null;
|
||||
@@ -1160,7 +1178,7 @@ export function createBhoteKoshiEventLayer({
|
||||
event.imagery.rectangle.east,
|
||||
event.imagery.rectangle.north,
|
||||
);
|
||||
const [beforeProvider, afterProvider] = await Promise.all([
|
||||
const [beforeProvider, afterProvider] = await (_providers ??= Promise.all([
|
||||
imageryProviderFactory(eventAsset(event.imagery.before.path), {
|
||||
rectangle,
|
||||
credit: 'Vantor WorldView Open Data · CC BY-NC 4.0',
|
||||
@@ -1169,8 +1187,13 @@ export function createBhoteKoshiEventLayer({
|
||||
rectangle,
|
||||
credit: 'Vantor WorldView Open Data · CC BY-NC 4.0',
|
||||
}),
|
||||
]);
|
||||
]));
|
||||
if (!_enabled) return;
|
||||
if (_native) {
|
||||
await _native.loadImages(event.imagery.rectangle, [beforeProvider, afterProvider]);
|
||||
setSplit(_split);
|
||||
return;
|
||||
}
|
||||
_imageryLayerCollection = _viewer.imageryLayers;
|
||||
_beforeLayer = _imageryLayerCollection.addImageryProvider(beforeProvider);
|
||||
_afterLayer = _imageryLayerCollection.addImageryProvider(afterProvider);
|
||||
@@ -1187,7 +1210,11 @@ export function createBhoteKoshiEventLayer({
|
||||
: [];
|
||||
let sampled = null;
|
||||
try {
|
||||
sampled = await terrainSampler(_viewer, observations, { signal });
|
||||
sampled = _engine && !_viewer
|
||||
? observations.map(({lon,lat}) => ({
|
||||
height: _engine.surface.terrainHeight(Cesium.Cartographic.fromDegrees(lon,lat)),
|
||||
}))
|
||||
: await terrainSampler(_viewer, observations, { signal });
|
||||
} catch (error) {
|
||||
if (error?.name === 'AbortError') throw error;
|
||||
console.warn(
|
||||
@@ -1574,13 +1601,13 @@ export function createBhoteKoshiEventLayer({
|
||||
if (_sceneControls.evidencePathElevation !== 'source') {
|
||||
// Sample once while preparing the shot, never from an animation frame.
|
||||
try {
|
||||
height = _viewer?.scene?.sampleHeight?.(
|
||||
height = _engine?.surface?.sampleHeight?.(position) ?? _viewer?.scene?.sampleHeight?.(
|
||||
position,
|
||||
[_floodCore, _floodHalo, _surgePoint].filter(Boolean),
|
||||
);
|
||||
} catch {}
|
||||
if (!Number.isFinite(height))
|
||||
height = _viewer?.scene?.globe?.getHeight?.(position);
|
||||
height = _engine?.surface?.terrainHeight(position) ?? _viewer?.scene?.globe?.getHeight?.(position);
|
||||
}
|
||||
if (!Number.isFinite(height)) height = point.fallbackElevationM;
|
||||
return Cesium.Cartesian3.fromDegrees(point.lon, point.lat, height + 8);
|
||||
@@ -2582,13 +2609,16 @@ export function createBhoteKoshiEventLayer({
|
||||
disableDepthTestDistance: tileSafe ? Number.POSITIVE_INFINITY : 0,
|
||||
},
|
||||
});
|
||||
_viewer.dataSources.add(_floodDataSource);
|
||||
if (_native) _nativeFlood = _native.draw(_floodDataSource);
|
||||
else _viewer.dataSources.add(_floodDataSource);
|
||||
}
|
||||
|
||||
function syncFloodSurfaceMode() {
|
||||
const nextMode = usesPhotorealFloodSurface() ? 'tiles' : 'terrain';
|
||||
if (!_viewer || !_floodDataSource || _floodSurfaceMode === nextMode) return;
|
||||
_viewer.dataSources.remove(_floodDataSource, true);
|
||||
if ((!_viewer && !_native) || !_floodDataSource || _floodSurfaceMode === nextMode) return;
|
||||
_nativeFlood?.destroy();
|
||||
_nativeFlood = null;
|
||||
_viewer?.dataSources.remove(_floodDataSource, true);
|
||||
_floodDataSource = null;
|
||||
_floodHalo = null;
|
||||
_floodCore = null;
|
||||
@@ -2601,7 +2631,7 @@ export function createBhoteKoshiEventLayer({
|
||||
|
||||
function createSplitControl(event) {
|
||||
_splitControl = createImagerySplit({
|
||||
scene: _viewer.scene,
|
||||
scene: _viewer?.scene,
|
||||
initialValue: _split,
|
||||
id: 'bhote-koshi-split-line',
|
||||
handleClass: 'bhote-koshi-split-handle',
|
||||
@@ -2621,7 +2651,7 @@ export function createBhoteKoshiEventLayer({
|
||||
function applyCinematicCamera() {
|
||||
if (
|
||||
!_cinematicActive ||
|
||||
!_viewer?.camera ||
|
||||
!cameraForScene() ||
|
||||
_cinematicKeyframes.length === 0
|
||||
)
|
||||
return;
|
||||
@@ -2635,7 +2665,7 @@ export function createBhoteKoshiEventLayer({
|
||||
_cinematicOffset.heading = Cesium.Math.toRadians(pose.headingDeg);
|
||||
_cinematicOffset.pitch = Cesium.Math.toRadians(pose.pitchDeg);
|
||||
_cinematicOffset.range = pose.rangeM;
|
||||
_viewer.camera.lookAt(pose.target, _cinematicOffset);
|
||||
cameraForScene().lookAt(pose.target, _cinematicOffset);
|
||||
_lastCinematicProgress = _progress;
|
||||
}
|
||||
|
||||
@@ -2705,7 +2735,7 @@ export function createBhoteKoshiEventLayer({
|
||||
_cinematicActive = true;
|
||||
_lastCinematicProgress = null;
|
||||
announceNavigationAuthority('bhote-koshi-cinematic');
|
||||
_viewer?.camera?.cancelFlight?.();
|
||||
cameraForScene()?.cancelFlight?.();
|
||||
}
|
||||
syncCinematicButton();
|
||||
return true;
|
||||
@@ -2723,7 +2753,7 @@ export function createBhoteKoshiEventLayer({
|
||||
_cinematicActive = false;
|
||||
_lastCinematicProgress = null;
|
||||
if (releaseCamera)
|
||||
_viewer?.camera?.lookAtTransform?.(Cesium.Matrix4.IDENTITY);
|
||||
cameraForScene()?.lookAtTransform?.(Cesium.Matrix4.IDENTITY);
|
||||
syncCinematicButton();
|
||||
renderHost.request('bhote-koshi-cinematic-stop');
|
||||
}
|
||||
@@ -2757,7 +2787,7 @@ export function createBhoteKoshiEventLayer({
|
||||
}
|
||||
|
||||
function attachCameraInputRelease() {
|
||||
_cameraInputTarget = _viewer?.scene?.canvas || null;
|
||||
_cameraInputTarget = canvasForScene() || null;
|
||||
for (const type of CINEMATIC_INPUT_EVENTS) {
|
||||
_cameraInputTarget?.addEventListener?.(type, handleManualCameraInput);
|
||||
}
|
||||
@@ -2803,7 +2833,7 @@ export function createBhoteKoshiEventLayer({
|
||||
}
|
||||
|
||||
function attachEvidenceCardActivation() {
|
||||
_evidenceClickTarget = _viewer?.scene?.canvas || null;
|
||||
_evidenceClickTarget = canvasForScene() || null;
|
||||
_evidenceClickTarget?.addEventListener?.('click', handleEvidenceCardClick);
|
||||
}
|
||||
|
||||
@@ -3273,6 +3303,7 @@ export function createBhoteKoshiEventLayer({
|
||||
if (_panelRefs?.progressInput)
|
||||
_panelRefs.progressInput.hidden = !scrubberAvailable;
|
||||
_splitControl?.setVisible(imageryAvailable);
|
||||
_native?.setVisible(imageryAvailable);
|
||||
if (_beforeLayer) _beforeLayer.show = imageryAvailable;
|
||||
if (_afterLayer) _afterLayer.show = imageryAvailable;
|
||||
if (_panelRefs?.caveat) {
|
||||
@@ -3391,6 +3422,7 @@ export function createBhoteKoshiEventLayer({
|
||||
function setSplit(split) {
|
||||
_split = clampUnit(split);
|
||||
_splitControl?.setValue(_split);
|
||||
_native?.setSplit(_split);
|
||||
syncPanel();
|
||||
renderHost.request('bhote-koshi-split');
|
||||
}
|
||||
@@ -3451,7 +3483,7 @@ export function createBhoteKoshiEventLayer({
|
||||
_embeddedEvidenceIndex = -1;
|
||||
_embeddedMedia?.hide?.();
|
||||
setSplit(0.5);
|
||||
focusView(_viewer, _event.viewpoints.corridor);
|
||||
if (cameraForScene()) focusView(_viewer, _event.viewpoints.corridor, _engine);
|
||||
}
|
||||
|
||||
function removeImagery() {
|
||||
@@ -3462,9 +3494,59 @@ export function createBhoteKoshiEventLayer({
|
||||
_imageryLayerCollection = null;
|
||||
}
|
||||
|
||||
async function init(viewer) {
|
||||
async function attachRenderer(viewer, {engine = null} = {}) {
|
||||
_viewer = viewer;
|
||||
_embeddedMedia = embeddedMediaFactory({ viewer });
|
||||
_engine = engine;
|
||||
if (!_viewer && _engine) _native = createNativeRenderer(engine);
|
||||
_embeddedMedia?.attachScene?.(viewer,engine);
|
||||
if (!_enabled) return;
|
||||
// The catalog has rebound the incoming map controller before this call.
|
||||
if (_mapStackController) {
|
||||
_comparisonLease = acquireImageryComparison(_mapStackController, {
|
||||
owner: BHOTE_KOSHI_LAYER_ID,
|
||||
switchPolicy: _presentation === BHOTE_KOSHI_SCENE_PRESENTATION ? 'preserve' : 'esri',
|
||||
});
|
||||
await _comparisonLease.ready;
|
||||
}
|
||||
await createImagery(_event);
|
||||
if (_floodDataSource) {
|
||||
if (_native) _nativeFlood = _native.draw(_floodDataSource);
|
||||
else await _viewer.dataSources.add(_floodDataSource);
|
||||
}
|
||||
registerDynamicCredit(engine ?? viewer, BHOTE_KOSHI_CREDIT);
|
||||
_scenePathPositionCache.clear();
|
||||
_lastCinematicProgress = null;
|
||||
await syncTerrainComparison();
|
||||
createSplitControl(_event);
|
||||
syncPresentationMode();
|
||||
attachCameraInputRelease();
|
||||
attachEvidenceCardActivation();
|
||||
updateProgress(_progress);
|
||||
setSplit(_split);
|
||||
}
|
||||
|
||||
async function detachRenderer() {
|
||||
detachCameraInputRelease();
|
||||
detachEvidenceCardActivation();
|
||||
_splitControl?.destroy();
|
||||
_splitControl = null;
|
||||
_embeddedMedia?.attachScene?.(null,null);
|
||||
removeImagery();
|
||||
_nativeFlood?.destroy();
|
||||
_nativeFlood = null;
|
||||
if (_floodDataSource && _viewer) _viewer.dataSources.remove(_floodDataSource,false);
|
||||
_native?.destroy();
|
||||
_native = null;
|
||||
_engine = _viewer = null;
|
||||
_mapStackController?.clearTerrainComparison?.();
|
||||
const lease = _comparisonLease;
|
||||
_comparisonLease = _mapStackController = null;
|
||||
await lease?.release();
|
||||
}
|
||||
|
||||
async function init(viewer, options) {
|
||||
await attachRenderer(viewer,options);
|
||||
_embeddedMedia = embeddedMediaFactory({ viewer, engine: _engine });
|
||||
await loadEvent();
|
||||
console.log('[Data:BhoteKoshi] Event pack ready');
|
||||
return true;
|
||||
@@ -3480,7 +3562,7 @@ export function createBhoteKoshiEventLayer({
|
||||
try {
|
||||
const event = await loadEvent();
|
||||
throwIfEnableCancelled(signal, _enabled);
|
||||
registerDynamicCredit(viewer, BHOTE_KOSHI_CREDIT);
|
||||
registerDynamicCredit(_engine ?? viewer, BHOTE_KOSHI_CREDIT);
|
||||
if (_mapStackController) {
|
||||
_comparisonLease = acquireImageryComparison(_mapStackController, {
|
||||
owner: BHOTE_KOSHI_LAYER_ID,
|
||||
@@ -3618,7 +3700,11 @@ export function createBhoteKoshiEventLayer({
|
||||
closeFrame(slot.poster);
|
||||
}
|
||||
_evidenceFrameSlots = [];
|
||||
if (_floodDataSource) _viewer.dataSources.remove(_floodDataSource, true);
|
||||
_nativeFlood?.destroy();
|
||||
_nativeFlood = null;
|
||||
_native?.clear();
|
||||
_providers = null;
|
||||
if (_floodDataSource && _viewer) _viewer.dataSources.remove(_floodDataSource, true);
|
||||
_floodDataSource = null;
|
||||
_floodSurfaceMode = null;
|
||||
_floodHalo = null;
|
||||
@@ -3672,7 +3758,8 @@ export function createBhoteKoshiEventLayer({
|
||||
_embeddedMedia?.destroy?.();
|
||||
_embeddedMedia = null;
|
||||
_event = null;
|
||||
_viewer = null;
|
||||
_native?.destroy();
|
||||
_native = _engine = _viewer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3849,6 +3936,8 @@ export function createBhoteKoshiEventLayer({
|
||||
source: 'Vantor + GeoPera',
|
||||
updateInterval: 0,
|
||||
init,
|
||||
attachRenderer,
|
||||
detachRenderer,
|
||||
enable,
|
||||
disable,
|
||||
update: async () => true,
|
||||
|
||||
@@ -130,7 +130,7 @@ export class LayerLifecycle {
|
||||
for (const [id, entry] of [...this.layers]) {
|
||||
await entry.toggleChain.catch(() => {});
|
||||
if (entry.module.attachRenderer && entry.module.detachRenderer) {
|
||||
if (entry.initialized) entry.module.detachRenderer(this.viewer);
|
||||
if (entry.initialized) await entry.module.detachRenderer(this.viewer);
|
||||
} else if (!(await this.destroyLayer(id))) {
|
||||
throw new Error(`Cannot detach layer ${id}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Texture, LinearFilter } from 'three';
|
||||
import { createImageryGround } from '../../recentImagery/render/navaraGround.js';
|
||||
import { drawStorySource } from '../../shared/render/navaraStory.js';
|
||||
|
||||
export function createNepalEventDisplay(engine, native) {
|
||||
const owner = { slots: { a: null, b: null }, preview: null, split: 0.5 };
|
||||
const ground = createImageryGround(engine, native, owner, 'gevNepalEvent');
|
||||
let disposed = false;
|
||||
function clear() {
|
||||
for (const id of ['a', 'b']) {
|
||||
owner.slots[id]?.atlas.texture.dispose();
|
||||
owner.slots[id] = null;
|
||||
}
|
||||
if (!disposed) ground.sync();
|
||||
}
|
||||
return {
|
||||
draw: (data) => drawStorySource(engine, native, data),
|
||||
async loadImages(box, providers) {
|
||||
const images = await Promise.all(
|
||||
providers.map((p) => p.requestImage(0, 0, 0)),
|
||||
);
|
||||
if (disposed) return;
|
||||
clear();
|
||||
for (const [i, id] of ['a', 'b'].entries()) {
|
||||
const texture = new Texture(images[i]);
|
||||
texture.flipY = false;
|
||||
texture.minFilter = texture.magFilter = LinearFilter;
|
||||
texture.generateMipmaps = false;
|
||||
texture.needsUpdate = true;
|
||||
owner.slots[id] = {
|
||||
atlas: {
|
||||
texture,
|
||||
plan: {
|
||||
geographic: true,
|
||||
bounds: [
|
||||
box.west,
|
||||
box.north,
|
||||
box.east - box.west,
|
||||
box.south - box.north,
|
||||
],
|
||||
},
|
||||
},
|
||||
box,
|
||||
alpha: 0.96,
|
||||
split: i === 0 ? 'left' : 'right',
|
||||
};
|
||||
}
|
||||
ground.sync();
|
||||
engine.render.request('nepal-imagery');
|
||||
},
|
||||
setVisible(show) {
|
||||
for (const slot of Object.values(owner.slots))
|
||||
if (slot) slot.alpha = show ? 0.96 : 0;
|
||||
engine.render.request('nepal-imagery');
|
||||
},
|
||||
setSplit(x) {
|
||||
owner.split = x;
|
||||
engine.render.request('nepal-split');
|
||||
},
|
||||
clear,
|
||||
destroy() {
|
||||
disposed = true;
|
||||
clear();
|
||||
ground.destroy();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -199,7 +199,7 @@ function buildState(discs) {
|
||||
function groundEffectClass(EffectDesc, NavaraEffect) {
|
||||
return class GroundEffectDesc extends EffectDesc {
|
||||
static key = KEY;
|
||||
static insertAfter = ['gevRecentImagery', 'toneMapping'];
|
||||
static insertAfter = ['gevObservedWeather', 'gevNepalEvent', 'gevRecentImagery', 'toneMapping'];
|
||||
static insertBefore = [
|
||||
'gevFirmsGround',
|
||||
'gevPreSharpenOverlay',
|
||||
|
||||
@@ -6,14 +6,14 @@ import {
|
||||
} from '../../shared/render/navaraGroundSurface.js';
|
||||
|
||||
const KEY = 'gevRecentImagery',
|
||||
registered = new WeakSet();
|
||||
registered = new WeakMap();
|
||||
const shader = /* glsl */ `
|
||||
uniform sampler2D tAllDepth,tSurfaceDepth,tA,tB;
|
||||
uniform mat4 uInverseProjection,uWorld;
|
||||
uniform float uLogDepthFC,uSplit,uPixelRatio;
|
||||
uniform vec4 uBoundsA,uBoundsB,uBoxA,uBoxB,uPreview;
|
||||
uniform vec2 uLookA,uLookB;
|
||||
uniform bool uPreviewVisible;
|
||||
uniform bool uPreviewVisible,uGeographicA,uGeographicB;
|
||||
vec3 displayColor(vec3 c) {
|
||||
return mix(c*12.92,1.055*pow(max(c,vec3(0.0)),vec3(1.0/2.4))-0.055,step(vec3(0.0031308),c));
|
||||
}
|
||||
@@ -21,10 +21,10 @@ vec3 linearColor(vec3 c) {
|
||||
return mix(c/12.92,pow(max((c+0.055)/1.055,vec3(0.0)),vec3(2.4)),step(vec3(0.04045),c));
|
||||
}
|
||||
bool inBox(vec2 geo,vec4 box) { return geo.x>=box.x && geo.y>=box.y && geo.x<=box.z && geo.y<=box.w; }
|
||||
vec3 imageColor(vec3 color,sampler2D image,vec4 bounds,vec4 box,vec2 look,vec2 geo,vec2 mercator,float x) {
|
||||
vec3 imageColor(vec3 color,sampler2D image,vec4 bounds,vec4 box,vec2 look,vec2 geo,vec2 mercator,float x,bool geographic) {
|
||||
if(look.x<=0.0 || !inBox(geo,box)) return color;
|
||||
if((look.y<0.0 && x>uSplit)||(look.y>0.0 && x<uSplit)) return color;
|
||||
vec2 at=(mercator-bounds.xy)/bounds.zw;
|
||||
vec2 at=((geographic?geo:mercator)-bounds.xy)/bounds.zw;
|
||||
if(any(lessThan(at,vec2(0.0)))||any(greaterThan(at,vec2(1.0)))) return color;
|
||||
vec4 pixel=texture2D(image,at);
|
||||
return mix(color,pixel.rgb,pixel.a*look.x);
|
||||
@@ -42,8 +42,8 @@ void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor) {
|
||||
vec2 geo=vec2(atan(point.y,point.x),latitude)*57.29577951308232;
|
||||
vec2 mercator=vec2((geo.x+180.0)/360.0,(1.0-log(tan(0.7853981633974483+latitude*0.5))/3.141592653589793)/2.0);
|
||||
vec3 color=displayColor(inputColor.rgb);
|
||||
color=imageColor(color,tA,uBoundsA,uBoxA,uLookA,geo,mercator,uv.x);
|
||||
color=imageColor(color,tB,uBoundsB,uBoxB,uLookB,geo,mercator,uv.x);
|
||||
color=imageColor(color,tA,uBoundsA,uBoxA,uLookA,geo,mercator,uv.x,uGeographicA);
|
||||
color=imageColor(color,tB,uBoundsB,uBoxB,uLookB,geo,mercator,uv.x,uGeographicB);
|
||||
if(uPreviewVisible && inBox(geo,uPreview)) {
|
||||
vec2 pixelWidth=max(fwidth(geo),vec2(1e-7));
|
||||
vec2 edges=min(geo-uPreview.xy,uPreview.zw-geo)/pixelWidth;
|
||||
@@ -61,14 +61,18 @@ export function createImageryGround(
|
||||
engine,
|
||||
{ view, EffectDesc, NavaraEffect },
|
||||
owner,
|
||||
key = KEY,
|
||||
) {
|
||||
if (!view || !EffectDesc || !NavaraEffect)
|
||||
throw new TypeError('Imagery requires Navara ground effects');
|
||||
if (!registered.has(view)) {
|
||||
if (!registered.has(view)) registered.set(view, new Set());
|
||||
if (!registered.get(view).has(key)) {
|
||||
class ImageryEffect extends EffectDesc {
|
||||
static key = KEY;
|
||||
static key = key;
|
||||
static insertAfter = ['toneMapping'];
|
||||
static insertBefore = [
|
||||
'gevNepalEvent',
|
||||
'gevObservedWeather',
|
||||
'gevEarthquakeGround',
|
||||
'gevFirmsGround',
|
||||
'gevPreSharpenOverlay',
|
||||
@@ -80,13 +84,15 @@ export function createImageryGround(
|
||||
];
|
||||
constructor(view, context, config) {
|
||||
super(view, context, config);
|
||||
this.owner = config[KEY].owner;
|
||||
this.owner = config[key].owner;
|
||||
}
|
||||
createPass() {
|
||||
this.effect = new Effect('GevRecentImagery', shader, {
|
||||
attributes: EffectAttribute.CONVOLUTION,
|
||||
uniforms: new Map([
|
||||
...groundSurfaceUniforms(),
|
||||
['uGeographicA', new Uniform(false)],
|
||||
['uGeographicB', new Uniform(false)],
|
||||
['uWorld', new Uniform(new Matrix4())],
|
||||
['uSplit', new Uniform(0.5)],
|
||||
['uPixelRatio', new Uniform(1)],
|
||||
@@ -115,6 +121,7 @@ export function createImageryGround(
|
||||
u.get('uPreviewVisible').value = Boolean(o.preview);
|
||||
for (const id of ['A', 'B']) {
|
||||
const r = o.slots[id.toLowerCase()];
|
||||
u.get(`uGeographic${id}`).value = r?.atlas?.plan.geographic === true;
|
||||
u.get(`t${id}`).value = r?.atlas?.texture || o.empty;
|
||||
u.get(`uBounds${id}`).value = r?.atlas?.plan.bounds || [0, 0, 1, 1];
|
||||
u.get(`uBox${id}`).value = boxVector(r?.box);
|
||||
@@ -129,8 +136,11 @@ export function createImageryGround(
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
view.registerEffect(KEY, ImageryEffect);
|
||||
registered.add(view);
|
||||
class NepalEffect extends ImageryEffect {
|
||||
static insertAfter = ['gevRecentImagery', 'toneMapping'];
|
||||
}
|
||||
view.registerEffect(key, key === KEY ? ImageryEffect : NepalEffect);
|
||||
registered.get(view).add(key);
|
||||
}
|
||||
owner.empty = new DataTexture(new Uint8Array(4), 1, 1);
|
||||
owner.empty.needsUpdate = true;
|
||||
@@ -141,7 +151,7 @@ export function createImageryGround(
|
||||
sync() {
|
||||
const visible = Boolean(owner.slots.a || owner.slots.b || owner.preview);
|
||||
if (visible && !handle)
|
||||
handle = view.addEffect({ [KEY]: { owner }, visible: true });
|
||||
handle = view.addEffect({ [key]: { owner }, visible: true });
|
||||
else handle?.update({ visible });
|
||||
},
|
||||
destroy() {
|
||||
|
||||
@@ -212,6 +212,8 @@ function groundEffectClass(EffectDesc, NavaraEffect) {
|
||||
static key = KEY;
|
||||
static insertAfter = [
|
||||
'gevEarthquakeGround',
|
||||
'gevObservedWeather',
|
||||
'gevNepalEvent',
|
||||
'gevRecentImagery',
|
||||
'toneMapping',
|
||||
];
|
||||
|
||||
@@ -7,6 +7,8 @@ import fs from 'node:fs/promises';
|
||||
// minimal look, whose pipeline has no toneMapping anchor.
|
||||
const declarations = [
|
||||
['gevRecentImagery', '../../recentImagery/render/navaraGround.js'],
|
||||
['gevNepalEvent', '../../recentImagery/render/navaraGround.js'],
|
||||
['gevObservedWeather', '../../weather/render/navaraSurface.js'],
|
||||
['gevEarthquakeGround', '../../earthquakes/render/navara.js'],
|
||||
['gevFirmsGround', './navaraGroundCells.js'],
|
||||
['gevPreSharpenOverlay', '../../../globe/navara/overlay.js'],
|
||||
@@ -18,7 +20,7 @@ const specs = await Promise.all(
|
||||
source = source.split('class GevPreSharpenOverlayEffectDesc')[1];
|
||||
const anchors = (name) =>
|
||||
[
|
||||
...source
|
||||
...(key === 'gevNepalEvent' && name === 'insertAfter' ? source.split('class NepalEffect')[1] : source)
|
||||
.match(new RegExp(`static ${name} = \\[([^\\]]*)\\]`))[1]
|
||||
.matchAll(/'([^']+)'/g),
|
||||
].map((m) => m[1]);
|
||||
|
||||
@@ -89,7 +89,7 @@ function weatherPass(engine, { view, EffectDesc, NavaraEffect }) {
|
||||
empty = null;
|
||||
class WeatherEffect extends EffectDesc {
|
||||
static key = KEY;
|
||||
static insertAfter = ['toneMapping', 'gevRecentImagery'];
|
||||
static insertAfter = ['gevNepalEvent', 'gevRecentImagery', 'toneMapping'];
|
||||
static insertBefore = [
|
||||
'gevEarthquakeGround',
|
||||
'gevFirmsGround',
|
||||
|
||||
@@ -234,7 +234,6 @@ const FRAME_TARGETS = new Map([
|
||||
* refusal (UNAVAILABLE_ON_ENGINE) instead of running.
|
||||
*/
|
||||
export const VIEWER_ONLY_ACTIONS = Object.freeze([
|
||||
'control_scene',
|
||||
'annotate_map',
|
||||
'clear_annotations',
|
||||
'fly_route',
|
||||
@@ -470,7 +469,7 @@ export function createGevActionRunner({
|
||||
};
|
||||
if (
|
||||
!viewer &&
|
||||
(VIEWER_ONLY_ACTIONS.includes(name) || CESIUM_LOOK_ACTIONS.includes(name))
|
||||
((name === 'control_scene' && !sceneDirector) || VIEWER_ONLY_ACTIONS.includes(name) || CESIUM_LOOK_ACTIONS.includes(name))
|
||||
)
|
||||
return unavailableOnEngine(name, engine);
|
||||
|
||||
|
||||
@@ -89,7 +89,6 @@ const follow = (engine, position) =>
|
||||
|
||||
test('the viewer-only actions are one exported list of real tools', () => {
|
||||
assert.deepEqual(VIEWER_ONLY_ACTIONS, [
|
||||
'control_scene',
|
||||
'annotate_map',
|
||||
'clear_annotations',
|
||||
'fly_route',
|
||||
|
||||
Reference in new issue
Block a user