import * as THREE from "three"; import { classifyPovLevel } from "./viewerMath"; import type { PovLevel } from "./viewerTypes"; import type { UniverseLayer } from "./viewerUniverseLayer"; import type { GalaxyLayer } from "./viewerGalaxyLayer"; import type { SystemLayer } from "./viewerSystemLayer"; import type { LocalLayer } from "./viewerLocalLayer"; export interface RenderFrameParams { clock: THREE.Clock; renderer: THREE.WebGLRenderer; universeLayer: UniverseLayer; galaxyLayer: GalaxyLayer; systemLayer: SystemLayer; localLayer: LocalLayer; getPovLevel: () => PovLevel; updateCamera: (delta: number) => void; updateAmbience: (delta: number) => void; updatePlanetPresentation: () => void; updateShipPresentation: () => void; updateNetworkPanel: () => void; applyZoomPresentation: () => void; recordPerformanceStats: (frameMs: number) => void; updatePerformancePanel: () => void; } export interface ResizeParams { galaxyLayer: GalaxyLayer; systemLayer: SystemLayer; localLayer: LocalLayer; width: number; height: number; } export interface CameraStepParams { currentDistance: number; desiredDistance: number; orbitPitch: number; delta: number; } export function renderFrame(params: RenderFrameParams) { const frameStartedAtMs = performance.now(); const delta = Math.min(params.clock.getDelta(), 0.033); params.updateCamera(delta); params.updateAmbience(delta); params.updatePlanetPresentation(); params.updateShipPresentation(); params.updateNetworkPanel(); params.applyZoomPresentation(); const povLevel = params.getPovLevel(); const activeCamera = povLevel === "galaxy" ? params.galaxyLayer.camera : params.systemLayer.camera; params.renderer.autoClear = false; params.renderer.clear(); // Universe backdrop — always first, rendered with the active camera so it aligns with the foreground params.universeLayer.render(params.renderer, activeCamera); params.renderer.clearDepth(); if (povLevel === "galaxy") { // Galaxy map on top of universe backdrop params.galaxyLayer.render(params.renderer); } else if (povLevel === "system") { params.systemLayer.render(params.renderer); } else { // local: system as mid-ground backdrop, then local on top params.systemLayer.render(params.renderer); params.renderer.clearDepth(); params.localLayer.render(params.renderer); } params.recordPerformanceStats(performance.now() - frameStartedAtMs); params.updatePerformancePanel(); } export function resizeViewer(params: ResizeParams) { const aspect = params.width / params.height; params.galaxyLayer.onResize(aspect); params.systemLayer.onResize(aspect); params.localLayer.onResize(aspect); } export function stepCamera(params: CameraStepParams) { const currentDistance = THREE.MathUtils.damp(params.currentDistance, params.desiredDistance, 7.5, params.delta); const povLevel = classifyPovLevel(currentDistance); const orbitPitch = THREE.MathUtils.clamp(params.orbitPitch, 0.18, 1.3); return { currentDistance, povLevel, orbitPitch }; }