From d9a053ea5a2edee57430566675ae4c16003e1f47 Mon Sep 17 00:00:00 2001 From: Jarrod Doyle Date: Sat, 6 Apr 2024 11:44:50 +0100 Subject: [PATCH 1/5] Chunks now know their dimensions and block dimensions can be defined --- src/core/app.rs | 5 ++- src/voxel/world/chunk.rs | 62 ++++++++++++++++++++++++++++++-------- src/voxel/world/manager.rs | 39 ++++++------------------ src/voxel/world/mod.rs | 5 ++- 4 files changed, 66 insertions(+), 45 deletions(-) diff --git a/src/core/app.rs b/src/core/app.rs index e5f1cb0..b1cb0e6 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -79,7 +79,10 @@ impl<'window> App<'window> { gain: 0.5, lacunarity: 2.0, }, - glam::uvec3(32, 32, 32), + voxel::world::ChunkSettings { + dimensions: glam::uvec3(32, 32, 32), + block_dimensions: glam::uvec3(8, 8, 8), + }, ); let mut renderer = BrickmapRenderer::new(&self.render_ctx, &camera_controller)?; diff --git a/src/voxel/world/chunk.rs b/src/voxel/world/chunk.rs index c9e8c3c..8c90700 100644 --- a/src/voxel/world/chunk.rs +++ b/src/voxel/world/chunk.rs @@ -1,38 +1,74 @@ use crate::math; -use super::Voxel; +use super::{GenerationSettings, Voxel}; + +#[derive(Debug, Clone, Copy)] +pub struct ChunkSettings { + pub dimensions: glam::UVec3, + pub block_dimensions: glam::UVec3, +} #[derive(Debug)] pub struct Chunk { - pos: glam::IVec3, + settings: ChunkSettings, noise: Vec, blocks: Vec>, } impl Chunk { - pub fn new(pos: glam::IVec3, noise: Vec, blocks: Vec>) -> Self { - Self { pos, noise, blocks } + pub fn new( + generation_settings: &GenerationSettings, + chunk_settings: ChunkSettings, + pos: glam::IVec3, + ) -> Self { + let dims = chunk_settings.dimensions; + + // We use dimensions of `chunk_dims + 1` because the corners on the last chunk + // block of each axis step outside of our 0..N bounds, sharing a value with the + // neighbouring chunk + let noise = simdnoise::NoiseBuilder::fbm_3d_offset( + pos.x as f32 * dims.x as f32, + dims.x as usize + 1, + pos.y as f32 * dims.y as f32, + dims.y as usize + 1, + pos.z as f32 * dims.z as f32, + dims.z as usize + 1, + ) + .with_seed(generation_settings.seed) + .with_freq(generation_settings.frequency) + .with_octaves(generation_settings.octaves) + .with_gain(generation_settings.gain) + .with_lacunarity(generation_settings.lacunarity) + .generate() + .0; + + let num_blocks = dims.x * dims.y * dims.z; + let blocks = vec![vec![]; num_blocks as usize]; + + Self { + settings: chunk_settings, + noise, + blocks, + } } - pub fn get_block(&mut self, block_pos: glam::UVec3, chunk_dims: glam::UVec3) -> Vec { - assert_eq!( - self.blocks.len(), - (chunk_dims.x * chunk_dims.y * chunk_dims.z) as usize - ); + pub fn get_block(&mut self, pos: glam::UVec3) -> Vec { + let dims = self.settings.dimensions; + assert!(pos.x < dims.x && pos.y < dims.y && pos.z < dims.z); - let block_idx = math::to_1d_index(block_pos, chunk_dims); + let block_idx = math::to_1d_index(pos, dims); let mut block = &self.blocks[block_idx]; if block.is_empty() { - self.gen_block(block_pos, block_idx, chunk_dims); + self.gen_block(pos, block_idx); block = &self.blocks[block_idx] } block.to_owned() } - pub fn gen_block(&mut self, block_pos: glam::UVec3, block_idx: usize, chunk_dims: glam::UVec3) { + pub fn gen_block(&mut self, block_pos: glam::UVec3, block_idx: usize) { let block = &mut self.blocks[block_idx]; - let noise_dims = chunk_dims + glam::uvec3(1, 1, 1); + let noise_dims = self.settings.dimensions + glam::uvec3(1, 1, 1); // Extract relevant noise values from the chunk let mut noise_vals = Vec::new(); diff --git a/src/voxel/world/manager.rs b/src/voxel/world/manager.rs index 9698ac9..3f2349d 100644 --- a/src/voxel/world/manager.rs +++ b/src/voxel/world/manager.rs @@ -1,25 +1,25 @@ use std::collections::HashMap; -use super::{Chunk, GenerationSettings, Voxel}; +use super::{chunk::ChunkSettings, Chunk, GenerationSettings, Voxel}; pub struct WorldManager { - settings: GenerationSettings, - chunk_dims: glam::UVec3, + generation_settings: GenerationSettings, + chunk_settings: ChunkSettings, chunks: HashMap, } impl WorldManager { - pub fn new(settings: GenerationSettings, chunk_dims: glam::UVec3) -> Self { + pub fn new(generation_settings: GenerationSettings, chunk_settings: ChunkSettings) -> Self { let chunks = HashMap::new(); Self { - settings, - chunk_dims, + generation_settings, + chunk_settings, chunks, } } pub fn get_chunk_dims(&self) -> glam::UVec3 { - self.chunk_dims + self.chunk_settings.dimensions } pub fn get_block(&mut self, chunk_pos: glam::IVec3, local_pos: glam::UVec3) -> Vec { @@ -31,31 +31,10 @@ impl WorldManager { } let chunk = self.chunks.get_mut(&chunk_pos).unwrap(); - chunk.get_block(local_pos, self.chunk_dims) + chunk.get_block(local_pos) } fn gen_chunk(&mut self, pos: glam::IVec3) -> Chunk { - // We use dimensions of `chunk_dims + 1` because the corners on the last chunk - // block of each axis step outside of our 0..N bounds, sharing a value with the - // neighbouring chunk - let noise = simdnoise::NoiseBuilder::fbm_3d_offset( - pos.x as f32 * self.chunk_dims.x as f32, - self.chunk_dims.x as usize + 1, - pos.y as f32 * self.chunk_dims.y as f32, - self.chunk_dims.y as usize + 1, - pos.z as f32 * self.chunk_dims.z as f32, - self.chunk_dims.z as usize + 1, - ) - .with_seed(self.settings.seed) - .with_freq(self.settings.frequency) - .with_octaves(self.settings.octaves) - .with_gain(self.settings.gain) - .with_lacunarity(self.settings.lacunarity) - .generate() - .0; - - let num_blocks = self.chunk_dims.x * self.chunk_dims.y * self.chunk_dims.z; - let blocks = vec![vec![]; num_blocks as usize]; - Chunk::new(pos, noise, blocks) + Chunk::new(&self.generation_settings, self.chunk_settings, pos) } } diff --git a/src/voxel/world/mod.rs b/src/voxel/world/mod.rs index 5fb1671..b293cdd 100644 --- a/src/voxel/world/mod.rs +++ b/src/voxel/world/mod.rs @@ -1,7 +1,10 @@ mod chunk; mod manager; -pub use {chunk::Chunk, manager::*}; +pub use { + chunk::{Chunk, ChunkSettings}, + manager::*, +}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Voxel { -- 2.40.1 From 35ea1390973e3a40aaeb0bfc97b88851b103402d Mon Sep 17 00:00:00 2001 From: Jarrod Doyle Date: Sat, 13 Apr 2024 14:59:48 +0100 Subject: [PATCH 2/5] Initial chunk region get --- src/voxel/world/chunk.rs | 40 ++++++++++++++++++++++++++++++++++++++ src/voxel/world/manager.rs | 5 ++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/voxel/world/chunk.rs b/src/voxel/world/chunk.rs index 8c90700..b6a1017 100644 --- a/src/voxel/world/chunk.rs +++ b/src/voxel/world/chunk.rs @@ -52,6 +52,46 @@ impl Chunk { } } + pub fn get_region( + &mut self, + region_start: glam::UVec3, + region_dims: glam::UVec3, + ) -> Vec { + let mut voxels = vec![]; + + let start = region_start; + let end = region_start + region_dims; + let dims = self.settings.dimensions * self.settings.block_dimensions; + assert!(end.x <= dims.x && end.y <= dims.y && end.z <= dims.z); + + for z in (start.z)..(end.z) { + for y in (start.y)..(end.y) { + for x in (start.x)..(end.x) { + voxels.push(self.get_voxel(glam::uvec3(x, y, z))); + } + } + } + + voxels + } + + pub fn get_voxel(&mut self, pos: glam::UVec3) -> Voxel { + let dims = self.settings.dimensions * self.settings.block_dimensions; + debug_assert!(pos.x < dims.x && pos.y < dims.y && pos.z < dims.z); + + let block_pos = pos / self.settings.block_dimensions; + let block_idx = math::to_1d_index(block_pos, self.settings.dimensions); + let mut block = &self.blocks[block_idx]; + if block.is_empty() { + self.gen_block(block_pos, block_idx); + block = &self.blocks[block_idx] + } + + let local_pos = pos % self.settings.block_dimensions; + let local_idx = math::to_1d_index(local_pos, self.settings.block_dimensions); + block[local_idx] + } + pub fn get_block(&mut self, pos: glam::UVec3) -> Vec { let dims = self.settings.dimensions; assert!(pos.x < dims.x && pos.y < dims.y && pos.z < dims.z); diff --git a/src/voxel/world/manager.rs b/src/voxel/world/manager.rs index 3f2349d..f97cdf5 100644 --- a/src/voxel/world/manager.rs +++ b/src/voxel/world/manager.rs @@ -31,7 +31,10 @@ impl WorldManager { } let chunk = self.chunks.get_mut(&chunk_pos).unwrap(); - chunk.get_block(local_pos) + let block_dims = self.chunk_settings.block_dimensions; + chunk.get_region(local_pos * block_dims, block_dims) + + // chunk.get_block(local_pos) } fn gen_chunk(&mut self, pos: glam::IVec3) -> Chunk { -- 2.40.1 From 74f12739821aaf978273d7de221e10ffe1d17dc3 Mon Sep 17 00:00:00 2001 From: Jarrod Doyle Date: Sat, 13 Apr 2024 21:24:56 +0100 Subject: [PATCH 3/5] Add ndarray --- Cargo.lock | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + 2 files changed, 49 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 078dc4b..8053ccb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -829,6 +829,16 @@ dependencies = [ "libc", ] +[[package]] +name = "matrixmultiply" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.1" @@ -879,6 +889,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + [[package]] name = "ndk" version = "0.8.0" @@ -909,6 +932,24 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "num-complex" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -1156,6 +1197,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "redox_syscall" version = "0.3.5" @@ -1540,6 +1587,7 @@ dependencies = [ "env_logger", "glam", "log", + "ndarray", "pollster", "simdnoise", "wgpu", diff --git a/Cargo.toml b/Cargo.toml index 7858be6..ad609ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ bytemuck = { version = "1.15.0", features = ["derive"] } env_logger = "0.11.3" glam = "0.26.0" log = "0.4.21" +ndarray = "0.15.6" pollster = "0.3.0" simdnoise = "3.1.6" wgpu = "0.19.3" -- 2.40.1 From 9fc4594b9ad0a5393c5897ce79a5f246d6ec6252 Mon Sep 17 00:00:00 2001 From: Jarrod Doyle Date: Sat, 13 Apr 2024 21:25:27 +0100 Subject: [PATCH 4/5] Experiment with using ndarray to represent chunk voxels --- src/voxel/world/chunk.rs | 143 +++++++++++++++++++++++++++------------ 1 file changed, 98 insertions(+), 45 deletions(-) diff --git a/src/voxel/world/chunk.rs b/src/voxel/world/chunk.rs index b6a1017..031aef9 100644 --- a/src/voxel/world/chunk.rs +++ b/src/voxel/world/chunk.rs @@ -1,3 +1,6 @@ +use ndarray::{s, Array3}; +use wgpu::naga::FastHashSet; + use crate::math; use super::{GenerationSettings, Voxel}; @@ -11,8 +14,9 @@ pub struct ChunkSettings { #[derive(Debug)] pub struct Chunk { settings: ChunkSettings, + genned_blocks: FastHashSet<(usize, usize, usize)>, noise: Vec, - blocks: Vec>, + blocks: Array3, } impl Chunk { @@ -42,13 +46,21 @@ impl Chunk { .generate() .0; - let num_blocks = dims.x * dims.y * dims.z; - let blocks = vec![vec![]; num_blocks as usize]; + let genned_blocks = FastHashSet::default(); + + let shape = chunk_settings.dimensions * chunk_settings.block_dimensions; + let num_voxels = shape.x * shape.y * shape.z; + let blocks = Array3::from_shape_vec( + (shape.x as usize, shape.y as usize, shape.z as usize), + vec![Voxel::Empty; num_voxels as usize], + ) + .unwrap(); Self { settings: chunk_settings, noise, blocks, + genned_blocks, } } @@ -57,57 +69,84 @@ impl Chunk { region_start: glam::UVec3, region_dims: glam::UVec3, ) -> Vec { - let mut voxels = vec![]; - let start = region_start; let end = region_start + region_dims; let dims = self.settings.dimensions * self.settings.block_dimensions; assert!(end.x <= dims.x && end.y <= dims.y && end.z <= dims.z); - for z in (start.z)..(end.z) { - for y in (start.y)..(end.y) { - for x in (start.x)..(end.x) { - voxels.push(self.get_voxel(glam::uvec3(x, y, z))); + // Check that all the blocks needed are generated and generated them if needed + // TODO: Don't hardcode this division!! + let start_block = start / 8; + let end_block = end / 8; + for z in start_block.z..(end_block.z) { + for y in (start_block.y)..(end_block.y) { + for x in (start_block.x)..(end_block.x) { + if !self + .genned_blocks + .contains(&(x as usize, y as usize, z as usize)) + { + self.gen_block(glam::uvec3(x, y, z)); + } } } } - voxels + // + let region = self + .blocks + .slice(s![ + (start.x as usize)..(end.x as usize), + (start.y as usize)..(end.y as usize), + (start.z as usize)..(end.z as usize) + ]) + .to_owned() + .into_raw_vec(); + // dbg!(®ion); + region } - pub fn get_voxel(&mut self, pos: glam::UVec3) -> Voxel { - let dims = self.settings.dimensions * self.settings.block_dimensions; - debug_assert!(pos.x < dims.x && pos.y < dims.y && pos.z < dims.z); + // pub fn get_voxel(&mut self, pos: glam::UVec3) -> Voxel { + // let dims = self.settings.dimensions * self.settings.block_dimensions; + // debug_assert!(pos.x < dims.x && pos.y < dims.y && pos.z < dims.z); - let block_pos = pos / self.settings.block_dimensions; - let block_idx = math::to_1d_index(block_pos, self.settings.dimensions); - let mut block = &self.blocks[block_idx]; - if block.is_empty() { - self.gen_block(block_pos, block_idx); - block = &self.blocks[block_idx] - } + // let block_pos = pos / self.settings.block_dimensions; + // let block_idx = math::to_1d_index(block_pos, self.settings.dimensions); + // let mut block = &self.blocks[block_idx]; + // if block.is_empty() { + // self.gen_block(block_pos, block_idx); + // block = &self.blocks[block_idx] + // } - let local_pos = pos % self.settings.block_dimensions; - let local_idx = math::to_1d_index(local_pos, self.settings.block_dimensions); - block[local_idx] - } + // let local_pos = pos % self.settings.block_dimensions; + // let local_idx = math::to_1d_index(local_pos, self.settings.block_dimensions); + // block[local_idx] + // } pub fn get_block(&mut self, pos: glam::UVec3) -> Vec { let dims = self.settings.dimensions; assert!(pos.x < dims.x && pos.y < dims.y && pos.z < dims.z); - let block_idx = math::to_1d_index(pos, dims); - let mut block = &self.blocks[block_idx]; - if block.is_empty() { - self.gen_block(pos, block_idx); - block = &self.blocks[block_idx] + let gen_key = &(pos.x as usize, pos.y as usize, pos.z as usize); + if !self.genned_blocks.contains(gen_key) { + self.gen_block(pos); } - block.to_owned() + let block_dims = self.settings.block_dimensions; + let start = pos * block_dims; + let end = start + block_dims; + let region = self + .blocks + .slice(s![ + (start.x as usize)..(end.x as usize), + (start.y as usize)..(end.y as usize), + (start.z as usize)..(end.z as usize) + ]) + .to_owned() + .into_raw_vec(); + region } - pub fn gen_block(&mut self, block_pos: glam::UVec3, block_idx: usize) { - let block = &mut self.blocks[block_idx]; + pub fn gen_block(&mut self, block_pos: glam::UVec3) { let noise_dims = self.settings.dimensions + glam::uvec3(1, 1, 1); // Extract relevant noise values from the chunk @@ -126,32 +165,46 @@ impl Chunk { } // If all the corners are negative, then all the interpolated values - // will be negative too. In that case we can just fill with empty. - if block_sign == -8.0 { - block.resize(512, Voxel::Empty); - } else { + // will be negative too. The chunk voxels are initialised as empty already + // so we only need to modify them if we have at least one positive corner + if block_sign != -8.0 { let mut vals = [0.0f32; 512]; math::tri_lerp_block(&noise_vals, &[8, 8, 8], &mut vals); + let block_dims = self.settings.block_dimensions; + let start = block_pos * block_dims; + let end = start + block_dims; + let mut block = self.blocks.slice_mut(s![ + (start.x as usize)..(end.x as usize), + (start.y as usize)..(end.y as usize), + (start.z as usize)..(end.z as usize) + ]); + // TODO: Better voxel colours - let mut idx = 0; - for z in 0..8 { - for y in 0..8 { - for x in 0..8 { - let val = vals[idx]; - idx += 1; + let mut val_idx = 0; + for z in 0..block_dims.z { + for y in 0..block_dims.y { + for x in 0..block_dims.x { + let val = vals[val_idx]; + val_idx += 1; if val > 0.0 { let r = ((x + 1) * 32 - 1) as u8; let g = ((y + 1) * 32 - 1) as u8; let b = ((z + 1) * 32 - 1) as u8; - block.push(Voxel::Color(r, g, b)); - } else { - block.push(Voxel::Empty); + let block_idx = [z as usize, y as usize, x as usize]; + block[block_idx] = Voxel::Color(r, g, b); } } } } } + + let key = ( + block_pos.x as usize, + block_pos.y as usize, + block_pos.z as usize, + ); + self.genned_blocks.insert(key); } } -- 2.40.1 From 7c9696c6f164f2e7ea395920c77451b2108588b6 Mon Sep 17 00:00:00 2001 From: Jarrod Doyle Date: Sat, 13 Apr 2024 21:29:03 +0100 Subject: [PATCH 5/5] Replace usage of hardcoded block dimensions --- src/voxel/world/chunk.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/voxel/world/chunk.rs b/src/voxel/world/chunk.rs index 031aef9..d55446f 100644 --- a/src/voxel/world/chunk.rs +++ b/src/voxel/world/chunk.rs @@ -75,9 +75,9 @@ impl Chunk { assert!(end.x <= dims.x && end.y <= dims.y && end.z <= dims.z); // Check that all the blocks needed are generated and generated them if needed - // TODO: Don't hardcode this division!! - let start_block = start / 8; - let end_block = end / 8; + let block_dims = self.settings.block_dimensions; + let start_block = start / block_dims; + let end_block = end / block_dims; for z in start_block.z..(end_block.z) { for y in (start_block.y)..(end_block.y) { for x in (start_block.x)..(end_block.x) { @@ -168,10 +168,15 @@ impl Chunk { // will be negative too. The chunk voxels are initialised as empty already // so we only need to modify them if we have at least one positive corner if block_sign != -8.0 { - let mut vals = [0.0f32; 512]; - math::tri_lerp_block(&noise_vals, &[8, 8, 8], &mut vals); - let block_dims = self.settings.block_dimensions; + + let mut vals = [0.0f32; 512]; + math::tri_lerp_block( + &noise_vals, + &[block_dims.x, block_dims.y, block_dims.z], + &mut vals, + ); + let start = block_pos * block_dims; let end = start + block_dims; let mut block = self.blocks.slice_mut(s![ -- 2.40.1