Refactor simulation and viewer architecture

This commit is contained in:
2026-03-14 15:08:49 -04:00
parent ddca4a16d5
commit 651556c916
71 changed files with 11472 additions and 9031 deletions

View File

@@ -0,0 +1,193 @@
import * as THREE from "three";
import {
determineActiveSystemId,
focusOnSelection,
getCameraFocusWorldPosition,
resolveSelectionPosition,
seedSystemFocusLocal,
toDisplayLocalPosition,
} from "./viewerCamera";
import {
syncFollowStateFromSelection,
updateFollowCamera,
updateSystemDetailVisibility,
} from "./viewerControls";
import { computeNodeLocalPosition, resolveBubblePosition, resolvePointPosition } from "./viewerWorldPresentation";
import { getAnimatedShipLocalPosition, resolveShipHeading } from "./viewerPresentation";
import type {
CameraMode,
NodeVisual,
PlanetVisual,
Selectable,
ShipVisual,
SystemVisual,
WorldState,
} from "./viewerTypes";
export interface ViewerNavigationContext {
getWorld: () => WorldState | undefined;
getWorldTimeSyncMs: () => number;
getActiveSystemId: () => string | undefined;
setActiveSystemId: (value: string | undefined) => void;
getCameraMode: () => CameraMode;
setCameraMode: (value: CameraMode) => void;
getCameraTargetShipId: () => string | undefined;
setCameraTargetShipId: (value: string | undefined) => void;
getCurrentDistance: () => number;
getSelectedItems: () => Selectable[];
getOrbitYaw: () => number;
galaxyFocus: THREE.Vector3;
systemFocusLocal: THREE.Vector3;
camera: 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) {}
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),
resolveBubblePosition: (bubbleId) => {
const bubble = this.context.getWorld()?.localBubbles.get(bubbleId);
return bubble ? resolveBubblePosition(this.context.createWorldPresentationContext(), bubble) : undefined;
},
resolvePointPosition: (systemId, nodeId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemId, nodeId),
activeSystemId: this.context.getActiveSystemId(),
galaxyFocus: this.context.galaxyFocus,
systemFocusLocal: this.context.systemFocusLocal,
});
}
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),
resolveBubblePosition: (bubbleId) => {
const bubble = this.context.getWorld()?.localBubbles.get(bubbleId);
return bubble ? resolveBubblePosition(this.context.createWorldPresentationContext(), bubble) : undefined;
},
resolvePointPosition: (systemId, nodeId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemId, nodeId),
});
}
updateActiveSystem() {
const nextActiveSystemId = determineActiveSystemId({
world: this.context.getWorld(),
cameraMode: this.context.getCameraMode(),
cameraTargetShipId: this.context.getCameraTargetShipId(),
currentDistance: this.context.getCurrentDistance(),
selectedItems: this.context.getSelectedItems(),
galaxyFocus: this.context.galaxyFocus,
});
if (nextActiveSystemId === this.context.getActiveSystemId()) {
return;
}
if (nextActiveSystemId) {
this.seedSystemFocusLocal(nextActiveSystemId);
}
this.context.setActiveSystemId(nextActiveSystemId);
this.updateSystemDetailVisibility();
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.camera,
followCameraPosition: this.context.followCameraPosition,
followCameraFocus: this.context.followCameraFocus,
followCameraDirection: this.context.followCameraDirection,
followCameraDesiredDirection: this.context.followCameraDesiredDirection,
followCameraOffset: this.context.followCameraOffset,
systemFocusLocal: this.context.systemFocusLocal,
delta,
getAnimatedShipLocalPosition,
toDisplayLocalPosition: (localPosition, systemId) => this.toDisplayLocalPosition(localPosition, systemId),
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);
}
updateSystemDetailVisibility() {
updateSystemDetailVisibility(this.context.systemVisuals, this.context.getActiveSystemId());
}
getCameraFocusWorldPosition() {
return getCameraFocusWorldPosition({
world: this.context.getWorld(),
activeSystemId: this.context.getActiveSystemId(),
galaxyFocus: this.context.galaxyFocus,
systemFocusLocal: this.context.systemFocusLocal,
});
}
seedSystemFocusLocal(systemId: string) {
seedSystemFocusLocal({
world: this.context.getWorld(),
systemId,
cameraMode: this.context.getCameraMode(),
cameraTargetShipId: this.context.getCameraTargetShipId(),
selectedItems: this.context.getSelectedItems(),
systemFocusLocal: this.context.systemFocusLocal,
worldTimeSyncMs: this.context.getWorldTimeSyncMs(),
nodeVisuals: this.context.nodeVisuals,
planetVisuals: this.context.planetVisuals,
computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
resolveBubblePosition: (bubbleId) => {
const bubble = this.context.getWorld()?.localBubbles.get(bubbleId);
return bubble ? resolveBubblePosition(this.context.createWorldPresentationContext(), bubble) : undefined;
},
resolvePointPosition: (systemIdValue, nodeId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemIdValue, nodeId),
});
}
toDisplayLocalPosition(localPosition: THREE.Vector3, systemId?: string) {
return toDisplayLocalPosition({
world: this.context.getWorld(),
systemId,
activeSystemId: this.context.getActiveSystemId(),
localPosition,
systemFocusLocal: this.context.systemFocusLocal,
});
}
}