From 4724e91281772c8ff5f0374bc2ae3ba885cd8880 Mon Sep 17 00:00:00 2001 From: Serkyo Date: Sat, 16 May 2026 19:03:29 +0200 Subject: [PATCH] feat(shared): define base voxel world types and procedural generation --- Cargo.lock | 101 ++++++++++++++++++++++++++++++++- crates/shared/Cargo.toml | 5 ++ crates/shared/src/generator.rs | 81 ++++++++++++++++++++++++++ crates/shared/src/lib.rs | 19 +------ crates/shared/src/world.rs | 75 ++++++++++++++++++++++++ 5 files changed, 263 insertions(+), 18 deletions(-) create mode 100644 crates/shared/src/generator.rs create mode 100644 crates/shared/src/world.rs diff --git a/Cargo.lock b/Cargo.lock index 1e8dce7..b188fd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "bitflags" version = "1.3.2" @@ -232,6 +238,8 @@ dependencies = [ "ash-window", "raw-window-handle", "renderer", + "serde_json", + "shared", "tracing", "tracing-subscriber", "winit", @@ -381,6 +389,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -460,6 +474,12 @@ dependencies = [ "wasip2", ] +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + [[package]] name = "glam" version = "0.29.3" @@ -504,6 +524,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "jni" version = "0.22.4" @@ -699,6 +725,17 @@ dependencies = [ "jni-sys 0.3.1", ] +[[package]] +name = "noise" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6da45c8333f2e152fc665d78a380be060eb84fad8ca4c9f7ac8ca29216cff0cc" +dependencies = [ + "num-traits", + "rand", + "rand_xorshift", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -708,6 +745,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_enum" version = "0.7.6" @@ -1103,6 +1149,30 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -1163,7 +1233,7 @@ dependencies = [ "ash", "ash-window", "bytemuck", - "glam", + "glam 0.29.3", "gpu-allocator", "raw-window-handle", "thiserror 2.0.18", @@ -1256,6 +1326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -1278,10 +1349,25 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "server" version = "0.1.0" dependencies = [ + "serde_json", + "shared", "tracing", "tracing-subscriber", ] @@ -1298,6 +1384,13 @@ dependencies = [ [[package]] name = "shared" version = "0.1.0" +dependencies = [ + "bytemuck", + "fastrand", + "glam 0.27.0", + "noise", + "serde", +] [[package]] name = "shlex" @@ -2135,3 +2228,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 9733483..3f90a17 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -7,3 +7,8 @@ edition = "2024" workspace = true [dependencies] +bytemuck = { version = "1.15", features = ["derive"] } +fastrand = "2.4.1" +glam = "0.27" +noise = "0.9" +serde = { version = "1.0", features = ["derive"] } diff --git a/crates/shared/src/generator.rs b/crates/shared/src/generator.rs new file mode 100644 index 0000000..64aa381 --- /dev/null +++ b/crates/shared/src/generator.rs @@ -0,0 +1,81 @@ +//! Procedural generation logic for the voxel world. + +use noise::{NoiseFn, Perlin}; +use serde::{Deserialize, Serialize}; + +use crate::world::{BlockId, CHUNK_SIZE, Chunk, ChunkPos}; + +/// Configuration parameters for deterministic world generation. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct WorldGenConfig { + /// The base height around which terrain features are generated. + pub base_height: u32, + /// The scale factor applied to the noise coordinates. Smaller values create wider features. + pub noise_scale: f64, + /// The block identifier used for the top layer of the terrain. + pub surface_block: BlockId, + /// The block identifier used for the layers immediately below the surface. + pub subsurface_block: BlockId, + /// The block identifier used for deep underground layers. + pub stone_block: BlockId, +} + +/// A deterministic terrain generator that produces voxel chunks. +pub struct VoxelGenerator { + /// The configuration parameters guiding the generation. + pub config: WorldGenConfig, + noise: Perlin, +} + +impl VoxelGenerator { + /// Initializes a new voxel generator with the specified configuration and seed. + #[must_use] + pub fn new(config: WorldGenConfig, seed: u32) -> Self { + Self { + config, + noise: Perlin::new(seed), + } + } + + /// Generates a complete voxel chunk for the specified position. + #[must_use] + #[allow(clippy::cast_precision_loss, clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + pub fn generate_chunk(&self, pos: ChunkPos) -> Chunk { + let mut chunk = Chunk::default(); + + for x in 0..CHUNK_SIZE { + for z in 0..CHUNK_SIZE { + let global_x = (pos.x * CHUNK_SIZE as i32) + x as i32; + let global_z = (pos.z * CHUNK_SIZE as i32) + z as i32; + + let noise_val = self.noise.get([ + f64::from(global_x) * self.config.noise_scale, + f64::from(global_z) * self.config.noise_scale, + ]); + + let amplitude = 15.0; + + let target_height = self.config.base_height as i32 + (noise_val * amplitude) as i32; + + for y in 0..CHUNK_SIZE { + let global_y = (pos.y * CHUNK_SIZE as i32) + y as i32; + + let block = if global_y > target_height { + BlockId::AIR + } else if global_y == target_height { + self.config.surface_block + } else if global_y > target_height - 3 { + self.config.subsurface_block + } else { + self.config.stone_block + }; + + if block != BlockId::AIR { + chunk.set(x, y, z, block); + } + } + } + } + chunk + } +} \ No newline at end of file diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 7cb28cf..aa1d3e3 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -3,20 +3,5 @@ //! This crate contains data structures and constants that are used by both //! the client and the server. -/// Adds two numbers together. -/// -/// This is a placeholder function for the initial crate setup. -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod world; +pub mod generator; \ No newline at end of file diff --git a/crates/shared/src/world.rs b/crates/shared/src/world.rs new file mode 100644 index 0000000..cf1a500 --- /dev/null +++ b/crates/shared/src/world.rs @@ -0,0 +1,75 @@ +//! Core data structures representing the voxel world. + +use bytemuck::{Pod, Zeroable}; +use serde::{Deserialize, Serialize}; + +/// The size of a chunk along one axis in blocks. +pub const CHUNK_SIZE: usize = 32; +/// The total number of blocks within a single chunk. +pub const CHUNK_VOLUME: usize = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; + +/// A unique identifier representing a type of block in the world. +#[repr(transparent)] +#[derive( + Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Pod, Zeroable, +)] +pub struct BlockId(pub u16); + +impl BlockId { + /// The block identifier representing empty space. + pub const AIR: BlockId = BlockId(0); +} + +/// A spatial volume containing voxel data. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Chunk { + /// The array of blocks contained within the chunk. + pub blocks: Box<[BlockId]>, +} + +impl Chunk { + /// Calculates the one-dimensional index for a three-dimensional block coordinate. + #[inline] + #[must_use] + pub const fn index(x: usize, y: usize, z: usize) -> usize { + (x * CHUNK_SIZE * CHUNK_SIZE) + (z * CHUNK_SIZE) + y + } + + /// Retrieves the block identifier at the specified coordinates. + #[must_use] + pub fn get(&self, x: usize, y: usize, z: usize) -> BlockId { + self.blocks[Self::index(x, y, z)] + } + + /// Sets the block identifier at the specified coordinates. + pub fn set(&mut self, x: usize, y: usize, z: usize, block: BlockId) { + self.blocks[Self::index(x, y, z)] = block; + } +} + +impl Default for Chunk { + fn default() -> Self { + Self { + blocks: vec![BlockId::AIR; CHUNK_VOLUME].into_boxed_slice(), + } + } +} + +/// The three-dimensional spatial coordinates of a chunk in the world. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ChunkPos { + /// The X coordinate of the chunk. + pub x: i32, + /// The Y coordinate of the chunk. + pub y: i32, + /// The Z coordinate of the chunk. + pub z: i32, +} + +impl ChunkPos { + /// Initializes a new chunk position. + #[must_use] + pub fn new(x: i32, y: i32, z: i32) -> Self { + Self { x, y, z } + } +} \ No newline at end of file