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, MuSetterMode2D, Orbit2D, OrbitTrait2D};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "copy", derive(Copy))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CompactOrbit2D {
pub eccentricity: f64,
pub periapsis: f64,
pub arg_pe: f64,
pub mean_anomaly: f64,
pub mu: f64,
}
impl CompactOrbit2D {
#[must_use]
pub fn new(eccentricity: f64, periapsis: f64, arg_pe: f64, mean_anomaly: f64, mu: f64) -> Self {
Self {
eccentricity,
periapsis,
arg_pe,
mean_anomaly,
mu,
}
}
#[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 {
eccentricity,
periapsis,
arg_pe,
mean_anomaly,
mu,
}
}
#[must_use]
pub fn new_circular(radius: f64, mean_anomaly: f64, mu: f64) -> Self {
Self {
eccentricity: 0.0,
periapsis: radius,
arg_pe: 0.0,
mean_anomaly,
mu,
}
}
}
impl OrbitTrait2D for CompactOrbit2D {
fn set_apoapsis(&mut self, apoapsis: f64) -> Result<(), crate::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);
}
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 {
let (sin_arg_pe, cos_arg_pe) = self.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),
}
}
#[inline]
fn get_pqw_basis_vector_p(&self) -> DVec2 {
self.get_transformation_matrix().x_axis
}
#[inline]
fn get_pqw_basis_vector_q(&self) -> DVec2 {
self.get_transformation_matrix().y_axis
}
#[inline]
fn get_eccentricity(&self) -> f64 {
self.eccentricity
}
#[inline]
fn set_eccentricity(&mut self, eccentricity: f64) {
self.eccentricity = eccentricity;
}
#[inline]
fn get_periapsis(&self) -> f64 {
self.periapsis
}
#[inline]
fn set_periapsis(&mut self, periapsis: f64) {
self.periapsis = periapsis;
}
#[inline]
fn get_arg_pe(&self) -> f64 {
self.arg_pe
}
#[inline]
fn set_arg_pe(&mut self, arg_pe: f64) {
self.arg_pe = arg_pe;
}
#[inline]
fn get_mean_anomaly_at_epoch(&self) -> f64 {
self.mean_anomaly
}
#[inline]
fn set_mean_anomaly_at_epoch(&mut self, mean_anomaly: f64) {
self.mean_anomaly = mean_anomaly;
}
#[inline]
fn get_gravitational_parameter(&self) -> f64 {
self.mu
}
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_compact_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_compact_orbit(new_mu, time);
*self = new;
}
}
}
}
impl From<Orbit2D> for CompactOrbit2D {
fn from(cached: Orbit2D) -> Self {
Self {
eccentricity: cached.get_eccentricity(),
periapsis: cached.get_periapsis(),
arg_pe: cached.get_arg_pe(),
mean_anomaly: cached.get_mean_anomaly_at_epoch(),
mu: cached.get_gravitational_parameter(),
}
}
}
impl Default for CompactOrbit2D {
fn default() -> Self {
Self::new_circular(1.0, 0.0, 1.0)
}
}