use crate::astro::orbit::KeplerianOrbit;
use crate::qtty::*;
type RadiansPerDay = crate::qtty::Quantity<crate::qtty::Per<Radian, Day>>;
const GAUSSIAN_GRAVITATIONAL_CONSTANT: RadiansPerDay = Quantity::new(0.017_202_098_95);
#[derive(Clone, Debug)]
pub struct Planet {
pub mass: Kilograms,
pub radius: Kilometers,
pub orbit: KeplerianOrbit,
pub albedo: Option<Albedos>,
}
impl Planet {
pub const fn new_const(mass: Kilograms, radius: Kilometers, orbit: KeplerianOrbit) -> Self {
Self {
mass,
radius,
orbit,
albedo: None,
}
}
pub const fn with_albedo(mut self, albedo: Albedos) -> Self {
self.albedo = Some(albedo);
self
}
pub fn builder() -> PlanetBuilder {
PlanetBuilder::default()
}
}
#[derive(Debug, Clone)]
pub enum PlanetBuilderError {
MissingMass,
MissingRadius,
MissingOrbit,
}
#[derive(Debug, Default, Clone)]
pub struct PlanetBuilder {
mass: Option<Kilograms>,
radius: Option<Kilometers>,
orbit: Option<KeplerianOrbit>,
albedo: Option<Albedos>,
}
impl PlanetBuilder {
pub fn mass(mut self, mass: impl Into<Kilograms>) -> Self {
self.mass = Some(mass.into());
self
}
pub fn radius(mut self, radius: impl Into<Kilometers>) -> Self {
self.radius = Some(radius.into());
self
}
pub fn orbit(mut self, orbit: KeplerianOrbit) -> Self {
self.orbit = Some(orbit);
self
}
pub fn albedo(mut self, albedo: Albedos) -> Self {
self.albedo = Some(albedo);
self
}
pub fn try_build(self) -> Result<Planet, PlanetBuilderError> {
Ok(Planet {
mass: self.mass.ok_or(PlanetBuilderError::MissingMass)?,
radius: self.radius.ok_or(PlanetBuilderError::MissingRadius)?,
orbit: self.orbit.ok_or(PlanetBuilderError::MissingOrbit)?,
albedo: self.albedo,
})
}
pub fn build(self) -> Planet {
self.try_build().expect("incomplete PlanetBuilder")
}
pub const fn build_unchecked(self) -> Planet {
match (self.mass, self.radius, self.orbit) {
(Some(mass), Some(radius), Some(orbit)) => Planet {
mass,
radius,
orbit,
albedo: None,
},
_ => panic!("PlanetBuilder::build_unchecked called with missing fields"),
}
}
}
pub trait OrbitExt {
fn period(&self) -> Seconds;
}
impl OrbitExt for KeplerianOrbit {
fn period(&self) -> Seconds {
use std::f64::consts::PI;
let a_au = self
.shape()
.semi_major_axis()
.to::<AstronomicalUnit>()
.value();
let k = GAUSSIAN_GRAVITATIONAL_CONSTANT.value();
let t_days = (2.0 * PI / k) * (a_au * a_au.sqrt());
Seconds::new(t_days * 86_400.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::qtty::{AstronomicalUnits, Degrees, Kilograms, Kilometers};
use crate::time::JulianDate;
#[test]
fn builder_roundtrip() {
let p = Planet::builder()
.mass(Kilograms::new(1.0))
.radius(Kilometers::new(1.0))
.orbit(KeplerianOrbit::new(
AstronomicalUnits::new(1.0),
0.0,
Degrees::new(0.0),
Degrees::new(0.0),
Degrees::new(0.0),
Degrees::new(0.0),
JulianDate::J2000,
))
.build();
assert_eq!(p.mass.value(), 1.0);
}
#[test]
fn with_albedo_roundtrip() {
let p = Planet::builder()
.mass(Kilograms::new(1.0))
.radius(Kilometers::new(1.0))
.orbit(KeplerianOrbit::new(
AstronomicalUnits::new(1.0),
0.0,
Degrees::new(0.0),
Degrees::new(0.0),
Degrees::new(0.0),
Degrees::new(0.0),
JulianDate::J2000,
))
.albedo(Albedos::new(0.30))
.build();
assert!((p.albedo.unwrap().value() - 0.30).abs() < 1e-10);
}
}