3.9 KiB
(no submodules)
Purpose
The atmospheric module provides the two physics primitives every planetary renderer, sky-colour solver, and observation-quality estimator needs: the local density of an atmospheric column at altitude, and the relative path length light travels through that column for a given elevation angle. Both functions accept a PlanetConfig so the same code path serves Earth, Mars, Titan, or any other modelled body.
These primitives are intentionally pure and free of vendor coupling so they can be reused by @suaveplan/universe, @suaveplan/gaming-environment-lighting, server-side astronomical computations, and offline analysis without dragging a graphics runtime into the call site.
Features
atmosphericDensity(planet, altitudeMetres)— exponential isothermal scale-height model that returns0for airless bodiesairMass(altitudeDeg)— Kasten–Young 1989 air-mass approximation valid from zenith down to the horizon- Numerical-stable horizon handling: negative altitudes are clamped to
0so the formula remains finite at the bound - Pure functions: no I/O, no side effects, no global state — safe for
Workerthreads, SSR, and deterministic tests
Usage
Basic Example
import { atmosphericDensity } from "@suaveplan/geo-planet";
import type { PlanetConfig } from "@suaveplan/geo-planet";
const earth: PlanetConfig = {
name: "Earth",
mass: 5.972e24,
radius: 6_378_137,
polarRadius: 6_356_752,
surfaceGravity: 9.81,
rotationPeriod: 86_164,
axialTilt: 23.44,
atmosphere: {
surfacePressure: 101_325,
scaleHeight: 8_500,
rayleighCoefficient: 5.8e-6,
mieCoefficient: 21e-6,
extinctionPerAirMass: 0.13,
},
satellites: [],
};
const surface = atmosphericDensity(earth, 0); // 101325 Pa
const cruise = atmosphericDensity(earth, 11_000); // ~27 kPa
Advanced Example
import { airMass, atmosphericDensity } from "@suaveplan/geo-planet";
import type { PlanetConfig } from "@suaveplan/geo-planet";
function transmittance(
planet: PlanetConfig,
altitudeMetres: number,
sunElevationDeg: number,
): number {
if (!planet.atmosphere) return 1;
const density = atmosphericDensity(planet, altitudeMetres);
const am = airMass(sunElevationDeg);
const tau = planet.atmosphere.extinctionPerAirMass * am;
return Math.exp(-tau) * (density / planet.atmosphere.surfacePressure);
}
// Use at every camera step in a sky shader, or to score
// observability for ground-station pass scheduling.
API Reference
atmosphericDensity(planet: PlanetConfig, altitudeMetres: number): number
Returns the local pressure (Pa) using p₀ · exp(-h / H) where p₀ is planet.atmosphere.surfacePressure and H is planet.atmosphere.scaleHeight. Returns 0 when planet.atmosphere === null. No clamping is applied to altitude; negative altitudes (subterranean, sub-sea-level) yield pressures above p₀, which is the physically correct extrapolation for a barometric law.
airMass(altitudeDeg: number): number
Returns the Kasten–Young air mass:
1 / (sin(alt) + 0.50572 · (alt + 6.07995)^-1.6364)
with alt in degrees. Negative altitudes are clamped to 0 so the result remains finite at the horizon (returning ≈ 38). At zenith the return value approaches 1.
Implementation Notes
The density function uses the isothermal barometric equation rather than a layered ISA model because every consumer in this project treats density as a smooth analytic function for shader sampling. The Kasten–Young formulation was chosen over the simpler 1 / sin(alt) secant model because it remains stable down to the horizon and only diverges by ≈ 0.4% from full atmospheric ray-tracing above 5° elevation. Both functions are allocation-free hot-path code; consumers can call them once per pixel without GC pressure.