| @@ -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); | |||
| } | |||
| @@ -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; | |||
| @@ -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<Vector2f> { | |||
| 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), | |||
| ] | |||
| } | |||
| @@ -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)?) | |||
| @@ -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::<<PersistWorld as Persistence>::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::<<PersistWorld as Persistence>::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::<<PersistWorld as Persistence>::Marker>() | |||
| @@ -139,7 +139,7 @@ fn create_world(world: &mut World, player_id: Entity) { | |||
| 500.0 * random::<f32>() - 250.0, | |||
| 500.0 * random::<f32>() - 250.0, | |||
| ), | |||
| size: 15.0, | |||
| shape: Shape::Dot, | |||
| }) | |||
| .with(Velocity { | |||
| dir: Vector2f::new(random::<f32>() - 0.5, random::<f32>() - 0.5).normalize(), | |||
| @@ -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), | |||
| @@ -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, | |||
| @@ -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), | |||
| @@ -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<Index, Modified>, | |||
| @@ -26,7 +26,7 @@ struct Modified { | |||
| } | |||
| impl FleetInfoUpdate { | |||
| pub fn new(world: &mut World) -> Result<Self, Error> { | |||
| 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, | |||
| }) | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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<Self>; | |||
| type Storage = FlaggedStorage<Self, VecStorage<Self>>; | |||
| } | |||
| impl Shape { | |||
| pub fn circle(&self) -> Option<f32> { | |||
| if let Self::Circle(r) = &self { | |||
| Some(*r) | |||
| } else { | |||
| None | |||
| } | |||
| } | |||
| } | |||
| impl Default for Shape { | |||
| fn default() -> Self { | |||
| Self::Dot | |||
| } | |||
| } | |||
| @@ -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::<Player>(); | |||
| @@ -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); | |||
| @@ -1,3 +1,5 @@ | |||
| mod global; | |||
| mod raster; | |||
| pub use global::Global; | |||
| pub use raster::Raster; | |||
| @@ -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<OffsetVec<Quad>>, | |||
| } | |||
| #[derive(Default)] | |||
| struct OffsetVec<T> | |||
| where | |||
| T: Default, | |||
| { | |||
| origin: usize, | |||
| data: Vec<T>, | |||
| } | |||
| #[derive(Default)] | |||
| pub struct Quad { | |||
| entities: HashSet<Index>, | |||
| } | |||
| 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<Item = (isize, isize, &Quad)> { | |||
| 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<F>(&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<T> OffsetVec<T> | |||
| 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::<usize>::new(); | |||
| assert_eq!(vec.origin, 0); | |||
| assert_eq!(vec.data, Vec::<usize>::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]); | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<ComponentEvent<Position>>, | |||
| } | |||
| impl RasterUpdate { | |||
| pub fn new(world: &mut World) -> Self { | |||
| let need_update = BitSet::new(); | |||
| let position_id = unsafe { | |||
| WriteStorage::<Position>::setup(world); | |||
| let mut position = world.write_component::<Position>(); | |||
| 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); | |||
| } | |||
| } | |||
| } | |||