use serde::{Deserialize}; use crate::{GameState, known_stars::{KNOWN_STARS, StarNotFoundError}, timeman::{Second, TimeMan}}; use std::{cell::RefCell, error::Error, f64::consts::PI}; const GRAVITATIONAL_CONSTANT: f64 = 6.67408e-20; pub type Kilograms = f64; pub type Kilometers = f64; pub type Percentage = f64; pub type Angle = f64; pub type BodyId = usize; pub type SystemId = usize; #[derive(Debug, Deserialize)] pub struct SerialOrbitalBody { name: String, orbits: Option, mass: Kilograms, radius: Kilometers, eccentricity: Percentage, inclination: Angle, long_asc_node: Angle, long_periapsis: Angle, sgp: f64, mean_long: Angle, semi_major_axis: Kilometers, } pub struct OrbitalBody { body: SerialOrbitalBody, id: BodyId, position: Option> } pub struct SolarSystem { id: SystemId, name: String, bodies: Vec, time: Option } impl SolarSystem { pub fn new_from_csv( id: SystemId, data: &'static str) -> Result> { let data_reader = stringreader::StringReader::new(data); let mut body_reader = csv::Reader::from_reader(data_reader); let mut bodies = Vec::::new(); for result in body_reader.deserialize() { let mut record: SerialOrbitalBody = result?; match record.orbits { Some(orbits) => { if record.sgp == 0.0 { record.sgp = (bodies[orbits].body.mass + record.mass) * GRAVITATIONAL_CONSTANT; } } None => {} } println!("New body: {:?}", record); bodies.push(OrbitalBody { body: record, id: bodies.len(), position: None }); } Ok(Self { id: id, name: bodies[0].name().clone(), bodies: bodies, time: None }) } pub fn new_from_known_star( id: SystemId, star: &'static str) -> Result> { let star_csv = match KNOWN_STARS.get(star).copied() { Some(csv) => csv, None => return Err(Box::new(StarNotFoundError { star: star })) }; SolarSystem::new_from_csv(id, star_csv) } pub fn id(&self) -> SystemId { self.id } pub fn name(&self) -> &String { &self.name } pub fn bodies(&self) -> &[OrbitalBody] { self.bodies.as_slice() } fn update_bodies( &mut self, time: Second) { self.bodies.iter_mut().for_each(|body| { body.position = Some(body.calculate_orbit_at(time)); }); self.time = Some(time); } pub fn body_position( &self, body: &OrbitalBody) -> cgmath::Vector3 { match body.body.orbits { Some(parent) => { let body_pos = body.position(); let parent_pos = self.bodies[parent].position(); body_pos + parent_pos }, None => body.position() } } pub fn update( &mut self, time: Second) { match self.time { Some(cache_time) => { if cache_time == time { return; } self.update_bodies(time); } None => { self.update_bodies(time); } } } } impl OrbitalBody { pub fn id(&self) -> BodyId { self.id } pub fn name(&self) -> &String { &self.body.name } pub fn radius(&self) -> f32 { self.body.radius as f32 } pub fn position(&self) -> cgmath::Vector3 { self.position.unwrap() } pub fn does_orbit(&self) -> bool { self.body.orbits.is_some() } pub fn get_orbits(&self) -> Option { self.body.orbits } pub fn orbital_period(&self) -> Second { ((self.body.semi_major_axis.powf(3.0) / self.body.sgp).sqrt() * std::f64::consts::TAU) as u64 } pub fn calculate_orbit_at( &self, time: Second) -> cgmath::Vector3 { if self.body.orbits.is_none() { return cgmath::Vector3::::new(0.0, 0.0, 0.0); } let eccentricity = self.body.eccentricity; let long_asc_node = self.body.long_asc_node; let arg_periaps = self.body.long_periapsis - long_asc_node; let sma_cubed = self.body.semi_major_axis.powf(3.0); let mean_motion = (self.body.sgp / sma_cubed).sqrt(); let mean_anomaly_epoch = self.body.mean_long - self.body.long_periapsis; let mean_anomaly = mean_anomaly_epoch + (mean_motion * time as f64); //Find the eccentric anomaly via newton's method let mut eccentric_anomaly = mean_anomaly; for _ in 0..100 { let (e_sin, e_cos) = eccentric_anomaly.sin_cos(); let new_anomaly = eccentric_anomaly - ( (eccentric_anomaly - eccentricity * e_sin - mean_anomaly) / (1.0 - eccentricity * e_cos) ); let diff = eccentric_anomaly - new_anomaly; eccentric_anomaly = new_anomaly; if diff.abs() < 1e-6 { break; } } let true_anomaly = 2.0 * ( ((1.0 + eccentricity) / (1.0 - eccentricity)).sqrt() * (eccentric_anomaly / 2.0).tan() ).atan(); let vw = true_anomaly + arg_periaps; let (vw_sin, vw_cos) = vw.sin_cos(); let (omega_sin, omega_cos) = long_asc_node.sin_cos(); let (i_sin, i_cos) = self.body.inclination.sin_cos(); let r = self.body.semi_major_axis * (1.0 - eccentricity * eccentric_anomaly.cos()); let x = r * (omega_cos * vw_cos - omega_sin * vw_sin * i_cos); let y = r * i_sin * vw_sin; let z = r * (omega_sin * vw_cos + omega_cos * vw_sin * i_cos); cgmath::Vector3::::new(x, y, z) } }