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::{Asteroid, AsteroidType, Player, PlayerOwned, Position}, misc::{ComponentEvent, LogResult, StorageHelper, StorageHelperMut}, }; use specs::{prelude::*, ReadStorage, System, World}; use crate::{ constants::{ ASTEROID_SIZE, PLAYER_COLOR_DEFAULT, UNIFORM_BUFFER_INDEX_CAMERA, UNIFORM_BUFFER_INDEX_UNIFORM, }, misc::WorldHelper, Error, }; pub struct Asteroids { program: Program, texture_metal: Texture, texture_crystal: Texture, vertex_array: VertexArray, reader_id: ReaderId>, asteroid_count: usize, } #[repr(C, packed)] struct VertexData { pos: Vector2f, size: gl::GLfloat, color: Vector4f, texture: gl::GLint, } impl Asteroids { pub fn new(world: &mut World) -> Result { WriteStorage::::setup(world); let program = world.load_program(vec![ (Type::Vertex, "resources/shader/asteroid/vert.glsl"), (Type::Geometry, "resources/shader/asteroid/geom.glsl"), (Type::Fragment, "resources/shader/asteroid/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]))?; program.unbind(); let texture_metal = world.load_texture("resources/textures/asteroid_metal.png")?; let texture_crystal = world.load_texture("resources/textures/asteroid_crystal.png")?; const STRIDE: gl::GLsizei = size_of::() as gl::GLsizei; const OFFSET_POS: gl::GLsizei = 0; const OFFSET_SIZ: gl::GLsizei = OFFSET_POS + size_of::() as gl::GLsizei; const OFFSET_CLR: gl::GLsizei = OFFSET_SIZ + 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, 1, DataType::Float, false, STRIDE, OFFSET_SIZ)? .vertex_attrib_pointer(2, 4, DataType::Float, false, STRIDE, OFFSET_CLR)? .vertex_attrib_pointer(3, 1, DataType::Int, false, STRIDE, OFFSET_TEX)? .build()?; let reader_id = world .system_data::>() .register_event_reader(); let asteroid_count = 0; Ok(Self { program, texture_metal, texture_crystal, vertex_array, reader_id, asteroid_count, }) } } #[derive(SystemData)] pub struct AsteroidsData<'a> { positions: ReadStorage<'a, Position>, asteroids: ReadStorage<'a, Asteroid>, players: ReadStorage<'a, Player>, owned: ReadStorage<'a, PlayerOwned>, } impl<'a> System<'a> for Asteroids { type SystemData = AsteroidsData<'a>; fn run(&mut self, data: Self::SystemData) { let AsteroidsData { positions, asteroids, players, owned, } = data; /* handle events */ let mut need_update = false; let events = asteroids.channel().read(&mut self.reader_id); for event in events { match event { ComponentEvent::Inserted(_, _) => { need_update = true; self.asteroid_count += 1; } ComponentEvent::Removed(_, _) => { need_update = true; self.asteroid_count -= 1; } ComponentEvent::Modified(_, _) => { unreachable!("Updates of the asteroid component should not be tracked!") } } } /* update vertex array */ let buffer = &mut self.vertex_array.buffers_mut()[0]; if need_update { buffer .buffer_size( Usage::StaticDraw, self.asteroid_count * size_of::(), ) .panic("Unable to change buffer size for asteroid data"); let data = (&positions, &asteroids, owned.maybe()); let mut buffer = buffer .map_mut::(true) .panic("Unable to map buffer for asteroid data"); for (i, (position, asteroid, owned)) in data.join().enumerate() { let mut d = &mut buffer[i]; d.pos = *position.pos(); d.size = position.shape().circle().unwrap_or(ASTEROID_SIZE); d.color = match owned.and_then(|owned| players.get(owned.owner())) { Some(pv) => *pv.color(), None => PLAYER_COLOR_DEFAULT, }; d.texture = match asteroid.type_() { AsteroidType::Metal => 0, AsteroidType::Crystal => 1, }; } } /* render asteroids */ let _guard = BindGuard::new(&self.program); let _guard = BindGuard::new(&self.vertex_array); gl::active_texture(gl::TEXTURE1); let _guard = BindGuard::new(&self.texture_crystal); gl::active_texture(gl::TEXTURE0); let _guard = BindGuard::new(&self.texture_metal); gl::enable(gl::BLEND); gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); gl::draw_arrays(gl::POINTS, 0, self.asteroid_count as _); gl::disable(gl::BLEND); } }