mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(awareness): ignore canvas input while the cockpit owns the camera
The cockpit turns Cesium's camera controls off and moves the camera on its own, so a click, drag or wheel on the canvas moved nothing yet still counted as the user handling the map: the cockpit's next move woke a nine-second pulse burst. Input now counts only while the camera controls take it, and a gesture still pending when another owner takes the camera is dropped.
This commit is contained in:
1 parent
fe3d17df02
commit
56a7cacbec
4 files changed
+109
-3
No files matched your search
@@ -14,13 +14,22 @@ export function createLifecycle({
|
||||
* Cesium canvas, where its camera controller listens), so a camera move the
|
||||
* user made can wake the range pulses and one the app made (tracking,
|
||||
* cockpit, orbit, a fly-to) does not. Keys are left out: Cesium's camera
|
||||
* controller reads none, and typing elsewhere must not count.
|
||||
* controller reads none, and typing elsewhere must not count. Nor does
|
||||
* input while the camera controller's inputs are off: the cockpit (or a
|
||||
* gizmo drag, or a drawing tool) owns the camera then, and the user's
|
||||
* gesture moves nothing.
|
||||
*/
|
||||
function noteCameraInput(event) {
|
||||
if (event?.type === 'pointermove' && !event.buttons) return;
|
||||
if (!cameraInputsEnabled()) return;
|
||||
layerState.lastCameraInputMs = Date.now();
|
||||
}
|
||||
|
||||
function cameraInputsEnabled() {
|
||||
const controller = layerState.viewer?.scene?.screenSpaceCameraController;
|
||||
return controller?.enableInputs !== false;
|
||||
}
|
||||
|
||||
const CAMERA_INPUT_EVENTS = [
|
||||
'pointerdown',
|
||||
'pointermove',
|
||||
@@ -79,6 +88,10 @@ export function createLifecycle({
|
||||
layerState.viewer.scene.preRender.addEventListener(() => {
|
||||
if (!layerState.enabled) return;
|
||||
const now = Date.now();
|
||||
// Another owner took the camera: a gesture begun before it did must
|
||||
// not wake the pulses on that owner's first move.
|
||||
if (!cameraInputsEnabled())
|
||||
layerState.lastCameraInputMs = Number.NEGATIVE_INFINITY;
|
||||
// "Did the camera move" uses the SAME quantized pose signature the fleet
|
||||
// rotation pass gates on (iconOrientation.cameraPoseSignature) rather than
|
||||
// camera.changed, whose granularity is globally degraded by other layers
|
||||
|
||||
@@ -92,7 +92,7 @@ export const AWARENESS_RANGE_RING_STYLE = Object.freeze({
|
||||
* stays on screen, static, and nothing asks for frames, so a parked view
|
||||
* goes quiet again. Only a camera move within `inputWakeMs` of the user
|
||||
* handling the map (pointer, wheel or touch on the canvas, which covers a
|
||||
* drag's inertia) wakes them; a moving subject and the app's own camera moves
|
||||
* drag's inertia, while the camera controls take input) wakes them; a moving subject and the app's own camera moves
|
||||
* (tracking, cockpit, orbit, a fly-to) do not. Below the disc's height they
|
||||
* rest too. While they run they never hold
|
||||
* continuous rendering; each drawn frame arms one more request after the
|
||||
|
||||
@@ -574,6 +574,99 @@ test('a camera move seen by the lifecycle wakes resting pulses; a parked camera
|
||||
assert.equal(state.lastCameraInputMs < Date.now() - inputWakeMs, true);
|
||||
});
|
||||
|
||||
/**
|
||||
* The lifecycle's camera-input listeners over `ring`, with a camera the test
|
||||
* moves by longitude (`pan`) and a camera controller whose inputs it can
|
||||
* switch off, as the cockpit view does. Pulses start resting.
|
||||
*/
|
||||
function inputLifecycle(t, ring) {
|
||||
const { state, rendering } = ring;
|
||||
const loop = frameLoop(t, ring, { date: true });
|
||||
state.enabled = true;
|
||||
state.viewer.canvas = new EventTarget();
|
||||
state.viewer.scene.screenSpaceCameraController = { enableInputs: true };
|
||||
state.viewer.camera = {
|
||||
positionWC: Cesium.Cartesian3.fromDegrees(3, 20, 800_000),
|
||||
heading: 0,
|
||||
pitch: -1,
|
||||
roll: 0,
|
||||
};
|
||||
const lifecycle = createLifecycle({
|
||||
state,
|
||||
services: {
|
||||
render: { holdContinuousRender() {}, releaseContinuousRender() {} },
|
||||
},
|
||||
parts: {
|
||||
rendering,
|
||||
model: {
|
||||
awarenessRefreshDecision: () => ({ moving: false, refresh: false }),
|
||||
awarenessNeedsContinuousRender: () => false,
|
||||
},
|
||||
},
|
||||
});
|
||||
lifecycle.attachRuntimeListeners();
|
||||
t.after(() => lifecycle.detachRuntimeListeners());
|
||||
rendering.renderVisual(flight(3));
|
||||
loop.advance(AWARENESS_RANGE_PULSE.activeMs + 500);
|
||||
let lon = 3;
|
||||
return {
|
||||
loop,
|
||||
lifecycle,
|
||||
controller: state.viewer.scene.screenSpaceCameraController,
|
||||
dispatch: (type, init = {}) =>
|
||||
state.viewer.canvas.dispatchEvent(Object.assign(new Event(type), init)),
|
||||
pan(stepDeg = 0.1) {
|
||||
lon += stepDeg;
|
||||
state.viewer.camera.positionWC = Cesium.Cartesian3.fromDegrees(
|
||||
lon,
|
||||
20,
|
||||
800_000,
|
||||
);
|
||||
loop.draw();
|
||||
},
|
||||
/** Whether the pulses drew frames during the next `ms`. */
|
||||
pulsing(ms = AWARENESS_RANGE_PULSE.activeMs) {
|
||||
const before = ring.pulseFrames();
|
||||
loop.advance(ms);
|
||||
return ring.pulseFrames() > before;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('input the camera controller ignores, as in the cockpit, does not wake the pulses', (t) => {
|
||||
const ring = ringRendering(t);
|
||||
const { activeMs } = AWARENESS_RANGE_PULSE;
|
||||
const { controller, dispatch, pan, pulsing, loop } = inputLifecycle(t, ring);
|
||||
// The cockpit takes the camera: clicks, drags and wheels on the canvas
|
||||
// move nothing, while the cockpit itself moves the camera every frame.
|
||||
controller.enableInputs = false;
|
||||
for (const [type, init] of [
|
||||
['pointerdown', {}],
|
||||
['pointermove', { buttons: 1 }],
|
||||
['wheel', {}],
|
||||
['touchstart', {}],
|
||||
]) {
|
||||
dispatch(type, init);
|
||||
pan();
|
||||
assert.equal(pulsing(), false, `${type} in the cockpit`);
|
||||
}
|
||||
// A gesture that began just before the cockpit took the camera is
|
||||
// dropped too, rather than waking the pulses on the cockpit's first move.
|
||||
controller.enableInputs = true;
|
||||
loop.advance(activeMs);
|
||||
dispatch('pointerdown');
|
||||
controller.enableInputs = false;
|
||||
pan();
|
||||
assert.equal(pulsing(), false, 'a gesture cut off by the cockpit');
|
||||
controller.enableInputs = true;
|
||||
pan();
|
||||
assert.equal(pulsing(), false, 'and stays dropped once it hands back');
|
||||
// Back in the user's hands, a drag wakes them again.
|
||||
dispatch('pointerdown');
|
||||
pan();
|
||||
assert.equal(pulsing(), true, 'a drag after the cockpit');
|
||||
});
|
||||
|
||||
test('disabling the layer tears the pulses down completely', (t) => {
|
||||
const ring = ringRendering(t);
|
||||
const { state, scene, rendering, motion, pulseFrames } = ring;
|
||||
|
||||
Reference in new issue
Block a user