use glam::DMat3;
use std::f64::consts::PI;
const DEG2RAD: f64 = PI / 180.0;
const SECONDS_PER_DAY: f64 = 86400.0;
const DAYS_PER_CENTURY: f64 = 36525.0;
pub fn compute_moon_rotation(tdb_seconds_since_j2000: f64) -> DMat3 {
let d = tdb_seconds_since_j2000 / SECONDS_PER_DAY;
let t = d / DAYS_PER_CENTURY;
let e1 = (125.045 - 0.052_992_0 * d) * DEG2RAD;
let e2 = (250.089 - 0.105_984_0 * d) * DEG2RAD;
let e3 = (260.008 + 13.012_009_0 * d) * DEG2RAD;
let e4 = (176.625 + 13.340_716_0 * d) * DEG2RAD;
let e6 = (311.589 + 26.724_071_0 * d) * DEG2RAD;
let e7 = (134.963 + 13.064_993_0 * d) * DEG2RAD;
let e10 = (15.134 - 0.155_280_0 * d) * DEG2RAD;
let e13 = (25.053 + 12.151_058_0 * d) * DEG2RAD;
let alpha0 = 269.9949 + 0.0031 * t - 3.8787 * e1.sin() - 0.1204 * e2.sin() + 0.0700 * e3.sin()
- 0.0172 * e4.sin()
+ 0.0072 * e6.sin()
- 0.0052 * e10.sin()
+ 0.0043 * e13.sin();
let delta0 = 66.5392 + 0.0130 * t + 1.5419 * e1.cos() + 0.0239 * e2.cos() - 0.0278 * e3.cos()
+ 0.0068 * e4.cos()
- 0.0029 * e6.cos()
+ 0.0009 * e7.cos()
+ 0.0008 * e10.cos()
- 0.0009 * e13.cos();
let w = 38.3213 + 13.176_358_15 * d + 3.5610 * e1.sin() + 0.1208 * e2.sin() - 0.0642 * e3.sin()
+ 0.0158 * e4.sin()
+ 0.0252 * e5_sin(d)
- 0.0066 * e6.sin()
- 0.0047 * e7.sin()
- 0.0046 * e8_sin(d)
+ 0.0028 * e9_sin(d)
+ 0.0052 * e10.sin()
+ 0.0040 * e11_sin(d)
+ 0.0019 * e12_sin(d)
- 0.0044 * e13.sin();
let alpha = alpha0 * DEG2RAD;
let delta = delta0 * DEG2RAD;
let w_rad = w * DEG2RAD;
let ca = (90.0_f64.to_radians() + alpha).cos();
let sa = (90.0_f64.to_radians() + alpha).sin();
let cd = (90.0_f64.to_radians() - delta).cos();
let sd = (90.0_f64.to_radians() - delta).sin();
let cw = w_rad.cos();
let sw = w_rad.sin();
let rz1 = DMat3::from_cols_array(&[ca, sa, 0.0, -sa, ca, 0.0, 0.0, 0.0, 1.0]);
let rx = DMat3::from_cols_array(&[1.0, 0.0, 0.0, 0.0, cd, sd, 0.0, -sd, cd]);
let rz2 = DMat3::from_cols_array(&[cw, sw, 0.0, -sw, cw, 0.0, 0.0, 0.0, 1.0]);
rz2 * rx * rz1
}
pub fn compute_moon_rotation_typed(
tdb_seconds_since_j2000: f64,
) -> astrodyn_quantities::frame_transform::FrameTransform<
astrodyn_quantities::frame::RootInertial,
astrodyn_quantities::frame::PlanetFixed<astrodyn_quantities::frame::Moon>,
> {
let mat = compute_moon_rotation(tdb_seconds_since_j2000);
astrodyn_quantities::frame_transform::FrameTransform::from_matrix(mat)
}
fn e5_sin(d: f64) -> f64 {
((358.473 + 0.985_600_3 * d) * DEG2RAD).sin()
}
fn e8_sin(d: f64) -> f64 {
((326.325 + 0.985_600_3 * d) * DEG2RAD).sin() }
fn e9_sin(d: f64) -> f64 {
((85.0 + 0.003_2 * d) * DEG2RAD).sin()
}
fn e11_sin(d: f64) -> f64 {
((119.743 + 0.008_6 * d) * DEG2RAD).sin()
}
fn e12_sin(d: f64) -> f64 {
((239.961 + 0.183_6 * d) * DEG2RAD).sin()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn moon_rotation_valid_at_j2000() {
let mat = compute_moon_rotation(0.0);
let det = mat.determinant();
assert!((det - 1.0).abs() < 1e-12, "det 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 };
assert!(
(product.col(j)[i] - expected).abs() < 1e-12,
"M*M^T[{i}][{j}] = {}, expected {expected}",
product.col(j)[i]
);
}
}
}
#[test]
fn moon_rotation_changes_over_day() {
let mat0 = compute_moon_rotation(0.0);
let mat1 = compute_moon_rotation(86400.0);
let diff = (mat1.col(0) - mat0.col(0)).length();
assert!(
diff > 0.1,
"Moon should rotate noticeably over 1 day, diff={diff}"
);
}
#[test]
fn moon_sidereal_month() {
let period_s = 27.322 * 86400.0;
let mat0 = compute_moon_rotation(0.0);
let mat1 = compute_moon_rotation(period_s);
let diff = (mat1.col(0) - mat0.col(0)).length();
assert!(
diff < 0.05,
"Moon rotation should nearly repeat after 1 sidereal month, diff={diff}"
);
}
}