use core::f64::consts::{PI, TAU};
use glam::{DMat2, DVec2};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "libm")]
#[allow(unused_imports)]
use crate::math::F64Math;
use crate::{ApoapsisSetterError, CompactOrbit2D, MuSetterMode2D, OrbitTrait2D};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "copy", derive(Copy))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Orbit2D {
eccentricity: f64,
periapsis: f64,
arg_pe: f64,
mean_anomaly: f64,
mu: f64,
cache: OrbitCachedCalculations,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "copy", derive(Copy))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
struct OrbitCachedCalculations {
transformation_matrix: DMat2,
}
impl Orbit2D {
#[must_use]
pub fn new(eccentricity: f64, periapsis: f64, arg_pe: f64, mean_anomaly: f64, mu: f64) -> Self {
let cache = Self::get_cached_calculations(arg_pe);
Self {
eccentricity,
periapsis,
arg_pe,
mean_anomaly,
mu,
cache,
}
}
#[must_use]
pub fn with_apoapsis(
apoapsis: f64,
periapsis: f64,
arg_pe: f64,
mean_anomaly: f64,
mu: f64,
) -> Self {
let eccentricity = (apoapsis - periapsis) / (apoapsis + periapsis);
Self::new(eccentricity, periapsis, arg_pe, mean_anomaly, mu)
}
#[must_use]
pub fn new_circular(radius: f64, mean_anomaly: f64, mu: f64) -> Self {
let matrix = DMat2::IDENTITY;
debug_assert_eq!(matrix, Self::get_transformation_matrix(0.0));
Self {
eccentricity: 0.0,
periapsis: radius,
arg_pe: 0.0,
mean_anomaly,
mu,
cache: OrbitCachedCalculations {
transformation_matrix: matrix,
},
}
}
fn update_cache(&mut self) {
self.cache = Self::get_cached_calculations(self.arg_pe);
}
fn get_cached_calculations(arg_pe: f64) -> OrbitCachedCalculations {
let transformation_matrix = Self::get_transformation_matrix(arg_pe);
OrbitCachedCalculations {
transformation_matrix,
}
}
fn get_transformation_matrix(arg_pe: f64) -> DMat2 {
let (sin_arg_pe, cos_arg_pe) = arg_pe.sin_cos();
DMat2 {
x_axis: DVec2::new(cos_arg_pe, sin_arg_pe),
y_axis: DVec2::new(-sin_arg_pe, cos_arg_pe),
}
}
}
impl OrbitTrait2D for Orbit2D {
fn set_apoapsis(&mut self, apoapsis: f64) -> Result<(), ApoapsisSetterError> {
if apoapsis < 0.0 {
Err(ApoapsisSetterError::ApoapsisNegative)
} else if apoapsis < self.periapsis {
Err(ApoapsisSetterError::ApoapsisLessThanPeriapsis)
} else {
self.eccentricity = (apoapsis - self.periapsis) / (apoapsis + self.periapsis);
Ok(())
}
}
fn set_apoapsis_force(&mut self, apoapsis: f64) {
let mut apoapsis = apoapsis;
if apoapsis < self.periapsis && apoapsis >= 0.0 {
(apoapsis, self.periapsis) = (self.periapsis, apoapsis);
self.arg_pe = (self.arg_pe + PI).rem_euclid(TAU);
self.mean_anomaly = (self.mean_anomaly + PI).rem_euclid(TAU);
self.update_cache();
}
if apoapsis < 0.0 && apoapsis > -self.periapsis {
self.eccentricity = 1.0;
} else {
self.eccentricity = (apoapsis - self.periapsis) / (apoapsis + self.periapsis);
}
}
#[inline]
fn get_transformation_matrix(&self) -> DMat2 {
self.cache.transformation_matrix
}
#[inline]
fn get_pqw_basis_vector_p(&self) -> DVec2 {
self.cache.transformation_matrix.x_axis
}
#[inline]
fn get_pqw_basis_vector_q(&self) -> DVec2 {
self.cache.transformation_matrix.y_axis
}
#[inline]
fn get_eccentricity(&self) -> f64 {
self.eccentricity
}
#[inline]
fn get_periapsis(&self) -> f64 {
self.periapsis
}
#[inline]
fn get_arg_pe(&self) -> f64 {
self.arg_pe
}
#[inline]
fn get_mean_anomaly_at_epoch(&self) -> f64 {
self.mean_anomaly
}
#[inline]
fn get_gravitational_parameter(&self) -> f64 {
self.mu
}
#[inline]
fn set_eccentricity(&mut self, value: f64) {
self.eccentricity = value;
}
#[inline]
fn set_periapsis(&mut self, value: f64) {
self.periapsis = value;
}
fn set_arg_pe(&mut self, value: f64) {
self.arg_pe = value;
self.update_cache();
}
#[inline]
fn set_mean_anomaly_at_epoch(&mut self, value: f64) {
self.mean_anomaly = value;
}
fn set_gravitational_parameter(&mut self, gravitational_parameter: f64, mode: MuSetterMode2D) {
let new_mu = gravitational_parameter;
match mode {
MuSetterMode2D::KeepElements => {
self.mu = new_mu;
}
MuSetterMode2D::KeepPositionAtTime(t) => {
let inv_abs_a_cubed = self.get_semi_major_axis().powi(3).abs().recip();
self.mean_anomaly +=
t * ((self.mu * inv_abs_a_cubed).sqrt() - (new_mu * inv_abs_a_cubed).sqrt());
self.mu = new_mu;
}
MuSetterMode2D::KeepKnownStateVectors {
state_vectors,
time,
} => {
let new = state_vectors.to_cached_orbit(new_mu, time);
*self = new;
}
MuSetterMode2D::KeepStateVectorsAtTime(time) => {
let ecc_anom = self.get_eccentric_anomaly_at_time(time);
let state_vectors = self.get_state_vectors_at_eccentric_anomaly(ecc_anom);
let new = state_vectors.to_cached_orbit(new_mu, time);
*self = new;
}
}
}
}
impl From<CompactOrbit2D> for Orbit2D {
fn from(compact: CompactOrbit2D) -> Self {
Self::new(
compact.eccentricity,
compact.periapsis,
compact.arg_pe,
compact.mean_anomaly,
compact.mu,
)
}
}
impl Default for Orbit2D {
fn default() -> Self {
Self::new_circular(1.0, 0.0, 1.0)
}
}