This commit is contained in:
griffi-gh 2023-01-16 21:59:29 +01:00
parent 2bc0231f22
commit 07582f968d
5 changed files with 133 additions and 15 deletions

View file

@ -1,3 +1,4 @@
use glam::Vec2;
use glium::{Surface, uniform}; use glium::{Surface, uniform};
use glium::uniforms::{Sampler, MinifySamplerFilter, MagnifySamplerFilter}; use glium::uniforms::{Sampler, MinifySamplerFilter, MagnifySamplerFilter};
use glium::glutin::{ use glium::glutin::{
@ -22,6 +23,7 @@ use shaders::{Programs, chunk::Vertex as ChunkVertex};
use camera::Camera; use camera::Camera;
use controller::Controls; use controller::Controls;
use world::World; use world::World;
use options::GameOptions;
struct State { struct State {
pub camera: Camera, pub camera: Camera,
@ -47,6 +49,8 @@ pub fn run() {
let programs = Programs::compile_all(&display); let programs = Programs::compile_all(&display);
log::info!("loading assets"); log::info!("loading assets");
let assets = Assets::load_all_sync(&display); let assets = Assets::load_all_sync(&display);
log::info!("init game options");
let options = GameOptions::default();
log::info!("init game state"); log::info!("init game state");
let mut state = State::init(); let mut state = State::init();
state.camera.position = [0., 0., -1.]; state.camera.position = [0., 0., -1.];
@ -100,18 +104,32 @@ pub fn run() {
_ => return _ => return
} }
//Calculate delta time
let now = Instant::now(); let now = Instant::now();
let dt = (now - last_render).as_secs_f32(); let dt = (now - last_render).as_secs_f32();
last_render = now; last_render = now;
//Update controls
state.controls.calculate(dt).apply_to_camera(&mut state.camera); 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(); 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 target_dimensions = target.get_dimensions();
let perspective = state.camera.perspective_matrix(target_dimensions); let perspective = state.camera.perspective_matrix(target_dimensions);
let view = state.camera.view_matrix(); let view = state.camera.view_matrix();
target.clear_color_and_depth((0.5, 0.5, 1., 1.), 1.);
//Draw example triangle
target.draw( target.draw(
&vertex_buffer, &vertex_buffer,
glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList), glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList),
@ -129,6 +147,8 @@ pub fn run() {
}, },
&Default::default() &Default::default()
).unwrap(); ).unwrap();
//Finish drawing
target.finish().unwrap(); target.finish().unwrap();
}); });
} }

View file

@ -1,11 +1,13 @@
use glam::{Vec2, IVec2}; use glam::{Vec2, IVec2};
use glium::Display;
use std::collections::HashMap; use std::collections::HashMap;
use crate::game::options::GameOptions; use crate::game::options::GameOptions;
mod chunk; mod chunk;
mod thread; mod thread;
use chunk::{Chunk, CHUNK_SIZE}; use chunk::{Chunk, ChunkState, CHUNK_SIZE};
use thread::WorldThreading;
const POSITIVE_X_NEIGHBOR: usize = 0; const POSITIVE_X_NEIGHBOR: usize = 0;
const NEGATIVE_X_NEIGHBOR: usize = 1; const NEGATIVE_X_NEIGHBOR: usize = 1;
@ -13,7 +15,8 @@ const POSITIVE_Z_NEIGHBOR: usize = 2;
const NEGATIVE_Z_NEIGHBOR: usize = 3; const NEGATIVE_Z_NEIGHBOR: usize = 3;
pub struct World { pub struct World {
pub chunks: HashMap<IVec2, Chunk> pub chunks: HashMap<IVec2, Chunk>,
pub thread: WorldThreading,
} }
impl World { impl World {
pub fn chunk_neighbors(&self, position: IVec2) -> [Option<&Chunk>; 4] { pub fn chunk_neighbors(&self, position: IVec2) -> [Option<&Chunk>; 4] {
@ -26,13 +29,56 @@ impl World {
} }
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
chunks: HashMap::new() chunks: HashMap::new(),
thread: WorldThreading::new(),
} }
} }
pub fn update_loaded_chunks(&mut self, around_position: Vec2, game_opt: &GameOptions) { pub fn update_loaded_chunks(&mut self, around_position: Vec2, options: &GameOptions, display: &Display) {
let render_dist = game_opt.render_distance as i32; let render_dist = options.render_distance as i32 + 1;
let inside_chunk = (around_position / CHUNK_SIZE as f32).as_ivec2(); let inside_chunk = (around_position / CHUNK_SIZE as f32).as_ivec2();
//Mark all chunks for unload
todo!() 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);
} }
} }

View file

@ -9,6 +9,7 @@ pub const CHUNK_SIZE: usize = 16;
pub const CHUNK_HEIGHT: usize = 255; pub const CHUNK_HEIGHT: usize = 255;
pub enum ChunkState { pub enum ChunkState {
Unload,
Nothing, Nothing,
Loaded, Loaded,
Rendered, Rendered,
@ -20,7 +21,7 @@ pub type ChunkMesh = VertexBuffer<ChunkVertex>;
pub struct Chunk { pub struct Chunk {
pub position: IVec2, pub position: IVec2,
pub block_data: Option<ChunkData>, pub block_data: Option<ChunkData>,
pub vertex_buffer: Option<ChunkMesh>, pub vertex_buffer: Option<(bool, ChunkMesh)>,
pub state: ChunkState, pub state: ChunkState,
pub desired: ChunkState, pub desired: ChunkState,
} }

View file

@ -1,22 +1,28 @@
use glam::IVec2; use glam::IVec2;
use glium::{Display, VertexBuffer};
use std::{ use std::{
thread::{self, JoinHandle}, thread::{self, JoinHandle},
collections::HashMap, collections::HashMap,
mem mem
}; };
use super::chunk::{Chunk, ChunkData, ChunkState}; use super::chunk::{Chunk, ChunkData, ChunkState};
use crate::game::shaders::chunk::Vertex as ChunkVertex;
mod world_gen; mod world_gen;
mod mesh_gen; mod mesh_gen;
struct WorldThreading { #[derive(Default)]
pub struct WorldThreading {
//drain_filter is not stable yet so //drain_filter is not stable yet so
//Options are needed here to take ownership, //Options are needed here to take ownership,
//None values should never appear here! //None values should never appear here!
pub load_tasks: HashMap<IVec2, Option<JoinHandle<ChunkData>>>, pub load_tasks: HashMap<IVec2, Option<JoinHandle<ChunkData>>>,
pub mesh_tasks: HashMap<IVec2, Option<JoinHandle<ChunkData>>>, pub mesh_tasks: HashMap<IVec2, Option<JoinHandle<Vec<ChunkVertex>>>>,
} }
impl WorldThreading { impl WorldThreading {
pub fn new() -> Self {
Self::default()
}
pub fn is_done(&self) -> bool { pub fn is_done(&self) -> bool {
self.load_tasks.is_empty() && self.load_tasks.is_empty() &&
self.mesh_tasks.is_empty() self.mesh_tasks.is_empty()
@ -32,7 +38,24 @@ impl WorldThreading {
log::warn!("load: discarded {}, reason: new task started", position); log::warn!("load: discarded {}, reason: new task started", position);
} }
} }
pub fn apply_tasks(&mut self, chunks: &mut HashMap<IVec2, Chunk>) { 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<IVec2, Chunk>, display: &Display) {
//LOAD TASKS
self.load_tasks.retain(|position, handle| { self.load_tasks.retain(|position, handle| {
if !chunks.contains_key(position) { if !chunks.contains_key(position) {
log::warn!("load: discarded {}, reason: chunk no longer exists", position); log::warn!("load: discarded {}, reason: chunk no longer exists", position);
@ -54,5 +77,31 @@ impl WorldThreading {
chunk.state = ChunkState::Loaded; chunk.state = ChunkState::Loaded;
false 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
});
} }
} }

View file

@ -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<ChunkVertex> {
vec![]
} }