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 - } -}