@@ -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); | |||
} | |||
} | |||
} |