use glam::{DMat3, DQuat};
use crate::TranslationalState;
use astrodyn_math::{JeodQuat, OrbitalElements};
pub fn jeodquat_angle_error(q1: &JeodQuat, q2: &JeodQuat) -> f64 {
let dot = (q1.scalar() * q2.scalar()
+ q1.vector().x * q2.vector().x
+ q1.vector().y * q2.vector().y
+ q1.vector().z * q2.vector().z)
.abs();
2.0 * dot.min(1.0).acos()
}
pub fn dquat_angle_error(a: DQuat, b: DQuat) -> f64 {
let dot = a.dot(b).abs();
2.0 * dot.min(1.0).acos()
}
pub fn angle_diff(a: f64, b: f64) -> f64 {
let tau = 2.0 * std::f64::consts::PI;
let mut d = (a - b) % tau;
if d > std::f64::consts::PI {
d -= tau;
}
if d < -std::f64::consts::PI {
d += tau;
}
d.abs()
}
pub fn angle_diff_restricted(a: f64, b: f64, max_magnitude: f64) -> f64 {
assert!(
a.abs() <= max_magnitude && b.abs() <= max_magnitude,
"angle_diff_restricted: input outside [-{max_magnitude}, {max_magnitude}] (a={a}, b={b})"
);
(a - b).abs()
}
pub fn max_mat_diff(a: &DMat3, b: &DMat3) -> f64 {
let mut max_d = 0.0_f64;
for c in 0..3 {
for r in 0..3 {
let d = (a.col(c)[r] - b.col(c)[r]).abs();
max_d = max_d.max(d);
}
}
max_d
}
pub fn state_from_elements(
a: f64,
e: f64,
i: f64,
raan: f64,
argp: f64,
nu: f64,
mu: f64,
) -> TranslationalState {
let mut oe = OrbitalElements::<astrodyn_quantities::frame::SelfPlanet>::default();
oe.semi_major_axis = a;
oe.e_mag = e;
oe.inclination = i;
oe.long_asc_node = raan;
oe.arg_periapsis = argp;
oe.true_anom = nu;
if e < 1.0 {
oe.semiparam = a * (1.0 - e * e);
} else {
oe.semiparam = a.abs() * (e * e - 1.0);
}
oe.nu_to_anomalies();
let (position, velocity) = oe.to_cartesian(mu).expect("to_cartesian failed");
TranslationalState { position, velocity }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dquat_angle_identity_zero() {
assert!(dquat_angle_error(DQuat::IDENTITY, DQuat::IDENTITY).abs() < 1e-15);
}
#[test]
fn jeodquat_angle_identity_zero() {
let q = JeodQuat::identity();
assert!(jeodquat_angle_error(&q, &q).abs() < 1e-15);
}
#[test]
fn angle_diff_wraparound() {
let tau = 2.0 * std::f64::consts::PI;
assert!(angle_diff(0.1, tau - 0.1) < 0.21);
assert!(angle_diff(0.0, 0.0).abs() < 1e-15);
}
#[test]
fn angle_diff_restricted_identity_zero() {
let half_pi = std::f64::consts::FRAC_PI_2;
assert!(angle_diff_restricted(0.0, 0.0, half_pi).abs() < 1e-15);
}
#[test]
fn angle_diff_restricted_full_range() {
let half_pi = std::f64::consts::FRAC_PI_2;
let d = angle_diff_restricted(half_pi, -half_pi, half_pi);
assert!((d - std::f64::consts::PI).abs() < 1e-15);
}
#[test]
#[should_panic(expected = "angle_diff_restricted")]
fn angle_diff_restricted_bound_violation_panics() {
let half_pi = std::f64::consts::FRAC_PI_2;
let _ = angle_diff_restricted(half_pi + 0.1, 0.0, half_pi);
}
#[test]
fn max_mat_diff_zero_for_identical() {
assert!(max_mat_diff(&DMat3::IDENTITY, &DMat3::IDENTITY).abs() < 1e-15);
}
#[test]
fn max_mat_diff_picks_largest_element() {
let mut b = DMat3::IDENTITY;
let mut col2 = b.col(2);
col2.y = 1.5; b = DMat3::from_cols(b.col(0), b.col(1), col2);
let d = max_mat_diff(&DMat3::IDENTITY, &b);
assert!((d - 1.5).abs() < 1e-15);
}
#[test]
fn state_from_elements_circular() {
let mu = 3.986_004_415e14;
let a = 7_000_000.0;
let s = state_from_elements(a, 0.0, 0.0, 0.0, 0.0, 0.0, mu);
assert!((s.position.length() - a).abs() < 1.0);
assert!((s.velocity.length() - (mu / a).sqrt()).abs() < 1e-6);
}
#[test]
fn state_from_elements_hyperbolic() {
let mu = 1.327e20;
let a = -1.0e12; let e = 1.5;
let s = state_from_elements(a, e, 0.0, 0.0, 0.0, 0.0, mu);
for v in [
s.position.x,
s.position.y,
s.position.z,
s.velocity.x,
s.velocity.y,
s.velocity.z,
] {
assert!(v.is_finite());
}
}
}