diff --git a/space-crush-app/src/constants.rs b/space-crush-app/src/constants.rs index 627664d..12b9f9e 100644 --- a/space-crush-app/src/constants.rs +++ b/space-crush-app/src/constants.rs @@ -4,6 +4,10 @@ use lazy_static::lazy_static; pub const UNIFORM_BUFFER_INDEX_CAMERA: gl::GLuint = 0; pub const UNIFORM_BUFFER_INDEX_UNIFORM: gl::GLuint = 1; +pub const SHIP_SIZE: f32 = 15.0; +pub const PLANET_SIZE: f32 = 200.0; +pub const ASTEROID_SIZE: f32 = 100.0; + lazy_static! { pub static ref PLAYER_COLOR_DEFAULT: Vector4f = Vector4f::new(1.0, 1.0, 1.0, 0.1); } diff --git a/space-crush-app/src/debug/mod.rs b/space-crush-app/src/debug/mod.rs index 3b244c0..8ae1b84 100644 --- a/space-crush-app/src/debug/mod.rs +++ b/space-crush-app/src/debug/mod.rs @@ -1,7 +1,9 @@ mod fleets; +mod raster; mod ships; mod summary; pub use fleets::Fleets; +pub use raster::Raster; pub use ships::Ships; pub use summary::Summary; diff --git a/space-crush-app/src/debug/raster.rs b/space-crush-app/src/debug/raster.rs new file mode 100644 index 0000000..b68869f --- /dev/null +++ b/space-crush-app/src/debug/raster.rs @@ -0,0 +1,52 @@ +use glc::vector::{Vector2f, Vector4f}; +use space_crush_common::resources::Raster as RasterResource; +use specs::{prelude::*, ReadExpect, System, World, WriteExpect}; + +use crate::resources::Geometry; + +#[derive(Default)] +pub struct Raster; + +#[derive(SystemData)] +pub struct FleetData<'a> { + geometry: WriteExpect<'a, Geometry>, + raster: ReadExpect<'a, RasterResource>, +} + +impl<'a> System<'a> for Raster { + type SystemData = FleetData<'a>; + + fn run(&mut self, data: Self::SystemData) { + let FleetData { + mut geometry, + raster, + } = data; + + let s = raster.size(); + + gl::enable(gl::BLEND); + gl::blend_func(gl::SRC_ALPHA, gl::ONE); + + for (x, y, q) in raster.quads() { + let a = if q.is_empty() { 0.05 } else { 0.5 }; + + geometry.render_lines( + Vector4f::new(1.0, 1.0, 1.0, a), + &create_quad(x as _, y as _, s), + ); + } + + gl::disable(gl::BLEND); + } +} + +fn create_quad(x: f32, y: f32, s: f32) -> Vec { + let f = s / 2.0; + vec![ + Vector2f::new(x * s + f, y * s + f), + Vector2f::new(x * s + f, y * s - f), + Vector2f::new(x * s - f, y * s - f), + Vector2f::new(x * s - f, y * s + f), + Vector2f::new(x * s + f, y * s + f), + ] +} diff --git a/space-crush-app/src/lib.rs b/space-crush-app/src/lib.rs index a9e4ff3..449c180 100644 --- a/space-crush-app/src/lib.rs +++ b/space-crush-app/src/lib.rs @@ -11,7 +11,9 @@ use specs::{Dispatcher, DispatcherBuilder, Entity, World}; pub use error::Error; -use debug::{Fleets as DebugFleets, Ships as DebugShips, Summary as DebugSummary}; +use debug::{ + Fleets as DebugFleets, Raster as RasterDebug, Ships as DebugShips, Summary as DebugSummary, +}; use misc::{Events, TextManager, Window}; use render::{Asteroids, Init, Planets, Ships}; use resources::{Camera, Config, Geometry, PlayerState, State, Uniform}; @@ -47,11 +49,12 @@ impl<'a, 'b> App<'a, 'b> { let mut dispatcher = DispatcherBuilder::new() .with(StateUpdate::new(world)?, "state_update", &[]) - .with(FleetInfoUpdate::new(world)?, "fleet_info_update", &[]) + .with(FleetInfoUpdate::new(world), "fleet_info_update", &[]) .with_thread_local(Init::new(world)?) .with_thread_local(Planets::new(world)?) .with_thread_local(Asteroids::new(world)?) .with_thread_local(Ships::new(world)?) + .with_thread_local(RasterDebug::default()) .with_thread_local(DebugShips::default()) .with_thread_local(DebugFleets::default()) .with_thread_local(DebugSummary::new(&text_manager)?) diff --git a/space-crush-app/src/main.rs b/space-crush-app/src/main.rs index 5f1c94a..15bea55 100644 --- a/space-crush-app/src/main.rs +++ b/space-crush-app/src/main.rs @@ -60,8 +60,8 @@ fn create_world(world: &mut World, player_id: Entity) { use space_crush_app::components::FleetInfo; use space_crush_common::{ components::{ - Asteroid, AsteroidType, Fleet, FleetOwned, Planet, Player, PlayerOwned, Position, Ship, - ShipType, Velocity, + Asteroid, AsteroidType, Fleet, FleetOwned, Planet, Player, PlayerOwned, Position, + Shape, Ship, ShipType, Velocity, }, misc::{PersistWorld, Persistence}, }; @@ -84,7 +84,7 @@ fn create_world(world: &mut World, player_id: Entity) { .marked::<::Marker>() .with(Position { pos: Vector2f::new(500.0, -500.0), - size: 100.0, + shape: Shape::Circle(100.0), }) .with(Fleet { orbit_min: 125.0, @@ -101,7 +101,7 @@ fn create_world(world: &mut World, player_id: Entity) { .marked::<::Marker>() .with(Position { pos: Vector2f::new(500.0, 500.0), - size: 100.0, + shape: Shape::Circle(100.0), }) .with(Fleet { orbit_min: 125.0, @@ -119,7 +119,7 @@ fn create_world(world: &mut World, player_id: Entity) { .with(PlayerOwned { owner: player_id }) .with(Position { pos: Vector2f::default(), - size: 250.0, + shape: Shape::Circle(250.0), }) .with(Fleet { orbit_min: 325.0, @@ -129,7 +129,7 @@ fn create_world(world: &mut World, player_id: Entity) { .with(Planet {}) .build(); - for i in 0..10 { + for i in 0..100 { world .create_entity() .marked::<::Marker>() @@ -139,7 +139,7 @@ fn create_world(world: &mut World, player_id: Entity) { 500.0 * random::() - 250.0, 500.0 * random::() - 250.0, ), - size: 15.0, + shape: Shape::Dot, }) .with(Velocity { dir: Vector2f::new(random::() - 0.5, random::() - 0.5).normalize(), diff --git a/space-crush-app/src/render/asteroids.rs b/space-crush-app/src/render/asteroids.rs index 465e6db..a03473f 100644 --- a/space-crush-app/src/render/asteroids.rs +++ b/space-crush-app/src/render/asteroids.rs @@ -12,7 +12,10 @@ use space_crush_common::{ use specs::{prelude::*, ReadExpect, ReadStorage, System, World}; use crate::{ - constants::{PLAYER_COLOR_DEFAULT, UNIFORM_BUFFER_INDEX_CAMERA, UNIFORM_BUFFER_INDEX_UNIFORM}, + constants::{ + ASTEROID_SIZE, PLAYER_COLOR_DEFAULT, UNIFORM_BUFFER_INDEX_CAMERA, + UNIFORM_BUFFER_INDEX_UNIFORM, + }, misc::WorldHelper, resources::Geometry, Error, @@ -85,7 +88,7 @@ impl<'a> System<'a> for Asteroids { for (position, asteroid, owned) in (&position, &asteroid, owned.maybe()).join() { let p_x = position.pos.x; let p_y = position.pos.y; - let s = position.size; + let s = position.shape.circle().unwrap_or(ASTEROID_SIZE); let _guard = match asteroid.type_ { AsteroidType::Metal => BindGuard::new(&self.texture_metal), diff --git a/space-crush-app/src/render/planets.rs b/space-crush-app/src/render/planets.rs index 7aeb5f4..dc20229 100644 --- a/space-crush-app/src/render/planets.rs +++ b/space-crush-app/src/render/planets.rs @@ -12,7 +12,10 @@ use space_crush_common::{ use specs::{prelude::*, ReadExpect, ReadStorage, System, World}; use crate::{ - constants::{PLAYER_COLOR_DEFAULT, UNIFORM_BUFFER_INDEX_CAMERA, UNIFORM_BUFFER_INDEX_UNIFORM}, + constants::{ + PLANET_SIZE, PLAYER_COLOR_DEFAULT, UNIFORM_BUFFER_INDEX_CAMERA, + UNIFORM_BUFFER_INDEX_UNIFORM, + }, misc::WorldHelper, resources::Geometry, Error, @@ -83,7 +86,7 @@ impl<'a> System<'a> for Planets { for (p, _, owned) in (&position, &planet, owned.maybe()).join() { let p_x = p.pos.x; let p_y = p.pos.y; - let s = p.size; + let s = p.shape.circle().unwrap_or(PLANET_SIZE); let c = match owned.and_then(|owned| player.get(owned.owner)) { Some(pv) => &pv.color, diff --git a/space-crush-app/src/render/ships.rs b/space-crush-app/src/render/ships.rs index ce97278..f65c1ba 100644 --- a/space-crush-app/src/render/ships.rs +++ b/space-crush-app/src/render/ships.rs @@ -12,7 +12,9 @@ use space_crush_common::{ use specs::{prelude::*, ReadExpect, ReadStorage, System, World}; use crate::{ - constants::{PLAYER_COLOR_DEFAULT, UNIFORM_BUFFER_INDEX_CAMERA, UNIFORM_BUFFER_INDEX_UNIFORM}, + constants::{ + PLAYER_COLOR_DEFAULT, SHIP_SIZE, UNIFORM_BUFFER_INDEX_CAMERA, UNIFORM_BUFFER_INDEX_UNIFORM, + }, misc::WorldHelper, resources::Geometry, Error, @@ -103,7 +105,7 @@ impl<'a> System<'a> for Ships { let p_y = p.pos.y; let d_x = v.dir.x; let d_y = v.dir.y; - let s = p.size; + let s = SHIP_SIZE; let m = Matrix4f::new( Vector4f::new(-s * d_y, s * d_x, 0.0, 0.0), diff --git a/space-crush-app/src/systems/fleet_info_update.rs b/space-crush-app/src/systems/fleet_info_update.rs index 02f9f2f..6ca7cbd 100644 --- a/space-crush-app/src/systems/fleet_info_update.rs +++ b/space-crush-app/src/systems/fleet_info_update.rs @@ -10,7 +10,7 @@ use specs::{ World, WriteStorage, }; -use crate::{components::FleetInfo, resources::PlayerState, Error}; +use crate::{components::FleetInfo, resources::PlayerState}; pub struct FleetInfoUpdate { modified: HashMap, @@ -26,7 +26,7 @@ struct Modified { } impl FleetInfoUpdate { - pub fn new(world: &mut World) -> Result { + pub fn new(world: &mut World) -> Self { let modified = HashMap::new(); let need_update = BitSet::new(); @@ -50,12 +50,12 @@ impl FleetInfoUpdate { .register_reader() }; - Ok(Self { + Self { modified, need_update, fleet_owned_id, player_owned_id, - }) + } } } diff --git a/space-crush-common/src/components/mod.rs b/space-crush-common/src/components/mod.rs index 51adf08..f140429 100644 --- a/space-crush-common/src/components/mod.rs +++ b/space-crush-common/src/components/mod.rs @@ -10,6 +10,6 @@ pub use asteroid::{Asteroid, Type as AsteroidType}; pub use fleet::{Fleet, Owned as FleetOwned}; pub use planet::Planet; pub use player::{Owned as PlayerOwned, Player}; -pub use position::Position; +pub use position::{Position, Shape}; pub use ship::{Count as ShipCount, Ship, Type as ShipType}; pub use velocity::Velocity; diff --git a/space-crush-common/src/components/position.rs b/space-crush-common/src/components/position.rs index 009ee6b..529371c 100644 --- a/space-crush-common/src/components/position.rs +++ b/space-crush-common/src/components/position.rs @@ -2,12 +2,36 @@ use glc::vector::Vector2f; use serde::{Deserialize, Serialize}; use specs::{Component, VecStorage}; +use crate::misc::FlaggedStorage; + #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct Position { pub pos: Vector2f, - pub size: f32, + pub shape: Shape, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Shape { + Dot, + Circle(f32), } impl Component for Position { - type Storage = VecStorage; + type Storage = FlaggedStorage>; +} + +impl Shape { + pub fn circle(&self) -> Option { + if let Self::Circle(r) = &self { + Some(*r) + } else { + None + } + } +} + +impl Default for Shape { + fn default() -> Self { + Self::Dot + } } diff --git a/space-crush-common/src/dispatcher.rs b/space-crush-common/src/dispatcher.rs index 77b9600..66fe774 100644 --- a/space-crush-common/src/dispatcher.rs +++ b/space-crush-common/src/dispatcher.rs @@ -2,8 +2,8 @@ use specs::{Dispatcher as Inner, DispatcherBuilder, World, WorldExt}; use crate::{ components::Player, - resources::Global, - systems::{Fleets, Movement, Process}, + resources::{Global, Raster}, + systems::{Fleets, Movement, Process, RasterUpdate}, }; pub struct Dispatcher<'a, 'b> { @@ -13,6 +13,7 @@ pub struct Dispatcher<'a, 'b> { impl<'a, 'b> Dispatcher<'a, 'b> { pub fn new(world: &mut World) -> Self { world.insert(Global::default()); + world.insert(Raster::new(250.0)); world.register::(); @@ -20,6 +21,7 @@ impl<'a, 'b> Dispatcher<'a, 'b> { .with(Process::default(), "process", &[]) .with(Movement::default(), "movement", &[]) .with(Fleets::default(), "fleets", &[]) + .with(RasterUpdate::new(world), "raster_update", &[]) .build(); dispatcher.setup(world); diff --git a/space-crush-common/src/resources/mod.rs b/space-crush-common/src/resources/mod.rs index d69e9bd..be42a84 100644 --- a/space-crush-common/src/resources/mod.rs +++ b/space-crush-common/src/resources/mod.rs @@ -1,3 +1,5 @@ mod global; +mod raster; pub use global::Global; +pub use raster::Raster; diff --git a/space-crush-common/src/resources/raster.rs b/space-crush-common/src/resources/raster.rs new file mode 100644 index 0000000..a564a3f --- /dev/null +++ b/space-crush-common/src/resources/raster.rs @@ -0,0 +1,210 @@ +#![allow(dead_code)] + +use std::collections::HashSet; + +use glc::vector::Vector2f; +use specs::world::Index; + +use crate::components::{Position, Shape}; + +pub struct Raster { + size: f32, + quads: OffsetVec>, +} + +#[derive(Default)] +struct OffsetVec +where + T: Default, +{ + origin: usize, + data: Vec, +} + +#[derive(Default)] +pub struct Quad { + entities: HashSet, +} + +impl Raster { + pub fn new(size: f32) -> Self { + Self { + size, + quads: OffsetVec::new(), + } + } + + pub fn size(&self) -> f32 { + self.size + } + + pub fn insert(&mut self, id: Index, position: &Position) -> bool { + self.update_quads(position, |q| { + q.entities.insert(id); + }) + } + + pub fn remove(&mut self, id: &Index, position: &Position) -> bool { + self.update_quads(position, |q| { + q.entities.remove(id); + }) + } + + pub fn quads(&self) -> impl Iterator { + self.quads.data.iter().enumerate().flat_map(move |(x, vy)| { + vy.data.iter().enumerate().map(move |(y, q)| { + ( + x as isize - self.quads.origin as isize, + y as isize - vy.origin as isize, + q, + ) + }) + }) + } + + fn update_quads(&mut self, position: &Position, mut f: F) -> bool + where + F: FnMut(&mut Quad), + { + let p = position.pos; + + match position.shape { + Shape::Dot => { + let x = (p.x / self.size).round() as isize; + let y = (p.y / self.size).round() as isize; + + f(self.quads.get_mut(x).get_mut(y)); + + true + } + Shape::Circle(r) => { + let s = self.size; + let min_x = ((p.x - r) / s).round() as isize - 1; + let max_x = ((p.x + r) / s).round() as isize + 1; + let min_y = ((p.y - r) / s).round() as isize - 1; + let max_y = ((p.y + r) / s).round() as isize + 1; + + let sr = r * r; + let s = self.size / 2.0; + let mut ret = false; + for x in min_x..=max_x { + for y in min_y..=max_y { + let fx = x as f32 * self.size; + let fy = y as f32 * self.size; + let quad = self.quads.get_mut(x).get_mut(y); + + if (Vector2f::new(fx - s, fy - s) - p).length_sqr() < sr + || (Vector2f::new(fx + s, fy - s) - p).length_sqr() < sr + || (Vector2f::new(fx - s, fy + s) - p).length_sqr() < sr + || (Vector2f::new(fx + s, fy + s) - p).length_sqr() < sr + { + f(quad); + ret = true; + } + } + } + + ret + } + } + } + + fn get_quad(&self, x: isize, y: isize) -> Option<&Quad> { + let row = self.quads.get(x)?; + let quad = row.get(y)?; + + Some(quad) + } + + fn get_quad_mut(&mut self, x: isize, y: isize) -> &mut Quad { + self.quads.get_mut(x).get_mut(y) + } +} + +impl OffsetVec +where + T: Default, +{ + pub fn new() -> Self { + Self { + origin: 0, + data: Vec::new(), + } + } + + pub fn get(&self, i: isize) -> Option<&T> { + let low = (0usize - self.origin) as isize; + let high = (self.data.len() - self.origin) as isize - 1; + + if i < low || i > high { + None + } else { + Some(&self.data[(i - low) as usize]) + } + } + + pub fn get_mut(&mut self, i: isize) -> &mut T { + let low = 0isize - self.origin as isize; + let high = (self.data.len() - self.origin) as isize - 1; + + if i > high { + let add = (i - low + 1) as usize; + self.data.resize_with(add, Default::default); + } else if i < low { + let add = (low - i) as usize; + + let mut prefix = Vec::new(); + prefix.resize_with(add, Default::default); + prefix.append(&mut self.data); + + self.data = prefix; + self.origin += add; + } + + &mut self.data[(i + self.origin as isize) as usize] + } +} + +impl Quad { + pub fn is_empty(&self) -> bool { + self.entities.is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn vec_get_mut() { + let mut vec = OffsetVec::::new(); + + assert_eq!(vec.origin, 0); + assert_eq!(vec.data, Vec::::new()); + + *vec.get_mut(0) = 100; + + assert_eq!(vec.origin, 0); + assert_eq!(vec.data, vec![100]); + + *vec.get_mut(1) = 101; + + assert_eq!(vec.origin, 0); + assert_eq!(vec.data, vec![100, 101]); + + *vec.get_mut(-2) = 98; + + assert_eq!(vec.origin, 2); + assert_eq!(vec.data, vec![98, 0, 100, 101]); + + *vec.get_mut(4) = 104; + + assert_eq!(vec.origin, 2); + assert_eq!(vec.data, vec![98, 0, 100, 101, 0, 0, 104]); + + *vec.get_mut(-4) = 96; + + assert_eq!(vec.origin, 4); + assert_eq!(vec.data, vec![96, 0, 98, 0, 100, 101, 0, 0, 104]); + } +} diff --git a/space-crush-common/src/systems/mod.rs b/space-crush-common/src/systems/mod.rs index dc3c6de..1446b93 100644 --- a/space-crush-common/src/systems/mod.rs +++ b/space-crush-common/src/systems/mod.rs @@ -1,7 +1,9 @@ mod fleets; mod movement; mod process; +mod raster_update; pub use fleets::Fleets; pub use movement::Movement; pub use process::Process; +pub use raster_update::RasterUpdate; diff --git a/space-crush-common/src/systems/movement.rs b/space-crush-common/src/systems/movement.rs index dc0d736..864412e 100644 --- a/space-crush-common/src/systems/movement.rs +++ b/space-crush-common/src/systems/movement.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] -use specs::{prelude::*, ParJoin, Read, ReadStorage, System, WriteStorage}; +use specs::{prelude::*, Read, ReadStorage, System, WriteStorage}; use crate::{ components::{Position, Velocity}, @@ -27,10 +27,8 @@ impl<'a> System<'a> for Movement { global, } = data; - (&mut position, &velocity) - .par_join() - .for_each(|(position, velocity)| { - position.pos = position.pos + velocity.dir * velocity.speed * global.delta; - }); + for (position, velocity) in (&mut position, &velocity).join() { + position.pos = position.pos + velocity.dir * velocity.speed * global.delta; + } } } diff --git a/space-crush-common/src/systems/raster_update.rs b/space-crush-common/src/systems/raster_update.rs new file mode 100644 index 0000000..66ed1e8 --- /dev/null +++ b/space-crush-common/src/systems/raster_update.rs @@ -0,0 +1,74 @@ +use shrev::ReaderId; +use specs::{hibitset::BitSetLike, prelude::*, Entities, ReadStorage, System, World, WriteStorage}; + +use crate::{components::Position, misc::ComponentEvent, resources::Raster}; + +pub struct RasterUpdate { + need_update: BitSet, + position_id: ReaderId>, +} + +impl RasterUpdate { + pub fn new(world: &mut World) -> Self { + let need_update = BitSet::new(); + let position_id = unsafe { + WriteStorage::::setup(world); + + let mut position = world.write_component::(); + position + .unprotected_storage_mut() + .channel_mut() + .register_reader() + }; + + Self { + need_update, + position_id, + } + } +} + +#[derive(SystemData)] +pub struct RasterUpdateData<'a> { + raster: WriteExpect<'a, Raster>, + entities: Entities<'a>, + positions: ReadStorage<'a, Position>, +} + +impl<'a> System<'a> for RasterUpdate { + type SystemData = RasterUpdateData<'a>; + + fn run(&mut self, data: Self::SystemData) { + let RasterUpdateData { + mut raster, + entities, + positions, + } = data; + + self.need_update.clear(); + + let events = positions + .unprotected_storage() + .channel() + .read(&mut self.position_id); + for event in events { + match event { + ComponentEvent::Inserted(id) => { + self.need_update.add(*id); + } + ComponentEvent::Modified(id, position) | ComponentEvent::Removed(id, position) => { + self.need_update.add(*id); + raster.remove(id, position); + } + } + } + + if self.need_update.is_empty() { + return; + } + + for (e, position) in (&entities, &positions).join() { + raster.insert(e.id(), position); + } + } +}