SPB Git forge
3commits 1branches 0releases
417.0 KBsize
maindefault branch
10 days agolast push
TypeScript 66.5% Python 30.9% JavaScript 1.4% CSS 0.7%
4.1 KB · 106 lines typescript
Raw Blame History
1/**2 * Geometry helpers shared by the globe: geodetic → unit-sphere coordinates (Three.js is Y-up), the compressed3 * altitude scale used for legibility, sun direction for the day/night terminator and CSS-token colour lookup.4 *5 * Altitude scale (documented in the UI tooltip): r = 1 + 0.06 + ln(1 + alt/400) × 0.12 with alt in km.6 * LEO (400 km) → 1.143 · 1 000 km → 1.21 · MEO (20 000 km) → 1.53 · GEO (35 786 km) → 1.60. It exaggerates low7 * shells so that the LEO swarm does not sit on the surface, and compresses MEO/GEO so the GEO ring stays on screen.8 */9import type { Vector3 } from 'three';1011export const EARTH_RADIUS_KM = 6371;12export const DEG = Math.PI / 180;1314export function altToRadius(altKm: number): number {15  const a = Math.max(0, altKm);16  return 1 + 0.06 + Math.log1p(a / 400) * 0.12;17}1819/** lon/lat (deg) at radius r → xyz (Y-up, prime meridian at +X, east positive). Writes into `out` (Float32Array|number[]). */20export function llaToXyz(latDeg: number, lonDeg: number, r: number, out: Float32Array | number[], off = 0): void {21  const lat = latDeg * DEG;22  const lon = lonDeg * DEG;23  const c = Math.cos(lat);24  out[off] = r * c * Math.cos(lon);25  out[off + 1] = r * Math.sin(lat);26  out[off + 2] = -r * c * Math.sin(lon);27}2829export function llaToVec(latDeg: number, lonDeg: number, r: number, v: Vector3): Vector3 {30  const lat = latDeg * DEG;31  const lon = lonDeg * DEG;32  const c = Math.cos(lat);33  return v.set(r * c * Math.cos(lon), r * Math.sin(lat), -r * c * Math.sin(lon));34}3536/**37 * Sub-solar point (approximate, ±0.5°): low-precision solar coordinates (Astronomical Almanac) + GMST.38 * Returns a unit vector in the same Y-up frame as `llaToXyz` — cheap enough to recompute every frame.39 */40export function sunDirection(date: Date, out: Vector3): Vector3 {41  const jd = date.getTime() / 86400000 + 2440587.5;42  const d = jd - 2451545.0;43  const g = ((357.529 + 0.98560028 * d) % 360) * DEG;44  const q = (280.459 + 0.98564736 * d) % 360;45  const L = (q + 1.915 * Math.sin(g) + 0.02 * Math.sin(2 * g)) * DEG;46  const e = (23.439 - 0.00000036 * d) * DEG;47  const ra = Math.atan2(Math.cos(e) * Math.sin(L), Math.cos(L));48  const dec = Math.asin(Math.sin(e) * Math.sin(L));49  const gmst = ((280.46061837 + 360.98564736629 * d) % 360) * DEG;50  const lon = ra - gmst; // sub-solar longitude (rad)51  const c = Math.cos(dec);52  return out.set(c * Math.cos(lon), Math.sin(dec), -c * Math.sin(lon)).normalize();53}5455/** Resolve a CSS custom property (design token) to a colour string usable by Three. */56export function cssVar(name: string, fallback: string): string {57  if (typeof window === 'undefined') return fallback;58  const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();59  return v || fallback;60}6162export const TOKEN_FALLBACKS: Record<string, string> = {63  '--leo': '#38d3ff',64  '--meo': '#8f7dff',65  '--geo': '#f5b544',66  '--heo': '#ff7ab6',67  '--other': '#7c869e',68  '--accent': '#38d3ff',69  '--accent-2': '#8f7dff',70  '--ink-3': '#6b7694',71  '--rule-strong': 'rgba(160, 180, 230, 0.28)',72  '--plane': '#0b1020',73  '--plane-2': '#111830',74  '--space': '#060912',75};7677export function token(name: keyof typeof TOKEN_FALLBACKS): string {78  return cssVar(name, TOKEN_FALLBACKS[name] ?? '#ffffff');79}8081/** Detect WebGL support without creating a persistent context (headless / old GPUs / blocked GL). */82export function hasWebGL(): boolean {83  if (typeof window === 'undefined') return false;84  try {85    const c = document.createElement('canvas');86    const gl = c.getContext('webgl2') ?? c.getContext('webgl');87    if (!gl) return false;88    const ext = gl.getExtension('WEBGL_lose_context');89    ext?.loseContext();90    return true;91  } catch {92    return false;93  }94}9596export function prefersReducedMotion(): boolean {97  return typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;98}99100/** Low-power heuristic: cap rendered points on small screens / few cores. */101export function isLowPower(): boolean {102  if (typeof window === 'undefined') return false;103  const cores = navigator.hardwareConcurrency ?? 8;104  return cores <= 4 || window.innerWidth < 768;105}106