diff --git a/Cargo.lock b/Cargo.lock index c720d8a..2a155d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -499,7 +499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ab77dbd8adecaf3f0db40581631b995f312a8a5ae3aa9993188bb8f23d83a5b" dependencies = [ "crossterm", - "strum", + "strum 0.24.1", "strum_macros 0.24.3", "unicode-width", ] @@ -1555,6 +1555,7 @@ dependencies = [ "simplelog", "snafu", "stb_image", + "strum 0.25.0", "tobj", "uom", ] @@ -3324,6 +3325,15 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros 0.25.2", +] + [[package]] name = "strum_macros" version = "0.24.3" diff --git a/Cargo.toml b/Cargo.toml index 969df3b..51e2567 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ dunce = "1.0" uom = "0.35" regex = "1.10" kalast_macros = { path = "macros", version = "0.4.0-beta" } +strum = { version = "0.25", features = ["derive"] } [dev-dependencies] rstest = "0.18" diff --git a/examples/ground-based-lightcurve-didymos-spice/cfg/cfg.yaml b/examples/ground-based-lightcurve-didymos-spice/cfg/cfg.yaml index dfb2527..da502a4 100644 --- a/examples/ground-based-lightcurve-didymos-spice/cfg/cfg.yaml +++ b/examples/ground-based-lightcurve-didymos-spice/cfg/cfg.yaml @@ -8,11 +8,13 @@ simulation: export: step: 60 duration: 8136 + pause_after_first_iteration: true scene: - spice: true + spice: /Users/gregoireh/data/spice/dart/mk/d420.tm camera: - !earth 2.0 + from: Earth + distance: 2 bodies: - id: Didymos diff --git a/examples/viewer/cfg/cfg.yaml b/examples/viewer/cfg/cfg.yaml index 8add432..830e4aa 100644 --- a/examples/viewer/cfg/cfg.yaml +++ b/examples/viewer/cfg/cfg.yaml @@ -1,4 +1,4 @@ -win: +window: ambient: [0.1, 0.1, 0.1] bodies: diff --git a/preferences.yaml b/preferences.yaml index e69de29..c52908e 100644 --- a/preferences.yaml +++ b/preferences.yaml @@ -0,0 +1 @@ +no_check: true \ No newline at end of file diff --git a/src/body/asteroid.rs b/src/body/asteroid.rs index af501d3..41dd31c 100644 --- a/src/body/asteroid.rs +++ b/src/body/asteroid.rs @@ -85,11 +85,11 @@ impl AirlessBody { } } -pub fn matrix_spin(spin: Float) -> Mat4 { +pub fn matrix_spin(spin: Float, axis: Vec3) -> Mat4 { if spin == 0.0 { Mat4::identity() } else { - Mat4::new_rotation(spin * Vec3::z()) + Mat4::new_rotation(spin * axis) } } diff --git a/src/cfg/body.rs b/src/cfg/body.rs index 1ff98d3..f8398e8 100644 --- a/src/cfg/body.rs +++ b/src/cfg/body.rs @@ -531,6 +531,9 @@ pub enum CfgState { #[serde(rename = "file")] File(PathBuf), + + #[serde(rename = "spice")] + Spice(CfgStateSpice), } impl CfgState { @@ -757,6 +760,18 @@ impl Default for CfgOrbitSpeedControl { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CfgStateSpice { + #[serde(default)] + pub origin: Option, + + #[serde(default)] + pub frame: Option, + + #[serde(default)] + pub frame_to: Option, +} + /** # Configuration of the Initialisation of the Temperature of the Body. diff --git a/src/cfg/config.rs b/src/cfg/config.rs index c4d9e04..12da710 100644 --- a/src/cfg/config.rs +++ b/src/cfg/config.rs @@ -1,4 +1,4 @@ -use crate::{CfgBody, CfgBodyError, CfgPreferences, CfgScene, CfgSimulation, CfgWindow}; +use crate::{CfgBody, CfgBodyError, CfgPreferences, CfgScene, CfgSimulation, CfgSpice, CfgWindow}; use itertools::Itertools; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -168,6 +168,10 @@ pub struct Cfg { #[serde(default)] pub simulation: CfgSimulation, + #[cfg(feature = "spice")] + #[serde(default)] + pub spice: CfgSpice, + #[serde(default)] pub window: CfgWindow, @@ -292,6 +296,10 @@ impl Cfg { pub fn extra(&self) -> &HashMap { &self.extra } + + pub fn using_spice(&self) -> bool { + self.scene.spice.is_some() + } } impl Configuration for Cfg {} @@ -303,6 +311,10 @@ impl Default for Cfg { bodies: vec![], scene: CfgScene::default(), simulation: CfgSimulation::default(), + + #[cfg(feature = "spice")] + spice: CfgSpice::default(), + window: CfgWindow::default(), preferences: CfgPreferences::default(), extra: HashMap::new(), diff --git a/src/cfg/mod.rs b/src/cfg/mod.rs index 3805e13..2bdb97a 100644 --- a/src/cfg/mod.rs +++ b/src/cfg/mod.rs @@ -56,6 +56,7 @@ mod config; mod preferences; mod scene; mod simulation; +mod spice; mod window; pub use body::*; @@ -63,4 +64,5 @@ pub use config::*; pub use preferences::*; pub use scene::*; pub use simulation::*; +pub use spice::*; pub use window::*; diff --git a/src/cfg/preferences.rs b/src/cfg/preferences.rs index 9784648..c7297cc 100644 --- a/src/cfg/preferences.rs +++ b/src/cfg/preferences.rs @@ -10,6 +10,7 @@ pub struct CfgPreferences { pub runs: PathBuf, #[serde(default)] + #[serde(alias = "no_check")] pub do_not_check_latest_version: bool, #[serde(default)] @@ -28,8 +29,8 @@ impl Default for CfgPreferences { fn default() -> Self { Self { runs: default_runs(), - auto_update: false, do_not_check_latest_version: false, + auto_update: false, debug: false, debug_cfg: false, } diff --git a/src/cfg/scene.rs b/src/cfg/scene.rs index 6067c56..e73e2df 100644 --- a/src/cfg/scene.rs +++ b/src/cfg/scene.rs @@ -1,9 +1,9 @@ -use std::path::PathBuf; - -use crate::{util::*, CfgBodyError, CfgStateCartesian, Configuration, Equatorial}; +use crate::{util::*, CfgBodyError, Configuration, Equatorial}; use serde::{Deserialize, Serialize}; use snafu::{prelude::*, Location}; +use std::path::PathBuf; +use strum::{Display, EnumString}; pub type SceneResult = std::result::Result; @@ -41,57 +41,92 @@ impl Default for CfgScene { impl Configuration for CfgScene {} #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] pub enum CfgCamera { #[serde(rename = "position")] Position(Vec3), /// Camera in from the direction of the Sun (Sun behind camera). /// The float is the distance from the center of frame to the camera. - #[serde(rename = "sun")] - Sun(Float), - - #[serde(rename = "earth")] - Earth(Float), + /// + /// TODO: COMPLETE DOC + #[serde(rename = "from")] + From(CfgCameraFrom), } impl CfgCamera { - pub fn as_earth(&self) -> SceneResult { - match self { - Self::Earth(distance) => Ok(*distance), - _ => panic!("Not in Earth mode."), - } + pub fn default_position() -> Vec3 { + Vec3::x() * 5.0 } } impl Default for CfgCamera { fn default() -> Self { - Self::Sun(5.0) + Self::Position(Self::default_position()) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CfgCameraFrom { + #[serde(default)] + pub from: CfgCameraFromOptions, + + #[serde(default)] + #[serde(alias = "distance")] + pub distance_origin: Float, +} + +#[derive(Clone, Debug, Serialize, Deserialize, EnumString, Display)] +pub enum CfgCameraFromOptions { + #[serde(rename = "sun")] + #[serde(alias = "Sun")] + Sun, + + #[serde(rename = "earth")] + #[serde(alias = "Earth")] + Earth, +} + +impl Default for CfgCameraFromOptions { + fn default() -> Self { + Self::Sun } } #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(tag = "type")] +#[serde(untagged)] pub enum CfgSun { - #[serde(rename = "cartesian")] - #[serde(alias = "cart")] - Cartesian(CfgStateCartesian), + #[serde(rename = "position")] + Position(Vec3), #[serde(rename = "equatorial")] - #[serde(alias = "astro")] Equatorial(Equatorial), + + #[serde(rename = "from")] + From(CfgSunFrom), } impl CfgSun { - pub fn as_equatorial(&self) -> SceneResult<&Equatorial> { - match self { - Self::Equatorial(coords) => Ok(coords), - _ => panic!("Not equatorial coordinates."), - } + pub fn default_position() -> Vec3 { + Vec3::x() * Self::default_distance() + } + + pub fn default_distance() -> Float { + 1.0 } } impl Default for CfgSun { fn default() -> Self { - Self::Cartesian(CfgStateCartesian::position_only(Vec3::x())) + Self::Position(Self::default_position()) } } + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum CfgSunFrom { + #[serde(rename = "from_spice")] + Spice, + + #[serde(rename = "from_orbit_body")] + OrbitBody, +} diff --git a/src/cfg/simulation.rs b/src/cfg/simulation.rs index 484479d..5a0b99c 100644 --- a/src/cfg/simulation.rs +++ b/src/cfg/simulation.rs @@ -8,7 +8,8 @@ pub type SimulationResult = std::result::Result /// Errors related to Kalast config. #[derive(Debug, Snafu)] pub enum CfgSimulationError { - SpiceNotLoaded {}, + #[snafu(display("Feature `spice` is not enabled."))] + FeatureSpiceNotEnabled {}, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -66,7 +67,7 @@ impl TimeOption { #[cfg(feature = "spice")] return Ok(spice::str2et(_s)); #[cfg(not(feature = "spice"))] - return Err(CfgSimulationError::SpiceNotLoaded {}); + return Err(CfgSimulationError::FeatureSpiceNotEnabled {}); } } } diff --git a/src/cfg/spice.rs b/src/cfg/spice.rs new file mode 100644 index 0000000..694f208 --- /dev/null +++ b/src/cfg/spice.rs @@ -0,0 +1,31 @@ +use crate::Configuration; + +use serde::{Deserialize, Serialize}; +use snafu::prelude::*; +use std::path::PathBuf; + +pub type CfgSpiceResult = std::result::Result; + +/// Errors related to Kalast config. +#[derive(Debug, Snafu)] +pub enum CfgSpiceError {} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CfgSpice { + #[serde(default)] + pub kernel: Option, + + #[serde(default)] + pub frame: Option, +} + +impl Default for CfgSpice { + fn default() -> Self { + Self { + kernel: None, + frame: None, + } + } +} + +impl Configuration for CfgSpice {} diff --git a/src/simu/export.rs b/src/simu/export.rs index cdddfac..15c6aa6 100644 --- a/src/simu/export.rs +++ b/src/simu/export.rs @@ -172,7 +172,7 @@ impl Export { CsvWriter::new(&mut file).finish(&mut df).unwrap(); let mut df = df!( - "sunpos" => scene.sun_pos.as_slice(), + "sunpos" => scene.sun.as_slice(), ) .unwrap(); let mut file = std::fs::File::options() diff --git a/src/simu/routines/core.rs b/src/simu/routines/core.rs index 28941b1..d4c9ba0 100644 --- a/src/simu/routines/core.rs +++ b/src/simu/routines/core.rs @@ -1,109 +1,254 @@ use crate::{ compute_cosine_emission_angle, compute_cosine_incidence_angle, compute_cosine_phase_angle, - find_matrix_spin, find_matrix_translation, find_reference_matrix_orientation, simu::Scene, - update_colormap_scalar, util::*, AirlessBody, Cfg, CfgBody, CfgScalar, FoldersRun, + find_ref_orbit, matrix_spin, position_in_inertial_frame, simu::Scene, update_colormap_scalar, + util::*, AirlessBody, Cfg, CfgBody, CfgCamera, CfgCameraFrom, CfgCameraFromOptions, + CfgFrameCenter, CfgScalar, CfgState, CfgStateCartesian, CfgSun, CfgSunFrom, FoldersRun, PreComputedBody, Time, Window, }; #[cfg(feature = "spice")] -use crate::CfgCamera; - -#[cfg(not(feature = "spice"))] -use crate::{find_ref_orbit, position_in_inertial_frame, CfgState}; +use crate::CfgStateSpice; use downcast_rs::{impl_downcast, DowncastSync}; - -#[cfg(not(feature = "spice"))] -use itertools::Itertools; +use itertools::{izip, Itertools}; pub trait RoutinesData { fn new(asteroid: &AirlessBody, _cb: &CfgBody, _scene: &Scene) -> Self; } pub trait Routines: DowncastSync { - fn fn_update_scene(&self, cfg: &Cfg, time: &Time, scene: &mut Scene) { + fn fn_update_scene(&self, cfg: &Cfg, time: &Time, _scene: &Scene) -> Scene { let elapsed_from_start = time.elapsed_seconds_from_start(); - #[cfg(not(feature = "spice"))] - if let Some(body) = cfg.bodies.first() { - let other_bodies = cfg.bodies.iter().skip(1).collect_vec(); - - match &body.state { - CfgState::Equatorial(coords) => { - let distance = cfg.scene.camera.as_earth().unwrap(); - let position = coords.xyz(distance); - dbg!(position); - scene.cam_pos = -position; - - let coords_sun = cfg.scene.sun.as_equatorial().unwrap(); - let position_sun = coords_sun.xyz(distance); - dbg!(position_sun); - scene.sun_pos = scene.cam_pos + position_sun; + let sun = match &cfg.scene.sun { + CfgSun::Position(p) => *p, + CfgSun::Equatorial(coords) => coords.xyz(CfgSun::default_distance()), + CfgSun::From(from) => match from { + CfgSunFrom::Spice => { + if cfg.using_spice() { + #[cfg(not(feature = "spice"))] + { + panic!("Feature `spice` is not enabled. The feature is required to compute the position of the Sun.") + } + + #[cfg(feature = "spice")] + { + if let Some(body) = cfg.bodies.first() { + let (position, _lt) = spice::spkpos( + "Sun", + elapsed_from_start as f64, + "ECLIPJ2000", + "none", + &body.id, + ); + Vec3::from_row_slice(&position) + } else { + panic!("A body must be loaded to compute the position of the Sun.") + } + } + } else { + panic!("Spice is not being used and is needed to compute the position of the Sun. Try loading a spice kernel to enable spice.") + } } - CfgState::Orbit(orb) => { - let (mu_ref, factor) = find_ref_orbit(&orb, &other_bodies); - let pos = position_in_inertial_frame( - orb.a * factor, - orb.e, - orb.i * RPD, - orb.node * RPD, - orb.peri * RPD, - elapsed_from_start as Float, - orb.tp, - mu_ref, - ); - if mu_ref == MU_SUN { - scene.sun_pos = -pos; + CfgSunFrom::OrbitBody => { + if let Some(body) = cfg.bodies.first() { + match &body.state { + CfgState::Orbit(orbit) => match &orbit.frame { + CfgFrameCenter::Sun => -position_in_inertial_frame( + orbit.a * AU, + orbit.e, + orbit.i * RPD, + orbit.node * RPD, + orbit.peri * RPD, + elapsed_from_start as Float, + orbit.tp, + MU_SUN, + ), + CfgFrameCenter::Body(_) => CfgSun::default_position(), + }, + CfgState::Cartesian(_) + | CfgState::Equatorial(_) + | CfgState::File(_) + | CfgState::Spice(_) => CfgSun::default_position(), + } + } else { + panic!("A body must be loaded to compute the position of the Sun.") } } - _ => {} - }; - } + }, + }; - #[cfg(feature = "spice")] - if let Some(body) = cfg.bodies.first() { - let context = match cfg.scene.camera { - CfgCamera::Earth(distance) => ("Earth", "J2000", distance), - _ => panic!("Expecting Earth with distance for camera settings"), - }; - let (position, _lt) = spice::spkpos( - context.0, - elapsed_from_start as f64, - context.1, - "none", - &body.id, - ); - let position = Vec3::from_row_slice(&position); - - let (position_sun, _lt_sun) = spice::spkpos( - "Sun", - elapsed_from_start as f64, - context.1, - "none", - &body.id, - ); - let position_sun = Vec3::from_row_slice(&position_sun); - - scene.cam_pos = position.normalize() * context.2; - scene.sun_pos = position_sun.normalize() * context.2; - } + let camera = match &cfg.scene.camera { + CfgCamera::Position(p) => *p, + CfgCamera::From(CfgCameraFrom { + from, + distance_origin, + }) => match &from { + CfgCameraFromOptions::Sun => sun.normalize() * *distance_origin, + CfgCameraFromOptions::Earth => { + if let Some(_body) = cfg.bodies.first() { + if cfg.using_spice() { + #[cfg(not(feature = "spice"))] + { + panic!("Feature `spice` is not enabled. The feature is required to compute the position of the camera from Earth direction.") + } + + #[cfg(feature = "spice")] + { + let (position, _lt) = spice::spkpos( + "Earth", + elapsed_from_start as f64, + "ECLIPJ2000", + "none", + &_body.id, + ); + let position = Vec3::from_row_slice(&position); + + position.normalize() * *distance_origin + } + } else { + panic!("Spice is not being used and is needed to compute the position of the camera from Earth direction. Try loading a spice kernel to enable spice.") + } + } else { + panic!("A body must be loaded to compute the position of the camera from Earth direction. Visualisation is centered on body") + } + } + }, + }; + + Scene { camera, sun } } fn fn_update_matrix_model( &self, cfg: &Cfg, body: usize, - bodies: &mut [AirlessBody], pre_computed_bodies: &mut [PreComputedBody], time: &Time, _scene: &Scene, - ) { - let mat_orient_ref = find_reference_matrix_orientation(&cfg, body, pre_computed_bodies); - let mat_spin = find_matrix_spin(cfg, body, time); - let mat_translation = find_matrix_translation(cfg, body, time); + ) -> Mat4 { + let elapsed_from_start = time.elapsed_seconds_from_start(); + + match &cfg.bodies[body].state { + CfgState::Spice(_from) => { + #[cfg(not(feature = "spice"))] + panic!("Feature `spice` is not enabled. The feature is required to compute the position of the camera from Earth direction."); + + #[cfg(feature = "spice")] + { + let CfgStateSpice { + origin, + frame, + frame_to, + } = _from; + let frame = frame.clone().unwrap_or("J2000".to_string()); - bodies[body].matrix_model = - mat_orient_ref * mat_translation * pre_computed_bodies[body].mat_orient * mat_spin; + let position = { + if let Some(origin) = origin { + let (position, _lt) = spice::spkpos( + &cfg.bodies[body].id, + elapsed_from_start as f64, + &frame, + "none", + &origin, + ); + Vec3::from_row_slice(&position) + } else { + Vec3::zeros() + } + }; + + let rotation = { + if let Some(frame_to) = frame_to { + let rotation = + spice::pxform(&frame, frame_to, elapsed_from_start as f64); + Mat3::from_row_slice(&rotation.iter().cloned().flatten().collect_vec()) + } else { + Mat3::identity() + } + }; + + let mut matrix_model = Mat4::new_translation(&position); + + for (e, new) in izip!( + matrix_model.fixed_view_mut::<3, 3>(1, 1).iter_mut(), + rotation.iter() + ) { + *e = *new; + } + + matrix_model + } + } + anything_else => { + let mut matrix_orientation_reference = Mat4::identity(); + + let mut matrix_orientation = pre_computed_bodies[body].mat_orient; + + let elapsed = time.elapsed_seconds(); + let np_elapsed = if cfg.bodies[body].spin.period == 0.0 { + 0.0 + } else { + elapsed as Float / cfg.bodies[body].spin.period + }; + let spin_angle = (TAU * np_elapsed + cfg.bodies[body].spin.spin0 * RPD) % TAU; + let matrix_spin = matrix_spin(spin_angle, cfg.bodies[body].spin.axis); + + let mut matrix_translation = Mat4::identity(); + + let other_bodies = cfg + .bodies + .iter() + .enumerate() + .filter(|(ii, _)| *ii != body) + .map(|(_, cb)| cb) + .collect_vec(); + + match &anything_else { + CfgState::Cartesian(CfgStateCartesian { + position, + orientation, + }) => { + matrix_orientation = glm::mat3_to_mat4(orientation); + matrix_translation = Mat4::new_translation(position); + } + CfgState::Orbit(orbit) => { + let (mu_ref, factor) = find_ref_orbit(&orbit, &other_bodies); + if mu_ref != MU_SUN { + let pos = position_in_inertial_frame( + orbit.a * factor, + orbit.e, + orbit.i * RPD, + orbit.node * RPD, + orbit.peri * RPD, + elapsed_from_start as Float, + orbit.tp, + mu_ref, + ); + matrix_translation = Mat4::new_translation(&(pos * 1e-3)); + } + + match &orbit.frame { + CfgFrameCenter::Sun => {} + CfgFrameCenter::Body(id) => { + for (pre, cb) in izip!(pre_computed_bodies.iter_mut(), &cfg.bodies) + { + if cb.id == *id { + matrix_orientation_reference = pre.mat_orient; + break; + } + } + } + } + } + CfgState::Equatorial(_) | CfgState::File(_) => {} + _ => panic!("tempo"), + }; + + let matrix_body = matrix_translation * matrix_orientation * matrix_spin; + matrix_orientation_reference * matrix_body + } + } } fn fn_iteration_body( @@ -177,7 +322,7 @@ pub trait Routines: DowncastSync { _scene: &Scene, win: &Window, ) { - if cfg.simulation.pause_after_first_iteration { + if cfg.simulation.pause_after_first_iteration || cfg.simulation.step == 0 { win.toggle_pause(); } } diff --git a/src/simu/routines/thermal.rs b/src/simu/routines/thermal.rs index 03bfa87..f6fc1cf 100644 --- a/src/simu/routines/thermal.rs +++ b/src/simu/routines/thermal.rs @@ -94,7 +94,7 @@ impl RoutinesData for ThermalData { let ratio = ratio.0 as Float / ratio.1 as Float; let mat = asteroid.surface.faces[0].vertex.material; - let init = effective_temperature(&scene.sun_pos, mat.albedo, mat.emissivity, ratio); + let init = effective_temperature(&scene.sun, mat.albedo, mat.emissivity, ratio); DMatrix::::from_element(depth_size, surf_size, init) } CfgTemperatureInit::Scalar(scalar) => { @@ -190,7 +190,7 @@ impl Routines for RoutinesThermalDefault { let mut shadows_mutual: Vec = vec![]; for other_body in other_bodies { - shadows_mutual = shadows(&scene.sun_pos, &bodies[body], &bodies[other_body]); + shadows_mutual = shadows(&scene.sun, &bodies[body], &bodies[other_body]); } for &index in shadows_mutual.iter().chain(&shadows_self).unique() { diff --git a/src/simu/scenario.rs b/src/simu/scenario.rs index 4feec8c..243a73a 100644 --- a/src/simu/scenario.rs +++ b/src/simu/scenario.rs @@ -1,8 +1,7 @@ use crate::{ check_if_latest_version, read_surface_main, simu::Scene, util::*, AirlessBody, Cfg, CfgCamera, - CfgInterior, CfgInteriorGrid1D, CfgRoutines, CfgStateCartesian, CfgSun, Equatorial, Export, - FoldersRun, FrameEvent, PreComputedBody, Result, Routines, RoutinesThermalDefault, - RoutinesViewerDefault, Time, Window, + CfgInterior, CfgInteriorGrid1D, CfgRoutines, CfgSun, Export, FoldersRun, FrameEvent, + PreComputedBody, Result, Routines, RoutinesThermalDefault, RoutinesViewerDefault, Time, Window, }; use chrono::Utc; @@ -67,23 +66,11 @@ impl Scenario { spice::furnsh(path.to_str().unwrap()); } - let sun_pos = match &cfg.scene.sun { - CfgSun::Cartesian(CfgStateCartesian { position, .. }) => position * AU, - CfgSun::Equatorial(Equatorial { ra: _ra, dec: _dec }) => Vec3::x() * AU, + let scene = Scene { + sun: CfgSun::default_position(), + camera: CfgCamera::default_position(), }; - // If camera is from Earth, we cannot give a correct value for position now. - // This is because in the background the simulation is always centered on the asteroid. - // So we wait until we have information on asteroid position with respect to Earth and then we invert it - // to place Earth from asteroid to make our simulation happy. - // Happy because otherwise it would be a mess to move camera and projection frustrum from developer side. - let cam_pos = match &cfg.scene.camera { - CfgCamera::Position(p) => *p, - CfgCamera::Sun(d) | CfgCamera::Earth(d) => sun_pos.normalize() * *d, - }; - - let scene = Scene { sun_pos, cam_pos }; - let win = Window::with_settings(|s| { s.width = cfg.window.width; s.height = cfg.window.height; @@ -100,12 +87,17 @@ impl Scenario { s.draw_normals = cfg.window.normals; s.normals_magnitude = cfg.window.normals_length; }) - .with_camera_position(&scene.cam_pos) + .with_camera_position(&scene.camera) .with_light_position(&scene.sun_pos_cubelight()); + let time_start = match cfg.simulation.start.seconds() { + Ok(seconds) => seconds, + Err(e) => panic!("{e} Spice is required to convert the starting date of the simulation to ephemeris time."), + } as usize; + let time = Time::new() .with_time_step(cfg.simulation.step) - .with_time_start(cfg.simulation.start.seconds().unwrap() as _); + .with_time_start(time_start); Ok(Self { cfg, @@ -202,7 +194,7 @@ impl Scenario { let event = self.win.events(); // Update camera data from keyboard movements. - self.scene.cam_pos = self.win.camera_position(); + self.scene.camera = self.win.camera_position(); match event { FrameEvent::Exit => break 'main_loop, @@ -223,14 +215,14 @@ impl Scenario { let elapsed = self.time.elapsed_seconds(); let jd = self.time.jd(); - self.routines - .fn_update_scene(&self.cfg, &self.time, &mut self.scene); + self.scene = self + .routines + .fn_update_scene(&self.cfg, &self.time, &self.scene); for body in 0..self.bodies.len() { - self.routines.fn_update_matrix_model( + self.bodies[body].matrix_model = self.routines.fn_update_matrix_model( &self.cfg, body, - &mut self.bodies, &mut self.pre_computed_bodies, &self.time, &mut self.scene, @@ -253,7 +245,7 @@ impl Scenario { ); } - self.win.set_camera_position(&self.scene.cam_pos); + self.win.set_camera_position(&self.scene.camera); self.win.set_light_direction(&self.scene.sun_dir()); // self.win.update_vaos(self.bodies.iter_mut().map(|b| &mut b.asteroid_mut().surface)); diff --git a/src/simu/scene.rs b/src/simu/scene.rs index 6d727eb..6687258 100644 --- a/src/simu/scene.rs +++ b/src/simu/scene.rs @@ -1,24 +1,25 @@ use crate::util::*; +#[derive(Debug, Clone)] pub struct Scene { - pub sun_pos: Vec3, - pub cam_pos: Vec3, + pub camera: Vec3, + pub sun: Vec3, } impl Scene { pub fn sun_dir(&self) -> Vec3 { - self.sun_pos.normalize() + self.sun.normalize() } pub fn sun_dist(&self) -> Float { - self.sun_pos.magnitude() + self.sun.magnitude() } pub fn cam_dir(&self) -> Vec3 { - self.cam_pos.normalize() + self.camera.normalize() } pub fn cam_dist(&self) -> Float { - self.cam_pos.magnitude() + self.camera.magnitude() } pub fn sun_pos_cubelight(&self) -> Vec3 { diff --git a/src/simu/util.rs b/src/simu/util.rs index 4a3baa2..51ca8a2 100644 --- a/src/simu/util.rs +++ b/src/simu/util.rs @@ -1,7 +1,6 @@ use crate::{ - cosine_angle, matrix_spin, position_in_inertial_frame, simu::Scene, util::*, AirlessBody, Cfg, - CfgBody, CfgFrameCenter, CfgMeshKind, CfgMeshSource, CfgOrbitKepler, CfgOrbitSpeedControl, - CfgState, CfgStateCartesian, Material, PreComputedBody, Result, Surface, Time, Window, + cosine_angle, simu::Scene, util::*, AirlessBody, Cfg, CfgBody, CfgFrameCenter, CfgMeshKind, + CfgMeshSource, CfgOrbitKepler, CfgOrbitSpeedControl, Material, Result, Surface, Window, }; use itertools::{izip, Itertools}; @@ -48,81 +47,6 @@ pub fn read_surface_low(cb: &CfgBody) -> Result { read_surface(cb, CfgMeshKind::Low) } -pub fn find_reference_matrix_orientation( - cfg: &Cfg, - body: usize, - pre_computed_bodies: &mut [PreComputedBody], -) -> Mat4 { - match &cfg.bodies[body].state { - CfgState::Cartesian(CfgStateCartesian { orientation, .. }) => { - glm::mat3_to_mat4(&orientation) - } - CfgState::Equatorial(_) => Mat4::identity(), - CfgState::File(_p) => Mat4::identity(), - CfgState::Orbit(orb) => match &orb.frame { - CfgFrameCenter::Sun => Mat4::identity(), - CfgFrameCenter::Body(id) => { - let mut mat = Mat4::identity(); - for (pre, cb) in izip!(pre_computed_bodies, &cfg.bodies) { - if cb.id == *id { - mat = pre.mat_orient; - break; - } - } - mat - } - }, - } -} - -pub fn find_matrix_translation(cfg: &Cfg, body: usize, time: &Time) -> Mat4 { - let elapsed_from_start = time.elapsed_seconds_from_start(); - let other_bodies = cfg - .bodies - .iter() - .enumerate() - .filter(|(ii, _)| *ii != body) - .map(|(_, cb)| cb) - .collect_vec(); - - match &cfg.bodies[body].state { - CfgState::Cartesian(CfgStateCartesian { - position, - orientation: _orientation, - }) => Mat4::new_translation(position), - CfgState::Equatorial(_) | CfgState::File(_) => Mat4::identity(), - CfgState::Orbit(orb) => { - let (mu_ref, factor) = find_ref_orbit(&orb, &other_bodies); - if mu_ref == MU_SUN { - Mat4::identity() - } else { - let pos = position_in_inertial_frame( - orb.a * factor, - orb.e, - orb.i * RPD, - orb.node * RPD, - orb.peri * RPD, - elapsed_from_start as Float, - orb.tp, - mu_ref, - ); - Mat4::new_translation(&(pos * 1e-3)) - } - } - } -} - -pub fn find_matrix_spin(cfg: &Cfg, body: usize, time: &Time) -> Mat4 { - let elapsed = time.elapsed_seconds(); - if cfg.bodies[body].spin.period == 0.0 { - Mat4::identity() - } else { - let np_elapsed = elapsed as Float / cfg.bodies[body].spin.period; - let spin = (TAU * np_elapsed + cfg.bodies[body].spin.spin0 * RPD) % TAU; - matrix_spin(spin) - } -} - /// return MU and factor for distances. pub fn find_ref_orbit(orbit: &CfgOrbitKepler, cfgs: &[&CfgBody]) -> (Float, Float) { match &orbit.frame { @@ -198,9 +122,9 @@ pub fn compute_cosine_phase_angle(body: &AirlessBody, scene: &Scene) -> DRVector .map(|f| { let v4 = glm::vec3_to_vec4(&f.vertex.position); let v3_oriented = glm::vec4_to_vec3(&(body.matrix_model * v4)); - (scene.sun_pos - v3_oriented) + (scene.sun - v3_oriented) .normalize() - .dot(&(scene.cam_pos - v3_oriented).normalize()) + .dot(&(scene.camera - v3_oriented).normalize()) }) .collect_vec(), )