feat(shared): define base voxel world types and procedural generation

This commit is contained in:
Serkyo 2026-05-16 19:03:29 +02:00
parent 38077c980d
commit 4724e91281
5 changed files with 263 additions and 18 deletions

101
Cargo.lock generated
View file

@ -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"

View file

@ -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"] }

View file

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

View file

@ -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;

View file

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