summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--assets/shaders/canvas.wgsl51
-rw-r--r--src/fleet.rs112
-rw-r--r--src/fleet/schedule.rs190
-rw-r--r--src/main.rs13
-rw-r--r--src/ntree.rs44
-rw-r--r--src/solar_system.rs1
-rw-r--r--src/tacmap.rs144
-rw-r--r--src/tacmap/camera.rs2
-rw-r--r--src/tacmap/fleet_render.rs2
-rw-r--r--src/tacmap/orbit_render.rs2
-rw-r--r--src/texture.rs57
-rw-r--r--src/ui.rs36
-rw-r--r--src/ui/bodies_window.rs7
-rw-r--r--src/ui/fleet_schedule.rs150
-rw-r--r--src/ui/fleet_window.rs114
-rw-r--r--src/ui/topbar.rs3
-rw-r--r--src/window.rs43
17 files changed, 762 insertions, 209 deletions
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<f32>,
+ @location(0) uv: vec2<f32>
+}
+
+const QUAD_VERTICES = array<vec2<f32>,4>(
+ vec2<f32>(0.0, 0.0),
+ vec2<f32>(2.0, 0.0),
+ vec2<f32>(0.0, 2.0),
+ vec2<f32>(2.0, 2.0),
+);
+
+const QUAD_UVS = array<vec2<f32>,4>(
+ vec2<f32>(0.0, 1.0),
+ vec2<f32>(1.0, 1.0),
+ vec2<f32>(0.0, 0.0),
+ vec2<f32>(1.0, 0.0),
+);
+
+@group(0) @binding(0)
+var canvas_texture: texture_2d<f32>;
+@group(0) @binding(1)
+var canvas_sampler: sampler;
+
+@group(1) @binding(0)
+var<uniform> rect: vec4<f32>;
+
+@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<f32>(
+ -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<f32>
+{
+ 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<Kilometers>,
heading: cgmath::Vector2<cgmath::Rad<f32>>,
+ velocity: cgmath::Vector3<f32>,
+ acceleration: cgmath::Vector3<f32>,
+
+ angular_velocity: cgmath::Vector2<f32>,
+ angular_acceleration: cgmath::Vector2<f32>,
+
system: Option<SystemId>,
baked_orbit: Option<(f64, StaticOrbit)>,
}
@@ -31,7 +38,7 @@ pub struct Fleet
pub struct FleetsManager
{
next_id: FleetId,
- fleets: HashMap<FleetId, Fleet>
+ fleets: HashMap<FleetId, Fleet>,
}
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<Second>)
{
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<usize>
{
- 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<Second>)
+ -> 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<Second>)
-> 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<Second>) -> Range<Second>;
+
+ fn tick(
+ &self,
+ star_systems: &[SolarSystem],
+ fleet: &mut Fleet,
+ time: &Range<Second>)
+ -> Result<(), Second>;
+}
+
+pub struct Schedule
+{
+ commands: Vec<Box<dyn ScheduleCommand>>,
+ groups: Vec<Range<usize>>,
+ start_time: Second
+}
+
+pub struct ScheduleManager
+{
+ schedules: HashMap<FleetId, Schedule>
+}
+
+impl Schedule
+{
+ pub fn new()
+ -> Self {
+ Self {
+ commands: vec![],
+ groups: vec![],
+ start_time: 0
+ }
+ }
+
+ pub fn commands(&self) -> &[Box<dyn ScheduleCommand>] { &self.commands }
+
+ pub fn top(&self) -> Option<&Box<dyn ScheduleCommand>> { self.commands.first() }
+ pub fn top_mut(&mut self) -> Option<&mut Box<dyn ScheduleCommand>> { self.commands.first_mut() }
+
+ pub fn pop(&mut self) -> Option<Box<dyn ScheduleCommand>> { self.commands.pop() }
+
+ pub fn add_command<T: ScheduleCommand + 'static>(
+ &mut self,
+ command: T)
+ {
+ self.commands.push(Box::new(command));
+ }
+
+ pub(crate) fn tick(
+ &mut self,
+ star_systems: &[SolarSystem],
+ fleet: &mut Fleet,
+ time: &Range<Second>)
+ -> 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
+ <T: ScheduleCommand + 'static>(
+ &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<Second>)
+ -> 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<Second>)
+ -> 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<SolarSystem>,
- 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<T>
}
-#[derive(Default, Clone)]
-pub struct NTree<T>
-{
- root: Option<NTreeNode<T>>
-}
-
-impl<T> NTree<T>
-{
- 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<T>)
- {
- self.root = Some(node);
- }
-
- pub fn root(&self) -> &Option<NTreeNode<T>> { &self.root }
- pub fn root_mut(&mut self) -> &mut Option<NTreeNode<T>> { &mut self.root }
-}
-
impl<T> NTreeNode<T>
{
pub fn new(val: T)
@@ -49,12 +23,6 @@ impl<T> NTreeNode<T>
pub fn children(&self) -> &[NTreeNode<T>] { &self.children.as_slice() }
- pub fn as_tree(self)
- -> NTree<T>
- {
- NTree { root: Some(self) }
- }
-
pub fn insert_value(
&mut self,
val: T)
@@ -69,13 +37,13 @@ impl<T> NTreeNode<T>
self.children.push(subtree);
}
- pub fn insert_tree(
- &mut self,
- subtree: NTree<T>)
+ 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<u32>,
+ canvas_texture: Option<Texture>,
+ 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::<f32>() 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::<Vec<_>>());
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<egui::Response>
{
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<BodyId>, Option<FleetId>),
+ 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<FleetId>,
- pub menu_panel: FleetMenuPanelSel,
+
+ menu_panel: FleetMenuPanelSel,
+ fleet_schedule_window: FleetScheduleWindow,
pub new_fleet_window: Option<NewFleetWindowState>
}
@@ -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::<f32>::from(heading.x),
- cgmath::Deg::<f32>::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::<f32>::from(heading.x),
+ cgmath::Deg::<f32>::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<GameState>,
- 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.