diff --git a/src/game.rs b/src/game.rs index b265f59..01273c3 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,3 +1,4 @@ +use glam::Vec2; use glium::{Surface, uniform}; use glium::uniforms::{Sampler, MinifySamplerFilter, MagnifySamplerFilter}; use glium::glutin::{ @@ -22,6 +23,7 @@ use shaders::{Programs, chunk::Vertex as ChunkVertex}; use camera::Camera; use controller::Controls; use world::World; +use options::GameOptions; struct State { pub camera: Camera, @@ -47,6 +49,8 @@ pub fn run() { let programs = Programs::compile_all(&display); log::info!("loading assets"); let assets = Assets::load_all_sync(&display); + log::info!("init game options"); + let options = GameOptions::default(); log::info!("init game state"); let mut state = State::init(); state.camera.position = [0., 0., -1.]; @@ -100,18 +104,32 @@ pub fn run() { _ => return } + //Calculate delta time let now = Instant::now(); let dt = (now - last_render).as_secs_f32(); last_render = now; + //Update controls state.controls.calculate(dt).apply_to_camera(&mut state.camera); + //Load new chunks + + state.world.update_loaded_chunks( + Vec2::new(state.camera.position[0], state.camera.position[2]), + &options, + &display + ); + + //Start drawing let mut target = display.draw(); + target.clear_color_and_depth((0.5, 0.5, 1., 1.), 1.); + + //Compute camera let target_dimensions = target.get_dimensions(); let perspective = state.camera.perspective_matrix(target_dimensions); let view = state.camera.view_matrix(); - target.clear_color_and_depth((0.5, 0.5, 1., 1.), 1.); + //Draw example triangle target.draw( &vertex_buffer, glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList), @@ -129,6 +147,8 @@ pub fn run() { }, &Default::default() ).unwrap(); + + //Finish drawing target.finish().unwrap(); }); } diff --git a/src/game/world.rs b/src/game/world.rs index 74c3e3b..d3a8708 100644 --- a/src/game/world.rs +++ b/src/game/world.rs @@ -1,11 +1,13 @@ use glam::{Vec2, IVec2}; +use glium::Display; use std::collections::HashMap; use crate::game::options::GameOptions; mod chunk; mod thread; -use chunk::{Chunk, CHUNK_SIZE}; +use chunk::{Chunk, ChunkState, CHUNK_SIZE}; +use thread::WorldThreading; const POSITIVE_X_NEIGHBOR: usize = 0; const NEGATIVE_X_NEIGHBOR: usize = 1; @@ -13,7 +15,8 @@ const POSITIVE_Z_NEIGHBOR: usize = 2; const NEGATIVE_Z_NEIGHBOR: usize = 3; pub struct World { - pub chunks: HashMap + pub chunks: HashMap, + pub thread: WorldThreading, } impl World { pub fn chunk_neighbors(&self, position: IVec2) -> [Option<&Chunk>; 4] { @@ -26,13 +29,56 @@ impl World { } pub fn new() -> Self { Self { - chunks: HashMap::new() + chunks: HashMap::new(), + thread: WorldThreading::new(), } } - pub fn update_loaded_chunks(&mut self, around_position: Vec2, game_opt: &GameOptions) { - let render_dist = game_opt.render_distance as i32; + 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(); - - todo!() + //Mark all chunks for unload + for (_, chunk) in &mut self.chunks { + chunk.desired = ChunkState::Unload; + } + //Load new/update chunks in range + for x in -render_dist..=render_dist { + for z in -render_dist..=render_dist { + let offset = IVec2::new(x, z); + let position = inside_chunk + offset; + if !self.chunks.contains_key(&position) { + self.chunks.insert(position, Chunk::new(position)); + } + let chunk = self.chunks.get_mut(&position).unwrap(); + if x == 0 || z == 0 || x == render_dist || z == render_dist { + chunk.desired = ChunkState::Loaded; + } else { + chunk.desired = ChunkState::Rendered; + } + } + } + //State up/downgrades are handled here! + self.chunks.retain(|&position, chunk| { + match chunk.desired { + ChunkState::Unload => { + return false + }, + ChunkState::Nothing => { + chunk.block_data = None; + chunk.vertex_buffer = None; + chunk.state = ChunkState::Nothing; + }, + ChunkState::Loaded if matches!(chunk.state, ChunkState::Nothing) => { + self.thread.queue_load(position); + }, + ChunkState::Loaded if matches!(chunk.state, ChunkState::Rendered) => { + chunk.vertex_buffer = None; + chunk.state = ChunkState::Loaded; + }, + _ => () + } + true + }); + //Apply changes from threads + self.thread.apply_tasks(&mut self.chunks, display); } } diff --git a/src/game/world/chunk.rs b/src/game/world/chunk.rs index 1830b61..520c2cf 100644 --- a/src/game/world/chunk.rs +++ b/src/game/world/chunk.rs @@ -9,6 +9,7 @@ pub const CHUNK_SIZE: usize = 16; pub const CHUNK_HEIGHT: usize = 255; pub enum ChunkState { + Unload, Nothing, Loaded, Rendered, @@ -20,7 +21,7 @@ pub type ChunkMesh = VertexBuffer; pub struct Chunk { pub position: IVec2, pub block_data: Option, - pub vertex_buffer: Option, + pub vertex_buffer: Option<(bool, ChunkMesh)>, pub state: ChunkState, pub desired: ChunkState, } diff --git a/src/game/world/thread.rs b/src/game/world/thread.rs index 0b01299..38e59d3 100644 --- a/src/game/world/thread.rs +++ b/src/game/world/thread.rs @@ -1,22 +1,28 @@ use glam::IVec2; +use glium::{Display, VertexBuffer}; use std::{ thread::{self, JoinHandle}, collections::HashMap, mem }; use super::chunk::{Chunk, ChunkData, ChunkState}; +use crate::game::shaders::chunk::Vertex as ChunkVertex; mod world_gen; mod mesh_gen; -struct WorldThreading { +#[derive(Default)] +pub struct WorldThreading { //drain_filter is not stable yet so //Options are needed here to take ownership, //None values should never appear here! pub load_tasks: HashMap>>, - pub mesh_tasks: HashMap>>, + pub mesh_tasks: HashMap>>>, } impl WorldThreading { + pub fn new() -> Self { + Self::default() + } pub fn is_done(&self) -> bool { self.load_tasks.is_empty() && self.mesh_tasks.is_empty() @@ -32,7 +38,24 @@ impl WorldThreading { log::warn!("load: discarded {}, reason: new task started", position); } } - pub fn apply_tasks(&mut self, chunks: &mut HashMap) { + pub fn queue_mesh(&mut self, chunk: &Chunk, neighbors: [&Chunk; 4]) { + let position = chunk.position; + let data = chunk.block_data.expect("Chunk has no mesh!"); + let neighbor_data = [ + neighbors[0].block_data.expect("Chunk has no mesh!"), + neighbors[1].block_data.expect("Chunk has no mesh!"), + neighbors[2].block_data.expect("Chunk has no mesh!"), + neighbors[3].block_data.expect("Chunk has no mesh!"), + ]; + let handle = thread::spawn(move || { + mesh_gen::generate_mesh(position, data, neighbor_data) + }); + if self.mesh_tasks.insert(chunk.position, Some(handle)).is_some() { + log::warn!("mesh: discarded {}, reason: new task started", chunk.position); + } + } + pub fn apply_tasks(&mut self, chunks: &mut HashMap, display: &Display) { + //LOAD TASKS self.load_tasks.retain(|position, handle| { if !chunks.contains_key(position) { log::warn!("load: discarded {}, reason: chunk no longer exists", position); @@ -54,5 +77,31 @@ impl WorldThreading { chunk.state = ChunkState::Loaded; false }); + //MESH TASKS + self.mesh_tasks.retain(|position, handle| { + if !chunks.contains_key(position) { + log::warn!("mesh: discarded {}, reason: chunk no longer exists", position); + return false + } + if !matches!(chunks.get(position).unwrap().desired, ChunkState::Rendered) { + log::warn!("mesh: discarded {}, reason: state undesired", position); + return false + } + if !handle.as_ref().expect("Something went terribly wrong").is_finished() { + //task not finished yet, keep it and wait + return true + } + log::info!("mesh: done {}", position); + let handle = mem::take(handle).unwrap(); + let data = handle.join().unwrap(); + let chunk = chunks.get_mut(position).unwrap(); + chunk.vertex_buffer = Some(( + true, + VertexBuffer::immutable(display, &data).expect("Failed to build VertexBuffer") + )); + chunk.state = ChunkState::Rendered; + false + }); + } } diff --git a/src/game/world/thread/mesh_gen.rs b/src/game/world/thread/mesh_gen.rs index 8f2d22b..70fdd06 100644 --- a/src/game/world/thread/mesh_gen.rs +++ b/src/game/world/thread/mesh_gen.rs @@ -1,5 +1,7 @@ -use crate::game::world::chunk::Chunk; +use glam::IVec2; +use crate::game::world::chunk::{Chunk, ChunkData}; +use crate::game::shaders::chunk::Vertex as ChunkVertex; -pub fn generate_mesh(chunk: &Chunk, neighbors: [&Chunk; 4]) { - +pub fn generate_mesh(position: IVec2, chunk_data: ChunkData, neighbors: [ChunkData; 4]) -> Vec { + vec![] }