use std::mem::size_of; use glc::{ buffer::{Buffer, Usage}, misc::{BindGuard, Bindable}, shader::{Program, Type, Uniform}, texture::Texture, vector::{Vector2f, Vector4f}, vertex_array::{DataType, VertexArray}, }; use space_crush_common::{ components::{Player, PlayerOwned, Position, Ship, ShipType, Velocity}, misc::{ComponentEvent, LogResult, StorageHelper, StorageHelperMut}, }; use specs::{prelude::*, ReadStorage, System, World}; use crate::{ constants::{PLAYER_COLOR_DEFAULT, UNIFORM_BUFFER_INDEX_CAMERA, UNIFORM_BUFFER_INDEX_UNIFORM}, misc::WorldHelper, Error, }; pub struct Ships { program: Program, vertex_array: VertexArray, texture_bomber: Texture, texture_fighter: Texture, texture_transporter: Texture, reader_id: ReaderId>, ship_count: usize, } #[repr(C, packed)] struct VertexData { pos: Vector2f, dir: Vector2f, color: Vector4f, texture: gl::GLint, } impl Ships { pub fn new(world: &World) -> Result { let program = world.load_program(vec![ (Type::Vertex, "resources/shader/ship/vert.glsl"), (Type::Geometry, "resources/shader/ship/geom.glsl"), (Type::Fragment, "resources/shader/ship/frag.glsl"), ])?; program.uniform_block_binding("Camera", UNIFORM_BUFFER_INDEX_CAMERA)?; program.uniform_block_binding("Global", UNIFORM_BUFFER_INDEX_UNIFORM)?; program.bind(); program.uniform("uTexture", Uniform::TextureVec(&[0, 1, 2]))?; program.unbind(); const STRIDE: gl::GLsizei = size_of::() as gl::GLsizei; const OFFSET_POS: gl::GLsizei = 0; const OFFSET_DIR: gl::GLsizei = OFFSET_POS + size_of::() as gl::GLsizei; const OFFSET_CLR: gl::GLsizei = OFFSET_DIR + size_of::() as gl::GLsizei; const OFFSET_TEX: gl::GLsizei = OFFSET_CLR + size_of::() as gl::GLsizei; let vertex_array = VertexArray::builder() .bind_buffer(Buffer::new()?) .vertex_attrib_pointer(0, 2, DataType::Float, false, STRIDE, OFFSET_POS)? .vertex_attrib_pointer(1, 2, DataType::Float, false, STRIDE, OFFSET_DIR)? .vertex_attrib_pointer(2, 4, DataType::Float, false, STRIDE, OFFSET_CLR)? .vertex_attrib_pointer(3, 1, DataType::Int, false, STRIDE, OFFSET_TEX)? .build()?; let texture_bomber = world.load_texture("resources/textures/ship_bomber.png")?; let texture_fighter = world.load_texture("resources/textures/ship_fighter.png")?; let texture_transporter = world.load_texture("resources/textures/ship_transporter.png")?; let reader_id = world .system_data::>() .register_event_reader(); let ship_count = 0; Ok(Self { program, vertex_array, texture_bomber, texture_fighter, texture_transporter, reader_id, ship_count, }) } } #[derive(SystemData)] pub struct ShipsData<'a> { positions: ReadStorage<'a, Position>, velocities: ReadStorage<'a, Velocity>, players: ReadStorage<'a, Player>, owned: ReadStorage<'a, PlayerOwned>, ships: ReadStorage<'a, Ship>, } impl<'a> System<'a> for Ships { type SystemData = ShipsData<'a>; fn run(&mut self, data: Self::SystemData) { let ShipsData { positions, velocities, players, owned, ships, } = data; /* handle events */ let old_count = self.ship_count; let events = ships.channel().read(&mut self.reader_id); for event in events { match event { ComponentEvent::Inserted(_, _) => self.ship_count += 1, ComponentEvent::Removed(_, _) => self.ship_count -= 1, ComponentEvent::Modified(_, _) => { unreachable!("Updates of the ship component should not be tracked!") } } } /* update vertex array */ let buffer = &mut self.vertex_array.buffers_mut()[0]; if old_count != self.ship_count { buffer .buffer_size(Usage::StaticDraw, self.ship_count * size_of::()) .panic("Unable to change buffer size for ship data"); } let data = (&positions, &velocities, &ships, owned.maybe()); let mut buffer = buffer .map_mut::(true) .panic("Unable to map buffer for ship data"); for (i, (position, velocity, ship, owned)) in data.join().enumerate() { let mut d = &mut buffer[i]; d.pos = *position.pos(); d.dir = *velocity.dir(); d.color = match owned.and_then(|owned| players.get(owned.owner())) { Some(pv) => *pv.color(), None => PLAYER_COLOR_DEFAULT, }; d.texture = match ship.type_() { ShipType::Fighter => 0, ShipType::Bomber => 1, ShipType::Transporter => 2, }; } drop(buffer); /* render ships */ let _guard = BindGuard::new(&self.program); let _guard = BindGuard::new(&self.vertex_array); gl::active_texture(gl::TEXTURE2); let _guard = BindGuard::new(&self.texture_transporter); gl::active_texture(gl::TEXTURE1); let _guard = BindGuard::new(&self.texture_bomber); gl::active_texture(gl::TEXTURE0); let _guard = BindGuard::new(&self.texture_fighter); gl::enable(gl::BLEND); gl::blend_func(gl::SRC_ALPHA, gl::ONE); gl::draw_arrays(gl::POINTS, 0, self.ship_count as _); gl::disable(gl::BLEND); } }