#![forbid(unsafe_code)]
#![deny(missing_docs)]
pub use astrodyn_quantities::prelude::*;
pub mod exponential;
pub mod met;
use core::marker::PhantomData;
use glam::DVec3;
use uom::si::f64::{MassDensity, Pressure, ThermodynamicTemperature};
use uom::si::mass_density::kilogram_per_cubic_meter;
use uom::si::pressure::pascal;
use uom::si::thermodynamic_temperature::kelvin;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AtmosphereState<P: Planet> {
pub density: f64,
pub temperature: f64,
pub pressure: f64,
pub wind: Velocity<PlanetInertial<P>>,
_p: PhantomData<P>,
}
impl<P: Planet> Default for AtmosphereState<P> {
fn default() -> Self {
Self {
density: 0.0,
temperature: 0.0,
pressure: 0.0,
wind: Velocity::<PlanetInertial<P>>::from_raw_si(DVec3::ZERO),
_p: PhantomData,
}
}
}
impl<P: Planet> AtmosphereState<P> {
#[inline]
pub const fn new(
density: f64,
temperature: f64,
pressure: f64,
wind: Velocity<PlanetInertial<P>>,
) -> Self {
Self {
density,
temperature,
pressure,
wind,
_p: PhantomData,
}
}
#[inline]
pub fn from_raw(density: f64, temperature: f64, pressure: f64, wind_raw: DVec3) -> Self {
Self {
density,
temperature,
pressure,
wind: Velocity::<PlanetInertial<P>>::from_raw_si(wind_raw),
_p: PhantomData,
}
}
#[inline]
pub fn density_typed(&self) -> MassDensity {
MassDensity::new::<kilogram_per_cubic_meter>(self.density)
}
#[inline]
pub fn temperature_typed(&self) -> ThermodynamicTemperature {
ThermodynamicTemperature::new::<kelvin>(self.temperature)
}
#[inline]
pub fn pressure_typed(&self) -> Pressure {
Pressure::new::<pascal>(self.pressure)
}
}
impl AtmosphereState<SelfPlanet> {
#[inline]
pub fn relabel<Q: Planet>(self) -> AtmosphereState<Q> {
AtmosphereState::<Q> {
density: self.density,
temperature: self.temperature,
pressure: self.pressure,
wind: Velocity::<PlanetInertial<Q>>::from_raw_si(self.wind.raw_si()),
_p: PhantomData,
}
}
}
pub fn compute_corotation_wind(omega: f64, inertial_pos: DVec3) -> DVec3 {
DVec3::new(-omega * inertial_pos.y, omega * inertial_pos.x, 0.0)
}
#[inline]
pub fn compute_corotation_wind_typed<P: Planet>(
omega: uom::si::f64::AngularVelocity,
pos: Position<PlanetInertial<P>>,
) -> Velocity<PlanetInertial<P>> {
use uom::si::angular_velocity::radian_per_second;
let w = omega.get::<radian_per_second>();
let p = pos.raw_si();
compute_corotation_wind(w, p).m_per_s_at::<PlanetInertial<P>>()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn corotation_wind_at_equator() {
let omega = 7.292115146706388e-5;
let r = 6_778_137.0; let pos = DVec3::new(r, 0.0, 0.0);
let wind = compute_corotation_wind(omega, pos);
assert!(wind.x.abs() < 1e-10);
assert!((wind.y - omega * r).abs() < 1e-6);
assert!(wind.z.abs() < 1e-10);
assert!(wind.length() > 400.0 && wind.length() < 600.0);
}
#[test]
fn corotation_wind_at_pole() {
let omega = 7.292115146706388e-5;
let pos = DVec3::new(0.0, 0.0, 6_778_137.0);
let wind = compute_corotation_wind(omega, pos);
assert!(wind.length() < 1e-10);
}
#[test]
fn corotation_wind_zero_omega() {
let pos = DVec3::new(7e6, 3e6, 1e6);
let wind = compute_corotation_wind(0.0, pos);
assert_eq!(wind, DVec3::ZERO);
}
#[test]
fn typed_accessors_roundtrip_bit_identical() {
let state = AtmosphereState::<Earth>::from_raw(
1.225e-12,
288.15,
2.537e-10,
DVec3::new(-359.7, 123.4, 0.5),
);
assert_eq!(
state.density_typed().get::<kilogram_per_cubic_meter>(),
state.density
);
assert_eq!(state.temperature_typed().get::<kelvin>(), state.temperature);
assert_eq!(state.pressure_typed().get::<pascal>(), state.pressure);
assert_eq!(state.wind.raw_si(), DVec3::new(-359.7, 123.4, 0.5));
}
#[test]
fn typed_accessors_default_state() {
let state = AtmosphereState::<Earth>::default();
assert_eq!(state.density_typed().get::<kilogram_per_cubic_meter>(), 0.0);
assert_eq!(state.temperature_typed().get::<kelvin>(), 0.0);
assert_eq!(state.pressure_typed().get::<pascal>(), 0.0);
assert_eq!(state.wind.raw_si(), DVec3::ZERO);
}
#[test]
fn corotation_wind_typed_matches_raw() {
let omega_raw = 7.292115146706388e-5;
let pos_raw = DVec3::new(7.0e6, 3.0e6, 1.0e6);
let typed_out = compute_corotation_wind_typed::<Earth>(
omega_raw.rad_per_s(),
pos_raw.m_at::<PlanetInertial<Earth>>(),
);
let raw_out = compute_corotation_wind(omega_raw, pos_raw);
assert_eq!(typed_out.raw_si(), raw_out);
}
#[test]
fn corotation_wind_typed_zero_omega() {
let pos = DVec3::new(7e6, 3e6, 1e6).m_at::<PlanetInertial<Earth>>();
let wind = compute_corotation_wind_typed::<Earth>(0.0.rad_per_s(), pos);
assert_eq!(wind.raw_si(), DVec3::ZERO);
}
#[test]
fn relabel_self_planet_to_earth() {
let state = AtmosphereState::<SelfPlanet>::from_raw(
1.0e-12,
300.0,
1.0e-10,
DVec3::new(10.0, 20.0, 0.0),
);
let earth = state.relabel::<Earth>();
assert_eq!(earth.density, 1.0e-12);
assert_eq!(earth.temperature, 300.0);
assert_eq!(earth.pressure, 1.0e-10);
assert_eq!(earth.wind.raw_si(), DVec3::new(10.0, 20.0, 0.0));
}
}