88 lines
3 KiB
Rust
88 lines
3 KiB
Rust
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
//! 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]
|
|
#[expect(
|
|
clippy::cast_possible_wrap,
|
|
clippy::cast_possible_truncation,
|
|
reason = "chunk and voxel coordinates stay within the ranges these casts assume"
|
|
)]
|
|
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
|
|
}
|
|
}
|