deadhead-space-freight-tycoon/apps/web/src/map/projection.ts

58 lines
2.1 KiB
TypeScript

import type { Location } from "@deadhead/content";
import { content } from "@deadhead/content";
import { locationPosition, type Position } from "@deadhead/sim";
/**
* Map coordinates: the Sun at (0, 0), the edge of the map at radius 1, y down.
* Real distances are compressed so the inner planets and Jupiter fit together.
*/
export interface MapPoint {
x: number;
y: number;
}
/** Heliocentric radius (million km) shown at the edge of the map. */
export const MAP_EDGE_MILLION_KM = 1000;
/** Moons are drawn this far from their planet (map units), whatever the real distance. */
export const MOON_MAP_OFFSET = 0.04;
/**
* Compressed radial scale: real distance from the Sun (million km) to map
* radius. Square root keeps the order (farther stays farther) while bringing
* Jupiter (~780M km) within a few times Earth's (~150M km) distance.
*/
export function radialScale(millionKm: number): number {
return Math.sqrt(Math.max(0, millionKm) / MAP_EDGE_MILLION_KM);
}
/** A heliocentric position on the map: same angle, compressed radius. */
export function toMapPoint(position: Position): MapPoint {
const r = Math.hypot(position.x, position.y);
if (r === 0) return { x: 0, y: 0 };
const k = radialScale(r) / r;
// Screens grow downwards; flip y so orbits run counter-clockwise as seen from above.
return { x: position.x * k, y: -position.y * k };
}
/**
* Where a location is drawn. A child body (the Moon) sits a fixed small
* distance from its parent in its true direction, since at map scale it would
* overlap it.
*/
export function locationMapPoint(location: Location): MapPoint {
const parentId = location.orbitParent?.locationId;
const parent = parentId ? content.location(parentId) : undefined;
if (!parent) return toMapPoint(locationPosition(location));
const earth = locationPosition(parent);
const moon = locationPosition(location);
const dx = moon.x - earth.x;
const dy = -(moon.y - earth.y);
const length = Math.hypot(dx, dy) || 1;
const base = toMapPoint(earth);
return {
x: base.x + (dx / length) * MOON_MAP_OFFSET,
y: base.y + (dy / length) * MOON_MAP_OFFSET,
};
}