Files
space-game/apps/viewer/src/viewerNavigationController.ts

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);
}
}