summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--assets/shaders/tacmap/body.wgsl51
-rw-r--r--assets/shaders/tacmap/fleet.wgsl73
-rw-r--r--assets/shaders/tacmap/orbit.wgsl22
-rw-r--r--src/fleet.rs255
-rw-r--r--src/main.rs32
-rw-r--r--src/solar_system.rs54
-rw-r--r--src/solar_system/body.rs46
-rw-r--r--src/solar_system/fleet.rs60
-rw-r--r--src/solar_system/orbit.rs18
-rw-r--r--src/tacmap.rs46
-rw-r--r--src/tacmap/body_render.rs32
-rw-r--r--src/tacmap/camera.rs10
-rw-r--r--src/tacmap/fleet_render.rs177
-rw-r--r--src/tacmap/orbit_render.rs70
-rw-r--r--src/timeman.rs25
-rw-r--r--src/ui.rs29
-rw-r--r--src/ui/bodies_window.rs4
-rw-r--r--src/ui/fleet_window.rs160
-rw-r--r--src/ui/topbar.rs29
-rw-r--r--src/window.rs15
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
diff --git a/src/ui.rs b/src/ui.rs
index 235481d..c2c4b05 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -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(