import type { AstronomyBody, Location } from "@deadhead/content"; import { Body, HelioVector, KM_PER_AU, RotateVector, Rotation_EQJ_ECL } from "astronomy-engine"; import type { GameTime } from "./clock"; /** A heliocentric point in the ecliptic plane, in million km. */ export interface Position { x: number; y: number; } const MILLION_KM_PER_AU = KM_PER_AU / 1e6; const MS_PER_DAY = 86_400_000; const J2000 = Date.UTC(2000, 0, 1, 12); /** Fixed Earth→Moon offset (mean lunar distance); the Moon's orbit is ignored. */ const MOON_OFFSET_MILLION_KM = 0.384; /** * Ceres is not in astronomy-engine, so it follows a circular orbit with its * real mean radius and period, starting from its approximate J2000 mean longitude. */ const CERES_RADIUS_AU = 2.7675; const CERES_PERIOD_DAYS = 1680.5; const CERES_J2000_LONGITUDE_DEG = 160; const eqjToEcliptic = Rotation_EQJ_ECL(); function planetPosition(body: Body, gameTime: GameTime): Position { const ecliptic = RotateVector(eqjToEcliptic, HelioVector(body, new Date(gameTime))); return { x: ecliptic.x * MILLION_KM_PER_AU, y: ecliptic.y * MILLION_KM_PER_AU }; } function ceresPosition(gameTime: GameTime): Position { const days = (gameTime - J2000) / MS_PER_DAY; const angle = ((CERES_J2000_LONGITUDE_DEG + (360 * days) / CERES_PERIOD_DAYS) * Math.PI) / 180; const radius = CERES_RADIUS_AU * MILLION_KM_PER_AU; return { x: radius * Math.cos(angle), y: radius * Math.sin(angle) }; } export function bodyPosition(body: AstronomyBody, gameTime: GameTime): Position { switch (body) { case "earth": return planetPosition(Body.Earth, gameTime); case "moon": { const earth = planetPosition(Body.Earth, gameTime); return { x: earth.x + MOON_OFFSET_MILLION_KM, y: earth.y }; } case "mars": return planetPosition(Body.Mars, gameTime); case "ceres": return ceresPosition(gameTime); case "jupiter": return planetPosition(Body.Jupiter, gameTime); } } /** A location's 2D position at a game time, in million km from the Sun. */ export function locationPosition(location: Location, gameTime: GameTime): Position { return bodyPosition(location.body, gameTime); }