feat: 3 scene rendering setup

This commit is contained in:
2026-03-18 08:49:51 -04:00
parent 933c6afd08
commit 358122a74a
33 changed files with 1094 additions and 1132 deletions

View File

@@ -1,8 +1,7 @@
import * as THREE from "three";
import { ACTIVE_SYSTEM_DETAIL_SCALE, PROJECTED_GALAXY_RADIUS } from "./viewerConstants";
import { computeMoonLocalPosition, computePlanetLocalPosition, currentWorldTimeSeconds, scaleLocalVector } from "./viewerMath";
import { rawObject } from "./viewerScenePrimitives";
import type { PlanetVisual, ShipVisual, SystemSummaryVisual, SystemVisual, WorldState } from "./viewerTypes";
import type { PlanetVisual, ShipVisual, SystemVisual, WorldState } from "./viewerTypes";
export function getAnimatedShipLocalPosition(visual: ShipVisual, now = performance.now()) {
const elapsedMs = now - visual.receivedAtMs;
@@ -26,23 +25,17 @@ export function resolveShipHeading(visual: ShipVisual, worldPosition: THREE.Vect
export function updatePlanetPresentation(
world: WorldState | undefined,
worldTimeSyncMs: number,
activeSystemId: string | undefined,
systemFocusLocal: THREE.Vector3,
planetVisuals: PlanetVisual[],
) {
const nowSeconds = currentWorldTimeSeconds(world, worldTimeSyncMs);
// In systemScene all positions use scaleLocalVector * ACTIVE_SYSTEM_DETAIL_SCALE.
// Star is always at origin (0,0,0); orbits are centered there.
for (const visual of planetVisuals) {
const scale = visual.systemId === activeSystemId ? ACTIVE_SYSTEM_DETAIL_SCALE : 1;
const localPosition = scaleLocalVector(computePlanetLocalPosition(visual.planet, nowSeconds));
const orbitOffset = visual.systemId === activeSystemId
? systemFocusLocal.clone().multiplyScalar(-scale)
: new THREE.Vector3();
const position = visual.systemId === activeSystemId
? localPosition.clone().sub(systemFocusLocal).multiplyScalar(scale)
: localPosition.multiplyScalar(scale);
const position = scaleLocalVector(computePlanetLocalPosition(visual.planet, nowSeconds))
.multiplyScalar(ACTIVE_SYSTEM_DETAIL_SCALE);
visual.orbit.setScaleScalar(scale);
visual.orbit.setPosition(orbitOffset);
visual.orbit.setScaleScalar(ACTIVE_SYSTEM_DETAIL_SCALE);
visual.orbit.setPosition(new THREE.Vector3(0, 0, 0));
visual.mesh.setPosition(position);
visual.icon.setPosition(position);
if (visual.ring) {
@@ -51,56 +44,45 @@ export function updatePlanetPresentation(
for (const [moonIndex, moon] of visual.moons.entries()) {
moon.orbit.setPosition(position);
moon.orbit.setScaleScalar(scale);
moon.orbit.setScaleScalar(ACTIVE_SYSTEM_DETAIL_SCALE);
moon.mesh.setPosition(
position.clone().add(
scaleLocalVector(computeMoonLocalPosition(visual.planet, moonIndex, nowSeconds, world?.seed ?? 1)).multiplyScalar(scale),
scaleLocalVector(computeMoonLocalPosition(visual.planet, moonIndex, nowSeconds, world?.seed ?? 1))
.multiplyScalar(ACTIVE_SYSTEM_DETAIL_SCALE),
),
);
}
}
}
export function updateSystemSummaryPresentation(
systemSummaryVisuals: Map<string, SystemSummaryVisual>,
camera: THREE.PerspectiveCamera,
activeSystemId?: string,
) {
const distanceScale = activeSystemId ? 0.05 : 0.085;
for (const [systemId, visual] of systemSummaryVisuals.entries()) {
const worldPosition = visual.sprite.getWorldPosition(new THREE.Vector3());
const distance = camera.position.distanceTo(worldPosition);
const minimumScale = activeSystemId && systemId !== activeSystemId ? 1200 : 1400;
const scale = Math.max(minimumScale, distance * distanceScale);
rawObject(visual.sprite).scale.set(scale, scale * 0.3125, 1);
}
}
export function updateSystemStarPresentation(
systemVisuals: Map<string, SystemVisual>,
activeSystemId: string | undefined,
systemFocusLocal: THREE.Vector3,
camera: THREE.PerspectiveCamera,
galaxyCamera: THREE.PerspectiveCamera,
setShellReticleOpacity: (sprite: SystemVisual["shellReticle"], opacity: number) => void,
) {
const activeSystem = activeSystemId ? systemVisuals.get(activeSystemId) : undefined;
for (const [systemId, visual] of systemVisuals.entries()) {
visual.root.setPosition(visual.galaxyPosition);
// galaxyRoot is always at the galaxy position of this system
visual.galaxyRoot.setPosition(visual.galaxyPosition);
visual.shellReticle.setScaleScalar(visual.shellReticleBaseScale);
if (!activeSystem) {
visual.starCluster.setPosition(new THREE.Vector3(0, 0, 0));
// Galaxy view: show star dot, hide shell reticle
visual.icon.setPosition(new THREE.Vector3(0, 0, 0));
visual.icon.setVisible(true);
visual.shellReticle.setPosition(new THREE.Vector3(0, 0, 0));
visual.shellReticle.setVisible(false);
setShellReticleOpacity(visual.shellReticle, 0);
const dotWorldPos = visual.icon.getWorldPosition(new THREE.Vector3());
visual.icon.setScaleScalar(galaxyCamera.position.distanceTo(dotWorldPos) * 0.01);
continue;
}
if (systemId !== activeSystemId) {
visual.starCluster.setPosition(new THREE.Vector3(0, 0, 0));
// Other systems in galaxy view while a system is active: show shell reticle projected to edge
visual.icon.setPosition(new THREE.Vector3(0, 0, 0));
visual.icon.setVisible(false);
visual.shellReticle.setPosition(new THREE.Vector3(0, 0, 0));
@@ -108,20 +90,19 @@ export function updateSystemStarPresentation(
setShellReticleOpacity(visual.shellReticle, 1);
const direction = visual.galaxyPosition.clone().sub(activeSystem.galaxyPosition);
if (direction.lengthSq() > 0.0001) {
visual.root.setPosition(
visual.galaxyRoot.setPosition(
activeSystem.galaxyPosition.clone().add(direction.normalize().multiplyScalar(PROJECTED_GALAXY_RADIUS)),
);
}
const reticleWorldPosition = visual.root.getWorldPosition(new THREE.Vector3());
const reticleDistance = camera.position.distanceTo(reticleWorldPosition);
const reticleWorldPosition = visual.galaxyRoot.getWorldPosition(new THREE.Vector3());
const reticleDistance = galaxyCamera.position.distanceTo(reticleWorldPosition);
const reticleScale = Math.max(900, reticleDistance * 0.032);
visual.shellReticle.setScaleScalar(reticleScale);
continue;
}
const offset = systemFocusLocal.clone().multiplyScalar(-ACTIVE_SYSTEM_DETAIL_SCALE);
visual.starCluster.setPosition(offset);
visual.icon.setPosition(offset);
// Active system in galaxy view: show star dot, hide shell reticle
visual.icon.setPosition(new THREE.Vector3(0, 0, 0));
visual.icon.setVisible(true);
visual.shellReticle.setVisible(false);
setShellReticleOpacity(visual.shellReticle, 0);