From 1b91fa94c026a46d74c07452895bc785717c4c82 Mon Sep 17 00:00:00 2001 From: Vijfhoek Date: Fri, 4 Jun 2021 01:45:51 +0200 Subject: [PATCH 1/3] Big project structure reorder --- src/main.rs | 3 +- src/renderable.rs | 10 ++++ src/state/mod.rs | 3 +- src/state/world_state.rs | 5 +- src/world/block.rs | 41 ++++++++++++++++ src/{ => world}/chunk.rs | 102 ++++++++++----------------------------- src/world/face_flags.rs | 10 ++++ src/world/mod.rs | 5 ++ src/{ => world}/quad.rs | 5 +- src/{ => world}/world.rs | 66 +++++++++++++------------ 10 files changed, 135 insertions(+), 115 deletions(-) create mode 100644 src/renderable.rs create mode 100644 src/world/block.rs rename src/{ => world}/chunk.rs (87%) create mode 100644 src/world/face_flags.rs create mode 100644 src/world/mod.rs rename src/{ => world}/quad.rs (98%) rename src/{ => world}/world.rs (97%) diff --git a/src/main.rs b/src/main.rs index bdf4459..2a95ef7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,9 @@ mod aabb; mod camera; -mod chunk; mod geometry; mod npc; -mod quad; mod render_context; +mod renderable; mod state; mod text_renderer; mod texture; diff --git a/src/renderable.rs b/src/renderable.rs new file mode 100644 index 0000000..addae07 --- /dev/null +++ b/src/renderable.rs @@ -0,0 +1,10 @@ +use std::time::Duration; + +use wgpu::RenderPass; + +use crate::{camera::Camera, render_context::RenderContext, view::View}; + +pub trait Renderable { + fn update(&mut self, render_context: &RenderContext, dt: Duration, camera: &Camera); + fn render<'a>(&'a self, render_pass: &mut RenderPass<'a>, view: &View) -> usize; +} diff --git a/src/state/mod.rs b/src/state/mod.rs index acbc1c9..18338db 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -20,8 +20,8 @@ pub const PRIMITIVE_STATE: wgpu::PrimitiveState = wgpu::PrimitiveState { strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: None, - polygon_mode: wgpu::PolygonMode::Fill, clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, conservative: false, }; @@ -47,6 +47,7 @@ impl State { }) .await .unwrap(); + println!("Using {:?}", adapter.get_info().backend); let (render_device, queue) = adapter .request_device( diff --git a/src/state/world_state.rs b/src/state/world_state.rs index 6ef7905..38f1232 100644 --- a/src/state/world_state.rs +++ b/src/state/world_state.rs @@ -13,11 +13,12 @@ use winit::{ use crate::{ camera::{Camera, Projection}, render_context::RenderContext, + renderable::Renderable, texture::{Texture, TextureManager}, time::Time, vertex::{BlockVertex, Vertex}, view::View, - world::World, + world::world::World, }; pub struct WorldState { @@ -428,7 +429,7 @@ impl WorldState { pub fn update(&mut self, dt: Duration, render_context: &RenderContext) { self.update_position(dt); - self.world.update(dt, render_context, &self.camera); + self.world.update(render_context, dt, &self.camera); self.view .update_view_projection(&self.camera, &self.projection); diff --git a/src/world/block.rs b/src/world/block.rs new file mode 100644 index 0000000..ccd9aaf --- /dev/null +++ b/src/world/block.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum BlockType { + Cobblestone = 1, + Dirt = 2, + Stone = 3, + Grass = 4, + Bedrock = 5, + Sand = 6, + Gravel = 7, + Water = 8, +} + +impl BlockType { + #[rustfmt::skip] + pub const fn texture_indices(self) -> (usize, usize, usize, usize, usize, usize) { + match self { + BlockType::Cobblestone => ( 0, 0, 0, 0, 0, 0), + BlockType::Dirt => ( 1, 1, 1, 1, 1, 1), + BlockType::Stone => ( 2, 2, 2, 2, 2, 2), + BlockType::Grass => ( 4, 4, 4, 4, 2, 3), + BlockType::Bedrock => ( 5, 5, 5, 5, 5, 5), + BlockType::Sand => ( 6, 6, 6, 6, 6, 6), + BlockType::Gravel => ( 7, 7, 7, 7, 7, 7), + BlockType::Water => ( 8, 8, 8, 8, 8, 8), // up to 71 + } + } + + pub const fn is_transparent(self) -> bool { + matches!(self, BlockType::Water) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Block { + pub block_type: BlockType, +} diff --git a/src/chunk.rs b/src/world/chunk.rs similarity index 87% rename from src/chunk.rs rename to src/world/chunk.rs index f3c6f5a..deeea0a 100644 --- a/src/chunk.rs +++ b/src/world/chunk.rs @@ -1,81 +1,31 @@ -use std::{collections::VecDeque, usize}; +use std::collections::VecDeque; use crate::{ aabb::Aabb, geometry::{Geometry, GeometryBuffers}, - quad::Quad, vertex::BlockVertex, view::View, + world::{ + block::{Block, BlockType}, + face_flags::*, + quad::Quad, + }, }; use ahash::{AHashMap, AHashSet}; use cgmath::{Point3, Vector3}; use noise::utils::{NoiseMapBuilder, PlaneMapBuilder}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; - use serde::{ de::{SeqAccess, Visitor}, - ser::{SerializeSeq, Serializer}, - Deserialize, Serialize, + ser::SerializeSeq, + Deserialize, Serialize, Serializer, }; -use serde_repr::{Deserialize_repr, Serialize_repr}; - -#[allow(dead_code)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum BlockType { - Cobblestone = 1, - Dirt = 2, - Stone = 3, - Grass = 4, - Bedrock = 5, - Sand = 6, - Gravel = 7, - Water = 8, -} - -impl BlockType { - #[rustfmt::skip] - pub const fn texture_indices(self) -> (usize, usize, usize, usize, usize, usize) { - match self { - BlockType::Cobblestone => ( 0, 0, 0, 0, 0, 0), - BlockType::Dirt => ( 1, 1, 1, 1, 1, 1), - BlockType::Stone => ( 2, 2, 2, 2, 2, 2), - BlockType::Grass => ( 4, 4, 4, 4, 2, 3), - BlockType::Bedrock => ( 5, 5, 5, 5, 5, 5), - BlockType::Sand => ( 6, 6, 6, 6, 6, 6), - BlockType::Gravel => ( 7, 7, 7, 7, 7, 7), - BlockType::Water => ( 8, 8, 8, 8, 8, 8), // up to 71 - } - } - - pub const fn is_transparent(self) -> bool { - matches!(self, BlockType::Water) - } -} - -pub type FaceFlags = usize; -pub const FACE_NONE: FaceFlags = 0; -pub const FACE_LEFT: FaceFlags = 1; -pub const FACE_RIGHT: FaceFlags = 2; -pub const FACE_BOTTOM: FaceFlags = 4; -pub const FACE_TOP: FaceFlags = 8; -pub const FACE_BACK: FaceFlags = 16; -pub const FACE_FRONT: FaceFlags = 32; -pub const FACE_ALL: FaceFlags = - FACE_LEFT | FACE_RIGHT | FACE_BOTTOM | FACE_TOP | FACE_BACK | FACE_FRONT; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Block { - pub block_type: BlockType, -} pub const CHUNK_SIZE: usize = 32; pub const CHUNK_ISIZE: isize = CHUNK_SIZE as isize; -type ChunkBlocks = [[[Option; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]; - pub struct Chunk { - pub blocks: ChunkBlocks, + pub blocks: [[[Option; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE], pub buffers: Option>, } @@ -88,23 +38,6 @@ impl Default for Chunk { } } -impl Serialize for Chunk { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(Some(CHUNK_SIZE.pow(3)))?; - for layer in self.blocks.iter() { - for row in layer { - for block in row { - seq.serialize_element(block)?; - } - } - } - seq.end() - } -} - struct ChunkVisitor; impl<'de> Visitor<'de> for ChunkVisitor { @@ -131,6 +64,23 @@ impl<'de> Visitor<'de> for ChunkVisitor { } } +impl Serialize for Chunk { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(CHUNK_SIZE.pow(3)))?; + for layer in self.blocks.iter() { + for row in layer { + for block in row { + seq.serialize_element(block)?; + } + } + } + seq.end() + } +} + impl<'de> Deserialize<'de> for Chunk { fn deserialize(deserializer: D) -> Result where diff --git a/src/world/face_flags.rs b/src/world/face_flags.rs new file mode 100644 index 0000000..82a3845 --- /dev/null +++ b/src/world/face_flags.rs @@ -0,0 +1,10 @@ +pub type FaceFlags = usize; +pub const FACE_NONE: FaceFlags = 0; +pub const FACE_LEFT: FaceFlags = 1; +pub const FACE_RIGHT: FaceFlags = 2; +pub const FACE_BOTTOM: FaceFlags = 4; +pub const FACE_TOP: FaceFlags = 8; +pub const FACE_BACK: FaceFlags = 16; +pub const FACE_FRONT: FaceFlags = 32; +pub const FACE_ALL: FaceFlags = + FACE_LEFT | FACE_RIGHT | FACE_BOTTOM | FACE_TOP | FACE_BACK | FACE_FRONT; diff --git a/src/world/mod.rs b/src/world/mod.rs new file mode 100644 index 0000000..791948d --- /dev/null +++ b/src/world/mod.rs @@ -0,0 +1,5 @@ +pub mod block; +pub mod chunk; +pub mod face_flags; +pub mod quad; +pub mod world; diff --git a/src/quad.rs b/src/world/quad.rs similarity index 98% rename from src/quad.rs rename to src/world/quad.rs index 8803e5c..88c719c 100644 --- a/src/quad.rs +++ b/src/world/quad.rs @@ -1,12 +1,9 @@ use cgmath::{Point3, Vector3, Zero}; use crate::{ - chunk::{ - BlockType, FaceFlags, FACE_ALL, FACE_BACK, FACE_BOTTOM, FACE_FRONT, FACE_LEFT, FACE_RIGHT, - FACE_TOP, - }, geometry::Geometry, vertex::BlockVertex, + world::{block::BlockType, face_flags::*}, }; #[derive(Debug)] diff --git a/src/world.rs b/src/world/world.rs similarity index 97% rename from src/world.rs rename to src/world/world.rs index 6874c54..f1695a4 100644 --- a/src/world.rs +++ b/src/world/world.rs @@ -2,11 +2,15 @@ use std::{collections::VecDeque, time::Duration}; use crate::{ camera::Camera, - chunk::{Block, BlockType, Chunk, CHUNK_ISIZE}, geometry::GeometryBuffers, npc::Npc, render_context::RenderContext, + renderable::Renderable, view::View, + world::{ + block::{Block, BlockType}, + chunk::{Chunk, CHUNK_ISIZE}, + }, }; use ahash::AHashMap; use cgmath::{EuclideanSpace, InnerSpace, Point3, Vector3}; @@ -31,34 +35,8 @@ pub const WORLD_HEIGHT: isize = 16 * 16 / CHUNK_ISIZE; const DEBUG_IO: bool = false; -impl World { - pub fn new() -> Self { - let chunks = AHashMap::new(); - let npc = Npc::load(); - - let chunk_database = sled::Config::new() - .path("chunks") - .mode(sled::Mode::HighThroughput) - .use_compression(true) - .open() - .unwrap(); - - Self { - chunks, - npc, - - chunk_database, - chunk_load_queue: VecDeque::new(), - chunk_save_queue: VecDeque::new(), - chunk_generate_queue: VecDeque::new(), - - highlighted: None, - - unload_timer: Duration::new(0, 0), - } - } - - pub fn update(&mut self, dt: Duration, render_context: &RenderContext, camera: &Camera) { +impl Renderable for World { + fn update(&mut self, render_context: &RenderContext, dt: Duration, camera: &Camera) { if let Some(position) = self.chunk_load_queue.pop_front() { let chunk = self.chunks.entry(position).or_default(); match chunk.load(position, &self.chunk_database) { @@ -144,7 +122,7 @@ impl World { } } - pub fn render<'a>(&'a self, render_pass: &mut RenderPass<'a>, view: &View) -> usize { + fn render<'a>(&'a self, render_pass: &mut RenderPass<'a>, view: &View) -> usize { let mut triangle_count = 0; for (position, chunk) in &self.chunks { @@ -166,6 +144,34 @@ impl World { triangle_count } +} + +impl World { + pub fn new() -> Self { + let chunks = AHashMap::new(); + let npc = Npc::load(); + + let chunk_database = sled::Config::new() + .path("chunks") + .mode(sled::Mode::HighThroughput) + .use_compression(true) + .open() + .unwrap(); + + Self { + chunks, + npc, + + chunk_database, + chunk_load_queue: VecDeque::new(), + chunk_save_queue: VecDeque::new(), + chunk_generate_queue: VecDeque::new(), + + highlighted: None, + + unload_timer: Duration::new(0, 0), + } + } pub fn enqueue_chunk_save(&mut self, position: Point3, unload: bool) { if let Some((_, unload_)) = self From 67f275b2fc9cd40b0735eeb3f4afe3ec5fbec2c9 Mon Sep 17 00:00:00 2001 From: Vijfhoek Date: Fri, 4 Jun 2021 02:23:26 +0200 Subject: [PATCH 2/3] Move update_chunk_geometry into Chunk --- src/state/world_state.rs | 2 +- src/world/chunk.rs | 50 +++-- src/world/mod.rs | 356 ++++++++++++++++++++++++++++++++++- src/world/world.rs | 388 --------------------------------------- 4 files changed, 395 insertions(+), 401 deletions(-) delete mode 100644 src/world/world.rs diff --git a/src/state/world_state.rs b/src/state/world_state.rs index 38f1232..b21eda4 100644 --- a/src/state/world_state.rs +++ b/src/state/world_state.rs @@ -18,7 +18,7 @@ use crate::{ time::Time, vertex::{BlockVertex, Vertex}, view::View, - world::world::World, + world::World, }; pub struct WorldState { diff --git a/src/world/chunk.rs b/src/world/chunk.rs index deeea0a..02fbc81 100644 --- a/src/world/chunk.rs +++ b/src/world/chunk.rs @@ -3,6 +3,7 @@ use std::collections::VecDeque; use crate::{ aabb::Aabb, geometry::{Geometry, GeometryBuffers}, + render_context::RenderContext, vertex::BlockVertex, view::View, world::{ @@ -20,6 +21,7 @@ use serde::{ ser::SerializeSeq, Deserialize, Serialize, Serializer, }; +use wgpu::BufferUsage; pub const CHUNK_SIZE: usize = 32; pub const CHUNK_ISIZE: isize = CHUNK_SIZE as isize; @@ -168,6 +170,22 @@ impl Chunk { } } + pub fn block_coords_to_local( + chunk_coords: Point3, + block_coords: Point3, + ) -> Option> { + let chunk_position = chunk_coords * CHUNK_ISIZE; + let position = block_coords - chunk_position; + if (0..CHUNK_ISIZE).contains(&position.x) + && (0..CHUNK_ISIZE).contains(&position.y) + && (0..CHUNK_ISIZE).contains(&position.z) + { + Some(position.cast().unwrap()) + } else { + None + } + } + #[rustfmt::skip] fn check_visible_faces(&self, x: usize, y: usize, z: usize) -> FaceFlags { let mut visible_faces = FACE_NONE; @@ -244,7 +262,7 @@ impl Chunk { offset: Point3, culled: AHashMap<(usize, usize), (BlockType, FaceFlags)>, queue: &mut VecDeque<(usize, usize)>, - highlighted: Option<&(Point3, Vector3)>, + highlighted: Option<(Vector3, Vector3)>, ) -> Vec { let mut quads: Vec = Vec::new(); let mut visited = AHashSet::new(); @@ -260,7 +278,7 @@ impl Chunk { if let Some(&(block_type, visible_faces)) = &culled.get(&(x, z)) { let mut quad_faces = visible_faces; - if hl == Some(Point3::new(x, y, z)) { + if hl == Some(Vector3::new(x, y, z)) { let mut quad = Quad::new(position, 1, 1); quad.highlighted_normal = highlighted.unwrap().1; quad.visible_faces = quad_faces; @@ -282,7 +300,7 @@ impl Chunk { for x_ in x..CHUNK_SIZE { xmax = x_ + 1; - if visited.contains(&(xmax, z)) || hl == Some(Point3::new(xmax, y, z)) { + if visited.contains(&(xmax, z)) || hl == Some(Vector3::new(xmax, y, z)) { break; } @@ -304,7 +322,7 @@ impl Chunk { zmax = z_ + 1; for x_ in x..xmax { - if visited.contains(&(x_, zmax)) || hl == Some(Point3::new(x_, y, zmax)) { + if visited.contains(&(x_, zmax)) || hl == Some(Vector3::new(x_, y, zmax)) { break 'z; } @@ -341,20 +359,30 @@ impl Chunk { geometry } - pub fn to_geometry( - &self, - position: Point3, - highlighted: Option<&(Point3, Vector3)>, - ) -> Geometry { + pub fn update_geometry( + &mut self, + render_context: &RenderContext, + chunk_coords: Point3, + highlighted: Option<(Point3, Vector3)>, + ) { + let highlighted = highlighted.and_then(|(position, normal)| { + Self::block_coords_to_local(chunk_coords, position).map(|x| (x, normal)) + }); + + let offset = chunk_coords * CHUNK_ISIZE; let quads: Vec = (0..CHUNK_SIZE) .into_par_iter() .flat_map(|y| { let (culled, mut queue) = self.cull_layer(y); - self.layer_to_quads(y, position, culled, &mut queue, highlighted) + self.layer_to_quads(y, offset, culled, &mut queue, highlighted) }) .collect(); - Self::quads_to_geometry(quads) + self.buffers = Some(GeometryBuffers::from_geometry( + render_context, + &Self::quads_to_geometry(quads), + BufferUsage::empty(), + )); } pub fn save(&self, position: Point3, store: &sled::Db) -> anyhow::Result<()> { diff --git a/src/world/mod.rs b/src/world/mod.rs index 791948d..8e0f8be 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -2,4 +2,358 @@ pub mod block; pub mod chunk; pub mod face_flags; pub mod quad; -pub mod world; + +use std::{collections::VecDeque, time::Duration}; + +use crate::{ + camera::Camera, + npc::Npc, + render_context::RenderContext, + renderable::Renderable, + view::View, + world::{ + block::{Block, BlockType}, + chunk::{Chunk, CHUNK_ISIZE}, + }, +}; +use ahash::AHashMap; +use cgmath::{EuclideanSpace, InnerSpace, Point3, Vector3}; +use wgpu::RenderPass; + +pub struct World { + pub npc: Npc, + + pub chunks: AHashMap, Chunk>, + pub chunk_database: sled::Db, + pub chunk_save_queue: VecDeque<(Point3, bool)>, + pub chunk_load_queue: VecDeque>, + pub chunk_generate_queue: VecDeque>, + + pub highlighted: Option<(Point3, Vector3)>, + + pub unload_timer: Duration, +} + +pub const RENDER_DISTANCE: isize = 8; +pub const WORLD_HEIGHT: isize = 16 * 16 / CHUNK_ISIZE; + +const DEBUG_IO: bool = false; + +impl Renderable for World { + fn update(&mut self, render_context: &RenderContext, dt: Duration, camera: &Camera) { + if let Some(position) = self.chunk_load_queue.pop_front() { + let chunk = self.chunks.entry(position).or_default(); + match chunk.load(position, &self.chunk_database) { + Err(error) => { + eprintln!("Failed to load/generate chunk {:?}: {:?}", position, error) + } + Ok(true) => { + chunk.update_geometry(render_context, position, self.highlighted); + self.enqueue_chunk_save(position, false); + if DEBUG_IO { + println!("Generated chunk {:?}", position); + } + } + Ok(false) => { + chunk.update_geometry(render_context, position, self.highlighted); + if DEBUG_IO { + println!("Loaded chunk {:?}", position); + } + } + } + } else if let Some((position, unload)) = self.chunk_save_queue.pop_front() { + if let Some(chunk) = self.chunks.get(&position) { + if let Err(err) = chunk.save(position, &self.chunk_database) { + eprintln!("Failed to save chunk {:?}: {:?}", position, err); + } else if unload { + self.chunks.remove(&position); + + if DEBUG_IO { + println!("Saved and unloaded chunk {:?}", position); + } + } else if DEBUG_IO { + println!("Saved chunk {:?}", position); + } + } else { + eprintln!("Tried to save unloaded chunk {:?}", position); + } + } + + self.update_highlight(render_context, camera); + + // Queue up new chunks for loading, if necessary + let camera_pos: Point3 = camera.position.cast().unwrap(); + let camera_chunk: Point3 = camera_pos.map(|n| n.div_euclid(CHUNK_ISIZE)); + let mut load_queue = Vec::new(); + for (x, y, z) in itertools::iproduct!( + -RENDER_DISTANCE..RENDER_DISTANCE, + 0..WORLD_HEIGHT, + -RENDER_DISTANCE..RENDER_DISTANCE + ) { + let point: Point3 = Point3::new(x + camera_chunk.x, y, z + camera_chunk.z); + if !self.chunks.contains_key(&point) && !self.chunk_load_queue.contains(&point) { + load_queue.push(point); + } + } + + // TODO Sort based on where camera is looking + load_queue.sort_unstable_by_key(|f| { + (f.x * CHUNK_ISIZE - camera_pos.x).abs() + (f.y * CHUNK_ISIZE - camera_pos.y).abs() + }); + + self.chunk_load_queue.extend(load_queue); + + // Unload chunks that are far away + self.unload_timer += dt; + if self.unload_timer.as_secs() >= 10 { + self.unload_timer = Duration::new(0, 0); + + let camera_pos = camera.position.to_vec(); + let unload_distance = (RENDER_DISTANCE * CHUNK_ISIZE) as f32 * 1.5; + + let mut unload_chunks = Vec::new(); + for point in self.chunks.keys() { + let pos: Point3 = (point * CHUNK_ISIZE).cast().unwrap(); + if (pos.x - camera_pos.x).abs() > unload_distance + || (pos.z - camera_pos.z).abs() > unload_distance + { + unload_chunks.push(*point); + } + } + for point in unload_chunks { + self.enqueue_chunk_save(point, true); + } + } + } + + fn render<'a>(&'a self, render_pass: &mut RenderPass<'a>, view: &View) -> usize { + let mut triangle_count = 0; + + for (position, chunk) in &self.chunks { + if !chunk.is_visible(position * CHUNK_ISIZE, &view) { + continue; + } + + if let Some(buffers) = chunk.buffers.as_ref() { + buffers.apply_buffers(render_pass); + triangle_count += buffers.draw_indexed(render_pass); + } + } + + { + let buffers = self.npc.geometry_buffers.as_ref().unwrap(); + buffers.set_buffers(render_pass); + triangle_count += buffers.draw_indexed(render_pass); + } + + triangle_count + } +} + +impl World { + pub fn new() -> Self { + let chunks = AHashMap::new(); + let npc = Npc::load(); + + let chunk_database = sled::Config::new() + .path("chunks") + .mode(sled::Mode::HighThroughput) + .use_compression(true) + .open() + .unwrap(); + + Self { + chunks, + npc, + + chunk_database, + chunk_load_queue: VecDeque::new(), + chunk_save_queue: VecDeque::new(), + chunk_generate_queue: VecDeque::new(), + + highlighted: None, + + unload_timer: Duration::new(0, 0), + } + } + + pub fn enqueue_chunk_save(&mut self, position: Point3, unload: bool) { + if let Some((_, unload_)) = self + .chunk_save_queue + .iter_mut() + .find(|(pos, _)| pos == &position) + { + *unload_ = *unload_ || unload; + } else { + self.chunk_save_queue.push_back((position, unload)); + } + } + + pub fn update_chunk_geometry( + &mut self, + render_context: &RenderContext, + chunk_position: Point3, + ) { + let chunk = self.chunks.get_mut(&chunk_position).unwrap(); + chunk.update_geometry(render_context, chunk_position, self.highlighted); + } + + fn update_highlight(&mut self, render_context: &RenderContext, camera: &Camera) { + let old = self.highlighted; + let new = self.raycast(camera.position, camera.direction()); + + let old_chunk = old.map(|(pos, _)| pos.map(|n| n.div_euclid(CHUNK_ISIZE))); + let new_chunk = new.map(|(pos, _)| pos.map(|n| n.div_euclid(CHUNK_ISIZE))); + + if old != new { + self.highlighted = new; + + if let Some(old_chunk_) = old_chunk { + self.update_chunk_geometry(render_context, old_chunk_); + } + + if let Some(new_chunk_) = new_chunk { + // Don't update the same chunk twice + if old_chunk != new_chunk { + self.update_chunk_geometry(render_context, new_chunk_); + } + } + } + } + + pub fn break_at_crosshair(&mut self, render_context: &RenderContext, camera: &Camera) { + if let Some((pos, _)) = self.raycast(camera.position, camera.direction()) { + self.set_block(pos.x as isize, pos.y as isize, pos.z as isize, None); + self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE); + } + } + + pub fn place_at_crosshair(&mut self, render_context: &RenderContext, camera: &Camera) { + if let Some((pos, face_normal)) = self.raycast(camera.position, camera.direction()) { + let new_pos = pos.cast().unwrap() + face_normal; + + self.set_block( + new_pos.x as isize, + new_pos.y as isize, + new_pos.z as isize, + Some(Block { + block_type: BlockType::Cobblestone, + }), + ); + + self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE); + } + } + + pub fn get_block(&self, x: isize, y: isize, z: isize) -> Option<&Block> { + let chunk = match self.chunks.get(&Point3::new( + x.div_euclid(CHUNK_ISIZE), + y.div_euclid(CHUNK_ISIZE), + z.div_euclid(CHUNK_ISIZE), + )) { + Some(chunk) => chunk, + None => return None, + }; + + let bx = x.rem_euclid(CHUNK_ISIZE) as usize; + let by = y.rem_euclid(CHUNK_ISIZE) as usize; + let bz = z.rem_euclid(CHUNK_ISIZE) as usize; + chunk.blocks[by][bz][bx].as_ref() + } + + pub fn set_block(&mut self, x: isize, y: isize, z: isize, block: Option) { + let chunk_position = Point3::new( + x.div_euclid(CHUNK_ISIZE), + y.div_euclid(CHUNK_ISIZE), + z.div_euclid(CHUNK_ISIZE), + ); + + if let Some(chunk) = self.chunks.get_mut(&chunk_position) { + let bx = x.rem_euclid(CHUNK_ISIZE) as usize; + let by = y.rem_euclid(CHUNK_ISIZE) as usize; + let bz = z.rem_euclid(CHUNK_ISIZE) as usize; + chunk.blocks[by][bz][bx] = block; + } + + self.chunk_save_queue + .push_back((chunk_position / CHUNK_ISIZE, false)); + } + + fn calc_scale(vector: Vector3, scalar: f32) -> f32 { + if scalar == 0.0 { + f32::INFINITY + } else { + (vector / scalar).magnitude() + } + } + + #[allow(dead_code)] + pub fn raycast( + &self, + origin: Point3, + direction: Vector3, + ) -> Option<(Point3, Vector3)> { + let direction = direction.normalize(); + let scale = Vector3::new( + Self::calc_scale(direction, direction.x), + Self::calc_scale(direction, direction.y), + Self::calc_scale(direction, direction.z), + ); + + let mut position: Point3 = origin.map(|x| x.floor() as i32); + let step = direction.map(|x| x.signum() as i32); + + // Truncate the origin + let mut lengths = Vector3 { + x: if direction.x < 0.0 { + (origin.x - position.x as f32) * scale.x + } else { + (position.x as f32 + 1.0 - origin.x) * scale.x + }, + y: if direction.y < 0.0 { + (origin.y - position.y as f32) * scale.y + } else { + (position.y as f32 + 1.0 - origin.y) * scale.y + }, + z: if direction.z < 0.0 { + (origin.z - position.z as f32) * scale.z + } else { + (position.z as f32 + 1.0 - origin.z) * scale.z + }, + }; + + let mut face; + + while lengths.magnitude() < 100.0 { + if lengths.x < lengths.y && lengths.x < lengths.z { + lengths.x += scale.x; + position.x += step.x; + face = Vector3::unit_x() * -step.x; + } else if lengths.y < lengths.x && lengths.y < lengths.z { + lengths.y += scale.y; + position.y += step.y; + face = Vector3::unit_y() * -step.y; + } else if lengths.z < lengths.x && lengths.z < lengths.y { + lengths.z += scale.z; + position.z += step.z; + face = Vector3::unit_z() * -step.z; + } else { + return None; + } + + if self + .get_block( + position.x as isize, + position.y as isize, + position.z as isize, + ) + .is_some() + { + // Intersection occurred + return Some((position.cast().unwrap(), face)); + } + } + + None + } +} diff --git a/src/world/world.rs b/src/world/world.rs deleted file mode 100644 index f1695a4..0000000 --- a/src/world/world.rs +++ /dev/null @@ -1,388 +0,0 @@ -use std::{collections::VecDeque, time::Duration}; - -use crate::{ - camera::Camera, - geometry::GeometryBuffers, - npc::Npc, - render_context::RenderContext, - renderable::Renderable, - view::View, - world::{ - block::{Block, BlockType}, - chunk::{Chunk, CHUNK_ISIZE}, - }, -}; -use ahash::AHashMap; -use cgmath::{EuclideanSpace, InnerSpace, Point3, Vector3}; -use wgpu::{BufferUsage, RenderPass}; - -pub struct World { - pub npc: Npc, - - pub chunks: AHashMap, Chunk>, - pub chunk_database: sled::Db, - pub chunk_save_queue: VecDeque<(Point3, bool)>, - pub chunk_load_queue: VecDeque>, - pub chunk_generate_queue: VecDeque>, - - pub highlighted: Option<(Point3, Vector3)>, - - pub unload_timer: Duration, -} - -pub const RENDER_DISTANCE: isize = 8; -pub const WORLD_HEIGHT: isize = 16 * 16 / CHUNK_ISIZE; - -const DEBUG_IO: bool = false; - -impl Renderable for World { - fn update(&mut self, render_context: &RenderContext, dt: Duration, camera: &Camera) { - if let Some(position) = self.chunk_load_queue.pop_front() { - let chunk = self.chunks.entry(position).or_default(); - match chunk.load(position, &self.chunk_database) { - Err(error) => { - eprintln!("Failed to load/generate chunk {:?}: {:?}", position, error) - } - Ok(true) => { - self.update_chunk_geometry(render_context, position); - self.enqueue_chunk_save(position, false); - if DEBUG_IO { - println!("Generated chunk {:?}", position); - } - } - Ok(false) => { - self.update_chunk_geometry(render_context, position); - if DEBUG_IO { - println!("Loaded chunk {:?}", position); - } - } - } - } else if let Some((position, unload)) = self.chunk_save_queue.pop_front() { - if let Some(chunk) = self.chunks.get(&position) { - if let Err(err) = chunk.save(position, &self.chunk_database) { - eprintln!("Failed to save chunk {:?}: {:?}", position, err); - } else if unload { - self.chunks.remove(&position); - - if DEBUG_IO { - println!("Saved and unloaded chunk {:?}", position); - } - } else if DEBUG_IO { - println!("Saved chunk {:?}", position); - } - } else { - eprintln!("Tried to save unloaded chunk {:?}", position); - } - } - - self.update_highlight(render_context, camera); - - // Queue up new chunks for loading, if necessary - let camera_pos: Point3 = camera.position.cast().unwrap(); - let camera_chunk: Point3 = camera_pos.map(|n| n.div_euclid(CHUNK_ISIZE)); - let mut load_queue = Vec::new(); - for (x, y, z) in itertools::iproduct!( - -RENDER_DISTANCE..RENDER_DISTANCE, - 0..WORLD_HEIGHT, - -RENDER_DISTANCE..RENDER_DISTANCE - ) { - let point: Point3 = Point3::new(x + camera_chunk.x, y, z + camera_chunk.z); - if !self.chunks.contains_key(&point) && !self.chunk_load_queue.contains(&point) { - load_queue.push(point); - } - } - - // TODO Sort based on where camera is looking - load_queue.sort_unstable_by_key(|f| { - (f.x * CHUNK_ISIZE - camera_pos.x).abs() + (f.y * CHUNK_ISIZE - camera_pos.y).abs() - }); - - self.chunk_load_queue.extend(load_queue); - - // Unload chunks that are far away - self.unload_timer += dt; - if self.unload_timer.as_secs() >= 10 { - self.unload_timer = Duration::new(0, 0); - - let camera_pos = camera.position.to_vec(); - let unload_distance = (RENDER_DISTANCE * CHUNK_ISIZE) as f32 * 1.5; - - let mut unload_chunks = Vec::new(); - for point in self.chunks.keys() { - let pos: Point3 = (point * CHUNK_ISIZE).cast().unwrap(); - if (pos.x - camera_pos.x).abs() > unload_distance - || (pos.z - camera_pos.z).abs() > unload_distance - { - unload_chunks.push(*point); - } - } - for point in unload_chunks { - self.enqueue_chunk_save(point, true); - } - } - } - - fn render<'a>(&'a self, render_pass: &mut RenderPass<'a>, view: &View) -> usize { - let mut triangle_count = 0; - - for (position, chunk) in &self.chunks { - if !chunk.is_visible(position * CHUNK_ISIZE, &view) { - continue; - } - - if let Some(buffers) = chunk.buffers.as_ref() { - buffers.apply_buffers(render_pass); - triangle_count += buffers.draw_indexed(render_pass); - } - } - - { - let buffers = self.npc.geometry_buffers.as_ref().unwrap(); - buffers.set_buffers(render_pass); - triangle_count += buffers.draw_indexed(render_pass); - } - - triangle_count - } -} - -impl World { - pub fn new() -> Self { - let chunks = AHashMap::new(); - let npc = Npc::load(); - - let chunk_database = sled::Config::new() - .path("chunks") - .mode(sled::Mode::HighThroughput) - .use_compression(true) - .open() - .unwrap(); - - Self { - chunks, - npc, - - chunk_database, - chunk_load_queue: VecDeque::new(), - chunk_save_queue: VecDeque::new(), - chunk_generate_queue: VecDeque::new(), - - highlighted: None, - - unload_timer: Duration::new(0, 0), - } - } - - pub fn enqueue_chunk_save(&mut self, position: Point3, unload: bool) { - if let Some((_, unload_)) = self - .chunk_save_queue - .iter_mut() - .find(|(pos, _)| pos == &position) - { - *unload_ = *unload_ || unload; - } else { - self.chunk_save_queue.push_back((position, unload)); - } - } - - pub fn update_chunk_geometry( - &mut self, - render_context: &RenderContext, - chunk_position: Point3, - ) { - let chunk = self.chunks.get_mut(&chunk_position).unwrap(); - let offset = chunk_position * CHUNK_ISIZE; - let geometry = chunk.to_geometry( - offset, - World::highlighted_for_chunk(self.highlighted, &chunk_position).as_ref(), - ); - - chunk.buffers = Some(GeometryBuffers::from_geometry( - render_context, - &geometry, - BufferUsage::empty(), - )); - } - - fn update_highlight(&mut self, render_context: &RenderContext, camera: &Camera) { - let old = self.highlighted; - let new = self.raycast(camera.position, camera.direction()); - - let old_chunk = old.map(|(pos, _)| pos.map(|n| n.div_euclid(CHUNK_ISIZE))); - let new_chunk = new.map(|(pos, _)| pos.map(|n| n.div_euclid(CHUNK_ISIZE))); - - if old != new { - self.highlighted = new; - - if let Some(old_chunk_) = old_chunk { - self.update_chunk_geometry(render_context, old_chunk_); - } - - if let Some(new_chunk_) = new_chunk { - // Don't update the same chunk twice - if old_chunk != new_chunk { - self.update_chunk_geometry(render_context, new_chunk_); - } - } - } - } - - pub fn break_at_crosshair(&mut self, render_context: &RenderContext, camera: &Camera) { - if let Some((pos, _)) = self.raycast(camera.position, camera.direction()) { - self.set_block(pos.x as isize, pos.y as isize, pos.z as isize, None); - self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE); - } - } - - pub fn place_at_crosshair(&mut self, render_context: &RenderContext, camera: &Camera) { - if let Some((pos, face_normal)) = self.raycast(camera.position, camera.direction()) { - let new_pos = pos.cast().unwrap() + face_normal; - - self.set_block( - new_pos.x as isize, - new_pos.y as isize, - new_pos.z as isize, - Some(Block { - block_type: BlockType::Cobblestone, - }), - ); - - self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE); - } - } - - pub fn highlighted_for_chunk( - highlighted: Option<(Point3, Vector3)>, - chunk_position: &Point3, - ) -> Option<(Point3, Vector3)> { - let position = chunk_position * CHUNK_ISIZE; - if let Some((pos, face)) = highlighted { - if pos.x >= position.x - && pos.x < position.x + CHUNK_ISIZE - && pos.y >= position.y - && pos.y < position.y + CHUNK_ISIZE - && pos.z >= position.z - && pos.z < position.z + CHUNK_ISIZE - { - let point: Point3 = EuclideanSpace::from_vec(pos - position); - Some((point.cast().unwrap(), face)) - } else { - None - } - } else { - None - } - } - - pub fn get_block(&self, x: isize, y: isize, z: isize) -> Option<&Block> { - let chunk = match self.chunks.get(&Point3::new( - x.div_euclid(CHUNK_ISIZE), - y.div_euclid(CHUNK_ISIZE), - z.div_euclid(CHUNK_ISIZE), - )) { - Some(chunk) => chunk, - None => return None, - }; - - let bx = x.rem_euclid(CHUNK_ISIZE) as usize; - let by = y.rem_euclid(CHUNK_ISIZE) as usize; - let bz = z.rem_euclid(CHUNK_ISIZE) as usize; - chunk.blocks[by][bz][bx].as_ref() - } - - pub fn set_block(&mut self, x: isize, y: isize, z: isize, block: Option) { - let chunk_position = Point3::new( - x.div_euclid(CHUNK_ISIZE), - y.div_euclid(CHUNK_ISIZE), - z.div_euclid(CHUNK_ISIZE), - ); - - if let Some(chunk) = self.chunks.get_mut(&chunk_position) { - let bx = x.rem_euclid(CHUNK_ISIZE) as usize; - let by = y.rem_euclid(CHUNK_ISIZE) as usize; - let bz = z.rem_euclid(CHUNK_ISIZE) as usize; - chunk.blocks[by][bz][bx] = block; - } - - self.chunk_save_queue - .push_back((chunk_position / CHUNK_ISIZE, false)); - } - - fn calc_scale(vector: Vector3, scalar: f32) -> f32 { - if scalar == 0.0 { - f32::INFINITY - } else { - (vector / scalar).magnitude() - } - } - - #[allow(dead_code)] - pub fn raycast( - &self, - origin: Point3, - direction: Vector3, - ) -> Option<(Point3, Vector3)> { - let direction = direction.normalize(); - let scale = Vector3::new( - Self::calc_scale(direction, direction.x), - Self::calc_scale(direction, direction.y), - Self::calc_scale(direction, direction.z), - ); - - let mut position: Point3 = origin.map(|x| x.floor() as i32); - let step = direction.map(|x| x.signum() as i32); - - // Truncate the origin - let mut lengths = Vector3 { - x: if direction.x < 0.0 { - (origin.x - position.x as f32) * scale.x - } else { - (position.x as f32 + 1.0 - origin.x) * scale.x - }, - y: if direction.y < 0.0 { - (origin.y - position.y as f32) * scale.y - } else { - (position.y as f32 + 1.0 - origin.y) * scale.y - }, - z: if direction.z < 0.0 { - (origin.z - position.z as f32) * scale.z - } else { - (position.z as f32 + 1.0 - origin.z) * scale.z - }, - }; - - let mut face; - - while lengths.magnitude() < 100.0 { - if lengths.x < lengths.y && lengths.x < lengths.z { - lengths.x += scale.x; - position.x += step.x; - face = Vector3::unit_x() * -step.x; - } else if lengths.y < lengths.x && lengths.y < lengths.z { - lengths.y += scale.y; - position.y += step.y; - face = Vector3::unit_y() * -step.y; - } else if lengths.z < lengths.x && lengths.z < lengths.y { - lengths.z += scale.z; - position.z += step.z; - face = Vector3::unit_z() * -step.z; - } else { - return None; - } - - if self - .get_block( - position.x as isize, - position.y as isize, - position.z as isize, - ) - .is_some() - { - // Intersection occurred - return Some((position.cast().unwrap(), face)); - } - } - - None - } -} From 9fb640c4e0c6463ab4a3b3d7e1ab2fef6ae476ac Mon Sep 17 00:00:00 2001 From: Vijfhoek Date: Fri, 4 Jun 2021 02:38:51 +0200 Subject: [PATCH 3/3] Replace a very ugly float comparison with integer logic --- src/state/hud_state.rs | 4 ++-- src/world/quad.rs | 31 ++++++++++++++++++------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/state/hud_state.rs b/src/state/hud_state.rs index 33f5d1c..d3fe888 100644 --- a/src/state/hud_state.rs +++ b/src/state/hud_state.rs @@ -287,8 +287,8 @@ impl HudState { pub const HUD_VERTICES: [HudVertex; 12] = [ // Crosshair HudVertex { position: [UI_SCALE_X * -8.0, UI_SCALE_Y * 8.0], texture_coordinates: [240.0 / 256.0, 0.0 / 256.0] }, - HudVertex { position: [UI_SCALE_X * 8.0, UI_SCALE_Y * 8.0], texture_coordinates: [256.0 / 256.0, 0.0 / 256.0] }, - HudVertex { position: [UI_SCALE_X * 8.0, UI_SCALE_Y * -8.0], texture_coordinates: [256.0 / 256.0, 16.0 / 256.0] }, + HudVertex { position: [UI_SCALE_X * 8.0, UI_SCALE_Y * 8.0], texture_coordinates: [ 1.0, 0.0 / 256.0] }, + HudVertex { position: [UI_SCALE_X * 8.0, UI_SCALE_Y * -8.0], texture_coordinates: [ 1.0, 16.0 / 256.0] }, HudVertex { position: [UI_SCALE_X * -8.0, UI_SCALE_Y * -8.0], texture_coordinates: [240.0 / 256.0, 16.0 / 256.0] }, // Hotbar diff --git a/src/world/quad.rs b/src/world/quad.rs index 88c719c..899450b 100644 --- a/src/world/quad.rs +++ b/src/world/quad.rs @@ -66,11 +66,11 @@ impl Quad { let mut current_index = start_index; let mut vertices = Vec::new(); let mut indices = Vec::new(); - let highlighted: [f32; 3] = self.highlighted_normal.cast().unwrap().into(); if self.visible_faces & FACE_LEFT == FACE_LEFT { - let normal = [-1.0, 0.0, 0.0]; - let highlighted = (normal == highlighted) as i32; + let normal = Vector3::new(-1, 0, 0); + let highlighted = (self.highlighted_normal == normal) as i32; + let normal = normal.cast().unwrap().into(); vertices.extend(&[ BlockVertex { position: [x, y, z ], texture_coordinates: [dz, 1.0], texture_id: t.0 as i32, normal, highlighted }, BlockVertex { position: [x, y, z + dz], texture_coordinates: [0.0, 1.0], texture_id: t.0 as i32, normal, highlighted }, @@ -85,8 +85,9 @@ impl Quad { } if self.visible_faces & FACE_RIGHT == FACE_RIGHT { - let normal = [1.0, 0.0, 0.0]; - let highlighted = (normal == highlighted) as i32; + let normal = Vector3::new(1, 0, 0); + let highlighted = (self.highlighted_normal == normal) as i32; + let normal = normal.cast().unwrap().into(); vertices.extend(&[ BlockVertex { position: [x + dx, y, z ], texture_coordinates: [0.0, 1.0], texture_id: t.1 as i32, normal, highlighted }, BlockVertex { position: [x + dx, y, z + dz], texture_coordinates: [dz, 1.0], texture_id: t.1 as i32, normal, highlighted }, @@ -101,8 +102,9 @@ impl Quad { } if self.visible_faces & FACE_BACK == FACE_BACK { - let normal = [0.0, 0.0, -1.0]; - let highlighted = (normal == highlighted) as i32; + let normal = Vector3::new(0, 0, -1); + let highlighted = (self.highlighted_normal == normal) as i32; + let normal = normal.cast().unwrap().into(); vertices.extend(&[ BlockVertex { position: [x, y, z], texture_coordinates: [dx, 1.0], texture_id: t.2 as i32, normal, highlighted }, BlockVertex { position: [x, y + dy, z], texture_coordinates: [dx, 0.0], texture_id: t.2 as i32, normal, highlighted }, @@ -117,8 +119,9 @@ impl Quad { } if self.visible_faces & FACE_FRONT == FACE_FRONT { - let normal = [0.0, 0.0, 1.0]; - let highlighted = (normal == highlighted) as i32; + let normal = Vector3::new(0, 0, 1); + let highlighted = (self.highlighted_normal == normal) as i32; + let normal = normal.cast().unwrap().into(); vertices.extend(&[ BlockVertex { position: [x, y, z + dz], texture_coordinates: [0.0, 1.0], texture_id: t.3 as i32, normal, highlighted }, BlockVertex { position: [x, y + dy, z + dz], texture_coordinates: [0.0, 0.0], texture_id: t.3 as i32, normal, highlighted }, @@ -133,8 +136,9 @@ impl Quad { } if self.visible_faces & FACE_BOTTOM == FACE_BOTTOM { - let normal = [0.0, -1.0, 0.0]; - let highlighted = (normal == highlighted) as i32; + let normal = Vector3::new(0, -1, 0); + let highlighted = (self.highlighted_normal == normal) as i32; + let normal = normal.cast().unwrap().into(); vertices.extend(&[ BlockVertex { position: [x, y, z ], texture_coordinates: [dx, 0.0], texture_id: t.4 as i32, normal, highlighted }, BlockVertex { position: [x, y, z + dz], texture_coordinates: [dx, dz ], texture_id: t.4 as i32, normal, highlighted }, @@ -149,8 +153,9 @@ impl Quad { } if self.visible_faces & FACE_TOP == FACE_TOP { - let normal = [0.0, 1.0, 0.0]; - let highlighted = (normal == highlighted) as i32; + let normal = Vector3::new(0, 1, 0); + let highlighted = (self.highlighted_normal == normal) as i32; + let normal = normal.cast().unwrap().into(); vertices.extend(&[ BlockVertex { position: [x, y + dy, z ], texture_coordinates: [0.0, 0.0], texture_id: t.5 as i32, normal, highlighted }, BlockVertex { position: [x, y + dy, z + dz], texture_coordinates: [0.0, dz ], texture_id: t.5 as i32, normal, highlighted },