275 lines
8.3 KiB
Rust
275 lines
8.3 KiB
Rust
use std::{
|
||
sync::atomic::{AtomicU64, Ordering},
|
||
time,
|
||
};
|
||
|
||
use enum_dispatch::enum_dispatch;
|
||
use legacy_rand::{LegacyRand, LegacySplitter};
|
||
use xoroshiro128::{Xoroshiro, XoroshiroSplitter};
|
||
|
||
mod gaussian;
|
||
pub mod legacy_rand;
|
||
pub mod xoroshiro128;
|
||
|
||
/// Global seed uniquifier used to generate unique seeds based on time.
|
||
///
|
||
/// This value is atomically updated each time a seed is requested to ensure
|
||
/// that seeds generated at the same time are still unique.
|
||
static SEED_UNIQUIFIER: AtomicU64 = AtomicU64::new(8682522807148012u64);
|
||
|
||
/// Generates a new random seed based on the current time and a uniquifier.
|
||
///
|
||
/// The seed is generated by combining:
|
||
/// - An atomic counter multiplied by a large constant.
|
||
/// - The current system time in nanoseconds.
|
||
///
|
||
/// This ensures that seeds are both time-dependent and unique across calls.
|
||
///
|
||
/// # Returns
|
||
/// A 64-bit seed value suitable for initializing random number generators.
|
||
pub fn get_seed() -> u64 {
|
||
let seed = SEED_UNIQUIFIER
|
||
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |val| {
|
||
Some(val.wrapping_mul(1181783497276652981u64))
|
||
})
|
||
.unwrap();
|
||
|
||
let nanos = time::SystemTime::now()
|
||
.duration_since(time::SystemTime::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_nanos();
|
||
|
||
let nano_upper = (nanos >> 8) as u64;
|
||
let nano_lower = nanos as u64;
|
||
seed ^ nano_upper ^ nano_lower
|
||
}
|
||
|
||
/// Unified random number generator enum that can represent different RNG implementations.
|
||
///
|
||
/// This enum allows for dynamic dispatch between different RNG implementations
|
||
/// while maintaining performance through `enum_dispatch`.
|
||
#[enum_dispatch(RandomImpl)]
|
||
pub enum RandomGenerator {
|
||
/// Xoroshiro128+ random number generator (modern, fast implementation).
|
||
Xoroshiro(Xoroshiro),
|
||
/// Legacy random number generator (compatible with older Minecraft versions).
|
||
Legacy(LegacyRand),
|
||
}
|
||
|
||
/// Unified random number deriver enum for creating child RNGs.
|
||
///
|
||
/// Derivers are used to create new independent random generators from a parent seed,
|
||
/// which is essential for deterministic world generation where different features
|
||
/// need their own RNGs.
|
||
#[enum_dispatch(RandomDeriverImpl)]
|
||
pub enum RandomDeriver {
|
||
/// Xoroshiro splitter implementation.
|
||
Xoroshiro(XoroshiroSplitter),
|
||
/// Legacy splitter implementation.
|
||
Legacy(LegacySplitter),
|
||
}
|
||
|
||
// TODO: Write unit test for this
|
||
#[macro_export]
|
||
macro_rules! population_seed_fn {
|
||
() => {
|
||
/// Generates a population seed for structure placement.
|
||
///
|
||
/// Population seeds are used to determine the placement of structures
|
||
/// like villages, temples, and other features. They combine the world seed
|
||
/// with block coordinates to create a unique seed for each chunk.
|
||
///
|
||
/// # Arguments
|
||
/// - `world_seed` – The base world seed.
|
||
/// - `block_x` – The X block coordinate.
|
||
/// - `block_z` – The Z block coordinate.
|
||
///
|
||
/// # Returns
|
||
/// A population seed for the given location.
|
||
pub fn get_population_seed(world_seed: u64, block_x: i32, block_z: i32) -> u64 {
|
||
let mut rand = Self::from_seed(world_seed);
|
||
let l = rand.next_i64() | 1;
|
||
let m = rand.next_i64() | 1;
|
||
let base = (block_x as i64)
|
||
.wrapping_mul(l)
|
||
.wrapping_add((block_z as i64).wrapping_mul(m));
|
||
(base as u64) ^ world_seed
|
||
}
|
||
};
|
||
}
|
||
|
||
/// Generates a decorator seed for feature placement.
|
||
///
|
||
/// Decorator seeds are used for placing individual features like trees,
|
||
/// flowers, and ores within a chunk.
|
||
///
|
||
/// # Arguments
|
||
/// - `population_seed` – The base population seed for the area.
|
||
/// - `index` – The index of the decorator.
|
||
/// - `step` – The decoration step number.
|
||
///
|
||
/// # Returns
|
||
/// A decorator seed for the given parameters.
|
||
// TODO: Write unit test for this
|
||
#[inline]
|
||
#[must_use]
|
||
pub const fn get_decorator_seed(population_seed: u64, index: u64, step: u64) -> u64 {
|
||
population_seed
|
||
.wrapping_add(index)
|
||
.wrapping_add(10_000u64.wrapping_mul(step))
|
||
}
|
||
|
||
/// Generates a region seed for large-scale feature placement.
|
||
///
|
||
/// Region seeds are used for features that span multiple chunks, such as
|
||
/// slime chunks or specific biome placements.
|
||
///
|
||
/// # Arguments
|
||
/// - `world_seed` – The base world seed.
|
||
/// - `region_x` – The X region coordinate.
|
||
/// - `region_z` – The Z region coordinate.
|
||
/// - `salt` – A salt value to make the seed unique for specific features.
|
||
///
|
||
/// # Returns
|
||
/// A region seed for the given location.
|
||
#[inline]
|
||
#[must_use]
|
||
pub fn get_region_seed(world_seed: u64, region_x: i32, region_z: i32, salt: u32) -> u64 {
|
||
let x_part = i64::from(region_x).wrapping_mul(341873128712) as u64;
|
||
let z_part = i64::from(region_z).wrapping_mul(132897987541) as u64;
|
||
|
||
world_seed
|
||
.wrapping_add(x_part)
|
||
.wrapping_add(z_part)
|
||
.wrapping_add(i64::from(salt) as u64)
|
||
}
|
||
|
||
/// Generates a carver seed for cave and ravine generation.
|
||
///
|
||
/// Carver seeds are used for terrain carving features like caves and ravines.
|
||
///
|
||
/// # Arguments
|
||
/// - `world_seed` – The base world seed (plus carver index).
|
||
/// - `chunk_x` – The X chunk coordinate.
|
||
/// - `chunk_z` – The Z chunk coordinate.
|
||
///
|
||
/// # Returns
|
||
/// A carver seed for the given chunk.
|
||
#[inline]
|
||
#[must_use]
|
||
pub fn get_carver_seed(world_seed: u64, chunk_x: i32, chunk_z: i32) -> u64 {
|
||
let mut random = LegacyRand::from_seed(world_seed);
|
||
let l = random.next_i64() | 1;
|
||
let m = random.next_i64() | 1;
|
||
((chunk_x as i64)
|
||
.wrapping_mul(l)
|
||
.wrapping_add((chunk_z as i64).wrapping_mul(m)) as u64)
|
||
^ world_seed
|
||
}
|
||
|
||
#[enum_dispatch]
|
||
#[expect(clippy::return_self_not_must_use)]
|
||
pub trait RandomImpl {
|
||
fn split(&mut self) -> Self;
|
||
|
||
fn next_splitter(&mut self) -> RandomDeriver;
|
||
|
||
fn next_i32(&mut self) -> i32;
|
||
|
||
fn next_bounded_i32(&mut self, bound: i32) -> i32;
|
||
|
||
fn next_inbetween_i32(&mut self, min: i32, max: i32) -> i32 {
|
||
self.next_bounded_i32(max - min + 1) + min
|
||
}
|
||
|
||
fn next_i64(&mut self) -> i64;
|
||
|
||
fn next_bool(&mut self) -> bool;
|
||
|
||
fn next_f32(&mut self) -> f32;
|
||
|
||
fn next_f64(&mut self) -> f64;
|
||
|
||
fn next_gaussian(&mut self) -> f64;
|
||
|
||
#[allow(clippy::suboptimal_flops)]
|
||
fn next_triangular(&mut self, mode: f64, deviation: f64) -> f64 {
|
||
mode + deviation * (self.next_f64() - self.next_f64())
|
||
}
|
||
|
||
fn skip(&mut self, count: i32) {
|
||
for _ in 0..count {
|
||
self.next_i64();
|
||
}
|
||
}
|
||
|
||
fn next_inbetween_i32_exclusive(&mut self, min: i32, max: i32) -> i32 {
|
||
min + self.next_bounded_i32(max - min)
|
||
}
|
||
}
|
||
|
||
#[enum_dispatch]
|
||
pub trait RandomDeriverImpl {
|
||
fn split_string(&self, seed: &str) -> RandomGenerator;
|
||
|
||
fn split_u64(&self, seed: u64) -> RandomGenerator;
|
||
|
||
fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator;
|
||
}
|
||
|
||
/// Hashes a block position into a 64-bit value for use in RNG seeding.
|
||
///
|
||
/// This hash function is designed to produce well-distributed values for
|
||
/// use as seeds for position-dependent random generation.
|
||
///
|
||
/// # Arguments
|
||
/// - `x` – The X coordinate.
|
||
/// - `y` – The Y coordinate.
|
||
/// - `z` – The Z coordinate.
|
||
///
|
||
/// # Returns
|
||
/// A 64-bit hash value.
|
||
#[must_use]
|
||
pub const fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 {
|
||
let l =
|
||
((x.wrapping_mul(3129871)) as i64) ^ ((z as i64).wrapping_mul(116129781i64)) ^ (y as i64);
|
||
|
||
let l = l
|
||
.wrapping_mul(l)
|
||
.wrapping_mul(42317861i64)
|
||
.wrapping_add(l.wrapping_mul(11i64));
|
||
|
||
l >> 16
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use crate::random::get_region_seed;
|
||
|
||
use super::hash_block_pos;
|
||
|
||
#[test]
|
||
fn region_seed() {
|
||
let seed = get_region_seed(12345612, 1, 1, 14357620);
|
||
assert_eq!(seed, 474797819485);
|
||
}
|
||
|
||
#[test]
|
||
fn block_position_hash() {
|
||
let values: [((i32, i32, i32), i64); 8] = [
|
||
((0, 0, 0), 0),
|
||
((1, 1, 1), 60311958971344),
|
||
((4, 4, 4), 120566413180880),
|
||
((25, 25, 25), 111753446486209),
|
||
((676, 676, 676), 75210837988243),
|
||
((458329, 458329, 458329), -43764888250),
|
||
((-387008604, -387008604, -387008604), 8437923733503),
|
||
((176771161, 176771161, 176771161), 18421337580760),
|
||
];
|
||
|
||
for ((x, y, z), value) in values {
|
||
assert_eq!(hash_block_pos(x, y, z), value);
|
||
}
|
||
}
|
||
}
|