204 lines
7.7 KiB
TypeScript
204 lines
7.7 KiB
TypeScript
import * as THREE from "three";
|
|
import { scaleGalaxyVector, toThreeVector } from "./viewerMath";
|
|
import {
|
|
determineActiveSystemId,
|
|
focusOnSelection,
|
|
getCameraFocusWorldPosition,
|
|
getSystemCameraFocus,
|
|
resolveSelectionPosition,
|
|
seedSystemFocusLocal,
|
|
toDisplayLocalPosition,
|
|
} from "./viewerCamera";
|
|
import {
|
|
syncFollowStateFromSelection,
|
|
updateFollowCamera,
|
|
} from "./viewerControls";
|
|
import { computeNodeLocalPosition, resolvePointPosition } from "./viewerWorldPresentation";
|
|
import { getAnimatedShipLocalPosition, resolveShipHeading } from "./viewerPresentation";
|
|
import type {
|
|
CameraMode,
|
|
NodeVisual,
|
|
PlanetVisual,
|
|
Selectable,
|
|
ShipVisual,
|
|
SystemVisual,
|
|
WorldState,
|
|
PovLevel,
|
|
} from "./viewerTypes";
|
|
|
|
export interface ViewerNavigationContext {
|
|
getWorld: () => WorldState | undefined;
|
|
getWorldTimeSyncMs: () => number;
|
|
getActiveSystemId: () => string | undefined;
|
|
setActiveSystemId: (value: string | undefined) => void;
|
|
onActiveSystemChanged: (oldId: string | undefined, newId: string | undefined) => void;
|
|
getCameraMode: () => CameraMode;
|
|
setCameraMode: (value: CameraMode) => void;
|
|
getCameraTargetShipId: () => string | undefined;
|
|
setCameraTargetShipId: (value: string | undefined) => void;
|
|
getCurrentDistance: () => number;
|
|
getPovLevel: () => PovLevel;
|
|
getSelectedItems: () => Selectable[];
|
|
getOrbitYaw: () => number;
|
|
getFollowOrbitYaw: () => number;
|
|
getFollowOrbitPitch: () => number;
|
|
galaxyAnchor: THREE.Vector3;
|
|
systemAnchor: THREE.Vector3;
|
|
galaxyCamera: THREE.PerspectiveCamera;
|
|
systemCamera: THREE.PerspectiveCamera;
|
|
shipVisuals: Map<string, ShipVisual>;
|
|
nodeVisuals: Map<string, NodeVisual>;
|
|
planetVisuals: PlanetVisual[];
|
|
systemVisuals: Map<string, SystemVisual>;
|
|
followCameraPosition: THREE.Vector3;
|
|
followCameraFocus: THREE.Vector3;
|
|
followCameraDirection: THREE.Vector3;
|
|
followCameraDesiredDirection: THREE.Vector3;
|
|
followCameraOffset: THREE.Vector3;
|
|
createWorldPresentationContext: () => any;
|
|
updatePanels: () => void;
|
|
updateGamePanel: (mode: string) => void;
|
|
}
|
|
|
|
export class ViewerNavigationController {
|
|
constructor(private readonly context: ViewerNavigationContext) {}
|
|
|
|
syncGalaxyAnchorToActiveSystem() {
|
|
const world = this.context.getWorld();
|
|
const activeSystemId = this.context.getActiveSystemId();
|
|
const povLevel = this.context.getPovLevel();
|
|
if (!world || !activeSystemId || povLevel === "galaxy") {
|
|
return;
|
|
}
|
|
|
|
const system = world.systems.get(activeSystemId);
|
|
if (!system) {
|
|
return;
|
|
}
|
|
|
|
this.context.galaxyAnchor.copy(scaleGalaxyVector(toThreeVector(system.galaxyPosition)));
|
|
}
|
|
|
|
focusOnSelection(selection: Selectable) {
|
|
focusOnSelection({
|
|
world: this.context.getWorld(),
|
|
selection,
|
|
worldTimeSyncMs: this.context.getWorldTimeSyncMs(),
|
|
nodeVisuals: this.context.nodeVisuals,
|
|
planetVisuals: this.context.planetVisuals,
|
|
computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
|
|
resolvePointPosition: (systemId, celestialId, anchorId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemId, celestialId, anchorId),
|
|
activeSystemId: this.context.getActiveSystemId(),
|
|
galaxyAnchor: this.context.galaxyAnchor,
|
|
systemAnchor: this.context.systemAnchor,
|
|
});
|
|
}
|
|
|
|
resolveSelectionPosition(selection: Selectable) {
|
|
return resolveSelectionPosition({
|
|
world: this.context.getWorld(),
|
|
selection,
|
|
worldTimeSyncMs: this.context.getWorldTimeSyncMs(),
|
|
nodeVisuals: this.context.nodeVisuals,
|
|
planetVisuals: this.context.planetVisuals,
|
|
computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
|
|
resolvePointPosition: (systemId, celestialId, anchorId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemId, celestialId, anchorId),
|
|
});
|
|
}
|
|
|
|
updateActiveSystem() {
|
|
const nextActiveSystemId = determineActiveSystemId({
|
|
world: this.context.getWorld(),
|
|
cameraMode: this.context.getCameraMode(),
|
|
cameraTargetShipId: this.context.getCameraTargetShipId(),
|
|
currentDistance: this.context.getCurrentDistance(),
|
|
selectedItems: this.context.getSelectedItems(),
|
|
galaxyAnchor: this.context.galaxyAnchor,
|
|
});
|
|
const previousSystemId = this.context.getActiveSystemId();
|
|
if (nextActiveSystemId === previousSystemId) {
|
|
return;
|
|
}
|
|
|
|
if (nextActiveSystemId) {
|
|
this.seedSystemFocusLocal(nextActiveSystemId);
|
|
}
|
|
|
|
this.context.setActiveSystemId(nextActiveSystemId);
|
|
this.context.onActiveSystemChanged(previousSystemId, nextActiveSystemId);
|
|
this.context.updatePanels();
|
|
this.context.updateGamePanel("Live");
|
|
}
|
|
|
|
updateFollowCamera(delta: number) {
|
|
const nextState = updateFollowCamera({
|
|
world: this.context.getWorld(),
|
|
cameraMode: this.context.getCameraMode(),
|
|
cameraTargetShipId: this.context.getCameraTargetShipId(),
|
|
shipVisuals: this.context.shipVisuals,
|
|
currentDistance: this.context.getCurrentDistance(),
|
|
camera: this.context.systemCamera,
|
|
followCameraPosition: this.context.followCameraPosition,
|
|
followCameraFocus: this.context.followCameraFocus,
|
|
followCameraDirection: this.context.followCameraDirection,
|
|
followCameraDesiredDirection: this.context.followCameraDesiredDirection,
|
|
followCameraOffset: this.context.followCameraOffset,
|
|
systemAnchor: this.context.systemAnchor,
|
|
delta,
|
|
followOrbitYaw: this.context.getFollowOrbitYaw(),
|
|
followOrbitPitch: this.context.getFollowOrbitPitch(),
|
|
getAnimatedShipLocalPosition,
|
|
toDisplayLocalPosition: (localPosition) => toDisplayLocalPosition(localPosition),
|
|
resolveShipHeading: (visual, worldPosition) => resolveShipHeading(visual, worldPosition, this.context.getOrbitYaw()),
|
|
});
|
|
this.context.setCameraMode(nextState.cameraMode);
|
|
this.context.setCameraTargetShipId(nextState.cameraTargetShipId);
|
|
return nextState.handled;
|
|
}
|
|
|
|
syncFollowStateFromSelection() {
|
|
const nextState = syncFollowStateFromSelection(
|
|
this.context.getSelectedItems(),
|
|
this.context.getCameraMode(),
|
|
this.context.getCameraTargetShipId(),
|
|
);
|
|
this.context.setCameraMode(nextState.cameraMode);
|
|
this.context.setCameraTargetShipId(nextState.cameraTargetShipId);
|
|
}
|
|
|
|
getCameraFocusWorldPosition() {
|
|
return getCameraFocusWorldPosition({
|
|
galaxyAnchor: this.context.galaxyAnchor,
|
|
});
|
|
}
|
|
|
|
getSystemCameraFocus() {
|
|
return getSystemCameraFocus(this.context.systemAnchor);
|
|
}
|
|
|
|
seedSystemFocusLocal(systemId: string) {
|
|
seedSystemFocusLocal({
|
|
world: this.context.getWorld(),
|
|
systemId,
|
|
cameraMode: this.context.getCameraMode(),
|
|
cameraTargetShipId: this.context.getCameraTargetShipId(),
|
|
selectedItems: this.context.getSelectedItems(),
|
|
systemAnchor: this.context.systemAnchor,
|
|
worldTimeSyncMs: this.context.getWorldTimeSyncMs(),
|
|
nodeVisuals: this.context.nodeVisuals,
|
|
planetVisuals: this.context.planetVisuals,
|
|
computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
|
|
resolvePointPosition: (systemIdValue, celestialId, anchorId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemIdValue, celestialId, anchorId),
|
|
});
|
|
}
|
|
|
|
toDisplayLocalPosition(localPosition: THREE.Vector3) {
|
|
return toDisplayLocalPosition(localPosition);
|
|
}
|
|
|
|
/** Returns a display position for the system camera, derived from a raw local position in meters. */
|
|
toSystemDisplayPosition(localPosition: THREE.Vector3) {
|
|
return toDisplayLocalPosition(localPosition);
|
|
}
|
|
}
|