use crate::components::AtmosphereState;
use avian3d::math::Scalar;
const T0: Scalar = 288.15;
const P0: Scalar = 101_325.0;
const L: Scalar = 0.006_5;
const H_TROP: Scalar = 11_000.0;
const T_TROP: Scalar = 216.65;
const R_AIR: Scalar = 287.052_87;
const G: Scalar = 9.806_65;
const GAMMA: Scalar = 1.4;
const TROP_EXPONENT: Scalar = G / (R_AIR * L);
const T_REF_SUTH: Scalar = 273.15;
const MU_REF_SUTH: Scalar = 1.716e-5;
const S_SUTH: Scalar = 110.4;
pub fn isa_temperature(h: Scalar) -> Scalar {
if h <= H_TROP {
T0 - L * h
} else {
T_TROP
}
}
pub fn isa_pressure(h: Scalar) -> Scalar {
if h <= H_TROP {
let t = isa_temperature(h);
P0 * (t / T0).powf(TROP_EXPONENT)
} else {
let p_trop = P0 * (T_TROP / T0).powf(TROP_EXPONENT);
p_trop * ((-G * (h - H_TROP)) / (R_AIR * T_TROP)).exp()
}
}
#[cfg(test)]
pub(crate) fn isa_density(h: Scalar) -> Scalar {
let t = isa_temperature(h);
let p = isa_pressure(h);
p / (R_AIR * t)
}
pub fn speed_of_sound(t: Scalar) -> Scalar {
(GAMMA * R_AIR * t).sqrt()
}
pub fn sutherland_viscosity(t: Scalar) -> Scalar {
MU_REF_SUTH * (t / T_REF_SUTH).powf(1.5) * (T_REF_SUTH + S_SUTH) / (t + S_SUTH)
}
pub fn atmosphere_at(h: Scalar) -> AtmosphereState {
let temperature_k = isa_temperature(h);
let pressure_pa = isa_pressure(h);
let density_kgm3 = pressure_pa / (R_AIR * temperature_k);
let speed_of_sound_ms = speed_of_sound(temperature_k);
AtmosphereState {
density_kgm3,
pressure_pa,
temperature_k,
speed_of_sound_ms,
}
}
#[cfg(test)]
mod tests {
use super::*;
const DENS_TOL: Scalar = 0.001;
const PRES_TOL: Scalar = 0.005;
const TEMP_TOL: Scalar = 0.001;
fn pct_err(got: Scalar, expected: Scalar) -> Scalar {
(got - expected).abs() / expected
}
#[test]
fn isa_sea_level() {
let atm = atmosphere_at(0.0);
assert!(
pct_err(atm.temperature_k, 288.15) < TEMP_TOL,
"T₀ {}",
atm.temperature_k
);
assert!(
pct_err(atm.pressure_pa, 101_325.0) < PRES_TOL,
"p₀ {}",
atm.pressure_pa
);
assert!(
pct_err(atm.density_kgm3, 1.2250) < DENS_TOL,
"ρ₀ {}",
atm.density_kgm3
);
}
#[test]
fn isa_1000m() {
let atm = atmosphere_at(1_000.0);
assert!(pct_err(atm.temperature_k, 281.65) < TEMP_TOL);
assert!(pct_err(atm.pressure_pa, 89_874.0) < PRES_TOL);
assert!(pct_err(atm.density_kgm3, 1.1117) < DENS_TOL);
}
#[test]
fn isa_5000m() {
let atm = atmosphere_at(5_000.0);
assert!(pct_err(atm.temperature_k, 255.65) < TEMP_TOL);
assert!(pct_err(atm.pressure_pa, 54_048.0) < PRES_TOL);
assert!(pct_err(atm.density_kgm3, 0.7364) < DENS_TOL);
}
#[test]
fn isa_11000m_tropopause() {
let atm = atmosphere_at(11_000.0);
assert!(pct_err(atm.temperature_k, 216.65) < TEMP_TOL);
assert!(pct_err(atm.pressure_pa, 22_632.0) < PRES_TOL);
assert!(pct_err(atm.density_kgm3, 0.3639) < DENS_TOL);
}
#[test]
fn isa_stratosphere_is_isothermal() {
assert!((isa_temperature(15_000.0) - 216.65).abs() < 0.01);
assert!((isa_temperature(20_000.0) - 216.65).abs() < 0.01);
}
#[test]
fn speed_of_sound_sea_level() {
let a = speed_of_sound(288.15);
assert!((a - 340.294).abs() < 0.01, "a₀ = {a}");
}
#[test]
fn sutherland_sea_level() {
let mu = sutherland_viscosity(288.15);
assert!((mu - 1.789e-5).abs() / 1.789e-5 < 0.005, "μ = {mu:.4e}");
}
#[test]
fn density_decreases_with_altitude() {
assert!(isa_density(0.0) > isa_density(5_000.0));
assert!(isa_density(5_000.0) > isa_density(11_000.0));
assert!(isa_density(11_000.0) > isa_density(15_000.0));
}
}