spaceman 0.1.0

A WIP library for calculating common orbital maneuvers in Kerbal Space Program.
Documentation
use std::f64::consts::PI;

use crate::celestial_bodies::CelestialBody;
use crate::hohmann_transfer::HohmannTransfer;
use crate::Apsis;

/// A relay network.
#[derive(Copy, Clone, Debug)]
pub struct RelayNetwork {
    /// The amount of satellites in the relay network.
    pub satellite_count: usize,
    /// The celestial body the satellites are orbiting.
    pub celestial_body: CelestialBody,
}

impl RelayNetwork {
    /// Create a new relay network.
    pub fn new(satellite_count: usize, celestial_body: CelestialBody) -> Self {
        Self {
            satellite_count,
            celestial_body,
        }
    }

    /// Calculate the minimum altitude the satellites need to communicate with each other.
    ///
    /// Returns [`None`] if the satellite count is smaller than 3.
    pub fn min_altitude(&self) -> Option<f64> {
        if self.satellite_count < 3 {
            return None;
        }

        Some(
            self.celestial_body.radius / (PI / self.satellite_count as f64).cos()
                - self.celestial_body.radius,
        )
    }

    /// Calculate the apsis of the transfer orbit
    /// used to place the satellites in their orbit with the respective altitude.
    ///
    /// If the apsis is the apoapsis, the relay's transfer periapsis is the relay altitude.
    /// Otherwise, the apoapsis of the transfer orbit is the relay altitude.
    ///
    /// The start altitude of the Hohmann transfer is the calculated apsis altitude,
    /// the end altitude is the relay altitude.
    /// To calculate the transfer, circularization or total Δv
    /// you can use the respective [`HohmannTransfer`] methods.
    ///
    /// Returns [`None`] if the minimum altitude is greater than the relay network's altitude
    /// or the satellite count is smaller than 3.
    pub fn transfer(&self, relay_altitude: f64, apsis: Apsis) -> Option<HohmannTransfer> {
        if relay_altitude < self.min_altitude()? {
            return None;
        }

        let period_ratio = match apsis {
            Apsis::Apo => (self.satellite_count as f64 + 1.0) / self.satellite_count as f64,
            Apsis::Peri => (self.satellite_count as f64 - 1.0) / self.satellite_count as f64,
        };

        let semi_major_axis =
            period_ratio.powf(2.0 / 3.0) * (self.celestial_body.radius + relay_altitude);

        let transfer_altitude =
            2.0 * (semi_major_axis - self.celestial_body.radius) - relay_altitude;

        Some(HohmannTransfer::new(
            transfer_altitude,
            relay_altitude,
            self.celestial_body,
        ))
    }
}