pub mod orbit; pub mod ship; use serde::{Deserialize}; use crate::{known_stars::*, solar_system::orbit::StaticOrbit, timeman::Second}; use std::error::Error; 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 CSVOrbitalBody { 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 { name: String, mass: Kilograms, radius: Kilometers, sgp: f64, orbit: Option, position: Option> } pub struct SolarSystem { id: SystemId, name: String, bodies: Vec, } pub fn update_systems( solar_systems: std::slice::IterMut<'_, SolarSystem>, time: Second) { for system in solar_systems { system.update(time); } } 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: CSVOrbitalBody = result?; match record.orbits { Some(orbits) => { if record.sgp == 0.0 { record.sgp = (bodies[orbits].mass + record.mass) * GRAVITATIONAL_CONSTANT; } } None => {} } if record.radius == 0.0 { continue; } println!("New body: {:?}", record); bodies.push(OrbitalBody { name: record.name, mass: record.mass, radius: record.radius, sgp: record.sgp, orbit: Some(StaticOrbit { parent: record.orbits, eccentricity: record.eccentricity, inclination: record.inclination, long_asc_node: record.long_asc_node, long_periapsis: record.long_periapsis, mean_long: record.mean_long, semi_major_axis: record.semi_major_axis }), position: None }); } Ok(Self { id: id, name: bodies[0].name().clone(), bodies: bodies, }) } 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() } pub fn body_position( &self, body: &OrbitalBody) -> cgmath::Vector3 { match &body.orbit { Some(orbit) => { match orbit.parent { Some(parent) => { let this_pos = body.position(); let parent_pos = self.bodies[parent].position(); this_pos + parent_pos }, None => body.position() } }, None => body.position() } } pub fn update( &mut self, time: Second) { self.bodies.iter_mut().for_each(|body| { body.position = Some(body.calculate_orbit_at(time)); }); } } impl OrbitalBody { pub fn name(&self) -> &String { &self.name } pub fn radius(&self) -> f32 { self.radius as f32 } pub fn position(&self) -> cgmath::Vector3 { self.position.unwrap() } pub fn does_orbit(&self) -> bool { match &self.orbit { Some(orbit) => orbit.parent.is_some(), None => false } } pub fn get_orbits(&self) -> Option { match &self.orbit { Some(orbit) => orbit.parent(), None => None } } pub fn orbital_period(&self) -> Second { match &self.orbit { Some(v) => v.period(self), None => return 0 } } pub fn calculate_orbit_at( &self, time: Second) -> cgmath::Vector3 { let orbit = match &self.orbit { Some(v) => v, None => return cgmath::vec3(0.0, 0.0, 0.0) }; if orbit.parent.is_none() { return cgmath::Vector3::::new(0.0, 0.0, 0.0); } let eccentricity = orbit.eccentricity; let long_asc_node = orbit.long_asc_node; let arg_periaps = orbit.long_periapsis - long_asc_node; let sma_cubed = orbit.semi_major_axis.powf(3.0); let mean_motion = (self.sgp / sma_cubed).sqrt(); let mean_anomaly_epoch = orbit.mean_long - orbit.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) = orbit.inclination.sin_cos(); let r = orbit.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) } }