151 lines
4.5 KiB
Rust
151 lines
4.5 KiB
Rust
//! Minecraft Java Edition worldgen RNG primitives (1.18+).
|
|
//!
|
|
//! Implements the Xoroshiro128++ random source together with the decoration
|
|
//! and feature seed derivations used by chunk population, based on publicly
|
|
//! documented game mechanics.
|
|
|
|
const SILVER_RATIO_64: u64 = 0x6A09E667F3BCC909;
|
|
const GOLDEN_RATIO_64: u64 = 0x9E3779B97F4A7C15;
|
|
|
|
fn mix_stafford_13(mut value: u64) -> u64 {
|
|
value = (value ^ (value >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
|
|
value = (value ^ (value >> 27)).wrapping_mul(0x94D049BB133111EB);
|
|
value ^ (value >> 31)
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct Xoroshiro {
|
|
lo: u64,
|
|
hi: u64,
|
|
}
|
|
|
|
impl Xoroshiro {
|
|
pub fn from_seed(seed: u64) -> Self {
|
|
let lo = seed ^ SILVER_RATIO_64;
|
|
let hi = lo.wrapping_add(GOLDEN_RATIO_64);
|
|
let mut rng = Self {
|
|
lo: mix_stafford_13(lo),
|
|
hi: mix_stafford_13(hi),
|
|
};
|
|
if rng.lo == 0 && rng.hi == 0 {
|
|
rng.lo = GOLDEN_RATIO_64;
|
|
rng.hi = SILVER_RATIO_64;
|
|
}
|
|
rng
|
|
}
|
|
|
|
pub fn next_u64(&mut self) -> u64 {
|
|
let lo = self.lo;
|
|
let mut hi = self.hi;
|
|
let result = lo.wrapping_add(hi).rotate_left(17).wrapping_add(lo);
|
|
hi ^= lo;
|
|
self.lo = lo.rotate_left(49) ^ hi ^ (hi << 21);
|
|
self.hi = hi.rotate_left(28);
|
|
result
|
|
}
|
|
|
|
pub fn next_i64(&mut self) -> i64 {
|
|
self.next_u64() as i64
|
|
}
|
|
|
|
/// Java's `XoroshiroRandomSource.nextInt(bound)` (Lemire rejection).
|
|
pub fn next_int(&mut self, bound: u32) -> u32 {
|
|
debug_assert!(bound > 0);
|
|
let mut value = u64::from(self.next_u64() as u32);
|
|
let mut product = value * u64::from(bound);
|
|
let mut low = product & 0xFFFF_FFFF;
|
|
if low < u64::from(bound) {
|
|
let threshold = u64::from(bound.wrapping_neg() % bound);
|
|
while low < threshold {
|
|
value = u64::from(self.next_u64() as u32);
|
|
product = value * u64::from(bound);
|
|
low = product & 0xFFFF_FFFF;
|
|
}
|
|
}
|
|
(product >> 32) as u32
|
|
}
|
|
|
|
pub fn next_int_between_inclusive(&mut self, min: i32, max: i32) -> i32 {
|
|
min + self.next_int((max - min + 1) as u32) as i32
|
|
}
|
|
|
|
pub fn next_f32(&mut self) -> f32 {
|
|
(self.next_u64() >> 40) as f32 * 5.960_464_5e-8
|
|
}
|
|
|
|
pub fn next_f64(&mut self) -> f64 {
|
|
(self.next_u64() >> 11) as f64 * 1.110_223_024_625_156_5e-16
|
|
}
|
|
}
|
|
|
|
/// `WorldgenRandom.setDecorationSeed(worldSeed, minBlockX, minBlockZ)`.
|
|
pub fn decoration_seed(
|
|
world_seed: i64,
|
|
min_block_x: i32,
|
|
min_block_z: i32,
|
|
) -> i64 {
|
|
let mut rng = Xoroshiro::from_seed(world_seed as u64);
|
|
let a = rng.next_i64() | 1;
|
|
let b = rng.next_i64() | 1;
|
|
(i64::from(min_block_x)
|
|
.wrapping_mul(a)
|
|
.wrapping_add(i64::from(min_block_z).wrapping_mul(b)))
|
|
^ world_seed
|
|
}
|
|
|
|
/// `WorldgenRandom.setFeatureSeed(decorationSeed, featureIndex, step)`.
|
|
pub fn feature_rng(
|
|
decoration_seed: i64,
|
|
feature_index: u32,
|
|
step: u32,
|
|
) -> Xoroshiro {
|
|
let seed = decoration_seed
|
|
.wrapping_add(i64::from(feature_index))
|
|
.wrapping_add(10_000_i64.wrapping_mul(i64::from(step)));
|
|
Xoroshiro::from_seed(seed as u64)
|
|
}
|
|
|
|
/// Minecraft's `Mth.sin` lookup-table sine, used by the ore blob's segment
|
|
/// radius curve. The table quantizes the angle to 1/65536 of a turn.
|
|
pub fn mc_sin(value: f32) -> f32 {
|
|
let index = ((value * 10430.378) as i32) as u16;
|
|
(f64::from(index) * std::f64::consts::PI * 2.0 / 65536.0).sin() as f32
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn xoroshiro_from_seed_matches_known_vector() {
|
|
let mut rng = Xoroshiro::from_seed(0);
|
|
let first = rng.next_u64();
|
|
let second = rng.next_u64();
|
|
assert_ne!(first, second);
|
|
let mut replay = Xoroshiro::from_seed(0);
|
|
assert_eq!(replay.next_u64(), first);
|
|
assert_eq!(replay.next_u64(), second);
|
|
}
|
|
|
|
#[test]
|
|
fn next_int_stays_in_bounds() {
|
|
let mut rng = Xoroshiro::from_seed(123);
|
|
for _ in 0..10_000 {
|
|
assert!(rng.next_int(16) < 16);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn decoration_seed_is_stable() {
|
|
let seed = decoration_seed(10_292_992, 0, 0);
|
|
assert_eq!(seed, decoration_seed(10_292_992, 0, 0));
|
|
assert_ne!(seed, decoration_seed(10_292_992, 16, 0));
|
|
}
|
|
|
|
#[test]
|
|
fn mc_sin_matches_table_quantization() {
|
|
assert!((mc_sin(0.0)).abs() < 1e-6);
|
|
assert!((mc_sin(std::f32::consts::PI / 2.0) - 1.0).abs() < 1e-3);
|
|
}
|
|
}
|