use glam::DMat3;
use std::f64::consts::PI;
const DEG2RAD: f64 = PI / 180.0;
const MAS2RAD: f64 = DEG2RAD / (1000.0 * 3600.0);
const SECONDS_PER_DAY: f64 = 86400.0;
const I_AT_J2000: f64 = 25.189_398_458_5 * DEG2RAD;
const I_DOT: f64 = -0.000_000_005 * DEG2RAD;
const MEAN_MOTION: f64 = 0.524_039_588_28 * DEG2RAD;
const MEAN_ANOMALY_J2000: f64 = 19.3564 * DEG2RAD;
const Q_ANGLE_J2000: f64 = 142.00 * DEG2RAD;
const I_M: [f64; 10] = [
-1.4 * MAS2RAD,
-0.4 * MAS2RAD,
0.0,
0.0,
-49.1 * MAS2RAD,
515.7 * MAS2RAD,
112.8 * MAS2RAD,
19.2 * MAS2RAD,
3.0 * MAS2RAD,
0.4 * MAS2RAD,
];
const PSI_M: [f64; 10] = [
0.0,
-632.6 * MAS2RAD,
-44.2 * MAS2RAD,
-4.0 * MAS2RAD,
-104.5 * MAS2RAD,
1097.0 * MAS2RAD,
240.1 * MAS2RAD,
40.9 * MAS2RAD,
6.5 * MAS2RAD,
1.0 * MAS2RAD,
];
const PSI_AT_J2000: f64 = 81.968_367_126_7 * DEG2RAD;
const PSI_DOT: f64 = -0.000_005_756 * DEG2RAD;
const NODE_N: f64 = 3.379_191_83 * DEG2RAD;
const INCL_J: f64 = 24.676_826_69 * DEG2RAD;
const PHI_AT_J2000: f64 = 133.384_62 * DEG2RAD;
const PLANET_ROT_VEL: f64 = 350.891_985_303 * DEG2RAD;
fn nj_matrix() -> DMat3 {
let cos_n = NODE_N.cos();
let sin_n = NODE_N.sin();
let cos_j = INCL_J.cos();
let sin_j = INCL_J.sin();
let n_mat = DMat3::from_cols_array(&[cos_n, sin_n, 0.0, -sin_n, cos_n, 0.0, 0.0, 0.0, 1.0]);
let j_mat = DMat3::from_cols_array(&[1.0, 0.0, 0.0, 0.0, cos_j, sin_j, 0.0, -sin_j, cos_j]);
n_mat * j_mat
}
struct NutationResult {
psi_nut: f64,
obliquity: f64,
matrix: DMat3,
}
fn compute_nutation(time_days: f64) -> NutationResult {
let mut psi_nut = 0.0;
let mut obliquity_nut = I_M[0];
for ii in 1..=9usize {
let (alpha_m, theta_m) = if ii <= 3 {
(ii as f64 * MEAN_MOTION, ii as f64 * MEAN_ANOMALY_J2000)
} else {
let k = (ii - 3) as f64;
(k * MEAN_MOTION, k * MEAN_ANOMALY_J2000 + Q_ANGLE_J2000)
};
let arg = alpha_m * time_days + theta_m;
psi_nut += PSI_M[ii] * arg.sin();
obliquity_nut += I_M[ii] * arg.cos();
}
let obliquity = I_AT_J2000 + I_DOT * time_days + obliquity_nut;
let cos_i = obliquity.cos();
let sin_i = obliquity.sin();
let matrix = DMat3::from_cols_array(&[1.0, 0.0, 0.0, 0.0, cos_i, sin_i, 0.0, -sin_i, cos_i]);
NutationResult {
psi_nut,
obliquity,
matrix,
}
}
fn compute_precession(time_days: f64, psi_nut: f64) -> DMat3 {
let psi_precess = PSI_AT_J2000 + PSI_DOT * time_days + psi_nut;
let cos_psi = psi_precess.cos();
let sin_psi = psi_precess.sin();
let p_mat =
DMat3::from_cols_array(&[cos_psi, sin_psi, 0.0, -sin_psi, cos_psi, 0.0, 0.0, 0.0, 1.0]);
nj_matrix() * p_mat
}
fn compute_rotation(time_days: f64, psi_nut: f64, obliquity: f64) -> DMat3 {
let mut phi_spin = PHI_AT_J2000 + PLANET_ROT_VEL * time_days - psi_nut * obliquity.cos();
phi_spin = phi_spin.rem_euclid(2.0 * PI);
let cos_phi = phi_spin.cos();
let sin_phi = phi_spin.sin();
DMat3::from_cols_array(&[cos_phi, sin_phi, 0.0, -sin_phi, cos_phi, 0.0, 0.0, 0.0, 1.0])
}
pub fn compute_mars_rotation(tt_seconds_since_j2000: f64) -> DMat3 {
let time_days = tt_seconds_since_j2000 / SECONDS_PER_DAY;
let nut = compute_nutation(time_days);
let p = compute_precession(time_days, nut.psi_nut);
let r = compute_rotation(time_days, nut.psi_nut, nut.obliquity);
(p * nut.matrix * r).transpose()
}
pub fn compute_mars_rotation_typed(
tt_seconds_since_j2000: f64,
) -> astrodyn_quantities::frame_transform::FrameTransform<
astrodyn_quantities::frame::RootInertial,
astrodyn_quantities::frame::PlanetFixed<astrodyn_quantities::frame::Mars>,
> {
let mat = compute_mars_rotation(tt_seconds_since_j2000);
astrodyn_quantities::frame_transform::FrameTransform::from_matrix(mat)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mars_rotation_at_j2000() {
let mat = compute_mars_rotation(0.0);
let det = mat.determinant();
assert!(
(det - 1.0).abs() < 1e-12,
"determinant should be 1, got {det}"
);
let mt = mat.transpose();
let product = mat * mt;
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
let actual = product.col(j)[i];
assert!(
(actual - expected).abs() < 1e-12,
"M*M^T[{i}][{j}] = {actual}, expected {expected}"
);
}
}
}
#[test]
fn mars_rotation_changes_with_time() {
let mat0 = compute_mars_rotation(0.0);
let mat1 = compute_mars_rotation(3600.0); let diff = (mat1.col(0) - mat0.col(0)).length();
assert!(
diff > 0.01,
"rotation should change over 1 hour, diff={diff}"
);
}
#[test]
fn mars_sidereal_day() {
let sidereal_day_s = 360.0 / (PLANET_ROT_VEL / DEG2RAD) * SECONDS_PER_DAY;
let mat0 = compute_mars_rotation(0.0);
let mat1 = compute_mars_rotation(sidereal_day_s);
let diff = (mat1.col(0) - mat0.col(0)).length();
assert!(
diff < 0.001,
"after one sidereal day, rotation should nearly repeat, diff={diff}"
);
}
#[test]
fn nutation_fourier_series_nonzero() {
let nut = compute_nutation(1000.0); assert!(
nut.psi_nut.abs() > 1e-10,
"nutation in longitude should be nonzero"
);
assert!(
(nut.obliquity - I_AT_J2000).abs() > 1e-12,
"obliquity should differ from J2000 value"
);
}
}