From 3dc92fad981e28c760f3c6e95f5a8153ea6c9be4 Mon Sep 17 00:00:00 2001 From: Jon Santmyer Date: Wed, 15 Apr 2026 22:22:45 -0400 Subject: work on orbits (not right yet), orbit camera --- assets/shaders/tacbody.wgsl | 20 ++---- assets/systems/sol.csv | 2 +- src/main.rs | 13 +++- src/solar_system.rs | 136 ++++++++++++++++++++++----------------- src/tacmap.rs | 7 ++- src/tacmap/camera.rs | 150 ++++++++++++++++++++++++++++++++++++-------- src/tacmap/render.rs | 7 +-- src/window/ui.rs | 40 +++++++++--- 8 files changed, 261 insertions(+), 114 deletions(-) diff --git a/assets/shaders/tacbody.wgsl b/assets/shaders/tacbody.wgsl index e8fb653..99392ac 100644 --- a/assets/shaders/tacbody.wgsl +++ b/assets/shaders/tacbody.wgsl @@ -31,33 +31,25 @@ fn vs_main( var view = camera.view; //Billboard the circle - view[0][0] = 1.0; - view[0][1] = 0.0; - view[0][2] = 0.0; - - view[1][0] = 0.0; - view[1][1] = 1.0; - view[1][2] = 0.0; - - view[2][0] = 0.0; - view[2][1] = 0.0; - view[2][2] = 1.0; + let camera_right = vec3(view[0][0], view[1][0], view[2][0]); + let camera_up = vec3(view[0][1], view[1][1], view[2][1]); + let model_pos = camera_right * model.position.x + + camera_up * model.position.y; let min_size = 0.025; //Scale the world around the camera scale. - let model_pos = model.position; let instance_pos = instance.position * camera.scale; let view_proj = camera.proj * view; let center_view_pos = view_proj * vec4(instance_pos, 1.0); - let vertex_view_pos = view_proj * vec4(instance_pos + (model_pos * camera.scale), 1.0); + let vertex_view_pos = view_proj * vec4(instance_pos + (model_pos * instance.radius * camera.scale), 1.0); let vertex_dist = length(vertex_view_pos - center_view_pos) / center_view_pos.w; if vertex_dist < min_size { out.clip_position = center_view_pos / center_view_pos.w; - out.clip_position += camera.proj * vec4(model_pos.xy * (min_size / 2), 0.0, 0.0); + out.clip_position += camera.proj * vec4(model.position.xy * (min_size / 2), 0.0, 0.0); }else{ out.clip_position = vertex_view_pos; } diff --git a/assets/systems/sol.csv b/assets/systems/sol.csv index 81fc14c..1c6dcfd 100644 --- a/assets/systems/sol.csv +++ b/assets/systems/sol.csv @@ -1,3 +1,3 @@ name,orbits,mass,radius,eccentricity,inclination,long_asc_node,long_periapsis,sgp,mean_long,semi_major_axis -Sol,0,1.988475e30,695700.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +Sol,,1.988475e30,695700.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 Earth,0,5.97217e24,6371.0,0.0167,0.1249,-0.1965,1.7966,3.986e14,1.7534,149598023 diff --git a/src/main.rs b/src/main.rs index b6efdcd..ae4233e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use std::cell::RefCell; use std::thread; use std::time::{Duration, Instant}; +use serde::de; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{EventLoop, ActiveEventLoop, ControlFlow}; use winit::error::{EventLoopError}; @@ -82,6 +83,16 @@ impl GameState pub fn timeman_mut(&mut self) -> &mut TimeMan { &mut self.timeman } + + fn update( + &mut self, + dt: Duration) + { + self.timeman.update(); + self.solar_systems.iter_mut().for_each(|solar_system| { + solar_system.update(self.timeman.seconds()); + }); + } } impl ApplicationHandler for SystemicApp @@ -126,7 +137,7 @@ impl ApplicationHandler for SystemicApp let delta_time = now - self.last_render_time; self.last_render_time = now; - game_state.borrow_mut().timeman.update(); + game_state.borrow_mut().update(delta_time); { window.update(game_state, delta_time); } diff --git a/src/solar_system.rs b/src/solar_system.rs index e8137f0..6730719 100644 --- a/src/solar_system.rs +++ b/src/solar_system.rs @@ -1,6 +1,7 @@ +use cgmath::Point3; use serde::{Deserialize}; -use crate::{known_stars::{KNOWN_STARS, StarNotFoundError}, timeman::Second}; -use std::error::Error; +use crate::{GameState, known_stars::{KNOWN_STARS, StarNotFoundError}, timeman::Second}; +use std::{cell::RefCell, error::Error}; const GRAVITATIONAL_CONSTANT: f64 = 6.67408e-11; @@ -16,7 +17,7 @@ pub type SystemId = usize; pub struct SerialOrbitalBody { name: String, - orbits: BodyId, + orbits: Option, mass: Kilograms, radius: Kilometers, @@ -33,13 +34,14 @@ pub struct SerialOrbitalBody pub struct OrbitalBody { body: SerialOrbitalBody, - position: Option<(Second, cgmath::Point3)> + position: Option> } pub struct SolarSystem { name: String, bodies: Vec, + time: Option } impl SolarSystem @@ -53,8 +55,16 @@ impl SolarSystem let mut bodies = Vec::::new(); for result in body_reader.deserialize() { - let record: SerialOrbitalBody = result?; + 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, position: None }); @@ -63,6 +73,7 @@ impl SolarSystem Ok(Self { name: bodies[0].name().clone(), bodies: bodies, + time: None }) } @@ -83,79 +94,92 @@ impl SolarSystem { 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 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 name(&self) -> &String { &self.body.name } pub fn radius(&self) -> f32 { self.body.radius as f32 } + pub fn position(&self) -> Point3 { self.position.unwrap() } - pub fn position(&self, time: Second) - -> Option> + fn calculate_orbit_at( + &self, + time: Second) + -> Point3 { - match self.position { - Some((cache_time, pos)) => { - if time == cache_time { - return Some(pos); - } - return None; - }, - None => None + if self.body.orbits.is_none() { + return Point3::::new(0.0, 0.0, 0.0); } - } - fn calculate_orbit( - &self, - time: i64) - -> cgmath::Point3 { - cgmath::Point3 { x: 0.0, y: 0.0, z: 0.0 } - /*let arg_periapsis = self.long_periapsis - self.long_asc_node; + 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 mean_angular_motion: f64 = ( - self.sgp as f64 / (self.semi_major_axis as f64).powf(3.0) - ).sqrt(); + let sma_cubed = self.body.semi_major_axis.powf(3.0); + let mean_motion = (self.body.sgp / sma_cubed.abs()).sqrt(); - let mean_anomaly = (self.mean_long - self.long_periapsis) + (mean_angular_motion * time as f64) as f32; + let mean_anomaly_epoch = self.body.mean_long - self.body.long_periapsis; + let mean_anomaly = mean_anomaly_epoch + (mean_motion * time as f64); - let mut eccentric_anomaly = mean_anomaly + self.eccentricity * mean_anomaly.sin(); + //Find the eccentric anomaly via newton's method + let mut eccentric_anomaly = mean_anomaly; for _ in 0..100 { - let new_eccentric = eccentric_anomaly + - (mean_anomaly - eccentric_anomaly + self.eccentricity * eccentric_anomaly.sin()) / - (1.0 - self.eccentricity * eccentric_anomaly.cos()); - if (new_eccentric - eccentric_anomaly).abs() < 1e-6 { - eccentric_anomaly = new_eccentric; + 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; } - eccentric_anomaly = new_eccentric; } - let beta = self.eccentricity / 1.0 + (1.0 - self.eccentricity * self.eccentricity).sqrt(); - let true_anomaly = eccentric_anomaly + 2.0 * - ((beta * eccentric_anomaly.sin()) / (1.0 - beta * eccentric_anomaly.cos())).atan(); - - let radius: f64 = self.semi_major_axis * (1.0 - self.eccentricity * eccentric_anomaly.cos()) as f64; - - let ohm_sin = self.long_asc_node.sin(); - let ohm_cos = self.long_asc_node.cos(); - - let inc_sin = self.inclination.sin(); - let inc_cos = self.inclination.cos(); + let true_anomaly = 2.0 * ( + ((1.0 + eccentricity) / (1.0 - eccentricity)).sqrt() + * (eccentric_anomaly / 2.0).tan() + ).atan(); - let per_anom_sin = (arg_periapsis + true_anomaly).sin(); - let per_anom_cos = (arg_periapsis + true_anomaly).cos(); + 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 x: f32 = (radius as f32) * ( - (ohm_cos * per_anom_cos) - - (ohm_sin * per_anom_sin * inc_cos)); - let y: f32 = (radius as f32) * ( - (ohm_sin * per_anom_cos) + - (ohm_cos * per_anom_sin * inc_cos)); - let z: f32 = (radius as f32) * (inc_sin * per_anom_sin); + 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); - OrbitState { - position: cgmath::Vector3::new(x, y, z) - }*/ + Point3::::new(x, y, z) } } diff --git a/src/tacmap.rs b/src/tacmap.rs index 89d21aa..b245f77 100644 --- a/src/tacmap.rs +++ b/src/tacmap.rs @@ -52,7 +52,7 @@ impl TacticalMap let camera = Camera::new( wgpuctx, cgmath::Point3::::new(0.0, 0.0, 1.0), - cgmath::Deg(-90.0), + cgmath::Deg(0.0), cgmath::Rad(0.0)); let projection = Projection::new( @@ -86,8 +86,11 @@ impl TacticalMap ui_state: &mut GameWindowUiState, dt: Duration) { - self.camera_controller.update(&mut self.camera, dt); ui_state.camera_scale = self.camera.get_scale(); + ui_state.camera_pos = Some(self.camera.get_position()); + self.camera.set_target(ui_state.camera_target); + + self.camera_controller.update(&mut self.camera, game_state, ui_state, dt); } pub fn keyboard_input( diff --git a/src/tacmap/camera.rs b/src/tacmap/camera.rs index 1a2787b..c6ee5c0 100644 --- a/src/tacmap/camera.rs +++ b/src/tacmap/camera.rs @@ -1,9 +1,9 @@ -use std::time::Duration; +use std::{cell::RefCell, time::Duration}; -use cgmath::{InnerSpace, Point3, Rad, Vector2, Vector3, Vector4, Zero, perspective}; +use cgmath::{EuclideanSpace, InnerSpace, Point3, Rad, Vector2, Vector3, Vector4, Zero, perspective}; use winit::{event::ElementState, keyboard::KeyCode}; -use crate::{solar_system::BodyId, wgpuctx::WgpuCtx}; +use crate::{GameState, solar_system, wgpuctx::WgpuCtx, window::ui::GameWindowUiState}; pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::from_cols( Vector4::new(1.0, 0.0, 0.0, 0.0), @@ -14,12 +14,13 @@ pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::from_co pub struct Camera { - position: Point3, + abs_position: Point3, + rel_position: Point3, pitch: Rad, yaw: Rad, scale: f32, - target: Option, + target: Option, buffer: wgpu::Buffer, staging_buffer: wgpu::Buffer, @@ -96,7 +97,8 @@ impl Camera } ); Self { - position: position.into(), + abs_position: position.into(), + rel_position: Point3::new(0.0, 0.0, 0.0), yaw: yaw.into(), pitch: pitch.into(), scale: 1.0, @@ -110,6 +112,14 @@ impl Camera pub fn get_scale(&self) -> f32 { self.scale } + pub fn get_position(&self) -> Point3 + { + Point3::new( + self.abs_position.x + self.rel_position.x, + self.abs_position.y + self.rel_position.y, + self.abs_position.z + self.rel_position.z) + } + pub fn bindgroup_layout(wgpuctx: &WgpuCtx) -> wgpu::BindGroupLayout { @@ -152,22 +162,44 @@ impl Camera wgpuctx.queue().write_buffer(&self.staging_buffer, 0, bytemuck::cast_slice(&[self.uniform(projection)])); } + pub fn set_target( + &mut self, + target: Option) + { + if target == self.target { return; } + if !target.is_some() || !self.target.is_some() { + self.abs_position = self.get_position(); + self.rel_position = Point3::new(0.0, 0.0, 0.0); + } + self.target = target; + } + + pub fn get_target(&self) -> Option + { self.target } + pub fn view_matrix( &self) -> cgmath::Matrix4 { - let (yaw_sin, yaw_cos) = self.yaw.0.sin_cos(); - let (pitch_sin, pitch_cos) = self.pitch.0.sin_cos(); - - cgmath::Matrix4::look_to_rh( - self.position, - Vector3::new( - pitch_cos * yaw_cos, - pitch_sin, - pitch_cos * yaw_sin - ).normalize(), - Vector3::unit_y() - ) + let (yaw_sin, yaw_cos) = self.pitch.0.sin_cos(); + let (pitch_sin, pitch_cos) = self.yaw.0.sin_cos(); + + if self.target.is_some() { + cgmath::Matrix4::look_at_rh( + self.get_position(), + self.abs_position, + Vector3::unit_y()) + }else{ + cgmath::Matrix4::look_to_rh( + self.get_position(), + Vector3::new( + pitch_cos * yaw_cos, + pitch_sin, + pitch_cos * yaw_sin + ).normalize(), + Vector3::unit_y() + ) + } } pub fn uniform( @@ -177,7 +209,7 @@ impl Camera { CameraUniform { view: (self.view_matrix()).into(), - proj: (OPENGL_TO_WGPU_MATRIX * projection.projection_matrix()).into(), + proj: projection.projection_matrix().into(), scale: self.scale } } @@ -215,30 +247,96 @@ impl CameraController } } - pub fn update( + fn update_orbit( &mut self, camera: &mut Camera, + target: Point3, + min_radius: f32, dt: Duration) { + camera.abs_position = target * camera.scale; + let dt = dt.as_secs_f32(); let speed = 1.0; - camera.pitch.0 += (self.rotation_pos_delta.x - self.rotation_neg_delta.x) * speed * dt; - camera.yaw.0 += (self.rotation_pos_delta.y - self.rotation_neg_delta.y) * speed * dt; + let current_radius = camera.rel_position.to_vec().magnitude(); + + let polar_diff = (self.position_pos_delta.z - self.position_neg_delta.z) * speed * dt; + let azimuth_diff = (self.position_pos_delta.x - self.position_neg_delta.x) * speed * dt; + let dist_diff = (self.position_pos_delta.y - self.position_neg_delta.y) * speed * dt; + + camera.pitch.0 += polar_diff; + let polar_cap = cgmath::Deg(179.999).0; + if camera.pitch.0 > polar_cap { camera.pitch.0 = polar_cap; } + if camera.pitch.0 < -polar_cap { camera.pitch.0 = -polar_cap; } + + camera.yaw.0 += azimuth_diff; + + let (az_sin, az_cos) = camera.yaw.0.sin_cos(); + let (p_sin, p_cos) = camera.pitch.0.sin_cos(); + + let radius = f32::max(min_radius * camera.scale, current_radius + dist_diff); + camera.rel_position = Point3::new( + radius * p_cos * az_cos, + radius * p_sin, + radius * p_cos * az_sin + ); + } + + fn update_pan( + &mut self, + camera: &mut Camera, + dt: Duration) + { + let dt = dt.as_secs_f32(); + let speed = 1.0; let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos(); let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize(); let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize(); let up = Vector3::new(0.0, 1.0, 0.0); - camera.position += forward * (self.position_pos_delta.z - self.position_neg_delta.z) * speed * dt; - camera.position += right * (self.position_pos_delta.x - self.position_neg_delta.x) * speed * dt; - camera.position += up * (self.position_pos_delta.y - self.position_neg_delta.y) * speed * dt; + camera.rel_position += forward * (self.position_pos_delta.z - self.position_neg_delta.z) * speed * dt; + camera.rel_position += right * (self.position_pos_delta.x - self.position_neg_delta.x) * speed * dt; + camera.rel_position += up * (self.position_pos_delta.y - self.position_neg_delta.y) * speed * dt; + + camera.abs_position = camera.rel_position; + + camera.pitch.0 += (self.rotation_pos_delta.x - self.rotation_neg_delta.x) * speed * dt; + camera.yaw.0 += (self.rotation_pos_delta.y - self.rotation_neg_delta.y) * speed * dt; + } + + pub fn update( + &mut self, + camera: &mut Camera, + game_state: &RefCell, + ui_state: &mut GameWindowUiState, + dt: Duration) + { camera.scale *= 1.0 + ((self.scale_delta.y - self.scale_delta.x) * 0.1); camera.scale = f32::max(1e-16, f32::min(1.0, camera.scale)); + + match ui_state.camera_target { + Some(body_id) => { + let game_state = game_state.borrow(); + let solar_systems = game_state.solar_systems(); + let current_system = match ui_state.current_system { + Some(id) => &solar_systems[id], + None => return +}; + + let body = ¤t_system.bodies()[body_id]; + self.update_orbit( + camera, + body.position().cast().unwrap(), + body.radius(), + dt); + } + None => self.update_pan(camera, dt) + } } -} +} //impl CameraController impl Projection { pub fn new< diff --git a/src/tacmap/render.rs b/src/tacmap/render.rs index c22878e..0f6b409 100644 --- a/src/tacmap/render.rs +++ b/src/tacmap/render.rs @@ -142,12 +142,7 @@ impl BodyRenderer let bodies = solar_system.bodies(); let body_instances = bodies.iter().map(|body| { - let position = match body.position(time) { - Some(pos) => pos, - None => return BodyInstanceRaw { - position: [0.0, 0.0, 0.0], - radius: body.radius() } - }; + let position = body.position(); BodyInstance { position: position, radius: body.radius() diff --git a/src/window/ui.rs b/src/window/ui.rs index 4d13740..60cd795 100644 --- a/src/window/ui.rs +++ b/src/window/ui.rs @@ -9,6 +9,7 @@ pub struct GameWindowUiState { pub current_system: Option, pub camera_scale: f32, + pub camera_pos: Option>, pub camera_target: Option, pub auto_time: Option, pub do_auto_tick: bool @@ -39,22 +40,16 @@ impl GameWindowUiState egui::TopBottomPanel::top("topbar").show( eguictx.context(), |ui| { - - ui.horizontal(|ui| { - ui.menu_button("File", |ui| { - - }); - }); ui.horizontal(|ui| { let solar_systems = game_state.solar_systems(); - let selected_label = match state.current_system { + let selected_system_label = match state.current_system { Some(id) => solar_systems[id].name(), None => "" }; egui::ComboBox::from_label("Current System") - .selected_text(selected_label) + .selected_text(selected_system_label) .show_ui(ui, |ui| { for (i, system) in solar_systems.iter().enumerate() { @@ -65,6 +60,35 @@ impl GameWindowUiState ); } }); + + let current_system = match state.current_system { + Some(id) => &solar_systems[id], + None => return + }; + + let selected_body_label = match state.camera_target { + Some(id) => current_system.bodies()[id].name(), + None => "" + }; + + ui.separator(); + + egui::ComboBox::from_label("Camera Target") + .selected_text(selected_body_label) + .show_ui(ui, |ui| { + + for (i, body) in current_system.bodies().iter().enumerate() { + ui.selectable_value( + &mut state.camera_target, + Some(i), + body.name()); + } + }); + + match state.camera_pos { + Some(pos) => { ui.label(format!("Pos x{} y{} z{}", pos.x, pos.y, pos.z)); }, + None => {} + } }); ui.horizontal(|ui| { -- cgit v1.2.3