use sciforge::hub::prelude::constants::G;
use std::cell::{Cell, RefCell};
use std::io::{BufRead, BufReader, Write};
use std::process::{Child, Command, Stdio};
pub const LUNARMASS: f64 = 7.342e22;
pub const LUNARRADIUS: f64 = 1.7374e6;
pub const EARTHMOONDISTANCE: f64 = 3.844e8;
pub const LUNARORBITALPERIOD: f64 = 2360592.0;
pub struct MoonState {
pub masskg: f64,
pub radiusm: f64,
pub semimajoraxism: f64,
pub orbitalperiods: f64,
pub eccentricity: f64,
pub inclinationrad: f64,
pub raanrad: f64,
pub argperigeerad: f64,
pub meananomalyrad: f64,
pub source: Cell<MoonSource>,
ipc: RefCell<Option<MoonIpc>>,
}
struct MoonIpc {
child: Child,
reader: BufReader<std::process::ChildStdout>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MoonSource {
Binary,
Simulation,
}
fn findmoonbinary() -> Option<std::path::PathBuf> {
if let Ok(path) = std::env::var("MOONBINARY") {
let p = std::path::PathBuf::from(path);
if p.is_file() {
return Some(p);
}
}
if let Ok(pathvar) = std::env::var("PATH") {
for dir in pathvar.split(':') {
if dir.is_empty() {
continue;
}
let candidate = std::path::Path::new(dir).join("moon");
if candidate.is_file() {
return Some(candidate);
}
}
}
None
}
fn spawnmoon(path: &std::path::Path) -> Option<MoonIpc> {
let mut child = Command::new(path)
.arg("--ipc")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.ok()?;
let stdout = child.stdout.take()?;
let reader = BufReader::new(stdout);
Some(MoonIpc { child, reader })
}
impl MoonState {
pub fn new() -> Self {
if let Some(path) = findmoonbinary()
&& let Some(ipc) = spawnmoon(&path)
{
return Self {
masskg: LUNARMASS,
radiusm: LUNARRADIUS,
semimajoraxism: EARTHMOONDISTANCE,
orbitalperiods: LUNARORBITALPERIOD,
eccentricity: crate::LUNARECCENTRICITY,
inclinationrad: crate::LUNARINCLINATIONDEG.to_radians(),
raanrad: 125.08f64.to_radians(),
argperigeerad: 318.15f64.to_radians(),
meananomalyrad: 0.0,
source: Cell::new(MoonSource::Binary),
ipc: RefCell::new(Some(ipc)),
};
}
Self::simulated()
}
pub fn simulated() -> Self {
Self {
masskg: LUNARMASS,
radiusm: LUNARRADIUS,
semimajoraxism: EARTHMOONDISTANCE,
orbitalperiods: LUNARORBITALPERIOD,
eccentricity: crate::LUNARECCENTRICITY,
inclinationrad: crate::LUNARINCLINATIONDEG.to_radians(),
raanrad: 125.08f64.to_radians(),
argperigeerad: 318.15f64.to_radians(),
meananomalyrad: 0.0,
source: Cell::new(MoonSource::Simulation),
ipc: RefCell::new(None),
}
}
fn ipcquery(&self, cmd: &str) -> Option<String> {
let mut ipcborrow = self.ipc.borrow_mut();
let ipc = ipcborrow.as_mut()?;
let stdin = ipc.child.stdin.as_mut()?;
writeln!(stdin, "{}", cmd).ok()?;
stdin.flush().ok()?;
let mut line = String::new();
ipc.reader.read_line(&mut line).ok()?;
Some(line.trim().to_string())
}
fn parsexyz(s: &str) -> Option<(f64, f64, f64)> {
let parts: Vec<&str> = s.split_whitespace().collect();
if parts.len() >= 3 {
let x = parts[0].parse::<f64>().ok()?;
let y = parts[1].parse::<f64>().ok()?;
let z = parts[2].parse::<f64>().ok()?;
Some((x, y, z))
} else {
None
}
}
pub fn step(&mut self, dts: f64) {
if self.source.get() == MoonSource::Binary {
if let Some(resp) = self.ipcquery(&format!("step {}", dts)) {
if let Some((x, y, z)) = Self::parsexyz(&resp) {
let r = (x * x + y * y + z * z).sqrt();
if r > 0.0 {
self.semimajoraxism = r;
}
}
return;
}
self.source.set(MoonSource::Simulation);
*self.ipc.borrow_mut() = None;
}
let pi2 = 2.0 * std::f64::consts::PI;
let n = pi2 / self.orbitalperiods;
self.meananomalyrad = (self.meananomalyrad + n * dts) % pi2;
let raanrate = -pi2 / (18.61 * crate::SECONDSPERYEAR);
self.raanrad = (self.raanrad + raanrate * dts) % pi2;
let apserate = pi2 / (8.85 * crate::SECONDSPERYEAR);
self.argperigeerad = (self.argperigeerad + apserate * dts) % pi2;
}
fn eccentricanomaly(&self) -> f64 {
let m = self.meananomalyrad;
let e = self.eccentricity;
let mut ea = m + e * m.sin();
for iter in 0..15 {
let f = ea - e * ea.sin() - m;
let fp = 1.0 - e * ea.cos();
ea -= f / fp;
if f.abs() < 1e-12 || iter == 14 {
break;
}
}
ea
}
fn trueanomaly(&self) -> f64 {
let ea = self.eccentricanomaly();
let e = self.eccentricity;
2.0 * f64::atan2(
(1.0 + e).sqrt() * (ea / 2.0).sin(),
(1.0 - e).sqrt() * (ea / 2.0).cos(),
)
}
pub fn distance(&self) -> f64 {
if self.source.get() == MoonSource::Binary {
let (x, y, z) = self.position();
return (x * x + y * y + z * z).sqrt();
}
let nu = self.trueanomaly();
let e = self.eccentricity;
self.semimajoraxism * (1.0 - e * e) / (1.0 + e * nu.cos())
}
pub fn position(&self) -> (f64, f64, f64) {
if self.source.get() == MoonSource::Binary
&& let Some(resp) = self.ipcquery("position")
&& let Some((x, y, z)) = Self::parsexyz(&resp)
{
return (x, y, z);
}
let nu = self.trueanomaly();
let r = {
let e = self.eccentricity;
self.semimajoraxism * (1.0 - e * e) / (1.0 + e * nu.cos())
};
let xorb = r * nu.cos();
let yorb = r * nu.sin();
let w = self.argperigeerad;
let xw = xorb * w.cos() - yorb * w.sin();
let yw = xorb * w.sin() + yorb * w.cos();
let i = self.inclinationrad;
let xi = xw;
let yi = yw * i.cos();
let zi = yw * i.sin();
let omega = self.raanrad;
let x = xi * omega.cos() - yi * omega.sin();
let y = xi * omega.sin() + yi * omega.cos();
let z = zi;
(x, y, z)
}
pub fn gravityat(&self, distancem: f64) -> f64 {
G * self.masskg / (distancem * distancem)
}
pub fn isbinary(&self) -> bool {
self.source.get() == MoonSource::Binary
}
}
impl Default for MoonState {
fn default() -> Self {
Self::new()
}
}