diff --git a/assets/blocks/iron_ore.json b/assets/blocks/iron_ore.json new file mode 100644 index 0000000..2ff96a6 --- /dev/null +++ b/assets/blocks/iron_ore.json @@ -0,0 +1,5 @@ +{ + "model": { + "simple": "iron_ore" + } +} diff --git a/assets/blocks/leaves.json b/assets/blocks/leaves.json new file mode 100644 index 0000000..880c8c4 --- /dev/null +++ b/assets/blocks/leaves.json @@ -0,0 +1,5 @@ +{ + "model": { + "simple": "leaves" + } +} diff --git a/assets/blocks/log.json b/assets/blocks/log.json new file mode 100644 index 0000000..02ae5c2 --- /dev/null +++ b/assets/blocks/log.json @@ -0,0 +1,5 @@ +{ + "model": { + "simple": "log_side" + } +} diff --git a/assets/textures/blocks/iron_ore.png b/assets/textures/blocks/iron_ore.png new file mode 100644 index 0000000..843542a Binary files /dev/null and b/assets/textures/blocks/iron_ore.png differ diff --git a/assets/textures/blocks/leaves.png b/assets/textures/blocks/leaves.png new file mode 100644 index 0000000..c0c8ed3 Binary files /dev/null and b/assets/textures/blocks/leaves.png differ diff --git a/assets/textures/blocks/log_side.png b/assets/textures/blocks/log_side.png new file mode 100644 index 0000000..f56657f Binary files /dev/null and b/assets/textures/blocks/log_side.png differ diff --git a/src/main.rs b/src/main.rs index 29b7fbd..549def0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ #![feature(duration_constants)] -#![allow(clippy::new_without_default)] +#![allow(clippy::new_without_default, clippy::let_unit_value)] use std::{process::ExitCode, sync::Arc, time::Instant}; diff --git a/src/render/mesh.rs b/src/render/mesh.rs index 2442ce4..c0ab809 100644 --- a/src/render/mesh.rs +++ b/src/render/mesh.rs @@ -46,12 +46,16 @@ impl WorldMeshState { &mut self, allocators: &Arc, coords: ChunkCoords, - world: &World, + world: &mut World, ) -> Result, Error> { - match self.chunks.entry(coords) { - Entry::Occupied(entry) => Ok(Some(entry.into_mut())), - Entry::Vacant(entry) => { - if let Some(chunk) = world.get(coords) { + if let Some(chunk) = world.get_mut(coords) { + if chunk.clear_dirty() { + self.chunks.remove(&coords); + } + + match self.chunks.entry(coords) { + Entry::Occupied(entry) => Ok(Some(entry.into_mut())), + Entry::Vacant(entry) => { let mesh = ChunkMesh::from_chunk( allocators, &self.block_model_registry, @@ -59,10 +63,12 @@ impl WorldMeshState { chunk, )?; Ok(Some(entry.insert(mesh))) - } else { - Ok(None) } } + } else { + self.chunks.remove(&coords); + + Ok(None) } } } diff --git a/src/world/chunk.rs b/src/world/chunk.rs index 37dc654..fa5a018 100644 --- a/src/world/chunk.rs +++ b/src/world/chunk.rs @@ -3,6 +3,7 @@ use super::NeighborQuery; pub struct Chunk { blocks: [u8; Self::SIZE * Self::SIZE * Self::HEIGHT], height_map: [u32; Self::SIZE * Self::SIZE], + dirty: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -12,6 +13,7 @@ pub struct ChunkCoords { } impl ChunkCoords { + #[inline] pub const fn new(x: i32, z: i32) -> Self { Self { x, z } } @@ -24,6 +26,12 @@ impl ChunkCoords { let (cx, cz) = self.origin_block(); (cx + x as i32, cz + z as i32) } + + pub fn from_block_coords(x: i32, z: i32) -> Self { + let cx = if x < 0 { x - Chunk::SIZE as i32 + 1 } else { x } / Chunk::SIZE as i32; + let cz = if z < 0 { z - Chunk::SIZE as i32 + 1 } else { z } / Chunk::SIZE as i32; + Self::new(cx, cz) + } } impl Chunk { @@ -34,9 +42,16 @@ impl Chunk { Self { blocks: [0; Self::SIZE * Self::SIZE * Self::HEIGHT], height_map: [0; Self::SIZE * Self::SIZE], + dirty: false, } } + pub fn clear_dirty(&mut self) -> bool { + let dirty = self.dirty; + self.dirty = false; + dirty + } + pub fn fill_layer u8>(&mut self, y: u32, f: F) { for x in 0..Self::SIZE as u32 { for z in 0..Self::SIZE as u32 { @@ -50,7 +65,12 @@ impl Chunk { } pub fn set_id(&mut self, x: u32, y: u32, z: u32, id: u8) { + if x >= Self::SIZE as u32 || z >= Self::SIZE as u32 || y >= Self::HEIGHT as u32 { + panic!("Invalid coords: {x}, {y}, {z}"); + } + self.blocks[Self::to_index(x, y, z)] = id; + self.dirty = true; if id != 0 && y >= self.top_height_at(x, z) { self.set_top_height_at(x, z, y + 1); diff --git a/src/world/feature.rs b/src/world/feature.rs new file mode 100644 index 0000000..12d6f83 --- /dev/null +++ b/src/world/feature.rs @@ -0,0 +1,467 @@ +use std::{ + mem, + ops::{Add, Deref, Range}, +}; + +use super::{level::BoundingBox, Chunk, ChunkCoords}; + +pub enum FeaturePlacementFilter { + ReplaceAll, + ReplaceAir, + Replace(u8), +} + +pub struct FeatureLayer { + x_range: Range, + z_range: Range, + content: Vec, +} + +pub struct FeatureBuilder { + y_range: Range, + layers: Vec, +} + +pub struct Feature { + width: u32, + height: u32, + depth: u32, + filter: FeaturePlacementFilter, + content: Vec, +} + +pub struct PositionedFeature { + inner: Feature, + x: i32, + y: i32, + z: i32, +} + +impl Deref for PositionedFeature { + type Target = Feature; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl FeaturePlacementFilter { + pub fn should_place(&self, from: u8) -> bool { + match self { + Self::ReplaceAir => from == 0, + Self::ReplaceAll => true, + &Self::Replace(id) => from == id, + } + } +} + +impl PositionedFeature { + pub fn aabb(&self) -> BoundingBox { + BoundingBox { + x: self.x, + y: self.y, + z: self.z, + w: self.width as i32, + h: self.height as i32, + d: self.depth as i32, + } + } + + pub fn place(&self, coords: ChunkCoords, chunk: &mut Chunk) { + let in_chunk_aabb = self.aabb().in_chunk(coords); + let Some(in_chunk_aabb) = in_chunk_aabb else { + return; + }; + + let (sx, sz) = coords.origin_block(); + + for ix in 0..in_chunk_aabb.w { + // Coords in chunk + let cx = ix + in_chunk_aabb.x; + // Coords in feature + let fx = cx as i32 + sx - self.x; + assert!(fx >= 0); + assert!(fx < self.width as i32); + + for iy in 0..in_chunk_aabb.h { + let cy = iy + in_chunk_aabb.y; + + for iz in 0..in_chunk_aabb.d { + let cz = iz + in_chunk_aabb.z; + let fz = cz as i32 + sz - self.z; + assert!(fz >= 0); + assert!(fz < self.depth as i32); + + let chunk_id = chunk.get_id(cx, cy, cz); + let feature_id = self.get(fx as u32, iy, fz as u32); + + if self.filter.should_place(chunk_id) { + chunk.set_id(cx, cy, cz, feature_id); + } + } + } + } + } +} + +impl Feature { + pub fn position(self, x: i32, y: i32, z: i32) -> PositionedFeature { + PositionedFeature { + inner: self, + x, + y, + z, + } + } + + pub fn position_centered_horizontally(self, x: i32, y: i32, z: i32) -> PositionedFeature { + let x = x - self.width as i32 / 2; + let z = z - self.width as i32 / 2; + PositionedFeature { + inner: self, + x, + y, + z, + } + } + + pub fn get(&self, x: u32, y: u32, z: u32) -> u8 { + let i = x + (z + y * self.depth) * self.width; + self.content[i as usize] + } +} + +impl FeatureLayer { + pub fn new() -> Self { + Self { + x_range: 0..0, + z_range: 0..0, + content: vec![], + } + } + + #[inline] + pub fn width(&self) -> u32 { + range_width(&self.x_range) + } + + #[inline] + pub fn depth(&self) -> u32 { + range_width(&self.z_range) + } + + #[inline] + pub fn size(&self) -> (u32, u32) { + (self.width(), self.depth()) + } + + pub fn set(&mut self, x: i32, z: i32, id: u8) { + self.adjust_for_placement(x, z); + let i = self.index(x, z); + self.content[i] = id; + } + + pub fn replace(&mut self, x: i32, z: i32, from: u8, to: u8) { + self.adjust_for_placement(x, z); + if self.get(x, z) == from { + self.set(x, z, to); + } + } + + pub fn get(&self, x: i32, z: i32) -> u8 { + self.content[self.index(x, z)] + } + + pub fn try_get(&self, x: i32, z: i32) -> Option { + if !self.x_range.contains(&x) || !self.z_range.contains(&z) { + None + } else { + Some(self.get(x, z)) + } + } + + pub fn reserve(&mut self, x_range: Range, z_range: Range) { + let new_x_range = x_range.start.min(self.x_range.start)..x_range.end.max(self.x_range.end); + let new_z_range = z_range.start.min(self.z_range.start)..z_range.end.max(self.z_range.end); + + if new_x_range == self.x_range && new_z_range == self.z_range { + return; + } + + let new_width = range_width(&new_x_range); + let new_depth = range_width(&new_z_range); + + let old_width = self.width(); + let old_depth = self.depth(); + + let shift_x = (self.x_range.start - new_x_range.start) as u32; + let shift_z = (self.z_range.start - new_z_range.start) as u32; + + let old = mem::replace(&mut self.content, vec![0; (new_width * new_depth) as usize]); + + for abs_z in 0..old_depth { + let new_abs_z = shift_z + abs_z; + let abs_x = 0..old_width as usize; + let new_abs_x = advance_range(&abs_x, shift_x as usize); + + let old_i = advance_range(&abs_x, (abs_z * old_width) as usize); + let new_i = advance_range(&new_abs_x, (new_abs_z * new_width) as usize); + + self.content[new_i].copy_from_slice(&old[old_i]); + } + + self.x_range = new_x_range; + self.z_range = new_z_range; + } + + pub fn adjust_for_placement(&mut self, x: i32, z: i32) { + // Start x,z ranges: 0..0 + let new_x_range = adjust_range(x, &self.x_range); + let new_z_range = adjust_range(z, &self.z_range); + + self.reserve(new_x_range, new_z_range); + } + + fn index(&self, x: i32, z: i32) -> usize { + assert!(self.x_range.contains(&x)); + assert!(self.z_range.contains(&z)); + + let abs_x = (x - self.x_range.start) as usize; + let abs_z = (z - self.z_range.start) as usize; + + abs_x + abs_z * self.width() as usize + } +} + +impl FeatureBuilder { + pub fn new() -> Self { + Self { + y_range: 0..0, + layers: vec![], + } + } + + pub fn height(&self) -> u32 { + (self.y_range.end - self.y_range.start) as u32 + } + + pub fn reserve_height(&mut self, y_range: Range) { + let new_y_range = y_range.start.min(self.y_range.start)..y_range.end.max(self.y_range.end); + + if new_y_range == self.y_range { + return; + } + + let new_height = range_width(&new_y_range); + + let shift_y = (self.y_range.start - new_y_range.start) as usize; + + // -1..3 -> -2..3, shift_y = 1 + // [??, -1, 0, 1, 2] + // [-2, -1, 0, 1, 2] + self.layers + .resize_with(new_height as usize, FeatureLayer::new); + self.layers.rotate_right(shift_y); + self.y_range = new_y_range; + } + + pub fn adjust_height_for_placement(&mut self, y: i32) { + let new_y_range = adjust_range(y, &self.y_range); + self.reserve_height(new_y_range); + } + + pub fn set(&mut self, x: i32, y: i32, z: i32, id: u8) { + self.adjust_height_for_placement(y); + let i = (y - self.y_range.start) as usize; + self.layers[i].set(x, z, id); + } + + pub fn replace(&mut self, x: i32, y: i32, z: i32, from: u8, to: u8) { + self.adjust_height_for_placement(y); + let i = (y - self.y_range.start) as usize; + self.layers[i].replace(x, z, from, to); + } + + pub fn get(&self, x: i32, y: i32, z: i32) -> u8 { + assert!(self.y_range.contains(&y)); + self.layers[(y - self.y_range.start) as usize].get(x, z) + } + + pub fn build(self, filter: FeaturePlacementFilter) -> Feature { + assert!(!self.layers.is_empty()); + + let x_range = largest_range_bounds(self.layers.iter().map(|l| &l.x_range)); + let z_range = largest_range_bounds(self.layers.iter().map(|l| &l.z_range)); + let width = range_width(&x_range); + let depth = range_width(&z_range); + let height = self.layers.len() as u32; + + let mut content = vec![0; (width * depth) as usize * self.layers.len()]; + + for (y, layer) in self.layers.into_iter().enumerate() { + for x in x_range.clone() { + for z in z_range.clone() { + let id = layer.try_get(x, z).unwrap_or(0); + + let ix = (x - x_range.start) as usize; + let iz = (z - z_range.start) as usize; + let i = ix + (iz + y * depth as usize) * width as usize; + + content[i] = id; + } + } + } + + Feature { + width, + height, + depth, + content, + filter, + } + } +} + +fn largest_range_bounds<'a, I: IntoIterator>>(ranges: I) -> Range { + let mut result = 0..0; + for range in ranges { + if range.start < result.start { + result.start = range.start; + } + if range.end > result.end { + result.end = range.end; + } + } + result +} + +fn advance_range>(range: &Range, x: T) -> Range { + range.start + x..range.end + x +} + +#[inline] +fn adjust_range(x: i32, axis: &Range) -> Range { + let mut new_x = axis.clone(); + if x < axis.start { + new_x.start = x; + } else if x >= axis.end { + new_x.end = x + 1; + } + new_x +} + +#[inline] +fn range_width(axis: &Range) -> u32 { + assert!(axis.end >= axis.start); + (axis.end - axis.start) as u32 +} + +#[cfg(test)] +mod tests { + use super::{FeatureBuilder, FeatureLayer}; + + #[test] + fn featuer_build() { + let mut builder = FeatureBuilder::new(); + + builder.set(0, 0, 0, 1); + builder.set(0, 1, 0, 1); + for y in 2..5 { + for x in -1..=1 { + for z in -1..=1 { + let id = if x == 0 && z == 0 { 3 } else { 2 }; + builder.set(x, y, z, id); + } + } + } + + // Should yield a 3x5x3 feature + let feature = builder.build(); + assert_eq!(feature.width, 3); + assert_eq!(feature.height, 5); + assert_eq!(feature.depth, 3); + assert_eq!( + &feature.content[..], + &[ + // y = 0 + 0, 0, 0, // z = -1 + 0, 1, 0, // z = 0 + 0, 0, 0, // z = 1 + // y = 1 + 0, 0, 0, // z = -1 + 0, 1, 0, // z = 0 + 0, 0, 0, // z = 1 + // y = 2 + 2, 2, 2, // z = -1 + 2, 3, 2, // z = 0 + 2, 2, 2, // z = 1 + // y = 3 + 2, 2, 2, // z = -1 + 2, 3, 2, // z = 0 + 2, 2, 2, // z = 1 + // y = 4 + 2, 2, 2, // z = -1 + 2, 3, 2, // z = 0 + 2, 2, 2, // z = 1 + ] + ); + } + + #[test] + fn feature_layer_resize() { + let mut layer = FeatureLayer::new(); + assert_eq!(layer.size(), (0, 0)); + + // 0x0 -> 1x1 + layer.set(0, 0, 1); + assert_eq!(layer.size(), (1, 1)); + assert_eq!(layer.get(0, 0), 1); + + // 1x1 -> 3x1 + layer.set(-2, 0, 2); + assert_eq!(layer.size(), (3, 1)); + assert_eq!(layer.get(0, 0), 1); + assert_eq!(layer.get(-1, 0), 0); + assert_eq!(layer.get(-2, 0), 2); + + // 3x1 -> 3x2 + layer.set(-1, -1, 3); + assert_eq!(layer.size(), (3, 2)); + assert_eq!( + &layer.content[..], + &[ + 0, 3, 0, // z = -1 + 2, 0, 1, // z = 0 + ] + ); + + // 3x2 -> 4x4 + layer.set(-3, -3, 4); + assert_eq!(layer.size(), (4, 4)); + assert_eq!( + &layer.content[..], + &[ + 4, 0, 0, 0, // z = -3 + 0, 0, 0, 0, // z = -2 + 0, 0, 3, 0, // z = -1 + 0, 2, 0, 1, // z = 0 + ] + ); + + // 4x4 -> 5x6 + layer.set(1, 2, 5); + assert_eq!(layer.size(), (5, 6)); + assert_eq!( + &layer.content[..], + &[ + 4, 0, 0, 0, 0, // z = -3 + 0, 0, 0, 0, 0, // z = -2 + 0, 0, 3, 0, 0, // z = -1 + 0, 2, 0, 1, 0, // z = 0 + 0, 0, 0, 0, 0, // z = 1 + 0, 0, 0, 0, 5, // z = 2 + ] + ); + } +} + diff --git a/src/world/generator.rs b/src/world/generator.rs index 53f82b1..6c28f19 100644 --- a/src/world/generator.rs +++ b/src/world/generator.rs @@ -17,7 +17,7 @@ impl ChunkGenerator for NoiseChunkGenerator { ) { let grass = block_registry.get("grass").unwrap_or(0); let dirt = block_registry.get("dirt").unwrap_or(0); - let stone = block_registry.get("stone").unwrap_or(0); + // let stone = block_registry.get("stone").unwrap_or(0); for ix in 0..Chunk::SIZE as u32 { for iz in 0..Chunk::SIZE as u32 { @@ -37,7 +37,8 @@ impl ChunkGenerator for NoiseChunkGenerator { let id = if y + 1 == h { grass } else if h - y > stone_depth { - stone + 0 + // stone } else { dirt }; diff --git a/src/world/level.rs b/src/world/level.rs index 1387bbf..9cb8686 100644 --- a/src/world/level.rs +++ b/src/world/level.rs @@ -1,20 +1,95 @@ -use std::collections::HashMap; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use glam::Vec3; use crate::render::asset::block::BlockRegistry; -use super::{Chunk, ChunkCoords, ChunkGenerator}; +use super::{ + feature::{FeatureBuilder, FeaturePlacementFilter, PositionedFeature}, + Chunk, ChunkCoords, ChunkGenerator, +}; pub struct World { chunks: HashMap, block_registry: BlockRegistry, + + // populated_chunk_features: HashSet, + unpopulated_chunks: HashSet, + pending_chunk_features: HashMap>>, +} + +#[derive(Clone, Copy, Debug)] +pub struct BoundingBox { + pub x: i32, + pub y: i32, + pub z: i32, + pub w: i32, + pub h: i32, + pub d: i32, +} + +#[derive(Clone, Copy, Debug)] +pub struct InChunkBoundingBox { + pub x: u32, + pub y: u32, + pub z: u32, + pub w: u32, + pub h: u32, + pub d: u32, +} + +impl BoundingBox { + pub fn map_chunks(self, mut f: F) { + let start = ChunkCoords::from_block_coords(self.x, self.z); + let end = ChunkCoords::from_block_coords( + self.x + self.w + Chunk::SIZE as i32 - 1, + self.z + self.d + Chunk::SIZE as i32 - 1, + ); + + for cx in start.x..end.x { + for cz in start.z..end.z { + f(ChunkCoords::new(cx, cz)); + } + } + } + + pub fn in_chunk(self, chunk: ChunkCoords) -> Option { + let cx = chunk.x * Chunk::SIZE as i32; + let cz = chunk.z * Chunk::SIZE as i32; + + let sx = (self.x - cx).clamp(0, Chunk::SIZE as i32) as u32; + let sz = (self.z - cz).clamp(0, Chunk::SIZE as i32) as u32; + + let dx = (self.x + self.w - cx).clamp(0, Chunk::SIZE as i32) as u32; + let dz = (self.z + self.d - cz).clamp(0, Chunk::SIZE as i32) as u32; + + let sy = self.y.clamp(0, Chunk::HEIGHT as i32) as u32; + let dy = (self.y + self.h).clamp(0, Chunk::HEIGHT as i32) as u32; + + if sx < dx && sy < dy && sz < dz { + Some(InChunkBoundingBox { + x: sx, + y: sy, + z: sz, + w: dx - sx, + h: dy - sy, + d: dz - sz, + }) + } else { + None + } + } } impl World { pub fn new(block_registry: BlockRegistry) -> Self { Self { chunks: HashMap::new(), + pending_chunk_features: HashMap::new(), + unpopulated_chunks: HashSet::new(), block_registry, } } @@ -25,36 +100,169 @@ impl World { let mut chunk = Chunk::empty(); g.generate_terrain(coords, &mut chunk, &self.block_registry); + + // Populate chunk with pending features + if let Some(pending) = self.pending_chunk_features.remove(&coords) { + for pending in pending { + pending.place(coords, &mut chunk); + } + } + + self.unpopulated_chunks.insert(coords); self.chunks.insert(coords, chunk); } + fn populate_terrain(&mut self, coords: ChunkCoords, g: &mut G) { + if !self.chunks.contains_key(&coords) { + self.generate(coords, g); + } + } + + fn populate_terrain_at( + &mut self, + center: ChunkCoords, + radius: i32, + g: &mut G, + ) { + for cx in -radius..=radius { + for cz in -radius..=radius { + let coords = ChunkCoords::new(center.x + cx, center.z + cz); + self.populate_terrain(coords, g); + } + } + } + + fn place_feature(&mut self, feature: Arc) { + let aabb = feature.aabb(); + aabb.map_chunks(|coords| { + if let Some(chunk) = self.chunks.get_mut(&coords) { + feature.place(coords, chunk); + } else { + let pending = self.pending_chunk_features.entry(coords).or_default(); + pending.push(feature.clone()); + } + }); + } + + fn populate_features(&mut self) { + let mut features = vec![]; + + let log = self.block_registry.get("log").unwrap_or(0); + let leaves = self.block_registry.get("leaves").unwrap_or(0); + + for coords in self.unpopulated_chunks.drain() { + let Some(chunk) = self.chunks.get_mut(&coords) else { + continue; + }; + + // For each unpopulated chunk, generate a set of per-chunk features + let trees = rand::random_range(3..8); + + for _ in 0..trees { + // Generate trees + // TODO collision detection for these + let trunk_height = rand::random_range(5..8); + let leaves_radius = trunk_height / 2 + rand::random_range(0..2); + + let fx = rand::random_range(0..Chunk::SIZE as u32); + let fz = rand::random_range(0..Chunk::SIZE as u32); + let fy = chunk.top_height_at(fx, fz); + + let mut builder = FeatureBuilder::new(); + + for y in 0..trunk_height { + builder.set(0, y, 0, log); + } + for x in -leaves_radius..=leaves_radius { + for y in -leaves_radius..=leaves_radius { + for z in -leaves_radius..=leaves_radius { + let p = Vec3::new(x as f32, y as f32, z as f32); + if p.distance(Vec3::ZERO) > leaves_radius as f32 - 0.1 { + continue; + } + + builder.replace( + x, + y + trunk_height / 2 + leaves_radius / 2 + 1, + z, + 0, + leaves, + ); + } + } + } + + let (root_x, root_z) = coords.block_coords(fx, fz); + + features.push(Arc::new( + builder + .build(FeaturePlacementFilter::ReplaceAir) + .position_centered_horizontally(root_x, fy as i32, root_z), + )); + } + } + + // Place features + for feature in features { + self.place_feature(feature); + } + } + pub fn get(&self, coords: ChunkCoords) -> Option<&Chunk> { self.chunks.get(&coords) } + pub fn get_mut(&mut self, coords: ChunkCoords) -> Option<&mut Chunk> { + self.chunks.get_mut(&coords) + } + pub fn update_with_camera( &mut self, camera_position: Vec3, g: &mut G, ) { - const VIEW_DISTANCE: i32 = 5; + const VIEW_DISTANCE: i32 = 1; - fn cpos(x: f32) -> i32 { - (x as i32 + Chunk::SIZE as i32 / 2) / Chunk::SIZE as i32 - } + let camera_chunk = + ChunkCoords::from_block_coords(camera_position.x as i32, camera_position.z as i32); - let camera_cx = cpos(camera_position.x); - let camera_cz = cpos(camera_position.z); - - for cx in camera_cx - VIEW_DISTANCE..=camera_cx + VIEW_DISTANCE { - for cz in camera_cz - VIEW_DISTANCE..=camera_cz + VIEW_DISTANCE { - let coords = ChunkCoords::new(cx, cz); - - if !self.chunks.contains_key(&coords) { - self.generate(coords, g); - } - } - } + self.populate_terrain_at(camera_chunk, VIEW_DISTANCE, g); + self.populate_features(); + // self.populate_features_at(camera_chunk, VIEW_DISTANCE); + } +} + +#[cfg(test)] +mod tests { + use crate::world::ChunkCoords; + + use super::BoundingBox; + + #[test] + fn test_bounding_box_chunks() { + let aabb = BoundingBox { + x: -1, + y: 31, + z: 15, + w: 3, + h: 10, + d: 3, + }; + + let mut v0 = false; + let mut v1 = false; + + aabb.map_chunks(|coords| { + if coords == ChunkCoords::new(-1, 0) { + v0 = true; + } else if coords == ChunkCoords::new(0, 0) { + v1 = true; + } else { + panic!("Unexpected chunk coords: {coords:?}"); + } + }); + + assert!(v0 && v1); } } diff --git a/src/world/mod.rs b/src/world/mod.rs index 11dccba..ecbec0b 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -1,6 +1,7 @@ use bitflags::bitflags; pub mod chunk; +pub mod feature; pub mod generator; pub mod level;