feat(cockpit): enter Cockpit with only the ridden aircraft's layer on

Cockpit entry no longer needs Contacts or both flight feeds: a civilian
aircraft needs Live Flights, a military one Military Flights. Contacts and
every other layer can be turned on or off before or during a session, and
turning off the ridden layer ends it. A Context mode change in flight still
blocks entry.

When the other air feed is off, Live Signals and the Contact card say
FLIGHTS FEED OFF or MILITARY FEED OFF · PARTIAL PICTURE instead of
presenting a partial picture as complete. Voice refuses entry with
subject-layer-off rather than contacts-inactive, and its tool description
says Contacts is not required.

Double-clicking the followed aircraft enters Cockpit through the same gate.
The Cockpit takes the scene's double-click before Cesium's stock handler,
which would otherwise untrack a followed aircraft whose model is a
primitive. The entry also shows under the Intel HUD readout, sharing the
Contacts button's face. A test keeps the classes the Cockpit adds to the
body from matching another element's rule.
This commit is contained in:
Denis Groshev committed 2026-11-04 21:53:05 +01:00
1 parent 82f17e694e
commit 219f3e1079
37 files changed
+964 -134

No files matched your search

+94
View File
@@ -1,5 +1,99 @@
/** Route Cockpit keys while preserving the active disclosure and form-control owner. */
import * as Cesium from 'cesium';
import { isPointerFree } from '../data/inputOwnership.js';
/** How far from the followed track a double-click may land, CSS pixels. */
export const SUBJECT_DOUBLE_CLICK_RADIUS_PX = 32;
/**
* Whether a double-click landed on the followed track's mark.
* @param {{x: number, y: number}|null} click Canvas position of the click.
* @param {{x: number, y: number}|null|undefined} subject Canvas position of
* the track, or nothing when it is off screen.
* @param {number} [radiusPx]
*/
export function doubleClickHitsSubject(
click,
subject,
radiusPx = SUBJECT_DOUBLE_CLICK_RADIUS_PX,
) {
if (!click || !subject) return false;
const distance = Math.hypot(click.x - subject.x, click.y - subject.y);
return Number.isFinite(distance) && distance <= radiusPx;
}
/** Where the track `kind` would ride is drawn now, as a world position. */
function subjectWorldPosition(kind) {
if (kind !== 'aircraft') return null;
return (
this.viewer.trackedEntity?.position?.getValue?.(
this.viewer.clock?.currentTime,
) ?? null
);
}
/** The followed track's canvas position now, or null off screen. */
function subjectCanvasPosition() {
const kind = this.resolveEntryKind();
const world = kind ? subjectWorldPosition.call(this, kind) : null;
const scene = this.viewer.scene;
if (!world || typeof scene?.cartesianToCanvasCoordinates !== 'function')
return null;
return (
scene.cartesianToCanvasCoordinates(world, new Cesium.Cartesian2()) ?? null
);
}
/**
* Enter first person when the followed track itself is double-clicked: the
* same entry, and the same gates, as C and the entry buttons. A
* double-click anywhere else, or one a tool owns (drawing ends a shape with
* one), is left alone.
* @param {{x: number, y: number}|null|undefined} position Canvas position.
* @returns {boolean} True when it landed on the followed track, entered or
* refused by the gate: the caller does nothing else with it.
*/
export function onSceneDoubleClick(position) {
if (this.active || this.destroyed || !isPointerFree()) return false;
if (!Number.isFinite(position?.x) || !Number.isFinite(position?.y))
return false;
if (!doubleClickHitsSubject(position, subjectCanvasPosition.call(this)))
return false;
this.enter();
return true;
}
/**
* Take the scene's double-click through the Cockpit first. Cesium's Viewer
* installs its own (pickAndTrackObject), which untracks whenever the pick is
* not an Entity: on a followed aircraft's model, a primitive, it would drop
* the very track the double-click means to ride. A double-click on the
* followed track is the Cockpit's; any other goes on to Cesium's.
* @returns {() => void} Puts Cesium's handler back, unless another owner
* (the draw tool borrows it) has replaced ours since.
*/
export function routeSceneDoubleClick() {
const handler = this.viewer.screenSpaceEventHandler;
if (typeof handler?.setInputAction !== 'function') return () => {};
const type = Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK;
const stock = handler.getInputAction?.(type) ?? null;
const routed = (event) => {
if (this.onSceneDoubleClick(event?.position)) return;
stock?.(event);
};
handler.setInputAction(routed, type);
return () => {
if (handler.isDestroyed?.() || handler.getInputAction?.(type) !== routed)
return;
if (stock) handler.setInputAction(stock, type);
else handler.removeInputAction(type);
};
}
/**
* The Cockpit's capture-phase keydown: Escape and C.
* @param {KeyboardEvent} event
*/
export function onKeyDown(event) {
if (this.destroyed) return false;
if (event.repeat || event.isComposing) return;