mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-07 20:04:00 +00:00
Fly the director, annotations, voice framing and local GeoJSON through the camera port
The scene director's shot flights, start pose and stops, the annotation framings and fly-to, the voice overhead framing and the local GeoJSON click flight now drive the renderer's camera port. The director's stops look the camera up with findCamera: teardown can run after the viewer is gone, and then there is no flight to stop. The local GeoJSON flight, part of the public infrastructure export, works on a plain Cesium viewer through the unbound camera, and looks it up before taking its input hold. Voice zoom stays for the move slice.
This commit is contained in:
1 parent
0b1c5d0f23
commit
bc69e8a4ee
8 files changed
+125
-73
No files matched your search
@@ -8,12 +8,23 @@
|
||||
"modules": [
|
||||
"src/data/infrastructure.js",
|
||||
"src/data/localGeojsonCore.js",
|
||||
"src/render/cesium/camera.js",
|
||||
"src/data/localGeojsonLod.js",
|
||||
"src/data/inputOwnership.js",
|
||||
"src/data/pickRegistry.js",
|
||||
"src/data/scenePick.js",
|
||||
"src/input/dispatcher.js",
|
||||
"src/render/cesium/cameraInput.js",
|
||||
"src/render/camera/events.js",
|
||||
"src/geo/index.js",
|
||||
"src/geo/celestial.js",
|
||||
"src/geo/quaternion.js",
|
||||
"src/geo/frames.js",
|
||||
"src/geo/ellipsoid.js",
|
||||
"src/geo/vector.js",
|
||||
"src/render/rendererOf.js",
|
||||
"src/render/cesium/common.js",
|
||||
"src/render/cesium/internals.js",
|
||||
"src/render/cesium/input.js",
|
||||
"src/render/cesium/ctrlWheelZoom.js",
|
||||
"src/render/input/router.js",
|
||||
@@ -1242,6 +1253,17 @@
|
||||
"src/data/bhoteKoshiFloodPath.js",
|
||||
"src/maps/catalog.js",
|
||||
"src/scenes/director.js",
|
||||
"src/render/cesium/camera.js",
|
||||
"src/geo/index.js",
|
||||
"src/render/rendererOf.js",
|
||||
"src/render/camera/events.js",
|
||||
"src/geo/celestial.js",
|
||||
"src/geo/quaternion.js",
|
||||
"src/geo/frames.js",
|
||||
"src/geo/ellipsoid.js",
|
||||
"src/geo/vector.js",
|
||||
"src/render/cesium/internals.js",
|
||||
"src/render/cesium/common.js",
|
||||
"src/scenes/sceneSaveError.js",
|
||||
"src/scenes/playbackAdapter.js",
|
||||
"src/director/playback.js",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { isUnavailableCapability } from '../sources/capability.js';
|
||||
import { defaultGeospatial } from '../search/defaults.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import { cameraOf } from '../render/cesium/camera.js';
|
||||
import { greatCircleMeters } from '../geoDistance.js';
|
||||
import {
|
||||
holdContinuousRender,
|
||||
@@ -1700,14 +1701,18 @@ export function createAnnotationEngine({
|
||||
Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height || 0),
|
||||
);
|
||||
const sphere = Cesium.BoundingSphere.fromPoints(cart);
|
||||
viewer.camera.flyToBoundingSphere(sphere, {
|
||||
offset: new Cesium.HeadingPitchRange(
|
||||
viewer.camera.heading,
|
||||
Cesium.Math.toRadians(-35),
|
||||
Math.max(900, sphere.radius * 2.6),
|
||||
),
|
||||
duration: 1.6,
|
||||
});
|
||||
const camera = cameraOf(viewer);
|
||||
camera.flyToBounds(
|
||||
{ center: sphere.center, radius: sphere.radius },
|
||||
{
|
||||
offset: {
|
||||
heading: camera.pose().heading,
|
||||
pitch: Cesium.Math.toRadians(-35),
|
||||
range: Math.max(900, sphere.radius * 2.6),
|
||||
},
|
||||
durationS: 1.6,
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
/* camera assist is best-effort */
|
||||
}
|
||||
@@ -1757,22 +1762,23 @@ export function createAnnotationEngine({
|
||||
: anno.ring,
|
||||
)
|
||||
: viewportRange(anno.viewport) || 600;
|
||||
viewer.camera.flyToBoundingSphere(
|
||||
new Cesium.BoundingSphere(
|
||||
Cesium.Cartesian3.fromDegrees(
|
||||
target.lon,
|
||||
target.lat,
|
||||
anno.anchor.height || 0,
|
||||
),
|
||||
range,
|
||||
),
|
||||
const camera = cameraOf(viewer);
|
||||
camera.flyToBounds(
|
||||
{
|
||||
offset: new Cesium.HeadingPitchRange(
|
||||
viewer.camera.heading,
|
||||
Cesium.Math.toRadians(-35),
|
||||
range * 2.4,
|
||||
),
|
||||
duration: 1.8,
|
||||
center: {
|
||||
lon: target.lon,
|
||||
lat: target.lat,
|
||||
height: anno.anchor.height || 0,
|
||||
},
|
||||
radius: range,
|
||||
},
|
||||
{
|
||||
offset: {
|
||||
heading: camera.pose().heading,
|
||||
pitch: Cesium.Math.toRadians(-35),
|
||||
range: range * 2.4,
|
||||
},
|
||||
durationS: 1.8,
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
@@ -1978,15 +1984,17 @@ export function createAnnotationEngine({
|
||||
}) {
|
||||
if (destroyed) return;
|
||||
try {
|
||||
viewer.camera.flyTo({
|
||||
destination: Cesium.Cartesian3.fromDegrees(lon, lat, height),
|
||||
orientation: {
|
||||
heading: Cesium.Math.toRadians(heading),
|
||||
pitch: Cesium.Math.toRadians(pitch),
|
||||
roll: 0,
|
||||
cameraOf(viewer).flyTo(
|
||||
{ lon, lat, height },
|
||||
{
|
||||
orientation: {
|
||||
heading: Cesium.Math.toRadians(heading),
|
||||
pitch: Cesium.Math.toRadians(pitch),
|
||||
roll: 0,
|
||||
},
|
||||
durationS: duration,
|
||||
},
|
||||
duration,
|
||||
});
|
||||
);
|
||||
} catch {
|
||||
/* camera flight best-effort */
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindFakeCesiumCamera } from '../testSupport/fakeCameraPort.mjs';
|
||||
import { createStandalonePlaceSearch } from '../standalone/placeSearch.js';
|
||||
// Voice-annotation resilience contract tests — pure logic, no network, no browser.
|
||||
//
|
||||
@@ -1262,7 +1263,7 @@ test('hand-drawn marks: one mark can be hidden, renamed, recoloured, framed and
|
||||
const { renderer, calls } = fakeRenderer();
|
||||
const flights = [];
|
||||
const engine = createAnnotationEngine({
|
||||
viewer: { camera: { heading: 0, flyToBoundingSphere: (sphere) => flights.push(sphere) } },
|
||||
viewer: bindFakeCesiumCamera({ camera: { heading: 0, flyToBoundingSphere: (sphere) => flights.push(sphere) } }),
|
||||
renderer,
|
||||
});
|
||||
let changes = 0;
|
||||
@@ -1331,12 +1332,12 @@ test('hand-drawn marks: remove, hide, clear, expiry and framing have their effec
|
||||
const { renderer, calls } = fakeRenderer();
|
||||
const flights = [];
|
||||
const engine = createAnnotationEngine({
|
||||
viewer: {
|
||||
viewer: bindFakeCesiumCamera({
|
||||
camera: {
|
||||
heading: 0,
|
||||
flyToBoundingSphere: (sphere) => flights.push(sphere),
|
||||
},
|
||||
},
|
||||
}),
|
||||
renderer,
|
||||
});
|
||||
let changes = 0;
|
||||
|
||||
@@ -186,21 +186,28 @@ test('consumer build includes only infrastructure code and resolves assets under
|
||||
const entry = output.find((item) => item.type === 'chunk' && item.isEntry);
|
||||
const sources = Object.keys(entry.modules).filter((id) => id.endsWith('.js'));
|
||||
assert.deepEqual(sources.map((id) => id.split('/').at(-1)).sort(), [
|
||||
'camera.js',
|
||||
'cameraInput.js',
|
||||
'celestial.js',
|
||||
'common.js',
|
||||
'ctrlWheelZoom.js',
|
||||
'dispatcher.js',
|
||||
'events.js',
|
||||
'heights.js',
|
||||
'infrastructure.js',
|
||||
'infrastructureData.js',
|
||||
'infrastructureOverlayEntry.js',
|
||||
'input.js',
|
||||
'inputOwnership.js',
|
||||
'internals.js',
|
||||
'localGeojsonCore.js',
|
||||
'localGeojsonLod.js',
|
||||
'pickRegistry.js',
|
||||
'rendererOf.js',
|
||||
'router.js',
|
||||
'scenePick.js',
|
||||
'sensorPick.js',
|
||||
'vector.js',
|
||||
]);
|
||||
assert.deepEqual(
|
||||
entry.imports,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { cameraOf } from '../render/cesium/camera.js';
|
||||
import {
|
||||
layerTitle,
|
||||
mapAnalystRecord,
|
||||
@@ -820,6 +821,8 @@ export function createLocalGeoJsonLayer(
|
||||
|
||||
if (targetPos) {
|
||||
const carto = Cesium.Cartographic.fromCartesian(targetPos);
|
||||
// Before the hold: with no camera, nothing would end it.
|
||||
const camera = cameraOf(viewer);
|
||||
|
||||
// Hold camera input so Cesium doesn't cancel the flight.
|
||||
// A hold, not a flag: the flight ending must not hand input
|
||||
@@ -828,16 +831,18 @@ export function createLocalGeoJsonLayer(
|
||||
'local-geojson-flight',
|
||||
);
|
||||
|
||||
viewer.camera.flyTo({
|
||||
destination: Cesium.Cartesian3.fromRadians(
|
||||
camera.flyTo(
|
||||
Cesium.Cartesian3.fromRadians(
|
||||
carto.longitude,
|
||||
carto.latitude,
|
||||
5000,
|
||||
),
|
||||
duration: 1.5,
|
||||
complete: releaseCamera,
|
||||
cancel: releaseCamera,
|
||||
});
|
||||
{
|
||||
durationS: 1.5,
|
||||
onComplete: releaseCamera,
|
||||
onCancel: releaseCamera,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+25
-22
@@ -21,6 +21,8 @@ import { createToast } from '../ui/toast.js';
|
||||
import { buildPlaybackQueue, playSceneQueue } from '../director/playback.js';
|
||||
import { createScenePlaybackAdapter } from './playbackAdapter.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import { cameraOf } from '../render/cesium/camera.js';
|
||||
import { findCamera } from '../render/rendererOf.js';
|
||||
import {
|
||||
SCENE_APPEND_RECIPES,
|
||||
SCENE_RECIPES,
|
||||
@@ -263,7 +265,8 @@ export class SceneDirector {
|
||||
this.stopScene(msg('Stopped'));
|
||||
this._loadAbort?.abort();
|
||||
this._loadGeneration++;
|
||||
this.viewer.camera.cancelFlight();
|
||||
// The viewer may be gone by now: then there is no flight to stop.
|
||||
findCamera(this.viewer)?.cancelFlight();
|
||||
this._storageToast?.cancel();
|
||||
await Promise.allSettled(this._pendingWork || []);
|
||||
this._clock.destroy();
|
||||
@@ -1746,16 +1749,16 @@ export class SceneDirector {
|
||||
|
||||
/** Place the camera at an authored replay start pose without an intermediate flight. */
|
||||
_setCameraView(cameraState) {
|
||||
if (!cameraState || typeof this.viewer?.camera?.setView !== 'function')
|
||||
return false;
|
||||
const camera = findCamera(this.viewer);
|
||||
if (!cameraState || !camera) return false;
|
||||
this.styleManager?.clearSearchedLocation?.();
|
||||
this.viewer.camera.cancelFlight?.();
|
||||
this.viewer.camera.setView({
|
||||
destination: Cesium.Cartesian3.fromDegrees(
|
||||
cameraState.lon,
|
||||
cameraState.lat,
|
||||
cameraState.alt,
|
||||
),
|
||||
camera.cancelFlight();
|
||||
camera.setView({
|
||||
destination: {
|
||||
lon: cameraState.lon,
|
||||
lat: cameraState.lat,
|
||||
height: cameraState.alt,
|
||||
},
|
||||
orientation: {
|
||||
heading: Cesium.Math.toRadians(cameraState.heading || 0),
|
||||
pitch: Cesium.Math.toRadians(cameraState.pitch ?? -35),
|
||||
@@ -2022,7 +2025,8 @@ export class SceneDirector {
|
||||
this._loadGeneration++;
|
||||
this._clock.stopShot();
|
||||
this._cancelActiveSceneTravel();
|
||||
this.viewer.camera.cancelFlight();
|
||||
// Stop runs in teardown too, after the viewer may be gone.
|
||||
findCamera(this.viewer)?.cancelFlight();
|
||||
this._clock.stop();
|
||||
if (!this._running || !this._runToken) return;
|
||||
this._runToken.cancelled = true;
|
||||
@@ -2464,11 +2468,11 @@ export class SceneDirector {
|
||||
0.2,
|
||||
Number(durationSec) || DEFAULT_SHOT_DURATION_SEC,
|
||||
);
|
||||
const destination = Cesium.Cartesian3.fromDegrees(
|
||||
cameraState.lon,
|
||||
cameraState.lat,
|
||||
cameraState.alt,
|
||||
);
|
||||
const destination = {
|
||||
lon: cameraState.lon,
|
||||
lat: cameraState.lat,
|
||||
height: cameraState.alt,
|
||||
};
|
||||
|
||||
await new Promise((resolve) => {
|
||||
// Guard against double-resolve from both callback and timeout
|
||||
@@ -2481,17 +2485,16 @@ export class SceneDirector {
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.viewer.camera.flyTo({
|
||||
destination,
|
||||
cameraOf(this.viewer).flyTo(destination, {
|
||||
orientation: {
|
||||
heading: Cesium.Math.toRadians(cameraState.heading || 0),
|
||||
pitch: Cesium.Math.toRadians(cameraState.pitch ?? -35),
|
||||
roll: Cesium.Math.toRadians(cameraState.roll || 0),
|
||||
},
|
||||
duration,
|
||||
easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
|
||||
complete: finish,
|
||||
cancel: finish,
|
||||
durationS: duration,
|
||||
easing: 'cubicInOut',
|
||||
onComplete: finish,
|
||||
onCancel: finish,
|
||||
});
|
||||
|
||||
// Safety timeout in case Cesium callbacks fail to fire
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindFakeCesiumCamera } from '../testSupport/fakeCameraPort.mjs';
|
||||
import { readShellSource } from '../testSupport/readShellSource.mjs';
|
||||
import { useStatusStore } from '../ui/stores/status.js';
|
||||
import { applicationPinia } from '../ui/vueRuntime.js';
|
||||
@@ -1072,7 +1073,7 @@ function fakeViewer() {
|
||||
const flights = [];
|
||||
const views = [];
|
||||
let cancelled = 0;
|
||||
return {
|
||||
return bindFakeCesiumCamera({
|
||||
flights,
|
||||
views,
|
||||
get cancelledFlights() { return cancelled; },
|
||||
@@ -1086,7 +1087,7 @@ function fakeViewer() {
|
||||
},
|
||||
cancelFlight() { cancelled++; },
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** Yield enough turns for the director's pending awaits to advance. */
|
||||
@@ -1776,7 +1777,7 @@ test('destroy drains cancelled LOAD work before its viewer can be discarded', as
|
||||
t.after(restore);
|
||||
let finishVisual;
|
||||
const dataManager = fakeDataManager();
|
||||
const viewer = { camera: { cancelFlight() {} } };
|
||||
const viewer = bindFakeCesiumCamera({ camera: { cancelFlight() {} } });
|
||||
const director = new SceneDirector(viewer, {
|
||||
applyVisualState: () => new Promise((resolve) => { finishVisual = resolve; }),
|
||||
}, dataManager);
|
||||
|
||||
+13
-8
@@ -12,6 +12,7 @@ import { msg } from '../i18n/index.js';
|
||||
import { useProviderSettingsStore } from '../ui/stores/providerSettings.js';
|
||||
import { applicationPinia } from '../ui/vueRuntime.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import { cameraOf } from '../render/cesium/camera.js';
|
||||
import { distance as chordM, toLonLatH } from '../geo/index.js';
|
||||
import { CITY_POIS } from '../cityPois.js';
|
||||
import { greatCircleKm } from '../geoDistance.js';
|
||||
@@ -2654,14 +2655,18 @@ async function frameOverhead(viewer, dataManager, styleManager, args = {}) {
|
||||
'frame',
|
||||
'frame_overhead',
|
||||
() => {
|
||||
viewer.camera.flyToBoundingSphere(sphere, {
|
||||
duration: 2.0,
|
||||
offset: new Cesium.HeadingPitchRange(
|
||||
viewer.camera.heading,
|
||||
pitch,
|
||||
sphere.radius * 2.4,
|
||||
),
|
||||
});
|
||||
const camera = cameraOf(viewer);
|
||||
camera.flyToBounds(
|
||||
{ center: sphere.center, radius: sphere.radius },
|
||||
{
|
||||
durationS: 2.0,
|
||||
offset: {
|
||||
heading: camera.pose().heading,
|
||||
pitch,
|
||||
range: sphere.radius * 2.4,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let detectionEnabled = false;
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user