use core::f64::consts::{PI, TAU};
use glam::DVec3;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "libm")]
#[allow(unused_imports)]
use crate::math::F64Math;
use crate::{ApoapsisSetterError, CompactOrbit, Matrix3x2, OrbitTrait};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "copy", derive(Copy))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Orbit {
eccentricity: f64,
periapsis: f64,
inclination: f64,
arg_pe: f64,
long_asc_node: 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: Matrix3x2,
}
impl Orbit {
#[must_use]
pub fn new(
eccentricity: f64,
periapsis: f64,
inclination: f64,
arg_pe: f64,
long_asc_node: f64,
mean_anomaly: f64,
mu: f64,
) -> Self {
let cache = Self::get_cached_calculations(inclination, arg_pe, long_asc_node);
Self {
eccentricity,
periapsis,
inclination,
arg_pe,
long_asc_node,
mean_anomaly,
mu,
cache,
}
}
#[must_use]
pub fn with_apoapsis(
apoapsis: f64,
periapsis: f64,
inclination: f64,
arg_pe: f64,
long_asc_node: f64,
mean_anomaly: f64,
mu: f64,
) -> Self {
let eccentricity = (apoapsis - periapsis) / (apoapsis + periapsis);
Self::new(
eccentricity,
periapsis,
inclination,
arg_pe,
long_asc_node,
mean_anomaly,
mu,
)
}
#[must_use]
pub fn new_circular(
radius: f64,
inclination: f64,
long_asc_node: f64,
mean_anomaly: f64,
mu: f64,
) -> Self {
let matrix = {
let mut matrix = Matrix3x2::default();
let (sin_inc, cos_inc) = inclination.sin_cos();
let (sin_lan, cos_lan) = long_asc_node.sin_cos();
matrix.e11 = cos_lan;
matrix.e12 = -(cos_inc * sin_lan);
matrix.e21 = sin_lan;
matrix.e22 = cos_inc * cos_lan;
matrix.e31 = 0.0;
matrix.e32 = sin_inc;
matrix
};
debug_assert_eq!(
matrix,
Self::get_transformation_matrix(inclination, 0.0, long_asc_node)
);
Self {
eccentricity: 0.0,
periapsis: radius,
inclination,
arg_pe: 0.0,
long_asc_node,
mean_anomaly,
mu,
cache: OrbitCachedCalculations {
transformation_matrix: matrix,
},
}
}
#[must_use]
pub fn new_flat(
eccentricity: f64,
periapsis: f64,
arg_pe: f64,
mean_anomaly: f64,
mu: f64,
) -> Self {
let matrix = {
let mut matrix = Matrix3x2::default();
let (sin_arg_pe, cos_arg_pe) = arg_pe.sin_cos();
matrix.e11 = cos_arg_pe;
matrix.e12 = -sin_arg_pe;
matrix.e21 = sin_arg_pe;
matrix.e22 = cos_arg_pe;
matrix.e31 = 0.0;
matrix.e32 = 0.0;
matrix
};
debug_assert_eq!(matrix, Self::get_transformation_matrix(0.0, arg_pe, 0.0));
Self {
eccentricity,
periapsis,
inclination: 0.0,
arg_pe,
long_asc_node: 0.0,
mean_anomaly,
mu,
cache: OrbitCachedCalculations {
transformation_matrix: matrix,
},
}
}
#[must_use]
pub fn new_flat_with_apoapsis(
apoapsis: f64,
periapsis: f64,
arg_pe: f64,
mean_anomaly: f64,
mu: f64,
) -> Self {
let matrix = {
let mut matrix = Matrix3x2::default();
let (sin_arg_pe, cos_arg_pe) = arg_pe.sin_cos();
matrix.e11 = cos_arg_pe;
matrix.e12 = -sin_arg_pe;
matrix.e21 = sin_arg_pe;
matrix.e22 = cos_arg_pe;
matrix.e31 = 0.0;
matrix.e32 = 0.0;
matrix
};
debug_assert_eq!(matrix, Self::get_transformation_matrix(0.0, arg_pe, 0.0));
let eccentricity = (apoapsis - periapsis) / (apoapsis + periapsis);
Self {
eccentricity,
periapsis,
inclination: 0.0,
arg_pe,
long_asc_node: 0.0,
mean_anomaly,
mu,
cache: OrbitCachedCalculations {
transformation_matrix: matrix,
},
}
}
#[must_use]
pub fn new_flat_circular(radius: f64, mean_anomaly: f64, mu: f64) -> Self {
let matrix = Matrix3x2::IDENTITY;
debug_assert_eq!(matrix, Self::get_transformation_matrix(0.0, 0.0, 0.0));
Self {
eccentricity: 0.0,
periapsis: radius,
inclination: 0.0,
arg_pe: 0.0,
long_asc_node: 0.0,
mean_anomaly,
mu,
cache: OrbitCachedCalculations {
transformation_matrix: matrix,
},
}
}
fn update_cache(&mut self) {
self.cache =
Self::get_cached_calculations(self.inclination, self.arg_pe, self.long_asc_node);
}
fn get_cached_calculations(
inclination: f64,
arg_pe: f64,
long_asc_node: f64,
) -> OrbitCachedCalculations {
let transformation_matrix =
Self::get_transformation_matrix(inclination, arg_pe, long_asc_node);
OrbitCachedCalculations {
transformation_matrix,
}
}
fn get_transformation_matrix(inclination: f64, arg_pe: f64, long_asc_node: f64) -> Matrix3x2 {
let mut matrix = Matrix3x2::default();
let (sin_inc, cos_inc) = inclination.sin_cos();
let (sin_arg_pe, cos_arg_pe) = arg_pe.sin_cos();
let (sin_lan, cos_lan) = long_asc_node.sin_cos();
matrix.e11 = cos_arg_pe * cos_lan - sin_arg_pe * cos_inc * sin_lan;
matrix.e12 = -(sin_arg_pe * cos_lan + cos_arg_pe * cos_inc * sin_lan);
matrix.e21 = cos_arg_pe * sin_lan + sin_arg_pe * cos_inc * cos_lan;
matrix.e22 = cos_arg_pe * cos_inc * cos_lan - sin_arg_pe * sin_lan;
matrix.e31 = sin_arg_pe * sin_inc;
matrix.e32 = cos_arg_pe * sin_inc;
matrix
}
}
impl OrbitTrait for Orbit {
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) -> Matrix3x2 {
self.cache.transformation_matrix
}
#[inline]
fn get_pqw_basis_vector_p(&self) -> DVec3 {
let m = self.cache.transformation_matrix;
DVec3::new(m.e11, m.e21, m.e31)
}
#[inline]
fn get_pqw_basis_vector_q(&self) -> DVec3 {
let m = self.cache.transformation_matrix;
DVec3::new(m.e12, m.e22, m.e32)
}
#[inline]
fn get_pqw_basis_vector_w(&self) -> DVec3 {
let m = self.cache.transformation_matrix;
let p = DVec3::new(m.e11, m.e21, m.e31);
let q = DVec3::new(m.e12, m.e22, m.e32);
p.cross(q)
}
#[inline]
fn get_eccentricity(&self) -> f64 {
self.eccentricity
}
#[inline]
fn get_periapsis(&self) -> f64 {
self.periapsis
}
#[inline]
fn get_inclination(&self) -> f64 {
self.inclination
}
#[inline]
fn get_arg_pe(&self) -> f64 {
self.arg_pe
}
#[inline]
fn get_long_asc_node(&self) -> f64 {
self.long_asc_node
}
#[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_inclination(&mut self, value: f64) {
self.inclination = value;
self.update_cache();
}
fn set_arg_pe(&mut self, value: f64) {
self.arg_pe = value;
self.update_cache();
}
fn set_long_asc_node(&mut self, value: f64) {
self.long_asc_node = 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: crate::MuSetterMode,
) {
let new_mu = gravitational_parameter;
match mode {
crate::MuSetterMode::KeepElements => {
self.mu = new_mu;
}
crate::MuSetterMode::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;
}
crate::MuSetterMode::KeepKnownStateVectors {
state_vectors,
time,
} => {
let new = state_vectors.to_cached_orbit(new_mu, time);
*self = new;
}
crate::MuSetterMode::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<CompactOrbit> for Orbit {
fn from(compact: CompactOrbit) -> Self {
Self::new(
compact.eccentricity,
compact.periapsis,
compact.inclination,
compact.arg_pe,
compact.long_asc_node,
compact.mean_anomaly,
compact.mu,
)
}
}
impl Default for Orbit {
fn default() -> Self {
Self::new_flat_circular(1.0, 0.0, 1.0)
}
}