141 lines
6.4 KiB
TypeScript
141 lines
6.4 KiB
TypeScript
import * as THREE from "three";
|
|
import { ACTIVE_SYSTEM_DETAIL_SCALE, PROJECTED_GALAXY_RADIUS } from "./viewerConstants";
|
|
import { computeMoonLocalPosition, computePlanetLocalPosition, currentWorldTimeSeconds, scaleLocalVector } from "./viewerMath";
|
|
import type { PlanetVisual, ShipVisual, SystemVisual, WorldState } from "./viewerTypes";
|
|
import { rawObject } from "./viewerScenePrimitives";
|
|
|
|
export const MIN_ICON_PIXELS = 25;
|
|
export const MAX_ICON_PIXELS = 50;
|
|
|
|
export function iconWorldScale(distToCamera: number, camera: THREE.PerspectiveCamera, pixels: number): number {
|
|
return pixels * distToCamera * 2 * Math.tan((camera.fov * Math.PI / 180) / 2) / window.innerHeight;
|
|
}
|
|
|
|
export function getAnimatedShipLocalPosition(visual: ShipVisual, now = performance.now()) {
|
|
const elapsedMs = now - visual.receivedAtMs;
|
|
const blendT = THREE.MathUtils.clamp(elapsedMs / visual.blendDurationMs, 0, 1);
|
|
return new THREE.Vector3().lerpVectors(visual.startPosition, visual.authoritativePosition, blendT);
|
|
}
|
|
|
|
export function resolveShipHeading(visual: ShipVisual, worldPosition: THREE.Vector3, orbitYaw: number) {
|
|
const desiredHeading = visual.targetPosition.clone().sub(worldPosition);
|
|
if (desiredHeading.lengthSq() > 0.01) {
|
|
return desiredHeading;
|
|
}
|
|
|
|
if (visual.velocity.lengthSq() > 0.01) {
|
|
return visual.velocity.clone();
|
|
}
|
|
|
|
return new THREE.Vector3(Math.cos(orbitYaw), 0, Math.sin(orbitYaw));
|
|
}
|
|
|
|
export function updatePlanetPresentation(
|
|
world: WorldState | undefined,
|
|
worldTimeSyncMs: number,
|
|
planetVisuals: PlanetVisual[],
|
|
systemCamera: THREE.PerspectiveCamera,
|
|
) {
|
|
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 position = scaleLocalVector(computePlanetLocalPosition(visual.planet, nowSeconds))
|
|
.multiplyScalar(ACTIVE_SYSTEM_DETAIL_SCALE);
|
|
|
|
visual.mesh.setPosition(position);
|
|
visual.icon.setPosition(position);
|
|
const iconWorldPos = visual.icon.getWorldPosition(new THREE.Vector3());
|
|
const distToIcon = systemCamera.position.distanceTo(iconWorldPos);
|
|
const t = THREE.MathUtils.clamp(distToIcon / 300, 0, 1);
|
|
const rawScale = visual.iconBaseScale * t * Math.sqrt(t);
|
|
const planetIconScale = THREE.MathUtils.clamp(rawScale, iconWorldScale(distToIcon, systemCamera, MIN_ICON_PIXELS), iconWorldScale(distToIcon, systemCamera, MAX_ICON_PIXELS));
|
|
visual.icon.setScaleScalar(planetIconScale);
|
|
if (visual.ring) {
|
|
visual.ring.setPosition(position);
|
|
}
|
|
|
|
const distToPlanet = systemCamera.position.distanceTo(position);
|
|
const moonOrbitOpacity = THREE.MathUtils.clamp(1 - distToPlanet / 500, 0, 1) * 0.18;
|
|
|
|
const clusterVisible = distToPlanet < 300;
|
|
for (const [moonIndex, moon] of visual.moons.entries()) {
|
|
const moonPos = position.clone().add(
|
|
scaleLocalVector(computeMoonLocalPosition(visual.planet.moons[moonIndex], nowSeconds))
|
|
.multiplyScalar(ACTIVE_SYSTEM_DETAIL_SCALE),
|
|
);
|
|
moon.mesh.setPosition(moonPos);
|
|
moon.mesh.setVisible(clusterVisible);
|
|
moon.icon.setPosition(moonPos);
|
|
moon.icon.setVisible(clusterVisible);
|
|
if (clusterVisible) {
|
|
const iconWorldPos = moon.icon.getWorldPosition(new THREE.Vector3());
|
|
const moonDist = systemCamera.position.distanceTo(iconWorldPos);
|
|
const t = THREE.MathUtils.clamp(moonDist / 120, 0, 1);
|
|
const rawMoonScale = moon.iconBaseScale * t * Math.sqrt(t);
|
|
const moonIconScale = THREE.MathUtils.clamp(rawMoonScale, iconWorldScale(moonDist, systemCamera, MIN_ICON_PIXELS), iconWorldScale(moonDist, systemCamera, MAX_ICON_PIXELS));
|
|
moon.icon.setScaleScalar(moonIconScale);
|
|
}
|
|
moon.orbit.setPosition(position);
|
|
const orbitObj = rawObject(moon.orbit);
|
|
if (orbitObj instanceof THREE.LineLoop) {
|
|
(orbitObj.material as THREE.LineBasicMaterial).opacity = moonOrbitOpacity;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
export function updateSystemStarPresentation(
|
|
systemVisuals: Map<string, SystemVisual>,
|
|
activeSystemId: string | undefined,
|
|
galaxyCamera: THREE.PerspectiveCamera,
|
|
setShellReticleOpacity: (sprite: SystemVisual["shellReticle"], opacity: number) => void,
|
|
) {
|
|
const activeSystem = activeSystemId ? systemVisuals.get(activeSystemId) : undefined;
|
|
|
|
for (const [systemId, visual] of systemVisuals.entries()) {
|
|
// galaxyRoot is always at the galaxy position of this system
|
|
visual.galaxyRoot.setPosition(visual.galaxyPosition);
|
|
visual.shellReticle.setScaleScalar(visual.shellReticleBaseScale);
|
|
|
|
if (!activeSystem) {
|
|
// 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) {
|
|
// 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));
|
|
visual.shellReticle.setVisible(true);
|
|
setShellReticleOpacity(visual.shellReticle, 1);
|
|
const direction = visual.galaxyPosition.clone().sub(activeSystem.galaxyPosition);
|
|
if (direction.lengthSq() > 0.0001) {
|
|
visual.galaxyRoot.setPosition(
|
|
activeSystem.galaxyPosition.clone().add(direction.normalize().multiplyScalar(PROJECTED_GALAXY_RADIUS)),
|
|
);
|
|
}
|
|
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;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|