diff options
| author | Jon Santmyer <jon@jonsantmyer.com> | 2026-05-10 13:29:56 -0400 |
|---|---|---|
| committer | Jon Santmyer <jon@jonsantmyer.com> | 2026-05-10 13:29:56 -0400 |
| commit | 7f63ec5c10eb7e8dd4edaabd1a6a437328911d39 (patch) | |
| tree | 36bd3d75ebc0c25256413c21a7cb28c9296953f5 | |
| parent | c9041e2e6fe59d6127bb1085b874e8e3cda8000e (diff) | |
| download | systemic4x-7f63ec5c10eb7e8dd4edaabd1a6a437328911d39.tar.gz systemic4x-7f63ec5c10eb7e8dd4edaabd1a6a437328911d39.tar.bz2 systemic4x-7f63ec5c10eb7e8dd4edaabd1a6a437328911d39.zip | |
fleets
| -rw-r--r-- | assets/shaders/tacmap/body.wgsl | 51 | ||||
| -rw-r--r-- | assets/shaders/tacmap/fleet.wgsl | 73 | ||||
| -rw-r--r-- | assets/shaders/tacmap/orbit.wgsl | 22 | ||||
| -rw-r--r-- | src/fleet.rs | 255 | ||||
| -rw-r--r-- | src/main.rs | 32 | ||||
| -rw-r--r-- | src/solar_system.rs | 54 | ||||
| -rw-r--r-- | src/solar_system/body.rs | 46 | ||||
| -rw-r--r-- | src/solar_system/fleet.rs | 60 | ||||
| -rw-r--r-- | src/solar_system/orbit.rs | 18 | ||||
| -rw-r--r-- | src/tacmap.rs | 46 | ||||
| -rw-r--r-- | src/tacmap/body_render.rs | 32 | ||||
| -rw-r--r-- | src/tacmap/camera.rs | 10 | ||||
| -rw-r--r-- | src/tacmap/fleet_render.rs | 177 | ||||
| -rw-r--r-- | src/tacmap/orbit_render.rs | 70 | ||||
| -rw-r--r-- | src/timeman.rs | 25 | ||||
| -rw-r--r-- | src/ui.rs | 29 | ||||
| -rw-r--r-- | src/ui/bodies_window.rs | 4 | ||||
| -rw-r--r-- | src/ui/fleet_window.rs | 160 | ||||
| -rw-r--r-- | src/ui/topbar.rs | 29 | ||||
| -rw-r--r-- | src/window.rs | 15 |
20 files changed, 914 insertions, 294 deletions
diff --git a/assets/shaders/tacmap/body.wgsl b/assets/shaders/tacmap/body.wgsl index 0cfd5f9..9e6419e 100644 --- a/assets/shaders/tacmap/body.wgsl +++ b/assets/shaders/tacmap/body.wgsl @@ -7,7 +7,6 @@ struct InstanceInput { struct VertexOutput { @builtin(position) clip_position: vec4<f32>, @location(0) local_position: vec3<f32>, - @location(1) distance: f32 }; struct CameraUniform { @@ -40,46 +39,42 @@ fn vs_main( let model = QUAD_VERTICES[index]; var view = camera.view; - //Billboard the circle - let camera_right = vec3<f32>(view[0][0], view[1][0], view[2][0]); - let camera_up = vec3<f32>(view[0][1], view[1][1], view[2][1]); - let model_pos = camera_right * model.x + - camera_up * model.y; + out.local_position = model; let min_size = 0.025; + let real_radius = max(min_size / 2.0, instance.radius * camera.scale); //Scale the world around the camera scale and translate about the camera's //absolute (/target) position let relative_pos = instance.position; - let origin_pos = instance.origin - camera.abs_pos; - - /*if all(relative_pos != origin_pos) { - if length(relative_pos) < 0.01 / camera.scale { - if length(relative_pos) != 0.0 { - out.clip_position = vec4<f32>(0.0, 0.0, 0.0, 1.0); - return out; - } - } - }*/ - - let instance_pos = (relative_pos + origin_pos - camera.rel_pos) * camera.scale; + let origin_pos = instance.origin - camera.abs_pos - camera.rel_pos; let view_proj = camera.proj * view; - let center_view_pos = view_proj * vec4<f32>(instance_pos, 1.0); - let vertex_view_pos = view_proj * vec4<f32>(instance_pos + (model_pos * instance.radius * camera.scale), 1.0); + let origin_vp_pos = view_proj * vec4<f32>(origin_pos * camera.scale, 1.0); + let offset_vp = view_proj * vec4<f32>(relative_pos * camera.scale, 0.0); + let center_vp_pos = origin_vp_pos + offset_vp; + + let offset_ndc = offset_vp.xy / origin_vp_pos.w; + + if (length(offset_ndc) < 2.0 * min_size){ + if all(offset_vp != vec4<f32>(0.0)) { + out.clip_position = vec4<f32>(0.0, 0.0, 0.0, 1.0); + return out; + } + } + + let model_proj = camera.proj * vec4<f32>(model.xy, 0.0, 0.0); + let vertex_vp_pos = center_vp_pos + vec4<f32>(model_proj.xy * instance.radius * camera.scale, 0.0, 0.0); - let vertex_dist = length(vertex_view_pos - center_view_pos) / center_view_pos.w; + let vertex_dist = length(vertex_vp_pos - center_vp_pos) / center_vp_pos.w; if vertex_dist < min_size { - out.clip_position = center_view_pos / center_view_pos.w; - out.clip_position += camera.proj * vec4<f32>(model.xy * (min_size / 2.0), 0.0, 0.0); + out.clip_position = center_vp_pos / center_vp_pos.w; + out.clip_position += model_proj * (min_size / 2.0); }else{ - out.clip_position = vertex_view_pos; + out.clip_position = vertex_vp_pos; } - out.local_position = model; - out.distance = length(instance_pos); - return out; } @@ -91,6 +86,6 @@ fn fs_main(in: VertexOutput if point_dist > 1.0 { alpha = 0.0; } - let color = vec3<f32>(1.0, 1.0, 1.0) / clamp(in.distance, 1.0, 1.5); + let color = vec3<f32>(1.0); return vec4<f32>(color, alpha); } diff --git a/assets/shaders/tacmap/fleet.wgsl b/assets/shaders/tacmap/fleet.wgsl new file mode 100644 index 0000000..8a87fb9 --- /dev/null +++ b/assets/shaders/tacmap/fleet.wgsl @@ -0,0 +1,73 @@ +struct InstanceInput { + @location(0) rotmat_0: vec3<f32>, + @location(1) rotmat_1: vec3<f32>, + @location(2) rotmat_2: vec3<f32>, + @location(3) origin: vec3<f32>, + @location(4) offset: vec3<f32>, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4<f32>, + @location(0) color: vec3<f32> +}; + +struct CameraUniform { + view: mat4x4<f32>, + proj: mat4x4<f32>, + abs_pos: vec3<f32>, + rel_pos: vec3<f32>, + scale: f32, +}; + +@group(0) @binding(0) +var<uniform> camera: CameraUniform; + +const FLEET_VERTICES = array<vec3<f32>,6>( + vec3<f32>(-1.0, -1.0, 0.0), + vec3<f32>(-1.0, 1.0 , 0.0), + vec3<f32>(1.0, 0.0 , 0.0), + + vec3<f32>(-1.0, 0.0, -1.0), + vec3<f32>(-1.0, 0.0, 1.0 ), + vec3<f32>(1.0, 0.0, 0.0 ), +); + +@vertex +fn vs_main( + @builtin(vertex_index) index: u32, + instance: InstanceInput +) -> VertexOutput { + var out: VertexOutput; + + let rotmat = mat3x3<f32>( + instance.rotmat_0, + instance.rotmat_1, + instance.rotmat_2 + ); + let model_pos = rotmat * FLEET_VERTICES[index] * 0.025; + var view = camera.view; + + let min_size = 0.025; + + //Scale the world around the camera scale and translate about the camera's + //absolute (/target) position + let origin_pos = instance.origin - camera.abs_pos; + let offset_pos = instance.offset - camera.rel_pos; + + let instance_pos = (offset_pos + origin_pos) * camera.scale; + let vertex_pos = instance_pos + model_pos; + + let view_proj = camera.proj * view; + let instance_vp_pos = view_proj * vec4<f32>(instance_pos, 1.0); + let model_vp_pos = view_proj * vec4<f32>(model_pos, 0.0); + + out.clip_position = instance_vp_pos + model_vp_pos; + out.color = vec3<f32>(0.75, 1.0, 0.75); + return out; +} + +@fragment +fn fs_main(in: VertexOutput +) -> @location(0) vec4<f32> { + return vec4<f32>(in.color, 1.0); +} diff --git a/assets/shaders/tacmap/orbit.wgsl b/assets/shaders/tacmap/orbit.wgsl index a890fd8..7276345 100644 --- a/assets/shaders/tacmap/orbit.wgsl +++ b/assets/shaders/tacmap/orbit.wgsl @@ -38,24 +38,32 @@ fn vs_main( origins[model.origin][1], origins[model.origin][2]); - let model_pos = ((origin - camera.abs_pos - camera.rel_pos) + model.position) * camera.scale; - let origin_pos = (origin - camera.abs_pos - camera.rel_pos) * camera.scale; + let offset_pos = model.position; + let origin_pos = origin - camera.abs_pos - camera.rel_pos; - let view = camera.view; + let view_proj = camera.proj * camera.view; + + let origin_view_pos = camera.view * vec4<f32>(origin_pos * camera.scale, 1.0); + let offset_view = camera.view * vec4<f32>(offset_pos * camera.scale, 0.0); + let point_view_pos = origin_view_pos + offset_view; let orbit_normal = normalize(model.position); var normal = orbit_normal; - if length(model_pos - origin_pos) > 0.01 { + let offset_ndc = offset_view.xy / origin_view_pos.z; + + let discard_radius = 0.05; + + if length(offset_ndc) > discard_radius { normal *= POINT_NORMALS[index]; } - let view_proj = camera.proj * camera.view; + let point_vp_pos = camera.proj * point_view_pos; //Scale the world around the camera scale and translate about the camera's //absolute (/target) position - let point_view_pos = view_proj * vec4<f32>(model_pos + normal * 0.005, 1.0); - out.clip_position = point_view_pos; + let segment_vp_pos = point_vp_pos + (view_proj * vec4<f32>(normal * 0.005, 0.0)); + out.clip_position = segment_vp_pos; return out; } diff --git a/src/fleet.rs b/src/fleet.rs new file mode 100644 index 0000000..39558cd --- /dev/null +++ b/src/fleet.rs @@ -0,0 +1,255 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::collections::hash_map::Iter; +use std::error::Error; +use std::iter::Filter; + +use cgmath::Zero; + +use crate::GameState; +use crate::solar_system::body::BodyId; +use crate::solar_system::orbit::StaticOrbiter; +use crate::solar_system::{SystemId, GRAVITATIONAL_CONSTANT, Kilometers, SolarSystem, body::OrbitalBody, orbit::StaticOrbit}; +use crate::timeman::{self, Second}; +use crate::ui::fleet_window::NewFleet; + +pub type FleetId = usize; + +pub const FLEET_TICK_DURATION: Second = timeman::HOUR; + +pub struct Fleet +{ + id: FleetId, + name: String, + + origin: cgmath::Vector3<Kilometers>, + offset: cgmath::Vector3<Kilometers>, + heading: cgmath::Vector2<cgmath::Rad<f32>>, + + system: Option<SystemId>, + baked_orbit: Option<(f64, StaticOrbit)>, +} + +pub struct FleetsManager +{ + next_id: FleetId, + fleets: HashMap<FleetId, Fleet> +} + +impl StaticOrbiter for Fleet { + fn orbit(&self) -> Option<&StaticOrbit> { + match &self.baked_orbit { + Some((_, orbit)) => { + Some(orbit) + }, + None => None + } + } + + fn sgp(&self) -> f64 { + match &self.baked_orbit { + Some((sgp, _)) => { *sgp }, + None => { 0.0 } + } + } +} + +impl Fleet +{ + pub const TICK_DURATION: Second = FLEET_TICK_DURATION; + + pub fn new( + id: FleetId, + name: String) + -> Self + { + Self { + id, + name, + origin: cgmath::vec3(0.0, 0.0, 0.0), + offset: cgmath::vec3(0.0, 0.0, 0.0), + heading: cgmath::vec2( + cgmath::Rad::zero(), + cgmath::Rad::zero()), + + system: None, + baked_orbit: None, + } + } + + pub fn id(&self) -> FleetId { self.id } + pub fn name(&self) -> &String { &self.name } + pub fn origin_position(&self) -> &cgmath::Vector3<Kilometers> + { &self.origin } + pub fn offset_position(&self) -> &cgmath::Vector3<Kilometers> + { &self.offset } + pub fn heading(&self) -> &cgmath::Vector2<cgmath::Rad<f32>> + { &self.heading } + pub fn system(&self) -> Option<SystemId> + { self.system } + + pub fn make_orbit( + &mut self, + star_system: &SolarSystem, + body: BodyId, + radius: Kilometers) + { + let body = star_system.body(body); + let sgp = body.mass() * GRAVITATIONAL_CONSTANT; + + self.baked_orbit = Some((sgp, StaticOrbit::new_circular(body, radius))); + self.system = Some(star_system.id()); + } + + fn subtick( + &mut self, + star_systems: &[SolarSystem], + time: Second) + -> Result<(), ()> + { + if let Some((_sgp, orbit)) = &self.baked_orbit { + let solar_system = &star_systems[self.system().unwrap()]; + let body = solar_system.body(orbit.parent()); + + self.origin = body.absolute_position(solar_system, time); + self.offset = orbit.calculate_position_at(self, time); + } + Ok(()) + } +} // impl Fleet + +impl FleetsManager +{ + pub fn new() + -> Self { + Self { + next_id: 0, + fleets: HashMap::new() + } + } + + fn reserve_new_id(&mut self) + -> FleetId + { + let new_id = self.next_id; + self.next_id += 1; + new_id + } + + pub fn all(&self) + -> Vec<usize> + { + self.fleets.keys().cloned().collect() + } + + pub fn all_in_system( + &self, + system: SystemId) + -> Vec<usize> + { + self.fleets.iter().filter_map(|v| { + if v.1.system().is_some_and(|id| { id == system }) { + Some(*v.0) + }else { + None + } + }).collect() + } + + pub fn entry_mut( + &mut self, + id: FleetId) + -> Option<&mut Fleet> + { self.fleets.get_mut(&id) } + + pub fn entry( + &self, + id: FleetId) + -> Option<&Fleet> + { self.fleets.get(&id) } + + fn subtick_fleet( + &mut self, + star_systems: &[SolarSystem], + time: Second) + -> Result<(), ()> + { + let mut tick_interrupt = Ok(()); + for (id, fleet) in &mut self.fleets { + let res = fleet.subtick(star_systems, time); + if res.is_err() { + tick_interrupt = Err(()); + } + } + tick_interrupt + } + + pub fn tick( + &mut self, + star_systems: &[SolarSystem], + time_then: Second, + time_now: Second) + -> Result<(), Second> + { + let tick_interval = Fleet::TICK_DURATION; + + let time_then_offset = time_then % tick_interval; + let time_now_offset = time_now % tick_interval; + + let tick_start = time_then + tick_interval - time_then_offset; + let tick_end = time_now - time_now_offset; + + let num_ticks = (tick_end - tick_start) / tick_interval; + let mut tick_interrupt: Option<Second> = None; + + for tick_i in 0..num_ticks+1 { + let tick_time = tick_start + tick_i * tick_interval; + if self.subtick_fleet(star_systems, tick_time).is_err() { + tick_interrupt = Some(tick_time); + } + } + + let final_tick = match tick_interrupt { + Some(v) => v, + None => tick_end + }; + + if final_tick != tick_end { + self.subtick_fleet(star_systems, final_tick); + } + + Ok(()) + } +} // impl FleetsManager + +impl GameState +{ + pub fn new_fleet_from_ui( + &mut self, + info: NewFleet) + -> Result<FleetId, Box<dyn Error>> + { + let star_system = &self.solar_systems[info.system]; + + let fleet_id = self.fleets.reserve_new_id(); + let mut fleet = Fleet::new(fleet_id, info.name); + fleet.make_orbit(star_system, info.orbiting, info.sma); + + self.fleets.fleets.insert( + fleet_id, + fleet); + + Ok(fleet_id) + } +} + +impl SolarSystem +{ + pub fn fleets( + &self, + fleets_manager: &FleetsManager) + -> Vec<usize> + { + fleets_manager.all_in_system(self.id()) + } +} diff --git a/src/main.rs b/src/main.rs index d7c76bc..a50eec1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod eguictx; mod vertex; mod texture; mod solar_system; +mod fleet; mod known_stars; mod timeman; mod ntree; @@ -23,6 +24,7 @@ use winit::window::WindowId; use solar_system::*; +use crate::fleet::FleetsManager; use crate::timeman::TimeMan; use crate::window::GameWindow; @@ -41,7 +43,8 @@ struct SystemicApp struct GameState { timeman: TimeMan, - solar_systems: Vec<SolarSystem> + solar_systems: Vec<SolarSystem>, + fleets: FleetsManager } impl SystemicApp @@ -63,23 +66,31 @@ impl GameState pub fn new() -> Self { let timeman = TimeMan::new(0); - let mut sol_system = match SolarSystem::new_from_known_star(0, "sol") + let sol_system = match SolarSystem::new_from_known_star(0, "sol") { Ok(system) => system, Err(e) => panic!("Unable to create sol system : {}", e) }; - sol_system.tick(timeman.seconds()); - Self { timeman: timeman, - solar_systems: vec![ sol_system ] + solar_systems: vec![ sol_system ], + fleets: FleetsManager::new() } } pub fn solar_systems(&self) -> &[SolarSystem] { self.solar_systems.as_slice() } + pub fn solar_systems_mut(&mut self) -> &mut [SolarSystem] + { self.solar_systems.as_mut_slice() } + + pub fn fleets(&self) -> &FleetsManager + { &self.fleets } + + pub fn fleets_mut(&mut self) -> &mut FleetsManager + { &mut self.fleets } + pub fn timeman(&self) -> &TimeMan { &self.timeman } @@ -90,10 +101,13 @@ impl GameState &mut self, _dt: Duration) { - let ticks = self.timeman.update(); - ticks.iter().for_each(|time| { - self.solar_systems.iter_mut().for_each(|system| { system.tick(*time) }); - }); + let time_then = self.timeman.update(); + if time_then == self.timeman.seconds() { return; } + + self.fleets.tick( + &self.solar_systems, + time_then, + self.timeman.seconds()); } } diff --git a/src/solar_system.rs b/src/solar_system.rs index 913ca00..94508fd 100644 --- a/src/solar_system.rs +++ b/src/solar_system.rs @@ -1,9 +1,7 @@ pub mod body; -pub mod fleet; pub mod orbit; use self::body::*; -use self::fleet::*; use self::orbit::*; use serde::{Deserialize}; @@ -11,7 +9,7 @@ use crate::known_stars::*; use crate::timeman::Second; use std::error::Error; -const GRAVITATIONAL_CONSTANT: f64 = 6.67408e-20; +pub const GRAVITATIONAL_CONSTANT: f64 = 6.67408e-20; pub type Kilograms = f64; pub type Kilometers = f64; @@ -38,18 +36,12 @@ pub struct CSVOrbitalBody semi_major_axis: Kilometers, } -pub trait HasMass -{ - fn mass() -> Kilograms; -} - pub struct SolarSystem { id: SystemId, name: String, bodies: Vec<OrbitalBody>, - fleets: Vec<Fleet> } impl SolarSystem @@ -74,7 +66,7 @@ impl SolarSystem } None => {} } - if record.radius == 0.0 { continue; } + //if record.radius == 0.0 { continue; } println!("New body: {:?}", record); bodies.push(OrbitalBody::new_from_record(bodies.len(), record)); @@ -84,7 +76,6 @@ impl SolarSystem id: id, name: bodies[0].name().clone(), bodies: bodies, - fleets: vec![] }) } @@ -113,25 +104,34 @@ impl SolarSystem pub fn body_position( &self, - body: &OrbitalBody) + body: &OrbitalBody, + time: Second) -> cgmath::Vector3<Kilometers> { - body.absolute_position(self) + body.absolute_position(self, time) } - pub fn fleets(&self) - -> &[Fleet] - { self.fleets.as_slice() } - - pub fn tick( - &mut self, - time: Second) + /*fn tick_tickables<T: Tickable>( + to_tick: &mut [T], + time_then: Second, + time_now: Second) { - self.bodies.iter_mut().for_each(|body| { - body.tick(time); - }); - self.fleets.iter_mut().for_each(|fleet| { - fleet.tick(time); - }); - } + let tick_interval = T::subtick_interval(); + + let time_then_offset = time_then % tick_interval; + let time_now_offset = time_now % tick_interval; + + let tick_start = time_then + tick_interval - time_then_offset; + let tick_end = time_now - time_now_offset; + + let num_ticks = (tick_end - tick_start) / tick_interval; + for tick_i in 0..num_ticks+1 { + let tick_second = tick_start + tick_i * tick_interval; + to_tick.iter_mut().for_each(|v| { v.tick(tick_second); }); + } + + if !T::tick_only_on_subtick() && tick_end != time_now { + to_tick.iter_mut().for_each(|v| { v.tick(time_now); }); + } + }*/ } diff --git a/src/solar_system/body.rs b/src/solar_system/body.rs index 7618a95..3e61486 100644 --- a/src/solar_system/body.rs +++ b/src/solar_system/body.rs @@ -1,7 +1,11 @@ +use crate::timeman; + use super::*; pub type BodyId = usize; +pub const BODY_TICK_DURATION: Second = timeman::DAY; + pub struct OrbitalBody { id: BodyId, @@ -11,11 +15,23 @@ pub struct OrbitalBody sgp: f64, orbit: Option<StaticOrbit>, - rel_pos: Option<cgmath::Vector3<Kilometers>> + rel_pos: Option<(i64, cgmath::Vector3<Kilometers>)> +} + +impl StaticOrbiter for OrbitalBody { + fn orbit(&self) -> Option<&StaticOrbit> { + self.orbit.as_ref() + } + + fn sgp(&self) -> f64 { + self.sgp + } } impl OrbitalBody { + pub const TICK_DURATION: Second = BODY_TICK_DURATION; + pub fn new_from_record( id: BodyId, record: CSVOrbitalBody) @@ -45,21 +61,30 @@ impl OrbitalBody pub fn id(&self) -> BodyId { self.id } pub fn name(&self) -> &String { &self.name } pub fn radius(&self) -> f32 { self.radius as f32 } - pub fn relative_position(&self) -> cgmath::Vector3<f64> { self.rel_pos.unwrap() } pub fn mass(&self) -> Kilograms { self.mass } pub fn sgp(&self) -> f64 { self.sgp } + pub fn relative_position( + &self, + time: Second) + -> cgmath::Vector3<f64> + { + let time = time - (time % BODY_TICK_DURATION); + self.calculate_orbit_at(time) + } + pub fn absolute_position( &self, - solar_system: &SolarSystem) + solar_system: &SolarSystem, + time: Second) -> cgmath::Vector3<Kilometers> { match &self.orbit { Some(orbit) => { - let parent_pos = solar_system.bodies()[orbit.parent()].absolute_position(solar_system); - parent_pos + self.relative_position() + let parent_pos = solar_system.bodies()[orbit.parent()].absolute_position(solar_system, time); + parent_pos + self.relative_position(time) }, - None => self.relative_position() + None => self.relative_position(time) } } @@ -81,16 +106,9 @@ impl OrbitalBody -> cgmath::Vector3<Kilometers> { match &self.orbit { - Some(orbit) => orbit.calculate_position_at(self.sgp(), time), + Some(orbit) => orbit.calculate_position_at(self, time), None => cgmath::vec3(0.0, 0.0, 0.0) } } - - pub fn tick( - &mut self, - time: Second) - { - self.rel_pos = Some(self.calculate_orbit_at(time)); - } } diff --git a/src/solar_system/fleet.rs b/src/solar_system/fleet.rs deleted file mode 100644 index c331cd7..0000000 --- a/src/solar_system/fleet.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::solar_system::{GRAVITATIONAL_CONSTANT, Kilometers, SolarSystem, body::OrbitalBody, orbit::StaticOrbit}; -use crate::timeman::Second; - -pub type FleetId = usize; - -pub struct Fleet -{ - id: FleetId, - name: String, - - position: cgmath::Vector3<Kilometers>, - velocity: cgmath::Vector3<f32>, - acceleration: cgmath::Vector3<f32>, - - baked_orbit: Option<(f64, StaticOrbit)>, -} - -impl Fleet -{ - pub fn new( - id: FleetId, - name: String) - -> Self - { - Self { - id, - name, - position: cgmath::vec3(0.0, 0.0, 0.0), - velocity: cgmath::vec3(0.0, 0.0, 0.0), - acceleration: cgmath::vec3(0.0, 0.0, 0.0), - baked_orbit: None, - } - } - - pub fn id(&self) -> FleetId { self.id } - pub fn name(&self) -> &String { &self.name } - - pub fn make_orbit( - &mut self, - body: &OrbitalBody, - radius: Kilometers) - { - let sgp = body.mass() * GRAVITATIONAL_CONSTANT; - self.baked_orbit = Some((sgp, StaticOrbit::new_circular(body, radius))); - } - - pub fn tick( - &mut self, - time: Second) - { - match &self.baked_orbit { - Some(orbit_info) => { - let sgp = orbit_info.0; - let orbit = &orbit_info.1; - self.position = orbit.calculate_position_at(sgp, time); - }, - None => {} - } - } -} diff --git a/src/solar_system/orbit.rs b/src/solar_system/orbit.rs index 3a1d129..f5a564c 100644 --- a/src/solar_system/orbit.rs +++ b/src/solar_system/orbit.rs @@ -1,4 +1,4 @@ -use crate::solar_system::{Angle, BodyId, Kilometers, OrbitalBody, Percentage, SolarSystem}; +use crate::solar_system::{Angle, BodyId, Kilometers, OrbitalBody, Percentage}; use crate::timeman::{Second}; pub struct StaticOrbit @@ -13,6 +13,12 @@ pub struct StaticOrbit semi_major_axis: Kilometers, } +pub trait StaticOrbiter +{ + fn orbit(&self) -> Option<&StaticOrbit>; + fn sgp(&self) -> f64; +} + impl StaticOrbit { pub fn new( @@ -51,9 +57,9 @@ impl StaticOrbit } } - pub fn period( + pub fn period<T: StaticOrbiter>( &self, - this_body: &OrbitalBody) + this_body: &T) -> Second { ((self.semi_major_axis.powf(3.0) / this_body.sgp()).sqrt() * std::f64::consts::TAU) as Second @@ -65,9 +71,9 @@ impl StaticOrbit pub fn sma(&self) -> Kilometers { self.semi_major_axis } - pub fn calculate_position_at( + pub fn calculate_position_at<T: StaticOrbiter>( &self, - sgp: f64, + this_body: &T, time: Second) -> cgmath::Vector3<f64> { @@ -77,7 +83,7 @@ impl StaticOrbit let arg_periaps = self.long_periapsis - long_asc_node; let sma_cubed = self.semi_major_axis.powf(3.0); - let mean_motion = (sgp / sma_cubed).sqrt(); + let mean_motion = (this_body.sgp() / sma_cubed).sqrt(); let mean_anomaly_epoch = self.mean_long - self.long_periapsis; let mean_anomaly = mean_anomaly_epoch + (mean_motion * time as f64); diff --git a/src/tacmap.rs b/src/tacmap.rs index cd9d878..9ff69f6 100644 --- a/src/tacmap.rs +++ b/src/tacmap.rs @@ -1,6 +1,7 @@ pub mod camera; mod render; mod body_render; +mod fleet_render; mod orbit_render; use std::time::Duration; @@ -9,14 +10,18 @@ use cgmath::InnerSpace; use winit::event::ElementState; use winit::keyboard::KeyCode; +use crate::fleet::FleetsManager; use crate::solar_system::SolarSystem; use crate::solar_system::SystemId; +use crate::timeman::Second; use crate::timeman::TimeMan; use crate::ui; use crate::wgpuctx::SceneCtx; use crate::wgpuctx::WgpuCtx; + use render::*; use body_render::*; +use fleet_render::*; use orbit_render::*; use camera::*; @@ -29,6 +34,7 @@ pub struct TacticalMap system_for_render: Option<SystemId>, body_renderer: BodyRenderer, + fleet_renderer: FleetRenderer, grid_renderer: GridRenderer, orbit_renderer: OrbitRenderer } @@ -61,6 +67,7 @@ impl TacticalMap system_for_render: None, body_renderer: BodyRenderer::new(wgpuctx), + fleet_renderer: FleetRenderer::new(wgpuctx), grid_renderer: GridRenderer::new(wgpuctx), orbit_renderer: OrbitRenderer::new(wgpuctx), } @@ -78,11 +85,12 @@ impl TacticalMap &mut self, solar_system: &SolarSystem, ui_state: &mut ui::State, + time: Second, dt: Duration) { self.camera.set_target(ui_state.camera_target); - self.camera_controller.update(&mut self.camera, solar_system, ui_state, dt); + self.camera_controller.update(&mut self.camera, solar_system, ui_state, time, dt); } pub fn keyboard_input( @@ -96,6 +104,7 @@ impl TacticalMap pub fn draw( &mut self, wgpuctx: &WgpuCtx, + fleets_manager: &FleetsManager, solar_system: &SolarSystem, timeman: &TimeMan, ) -> Result<(), wgpu::SurfaceError> @@ -126,6 +135,16 @@ impl TacticalMap Err(e) => println!("Tactical map body render update error: {}", e) } + match self.fleet_renderer.update( + wgpuctx, + fleets_manager, + solar_system, + timeman.seconds()) + { + Ok(()) => {}, + Err(e) => println!("Tactical map fleet render update error: {}", e) + } + Ok(()) } @@ -142,12 +161,14 @@ impl TacticalMap { self.grid_renderer.render( pass, &self.camera); self.orbit_renderer.render(pass, &self.camera); + self.fleet_renderer.render(pass, &self.camera); self.body_renderer.render( pass, &self.camera); } pub fn paint_labels( &self, solar_system: &SolarSystem, + time: Second, screen_size: egui::Vec2, ui: &mut egui::Ui) { @@ -160,28 +181,27 @@ impl TacticalMap bodies.iter().for_each(|body| { let scaled_radius = (screen_size.y * body.radius() * self.camera.get_scale()).max(16.0); - let world_pos = solar_system.body_position(body); - let local_pos = world_pos - self.camera.get_combined_position(); - + let offset_pos = body.relative_position(time); let origin_pos = match body.get_orbit() { Some(orbit) => { - solar_system.body_position(&bodies[orbit.parent()]) + solar_system.body_position(&bodies[orbit.parent()], time) }, None => cgmath::vec3(0.0, 0.0, 0.0) }; - let origin_local_pos = origin_pos - self.camera.get_origin_position(); + let origin_local_pos = origin_pos - self.camera.get_combined_position(); - let scaled_pos = local_pos * self.camera.get_scale() as f64; - let scaled_origin_pos = origin_local_pos * self.camera.get_scale() as f64; + let offset_pos_scaled = offset_pos * self.camera.get_scale() as f64; + let origin_pos_scaled = origin_local_pos * self.camera.get_scale() as f64; - let clip_pos = pv_matrix * scaled_pos.map(|v| { v as f32 }).extend(1.0); - let origin_clip_pos = pv_matrix * scaled_origin_pos.map(|v| { v as f32 }).extend(1.0); + let origin_clip_pos = pv_matrix * origin_pos_scaled.map(|v| { v as f32 }).extend(1.0); + let offset_clip_pos = pv_matrix * offset_pos_scaled.map(|v| { v as f32}).extend(0.0); + let clip_pos = origin_clip_pos + offset_clip_pos; let ndc_pos = clip_pos.truncate() / clip_pos.w; - let origin_ndc_pos = origin_clip_pos.truncate() / origin_clip_pos.w; + let offset_ndc = offset_clip_pos.truncate() / origin_clip_pos.w; - if (ndc_pos - origin_ndc_pos).magnitude() < 0.05 { - if ndc_pos != origin_ndc_pos { + if offset_ndc.truncate().magnitude() < 0.1 { + if offset_pos != cgmath::vec3(0.0, 0.0, 0.0) { return; } } diff --git a/src/tacmap/body_render.rs b/src/tacmap/body_render.rs index 92ac13f..23a04c9 100644 --- a/src/tacmap/body_render.rs +++ b/src/tacmap/body_render.rs @@ -1,7 +1,8 @@ use std::error::Error; -use crate::solar_system::Kilometers; -use crate::timeman::{SYSTEM_TICK_INTERVAL, Second}; +use crate::solar_system::{Kilometers}; +use crate::solar_system::body::OrbitalBody; +use crate::timeman::{Second}; use crate::wgpuctx::{WgpuCtx, pipeline::RenderPipelineBuilder}; use super::*; @@ -9,7 +10,7 @@ use super::*; pub struct BodyRenderer { needs_rebuild: bool, - last_time: Option<Second>, + last_tick: Option<i64>, pipeline: wgpu::RenderPipeline, @@ -35,7 +36,7 @@ impl BodyRenderer Self { needs_rebuild: true, - last_time: None, + last_tick: None, pipeline: render_pipeline, body_instance_buffer: None } @@ -75,7 +76,7 @@ impl BodyRenderer } ); - self.last_time = None; + self.last_tick = None; self.body_instance_buffer = Some((bodies.len(), buffer)); } @@ -88,23 +89,12 @@ impl BodyRenderer { //If the last updated time is the same, we don't need to update //the positions of all the bodies. - if self.last_time.is_some_and(|last_time| { last_time == time }) { + let tick = time / OrbitalBody::TICK_DURATION; + if self.last_tick.is_some_and(|last_tick| { last_tick == tick }) { return Ok(()); } - //Round to the nearest tick - let tick_time = match self.last_time { - Some(last_time) => { - let this_tick = time / SYSTEM_TICK_INTERVAL; - let last_tick = last_time / SYSTEM_TICK_INTERVAL; - - if this_tick == last_tick { return Ok(()) } - this_tick * SYSTEM_TICK_INTERVAL - } - None => 0 - }; - - self.last_time = Some(time); + self.last_tick = Some(time); let (_, bodies_buffer) = match &self.body_instance_buffer { Some(tuple) => tuple, @@ -113,11 +103,11 @@ impl BodyRenderer let bodies = solar_system.bodies(); let body_instances = bodies.iter().map(|body| { - let position = body.relative_position(); + let position = body.relative_position(time); let origin = match body.get_orbit() { Some(orbit) => { let origin_body = &bodies[orbit.parent()]; - origin_body.relative_position() + origin_body.relative_position(time) }, None => cgmath::Vector3::new(0.0, 0.0, 0.0) }; diff --git a/src/tacmap/camera.rs b/src/tacmap/camera.rs index af163b8..1f2549b 100644 --- a/src/tacmap/camera.rs +++ b/src/tacmap/camera.rs @@ -6,6 +6,7 @@ use winit::keyboard::KeyCode; use crate::solar_system::body::OrbitalBody; use crate::solar_system::{self, Kilometers, SolarSystem}; +use crate::timeman::Second; use crate::ui; use crate::wgpuctx::WgpuCtx; @@ -268,6 +269,7 @@ impl CameraController camera: &mut Camera, solar_system: &SolarSystem, target: &OrbitalBody, + time: Second, dt: Duration) { let target_radius = (target.radius() * 2.0).max(1.0); @@ -277,11 +279,11 @@ impl CameraController match target.get_orbit() { Some(orbit) => { let parent = solar_system.body(orbit.parent()); - camera.origin_position = parent.absolute_position(solar_system); - camera.rel_position = target.relative_position().map(|v| { v as f32 }); + camera.origin_position = parent.absolute_position(solar_system, time); + camera.rel_position = target.relative_position(time).map(|v| { v as f32 }); }, None => { - camera.origin_position = target.absolute_position(solar_system); + camera.origin_position = target.absolute_position(solar_system, time); camera.rel_position.set_zero(); } } @@ -330,6 +332,7 @@ impl CameraController camera: &mut Camera, solar_system: &SolarSystem, ui_state: &mut ui::State, + time: Second, dt: Duration) { match ui_state.camera_target { @@ -340,6 +343,7 @@ impl CameraController camera, solar_system, body, + time, dt); } None => self.update_pan(camera, dt) diff --git a/src/tacmap/fleet_render.rs b/src/tacmap/fleet_render.rs new file mode 100644 index 0000000..ba21318 --- /dev/null +++ b/src/tacmap/fleet_render.rs @@ -0,0 +1,177 @@ +use std::error::Error; + +use crate::fleet::FleetsManager; +use crate::solar_system::orbit::StaticOrbiter; +use crate::solar_system::{Kilometers}; +use crate::timeman::{Second}; +use crate::wgpuctx::{WgpuCtx, pipeline::RenderPipelineBuilder}; + +use super::*; + +pub struct FleetRenderer +{ + last_time: Option<Second>, + + pipeline: wgpu::RenderPipeline, + + fleet_instance_buffer: Option<(usize, wgpu::Buffer)> +} + +struct FleetInstance +{ + offset: cgmath::Vector3<Kilometers>, + origin: cgmath::Vector3<Kilometers>, + heading: cgmath::Vector2<cgmath::Rad<f32>> +} + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct FleetInstanceRaw +{ + rotmat: [[f32;3];3], + origin: [f32;3], + offset: [f32;3], +} + +impl FleetRenderer +{ + pub fn new( + wgpuctx: &WgpuCtx) + -> Self { + let shader = wgpuctx.create_shader( + wgpu::include_wgsl!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/assets/shaders/tacmap/fleet.wgsl") + )); + + let render_pipeline = RenderPipelineBuilder::new(&shader) + .add_bindgroup(&Camera::bindgroup_layout(wgpuctx)) + .add_vertex_layout(FleetInstanceRaw::descr()) + .cull_mode(None) + .build(Some("Tactical map fleet render pipeline"), wgpuctx); + + Self { + last_time: None, + pipeline: render_pipeline, + fleet_instance_buffer: None + } + } + + pub fn update( + &mut self, + wgpuctx: &WgpuCtx, + fleets_manager: &FleetsManager, + solar_system: &SolarSystem, + time: Second) + -> Result<(), Box<dyn Error>> + { + //If the last updated time is the same, we don't need to update + //the positions of all the bodies. + if self.last_time.is_some_and(|last_time| { last_time == time }) { + return Ok(()); + } + + self.last_time = Some(time); + + if let Some((_, buffer)) = &self.fleet_instance_buffer { + buffer.destroy(); + self.fleet_instance_buffer = None; + } + + let fleets = solar_system.fleets(fleets_manager); + let fleets_instances = fleets.iter().filter_map(|id| { + let fleet = match fleets_manager.entry(*id) { + Some(fleet) => fleet, + None => { return None; } + }; + let heading = fleet.heading(); + let offset = fleet.offset_position(); + let origin = match fleet.orbit() { + Some(orbit) => { + let origin_body = solar_system.body(orbit.parent()); + origin_body.absolute_position(solar_system, time) + }, + None => cgmath::vec3(0.0, 0.0, 0.0) + }; + Some( + FleetInstance { + heading: *heading, + offset: *offset, + origin: origin + }.raw() + ) + }).collect::<Vec<_>>(); + let fleets_buffer = wgpuctx.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(fleets_instances.as_slice()), + usage: wgpu::BufferUsages::VERTEX + }); + + self.fleet_instance_buffer = Some((fleets.len(), fleets_buffer)); + + Ok(()) + } + + pub fn render( + &self, + pass: &mut wgpu::RenderPass, + camera: &Camera) + { + let (num_fleets, fleets_buffer) = match &self.fleet_instance_buffer { + Some(tuple) => tuple, + None => return + }; + if *num_fleets == 0 { return; } + + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, camera.bindgroup(), &[]); + + pass.set_vertex_buffer(0, fleets_buffer.slice(..)); + + pass.draw(0..6, 0..*num_fleets as _); + } +} // impl RenderState + +impl FleetInstance +{ + fn raw(&self) -> FleetInstanceRaw + { + FleetInstanceRaw { + rotmat: (cgmath::Matrix3::from_angle_x(self.heading.x) * + cgmath::Matrix3::from_angle_y(self.heading.y)) + .into(), + origin: [ + self.origin.x as f32, + self.origin.y as f32, + self.origin.z as f32 + ], + offset: [ + self.offset.x as f32, + self.offset.y as f32, + self.offset.z as f32 + ] + } + } +} // impl BodyInstance + +impl FleetInstanceRaw +{ + const ATTRIBS: [wgpu::VertexAttribute;5] = + wgpu::vertex_attr_array![ + 0 => Float32x3, 1 => Float32x3, 2 => Float32x3, + 3 => Float32x3, + 4 => Float32x3 + ]; + + pub fn descr() + -> wgpu::VertexBufferLayout<'static> { + use std::mem; + + wgpu::VertexBufferLayout { + array_stride: mem::size_of::<Self>() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &Self::ATTRIBS + } + } +} diff --git a/src/tacmap/orbit_render.rs b/src/tacmap/orbit_render.rs index c003366..7c0a089 100644 --- a/src/tacmap/orbit_render.rs +++ b/src/tacmap/orbit_render.rs @@ -3,17 +3,18 @@ use std::error::Error; use super::*; use crate::solar_system::body::{BodyId, OrbitalBody}; -use crate::timeman::{SYSTEM_TICK_INTERVAL, Second}; +use crate::solar_system::orbit::StaticOrbiter; +use crate::timeman::{self, Second}; use crate::wgpuctx::{WgpuCtx, pipeline::RenderPipelineBuilder}; pub struct OrbitRenderer { needs_rebuild: bool, - last_time: Option<Second>, + last_tick: Option<Second>, pipeline: wgpu::RenderPipeline, - orbit_vertex_buffers: Option<Vec<(BodyId, Option<wgpu::Buffer>)>>, + orbit_vertex_buffers: Option<Vec<(BodyId, wgpu::Buffer)>>, origin_buffer: Option<wgpu::Buffer>, origin_bind_group: Option<wgpu::BindGroup>, origin_bind_group_layout: wgpu::BindGroupLayout @@ -58,7 +59,7 @@ impl OrbitRenderer Self { needs_rebuild: true, - last_time: None, + last_tick: None, pipeline: render_pipeline, orbit_vertex_buffers: None, origin_buffer: None, @@ -70,28 +71,29 @@ impl OrbitRenderer pub fn mark_to_rebuild(&mut self) { self.needs_rebuild = true; } - fn create_orbit_buffer_for_body( + fn create_orbit_buffer<T: StaticOrbiter>( &self, wgpuctx: &WgpuCtx, - body: &OrbitalBody, + orbiter: &T, time: Second) - -> (usize, Option<wgpu::Buffer>) + -> Option<(usize, wgpu::Buffer)> { - let orbit = match body.get_orbit() { + let orbit = match orbiter.orbit() { Some(orbit) => orbit, - None => return (0, None) + None => { return None; } }; - let period = body.orbital_period(); + let period = orbit.period(orbiter); - let num_points = ((period / (SYSTEM_TICK_INTERVAL)) as usize).clamp(90, 360); + let num_points = ((period / timeman::HOUR) as usize).clamp(90, 360); let period_interval = period as f64 / num_points as f64; let mut points = vec![ OrbitVertex::default();(num_points+1)*2]; for i in 0..num_points { - let position = body.calculate_orbit_at( + let position = orbit.calculate_position_at( + orbiter, time + (i as f64 * period_interval) as Second); points[i*2].origin_id = orbit.parent() as _; @@ -113,7 +115,7 @@ impl OrbitRenderer contents: bytemuck::cast_slice(&points) } ); - ((num_points+1)*2, Some(buffer)) + Some(((num_points+1)*2, buffer)) } pub fn rebuild( @@ -129,10 +131,7 @@ impl OrbitRenderer match &self.orbit_vertex_buffers { Some(buffers) => { for buffer in buffers { - match &buffer.1 { - Some(v) => v.destroy(), - None => {} - } + buffer.1.destroy(); } self.orbit_vertex_buffers = None; }, @@ -171,6 +170,10 @@ impl OrbitRenderer } )); self.origin_buffer = Some(origin_buffer); + + self.orbit_vertex_buffers = Some(bodies.iter().filter_map(|body| { + self.create_orbit_buffer(wgpuctx, body, 0) + }).collect::<Vec<_>>()); } pub fn update( @@ -180,24 +183,14 @@ impl OrbitRenderer time: Second) -> Result<(), Box<dyn Error>> { - if self.last_time.is_some_and(|t| { t == time }) { + let tick = time / OrbitalBody::TICK_DURATION; + if self.last_tick.is_some_and(|t| { t == tick }) { return Ok(()); } - - let tick_time = match self.last_time { - Some(last_time) => { - let this_tick = time / SYSTEM_TICK_INTERVAL; - let last_tick = last_time / SYSTEM_TICK_INTERVAL; - - if this_tick == last_tick { return Ok(()) } - this_tick * SYSTEM_TICK_INTERVAL - } - None => 0 - }; - self.last_time = Some(time); + self.last_tick = Some(time); let positions = solar_system.bodies().iter().map(|body| { - let pos = solar_system.body_position(body); + let pos = solar_system.body_position(body, time); [ pos.x as f32, pos.y as f32, pos.z as f32 ] }).collect::<Vec<_>>(); @@ -208,10 +201,10 @@ impl OrbitRenderer None => { return Err(Box::new(NeedsRebuildError)); } }; - let bodies = solar_system.bodies(); - self.orbit_vertex_buffers = Some(bodies.iter().map(|body| { + //let bodies = solar_system.bodies(); + /*self.orbit_vertex_buffers = Some(bodies.iter().map(|body| { self.create_orbit_buffer_for_body(wgpuctx, body, tick_time) - }).collect::<Vec<_>>()); + }).collect::<Vec<_>>());*/ Ok(()) } @@ -234,13 +227,8 @@ impl OrbitRenderer } for (num_points, buffer) in buffers { - match &buffer { - Some(buffer) => { - pass.set_vertex_buffer(0, buffer.slice(..)); - pass.draw(0..(*num_points as u32), 0..1); - }, - None => {} - } + pass.set_vertex_buffer(0, buffer.slice(..)); + pass.draw(0..(*num_points as u32), 0..1); } } } diff --git a/src/timeman.rs b/src/timeman.rs index 27d5450..62296d7 100644 --- a/src/timeman.rs +++ b/src/timeman.rs @@ -5,8 +5,6 @@ pub const HOUR: Second = MINUTE * 60; pub const DAY: Second = HOUR * 24; pub const YEAR: Second = DAY * 365; -pub const SYSTEM_TICK_INTERVAL: Second = 1 * DAY; - pub struct TimeMan { time: Second, @@ -39,33 +37,16 @@ impl TimeMan pub fn update( &mut self) - -> Vec<Second> + -> Second { match self.auto_tick { Some(advance) => { self.time += advance; }, None => {} } - let time_diff = self.time - self.last_time; - - let tick_diff = self.time / SYSTEM_TICK_INTERVAL - - self.last_time / SYSTEM_TICK_INTERVAL; - - if time_diff == 0 || tick_diff == 0 { - self.last_time = self.time; - return vec![]; - } - - let time_start = self.last_time - (self.last_time % SYSTEM_TICK_INTERVAL); - let time_end = self.time - (self.time % SYSTEM_TICK_INTERVAL); - - let tick_start = time_start / SYSTEM_TICK_INTERVAL; - let tick_end = time_end / SYSTEM_TICK_INTERVAL; - + let last_time = self.last_time; self.last_time = self.time; - (tick_start..tick_end).map(|v| { - v * SYSTEM_TICK_INTERVAL - }).collect::<Vec<_>>() + last_time } pub fn format_duration(time: Second) -> String @@ -15,8 +15,8 @@ use crate::ui::topbar::TopBarState; pub struct State { pub camera_target: Option<BodyId>, - pub topbar_sate: TopBarState, + pub topbar_sate: TopBarState, pub bodies_window: BodiesWindowState, pub fleet_window: FleetWindowState } @@ -30,31 +30,34 @@ impl State { let mut game_state = game_state.borrow_mut(); - let topbar_action = TopBarState::render( - &mut self.topbar_sate, + let topbar_action = self.topbar_sate.paint( + eguictx, &game_state, - eguictx); + &self.bodies_window, + &self.fleet_window); if let Some(by) = topbar_action.advance_tick { game_state.timeman_mut().advance(by) } + if topbar_action.toggle_bodies_window { self.bodies_window.open = !self.bodies_window.open; } + if topbar_action.toggle_fleets_window { self.fleet_window.open = !self.fleet_window.open; } let current_system = match self.topbar_sate.current_system { Some(id) => &game_state.solar_systems()[id], None => return }; - if self.topbar_sate.bodies_window_visible { - let bodies_window_action = - self.bodies_window.render(current_system, eguictx); - if bodies_window_action.focus_body.is_some() { - self.camera_target = bodies_window_action.focus_body; - } + let bodies_window_action = + self.bodies_window.render(current_system, eguictx); + if bodies_window_action.focus_body.is_some() { + self.camera_target = bodies_window_action.focus_body; } - if self.topbar_sate.fleet_window_visible { - let fleet_window_action = - self.fleet_window.render(game_state.borrow(), eguictx); + let fleet_window_action = + self.fleet_window.paint(game_state.borrow(), eguictx, &self.camera_target); + + if let Some(new_fleet) = fleet_window_action.new_fleet { + game_state.new_fleet_from_ui(new_fleet); } } } diff --git a/src/ui/bodies_window.rs b/src/ui/bodies_window.rs index ece605b..21a2060 100644 --- a/src/ui/bodies_window.rs +++ b/src/ui/bodies_window.rs @@ -10,6 +10,7 @@ use crate::timeman::TimeMan; #[derive(Default, Clone)] pub struct BodiesWindowState { + pub open: bool, last_system: Option<SystemId>, system_heirarchy: NTree<SystemId>, selected_body: Option<BodyId> @@ -76,7 +77,9 @@ impl BodiesWindowState let mut action = BodiesWindowAction::default(); + let mut bodies_window_open = self.open; egui::Window::new("Bodies") + .open(&mut &mut bodies_window_open) .resizable(true) .show(eguictx.context(), |ui| { @@ -85,6 +88,7 @@ impl BodiesWindowState self.paint_body_info_panel(&mut action, current_system, ui); }); }); + self.open = bodies_window_open; action } diff --git a/src/ui/fleet_window.rs b/src/ui/fleet_window.rs index 67a0dd9..eb8ccde 100644 --- a/src/ui/fleet_window.rs +++ b/src/ui/fleet_window.rs @@ -1,56 +1,112 @@ use std::cell::RefCell; -use crate::{GameState, eguictx::EguiCtx, solar_system::{self, SolarSystem, fleet::Fleet}, ui}; +use crate::GameState; +use crate::eguictx::EguiCtx; +use crate::fleet::{Fleet, FleetsManager}; +use crate::solar_system::{Kilometers, SolarSystem, SystemId}; +use crate::solar_system::body::BodyId; #[derive(Default, Clone)] pub struct FleetWindowState { + pub open: bool, + pub selected_system: Option<SystemId>, + pub new_fleet_window: Option<NewFleetWindowState> } #[derive(Default, Clone)] pub struct FleetWindowAction { - + pub new_fleet: Option<NewFleet> +} + +#[derive(Default, Clone)] +pub struct NewFleetWindowState +{ + pub open: bool, + pub parent_system: SystemId, + pub parent_body: BodyId, + + pub fleet_name: String, + pub fleet_sma: Kilometers +} + +#[derive(Default, Clone)] +pub struct NewFleet +{ + pub system: SystemId, + pub orbiting: BodyId, + pub name: String, + pub sma: Kilometers } impl FleetWindowState { - pub fn render( + pub fn paint( &mut self, game_state: &GameState, - eguictx: &EguiCtx) + eguictx: &EguiCtx, + focused_body: &Option<BodyId>) -> FleetWindowAction { let mut action = FleetWindowAction::default(); let star_systems = game_state.solar_systems(); + let fleets_manager = game_state.fleets(); + let mut mgr_open = self.open; egui::Window::new("Fleet Manager") + .open(&mut mgr_open) .show(eguictx.context(), |ui| { ui.horizontal(|ui| { - self.paint_systems_list(star_systems, ui); - ui.add(egui::Separator::default().vertical()); + self.paint_systems_list( + &mut action, + ui, + fleets_manager, + star_systems, + focused_body + ); }); }); + + match &mut self.new_fleet_window { + Some(new_fleet_window) => { + action.new_fleet = new_fleet_window.paint(game_state, eguictx); + if action.new_fleet.is_some() || !new_fleet_window.open { + self.new_fleet_window = None; + } + } + None => {} + } + + self.open = mgr_open; action } fn paint_systems_list( &mut self, + action: &mut FleetWindowAction, + ui: &mut egui::Ui, + fleets_manager: &FleetsManager, star_systems: &[SolarSystem], - ui: &mut egui::Ui) + focused_body: &Option<BodyId>) -> egui::InnerResponse<()> { ui.vertical(|ui| { for system in star_systems { - let resp = self.paint_fleet_list(&system, ui); - if resp.header_response.secondary_clicked() { - resp.header_response.context_menu(|ui| { + self.paint_fleet_list(fleets_manager, &system, ui); + } - }); + if self.selected_system.is_none() { return; } + if ui.button("New Fleet").clicked() { + if self.new_fleet_window.is_none() { + self.new_fleet_window = Some(NewFleetWindowState::new( + self.selected_system.unwrap(), + focused_body.unwrap_or(0) + )); } } }) @@ -58,16 +114,29 @@ impl FleetWindowState fn paint_fleet_list( &mut self, + fleets_manager: &FleetsManager, star_system: &SolarSystem, ui: &mut egui::Ui) - -> egui::CollapsingResponse<()> { - let fleets = star_system.fleets(); - ui.collapsing(star_system.name(), |ui| { - for fleet in fleets { - self.paint_fleet_entry(fleet, ui); + let fleet_ids = star_system.fleets(fleets_manager); + egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.make_persistent_id(format!("fleet_window_star_{}", star_system.id())), + true + ) + .show_header(ui, |ui| { + let selected = self.selected_system.is_some_and(|id| { id == star_system.id() }); + if ui.selectable_label(selected, star_system.name()).clicked() { + self.selected_system = if !selected { Some(star_system.id()) } else { None }; } }) + .body(|ui| { + for id in fleet_ids { + if let Some(fleet) = fleets_manager.entry(id) { + self.paint_fleet_entry(fleet, ui); + } + } + }); } fn paint_fleet_entry( @@ -77,4 +146,63 @@ impl FleetWindowState { ui.label(fleet.name()); } +} // FleetWindowAction + +impl NewFleetWindowState +{ + pub fn new( + parent_system: SystemId, + parent_body: BodyId) + -> Self { + Self { + open: true, + parent_system: parent_system, + parent_body: parent_body, + ..Default::default() + } + } + + pub fn paint( + &mut self, + game_state: &GameState, + eguictx: &EguiCtx) + -> Option<NewFleet> + { + let solar_system = &game_state.solar_systems()[self.parent_system]; + let orbiting = solar_system.body(self.parent_body); + + match egui::Window::new("New Fleet") + .collapsible(false) + .open(&mut self.open) + .show(eguictx.context(), |ui| { + ui.vertical(|ui| { + ui.horizontal(|ui| { + ui.label("Name: "); + ui.add(egui::TextEdit::singleline(&mut self.fleet_name)); + }); + ui.label(format!("Orbiting: {}", orbiting.name())); + ui.horizontal(|ui| { + ui.label("SMA: "); + ui.add(egui::DragValue::new(&mut self.fleet_sma) + .range(orbiting.radius()..=f32::INFINITY) + .clamp_existing_to_range(true) + .suffix("km")); + }); + if ui.button("Create").clicked() { + return Some(NewFleet { + system: self.parent_system, + orbiting: self.parent_body, + name: self.fleet_name.clone(), + sma: self.fleet_sma, + }); + } + None + }).inner + }) { + Some(resp) => { + resp.inner? + }, + None => None + } + } } diff --git a/src/ui/topbar.rs b/src/ui/topbar.rs index a5d92c5..ba26d7c 100644 --- a/src/ui/topbar.rs +++ b/src/ui/topbar.rs @@ -1,6 +1,6 @@ use std::cell::RefCell; -use crate::{GameState, eguictx::EguiCtx, solar_system::SystemId, timeman::{self, Second, TimeMan}}; +use crate::{GameState, eguictx::EguiCtx, solar_system::SystemId, timeman::{self, Second, TimeMan}, ui::{self, bodies_window::BodiesWindowState, fleet_window::FleetWindowState}}; #[derive(Default, Clone)] pub struct TopBarState @@ -8,23 +8,24 @@ pub struct TopBarState pub current_system: Option<SystemId>, pub auto_tick: Option<Second>, pub do_auto_tick: bool, - - pub bodies_window_visible: bool, - pub fleet_window_visible: bool, } #[derive(Default, Clone)] pub struct TopBarAction { pub advance_tick: Option<Second>, + pub toggle_bodies_window: bool, + pub toggle_fleets_window: bool, } impl TopBarState { - pub fn render( + pub fn paint( &mut self, + eguictx: &EguiCtx, game_state: &GameState, - eguictx: &EguiCtx) + bodies_window_state: &BodiesWindowState, + fleets_window_state: &FleetWindowState) -> TopBarAction { let mut action: TopBarAction = TopBarAction::default(); @@ -57,7 +58,7 @@ impl TopBarState }); ui.horizontal(|ui| { - self.paint_empire_buttons(ui); + self.paint_empire_buttons(ui, &mut action, bodies_window_state, fleets_window_state); }); }); @@ -77,13 +78,17 @@ impl TopBarState pub fn paint_empire_buttons( &mut self, - ui: &mut egui::Ui) + ui: &mut egui::Ui, + action: &mut TopBarAction, + bodies_window_state: &BodiesWindowState, + fleets_window_state: &FleetWindowState + ) { - if ui.add(egui::Button::new("Bodies").selected(self.bodies_window_visible)).clicked() { - self.bodies_window_visible = !self.bodies_window_visible; + if ui.add(egui::Button::new("Bodies").selected(bodies_window_state.open)).clicked() { + action.toggle_bodies_window = true; } - if ui.add(egui::Button::new("Fleets").selected(self.fleet_window_visible)).clicked() { - self.fleet_window_visible = !self.fleet_window_visible; + if ui.add(egui::Button::new("Fleets").selected(fleets_window_state.open)).clicked() { + action.toggle_fleets_window = true; } } diff --git a/src/window.rs b/src/window.rs index d895040..29c347f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -75,7 +75,11 @@ impl GameWindow None => { return; } }; - self.tactical_map.update(current_system, &mut self.ui_state, dt); + self.tactical_map.update( + current_system, + &mut self.ui_state, + game_state.timeman().seconds(), + dt); } pub fn keyboard_input( @@ -105,10 +109,12 @@ impl GameWindow self.ui_state.render(game_state, &self.eguictx); if self.ui_state.topbar_sate.current_system.is_some() { let game_state = game_state.borrow(); + let fleets_manager = game_state.fleets(); let current_system = &game_state.solar_systems()[self.ui_state.topbar_sate.current_system.unwrap()]; self.tactical_map.draw( &self.wgpuctx, + fleets_manager, current_system, game_state.timeman())?; @@ -124,7 +130,12 @@ impl GameWindow egui::CentralPanel::default() .frame(egui::Frame::new()) .show(self.eguictx.context(), |ui| { - self.tactical_map.paint_labels(current_system, screen_size, ui); + self.tactical_map.paint_labels( + current_system, + game_state.timeman().seconds(), + screen_size, + ui + ); }); } self.eguictx.present( |
