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" 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..d55446f 100644 --- a/src/voxel/world/chunk.rs +++ b/src/voxel/world/chunk.rs @@ -1,38 +1,153 @@ +use ndarray::{s, Array3}; +use wgpu::naga::FastHashSet; + 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, + genned_blocks: FastHashSet<(usize, usize, usize)>, noise: Vec, - blocks: Vec>, + blocks: Array3, } 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 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, + } } - 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_region( + &mut self, + region_start: glam::UVec3, + region_dims: glam::UVec3, + ) -> 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); - let block_idx = math::to_1d_index(block_pos, chunk_dims); - let mut block = &self.blocks[block_idx]; - if block.is_empty() { - self.gen_block(block_pos, block_idx, chunk_dims); - block = &self.blocks[block_idx] + // Check that all the blocks needed are generated and generated them if needed + 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) { + if !self + .genned_blocks + .contains(&(x as usize, y as usize, z as usize)) + { + self.gen_block(glam::uvec3(x, y, z)); + } + } + } } - block.to_owned() + // + 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 gen_block(&mut self, block_pos: glam::UVec3, block_idx: usize, chunk_dims: glam::UVec3) { - let block = &mut self.blocks[block_idx]; - let noise_dims = chunk_dims + glam::uvec3(1, 1, 1); + // 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); + + 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); + } + + 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) { + let noise_dims = self.settings.dimensions + glam::uvec3(1, 1, 1); // Extract relevant noise values from the chunk let mut noise_vals = Vec::new(); @@ -50,32 +165,51 @@ 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 block_dims = self.settings.block_dimensions; + let mut vals = [0.0f32; 512]; - math::tri_lerp_block(&noise_vals, &[8, 8, 8], &mut vals); + 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![ + (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); } } diff --git a/src/voxel/world/manager.rs b/src/voxel/world/manager.rs index 9698ac9..f97cdf5 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,13 @@ impl WorldManager { } let chunk = self.chunks.get_mut(&chunk_pos).unwrap(); - chunk.get_block(local_pos, self.chunk_dims) + 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 { - // 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 {