mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 13:52:19 +00:00
Hide a selected radio station beyond the horizon, also after a switch
Re-adding the selected station's marker reset it to visible, and the horizon check skips a still camera, so a station selected on the far side of the Earth showed through the globe until the camera moved. The marker now starts with the station's current horizon visibility, and the layer recomputes it once the view is restored after an engine switch.
This commit is contained in:
1 parent
697dc91e81
commit
3ab0bbf7c1
3 files changed
+150
-1
No files matched your search
@@ -50,6 +50,11 @@ export function createLifecycle({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resumeViewport() {
|
||||||
|
if (parts.interaction.radioPresentationAllowed())
|
||||||
|
parts.rendering.updateRenderVisibility();
|
||||||
|
},
|
||||||
|
|
||||||
detachRenderer() {
|
detachRenderer() {
|
||||||
parts.interaction.removeInteraction();
|
parts.interaction.removeInteraction();
|
||||||
clearTimeout(layerState._overlayPublishTimer);
|
clearTimeout(layerState._overlayPublishTimer);
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { LayerLifecycle } from '../../data/lifecycle.js';
|
||||||
|
import * as geo from '../../geo/index.js';
|
||||||
|
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||||
|
import { createRadioLayer } from './index.js';
|
||||||
|
import { createRadioDisplay } from './render/display.js';
|
||||||
|
|
||||||
|
const station = {
|
||||||
|
id: '00000000-0000-4000-8000-000000000001',
|
||||||
|
name: 'Station',
|
||||||
|
lat: 0,
|
||||||
|
lon: 0,
|
||||||
|
streamUrl: 'https://audio.example.test/station.mp3',
|
||||||
|
homepage: null,
|
||||||
|
tags: ['news'],
|
||||||
|
languages: ['English'],
|
||||||
|
state: '',
|
||||||
|
country: 'United States',
|
||||||
|
countryCode: 'US',
|
||||||
|
metadataTrust: 'untrusted-community',
|
||||||
|
codec: 'MP3',
|
||||||
|
bitrate: 128,
|
||||||
|
};
|
||||||
|
const noop = () => {};
|
||||||
|
const at = (engine, longitude) =>
|
||||||
|
engine.camera.setView({
|
||||||
|
destination: geo.cartesian3.fromDegrees(longitude, 0, 1_000_000),
|
||||||
|
});
|
||||||
|
|
||||||
|
function assertSelection(engine, shown) {
|
||||||
|
for (const id of ['radio-selected', 'radio-bracket']) {
|
||||||
|
const { store } = engine.fake.moverSet(id);
|
||||||
|
assert.equal(store.count, 1, id);
|
||||||
|
assert.equal(store.pickIds[0], `radio:selected:${station.id}`, id);
|
||||||
|
assert.equal(Boolean(store.shows[0]), shown, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fixture(t, kind, longitude = 0) {
|
||||||
|
t.mock.timers.enable({ apis: ['setTimeout', 'setInterval'] });
|
||||||
|
const engine = createFakeEngine({ kind });
|
||||||
|
at(engine, longitude);
|
||||||
|
const layer = createRadioLayer({
|
||||||
|
source: {
|
||||||
|
getDirectory: async () => ({
|
||||||
|
stations: [station],
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
stale: false,
|
||||||
|
degraded: false,
|
||||||
|
acceptedGeneration: 1,
|
||||||
|
catalogInstance: 'fixture',
|
||||||
|
}),
|
||||||
|
recordClick: async () => {},
|
||||||
|
},
|
||||||
|
services: {
|
||||||
|
ground: { cachedGroundFloor: () => 0, warmGroundFloor: noop },
|
||||||
|
globe: {},
|
||||||
|
picking: {
|
||||||
|
resolvePickId: (pick) => pick?.id,
|
||||||
|
isOwnedByOtherLayer: () => false,
|
||||||
|
},
|
||||||
|
render: {
|
||||||
|
governorRequestRender: (reason, engine) =>
|
||||||
|
engine.render.request(reason),
|
||||||
|
},
|
||||||
|
overlays: { clearSource: noop, setVisible: noop, setEntries: noop },
|
||||||
|
},
|
||||||
|
rendererFactory: (context) => createRadioDisplay(context.engine, context),
|
||||||
|
});
|
||||||
|
const catalog = {
|
||||||
|
layers: [layer],
|
||||||
|
metadata: [{ id: 'radio', disposition: 'enabled-only', token: 'r' }],
|
||||||
|
};
|
||||||
|
const manager = new LayerLifecycle(null);
|
||||||
|
t.after(async () => {
|
||||||
|
await manager.destroyAll();
|
||||||
|
engine.destroy();
|
||||||
|
});
|
||||||
|
await manager.bindCatalog(engine, catalog);
|
||||||
|
assert.equal(await manager.setEnabled('radio', true), true);
|
||||||
|
assert.equal(layer.selectStation(station.id), true);
|
||||||
|
engine.fake.frame();
|
||||||
|
return { engine, layer, manager, catalog };
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [from, to] of [
|
||||||
|
['cesium', 'navara'],
|
||||||
|
['navara', 'cesium'],
|
||||||
|
]) {
|
||||||
|
test(`selected radio markers resume at the restored camera (${from} -> ${to})`, async (t) => {
|
||||||
|
const { engine, layer, manager, catalog } = await fixture(t, from);
|
||||||
|
assertSelection(engine, true);
|
||||||
|
const incoming = createFakeEngine({ kind: to });
|
||||||
|
t.after(() => incoming.destroy());
|
||||||
|
at(incoming, 90);
|
||||||
|
await manager.detachScene();
|
||||||
|
let restoreView;
|
||||||
|
const viewRestored = new Promise((resolve) => {
|
||||||
|
restoreView = resolve;
|
||||||
|
});
|
||||||
|
await manager.bindCatalog(incoming, catalog, { viewRestored });
|
||||||
|
incoming.fake.frame();
|
||||||
|
assertSelection(incoming, false);
|
||||||
|
at(incoming, 0);
|
||||||
|
restoreView();
|
||||||
|
await manager.resumeScene();
|
||||||
|
incoming.fake.frame();
|
||||||
|
assertSelection(incoming, true);
|
||||||
|
assert.equal(layer.getUIState().selected.id, station.id);
|
||||||
|
assert.equal(layer.getUIState().audioState, 'stopped');
|
||||||
|
t.mock.timers.tick(1500);
|
||||||
|
incoming.fake.frame();
|
||||||
|
assertSelection(incoming, true);
|
||||||
|
await manager.setEnabled('radio', false);
|
||||||
|
layer.resumeViewport();
|
||||||
|
for (const id of ['radio-selected', 'radio-bracket'])
|
||||||
|
assert.equal(incoming.fake.moverSet(id).store.count, 0, id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`a stationary far-side radio selection stays hidden across rebuilds and engines (${from} -> ${to})`, async (t) => {
|
||||||
|
const { engine, layer, manager, catalog } = await fixture(t, from, 90);
|
||||||
|
// A preference update checks the horizon before the delayed marker rebuild.
|
||||||
|
layer.setParams({ volume: 0.36 });
|
||||||
|
assertSelection(engine, false);
|
||||||
|
t.mock.timers.tick(1500);
|
||||||
|
engine.fake.frame();
|
||||||
|
assertSelection(engine, false);
|
||||||
|
const incoming = createFakeEngine({ kind: to });
|
||||||
|
t.after(() => incoming.destroy());
|
||||||
|
at(incoming, 90);
|
||||||
|
await manager.detachScene();
|
||||||
|
await manager.bindCatalog(incoming, catalog);
|
||||||
|
await manager.resumeScene();
|
||||||
|
incoming.fake.frame();
|
||||||
|
assertSelection(incoming, false);
|
||||||
|
assert.equal(layer.getUIState().selected.id, station.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -274,7 +274,12 @@ export function createRadioDisplay(engine, { state, services, parts }) {
|
|||||||
if (initialized) syncSelection();
|
if (initialized) syncSelection();
|
||||||
},
|
},
|
||||||
addSelected(station, position, color) {
|
addSelected(station, position, color) {
|
||||||
selectedRecord = { station, position, color: rgba(color), visible: true };
|
selectedRecord = {
|
||||||
|
station,
|
||||||
|
position,
|
||||||
|
color: rgba(color),
|
||||||
|
visible: engine.projector.horizonVisible(position),
|
||||||
|
};
|
||||||
syncSelection();
|
syncSelection();
|
||||||
},
|
},
|
||||||
setSelectedVisible(value) {
|
setSelectedVisible(value) {
|
||||||
|
|||||||
Reference in new issue
Block a user