From 14ca7b5fc15eb2618b46bde0cac85e37ebc9ebd9 Mon Sep 17 00:00:00 2001 From: Jon Santmyer Date: Thu, 21 May 2026 07:58:47 -0400 Subject: tacmap back as canvas. begin work on fleet scheduling --- assets/shaders/canvas.wgsl | 51 ++++++++++++ src/fleet.rs | 112 ++++++++++++++++---------- src/fleet/schedule.rs | 190 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 13 ++-- src/ntree.rs | 44 ++--------- src/solar_system.rs | 1 - src/tacmap.rs | 144 ++++++++++++++++++++++++++++++++-- src/tacmap/camera.rs | 2 +- src/tacmap/fleet_render.rs | 2 +- src/tacmap/orbit_render.rs | 2 +- src/texture.rs | 57 ++++++++------ src/ui.rs | 36 ++++----- src/ui/bodies_window.rs | 7 +- src/ui/fleet_schedule.rs | 150 +++++++++++++++++++++++++++++++++++ src/ui/fleet_window.rs | 114 ++++++++++++++++----------- src/ui/topbar.rs | 3 +- src/window.rs | 43 ++++++---- 17 files changed, 762 insertions(+), 209 deletions(-) create mode 100644 assets/shaders/canvas.wgsl create mode 100644 src/fleet/schedule.rs create mode 100644 src/ui/fleet_schedule.rs diff --git a/assets/shaders/canvas.wgsl b/assets/shaders/canvas.wgsl new file mode 100644 index 0000000..0aa61b1 --- /dev/null +++ b/assets/shaders/canvas.wgsl @@ -0,0 +1,51 @@ +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) uv: vec2 +} + +const QUAD_VERTICES = array,4>( + vec2(0.0, 0.0), + vec2(2.0, 0.0), + vec2(0.0, 2.0), + vec2(2.0, 2.0), +); + +const QUAD_UVS = array,4>( + vec2(0.0, 1.0), + vec2(1.0, 1.0), + vec2(0.0, 0.0), + vec2(1.0, 0.0), +); + +@group(0) @binding(0) +var canvas_texture: texture_2d; +@group(0) @binding(1) +var canvas_sampler: sampler; + +@group(1) @binding(0) +var rect: vec4; + +@vertex +fn vs_main( + @builtin(vertex_index) index: u32, +) -> VertexOutput { + var out: VertexOutput; + + let model = QUAD_VERTICES[index]; + + out.uv = QUAD_UVS[index]; + out.clip_position = vec4( + -1.0 + (model.x * rect.z), + -1.0 + (model.y * rect.w), + 0.0, + 1.0); + + return out; +} + +@fragment +fn fs_main(in: VertexOutput) +-> @location(0) vec4 +{ + return textureSample(canvas_texture, canvas_sampler, in.uv); +} diff --git a/src/fleet.rs b/src/fleet.rs index 344aef3..2d7dad5 100644 --- a/src/fleet.rs +++ b/src/fleet.rs @@ -1,16 +1,17 @@ -use std::cell::RefCell; -use std::collections::HashMap; -use std::collections::hash_map::Iter; +pub mod schedule; + +use std::collections::{BinaryHeap, HashMap}; use std::error::Error; -use std::iter::Filter; +use std::ops::{Range, RangeBounds}; use cgmath::Zero; use crate::GameState; +use crate::fleet::schedule::{Schedule, ScheduleManager}; 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::solar_system::{SystemId, GRAVITATIONAL_CONSTANT, Kilometers, SolarSystem, orbit::StaticOrbit}; +use crate::timeman::{HOUR, Second}; use crate::ui::fleet_window::NewFleet; pub type FleetId = usize; @@ -24,6 +25,12 @@ pub struct Fleet offset: cgmath::Vector3, heading: cgmath::Vector2>, + velocity: cgmath::Vector3, + acceleration: cgmath::Vector3, + + angular_velocity: cgmath::Vector2, + angular_acceleration: cgmath::Vector2, + system: Option, baked_orbit: Option<(f64, StaticOrbit)>, } @@ -31,7 +38,7 @@ pub struct Fleet pub struct FleetsManager { next_id: FleetId, - fleets: HashMap + fleets: HashMap, } impl StaticOrbiter for Fleet { @@ -54,6 +61,8 @@ impl StaticOrbiter for Fleet { impl Fleet { + pub const SUBTICK_DURATION: Second = HOUR; + pub fn new( id: FleetId, name: String) @@ -67,9 +76,15 @@ impl Fleet heading: cgmath::vec2( cgmath::Rad::zero(), cgmath::Rad::zero()), + + velocity: cgmath::vec3(0.0, 0.0, 0.0), + acceleration: cgmath::vec3(0.0, 0.0, 0.0), + + angular_velocity: cgmath::vec2(0.0, 0.0), + angular_acceleration: cgmath::vec2(0.0, 0.0), system: None, - baked_orbit: None, + baked_orbit: None } } @@ -97,20 +112,20 @@ impl Fleet self.system = Some(star_system.id()); } - fn subtick( + fn tick( &mut self, star_systems: &[SolarSystem], - time: Second) - -> Result<(), ()> + time: &Range) { 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); + self.origin = body.absolute_position(solar_system, time.end); + self.offset = orbit.calculate_position_at(self, time.end); + }else{ + } - Ok(()) } } // impl Fleet @@ -143,53 +158,66 @@ impl FleetsManager system: SystemId) -> Vec { - self.fleets.iter().filter_map(|v| { - if v.1.system().is_some_and(|id| { id == system }) { - Some(*v.0) + self.fleets.values().filter_map(|v| { + if v.system().is_some_and(|id| { id == system }) { + Some(v.id()) }else { None } }).collect() } - pub fn entry_mut( - &mut self, - id: FleetId) - -> Option<&mut Fleet> + pub fn fleet_mut(&mut self, id: FleetId) -> Option<&mut Fleet> { self.fleets.get_mut(&id) } - - pub fn entry( - &self, - id: FleetId) - -> Option<&Fleet> + pub fn fleet(&self, id: FleetId) -> Option<&Fleet> { self.fleets.get(&id) } - fn subtick_fleet( + fn subtick( &mut self, star_systems: &[SolarSystem], - time: Second) - -> Result<(), ()> + scheduler: &mut ScheduleManager, + time: &Range) + -> Result<(), Second> { - 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(()); - } + let interrupt = scheduler.subtick(self, star_systems, time); + let new_range = time.start.. + (if interrupt.is_err() + { interrupt.expect_err("Expect interrupt") } + else { time.end } + ); + + for (_, fleet) in self.fleets.iter_mut() { + fleet.tick(star_systems, &new_range); } - tick_interrupt + + interrupt } pub fn tick( &mut self, star_systems: &[SolarSystem], - time_then: Second, - time_now: Second) + scheduler: &mut ScheduleManager, + time: Range) -> Result<(), Second> { - self.subtick_fleet(star_systems, time_now); + let delta_time = time.end - time.start; + let subticks = delta_time / Fleet::SUBTICK_DURATION; + + for tick in 0..subticks { + let last_tick_time = time.start + tick * Fleet::SUBTICK_DURATION; + let now_tick_time = last_tick_time + Fleet::SUBTICK_DURATION; + + let tick_time = last_tick_time..now_tick_time; + let interrupt = self.subtick(star_systems, scheduler, &tick_time); + if interrupt.is_err() { + return interrupt; + } + } + + let last_tick_start = time.start + subticks * Fleet::SUBTICK_DURATION; + let last_tick_time = last_tick_start..time.end; - Ok(()) + self.subtick(star_systems, scheduler, &last_tick_time) } } // impl FleetsManager @@ -206,9 +234,7 @@ impl GameState 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); + self.fleets.fleets.insert(fleet_id, fleet); Ok(fleet_id) } diff --git a/src/fleet/schedule.rs b/src/fleet/schedule.rs new file mode 100644 index 0000000..c0e275b --- /dev/null +++ b/src/fleet/schedule.rs @@ -0,0 +1,190 @@ +use std::collections::{BinaryHeap, HashMap, VecDeque}; +use std::ops::Range; +use std::sync::RwLock; + +use crate::fleet::{Fleet, FleetId, FleetsManager}; +use crate::timeman::Second; +use crate::solar_system::{Kilometers, SolarSystem}; + +pub trait ScheduleCommand +{ + //Remaining time interval for this command. + fn time_range(&self, time: &Range) -> Range; + + fn tick( + &self, + star_systems: &[SolarSystem], + fleet: &mut Fleet, + time: &Range) + -> Result<(), Second>; +} + +pub struct Schedule +{ + commands: Vec>, + groups: Vec>, + start_time: Second +} + +pub struct ScheduleManager +{ + schedules: HashMap +} + +impl Schedule +{ + pub fn new() + -> Self { + Self { + commands: vec![], + groups: vec![], + start_time: 0 + } + } + + pub fn commands(&self) -> &[Box] { &self.commands } + + pub fn top(&self) -> Option<&Box> { self.commands.first() } + pub fn top_mut(&mut self) -> Option<&mut Box> { self.commands.first_mut() } + + pub fn pop(&mut self) -> Option> { self.commands.pop() } + + pub fn add_command( + &mut self, + command: T) + { + self.commands.push(Box::new(command)); + } + + pub(crate) fn tick( + &mut self, + star_systems: &[SolarSystem], + fleet: &mut Fleet, + time: &Range) + -> Result<(), Second> + { + while let Some(command) = self.top() { + if command.time_range(time).end > time.start { + self.pop(); + continue; + } + + return command.tick(star_systems, fleet, time); + }; + Ok(()) + } +} // impl Schedule + +impl ScheduleManager +{ + pub fn new() -> Self + { + Self { + schedules: HashMap::new() + } + } + + pub fn schedules_mut( + &mut self) + -> std::collections::hash_map::IterMut<'_, FleetId, Schedule> + { + self.schedules.iter_mut() + } + + pub fn schedules( + &self) + -> std::collections::hash_map::Iter<'_, FleetId, Schedule> + { + self.schedules.iter() + } + + pub fn add_command_to_schedule + ( + &mut self, + id: FleetId, + command: T) + { + if let Some(schedule) = self.schedules.get_mut(&id) { + schedule.add_command(command); + } + } + + pub fn schedule_mut( + &mut self, + id: FleetId) + -> Option<&mut Schedule> + { + self.schedules.get_mut(&id) + } + + pub fn schedule( + &self, + id: FleetId) + -> Option<&Schedule> + { + self.schedules.get(&id) + } + + pub fn remove_schedule( + &mut self, + id: FleetId) + { + self.schedules.remove_entry(&id); + } + + fn next_schedule_tick( + &self, + time: &Range) + -> Option<(FleetId, Second)> { + let mut least_cmd: Option<(FleetId, Second)> = None; + for (id, schedule) in self.schedules.iter() { + let top_cmd = match schedule.top() { + Some(cmd) => cmd.time_range(time).end, + None => { continue; } + }; + + if let Some(least_val) = least_cmd { + if least_val.1 > top_cmd { + least_cmd = Some((*id, top_cmd)); + } + }else{ + least_cmd = Some((*id, top_cmd)); + } + } + least_cmd + } + + pub fn subtick( + &mut self, + fleets_man: &mut FleetsManager, + star_systems: &[SolarSystem], + time: &Range) + -> Result<(), Second> + { + let mut last_time = time.start; + while let Some((fleet_id, fleet_tick)) = self.next_schedule_tick(time) { + if fleet_tick > time.end { + break; + } + let fleet = match fleets_man.fleet_mut(fleet_id) { + Some(v) => v, + None => { + self.remove_schedule(fleet_id); + continue; + } + }; + + let schedule = self.schedules.get_mut(&fleet_id).unwrap(); + let tick_range = last_time..fleet_tick; + let schedule_tick_interrupt = schedule.tick( + star_systems, fleet, &tick_range); + + if schedule_tick_interrupt.is_err() { + return schedule_tick_interrupt; + } + + last_time = fleet_tick; + }; + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 68ecd55..e4d0518 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ use winit::window::WindowId; use solar_system::*; use crate::fleet::FleetsManager; +use crate::fleet::schedule::ScheduleManager; use crate::timeman::TimeMan; use crate::window::GameWindow; @@ -44,7 +45,8 @@ struct GameState { timeman: TimeMan, solar_systems: Vec, - fleets: FleetsManager + fleets: FleetsManager, + scheduler: ScheduleManager, } impl SystemicApp @@ -75,7 +77,8 @@ impl GameState Self { timeman: timeman, solar_systems: vec![ sol_system ], - fleets: FleetsManager::new() + fleets: FleetsManager::new(), + scheduler: ScheduleManager::new() } } @@ -105,9 +108,9 @@ impl GameState if time_then == self.timeman.seconds() { return; } self.fleets.tick( - &self.solar_systems, - time_then, - self.timeman.seconds()); + &self.solar_systems, + &mut self.scheduler, + time_then..self.timeman.seconds()); } } diff --git a/src/ntree.rs b/src/ntree.rs index 26c241c..5500649 100644 --- a/src/ntree.rs +++ b/src/ntree.rs @@ -7,32 +7,6 @@ pub struct NTreeNode } -#[derive(Default, Clone)] -pub struct NTree -{ - root: Option> -} - -impl NTree -{ - pub fn set_root_val( - &mut self, - val: T) - { - self.root = Some(NTreeNode { value: val, children: vec![] }); - } - - pub fn set_root( - &mut self, - node: NTreeNode) - { - self.root = Some(node); - } - - pub fn root(&self) -> &Option> { &self.root } - pub fn root_mut(&mut self) -> &mut Option> { &mut self.root } -} - impl NTreeNode { pub fn new(val: T) @@ -49,12 +23,6 @@ impl NTreeNode pub fn children(&self) -> &[NTreeNode] { &self.children.as_slice() } - pub fn as_tree(self) - -> NTree - { - NTree { root: Some(self) } - } - pub fn insert_value( &mut self, val: T) @@ -69,13 +37,13 @@ impl NTreeNode self.children.push(subtree); } - pub fn insert_tree( - &mut self, - subtree: NTree) + pub fn traverse_preorder(&self) + -> Vec<&T> { - match subtree.root { - Some(subtree_root) => self.insert_node(subtree_root), - None => { panic!("Tried to put null subtree") } + let mut result = vec![self.value()]; + for child in &self.children { + result.extend(child.traverse_preorder().iter()); } + result } } diff --git a/src/solar_system.rs b/src/solar_system.rs index 7799a59..a23dff8 100644 --- a/src/solar_system.rs +++ b/src/solar_system.rs @@ -6,7 +6,6 @@ use self::orbit::*; use serde::{Deserialize}; use crate::known_stars::*; -use crate::ntree::NTree; use crate::ntree::NTreeNode; use crate::timeman::Second; use std::error::Error; diff --git a/src/tacmap.rs b/src/tacmap.rs index 05747d4..e04db9d 100644 --- a/src/tacmap.rs +++ b/src/tacmap.rs @@ -13,11 +13,14 @@ use winit::keyboard::KeyCode; use crate::fleet::FleetsManager; use crate::solar_system::SolarSystem; use crate::solar_system::SystemId; +use crate::texture::Texture; use crate::timeman::Second; use crate::timeman::TimeMan; use crate::ui; +use crate::wgpuctx::RenderPassBuilder; use crate::wgpuctx::SceneCtx; use crate::wgpuctx::WgpuCtx; +use crate::wgpuctx::pipeline::RenderPipelineBuilder; use render::*; use body_render::*; @@ -27,6 +30,12 @@ use camera::*; pub struct TacticalMap { + canvas_size: winit::dpi::PhysicalSize, + canvas_texture: Option, + canvas_pipeline: wgpu::RenderPipeline, + canvas_buffer: wgpu::Buffer, + canvas_bindgroup: wgpu::BindGroup, + camera: Camera, pmatrix: Projection, @@ -41,12 +50,71 @@ pub struct TacticalMap impl TacticalMap { + fn bindgroup_layout(device: &wgpu::Device) + -> wgpu::BindGroupLayout + { + device.create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None + }, + count: None, + } + ] + } + ) + } + pub fn new(wgpuctx: &WgpuCtx) -> Self { let surface_size = winit::dpi::PhysicalSize::new( wgpuctx.surface_config().width, wgpuctx.surface_config().height ); + + let canvas_shader = wgpuctx.create_shader( + wgpu::include_wgsl!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/assets/shaders/canvas.wgsl") + )); + + let canvas_texture_bindgroup_layout = Texture::bindgroup_layout(wgpuctx.device()); + let canvas_position_bindgroup_layout = Self::bindgroup_layout(wgpuctx.device()); + + let canvas_pipeline = RenderPipelineBuilder::new(&canvas_shader) + .primitive_topology(wgpu::PrimitiveTopology::TriangleStrip) + .cull_mode(None) + .add_bindgroup(&canvas_texture_bindgroup_layout) + .add_bindgroup(&canvas_position_bindgroup_layout) + .build(Some("Tactical map canvas pipeline"), wgpuctx); + + let canvas_buffer = wgpuctx.create_buffer( + &wgpu::BufferDescriptor { + label: None, + size: 4 * std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | + wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + } + ); + + let canvas_bindgroup = wgpuctx.device().create_bind_group( + &wgpu::BindGroupDescriptor { + label: None, + layout: &canvas_position_bindgroup_layout, + entries: &[ wgpu::BindGroupEntry { + binding: 0, + resource: canvas_buffer.as_entire_binding(), + }], + } + ); let camera = Camera::new( wgpuctx, @@ -61,6 +129,12 @@ impl TacticalMap (0.01, 1000.0)); Self { + canvas_size: winit::dpi::PhysicalSize::new(0, 0), + canvas_texture: None, + canvas_pipeline: canvas_pipeline, + canvas_buffer: canvas_buffer, + canvas_bindgroup: canvas_bindgroup, + camera: camera, pmatrix: projection, camera_controller: CameraController::new(), @@ -78,7 +152,12 @@ impl TacticalMap width: u32, height: u32) { - self.pmatrix.resize(width, height); + let new_size = winit::dpi::PhysicalSize::new(width, height); + if self.canvas_size != new_size { + self.pmatrix.resize(width, height); + self.canvas_size = new_size; + self.canvas_texture = None; + } } pub fn update( @@ -103,15 +182,36 @@ impl TacticalMap pub fn prepare( &mut self, - scene: &mut SceneCtx, + rect: egui::Rect, wgpuctx: &WgpuCtx, fleets_man: &FleetsManager, solar_system: &SolarSystem, timeman: &TimeMan, ) -> Result<(), ()> { + //Prepare canvas + if self.canvas_texture.is_none() { + println!("Rebuilding canvas texture to {:?}", rect); + self.canvas_texture = Some(wgpuctx.new_texture( + wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: self.canvas_size.width, + height: self.canvas_size.height, + depth_or_array_layers: 1 + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpuctx.surface_config().format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | + wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &wgpuctx.surface_config().view_formats + } + )); + } + self.camera.update_buffer(wgpuctx, &self.pmatrix); - self.camera.stage_changes(scene.encoder_mut()); if self.system_for_render.is_none() || self.system_for_render.unwrap() != solar_system.id() { @@ -151,6 +251,34 @@ impl TacticalMap Err(e) => println!("Tactical map fleet render update error: {}", e) } + let window_width = wgpuctx.surface_config().width as f32; + let window_height = wgpuctx.surface_config().height as f32; + wgpuctx.queue().write_buffer(&self.canvas_buffer, 0, + bytemuck::cast_slice(&[ + rect.min.x / window_width, + rect.min.y / window_height, + rect.width() / window_width, + rect.height() / window_height + ])); + + let canvas_view = self.canvas_texture.as_ref().unwrap().view(); + let mut canvas_scene = SceneCtx::from_view_default(wgpuctx, canvas_view, Some("Tactical map canvas scene")); + + self.camera.stage_changes(canvas_scene.encoder_mut()); + + { + let mut pass = RenderPassBuilder::new(Some("Tactical map render pass"), canvas_view) + .clear_color(wgpu::Color { r: 0.0, g: 0.0, b: 0.1, a: 1.0 }) + .build_from_scene(&mut canvas_scene); + + self.grid_renderer.render( &mut pass, &self.camera); + self.orbit_renderer.render(&mut pass, &self.camera); + self.fleet_renderer.render(&mut pass, &self.camera); + self.body_renderer.render( &mut pass, &self.camera); + } + + canvas_scene.submit(wgpuctx); + Ok(()) } @@ -158,10 +286,12 @@ impl TacticalMap &self, pass: &mut wgpu::RenderPass<'_>) { - 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); + pass.set_pipeline(&self.canvas_pipeline); + + self.canvas_texture.as_ref().unwrap().use_on_pass(pass, 0); + pass.set_bind_group(1, &self.canvas_bindgroup, &[]); + + pass.draw(0..4, 0..1); } pub fn paint_labels( diff --git a/src/tacmap/camera.rs b/src/tacmap/camera.rs index 1f2549b..d751a2a 100644 --- a/src/tacmap/camera.rs +++ b/src/tacmap/camera.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use cgmath::{InnerSpace, Point3, Rad, Vector2, Vector3, Zero, perspective}; +use cgmath::{InnerSpace, Rad, Vector2, Vector3, Zero, perspective}; use winit::event::ElementState; use winit::keyboard::KeyCode; diff --git a/src/tacmap/fleet_render.rs b/src/tacmap/fleet_render.rs index ba21318..cf23033 100644 --- a/src/tacmap/fleet_render.rs +++ b/src/tacmap/fleet_render.rs @@ -80,7 +80,7 @@ impl FleetRenderer let fleets = solar_system.fleets(fleets_manager); let fleets_instances = fleets.iter().filter_map(|id| { - let fleet = match fleets_manager.entry(*id) { + let fleet = match fleets_manager.fleet(*id) { Some(fleet) => fleet, None => { return None; } }; diff --git a/src/tacmap/orbit_render.rs b/src/tacmap/orbit_render.rs index 32c0abb..d4ca2fa 100644 --- a/src/tacmap/orbit_render.rs +++ b/src/tacmap/orbit_render.rs @@ -201,7 +201,7 @@ impl OrbitRenderer self.fleet_orbit_vertex_buffers = Some(fleets_man.all_in_system(solar_system.id()).iter().filter_map(|id| { - let fleet = fleets_man.entry(*id).unwrap(); + let fleet = fleets_man.fleet(*id).unwrap(); self.create_orbit_buffer(wgpuctx, fleet, time) }).collect::>()); diff --git a/src/texture.rs b/src/texture.rs index cef96d6..9cc0656 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -29,31 +29,7 @@ impl Texture } ); - let bind_group_layout = device.create_bind_group_layout( - &wgpu::BindGroupLayoutDescriptor { - label: None, - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { - filterable: true - }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false - }, - count: None - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None - } - ] - } - ); + let bind_group_layout = Self::bindgroup_layout(device); let bind_group = device.create_bind_group( &wgpu::BindGroupDescriptor { @@ -81,6 +57,37 @@ impl Texture } } + pub fn bindgroup_layout( + device: &wgpu::Device) + -> wgpu::BindGroupLayout + { + device.create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: true + }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false + }, + count: None + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None + } + ] + } + ) + } + pub fn view(&self) -> &wgpu::TextureView { &self.view } diff --git a/src/ui.rs b/src/ui.rs index 73389a8..e1972d9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,11 +1,11 @@ pub mod topbar; pub mod bodies_window; pub mod fleet_window; +pub mod fleet_schedule; use std::{borrow::Borrow, cell::RefCell}; use crate::GameState; -use crate::eguictx::EguiCtx; use crate::solar_system::body::BodyId; use crate::ui::bodies_window::BodiesWindowState; use crate::ui::fleet_window::FleetWindowState; @@ -42,26 +42,24 @@ impl State 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 let Some(current_system_id) = self.topbar_sate.current_system { + let current_system = &game_state.solar_systems()[current_system_id]; + let bodies_window_action = + self.bodies_window.paint(ui, current_system); + if bodies_window_action.focus_body.is_some() { + self.camera_target = bodies_window_action.focus_body; + } - let bodies_window_action = - self.bodies_window.paint(ui, current_system); - if bodies_window_action.focus_body.is_some() { - self.camera_target = bodies_window_action.focus_body; - } - - let fleet_window_action = - self.fleet_window.paint( - ui, - game_state.borrow(), - &self.topbar_sate.current_system, - &self.camera_target); + let fleet_window_action = + self.fleet_window.paint( + ui, + game_state.borrow(), + &self.topbar_sate.current_system, + &self.camera_target); - if let Some(new_fleet) = fleet_window_action.new_fleet { - game_state.new_fleet_from_ui(new_fleet); + 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 7e24948..a93f11c 100644 --- a/src/ui/bodies_window.rs +++ b/src/ui/bodies_window.rs @@ -1,6 +1,6 @@ -use crate::ntree::{NTree, NTreeNode}; -use crate::solar_system::body::{BodyId, OrbitalBody}; -use crate::solar_system::{SolarSystem, SystemId}; +use crate::ntree::NTreeNode; +use crate::solar_system::body::BodyId; +use crate::solar_system::SolarSystem; use crate::timeman::TimeMan; @@ -88,7 +88,6 @@ impl BodiesWindowState -> Option { let resp = egui::ScrollArea::vertical() - .auto_shrink(true) .min_scrolled_height(200.0) .show(ui, |ui| { ui.vertical(|ui| { diff --git a/src/ui/fleet_schedule.rs b/src/ui/fleet_schedule.rs new file mode 100644 index 0000000..b0e5dd7 --- /dev/null +++ b/src/ui/fleet_schedule.rs @@ -0,0 +1,150 @@ +use crate::solar_system::SolarSystem; +use crate::solar_system::body::BodyId; +use crate::fleet::{Fleet, FleetId, FleetsManager}; + +#[derive(Clone)] +pub struct FleetScheduleWindow +{ + pub target: (Option, Option), + pub linear_acceleration_limit: f32, + pub angular_acceleration_limit: f32, +} + +impl Default for FleetScheduleWindow +{ + fn default() -> Self { + Self { + target: Default::default(), + linear_acceleration_limit: 9.81, + angular_acceleration_limit: 15.0 + } + } +} + +impl FleetScheduleWindow +{ + pub fn paint( + &mut self, + star_systems: &[SolarSystem], + fleets_man: &FleetsManager, + fleet: &Fleet, + ui: &mut egui::Ui) + { + ui.horizontal(|limits_ui| { + limits_ui.vertical(|linear_limit_ui| { + linear_limit_ui.label("Linear acceleration limit"); + linear_limit_ui.add( + egui::DragValue::new(&mut self.linear_acceleration_limit) + .range(0.0..=(9.8*2.0)) + .clamp_existing_to_range(true) + .suffix(" m/s^2") + .speed(0.1) + .max_decimals_opt(Some(2)) + ); + }); + limits_ui.separator(); + limits_ui.vertical(|angular_limit_ui| { + angular_limit_ui.label("Angular acceleration limit"); + angular_limit_ui.add( + egui::DragValue::new(&mut self.angular_acceleration_limit) + .range(0.0..=30.0) + .clamp_existing_to_range(true) + .suffix(" °/s^2") + .speed(0.1) + .max_decimals_opt(Some(2)) + ); + }); + }); + ui.separator(); + + let fleet_system = &star_systems[fleet.system().unwrap_or(0)]; + let bodies = fleet_system.bodies(); + let bodies_order = fleet_system.heirarchy().traverse_preorder(); + let system_fleets = fleet_system.fleets(fleets_man); + + let row_height = ui.spacing().interact_size.y; + ui.horizontal(|panel_ui| + { + panel_ui.vertical(|vertical_ui| { + vertical_ui.set_max_width(128.0); + vertical_ui.vertical_centered(|centered_ui| { + centered_ui.label("Target"); + }); + egui::ScrollArea::vertical() + .id_salt("fleet_schedule_objs") + .auto_shrink(false) + .min_scrolled_height(256.0) + .show_rows(vertical_ui, row_height, bodies.len() + system_fleets.len(), + |objs_ui, rows| { + let fleet_rows_start = 0; + let bodies_rows_start = system_fleets.len(); + + for row in rows { + match row < bodies_rows_start { + true => { + let id = system_fleets[row - fleet_rows_start]; + let selected = self.target.1 + .is_some_and(|v| { v == id }); + let fleet = fleets_man.fleet(id); + if let Some(fleet) = fleet { + if objs_ui.selectable_label(selected, fleet.name()).clicked() { + self.select_fleet_from_schedule_objs(id, selected); + } + } + }, + false => { + let id = *bodies_order[row - bodies_rows_start]; + let selected = self.target.0 + .is_some_and(|v| { v == id }); + let body = &bodies[id]; + if objs_ui.selectable_label(selected, body.name()).clicked() { + self.select_body_from_schedule_objs(id, selected); + } + } + } + } + }); + }); + + panel_ui.vertical(|vertical_ui| { + vertical_ui.set_max_width(128.0); + vertical_ui.vertical_centered(|centered_ui| { + centered_ui.label("Commands"); + }); + egui::ScrollArea::vertical() + .id_salt("fleet_schedule_commands") + .auto_shrink(false) + .min_scrolled_height(256.0) + .show_rows(vertical_ui, row_height, 0, + |cmd_ui, rows| { + + }); + }); + + panel_ui.vertical(|vertical_ui| { + vertical_ui.set_max_width(128.0); + vertical_ui.vertical_centered(|centered_ui| { + centered_ui.label("Schedule"); + }); + }); + }); + } + + fn select_body_from_schedule_objs( + &mut self, + id: BodyId, + selected: bool) + { + self.target = + (if selected { None } else { Some(id) }, None); + } + + fn select_fleet_from_schedule_objs( + &mut self, + id: FleetId, + selected: bool) + { + self.target = + (None, if selected { None } else { Some(id) }); + } +} diff --git a/src/ui/fleet_window.rs b/src/ui/fleet_window.rs index 55e3d59..4db4ce8 100644 --- a/src/ui/fleet_window.rs +++ b/src/ui/fleet_window.rs @@ -3,6 +3,7 @@ use crate::fleet::{Fleet, FleetId, FleetsManager}; use crate::solar_system::orbit::StaticOrbiter; use crate::solar_system::{Kilometers, SolarSystem, SystemId}; use crate::solar_system::body::BodyId; +use crate::ui::fleet_schedule::FleetScheduleWindow; #[derive(Default, Clone, PartialEq)] enum FleetMenuPanelSel { @@ -16,7 +17,9 @@ pub struct FleetWindowState { pub open: bool, pub selected_fleet: Option, - pub menu_panel: FleetMenuPanelSel, + + menu_panel: FleetMenuPanelSel, + fleet_schedule_window: FleetScheduleWindow, pub new_fleet_window: Option } @@ -107,7 +110,7 @@ impl FleetWindowState ui: &mut egui::Ui) { let fleet = match self.selected_fleet { - Some(id) => { fleets_man.entry(id) }, + Some(id) => { fleets_man.fleet(id) }, None => { return; } }; let fleet = match fleet { @@ -128,64 +131,74 @@ impl FleetWindowState self.menu_panel = FleetMenuPanelSel::Schedule; } }); - self.paint_fleet_menu_info(star_systems, fleets_man, fleet, ui); + + egui::Frame::new() + .stroke(egui::Stroke::new(1.0, egui::Color32::LIGHT_GRAY)) + .show(ui, |ui| { + ui.set_min_width(200.0); + ui.set_min_height(200.0); + ui.style_mut().interaction.selectable_labels = false; + + match self.menu_panel { + FleetMenuPanelSel::Info => + self.paint_fleet_menu_info(star_systems, fleets_man, fleet, ui), + FleetMenuPanelSel::Schedule => + self.fleet_schedule_window.paint(star_systems, fleets_man, fleet, ui) + }; + }); }); } fn paint_fleet_menu_info( &mut self, star_systems: &[SolarSystem], - fleets_man: &FleetsManager, + _fleets_man: &FleetsManager, fleet: &Fleet, ui: &mut egui::Ui) { if self.menu_panel != FleetMenuPanelSel::Info { return; } - egui::Frame::canvas(ui.style()).show(ui, |ui| { - ui.set_min_width(200.0); - ui.style_mut().interaction.selectable_labels = false; - if let Some(orbit) = fleet.orbit() { - let star_system = &star_systems[orbit.system()]; - let parent = star_system.body(orbit.parent()); + if let Some(orbit) = fleet.orbit() { + let star_system = &star_systems[orbit.system()]; + let parent = star_system.body(orbit.parent()); - ui.push_id("orbital_info_table", |ui| { - egui_extras::TableBuilder::new(ui) - .column(egui_extras::Column::auto()) - .column(egui_extras::Column::remainder()) - .body(|mut body| { - body.row(16.0, |mut row| { - row.col(|col| { col.label("System"); }); - row.col(|col| { col.label(star_system.name()); }); - }); - body.row(16.0, |mut row| { - row.col(|col| { col.label("Orbiting"); }); - row.col(|col| { col.label(parent.name()); }); - }); - body.row(16.0, |mut row| { - row.col(|col| { col.label("Radius"); }); - row.col(|col| { col.label(format!("{:.1}", orbit.sma())); }); - }); + ui.push_id("orbital_info_table", |ui| { + egui_extras::TableBuilder::new(ui) + .column(egui_extras::Column::auto()) + .column(egui_extras::Column::remainder()) + .body(|mut body| { + body.row(16.0, |mut row| { + row.col(|col| { col.label("System"); }); + row.col(|col| { col.label(star_system.name()); }); + }); + body.row(16.0, |mut row| { + row.col(|col| { col.label("Orbiting"); }); + row.col(|col| { col.label(parent.name()); }); + }); + body.row(16.0, |mut row| { + row.col(|col| { col.label("Radius"); }); + row.col(|col| { col.label(format!("{:.1}", orbit.sma())); }); }); }); + }); - ui.separator(); - } + ui.separator(); + } - let heading = fleet.heading(); - egui_extras::TableBuilder::new(ui) - .column(egui_extras::Column::auto()) - .column(egui_extras::Column::remainder()) - .body(|mut body| { - body.row(16.0, |mut row| { - row.col(|col| { col.label("Heading"); }); - row.col(|col| { - col.label(format!("{:?} by {:?}", - cgmath::Deg::::from(heading.x), - cgmath::Deg::::from(heading.y) - )); - }); + let heading = fleet.heading(); + egui_extras::TableBuilder::new(ui) + .column(egui_extras::Column::auto()) + .column(egui_extras::Column::remainder()) + .body(|mut body| { + body.row(16.0, |mut row| { + row.col(|col| { col.label("Heading"); }); + row.col(|col| { + col.label(format!("{:?} by {:?}", + cgmath::Deg::::from(heading.x), + cgmath::Deg::::from(heading.y) + )); }); }); }); @@ -199,7 +212,7 @@ impl FleetWindowState let fleet_ids = fleets_man.all(); ui.vertical(|ui| { for id in fleet_ids { - if let Some(fleet) = fleets_man.entry(id) { + if let Some(fleet) = fleets_man.fleet(id) { self.paint_fleet_entry(fleet, ui); } } @@ -213,9 +226,18 @@ impl FleetWindowState { let selected = self.selected_fleet.is_some_and(|id| { id == fleet.id() }); if ui.selectable_label(selected, fleet.name()).clicked() { - self.selected_fleet = if selected { None } else { Some(fleet.id()) }; + self.select_fleet_from_entries_list(fleet, selected); } } + + fn select_fleet_from_entries_list( + &mut self, + fleet: &Fleet, + selected: bool) + { + self.selected_fleet = if selected { None } else { Some(fleet.id()) }; + self.fleet_schedule_window.target = (None, None); + } } // FleetWindowAction impl NewFleetWindowState @@ -287,4 +309,6 @@ impl Default for NewFleet sma: Default::default() } } -} +} // impl NewFleet + + diff --git a/src/ui/topbar.rs b/src/ui/topbar.rs index 511c67e..f560310 100644 --- a/src/ui/topbar.rs +++ b/src/ui/topbar.rs @@ -1,6 +1,5 @@ -use std::cell::RefCell; -use crate::{GameState, eguictx::EguiCtx, solar_system::SystemId, timeman::{self, Second, TimeMan}, ui::{self, bodies_window::BodiesWindowState, fleet_window::FleetWindowState}}; +use crate::{GameState, solar_system::SystemId, timeman::{self, Second, TimeMan}, ui::{bodies_window::BodiesWindowState, fleet_window::FleetWindowState}}; #[derive(Default, Clone)] pub struct TopBarState diff --git a/src/window.rs b/src/window.rs index 3c395d5..18021f4 100644 --- a/src/window.rs +++ b/src/window.rs @@ -94,7 +94,7 @@ impl GameWindow pub fn render( &mut self, game_state: &RefCell, - egui_event_resp: egui_winit::EventResponse) + _egui_event_resp: egui_winit::EventResponse) -> Result<(), wgpu::CurrentSurfaceTexture> { if !self.wgpuctx.is_ready() { return Ok(()); @@ -116,30 +116,39 @@ impl GameWindow .title_bar(false) .interactable(false) .frame(egui::Frame::NONE) - .show(self.eguictx.context(), |ui| { + .show(self.eguictx.context(), + |ui| { self.ui_state.paint(game_state, ui); - + 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()]; - match self.tactical_map.prepare( - &mut scene, - &self.wgpuctx, - fleets_manager, - current_system, - game_state.timeman()) - { - Ok(_) => {}, - Err(_) => { - println!("Error in tactical map prepare"); - panic!(); + egui::CentralPanel::no_frame().show_inside(ui, |central_panel| { + let panel_rect = central_panel.max_rect(); + + self.tactical_map.resize( + panel_rect.width() as u32, + panel_rect.height() as u32); + + match self.tactical_map.prepare( + panel_rect, + &self.wgpuctx, + fleets_manager, + current_system, + game_state.timeman()) + { + Ok(_) => {}, + Err(_) => { + println!("Error in tactical map prepare"); + panic!(); + } } - } - + }); + let mut pass = RenderPassBuilder::new(Some("Systemic window render pass"), &view) - .clear_color(wgpu::Color { r: 0.0, g: 0.0, b: 0.1, a: 1.0 }) + .clear_color(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }) .build_from_scene(&mut scene); //Draw the tactical map canvas. -- cgit v1.2.3