mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-12 06:12:18 +00:00
refactor(installations): use globe camera contract
This commit is contained in:
1 parent
c58bc9864b
commit
f4f0088814
6 files changed
+100
-17
No files matched your search
Vendored
+7
-8
@@ -1,4 +1,3 @@
|
|||||||
import * as Cesium from 'cesium';
|
|
||||||
import * as geo from '../../geo/index.js';
|
import * as geo from '../../geo/index.js';
|
||||||
import { installationFeedback } from '../../data/installationFeedback.js';
|
import { installationFeedback } from '../../data/installationFeedback.js';
|
||||||
import {
|
import {
|
||||||
@@ -39,7 +38,7 @@ export function createControls({ state: layerState, services, parts, source }) {
|
|||||||
* only area that answers "what is near this contact". The window moves
|
* only area that answers "what is near this contact". The window moves
|
||||||
* only after the subject travels ANCHOR_REFRESH_M, and cached tiles make
|
* only after the subject travels ANCHOR_REFRESH_M, and cached tiles make
|
||||||
* that cheap. Passing null returns the layer to viewport loading.
|
* that cheap. Passing null returns the layer to viewport loading.
|
||||||
* @param {Cesium.Cartesian3|{latitude:number, longitude:number}|null} position
|
* @param {object|{latitude:number, longitude:number}|null} position
|
||||||
* Subject position.
|
* Subject position.
|
||||||
* @returns {boolean} Whether a new load was scheduled.
|
* @returns {boolean} Whether a new load was scheduled.
|
||||||
*/
|
*/
|
||||||
@@ -145,19 +144,19 @@ export function createControls({ state: layerState, services, parts, source }) {
|
|||||||
*/
|
*/
|
||||||
focusById(id) {
|
focusById(id) {
|
||||||
const record = layerState.recordById.get(String(id));
|
const record = layerState.recordById.get(String(id));
|
||||||
if (!record || !layerState.viewer) return false;
|
if (!record || !layerState.engine?.camera?.flyToSphere) return false;
|
||||||
// No camera flight without a real selection: a flight plus a stale subject
|
// No camera flight without a real selection: a flight plus a stale subject
|
||||||
// reads as success to Context navigation and strands NEXT on this item.
|
// reads as success to Context navigation and strands NEXT on this item.
|
||||||
if (!parts.selection.selectRecord(record.id)) return false;
|
if (!parts.selection.selectRecord(record.id)) return false;
|
||||||
layerState.viewer.camera.flyToBoundingSphere(
|
layerState.engine.camera.flyToSphere(
|
||||||
new Cesium.BoundingSphere(
|
{
|
||||||
geo.cartesian3.fromDegrees(
|
center: geo.cartesian3.fromDegrees(
|
||||||
record.longitude,
|
record.longitude,
|
||||||
record.latitude,
|
record.latitude,
|
||||||
parts.rendering.installationSurfaceHeightM(record),
|
parts.rendering.installationSurfaceHeightM(record),
|
||||||
),
|
),
|
||||||
18000,
|
radius: 18000,
|
||||||
),
|
},
|
||||||
{ duration: 1.4 },
|
{ duration: 1.4 },
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import * as geo from '../../geo/index.js';
|
||||||
|
import { createControls } from './controls.js';
|
||||||
|
|
||||||
|
test('focus frames a selected installation through the globe engine camera', () => {
|
||||||
|
const record = { id: 'osm:node:12', latitude: 70, longitude: 179.8 };
|
||||||
|
let flight = null;
|
||||||
|
const state = {
|
||||||
|
engine: {
|
||||||
|
camera: {
|
||||||
|
flyToSphere: (sphere, options) => {
|
||||||
|
flight = { sphere, options };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
recordById: new Map([[record.id, record]]),
|
||||||
|
};
|
||||||
|
const controls = createControls({
|
||||||
|
state,
|
||||||
|
services: {},
|
||||||
|
parts: {
|
||||||
|
selection: { selectRecord: (id) => id === record.id },
|
||||||
|
rendering: { installationSurfaceHeightM: () => 126 },
|
||||||
|
},
|
||||||
|
source: {},
|
||||||
|
}).methods;
|
||||||
|
|
||||||
|
assert.equal(controls.focusById(record.id), true);
|
||||||
|
assert.deepEqual(
|
||||||
|
flight.sphere.center,
|
||||||
|
geo.cartesian3.fromDegrees(179.8, 70, 126),
|
||||||
|
);
|
||||||
|
assert.equal(flight.sphere.radius, 18000);
|
||||||
|
assert.deepEqual(flight.options, { duration: 1.4 });
|
||||||
|
});
|
||||||
@@ -15,19 +15,20 @@ export function createLifecycle({
|
|||||||
const { clearSelectedEntityContextForLayer } = services.context;
|
const { clearSelectedEntityContextForLayer } = services.context;
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
init(viewer) {
|
init(viewer, { engine = null } = {}) {
|
||||||
layerState.viewer = viewer;
|
layerState.viewer = viewer;
|
||||||
|
layerState.engine = engine;
|
||||||
layerState.dataSource = new Cesium.CustomDataSource(
|
layerState.dataSource = new Cesium.CustomDataSource(
|
||||||
'military-installations',
|
'military-installations',
|
||||||
);
|
);
|
||||||
viewer.dataSources.add(layerState.dataSource);
|
viewer.dataSources.add(layerState.dataSource);
|
||||||
|
const camera = engine?.camera ?? viewer.camera;
|
||||||
const cameraChanged = () => {
|
const cameraChanged = () => {
|
||||||
if (!layerState.contextAnchor) parts.viewport.scheduleLoad();
|
if (!layerState.contextAnchor) parts.viewport.scheduleLoad();
|
||||||
};
|
};
|
||||||
layerState.moveEndRemove =
|
layerState.moveEndRemove = camera.moveEnd.addEventListener(cameraChanged);
|
||||||
viewer.camera.moveEnd.addEventListener(cameraChanged);
|
|
||||||
layerState.changedRemove =
|
layerState.changedRemove =
|
||||||
viewer.camera.changed?.addEventListener(cameraChanged);
|
camera.changed?.addEventListener(cameraChanged);
|
||||||
parts.selection.installInteraction(viewer);
|
parts.selection.installInteraction(viewer);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ export function createLifecycle({
|
|||||||
if (layerState.dataSource && viewer)
|
if (layerState.dataSource && viewer)
|
||||||
viewer.dataSources.remove(layerState.dataSource, true);
|
viewer.dataSources.remove(layerState.dataSource, true);
|
||||||
layerState.dataSource = null;
|
layerState.dataSource = null;
|
||||||
|
layerState.engine = null;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -189,3 +189,45 @@ test('wide views refresh screen decluttering when the globe rectangle stays unch
|
|||||||
t.mock.timers.tick(200);
|
t.mock.timers.tick(200);
|
||||||
assert.equal(loads, 2, 'a different continent needs fresh projected labels');
|
assert.equal(loads, 2, 'a different continent needs fresh projected labels');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the engine camera drives installation viewport requests without a viewer', (t) => {
|
||||||
|
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||||
|
const rectangle = {
|
||||||
|
west: Cesium.Math.toRadians(179),
|
||||||
|
south: Cesium.Math.toRadians(-12),
|
||||||
|
east: Cesium.Math.toRadians(-178),
|
||||||
|
north: Cesium.Math.toRadians(16),
|
||||||
|
};
|
||||||
|
const state = {
|
||||||
|
enabled: true,
|
||||||
|
viewer: null,
|
||||||
|
engine: {
|
||||||
|
camera: {
|
||||||
|
viewRectangle: () => rectangle,
|
||||||
|
pose: () => ({
|
||||||
|
position: { x: 1, y: 2, z: 3 },
|
||||||
|
direction: { x: -1, y: 0, z: 0 },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const loads = [];
|
||||||
|
const parts = {
|
||||||
|
ingestion: {
|
||||||
|
loadInstallations() {
|
||||||
|
loads.push(parts.viewport.loadArea(null));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
parts.viewport = createViewport({ state, parts, services: {}, source: {} });
|
||||||
|
|
||||||
|
parts.viewport.scheduleLoad();
|
||||||
|
t.mock.timers.tick(200);
|
||||||
|
|
||||||
|
assert.equal(loads.length, 1);
|
||||||
|
assert.ok(Math.abs(loads[0].box.south + 12) < 1e-10);
|
||||||
|
assert.equal(loads[0].box.west, 179);
|
||||||
|
assert.equal(loads[0].box.north, 16);
|
||||||
|
assert.equal(loads[0].box.east, -178);
|
||||||
|
assert.equal(loads[0].coverage.kind, 'viewport');
|
||||||
|
});
|
||||||
@@ -9,6 +9,7 @@ export function createState({ services }) {
|
|||||||
|
|
||||||
Object.assign(state, {
|
Object.assign(state, {
|
||||||
viewer: null,
|
viewer: null,
|
||||||
|
engine: null,
|
||||||
dataSource: null,
|
dataSource: null,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
records: [],
|
records: [],
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ export function createViewport({ state: layerState, services, parts, source }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function viewportBox(viewer) {
|
function viewportBox(viewer) {
|
||||||
const rectangle = viewer?.camera?.computeViewRectangle(
|
const rectangle =
|
||||||
viewer.scene.globe.ellipsoid,
|
layerState.engine?.camera?.viewRectangle?.() ??
|
||||||
);
|
viewer?.camera?.computeViewRectangle(viewer.scene.globe.ellipsoid);
|
||||||
if (!rectangle) return null;
|
if (!rectangle) return null;
|
||||||
const south = geo.math.toDegrees(rectangle.south);
|
const south = geo.math.toDegrees(rectangle.south);
|
||||||
const north = geo.math.toDegrees(rectangle.north);
|
const north = geo.math.toDegrees(rectangle.north);
|
||||||
@@ -89,14 +89,17 @@ export function createViewport({ state: layerState, services, parts, source }) {
|
|||||||
function scheduleLoad() {
|
function scheduleLoad() {
|
||||||
if (!layerState.enabled) return;
|
if (!layerState.enabled) return;
|
||||||
const { box, coverage } = loadArea(layerState.viewer);
|
const { box, coverage } = loadArea(layerState.viewer);
|
||||||
const camera = layerState.viewer.camera;
|
const pose = layerState.engine?.camera?.pose?.();
|
||||||
|
const camera = layerState.viewer?.camera;
|
||||||
|
const position = pose?.position ?? camera?.positionWC;
|
||||||
|
const direction = pose?.direction ?? camera?.directionWC;
|
||||||
const widePose =
|
const widePose =
|
||||||
coverage.kind === 'viewport' &&
|
coverage.kind === 'viewport' &&
|
||||||
box &&
|
box &&
|
||||||
(box.east < box.west ||
|
(box.east < box.west ||
|
||||||
box.east - box.west > 10 ||
|
box.east - box.west > 10 ||
|
||||||
box.north - box.south > 10)
|
box.north - box.south > 10)
|
||||||
? [camera.positionWC, camera.directionWC]
|
? [position, direction]
|
||||||
.map((v) =>
|
.map((v) =>
|
||||||
v ? [v.x, v.y, v.z].map((n) => n.toFixed(4)).join(',') : '',
|
v ? [v.x, v.y, v.z].map((n) => n.toFixed(4)).join(',') : '',
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in new issue
Block a user