diff --git a/simulator/src/main.rs b/simulator/src/main.rs index fff855d..f7462f5 100644 --- a/simulator/src/main.rs +++ b/simulator/src/main.rs @@ -1,5 +1,5 @@ use cgmath::{Rotation3, Vector3}; -use solar_engine::{Application, Body, Key, KeyState, Light, LightType, Simulator}; +use solar_engine::{Application, Body, InputEvent, Key, Light, LightType, MouseButton, Simulator}; use std::sync::{Arc, RwLock}; use std::thread; @@ -21,6 +21,17 @@ pub async fn run() { mass: 5.972e24, radius: 6.371e6, }); + + let earth_position = sim.bodies[1].position; + let earth_velocity = sim.bodies[1].velocity; + + sim.add_body(Body { + name: "Moon".into(), + position: earth_position + Vector3::new(384.4e6, 0.0, 0.0), + velocity: earth_velocity + Vector3::new(0.0, 1022.0, 0.0), + mass: 7.342e22, + radius: 1.737e6, + }); } let sim_clone = simulator.clone(); @@ -81,30 +92,50 @@ pub async fn run() { state.set_instances(instances); }) - .on_input(move |state, event| { - if event.state == KeyState::Pressed { - return match event.key { - Key::Period => { - let mut sim = simulator.write().unwrap(); - sim.increase_timewarp(); - println!("Timewarp: {}", sim.get_timewarp()); + .on_input({ + let simulator = simulator.clone(); + move |state, event| { + match event { + InputEvent::MouseDragged { delta, button: MouseButton::Left } => { + state.camera_mut().rotate_yaw_pitch(-delta.x as f32 * 0.1, delta.y as f32 * 0.1); } - Key::Comma => { - let mut sim = simulator.write().unwrap(); - sim.decrease_timewarp(); - println!("Timewarp: {}", sim.get_timewarp()); + InputEvent::MouseWheel { delta } => { + state.camera_mut().zoom(delta * 0.05); } - Key::Minus => { - let mut sim = simulator.write().unwrap(); - sim.reset_timewarp(); - println!("Timewarp: {}", sim.get_timewarp()); + InputEvent::KeyPressed { key, .. } => { + match key { + Key::ArrowLeft => { + state.camera_mut().translate(Vector3::new(-1.0, 0.0, 0.0)); + } + Key::ArrowRight => { + state.camera_mut().translate(Vector3::new(1.0, 0.0, 0.0)); + } + Key::ArrowUp => { + state.camera_mut().translate(Vector3::new(0.0, 1.0, 0.0)); + } + Key::ArrowDown => { + state.camera_mut().translate(Vector3::new(0.0, -1.0, 0.0)); + } + Key::Period => { + let mut sim = simulator.write().unwrap(); + sim.increase_timewarp(); + println!("Timewarp: {}", sim.get_timewarp()); + } + Key::Comma => { + let mut sim = simulator.write().unwrap(); + sim.decrease_timewarp(); + println!("Timewarp: {}", sim.get_timewarp()); + } + Key::Minus => { + let mut sim = simulator.write().unwrap(); + sim.reset_timewarp(); + println!("Timewarp: {}", sim.get_timewarp()); + } + _ => {} + } } - Key::ArrowLeft => state.camera_mut().rotate_yaw_pitch(-5.0, 0.0), - Key::ArrowRight => state.camera_mut().rotate_yaw_pitch(5.0, 0.0), - Key::ArrowUp => state.camera_mut().zoom(-0.2), - Key::ArrowDown => state.camera_mut().zoom(0.2), _ => {} - }; + } } }) .run(); diff --git a/solar_engine/src/application.rs b/solar_engine/src/application.rs index 5dd7997..c7b84d5 100644 --- a/solar_engine/src/application.rs +++ b/solar_engine/src/application.rs @@ -1,19 +1,20 @@ use winit::application::ApplicationHandler; -use winit::event::{Modifiers, WindowEvent}; +use winit::event::{ElementState, Modifiers, MouseScrollDelta, WindowEvent}; use winit::event_loop::ActiveEventLoop; use winit::window::{Window, WindowId}; -use crate::input::{from_winit_input, InputEvent}; +use crate::input::{InputEvent, InputTracker}; pub struct StateApplication<'a> { state: Option>, modifiers: Modifiers, update_fn: Option) + 'a>>, input_fn: Option, &InputEvent) + 'a>>, + input_tracker: InputTracker } impl<'a> StateApplication<'a> { pub fn new() -> Self { - Self { state: None, update_fn: None, input_fn: None, modifiers: Modifiers::default() } + Self { state: None, update_fn: None, input_fn: None, modifiers: Modifiers::default(), input_tracker: InputTracker::default() } } pub fn on_update) + 'a>(mut self, func: F) -> Self { @@ -63,18 +64,18 @@ impl<'a> ApplicationHandler for StateApplication<'a> { self.state.as_mut().unwrap().render().unwrap(); } - WindowEvent::KeyboardInput { event, .. } => { - if let Some(state) = self.state.as_mut() { - if let Some(input_fn) = self.input_fn.as_mut() { - let key_event = from_winit_input(&event, self.modifiers); - input_fn(state, &key_event); - } - } - } WindowEvent::ModifiersChanged(modifiers) => { self.modifiers = modifiers; } - _ => {} + _ => { + if let Some(state) = self.state.as_mut() { + if let Some(event) = self.input_tracker.handle_window_event(&event, self.modifiers) { + if let Some(input_fn) = self.input_fn.as_mut() { + input_fn(state, &event); + } + } + } + } } } } diff --git a/solar_engine/src/camera.rs b/solar_engine/src/camera.rs index 4ff3e5a..2cee654 100644 --- a/solar_engine/src/camera.rs +++ b/solar_engine/src/camera.rs @@ -35,19 +35,27 @@ impl Camera { } pub fn rotate_yaw_pitch(&mut self, yaw: f32, pitch: f32) { - let dir = (self.target - self.eye).normalize(); + let offset = self.eye - self.target; + let distance = offset.magnitude(); + let yaw_q = Quaternion::from_axis_angle(Vector3::unit_y(), Rad(yaw.to_radians())); + let right = offset.cross(self.up).normalize(); + let pitch_q = Quaternion::from_axis_angle(right, Rad(pitch.to_radians())); + + let rotation = yaw_q * pitch_q; + let new_offset = rotation.rotate_vector(offset); + + self.eye = self.target + new_offset; + self.up = rotation.rotate_vector(self.up); + } + + pub fn translate(&mut self, translation: Vector3) { + let dir = (self.target - self.eye).normalize(); let horizontal = Vector3::unit_y().cross(dir).normalize(); let vertical = horizontal.cross(dir).normalize(); - let pitch_q = Quaternion::from_axis_angle(vertical, Rad(pitch.to_radians())); - let yaw_q = Quaternion::from_axis_angle(Vector3::unit_y(), Rad(yaw.to_radians())); - - let rotation = yaw_q * pitch_q; - let rotated = rotation.rotate_vector(dir).normalize(); - - let distance = (self.target - self.eye).magnitude(); - self.eye = self.target - rotated * distance; + self.eye += horizontal * translation.x + vertical * translation.y; + self.target += horizontal * translation.x + vertical * translation.y; } pub fn zoom(&mut self, amount: f32) { diff --git a/solar_engine/src/input.rs b/solar_engine/src/input.rs index edc1c3d..21d3059 100644 --- a/solar_engine/src/input.rs +++ b/solar_engine/src/input.rs @@ -1,5 +1,5 @@ -use log::info; -use winit::event::{ElementState, KeyEvent, Modifiers}; +use cgmath::Vector2; +use winit::event::{ElementState, KeyEvent, MouseScrollDelta, Modifiers, WindowEvent}; use winit::keyboard::{Key as WinitKey, ModifiersKeyState}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -33,16 +33,9 @@ pub enum Key { Numpad5, Numpad6, Numpad7, Numpad8, Numpad9, NumpadAdd, NumpadSubtract, NumpadMultiply, NumpadDivide, NumpadEnter, - // Unknown Unknown, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum KeyState { - Pressed, - Released, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct KeyModifiers { pub lshift: bool, @@ -55,35 +48,121 @@ pub struct KeyModifiers { pub lsuper_key: bool, } -#[derive(Debug, Clone)] -pub struct InputEvent { - pub key: Key, - pub state: KeyState, - pub text: String, - pub modifiers: KeyModifiers, +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum MouseButton { + Left, + Right, + Middle, + Other(u16), } -pub fn from_winit_input(event: &KeyEvent, modifiers: Modifiers) -> InputEvent { - InputEvent { - key: map_winit_key(&event.logical_key), - state: match event.state { - ElementState::Pressed => KeyState::Pressed, - ElementState::Released => KeyState::Released, +#[derive(Debug, Clone)] +pub enum InputEvent { + KeyPressed { + key: Key, + modifiers: KeyModifiers, + text: String, + }, + KeyReleased { + key: Key, + modifiers: KeyModifiers, + }, + MouseMoved { + position: (f64, f64), + }, + MouseDragged { + delta: Vector2, + button: MouseButton, + }, + MouseWheel { + delta: f32, + }, +} + +#[derive(Default)] +pub struct InputTracker { + pub last_cursor_pos: Option<(f64, f64)>, + pub dragging_button: Option, +} + +impl InputTracker { + pub fn handle_window_event(&mut self, event: &WindowEvent, modifiers: Modifiers) -> Option { + match event { + WindowEvent::KeyboardInput { event, .. } => { + Some(handle_keyboard_input(event, modifiers)) + } + WindowEvent::CursorMoved { position, .. } => { + if let (Some(last), Some(button)) = (self.last_cursor_pos, self.dragging_button) { + let delta = Vector2::new(position.x - last.0, position.y - last.1); + self.last_cursor_pos = Some((position.x, position.y)); + Some(InputEvent::MouseDragged { delta, button }) + } else { + self.last_cursor_pos = Some((position.x, position.y)); + Some(InputEvent::MouseMoved { + position: (position.x, position.y), + }) + } + } + WindowEvent::MouseInput { state, button, .. } => { + let mapped = map_button(*button); + match state { + ElementState::Pressed => { + self.dragging_button = Some(mapped); + } + ElementState::Released => { + self.dragging_button = None; + } + } + None + } + WindowEvent::MouseWheel { delta, .. } => { + let scroll = match delta { + MouseScrollDelta::LineDelta(_, y) => *y, + MouseScrollDelta::PixelDelta(p) => p.y as f32, + }; + Some(InputEvent::MouseWheel { delta: scroll }) + } + _ => None, + } + } +} + +fn handle_keyboard_input(event: &KeyEvent, modifiers: Modifiers) -> InputEvent { + let key = map_winit_key(&event.logical_key); + let mods = KeyModifiers { + lshift: modifiers.lshift_state() == ModifiersKeyState::Pressed, + rshift: modifiers.rshift_state() == ModifiersKeyState::Pressed, + lcontrol: modifiers.lcontrol_state() == ModifiersKeyState::Pressed, + rcontrol: modifiers.rcontrol_state() == ModifiersKeyState::Pressed, + lalt: modifiers.lalt_state() == ModifiersKeyState::Pressed, + ralt: modifiers.ralt_state() == ModifiersKeyState::Pressed, + rsuper_key: modifiers.rsuper_state() == ModifiersKeyState::Pressed, + lsuper_key: modifiers.lsuper_state() == ModifiersKeyState::Pressed, + }; + + match event.state { + ElementState::Pressed => InputEvent::KeyPressed { + key, + modifiers: mods, + text: event.text.clone().unwrap_or_default().into(), }, - text: event.text.clone().unwrap_or_default().into(), - modifiers: KeyModifiers { - lshift: modifiers.lshift_state() == ModifiersKeyState::Pressed, - rshift: modifiers.rshift_state() == ModifiersKeyState::Pressed, - lcontrol: modifiers.lcontrol_state() == ModifiersKeyState::Pressed, - rcontrol: modifiers.rcontrol_state() == ModifiersKeyState::Pressed, - lalt: modifiers.lalt_state() == ModifiersKeyState::Pressed, - ralt: modifiers.ralt_state() == ModifiersKeyState::Pressed, - rsuper_key: modifiers.rsuper_state() == ModifiersKeyState::Pressed, - lsuper_key: modifiers.lsuper_state() == ModifiersKeyState::Pressed, + ElementState::Released => InputEvent::KeyReleased { + key, + modifiers: mods, }, } } +pub fn map_button(button: winit::event::MouseButton) -> MouseButton { + match button { + winit::event::MouseButton::Left => MouseButton::Left, + winit::event::MouseButton::Right => MouseButton::Right, + winit::event::MouseButton::Middle => MouseButton::Middle, + winit::event::MouseButton::Other(n) => MouseButton::Other(n), + _ => MouseButton::Other(0), + } +} + pub fn map_winit_key(key: &WinitKey) -> Key { use Key::*; use winit::keyboard::NamedKey; diff --git a/solar_engine/src/lib.rs b/solar_engine/src/lib.rs index 660fddc..8f93fa4 100644 --- a/solar_engine/src/lib.rs +++ b/solar_engine/src/lib.rs @@ -22,7 +22,7 @@ pub use state::State; pub use input::Key; pub use input::map_winit_key; pub use input::InputEvent; -pub use input::KeyState; +pub use input::MouseButton; pub use light::Light; pub use light::LightType; \ No newline at end of file diff --git a/solar_engine/src/simulator.rs b/solar_engine/src/simulator.rs index 6bfa553..74c39f0 100644 --- a/solar_engine/src/simulator.rs +++ b/solar_engine/src/simulator.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::sync::Mutex; use cgmath::{InnerSpace, Vector3}; use crate::body::Body; @@ -51,40 +52,30 @@ impl Simulator { let masses: Vec = self.bodies.iter().map(|b| b.mass).collect(); - fn compute_accelerations(states: &[State], masses: &[f64]) -> Vec> { - let n = states.len(); - let accels = (0..n).map(|_| Mutex::new(Vector3::new(0.0, 0.0, 0.0))).collect::>(); + fn compute_accelerations(states: &[State], masses: &[f64], ownership: &HashMap) -> Vec> { + let mut accels = vec![Vector3::new(0.0, 0.0, 0.0); states.len()]; - (0..n).into_par_iter().for_each(|i| { - for j in (i + 1)..n { - let r = states[j].position - states[i].position; - let dist_sq = r.magnitude2(); - let dist = dist_sq.sqrt(); + for (&i, &j) in ownership { + let r = states[j].position - states[i].position; + let dist_sq = r.magnitude2(); + let dist = dist_sq.sqrt(); - if dist < 1e-8 { - continue; - } - - let force = G * masses[i] * masses[j] / dist_sq; - let accel = force * r / (dist * masses[i]); - let accel_j = -force * r / (dist * masses[j]); - - { - let mut a_i_lock = accels[i].lock().unwrap(); - *a_i_lock += accel; - } - { - let mut a_j_lock = accels[j].lock().unwrap(); - *a_j_lock += accel_j; - } + if dist < 1e-8 { + continue; } - }); - accels.into_iter().map(|mutex| mutex.into_inner().unwrap()).collect() + let force = G * masses[i] * masses[j] / dist_sq; + let accel = force * r / (dist * masses[i]); + + accels[i] += accel; + } + + accels } + let ownership = self.compute_soi_owners(); let k1_pos = original_states.iter().map(|s| s.velocity).collect::>(); - let k1_vel = compute_accelerations(&original_states, &masses); + let k1_vel = compute_accelerations(&original_states, &masses, &ownership); let mut temp_states: Vec = original_states.iter().enumerate().map(|(i, s)| { State { @@ -94,7 +85,7 @@ impl Simulator { }).collect(); let k2_pos = temp_states.iter().map(|s| s.velocity).collect::>(); - let k2_vel = compute_accelerations(&temp_states, &masses); + let k2_vel = compute_accelerations(&temp_states, &masses, &ownership); for i in 0..n { temp_states[i].position = original_states[i].position + k2_pos[i] * (dt / 2.0); @@ -102,7 +93,7 @@ impl Simulator { } let k3_pos = temp_states.iter().map(|s| s.velocity).collect::>(); - let k3_vel = compute_accelerations(&temp_states, &masses); + let k3_vel = compute_accelerations(&temp_states, &masses, &ownership); for i in 0..n { temp_states[i].position = original_states[i].position + k3_pos[i] * dt; @@ -110,7 +101,7 @@ impl Simulator { } let k4_pos = temp_states.iter().map(|s| s.velocity).collect::>(); - let k4_vel = compute_accelerations(&temp_states, &masses); + let k4_vel = compute_accelerations(&temp_states, &masses, &ownership); for i in 0..n { let body = &mut self.bodies[i]; @@ -122,6 +113,33 @@ impl Simulator { self.time += dt; } + fn compute_soi_owners(&self) -> HashMap { + let mut ownership = HashMap::new(); + for (i, body) in self.bodies.iter().enumerate() { + let mut min_distance = f64::MAX; + let mut dominant_index = None; + + for (j, other) in self.bodies.iter().enumerate() { + if i == j { + continue; + } + + let r = (body.position - other.position).magnitude(); + let soi_radius = r * (body.mass / other.mass).powf(2.0 / 5.0); + + if r < soi_radius && r < min_distance { + min_distance = r; + dominant_index = Some(j); + } + } + + if let Some(j) = dominant_index { + ownership.insert(i, j); + } + } + ownership + } + pub fn increase_timewarp(&mut self) { if let Some(new) = self.timewarp.checked_mul(2) { if new <= MAX_TIMEWARP {