feat: 3 scene rendering setup
This commit is contained in:
@@ -2,49 +2,15 @@ import * as THREE from "three";
|
||||
import {
|
||||
MAX_CAMERA_DISTANCE,
|
||||
MIN_CAMERA_DISTANCE,
|
||||
ZOOM_DISTANCE,
|
||||
NAV_DISTANCE,
|
||||
} from "./viewerConstants";
|
||||
import { createViewerHud } from "./viewerHud";
|
||||
import {
|
||||
classifyZoomLevel,
|
||||
computeZoomBlend,
|
||||
formatBytes,
|
||||
inventoryAmount,
|
||||
smoothBand,
|
||||
} from "./viewerMath";
|
||||
import { updatePanFromKeyboard } from "./viewerCamera";
|
||||
import {
|
||||
createCirclePoints,
|
||||
shipLength,
|
||||
shipPresentationColor,
|
||||
shipSize,
|
||||
spatialNodeColor,
|
||||
} from "./viewerSceneAppearance";
|
||||
import {
|
||||
createBackdropStars,
|
||||
createNebulaClouds,
|
||||
createNebulaTexture,
|
||||
} from "./viewerSceneFactory";
|
||||
import {
|
||||
setShellReticleOpacity,
|
||||
} from "./viewerControls";
|
||||
import {
|
||||
recordPerformanceStats,
|
||||
updateNetworkPanel as renderNetworkPanel,
|
||||
updatePerformancePanel as renderPerformancePanel,
|
||||
} from "./viewerTelemetry";
|
||||
import { setShellReticleOpacity } from "./viewerControls";
|
||||
import { renderFrame, resizeViewer, stepCamera } from "./viewerRenderLoop";
|
||||
import { updatePlanetPresentation } from "./viewerPresentation";
|
||||
import {
|
||||
renderRecentEvents,
|
||||
updateGameStatus,
|
||||
updateSystemSummaries,
|
||||
updateWorldPresentation,
|
||||
} from "./viewerWorldPresentation";
|
||||
import {
|
||||
resolveFocusedBubbleId,
|
||||
} from "./viewerSelection";
|
||||
import { describeSelectionParent, updateSystemPanel } from "./viewerPanels";
|
||||
import { updateSystemStarPresentation } from "./viewerPresentation";
|
||||
import { resolveFocusedCelestialId } from "./viewerSelection";
|
||||
import { describeSelectionParent } from "./viewerPanels";
|
||||
import {
|
||||
createInitialNetworkStats,
|
||||
createInitialPerformanceStats,
|
||||
@@ -55,68 +21,65 @@ import { ViewerNavigationController } from "./viewerNavigationController";
|
||||
import { ViewerSceneDataController } from "./viewerSceneDataController";
|
||||
import { ViewerPresentationController } from "./viewerPresentationController";
|
||||
import { createViewerControllers, wireViewerEvents } from "./viewerControllerFactory";
|
||||
import type { SceneNode } from "./viewerScenePrimitives";
|
||||
import type { FactionSnapshot, ShipSnapshot } from "./contracts";
|
||||
import { toDisplayLocalPosition, getSystemCameraFocus } from "./viewerCamera";
|
||||
import { UniverseLayer } from "./viewerUniverseLayer";
|
||||
import { GalaxyLayer } from "./viewerGalaxyLayer";
|
||||
import { SystemLayer } from "./viewerSystemLayer";
|
||||
import { LocalLayer } from "./viewerLocalLayer";
|
||||
import type { FactionSnapshot } from "./contracts";
|
||||
import type {
|
||||
BubbleVisual,
|
||||
CelestialVisual,
|
||||
CameraMode,
|
||||
ClaimVisual,
|
||||
ConstructionSiteVisual,
|
||||
DragMode,
|
||||
HistoryWindowState,
|
||||
MoonVisual,
|
||||
NetworkStats,
|
||||
NodeVisual,
|
||||
OrbitLineVisual,
|
||||
OrbitalAnchor,
|
||||
PerformanceStats,
|
||||
PlanetVisual,
|
||||
PresentationEntry,
|
||||
Selectable,
|
||||
ShipVisual,
|
||||
SpatialNodeVisual,
|
||||
StructureVisual,
|
||||
SystemSummaryVisual,
|
||||
SystemVisual,
|
||||
WorldState,
|
||||
ZoomLevel,
|
||||
PovLevel,
|
||||
} from "./viewerTypes";
|
||||
|
||||
export class ViewerAppController {
|
||||
private readonly container: HTMLElement;
|
||||
private readonly renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
private readonly scene = new THREE.Scene();
|
||||
private readonly camera = new THREE.PerspectiveCamera(50, 1, 0.1, 160000);
|
||||
|
||||
// ── Three independent rendering layers ───────────────────────────────────
|
||||
private readonly universeLayer = new UniverseLayer();
|
||||
private readonly galaxyLayer = new GalaxyLayer();
|
||||
private readonly systemLayer = new SystemLayer();
|
||||
private readonly localLayer = new LocalLayer();
|
||||
|
||||
private readonly clock = new THREE.Clock();
|
||||
private readonly raycaster = new THREE.Raycaster();
|
||||
private readonly mouse = new THREE.Vector2();
|
||||
private readonly galaxyFocus = new THREE.Vector3(2200, 0, 300);
|
||||
private readonly systemFocusLocal = new THREE.Vector3();
|
||||
|
||||
// ── Galaxy-space anchor ───────────────────────────────────────────────────
|
||||
private readonly galaxyAnchor = new THREE.Vector3(2200, 0, 300);
|
||||
// ── System-space anchor ───────────────────────────────────────────────────
|
||||
private readonly systemAnchor = new THREE.Vector3();
|
||||
|
||||
private readonly cameraOffset = new THREE.Vector3();
|
||||
private readonly keyState = new Set<string>();
|
||||
private readonly systemGroup = new THREE.Group();
|
||||
private readonly spatialNodeGroup = new THREE.Group();
|
||||
private readonly bubbleGroup = new THREE.Group();
|
||||
private readonly nodeGroup = new THREE.Group();
|
||||
private readonly stationGroup = new THREE.Group();
|
||||
private readonly claimGroup = new THREE.Group();
|
||||
private readonly constructionSiteGroup = new THREE.Group();
|
||||
private readonly shipGroup = new THREE.Group();
|
||||
private readonly ambienceGroup = new THREE.Group();
|
||||
|
||||
private readonly gamePanelEl: HTMLDivElement;
|
||||
private readonly selectableTargets = new Map<THREE.Object3D, Selectable>();
|
||||
private readonly presentationEntries: PresentationEntry[] = [];
|
||||
private readonly nodeVisuals = new Map<string, NodeVisual>();
|
||||
private readonly spatialNodeVisuals = new Map<string, SpatialNodeVisual>();
|
||||
private readonly bubbleVisuals = new Map<string, BubbleVisual>();
|
||||
|
||||
private readonly celestialVisuals = new Map<string, CelestialVisual>();
|
||||
private readonly stationVisuals = new Map<string, StructureVisual>();
|
||||
private readonly claimVisuals = new Map<string, ClaimVisual>();
|
||||
private readonly constructionSiteVisuals = new Map<string, ConstructionSiteVisual>();
|
||||
private readonly shipVisuals = new Map<string, ShipVisual>();
|
||||
private readonly systemVisuals = new Map<string, SystemVisual>();
|
||||
private readonly systemSummaryVisuals = new Map<string, SystemSummaryVisual>();
|
||||
private readonly planetVisuals: PlanetVisual[] = [];
|
||||
private readonly nodeVisuals = new Map<string, NodeVisual>();
|
||||
private readonly planetVisuals: any[] = [];
|
||||
private readonly orbitLines: OrbitLineVisual[] = [];
|
||||
|
||||
private readonly statusEl: HTMLDivElement;
|
||||
private readonly gameSummaryEl: HTMLSpanElement;
|
||||
private readonly systemPanelEl: HTMLDivElement;
|
||||
@@ -145,9 +108,9 @@ export class ViewerAppController {
|
||||
|
||||
private selectedItems: Selectable[] = [];
|
||||
private worldSignature = "";
|
||||
private zoomLevel: ZoomLevel = "system";
|
||||
private currentDistance = ZOOM_DISTANCE.system;
|
||||
private desiredDistance = ZOOM_DISTANCE.system;
|
||||
private povLevel: PovLevel = "system";
|
||||
private currentDistance = NAV_DISTANCE.system;
|
||||
private desiredDistance = NAV_DISTANCE.system;
|
||||
private orbitYaw = -2.3;
|
||||
private orbitPitch = 0.62;
|
||||
private cameraMode: CameraMode = "tactical";
|
||||
@@ -181,23 +144,7 @@ export class ViewerAppController {
|
||||
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
this.renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||
|
||||
this.scene.background = new THREE.Color(0x040912);
|
||||
this.scene.fog = new THREE.FogExp2(0x040912, 0.00011);
|
||||
this.scene.add(new THREE.AmbientLight(0x90a6c0, 0.55));
|
||||
const keyLight = new THREE.DirectionalLight(0xdcecff, 1.3);
|
||||
keyLight.position.set(1000, 1200, 800);
|
||||
this.scene.add(keyLight);
|
||||
this.scene.add(
|
||||
this.ambienceGroup,
|
||||
this.systemGroup,
|
||||
this.spatialNodeGroup,
|
||||
this.bubbleGroup,
|
||||
this.nodeGroup,
|
||||
this.stationGroup,
|
||||
this.claimGroup,
|
||||
this.constructionSiteGroup,
|
||||
this.shipGroup,
|
||||
);
|
||||
|
||||
const hud = createViewerHud(document);
|
||||
this.gamePanelEl = hud.gamePanelEl;
|
||||
this.statusEl = hud.statusEl;
|
||||
@@ -263,12 +210,11 @@ export class ViewerAppController {
|
||||
return this.sceneDataController.createWorldPresentationContext({
|
||||
world: this.world,
|
||||
activeSystemId: this.activeSystemId,
|
||||
zoomLevel: this.zoomLevel,
|
||||
povLevel: this.povLevel,
|
||||
orbitYaw: this.orbitYaw,
|
||||
camera: this.camera,
|
||||
systemFocusLocal: this.systemFocusLocal,
|
||||
toDisplayLocalPosition: this.toDisplayLocalPosition.bind(this),
|
||||
updateSystemDetailVisibility: () => this.navigationController.updateSystemDetailVisibility(),
|
||||
systemCamera: this.systemLayer.camera,
|
||||
systemAnchor: this.systemAnchor,
|
||||
toDisplayLocalPosition: (localPosition) => toDisplayLocalPosition(localPosition),
|
||||
setShellReticleOpacity: (sprite, opacity) => this.setShellReticleOpacity(sprite, opacity),
|
||||
});
|
||||
}
|
||||
@@ -285,8 +231,14 @@ export class ViewerAppController {
|
||||
renderFrame({
|
||||
clock: this.clock,
|
||||
renderer: this.renderer,
|
||||
scene: this.scene,
|
||||
camera: this.camera,
|
||||
universeScene: this.universeLayer.scene,
|
||||
galaxyScene: this.galaxyLayer.scene,
|
||||
galaxyCamera: this.galaxyLayer.camera,
|
||||
systemScene: this.systemLayer.scene,
|
||||
systemCamera: this.systemLayer.camera,
|
||||
localScene: this.localLayer.scene,
|
||||
localCamera: this.localLayer.camera,
|
||||
getPovLevel: () => this.povLevel,
|
||||
updateCamera: (delta) => this.updateCamera(delta),
|
||||
updateAmbience: (delta) => this.presentationController.updateAmbience(delta),
|
||||
updatePlanetPresentation: () => this.presentationController.updatePlanetPresentation(),
|
||||
@@ -298,10 +250,13 @@ export class ViewerAppController {
|
||||
});
|
||||
}
|
||||
|
||||
private updateAmbience(delta: number) {
|
||||
this.ambienceGroup.position.copy(this.camera.position);
|
||||
this.ambienceGroup.rotation.y += delta * 0.005;
|
||||
this.ambienceGroup.rotation.x = Math.sin(performance.now() * 0.00003) * 0.015;
|
||||
private computeOrbitOffset(): THREE.Vector3 {
|
||||
const horizontalDistance = this.currentDistance * Math.cos(this.orbitPitch);
|
||||
return new THREE.Vector3(
|
||||
Math.cos(this.orbitYaw) * horizontalDistance,
|
||||
this.currentDistance * Math.sin(this.orbitPitch),
|
||||
Math.sin(this.orbitYaw) * horizontalDistance,
|
||||
);
|
||||
}
|
||||
|
||||
private updateCamera(delta: number) {
|
||||
@@ -312,25 +267,38 @@ export class ViewerAppController {
|
||||
delta,
|
||||
});
|
||||
this.currentDistance = nextState.currentDistance;
|
||||
this.zoomLevel = nextState.zoomLevel;
|
||||
this.povLevel = nextState.povLevel;
|
||||
this.orbitPitch = nextState.orbitPitch;
|
||||
this.navigationController.updateActiveSystem();
|
||||
|
||||
if (this.cameraMode === "follow" && this.navigationController.updateFollowCamera(delta)) {
|
||||
// Follow camera directly controls systemLayer.camera in updateFollowCamera.
|
||||
// Still update galaxy camera independently.
|
||||
const orbitOffset = this.computeOrbitOffset();
|
||||
this.galaxyLayer.updateCamera(this.galaxyAnchor, orbitOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
this.updatePanFromKeyboard(delta);
|
||||
this.orbitPitch = THREE.MathUtils.clamp(this.orbitPitch, 0.18, 1.3);
|
||||
|
||||
const horizontalDistance = this.currentDistance * Math.cos(this.orbitPitch);
|
||||
const focus = this.navigationController.getCameraFocusWorldPosition();
|
||||
this.cameraOffset.set(
|
||||
Math.cos(this.orbitYaw) * horizontalDistance,
|
||||
this.currentDistance * Math.sin(this.orbitPitch),
|
||||
Math.sin(this.orbitYaw) * horizontalDistance,
|
||||
const orbitOffset = this.computeOrbitOffset();
|
||||
|
||||
this.galaxyLayer.updateCamera(this.galaxyAnchor, orbitOffset);
|
||||
|
||||
if (this.activeSystemId) {
|
||||
this.systemLayer.updateCamera(getSystemCameraFocus(this.systemAnchor), orbitOffset);
|
||||
}
|
||||
|
||||
this.localLayer.updateCamera(orbitOffset);
|
||||
|
||||
// Update star dot scales in galaxy scene
|
||||
updateSystemStarPresentation(
|
||||
this.systemVisuals,
|
||||
this.activeSystemId,
|
||||
this.galaxyLayer.camera,
|
||||
(sprite, opacity) => this.setShellReticleOpacity(sprite, opacity),
|
||||
);
|
||||
this.camera.position.copy(focus).add(this.cameraOffset);
|
||||
this.camera.lookAt(focus);
|
||||
}
|
||||
|
||||
private updatePanFromKeyboard(delta: number) {
|
||||
@@ -338,10 +306,10 @@ export class ViewerAppController {
|
||||
this.keyState,
|
||||
this.orbitYaw,
|
||||
this.currentDistance,
|
||||
this.zoomLevel,
|
||||
this.povLevel,
|
||||
this.activeSystemId,
|
||||
this.systemFocusLocal,
|
||||
this.galaxyFocus,
|
||||
this.systemAnchor,
|
||||
this.galaxyAnchor,
|
||||
delta,
|
||||
MIN_CAMERA_DISTANCE,
|
||||
MAX_CAMERA_DISTANCE,
|
||||
@@ -352,16 +320,6 @@ export class ViewerAppController {
|
||||
this.presentationController.updateSystemSummaries();
|
||||
}
|
||||
|
||||
private registerPresentation(
|
||||
detail: SceneNode,
|
||||
icon: SceneNode,
|
||||
hideDetailInUniverse: boolean,
|
||||
hideIconInUniverse = false,
|
||||
systemId?: string,
|
||||
) {
|
||||
this.presentationEntries.push({ detail, icon, systemId, hideDetailInUniverse, hideIconInUniverse });
|
||||
}
|
||||
|
||||
private renderRecentEvents(entityKind: string, entityId: string) {
|
||||
return this.presentationController.renderRecentEvents(entityKind, entityId);
|
||||
}
|
||||
@@ -378,14 +336,16 @@ export class ViewerAppController {
|
||||
this.interactionController.refreshHistoryWindows();
|
||||
}
|
||||
|
||||
private resolveFocusedBubbleId() {
|
||||
return resolveFocusedBubbleId(this.world, this.selectedItems);
|
||||
private resolveFocusedCelestialId() {
|
||||
return resolveFocusedCelestialId(this.world, this.selectedItems);
|
||||
}
|
||||
|
||||
private onResize = () => {
|
||||
resizeViewer({
|
||||
renderer: this.renderer,
|
||||
camera: this.camera,
|
||||
galaxyCamera: this.galaxyLayer.camera,
|
||||
systemCamera: this.systemLayer.camera,
|
||||
localCamera: this.localLayer.camera,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -398,7 +358,7 @@ export class ViewerAppController {
|
||||
}
|
||||
|
||||
private toDisplayLocalPosition(localPosition: THREE.Vector3, systemId?: string) {
|
||||
return this.navigationController.toDisplayLocalPosition(localPosition, systemId);
|
||||
return toDisplayLocalPosition(localPosition);
|
||||
}
|
||||
|
||||
private updateSystemPanel() {
|
||||
|
||||
Reference in New Issue
Block a user