diff --git a/src/game.rs b/src/game.rs index 8ffd1f5..54cfb44 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,6 +1,5 @@ use glam::Vec2; -use glium::{Surface, uniform}; -use glium::uniforms::{Sampler, MinifySamplerFilter, MagnifySamplerFilter}; +use glium::Surface; use glium::glutin::{ event::{Event, WindowEvent, DeviceEvent}, event_loop::{EventLoop, ControlFlow}, @@ -19,7 +18,7 @@ mod options; use assets::Assets; use display::init_display; -use shaders::{Programs, chunk::Vertex as ChunkVertex}; +use shaders::Programs; use camera::Camera; use controller::Controls; use world::World; @@ -53,15 +52,15 @@ pub fn run() { let options = GameOptions::default(); log::info!("init game state"); let mut state = State::init(); - state.camera.position = [0., 0., -1.]; + state.camera.position = [0., 260., -1.]; log::info!("game loaded"); //======================= - let vertex1 = ChunkVertex { position: [-0.5, -0.5, 0.], uv: [0., 0.], normal: [0., 1., 0.] }; - let vertex2 = ChunkVertex { position: [ 0.0, 0.5, 0.], uv: [0., 1.], normal: [0., 1., 0.] }; - let vertex3 = ChunkVertex { position: [ 0.5, -0.5, 0.], uv: [1., 1.], normal: [0., 1., 0.] }; - let shape = vec![vertex1, vertex2, vertex3]; - let vertex_buffer = glium::VertexBuffer::new(&display, &shape).unwrap(); + // let vertex1 = ChunkVertex { position: [-0.5, -0.5, 0.], uv: [0., 0.], normal: [0., 1., 0.] }; + // let vertex2 = ChunkVertex { position: [ 0.0, 0.5, 0.], uv: [0., 1.], normal: [0., 1., 0.] }; + // let vertex3 = ChunkVertex { position: [ 0.5, -0.5, 0.], uv: [1., 1.], normal: [0., 1., 0.] }; + // let shape = vec![vertex1, vertex2, vertex3]; + // let vertex_buffer = glium::VertexBuffer::new(&display, &shape).unwrap(); //======================= let mut last_render = Instant::now(); @@ -122,12 +121,11 @@ pub fn run() { let target_dimensions = target.get_dimensions(); let perspective = state.camera.perspective_matrix(target_dimensions); let view = state.camera.view_matrix(); - - //Draw example triangle //Draw chunks state.world.render(&mut target, &programs, &assets, perspective, view); - + + //Draw example triangle // target.draw( // &vertex_buffer, // glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList), diff --git a/src/game/controller.rs b/src/game/controller.rs index 7ba25d5..da4623a 100644 --- a/src/game/controller.rs +++ b/src/game/controller.rs @@ -109,7 +109,7 @@ impl Default for Controls { fn default() -> Self { Self { inputs: Default::default(), - speed: 1., + speed: 10., sensitivity: 2., } } diff --git a/src/game/world.rs b/src/game/world.rs index cf1a6ef..0dcba11 100644 --- a/src/game/world.rs +++ b/src/game/world.rs @@ -1,6 +1,8 @@ use glam::{Vec2, IVec2}; use glium::{ - Display, Frame, Surface, uniform, + Display, Frame, Surface, + DrawParameters, Depth, + DepthTest, uniform, uniforms::{ Sampler, SamplerBehavior, MinifySamplerFilter, MagnifySamplerFilter, @@ -24,6 +26,8 @@ const NEGATIVE_X_NEIGHBOR: usize = 1; const POSITIVE_Z_NEIGHBOR: usize = 2; const NEGATIVE_Z_NEIGHBOR: usize = 3; +const MAX_TASKS: usize = 8; + pub struct World { pub chunks: HashMap, pub thread: WorldThreading, @@ -37,12 +41,14 @@ impl World { self.chunks.get(&(position - IVec2::new(0, 1))), ] } + pub fn new() -> Self { Self { chunks: HashMap::new(), thread: WorldThreading::new(), } } + pub fn render( &self, target: &mut Frame, @@ -56,28 +62,39 @@ impl World { magnify_filter: MagnifySamplerFilter::Nearest, ..Default::default() }; + let draw_parameters = DrawParameters { + depth: Depth { + test: DepthTest::IfLess, + write: true, + ..Default::default() + }, + //backface_culling: glium::draw_parameters::BackfaceCullingMode::CullClockwise, + ..Default::default() + }; for (&position, chunk) in &self.chunks { - if let Some((_, vertex, index)) = &chunk.mesh { + if let Some(mesh) = &chunk.mesh { target.draw( - vertex, - index, + &mesh.vertex_buffer, + &mesh.index_buffer, &programs.chunk, &uniform! { model: [ [1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 1., 0.], + //[0., 0., 0., 1.0_f32] [(position.x * CHUNK_SIZE as i32) as f32, 0., (position.y * CHUNK_SIZE as i32) as f32, 1.0_f32] ], view: view, - persperctive: perspective, + perspective: perspective, tex: Sampler(&assets.textures.block_atlas, sampler) }, - &Default::default() + &draw_parameters ).unwrap(); } } } + pub fn update_loaded_chunks(&mut self, around_position: Vec2, options: &GameOptions, display: &Display) { let render_dist = options.render_distance as i32 + 1; let inside_chunk = (around_position / CHUNK_SIZE as f32).as_ivec2(); @@ -98,43 +115,49 @@ impl World { { //we only need mutable reference here: let chunk = self.chunks.get_mut(&position).unwrap(); - if x == 0 || z == 0 || x == render_dist || z == render_dist { + if x == -render_dist || z == -render_dist || x == render_dist || z == render_dist { chunk.desired = ChunkState::Loaded; } else { chunk.desired = ChunkState::Rendered; } } - //borrow chunk immutably let chunk = self.chunks.get(&position).unwrap(); - if matches!(chunk.state, ChunkState::Nothing) && matches!(chunk.desired, ChunkState::Loaded | ChunkState::Rendered) { - self.thread.queue_load(position); - } else if matches!(chunk.state, ChunkState::Loaded) && matches!(chunk.desired, ChunkState::Rendered) { - fn all_some<'a>(x: [Option<&'a Chunk>; 4]) -> Option<[&'a Chunk; 4]> { - Some([x[0]?, x[1]?, x[2]?, x[3]?]) - } - if let Some(neighbors) = all_some(self.chunk_neighbors(chunk.position)) { - if { - neighbors[0].block_data.is_some() && - neighbors[1].block_data.is_some() && - neighbors[2].block_data.is_some() && - neighbors[3].block_data.is_some() - } { - self.thread.queue_mesh( - position, - chunk.block_data.clone().unwrap(), - [ - neighbors[0].block_data.clone().unwrap(), - neighbors[1].block_data.clone().unwrap(), - neighbors[2].block_data.clone().unwrap(), - neighbors[3].block_data.clone().unwrap(), - ] - ); + if self.thread.task_amount() < MAX_TASKS { + if matches!(chunk.state, ChunkState::Nothing) && matches!(chunk.desired, ChunkState::Loaded | ChunkState::Rendered) { + self.thread.queue_load(position); + self.chunks.get_mut(&position).unwrap().state = ChunkState::Loading; + } else if matches!(chunk.state, ChunkState::Loaded) && matches!(chunk.desired, ChunkState::Rendered) { + let mut state_changed = false; + fn all_some<'a>(x: [Option<&'a Chunk>; 4]) -> Option<[&'a Chunk; 4]> { + Some([x[0]?, x[1]?, x[2]?, x[3]?]) + } + if let Some(neighbors) = all_some(self.chunk_neighbors(chunk.position)) { + if { + neighbors[0].block_data.is_some() && + neighbors[1].block_data.is_some() && + neighbors[2].block_data.is_some() && + neighbors[3].block_data.is_some() + } { + self.thread.queue_mesh( + position, + chunk.block_data.clone().unwrap(), + [ + neighbors[0].block_data.clone().unwrap(), + neighbors[1].block_data.clone().unwrap(), + neighbors[2].block_data.clone().unwrap(), + neighbors[3].block_data.clone().unwrap(), + ] + ); + state_changed = true; + } + } + if state_changed { + self.chunks.get_mut(&position).unwrap().state = ChunkState::Rendering; } } } } } - //Unloads and state downgrades self.chunks.retain(|_, chunk| { match chunk.desired { diff --git a/src/game/world/chunk.rs b/src/game/world/chunk.rs index 7b4f093..e7f54e5 100644 --- a/src/game/world/chunk.rs +++ b/src/game/world/chunk.rs @@ -19,10 +19,16 @@ pub enum ChunkState { pub type ChunkData = Box<[[[Block; CHUNK_SIZE]; CHUNK_HEIGHT]; CHUNK_SIZE]>; +pub struct ChunkMesh { + pub is_dirty: bool, + pub vertex_buffer: VertexBuffer, + pub index_buffer: IndexBuffer, +} + pub struct Chunk { pub position: IVec2, pub block_data: Option, - pub mesh: Option<(bool, VertexBuffer, IndexBuffer)>, + pub mesh: Option, pub state: ChunkState, pub desired: ChunkState, } diff --git a/src/game/world/thread.rs b/src/game/world/thread.rs index 02958a2..3e5b0cc 100644 --- a/src/game/world/thread.rs +++ b/src/game/world/thread.rs @@ -3,7 +3,7 @@ use glium::{Display, VertexBuffer, IndexBuffer, index::PrimitiveType}; use std::{mem, thread::{self, JoinHandle}}; use hashbrown::HashMap; use super::chunk::{Chunk, ChunkData, ChunkState}; -use crate::game::shaders::chunk::Vertex as ChunkVertex; +use crate::game::{shaders::chunk::Vertex as ChunkVertex, world::chunk::ChunkMesh}; mod world_gen; mod mesh_gen; @@ -14,7 +14,7 @@ pub struct WorldThreading { //Options are needed here to take ownership, //None values should never appear here! pub load_tasks: HashMap>>, - pub mesh_tasks: HashMap, Vec)>>>, + pub mesh_tasks: HashMap, Vec)>>>, } impl WorldThreading { pub fn new() -> Self { @@ -84,11 +84,11 @@ impl WorldThreading { let handle = mem::take(handle).unwrap(); let (shape, index) = handle.join().unwrap(); let chunk = chunks.get_mut(position).unwrap(); - chunk.mesh = Some(( - true, - VertexBuffer::immutable(display, &shape).expect("Failed to build VertexBuffer"), - IndexBuffer::immutable(display, PrimitiveType::TrianglesList, &index).expect("Failed to build IndexBuffer") - )); + chunk.mesh = Some(ChunkMesh { + is_dirty: false, + vertex_buffer: VertexBuffer::new(display, &shape).expect("Failed to build VertexBuffer"), + index_buffer: IndexBuffer::new(display, PrimitiveType::TrianglesList, &index).expect("Failed to build IndexBuffer") + }); chunk.state = ChunkState::Rendered; false }); diff --git a/src/game/world/thread/mesh_gen.rs b/src/game/world/thread/mesh_gen.rs index 0fab20c..b1175ae 100644 --- a/src/game/world/thread/mesh_gen.rs +++ b/src/game/world/thread/mesh_gen.rs @@ -1,15 +1,126 @@ -use glam::IVec2; -use crate::game::world::chunk::ChunkData; -use crate::game::shaders::chunk::Vertex as ChunkVertex; +use glam::{IVec2, IVec3, Vec2, Vec3A, vec3a, vec2, ivec3}; +use strum::{EnumIter, IntoEnumIterator}; +use crate::game::{ + world::{ + POSITIVE_X_NEIGHBOR, + NEGATIVE_X_NEIGHBOR, + POSITIVE_Z_NEIGHBOR, + NEGATIVE_Z_NEIGHBOR, + chunk::{ChunkData, CHUNK_SIZE, CHUNK_HEIGHT} + }, + shaders::chunk::Vertex, + blocks::Block +}; -pub fn generate_mesh(position: IVec2, chunk_data: ChunkData, neighbors: [ChunkData; 4]) -> (Vec, Vec) { - let mut vertex = Vec::new(); - let mut index = Vec::new(); - vertex.push(ChunkVertex { position: [-0.5, -0.5, 0.], uv: [0., 0.], normal: [0., 1., 0.] }); - vertex.push(ChunkVertex { position: [ 0.0, 0.5, 0.], uv: [0., 1.], normal: [0., 1., 0.] }); - vertex.push(ChunkVertex { position: [ 0.5, -0.5, 0.], uv: [1., 1.], normal: [0., 1., 0.] }); - index.push(0); - index.push(1); - index.push(2); - (vertex, index) +#[repr(usize)] +#[derive(Clone, Copy, Debug, EnumIter)] +pub enum CubeFace { + Top = 0, + Front = 1, + Left = 2, + Right = 3, + Back = 4, + Bottom = 5, +} +const CUBE_FACE_VERTICES: [[Vec3A; 4]; 6] = [ + [vec3a(0., 1., 0.), vec3a(0., 1., 1.), vec3a(1., 1., 0.), vec3a(1., 1., 1.)], + [vec3a(0., 0., 0.), vec3a(0., 1., 0.), vec3a(1., 0., 0.), vec3a(1., 1., 0.)], + [vec3a(0., 0., 1.), vec3a(0., 1., 1.), vec3a(0., 0., 0.), vec3a(0., 1., 0.)], + [vec3a(1., 0., 0.), vec3a(1., 1., 0.), vec3a(1., 0., 1.), vec3a(1., 1., 1.)], + [vec3a(1., 0., 1.), vec3a(1., 1., 1.), vec3a(0., 0., 1.), vec3a(0., 1., 1.)], + [vec3a(0., 0., 1.), vec3a(0., 0., 0.), vec3a(1., 0., 1.), vec3a(1., 0., 0.)] +]; +pub const CUBE_FACE_NORMALS: [[f32; 3]; 6] = [ + [0., 1., 0.], + [0., 0., -1.], + [-1., 0., 0.], + [1., 0., 0.], + [0., 0., 1.], + [0., -1., 0.] +]; +pub const CUBE_FACE_INDICES: [u32; 6] = [0, 1, 2, 2, 1, 3]; + +#[derive(Default)] +struct MeshBuilder { + vertex_buffer: Vec, + index_buffer: Vec, + idx_counter: u32, +} +impl MeshBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn add_face(&mut self, face: CubeFace, coord: IVec3, uvs: [Vec2; 4]) { + let coord = coord.as_vec3a(); + let face_index = face as usize; + + //Push vertexes + let norm = CUBE_FACE_NORMALS[face_index]; + let vert = CUBE_FACE_VERTICES[face_index]; + self.vertex_buffer.reserve(4); + for i in 0..4 { + self.vertex_buffer.push(Vertex { + position: (coord + vert[i]).to_array(), + normal: norm, + uv: uvs[i].to_array() + }); + } + + //Push indices + self.index_buffer.extend_from_slice(&CUBE_FACE_INDICES.map(|x| x + self.idx_counter)); + self.idx_counter += 4; + } + + pub fn finish(self) -> (Vec, Vec) { + (self.vertex_buffer, self.index_buffer) + } +} + +pub fn generate_mesh(position: IVec2, chunk_data: ChunkData, neighbors: [ChunkData; 4]) -> (Vec, Vec) { + let get_block = |pos: IVec3| -> Block { + if pos.x < 0 { + neighbors[NEGATIVE_X_NEIGHBOR][(CHUNK_SIZE as i32 + pos.x) as usize][pos.y as usize][pos.z as usize] + } else if pos.x >= CHUNK_SIZE as i32 { + neighbors[POSITIVE_X_NEIGHBOR][pos.x as usize - CHUNK_SIZE as usize][pos.y as usize][pos.z as usize] + } else if pos.z < 0 { + neighbors[NEGATIVE_Z_NEIGHBOR][pos.x as usize][pos.y as usize][(CHUNK_SIZE as i32 + pos.z) as usize] + } else if pos.z >= CHUNK_SIZE as i32 { + neighbors[POSITIVE_Z_NEIGHBOR][pos.x as usize][pos.y as usize][pos.z as usize - CHUNK_SIZE as usize] + } else { + chunk_data[pos.x as usize][pos.y as usize][pos.z as usize] + } + }; + + let mut builer = MeshBuilder::new(); + + for x in 0..CHUNK_SIZE { + for y in 0..CHUNK_HEIGHT { + for z in 0..CHUNK_SIZE { + let coord = ivec3(x as i32, y as i32, z as i32); + if get_block(coord).descriptor().render.is_none() { + continue + } + for face in CubeFace::iter() { + let facing = Vec3A::from_array(CUBE_FACE_NORMALS[face as usize]).as_ivec3(); + let facing_coord = coord + facing; + let show = { + (facing_coord.y < 0) || + (facing_coord.y >= CHUNK_HEIGHT as i32) || + get_block(facing_coord).descriptor().render.is_none() + }; + if show { + builer.add_face(face, coord, [ + vec2(0., 0.), + vec2(0., 1.), + vec2(1., 0.), + vec2(1., 1.), + ]); + } + } + } + } + } + + builer.finish() }