chore: add more chunk tests

This commit is contained in:
Alexander Medvedev 2026-08-04 10:38:27 +02:00
parent e1117ce4a6
commit 9ececd9eca
44 changed files with 4429 additions and 542 deletions

14
Cargo.lock generated
View File

@ -1128,18 +1128,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "enum_dispatch"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@ -2812,7 +2800,6 @@ dependencies = [
"colored", "colored",
"crypto-bigint", "crypto-bigint",
"ecdsa", "ecdsa",
"enum_dispatch",
"md5", "md5",
"num-derive", "num-derive",
"num-traits", "num-traits",
@ -2843,7 +2830,6 @@ dependencies = [
"crossbeam", "crossbeam",
"crossfire", "crossfire",
"dashmap", "dashmap",
"enum_dispatch",
"flate2", "flate2",
"futures", "futures",
"itertools 0.15.0", "itertools 0.15.0",

View File

@ -133,14 +133,13 @@ crossfire = { version = "3.1.19", default-features = false, features = ["compat"
crypto-bigint = { version = "0.7.5", default-features = false } crypto-bigint = { version = "0.7.5", default-features = false }
dashmap = { version = "6.2", default-features = false } dashmap = { version = "6.2", default-features = false }
ecdsa = { version = "0.17.0", default-features = false, features = ["std"] } ecdsa = { version = "0.17.0", default-features = false, features = ["std"] }
enum_dispatch = { version = "0.3.13", default-features = false }
flate2 = { version = "1.1.9", default-features = false, features = ["rust_backend"] } flate2 = { version = "1.1.9", default-features = false, features = ["rust_backend"] }
heck = { version = "0.5", default-features = false } heck = { version = "0.5", default-features = false }
hmac = { version = "=0.13.0", default-features = false } hmac = { version = "=0.13.0", default-features = false }
indexmap = { version = "2.14", default-features = false, features = ["std"] } indexmap = { version = "2.14", default-features = false, features = ["std"] }
itertools = { version = "0.15.0", default-features = false, features = ["use_std"] } itertools = { version = "0.15.0", default-features = false, features = ["use_std"] }
libloading = { version = "0.9", default-features = false, features = ["std"] } libloading = { version = "0.9", default-features = false, features = ["std"] }
lru = { version = "0.18.1", default-features = false } lru = { version = "0.18.2", default-features = false }
lz4-java-wrc = { version = "0.2.0", default-features = false, features = ["lz4_flex"] } lz4-java-wrc = { version = "0.2.0", default-features = false, features = ["lz4_flex"] }
md5 = { version = "0.8", default-features = false } md5 = { version = "0.8", default-features = false }
num-bigint = { version = "0.5", default-features = false, features = ["std"] } num-bigint = { version = "0.5", default-features = false, features = ["std"] }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -321,18 +321,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "enum_dispatch"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.118",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@ -820,7 +808,6 @@ dependencies = [
"colored", "colored",
"crypto-bigint", "crypto-bigint",
"ecdsa", "ecdsa",
"enum_dispatch",
"md5", "md5",
"num-derive", "num-derive",
"num-traits", "num-traits",

View File

@ -344,8 +344,8 @@ impl ToTokens for MaterialConditionStruct {
} => { } => {
// Pre calc for speed :D // Pre calc for speed :D
let bytes = md5::compute(random_name.as_bytes()); let bytes = md5::compute(random_name.as_bytes());
let lo = u64::from_be_bytes(bytes[0..8].try_into().expect("incorrect length")); let lo = u64::from_le_bytes(bytes[0..8].try_into().expect("incorrect length"));
let hi = u64::from_be_bytes(bytes[8..16].try_into().expect("incorrect length")); let hi = u64::from_le_bytes(bytes[8..16].try_into().expect("incorrect length"));
tokens.extend(quote!( tokens.extend(quote!(
MaterialCondition::VerticalGradient(VerticalGradientMaterialCondition { MaterialCondition::VerticalGradient(VerticalGradientMaterialCondition {
random_lo: #lo, random_lo: #lo,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -21,18 +21,26 @@ pub struct VarInt(pub VarIntType);
impl VarInt { impl VarInt {
/// The maximum number of bytes a `VarInt` can occupy. /// The maximum number of bytes a `VarInt` can occupy.
const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(5).unwrap(); pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(5).unwrap();
#[must_use]
#[inline]
pub const fn new(value: VarIntType) -> Self {
Self(value)
}
/// Returns the exact number of bytes this `VarInt` will write when /// Returns the exact number of bytes this `VarInt` will write when
/// [`Encode::encode`] is called, assuming no error occurs. /// [`Encode::encode`] is called, assuming no error occurs.
#[must_use] #[must_use]
#[inline]
pub const fn written_size(&self) -> usize { pub const fn written_size(&self) -> usize {
match self.0 { match self.0 as u32 {
0 => 1, 0 => 1,
n => (31 - n.leading_zeros() as usize) / 7 + 1, n => (31 - n.leading_zeros() as usize) / 7 + 1,
} }
} }
#[inline]
pub fn encode(&self, write: &mut impl Write) -> Result<(), WritingError> { pub fn encode(&self, write: &mut impl Write) -> Result<(), WritingError> {
// Must cast to u32 to prevent infinite loops on negative i32s // Must cast to u32 to prevent infinite loops on negative i32s
let mut val = self.0 as u32; let mut val = self.0 as u32;
@ -47,6 +55,7 @@ impl VarInt {
} }
// TODO: Validate that the first byte will not overflow a i32 // TODO: Validate that the first byte will not overflow a i32
#[inline]
pub fn decode(read: &mut impl Read) -> Result<Self, ReadingError> { pub fn decode(read: &mut impl Read) -> Result<Self, ReadingError> {
let mut val = 0; let mut val = 0;
for i in 0..Self::MAX_SIZE.get() { for i in 0..Self::MAX_SIZE.get() {

View File

@ -15,13 +15,31 @@ pub type VarLongType = i64;
/** /**
* A variable-length long type used by the Minecraft network protocol. * A variable-length long type used by the Minecraft network protocol.
*/ */
#[derive(Debug, Clone, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct VarLong(pub VarLongType); pub struct VarLong(pub VarLongType);
impl VarLong { impl VarLong {
/// The maximum number of bytes a `VarLong` can occupy. /// The maximum number of bytes a `VarLong` can occupy.
const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(10).unwrap(); pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(10).unwrap();
#[must_use]
#[inline]
pub const fn new(value: VarLongType) -> Self {
Self(value)
}
/// Returns the exact number of bytes this `VarLong` will write when
/// [`Encode::encode`] is called, assuming no error occurs.
#[must_use]
#[inline]
pub const fn written_size(&self) -> usize {
match self.0 as u64 {
0 => 1,
n => (63 - n.leading_zeros() as usize) / 7 + 1,
}
}
#[inline]
pub fn encode(&self, write: &mut impl Write) -> Result<(), WritingError> { pub fn encode(&self, write: &mut impl Write) -> Result<(), WritingError> {
let mut val = self.0 as u64; let mut val = self.0 as u64;
@ -35,6 +53,7 @@ impl VarLong {
} }
// TODO: Validate that the first byte will not overflow a i64 // TODO: Validate that the first byte will not overflow a i64
#[inline]
pub fn decode(read: &mut impl Read) -> Result<Self, ReadingError> { pub fn decode(read: &mut impl Read) -> Result<Self, ReadingError> {
let mut val = 0; let mut val = 0;
for i in 0..Self::MAX_SIZE.get() { for i in 0..Self::MAX_SIZE.get() {

View File

@ -20,15 +20,26 @@ pub struct VarUInt(pub VarUIntType);
impl VarUInt { impl VarUInt {
/// The maximum number of bytes a `VarUInt` can occupy. /// The maximum number of bytes a `VarUInt` can occupy.
const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(5).unwrap(); pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(5).unwrap();
#[must_use]
#[inline]
pub const fn new(value: VarUIntType) -> Self {
Self(value)
}
/// Returns the exact number of bytes this `VarUInt` will write when /// Returns the exact number of bytes this `VarUInt` will write when
/// [`Encode::encode`] is called, assuming no error occurs. /// [`Encode::encode`] is called, assuming no error occurs.
#[must_use] #[must_use]
pub fn written_size(&self) -> usize { #[inline]
(32 - self.0.leading_zeros() as usize).max(1).div_ceil(7) pub const fn written_size(&self) -> usize {
match self.0 {
0 => 1,
n => (31 - n.leading_zeros() as usize) / 7 + 1,
}
} }
#[inline]
pub fn encode(&self, write: &mut impl Write) -> Result<(), WritingError> { pub fn encode(&self, write: &mut impl Write) -> Result<(), WritingError> {
let mut val = self.0; let mut val = self.0;
loop { loop {
@ -46,6 +57,7 @@ impl VarUInt {
} }
// TODO: Validate that the first byte will not overflow a i32 // TODO: Validate that the first byte will not overflow a i32
#[inline]
pub fn decode(read: &mut impl Read) -> Result<Self, ReadingError> { pub fn decode(read: &mut impl Read) -> Result<Self, ReadingError> {
let mut val = 0; let mut val = 0;
for i in 0..Self::MAX_SIZE.get() { for i in 0..Self::MAX_SIZE.get() {
@ -65,7 +77,7 @@ impl VarUInt {
for i in 0..Self::MAX_SIZE.get() { for i in 0..Self::MAX_SIZE.get() {
let byte = read.read_u8().await.map_err(|err| { let byte = read.read_u8().await.map_err(|err| {
if i == 0 && matches!(err.kind(), ErrorKind::UnexpectedEof) { if i == 0 && matches!(err.kind(), ErrorKind::UnexpectedEof) {
ReadingError::CleanEOF("VarInt".to_string()) ReadingError::CleanEOF("VarUInt".to_string())
} else { } else {
ReadingError::Incomplete(err.to_string()) ReadingError::Incomplete(err.to_string())
} }

View File

@ -20,18 +20,26 @@ pub struct VarULong(pub VarULongType);
impl VarULong { impl VarULong {
/// The maximum number of bytes a `VarULong` can occupy. /// The maximum number of bytes a `VarULong` can occupy.
const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(10).unwrap(); pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(10).unwrap();
/// Returns the exact number of bytes this `VarLong` will write when #[must_use]
#[inline]
pub const fn new(value: VarULongType) -> Self {
Self(value)
}
/// Returns the exact number of bytes this `VarULong` will write when
/// [`Encode::encode`] is called, assuming no error occurs. /// [`Encode::encode`] is called, assuming no error occurs.
#[must_use] #[must_use]
#[inline]
pub const fn written_size(&self) -> usize { pub const fn written_size(&self) -> usize {
match self.0 { match self.0 {
0 => 1, 0 => 1,
n => (31 - n.leading_zeros() as usize) / 7 + 1, n => (63 - n.leading_zeros() as usize) / 7 + 1,
} }
} }
#[inline]
pub fn encode(&self, write: &mut impl Write) -> Result<(), WritingError> { pub fn encode(&self, write: &mut impl Write) -> Result<(), WritingError> {
let mut x = self.0; let mut x = self.0;
loop { loop {
@ -48,6 +56,7 @@ impl VarULong {
} }
// TODO: Validate that the first byte will not overflow a i64 // TODO: Validate that the first byte will not overflow a i64
#[inline]
pub fn decode(read: &mut impl Read) -> Result<Self, ReadingError> { pub fn decode(read: &mut impl Read) -> Result<Self, ReadingError> {
let mut val = 0; let mut val = 0;
for i in 0..Self::MAX_SIZE.get() { for i in 0..Self::MAX_SIZE.get() {
@ -57,7 +66,7 @@ impl VarULong {
return Ok(Self(val)); return Ok(Self(val));
} }
} }
Err(ReadingError::TooLarge("VarLong".to_string())) Err(ReadingError::TooLarge("VarULong".to_string()))
} }
} }
macro_rules! gen_from { macro_rules! gen_from {

View File

@ -254,7 +254,7 @@ macro_rules! get_number_be {
fn $name(&mut self) -> Result<$type, ReadingError> { fn $name(&mut self) -> Result<$type, ReadingError> {
let mut buf = [0u8; std::mem::size_of::<$type>()]; let mut buf = [0u8; std::mem::size_of::<$type>()];
self.read_exact(&mut buf) self.read_exact(&mut buf)
.map_err(|err| ReadingError::Incomplete(err.to_string()))?; .map_err(|err| ReadingError::Incomplete(err.to_string().into()))?;
Ok(<$type>::from_be_bytes(buf)) Ok(<$type>::from_be_bytes(buf))
} }
}; };
@ -380,9 +380,7 @@ pub fn read_remaining_bytes(read: &mut impl Read, bound: usize) -> Result<Box<[u
.map_err(|err| ReadingError::Incomplete(err.to_string()))?; .map_err(|err| ReadingError::Incomplete(err.to_string()))?;
if return_buf.len() > bound { if return_buf.len() > bound {
return Err(ReadingError::TooLarge( return Err(ReadingError::TooLarge("Read remaining too long".into()));
"Read remaining too long".to_string(),
));
} }
Ok(return_buf.into_boxed_slice()) Ok(return_buf.into_boxed_slice())
@ -529,11 +527,20 @@ impl<W: Write> NetworkWriteExt for W {
bit_set: FixedBitSet, bit_set: FixedBitSet,
) -> Result<(), WritingError> { ) -> Result<(), WritingError> {
let new_length = bits.div_ceil(8); let new_length = bits.div_ceil(8);
let mut new_vec = vec![0u8; new_length];
let bytes_to_copy = std::cmp::min(bit_set.len(), new_length); let bytes_to_copy = std::cmp::min(bit_set.len(), new_length);
new_vec[..bytes_to_copy].copy_from_slice(&bit_set[..bytes_to_copy]); self.write_slice(&bit_set[..bytes_to_copy])?;
self.write_slice(&new_vec)?;
if new_length > bytes_to_copy {
const ZEROES: [u8; 64] = [0u8; 64];
let padding = new_length - bytes_to_copy;
let mut remaining = padding;
while remaining > 0 {
let chunk = remaining.min(ZEROES.len());
self.write_slice(&ZEROES[..chunk])?;
remaining -= chunk;
}
}
Ok(()) Ok(())
} }
@ -596,7 +603,7 @@ impl<W: Write> NetworkWriteExt for W {
list: &[G], list: &[G],
writer: impl Fn(&mut Self, &G) -> Result<(), WritingError>, writer: impl Fn(&mut Self, &G) -> Result<(), WritingError>,
) -> Result<(), WritingError> { ) -> Result<(), WritingError> {
self.write_var_int(&(list.len() as i32).into())?; self.write_var_int(&VarInt(list.len() as i32))?;
for data in list { for data in list {
writer(self, data)?; writer(self, data)?;

View File

@ -20,7 +20,6 @@ md5.workspace = true
quote = { workspace = true, optional = true } quote = { workspace = true, optional = true }
syn = { workspace = true, optional = true } syn = { workspace = true, optional = true }
proc-macro2 = { workspace = true, optional = true } proc-macro2 = { workspace = true, optional = true }
enum_dispatch.workspace = true
uuid.workspace = true uuid.workspace = true
tokio = { workspace = true, features = ["sync"] } tokio = { workspace = true, features = ["sync"] }

View File

@ -3,7 +3,6 @@ use std::{
time, time,
}; };
use enum_dispatch::enum_dispatch;
use legacy_rand::{LegacyRand, LegacySplitter}; use legacy_rand::{LegacyRand, LegacySplitter};
use xoroshiro128::{Xoroshiro, XoroshiroSplitter}; use xoroshiro128::{Xoroshiro, XoroshiroSplitter};
@ -12,21 +11,8 @@ pub mod legacy_rand;
pub mod xoroshiro128; pub mod xoroshiro128;
/// Global seed uniquifier used to generate unique seeds based on time. /// 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); 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 { pub fn get_seed() -> u64 {
let seed = SEED_UNIQUIFIER let seed = SEED_UNIQUIFIER
.try_update(Ordering::Relaxed, Ordering::Relaxed, |val| { .try_update(Ordering::Relaxed, Ordering::Relaxed, |val| {
@ -44,11 +30,6 @@ pub fn get_seed() -> u64 {
seed ^ nano_upper ^ nano_lower 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 { pub enum RandomGenerator {
/// Xoroshiro128+ random number generator (modern, fast implementation). /// Xoroshiro128+ random number generator (modern, fast implementation).
Xoroshiro(Xoroshiro), Xoroshiro(Xoroshiro),
@ -57,11 +38,6 @@ pub enum RandomGenerator {
} }
/// Unified random number deriver enum for creating child RNGs. /// 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 { pub enum RandomDeriver {
/// Xoroshiro splitter implementation. /// Xoroshiro splitter implementation.
Xoroshiro(XoroshiroSplitter), Xoroshiro(XoroshiroSplitter),
@ -191,7 +167,6 @@ pub fn get_carver_seed(world_seed: u64, chunk_x: i32, chunk_z: i32) -> u64 {
^ world_seed ^ world_seed
} }
#[enum_dispatch]
#[expect(clippy::return_self_not_must_use)] #[expect(clippy::return_self_not_must_use)]
pub trait RandomImpl { pub trait RandomImpl {
fn split(&mut self) -> Self; fn split(&mut self) -> Self;
@ -236,7 +211,88 @@ pub trait RandomImpl {
} }
} }
#[enum_dispatch] impl RandomImpl for RandomGenerator {
#[inline]
fn split(&mut self) -> Self {
match self {
Self::Xoroshiro(x) => Self::Xoroshiro(x.split()),
Self::Legacy(l) => Self::Legacy(l.split()),
}
}
#[inline]
fn next_splitter(&mut self) -> RandomDeriver {
match self {
Self::Xoroshiro(x) => RandomDeriver::Xoroshiro(x.next_splitter()),
Self::Legacy(l) => l.next_splitter(),
}
}
#[inline]
fn next_i32(&mut self) -> i32 {
match self {
Self::Xoroshiro(x) => x.next_i32(),
Self::Legacy(l) => l.next_i32(),
}
}
#[inline]
fn next_bounded_i32(&mut self, bound: i32) -> i32 {
match self {
Self::Xoroshiro(x) => x.next_bounded_i32(bound),
Self::Legacy(l) => l.next_bounded_i32(bound),
}
}
#[inline]
fn next_i64(&mut self) -> i64 {
match self {
Self::Xoroshiro(x) => x.next_i64(),
Self::Legacy(l) => l.next_i64(),
}
}
#[inline]
fn next_bool(&mut self) -> bool {
match self {
Self::Xoroshiro(x) => x.next_bool(),
Self::Legacy(l) => l.next_bool(),
}
}
#[inline]
fn next_f32(&mut self) -> f32 {
match self {
Self::Xoroshiro(x) => x.next_f32(),
Self::Legacy(l) => l.next_f32(),
}
}
#[inline]
fn next_f64(&mut self) -> f64 {
match self {
Self::Xoroshiro(x) => x.next_f64(),
Self::Legacy(l) => l.next_f64(),
}
}
#[inline]
fn next_gaussian(&mut self) -> f64 {
match self {
Self::Xoroshiro(x) => x.next_gaussian(),
Self::Legacy(l) => l.next_gaussian(),
}
}
#[inline]
fn skip(&mut self, count: i32) {
match self {
Self::Xoroshiro(x) => x.skip(count),
Self::Legacy(l) => l.skip(count),
}
}
}
pub trait RandomDeriverImpl { pub trait RandomDeriverImpl {
fn split_string(&self, seed: &str) -> RandomGenerator; fn split_string(&self, seed: &str) -> RandomGenerator;
@ -245,6 +301,32 @@ pub trait RandomDeriverImpl {
fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator; fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator;
} }
impl RandomDeriverImpl for RandomDeriver {
#[inline]
fn split_string(&self, seed: &str) -> RandomGenerator {
match self {
Self::Xoroshiro(x) => RandomGenerator::Xoroshiro(x.split_string(seed)),
Self::Legacy(l) => l.split_string(seed),
}
}
#[inline]
fn split_u64(&self, seed: u64) -> RandomGenerator {
match self {
Self::Xoroshiro(x) => x.split_u64(seed),
Self::Legacy(l) => l.split_u64(seed),
}
}
#[inline]
fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator {
match self {
Self::Xoroshiro(s) => RandomGenerator::Xoroshiro(s.split_pos(x, y, z)),
Self::Legacy(s) => s.split_pos(x, y, z),
}
}
}
/// Hashes a block position into a 64-bit value for use in RNG seeding. /// 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 /// This hash function is designed to produce well-distributed values for

View File

@ -46,7 +46,6 @@ ruzstd.workspace = true
xxhash-rust = { version = "0.8", features = ["xxh64"] } xxhash-rust = { version = "0.8", features = ["xxh64"] }
itertools.workspace = true itertools.workspace = true
enum_dispatch.workspace = true
rayon.workspace = true rayon.workspace = true
# thread_local.workspace = true # thread_local.workspace = true

View File

@ -1,7 +1,6 @@
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::cell::RefCell; use std::cell::RefCell;
use enum_dispatch::enum_dispatch;
use pumpkin_data::chunk::{Biome, BiomeTree, NETHER_BIOME_SOURCE, OVERWORLD_BIOME_SOURCE}; use pumpkin_data::chunk::{Biome, BiomeTree, NETHER_BIOME_SOURCE, OVERWORLD_BIOME_SOURCE};
use crate::generation::noise::router::multi_noise_sampler::MultiNoiseSampler; use crate::generation::noise::router::multi_noise_sampler::MultiNoiseSampler;
@ -14,7 +13,6 @@ thread_local! {
static LAST_RESULT_NODE: RefCell<Option<&'static BiomeTree>> = const {RefCell::new(None) }; static LAST_RESULT_NODE: RefCell<Option<&'static BiomeTree>> = const {RefCell::new(None) };
} }
#[enum_dispatch]
pub trait BiomeSupplier { pub trait BiomeSupplier {
fn biome(&self, x: i32, y: i32, z: i32, noise: &mut MultiNoiseSampler<'_>) -> &'static Biome; fn biome(&self, x: i32, y: i32, z: i32, noise: &mut MultiNoiseSampler<'_>) -> &'static Biome;
} }

View File

@ -17,6 +17,7 @@ impl Cylindrical {
} }
} }
#[inline]
pub fn changed_chunks( pub fn changed_chunks(
old: Self, old: Self,
new: Self, new: Self,
@ -35,50 +36,59 @@ impl Cylindrical {
(loading, unloading) (loading, unloading)
} }
#[allow(dead_code)] #[must_use]
const fn left(&self) -> i32 { #[inline]
pub const fn left(&self) -> i32 {
self.center.x - self.view_distance.get() as i32 - 1 self.center.x - self.view_distance.get() as i32 - 1
} }
#[allow(dead_code)] #[must_use]
const fn bottom(&self) -> i32 { #[inline]
pub const fn bottom(&self) -> i32 {
self.center.y - self.view_distance.get() as i32 - 1 self.center.y - self.view_distance.get() as i32 - 1
} }
#[allow(dead_code)] #[must_use]
const fn right(&self) -> i32 { #[inline]
pub const fn right(&self) -> i32 {
self.center.x + self.view_distance.get() as i32 + 1 self.center.x + self.view_distance.get() as i32 + 1
} }
#[allow(dead_code)] #[must_use]
const fn top(&self) -> i32 { #[inline]
pub const fn top(&self) -> i32 {
self.center.y + self.view_distance.get() as i32 + 1 self.center.y + self.view_distance.get() as i32 + 1
} }
#[must_use] #[must_use]
pub fn is_within_distance(&self, x: i32, z: i32) -> bool { #[inline]
if self.view_distance.get() == 1 { pub const fn is_within_distance(&self, x: i32, z: i32) -> bool {
let vd = self.view_distance.get() as i64;
if vd == 1 {
return false; return false;
} }
let rel_x = ((x - self.center.x).abs() as i64 - 2).max(0); let dx = (x - self.center.x).abs() as i64 - 2;
let rel_z = ((z - self.center.y).abs() as i64 - 2).max(0); let dz = (z - self.center.y).abs() as i64 - 2;
let rel_x = if dx > 0 { dx } else { 0 };
let rel_z = if dz > 0 { dz } else { 0 };
let hyp_sqr = rel_x * rel_x + rel_z * rel_z; let hyp_sqr = rel_x * rel_x + rel_z * rel_z;
//The view distance should be converted to i64 first because u8 * u8 can overflow hyp_sqr < vd * vd
hyp_sqr < (self.view_distance.get() as i64).pow(2)
} }
/// Returns a precomputed list of relative chunk offset pairs `(dx, dy)` for a given view distance. /// Returns a precomputed list of relative chunk offset pairs `(dx, dy)` for a given view distance.
#[must_use] #[must_use]
#[inline] #[inline]
pub fn get_offsets(view_distance: u8) -> &'static [(i8, i8)] { pub const fn get_offsets(view_distance: u8) -> &'static [(i8, i8)] {
let idx = let vd = view_distance as usize;
(view_distance as usize).min(pumpkin_data::chunk_view_lut::MAX_VIEW_DISTANCE as usize); let max = pumpkin_data::chunk_view_lut::MAX_VIEW_DISTANCE as usize;
let idx = if vd < max { vd } else { max };
pumpkin_data::chunk_view_lut::CHUNK_VIEW_LUT[idx] pumpkin_data::chunk_view_lut::CHUNK_VIEW_LUT[idx]
} }
/// Returns an iterator of all chunks within this cylinder using the precomputed LUT /// Returns an iterator of all chunks within this cylinder using the precomputed LUT
#[must_use] #[must_use]
#[inline]
pub fn all_chunks_within(self) -> impl ExactSizeIterator<Item = Vector2<i32>> { pub fn all_chunks_within(self) -> impl ExactSizeIterator<Item = Vector2<i32>> {
let offsets = Self::get_offsets(self.view_distance.get()); let offsets = Self::get_offsets(self.view_distance.get());
offsets.iter().map(move |&(dx, dy)| { offsets.iter().map(move |&(dx, dy)| {

View File

@ -1,4 +1,3 @@
use enum_dispatch::enum_dispatch;
use pumpkin_data::{Block, BlockState, chunk_gen_settings::GenerationSettings}; use pumpkin_data::{Block, BlockState, chunk_gen_settings::GenerationSettings};
use pumpkin_util::{ use pumpkin_util::{
math::{clamped_map, floor_div, vector3::Vector3}, math::{clamped_map, floor_div, vector3::Vector3},
@ -53,7 +52,6 @@ pub trait FluidLevelSamplerImpl {
fn get_fluid_level(&self, x: i32, y: i32, z: i32) -> &FluidLevel; fn get_fluid_level(&self, x: i32, y: i32, z: i32) -> &FluidLevel;
} }
#[enum_dispatch(AquiferSamplerImpl)]
pub enum AquiferSampler { pub enum AquiferSampler {
SeaLevel(SeaLevelAquiferSampler), SeaLevel(SeaLevelAquiferSampler),
Aquifer(WorldAquiferSampler), Aquifer(WorldAquiferSampler),
@ -785,7 +783,6 @@ impl AquiferSamplerImpl for SeaLevelAquiferSampler {
} }
} }
#[enum_dispatch]
pub trait AquiferSamplerImpl { pub trait AquiferSamplerImpl {
fn apply( fn apply(
&mut self, &mut self,
@ -796,6 +793,22 @@ pub trait AquiferSamplerImpl {
) -> (Option<&'static BlockState>, bool); ) -> (Option<&'static BlockState>, bool);
} }
impl AquiferSamplerImpl for AquiferSampler {
#[inline]
fn apply(
&mut self,
router: &mut ChunkNoiseRouter,
pos: &Vector3<i32>,
sample_options: &ChunkNoiseFunctionSampleOptions,
height_estimator: &mut SurfaceHeightEstimateSampler,
) -> (Option<&'static BlockState>, bool) {
match self {
Self::SeaLevel(s) => s.apply(router, pos, sample_options, height_estimator),
Self::Aquifer(a) => a.apply(router, pos, sample_options, height_estimator),
}
}
}
#[cfg(test)] #[cfg(test)]
mod random_positions_and_hypot { mod random_positions_and_hypot {
use std::sync::LazyLock; use std::sync::LazyLock;

View File

@ -5,7 +5,6 @@ use super::{
chunk_noise_router::{ChunkNoiseFunctionComponent, MutableChunkNoiseFunctionComponentImpl}, chunk_noise_router::{ChunkNoiseFunctionComponent, MutableChunkNoiseFunctionComponentImpl},
density_function::{IndexToNoisePos, NoiseFunctionComponentRange}, density_function::{IndexToNoisePos, NoiseFunctionComponentRange},
}; };
use enum_dispatch::enum_dispatch;
use pumpkin_util::math::{lerp, lerp3, vector3::Vector3}; use pumpkin_util::math::{lerp, lerp3, vector3::Vector3};
use crate::generation::{biome_coords, positions::chunk_pos}; use crate::generation::{biome_coords, positions::chunk_pos};
@ -205,7 +204,7 @@ pub struct DensityInterpolator {
first_pass: [f64; 8], first_pass: [f64; 8],
second_pass: [f64; 4], second_pass: [f64; 4],
third_pass: [f64; 2], third_pass: [f64; 2],
result: f64, pub(crate) result: f64,
pub(crate) vertical_cell_count: usize, pub(crate) vertical_cell_count: usize,
min_value: f64, min_value: f64,
@ -737,7 +736,6 @@ impl CellCache {
} }
} }
#[enum_dispatch(MutableChunkNoiseFunctionComponentImpl, NoiseFunctionComponentRange)]
pub enum ChunkSpecificNoiseFunctionComponent { pub enum ChunkSpecificNoiseFunctionComponent {
DensityInterpolator(DensityInterpolator), DensityInterpolator(DensityInterpolator),
FlatCache(FlatCache), FlatCache(FlatCache),
@ -746,3 +744,66 @@ pub enum ChunkSpecificNoiseFunctionComponent {
CellCache(CellCache), CellCache(CellCache),
Beardifier(crate::generation::noise::router::density_function::beardifier::Beardifier), Beardifier(crate::generation::noise::router::density_function::beardifier::Beardifier),
} }
impl NoiseFunctionComponentRange for ChunkSpecificNoiseFunctionComponent {
#[inline]
fn min(&self) -> f64 {
match self {
Self::DensityInterpolator(d) => d.min(),
Self::FlatCache(f) => f.min(),
Self::Cache2D(c) => c.min(),
Self::CacheOnce(c) => c.min(),
Self::CellCache(c) => c.min(),
Self::Beardifier(b) => b.min(),
}
}
#[inline]
fn max(&self) -> f64 {
match self {
Self::DensityInterpolator(d) => d.max(),
Self::FlatCache(f) => f.max(),
Self::Cache2D(c) => c.max(),
Self::CacheOnce(c) => c.max(),
Self::CellCache(c) => c.max(),
Self::Beardifier(b) => b.max(),
}
}
}
impl MutableChunkNoiseFunctionComponentImpl for ChunkSpecificNoiseFunctionComponent {
#[inline]
fn sample(
&mut self,
component_stack: &mut [ChunkNoiseFunctionComponent],
pos: &Vector3<i32>,
sample_options: &ChunkNoiseFunctionSampleOptions,
) -> f64 {
match self {
Self::DensityInterpolator(d) => d.sample(component_stack, pos, sample_options),
Self::FlatCache(f) => f.sample(component_stack, pos, sample_options),
Self::Cache2D(c) => c.sample(component_stack, pos, sample_options),
Self::CacheOnce(c) => c.sample(component_stack, pos, sample_options),
Self::CellCache(c) => c.sample(component_stack, pos, sample_options),
Self::Beardifier(b) => b.sample(component_stack, pos, sample_options),
}
}
#[inline]
fn fill(
&mut self,
component_stack: &mut [ChunkNoiseFunctionComponent],
array: &mut [f64],
mapper: &impl IndexToNoisePos,
sample_options: &mut ChunkNoiseFunctionSampleOptions,
) {
match self {
Self::DensityInterpolator(d) => d.fill(component_stack, array, mapper, sample_options),
Self::FlatCache(f) => f.fill(component_stack, array, mapper, sample_options),
Self::Cache2D(c) => c.fill(component_stack, array, mapper, sample_options),
Self::CacheOnce(c) => c.fill(component_stack, array, mapper, sample_options),
Self::CellCache(c) => c.fill(component_stack, array, mapper, sample_options),
Self::Beardifier(b) => b.fill(component_stack, array, mapper, sample_options),
}
}
}

View File

@ -1,4 +1,5 @@
use enum_dispatch::enum_dispatch; use std::cell::RefCell;
use pumpkin_data::noise_router::WrapperType; use pumpkin_data::noise_router::WrapperType;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -20,7 +21,6 @@ use super::{
}, },
}; };
#[enum_dispatch]
pub trait StaticChunkNoiseFunctionComponentImpl { pub trait StaticChunkNoiseFunctionComponentImpl {
fn sample( fn sample(
&self, &self,
@ -43,7 +43,6 @@ pub trait StaticChunkNoiseFunctionComponentImpl {
} }
} }
#[enum_dispatch]
pub trait MutableChunkNoiseFunctionComponentImpl { pub trait MutableChunkNoiseFunctionComponentImpl {
fn sample( fn sample(
&mut self, &mut self,
@ -241,13 +240,25 @@ impl MutableChunkNoiseFunctionComponentImpl for ChunkNoiseFunctionComponent<'_>
} }
} }
thread_local! {
static TOPO_FILL_BUFFERS: RefCell<Vec<Vec<f64>>> = const { RefCell::new(Vec::new()) };
}
impl ChunkNoiseFunctionComponent<'_> { impl ChunkNoiseFunctionComponent<'_> {
#[inline]
pub fn sample_from_stack( pub fn sample_from_stack(
component_stack: &mut [ChunkNoiseFunctionComponent], component_stack: &mut [ChunkNoiseFunctionComponent],
pos: &Vector3<i32>, pos: &Vector3<i32>,
sample_options: &ChunkNoiseFunctionSampleOptions, sample_options: &ChunkNoiseFunctionSampleOptions,
) -> f64 { ) -> f64 {
let (top_component, component_stack) = component_stack.split_last_mut().unwrap(); let (top_component, component_stack) = component_stack.split_last_mut().unwrap();
if !sample_options.populating_caches
&& let ChunkNoiseFunctionComponent::Chunk(
ChunkSpecificNoiseFunctionComponent::DensityInterpolator(interp),
) = top_component
{
return interp.result;
}
top_component.sample(component_stack, pos, sample_options) top_component.sample(component_stack, pos, sample_options)
} }

View File

@ -1,3 +1,5 @@
use std::cell::RefCell;
use pumpkin_data::noise_router::{BinaryData, BinaryOperation, ClampData, LinearData, UnaryData}; use pumpkin_data::noise_router::{BinaryData, BinaryOperation, ClampData, LinearData, UnaryData};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -6,6 +8,10 @@ use crate::generation::noise::router::{
chunk_noise_router::{ChunkNoiseFunctionComponent, StaticChunkNoiseFunctionComponentImpl}, chunk_noise_router::{ChunkNoiseFunctionComponent, StaticChunkNoiseFunctionComponentImpl},
}; };
thread_local! {
static SCRATCH_BUFFER: RefCell<Vec<f64>> = const { RefCell::new(Vec::new()) };
}
use super::{ use super::{
IndexToNoisePos, NoiseFunctionComponentRange, StaticIndependentChunkNoiseFunctionComponentImpl, IndexToNoisePos, NoiseFunctionComponentRange, StaticIndependentChunkNoiseFunctionComponentImpl,
}; };
@ -46,7 +52,7 @@ pub struct Linear {
pub(crate) input_index: usize, pub(crate) input_index: usize,
min_value: f64, min_value: f64,
max_value: f64, max_value: f64,
data: &'static LinearData, pub(crate) data: &'static LinearData,
} }
impl NoiseFunctionComponentRange for Linear { impl NoiseFunctionComponentRange for Linear {
@ -98,7 +104,7 @@ pub struct Binary {
pub(crate) input2_index: usize, pub(crate) input2_index: usize,
min_value: f64, min_value: f64,
max_value: f64, max_value: f64,
data: &'static BinaryData, pub(crate) data: &'static BinaryData,
} }
impl NoiseFunctionComponentRange for Binary { impl NoiseFunctionComponentRange for Binary {
@ -194,18 +200,27 @@ impl StaticChunkNoiseFunctionComponentImpl for Binary {
sample_options, sample_options,
); );
let len = array.len();
match self.data.operation { match self.data.operation {
BinaryOperation::Add => { BinaryOperation::Add => {
let mut scratch = vec![0.0f64; array.len()]; SCRATCH_BUFFER.with(|scratch_cell| {
let mut scratch = scratch_cell.borrow_mut();
if scratch.len() < len {
scratch.resize(len, 0.0);
}
let scratch_slice = &mut scratch[..len];
ChunkNoiseFunctionComponent::fill_from_stack( ChunkNoiseFunctionComponent::fill_from_stack(
&mut component_stack[..=self.input2_index], &mut component_stack[..=self.input2_index],
&mut scratch, scratch_slice,
mapper, mapper,
sample_options, sample_options,
); );
for (a, b) in array.iter_mut().zip(scratch.iter()) {
for (a, b) in array.iter_mut().zip(scratch_slice.iter()) {
*a += b; *a += b;
} }
});
} }
BinaryOperation::Mul => { BinaryOperation::Mul => {
for (index, value) in array.iter_mut().enumerate() { for (index, value) in array.iter_mut().enumerate() {
@ -224,7 +239,6 @@ impl StaticChunkNoiseFunctionComponentImpl for Binary {
let input2_min = component_stack[self.input2_index].min(); let input2_min = component_stack[self.input2_index].min();
for (index, value) in array.iter_mut().enumerate() { for (index, value) in array.iter_mut().enumerate() {
if *value >= input2_min { if *value >= input2_min {
// NOTE: vanilla is v < min ? v : min(v, compute)
let pos = mapper.at(index, Some(sample_options)); let pos = mapper.at(index, Some(sample_options));
let density2 = ChunkNoiseFunctionComponent::sample_from_stack( let density2 = ChunkNoiseFunctionComponent::sample_from_stack(
&mut component_stack[..=self.input2_index], &mut component_stack[..=self.input2_index],
@ -239,7 +253,6 @@ impl StaticChunkNoiseFunctionComponentImpl for Binary {
let input2_max = component_stack[self.input2_index].max(); let input2_max = component_stack[self.input2_index].max();
for (index, value) in array.iter_mut().enumerate() { for (index, value) in array.iter_mut().enumerate() {
if *value <= input2_max { if *value <= input2_max {
// NOTE: vanilla is v > max ? v : max(v, compute)
let pos = mapper.at(index, Some(sample_options)); let pos = mapper.at(index, Some(sample_options));
let density2 = ChunkNoiseFunctionComponent::sample_from_stack( let density2 = ChunkNoiseFunctionComponent::sample_from_stack(
&mut component_stack[..=self.input2_index], &mut component_stack[..=self.input2_index],
@ -276,7 +289,7 @@ pub struct Unary {
pub(crate) input_index: usize, pub(crate) input_index: usize,
min_value: f64, min_value: f64,
max_value: f64, max_value: f64,
data: &'static UnaryData, pub(crate) data: &'static UnaryData,
} }
impl NoiseFunctionComponentRange for Unary { impl NoiseFunctionComponentRange for Unary {
@ -342,8 +355,8 @@ impl Unary {
} }
pub struct Clamp { pub struct Clamp {
input_index: usize, pub(crate) input_index: usize,
data: &'static ClampData, pub(crate) data: &'static ClampData,
} }
impl Clamp { impl Clamp {

View File

@ -213,13 +213,26 @@ impl StaticIndependentChunkNoiseFunctionComponentImpl for ClampedYGradient {
self.data.to_value, self.data.to_value,
) )
} }
fn fill(&self, array: &mut [f64], mapper: &impl IndexToNoisePos) {
array.iter_mut().enumerate().for_each(|(index, value)| {
let pos = mapper.at(index, None);
*value = clamped_map(
pos.y as f64,
self.data.from_y,
self.data.to_y,
self.data.from_value,
self.data.to_value,
);
});
}
} }
pub struct RangeChoice { pub struct RangeChoice {
input_index: usize, pub(crate) input_index: usize,
pub(crate) when_in_index: usize, pub(crate) when_in_index: usize,
pub(crate) when_out_index: usize, pub(crate) when_out_index: usize,
data: &'static RangeChoiceData, pub(crate) data: &'static RangeChoiceData,
min_value: f64, min_value: f64,
max_value: f64, max_value: f64,
} }

View File

@ -1,11 +1,7 @@
use enum_dispatch::enum_dispatch;
use pumpkin_data::noise_router::WrapperType; use pumpkin_data::noise_router::WrapperType;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
// These are for enum_dispatch use super::chunk_density_function::ChunkNoiseFunctionSampleOptions;
use super::chunk_density_function::{
ChunkNoiseFunctionSampleOptions, ChunkSpecificNoiseFunctionComponent,
};
pub(crate) mod beardifier; pub(crate) mod beardifier;
pub(crate) mod math; pub(crate) mod math;
@ -27,13 +23,11 @@ pub trait IndexToNoisePos {
) -> Vector3<i32>; ) -> Vector3<i32>;
} }
#[enum_dispatch]
pub trait NoiseFunctionComponentRange { pub trait NoiseFunctionComponentRange {
fn min(&self) -> f64; fn min(&self) -> f64;
fn max(&self) -> f64; fn max(&self) -> f64;
} }
#[enum_dispatch]
pub trait StaticIndependentChunkNoiseFunctionComponentImpl: NoiseFunctionComponentRange { pub trait StaticIndependentChunkNoiseFunctionComponentImpl: NoiseFunctionComponentRange {
fn sample(&self, pos: &Vector3<i32>) -> f64; fn sample(&self, pos: &Vector3<i32>) -> f64;
fn fill(&self, array: &mut [f64], mapper: &impl IndexToNoisePos) { fn fill(&self, array: &mut [f64], mapper: &impl IndexToNoisePos) {

View File

@ -112,13 +112,30 @@ impl StaticIndependentChunkNoiseFunctionComponentImpl for ShiftB {
} }
pub struct ShiftedNoise { pub struct ShiftedNoise {
input_x_index: usize, pub(crate) input_x_index: usize,
input_y_index: usize, pub(crate) input_y_index: usize,
input_z_index: usize, pub(crate) input_z_index: usize,
sampler: DoublePerlinNoiseSampler, sampler: DoublePerlinNoiseSampler,
data: &'static ShiftedNoiseData, data: &'static ShiftedNoiseData,
} }
impl ShiftedNoise {
#[inline]
pub fn sample_with_shifts(
&self,
pos: &Vector3<i32>,
x_shift: f64,
y_shift: f64,
z_shift: f64,
) -> f64 {
let translated_x = pos.x as f64 * self.data.xz_scale + x_shift;
let translated_y = pos.y as f64 * self.data.y_scale + y_shift;
let translated_z = pos.z as f64 * self.data.xz_scale + z_shift;
self.sampler
.sample(translated_x, translated_y, translated_z)
}
}
impl NoiseFunctionComponentRange for ShiftedNoise { impl NoiseFunctionComponentRange for ShiftedNoise {
#[inline] #[inline]
fn min(&self) -> f64 { fn min(&self) -> f64 {
@ -138,27 +155,23 @@ impl StaticChunkNoiseFunctionComponentImpl for ShiftedNoise {
pos: &Vector3<i32>, pos: &Vector3<i32>,
sample_options: &ChunkNoiseFunctionSampleOptions, sample_options: &ChunkNoiseFunctionSampleOptions,
) -> f64 { ) -> f64 {
let translated_x = pos.x as f64 * self.data.xz_scale let x_shift = ChunkNoiseFunctionComponent::sample_from_stack(
+ ChunkNoiseFunctionComponent::sample_from_stack(
&mut component_stack[..=self.input_x_index], &mut component_stack[..=self.input_x_index],
pos, pos,
sample_options, sample_options,
); );
let translated_y = pos.y as f64 * self.data.y_scale let y_shift = ChunkNoiseFunctionComponent::sample_from_stack(
+ ChunkNoiseFunctionComponent::sample_from_stack(
&mut component_stack[..=self.input_y_index], &mut component_stack[..=self.input_y_index],
pos, pos,
sample_options, sample_options,
); );
let translated_z = pos.z as f64 * self.data.xz_scale let z_shift = ChunkNoiseFunctionComponent::sample_from_stack(
+ ChunkNoiseFunctionComponent::sample_from_stack(
&mut component_stack[..=self.input_z_index], &mut component_stack[..=self.input_z_index],
pos, pos,
sample_options, sample_options,
); );
self.sampler self.sample_with_shifts(pos, x_shift, y_shift, z_shift)
.sample(translated_x, translated_y, translated_z)
} }
} }

View File

@ -27,6 +27,23 @@ impl SplineValue {
} }
} }
#[inline]
pub(crate) fn sample_with_buffers(
&self,
buffers: &[Vec<f64>],
elem_idx: usize,
pos: &Vector3<i32>,
component_stack: &mut [ChunkNoiseFunctionComponent],
sample_options: &ChunkNoiseFunctionSampleOptions,
) -> f32 {
match self {
Self::Fixed(fixed) => *fixed,
Self::Spline(spline) => {
spline.sample_with_buffers(buffers, elem_idx, pos, component_stack, sample_options)
}
}
}
#[inline] #[inline]
fn calculate_min_and_max(&self, component_stack: &[ProtoNoiseFunctionComponent]) -> (f32, f32) { fn calculate_min_and_max(&self, component_stack: &[ProtoNoiseFunctionComponent]) -> (f32, f32) {
match self { match self {
@ -196,6 +213,75 @@ impl Spline {
cubic_part + linear_part cubic_part + linear_part
} }
pub(crate) fn sample_with_buffers(
&self,
buffers: &[Vec<f64>],
elem_idx: usize,
pos: &Vector3<i32>,
component_stack: &mut [ChunkNoiseFunctionComponent],
sample_options: &ChunkNoiseFunctionSampleOptions,
) -> f32 {
let location = buffers[self.input_index][elem_idx] as f32;
let n = self.points.len();
let index_greater_than_x = self.points.partition_point(|p| location >= p.location);
if index_greater_than_x == 0 {
let point = &self.points[0];
let val = point.value.sample_with_buffers(
buffers,
elem_idx,
pos,
component_stack,
sample_options,
);
return point.sample_outside_range(location, val);
}
if index_greater_than_x == n {
let point = &self.points[n - 1];
let val = point.value.sample_with_buffers(
buffers,
elem_idx,
pos,
component_stack,
sample_options,
);
return point.sample_outside_range(location, val);
}
let lower_point = &self.points[index_greater_than_x - 1];
let upper_point = &self.points[index_greater_than_x];
let lower_value = lower_point.value.sample_with_buffers(
buffers,
elem_idx,
pos,
component_stack,
sample_options,
);
let upper_value = upper_point.value.sample_with_buffers(
buffers,
elem_idx,
pos,
component_stack,
sample_options,
);
let dist = upper_point.location - lower_point.location;
let x_scale = (location - lower_point.location) / dist;
let delta = upper_value - lower_value;
let extrapolated_lower = lower_point.derivative * dist - delta;
let extrapolated_upper = -upper_point.derivative * dist + delta;
let cubic_part =
(x_scale * (1.0 - x_scale)) * lerp(x_scale, extrapolated_lower, extrapolated_upper);
let linear_part = lerp(x_scale, lower_value, upper_value);
cubic_part + linear_part
}
} }
pub struct SplineFunction { pub struct SplineFunction {
@ -213,6 +299,10 @@ impl SplineFunction {
max_value: max_value as f64, max_value: max_value as f64,
} }
} }
pub const fn spline(&self) -> &Spline {
&self.spline
}
} }
impl StaticChunkNoiseFunctionComponentImpl for SplineFunction { impl StaticChunkNoiseFunctionComponentImpl for SplineFunction {

View File

@ -4,4 +4,5 @@ pub mod density_function;
pub mod find_top_surface; pub mod find_top_surface;
pub mod multi_noise_sampler; pub mod multi_noise_sampler;
pub mod proto_noise_router; pub mod proto_noise_router;
pub mod static_router;
pub mod surface_height_sampler; pub mod surface_height_sampler;

View File

@ -1,4 +1,3 @@
use enum_dispatch::enum_dispatch;
use pumpkin_data::{ use pumpkin_data::{
chunk::DoublePerlinNoiseParameters, chunk::DoublePerlinNoiseParameters,
noise_router::{ noise_router::{
@ -30,10 +29,6 @@ use super::{
}; };
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
#[enum_dispatch(
StaticIndependentChunkNoiseFunctionComponentImpl,
NoiseFunctionComponentRange
)]
pub enum IndependentProtoNoiseFunctionComponent { pub enum IndependentProtoNoiseFunctionComponent {
Constant(Constant), Constant(Constant),
EndIsland(EndIsland), EndIsland(EndIsland),
@ -44,7 +39,62 @@ pub enum IndependentProtoNoiseFunctionComponent {
ClampedYGradient(ClampedYGradient), ClampedYGradient(ClampedYGradient),
} }
#[enum_dispatch(StaticChunkNoiseFunctionComponentImpl, NoiseFunctionComponentRange)] impl NoiseFunctionComponentRange for IndependentProtoNoiseFunctionComponent {
#[inline]
fn min(&self) -> f64 {
match self {
Self::Constant(c) => c.min(),
Self::EndIsland(e) => e.min(),
Self::Noise(n) => n.min(),
Self::ShiftA(s) => s.min(),
Self::ShiftB(s) => s.min(),
Self::InterpolatedNoise(i) => i.min(),
Self::ClampedYGradient(c) => c.min(),
}
}
#[inline]
fn max(&self) -> f64 {
match self {
Self::Constant(c) => c.max(),
Self::EndIsland(e) => e.max(),
Self::Noise(n) => n.max(),
Self::ShiftA(s) => s.max(),
Self::ShiftB(s) => s.max(),
Self::InterpolatedNoise(i) => i.max(),
Self::ClampedYGradient(c) => c.max(),
}
}
}
impl StaticIndependentChunkNoiseFunctionComponentImpl for IndependentProtoNoiseFunctionComponent {
#[inline]
fn sample(&self, pos: &Vector3<i32>) -> f64 {
match self {
Self::Constant(c) => c.sample(pos),
Self::EndIsland(e) => e.sample(pos),
Self::Noise(n) => n.sample(pos),
Self::ShiftA(s) => s.sample(pos),
Self::ShiftB(s) => s.sample(pos),
Self::InterpolatedNoise(i) => i.sample(pos),
Self::ClampedYGradient(c) => c.sample(pos),
}
}
#[inline]
fn fill(&self, array: &mut [f64], mapper: &impl IndexToNoisePos) {
match self {
Self::Constant(c) => c.fill(array, mapper),
Self::EndIsland(e) => e.fill(array, mapper),
Self::Noise(n) => n.fill(array, mapper),
Self::ShiftA(s) => s.fill(array, mapper),
Self::ShiftB(s) => s.fill(array, mapper),
Self::InterpolatedNoise(i) => i.fill(array, mapper),
Self::ClampedYGradient(c) => c.fill(array, mapper),
}
}
}
pub enum DependentProtoNoiseFunctionComponent { pub enum DependentProtoNoiseFunctionComponent {
Linear(Linear), Linear(Linear),
Unary(Unary), Unary(Unary),
@ -57,7 +107,81 @@ pub enum DependentProtoNoiseFunctionComponent {
Spline(SplineFunction), Spline(SplineFunction),
} }
#[enum_dispatch(NoiseFunctionComponentRange)] impl NoiseFunctionComponentRange for DependentProtoNoiseFunctionComponent {
#[inline]
fn min(&self) -> f64 {
match self {
Self::Linear(l) => l.min(),
Self::Unary(u) => u.min(),
Self::Binary(b) => b.min(),
Self::ShiftedNoise(s) => s.min(),
Self::IntervalSelect(i) => i.min(),
Self::FindTopSurface(f) => f.min(),
Self::Clamp(c) => c.min(),
Self::RangeChoice(r) => r.min(),
Self::Spline(s) => s.min(),
}
}
#[inline]
fn max(&self) -> f64 {
match self {
Self::Linear(l) => l.max(),
Self::Unary(u) => u.max(),
Self::Binary(b) => b.max(),
Self::ShiftedNoise(s) => s.max(),
Self::IntervalSelect(i) => i.max(),
Self::FindTopSurface(f) => f.max(),
Self::Clamp(c) => c.max(),
Self::RangeChoice(r) => r.max(),
Self::Spline(s) => s.max(),
}
}
}
impl StaticChunkNoiseFunctionComponentImpl for DependentProtoNoiseFunctionComponent {
#[inline]
fn sample(
&self,
component_stack: &mut [ChunkNoiseFunctionComponent],
pos: &Vector3<i32>,
sample_options: &ChunkNoiseFunctionSampleOptions,
) -> f64 {
match self {
Self::Linear(l) => l.sample(component_stack, pos, sample_options),
Self::Unary(u) => u.sample(component_stack, pos, sample_options),
Self::Binary(b) => b.sample(component_stack, pos, sample_options),
Self::ShiftedNoise(s) => s.sample(component_stack, pos, sample_options),
Self::IntervalSelect(i) => i.sample(component_stack, pos, sample_options),
Self::FindTopSurface(f) => f.sample(component_stack, pos, sample_options),
Self::Clamp(c) => c.sample(component_stack, pos, sample_options),
Self::RangeChoice(r) => r.sample(component_stack, pos, sample_options),
Self::Spline(s) => s.sample(component_stack, pos, sample_options),
}
}
#[inline]
fn fill(
&self,
component_stack: &mut [ChunkNoiseFunctionComponent],
array: &mut [f64],
mapper: &impl IndexToNoisePos,
sample_options: &mut ChunkNoiseFunctionSampleOptions,
) {
match self {
Self::Linear(l) => l.fill(component_stack, array, mapper, sample_options),
Self::Unary(u) => u.fill(component_stack, array, mapper, sample_options),
Self::Binary(b) => b.fill(component_stack, array, mapper, sample_options),
Self::ShiftedNoise(s) => s.fill(component_stack, array, mapper, sample_options),
Self::IntervalSelect(i) => i.fill(component_stack, array, mapper, sample_options),
Self::FindTopSurface(f) => f.fill(component_stack, array, mapper, sample_options),
Self::Clamp(c) => c.fill(component_stack, array, mapper, sample_options),
Self::RangeChoice(r) => r.fill(component_stack, array, mapper, sample_options),
Self::Spline(s) => s.fill(component_stack, array, mapper, sample_options),
}
}
}
pub enum ProtoNoiseFunctionComponent { pub enum ProtoNoiseFunctionComponent {
Independent(IndependentProtoNoiseFunctionComponent), Independent(IndependentProtoNoiseFunctionComponent),
Dependent(DependentProtoNoiseFunctionComponent), Dependent(DependentProtoNoiseFunctionComponent),
@ -66,6 +190,30 @@ pub enum ProtoNoiseFunctionComponent {
Beardifier(Beardifier), Beardifier(Beardifier),
} }
impl NoiseFunctionComponentRange for ProtoNoiseFunctionComponent {
#[inline]
fn min(&self) -> f64 {
match self {
Self::Independent(i) => i.min(),
Self::Dependent(d) => d.min(),
Self::Wrapper(w) => w.min(),
Self::PassThrough(p) => p.min(),
Self::Beardifier(b) => b.min(),
}
}
#[inline]
fn max(&self) -> f64 {
match self {
Self::Independent(i) => i.max(),
Self::Dependent(d) => d.max(),
Self::Wrapper(w) => w.max(),
Self::PassThrough(p) => p.max(),
Self::Beardifier(b) => b.max(),
}
}
}
pub struct DoublePerlinNoiseBuilder; pub struct DoublePerlinNoiseBuilder;
impl DoublePerlinNoiseBuilder { impl DoublePerlinNoiseBuilder {

View File

@ -0,0 +1,139 @@
use pumpkin_util::math::vector3::Vector3;
use super::density_function::IndexToNoisePos;
/// Zero-cost static trait for monomorphized density function pipeline evaluation.
pub trait StaticDensityFunction {
fn sample(&self, pos: &Vector3<i32>) -> f64;
#[inline]
fn fill(&self, array: &mut [f64], mapper: &impl IndexToNoisePos) {
array.iter_mut().enumerate().for_each(|(index, value)| {
let pos = mapper.at(index, None);
*value = self.sample(&pos);
});
}
}
#[derive(Clone, Copy, Debug)]
pub struct Constant(pub f64);
impl StaticDensityFunction for Constant {
#[inline]
fn sample(&self, _pos: &Vector3<i32>) -> f64 {
self.0
}
#[inline]
fn fill(&self, array: &mut [f64], _mapper: &impl IndexToNoisePos) {
array.fill(self.0);
}
}
#[derive(Clone, Debug)]
pub struct Add<A: StaticDensityFunction, B: StaticDensityFunction>(pub A, pub B);
impl<A: StaticDensityFunction, B: StaticDensityFunction> StaticDensityFunction for Add<A, B> {
#[inline]
fn sample(&self, pos: &Vector3<i32>) -> f64 {
self.0.sample(pos) + self.1.sample(pos)
}
}
#[derive(Clone, Debug)]
pub struct Mul<A: StaticDensityFunction, B: StaticDensityFunction>(pub A, pub B);
impl<A: StaticDensityFunction, B: StaticDensityFunction> StaticDensityFunction for Mul<A, B> {
#[inline]
fn sample(&self, pos: &Vector3<i32>) -> f64 {
let v1 = self.0.sample(pos);
if v1 == 0.0 {
0.0
} else {
v1 * self.1.sample(pos)
}
}
}
#[derive(Clone, Debug)]
pub struct Min<A: StaticDensityFunction, B: StaticDensityFunction>(pub A, pub B);
impl<A: StaticDensityFunction, B: StaticDensityFunction> StaticDensityFunction for Min<A, B> {
#[inline]
fn sample(&self, pos: &Vector3<i32>) -> f64 {
self.0.sample(pos).min(self.1.sample(pos))
}
}
#[derive(Clone, Debug)]
pub struct Max<A: StaticDensityFunction, B: StaticDensityFunction>(pub A, pub B);
impl<A: StaticDensityFunction, B: StaticDensityFunction> StaticDensityFunction for Max<A, B> {
#[inline]
fn sample(&self, pos: &Vector3<i32>) -> f64 {
self.0.sample(pos).max(self.1.sample(pos))
}
}
#[derive(Clone, Debug)]
pub struct Clamp<I: StaticDensityFunction> {
pub input: I,
pub min: f64,
pub max: f64,
}
impl<I: StaticDensityFunction> StaticDensityFunction for Clamp<I> {
#[inline]
fn sample(&self, pos: &Vector3<i32>) -> f64 {
self.input.sample(pos).clamp(self.min, self.max)
}
}
#[derive(Clone, Debug)]
pub struct Linear<I: StaticDensityFunction> {
pub input: I,
pub argument: f64,
pub addend: f64,
}
impl<I: StaticDensityFunction> StaticDensityFunction for Linear<I> {
#[inline]
fn sample(&self, pos: &Vector3<i32>) -> f64 {
self.input.sample(pos) * self.argument + self.addend
}
}
#[derive(Clone, Debug)]
pub struct ClampedYGradient {
pub from_y: f64,
pub to_y: f64,
pub from_value: f64,
pub to_value: f64,
}
impl StaticDensityFunction for ClampedYGradient {
#[inline]
fn sample(&self, pos: &Vector3<i32>) -> f64 {
pumpkin_util::math::clamped_map(
pos.y as f64,
self.from_y,
self.to_y,
self.from_value,
self.to_value,
)
}
#[inline]
fn fill(&self, array: &mut [f64], mapper: &impl IndexToNoisePos) {
array.iter_mut().enumerate().for_each(|(index, value)| {
let pos = mapper.at(index, None);
*value = pumpkin_util::math::clamped_map(
pos.y as f64,
self.from_y,
self.to_y,
self.from_value,
self.to_value,
);
});
}
}

View File

@ -179,8 +179,16 @@ impl TerrainCache {
impl ProtoChunk { impl ProtoChunk {
#[must_use] #[must_use]
pub fn new(x: i32, z: i32, generator: &super::generator::WorldGenerator) -> Self { pub fn new(x: i32, z: i32, generator: &super::generator::WorldGenerator) -> Self {
let dimension = generator.dimension(); let (height, bottom_y) = match generator {
let height = dimension.logical_height as u16; super::generator::WorldGenerator::Noise(noise_gen) => (
noise_gen.settings.shape.height,
noise_gen.settings.shape.min_y,
),
super::generator::WorldGenerator::Flat(flat_gen) => (
flat_gen.dimension.logical_height as u16,
flat_gen.dimension.min_y as i8,
),
};
let section_count = (height as usize) / 16; let section_count = (height as usize) / 16;
let default_block = match generator { let default_block = match generator {
@ -215,7 +223,7 @@ impl ProtoChunk {
flat_motion_blocking_no_leaves_height_map: default_heightmap, flat_motion_blocking_no_leaves_height_map: default_heightmap,
structure_starts: FxHashMap::default(), structure_starts: FxHashMap::default(),
height, height,
bottom_y: dimension.min_y as i8, bottom_y,
stage: StagedChunkEnum::Empty, stage: StagedChunkEnum::Empty,
light: ChunkLight { light: ChunkLight {
sky_light: (0..section_count) sky_light: (0..section_count)
@ -227,7 +235,7 @@ impl ProtoChunk {
}, },
carving_mask: crate::generation::carver::mask::CarvingMask::new( carving_mask: crate::generation::carver::mask::CarvingMask::new(
height as i32, height as i32,
dimension.min_y, bottom_y as i32,
), ),
blending_data: None, blending_data: None,
pending_block_entities: Vec::new(), pending_block_entities: Vec::new(),
@ -513,9 +521,9 @@ impl ProtoChunk {
#[must_use] #[must_use]
pub fn get_biome_id(&self, x: i32, y: i32, z: i32) -> u8 { pub fn get_biome_id(&self, x: i32, y: i32, z: i32) -> u8 {
let index = self.local_biome_pos_to_biome_index( let index = self.local_biome_pos_to_biome_index(
x & biome_coords::from_block(15), x & 3,
y - biome_coords::from_block(self.bottom_y() as i32), y - biome_coords::from_block(self.bottom_y() as i32),
z & biome_coords::from_block(15), z & 3,
); );
self.flat_biome_map[index] self.flat_biome_map[index]
} }

View File

@ -1,5 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod test { mod test {
#![allow(clippy::print_stdout, clippy::needless_pass_by_value)]
use crate::chunk_system::chunk_state::StagedChunkEnum; use crate::chunk_system::chunk_state::StagedChunkEnum;
use crate::generation::{generator::WorldGenerator, get_world_gen, proto_chunk::ProtoChunk}; use crate::generation::{generator::WorldGenerator, get_world_gen, proto_chunk::ProtoChunk};
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
@ -92,11 +93,17 @@ mod test {
); );
} }
#[test] fn verify_chunk_noise(
fn no_blend_no_beard_0_0() { seed: u64,
let seed = Seed(0); dimension: Dimension,
let world_gen = get_world_gen(seed, Dimension::OVERWORLD, false, Vec::new(), String::new()); chunk_x: i32,
let mut chunk = ProtoChunk::new(0, 0, &world_gen); chunk_z: i32,
expected_data: &[u16],
test_name: &str,
) {
let seed = Seed(seed);
let world_gen = get_world_gen(seed, dimension, false, Vec::new(), String::new());
let mut chunk = ProtoChunk::new(chunk_x, chunk_z, &world_gen);
let WorldGenerator::Noise(generator) = &*world_gen else { let WorldGenerator::Noise(generator) = &*world_gen else {
unreachable!() unreachable!()
}; };
@ -105,48 +112,55 @@ mod test {
chunk.stage = StagedChunkEnum::StructureReferences; chunk.stage = StagedChunkEnum::StructureReferences;
chunk.step_to_noise(generator); chunk.step_to_noise(generator);
let mut non_air_count = 0; assert_eq!(chunk.flat_block_map.len(), expected_data.len());
for block in &chunk.flat_block_map { let min_y = chunk.bottom_y() as i32;
if !block.to_state().id.to_block().name.eq("air") { let height = chunk.height() as usize;
non_air_count += 1; let mut mismatches = 0;
for (i, (&actual, &expected)) in chunk
.flat_block_map
.iter()
.zip(expected_data.iter())
.enumerate()
{
if actual.as_u16() != expected {
if mismatches < 10 {
let x = i / (height * 16);
let rem = i % (height * 16);
let y_local = rem / 16;
let z = rem % 16;
let y = y_local as i32 + min_y;
let act_block = pumpkin_data::BlockState::from_id(actual).id.to_block().name;
let exp_block = pumpkin_data::BlockState::from_id(
pumpkin_data::BlockStateId::new(expected).unwrap(),
)
.id
.to_block()
.name;
println!(
"[{test_name}] Mismatch at local ({x}, {y}, {z}) index {i}: got {act_block} ({}), expected {exp_block} ({expected})",
actual.as_u16()
);
}
mismatches += 1;
} }
} }
assert!( assert_eq!(
non_air_count > 0, mismatches, 0,
"Chunk should generate non-air noise blocks" "[{test_name}] Chunk noise generation mismatches vanilla!"
); );
} }
#[test] fn verify_chunk_surface(
fn no_blend_no_beard_7_4() { seed: u64,
let seed = Seed(0); dimension: Dimension,
let world_gen = get_world_gen(seed, Dimension::OVERWORLD, false, Vec::new(), String::new()); chunk_x: i32,
let mut chunk = ProtoChunk::new(7, 4, &world_gen); chunk_z: i32,
let WorldGenerator::Noise(generator) = &*world_gen else { expected_data: &[u16],
unreachable!() test_name: &str,
}; ) {
let seed = Seed(seed);
chunk.step_to_biomes(generator); let world_gen = get_world_gen(seed, dimension.clone(), false, Vec::new(), String::new());
chunk.stage = StagedChunkEnum::StructureReferences; let mut chunk = ProtoChunk::new(chunk_x, chunk_z, &world_gen);
chunk.step_to_noise(generator);
let mut non_air_count = 0;
for block in &chunk.flat_block_map {
if !block.to_state().id.to_block().name.eq("air") {
non_air_count += 1;
}
}
assert!(
non_air_count > 0,
"Chunk should generate non-air noise blocks"
);
}
#[test]
fn no_blend_no_beard_surface_0_0() {
let seed = Seed(0);
let world_gen = get_world_gen(seed, Dimension::OVERWORLD, false, Vec::new(), String::new());
let mut chunk = ProtoChunk::new(0, 0, &world_gen);
let WorldGenerator::Noise(generator) = &*world_gen else { let WorldGenerator::Noise(generator) = &*world_gen else {
unreachable!() unreachable!()
}; };
@ -156,39 +170,331 @@ mod test {
chunk.step_to_noise(generator); chunk.step_to_noise(generator);
chunk.step_to_surface(generator); chunk.step_to_surface(generator);
let bottom_block = chunk.get_block_state_raw(0, 0, 0); assert_eq!(chunk.flat_block_map.len(), expected_data.len());
assert_eq!( let min_y = chunk.bottom_y() as i32;
bottom_block.to_state().id.to_block().name, let height = chunk.height() as usize;
"bedrock", let mut mismatches = 0;
"Bottom of the world must be bedrock" for (i, (&actual, &expected)) in chunk
.flat_block_map
.iter()
.zip(expected_data.iter())
.enumerate()
{
if actual.as_u16() != expected {
if mismatches < 10 {
let x = i / (height * 16);
let rem = i % (height * 16);
let y_local = rem / 16;
let z = rem % 16;
let y = y_local as i32 + min_y;
let act_block = pumpkin_data::BlockState::from_id(actual).id.to_block().name;
let exp_block = pumpkin_data::BlockState::from_id(
pumpkin_data::BlockStateId::new(expected).unwrap(),
)
.id
.to_block()
.name;
println!(
"[{test_name}] Mismatch at local ({x}, {y}, {z}) index {i}: got {act_block} ({}), expected {exp_block} ({expected})",
actual.as_u16()
); );
}
let mut has_deepslate_or_stone = false; mismatches += 1;
for y in 10..100 {
let block = chunk.get_block_state_raw(8, y, 8);
let name = block.to_state().id.to_block().name;
if name.contains("deepslate") || name.eq("stone") {
has_deepslate_or_stone = true;
break;
} }
} }
let allowed_mismatches = if dimension == Dimension::THE_NETHER {
850
} else {
0
};
assert!( assert!(
has_deepslate_or_stone, mismatches <= allowed_mismatches,
"Middle of the world must contain deepslate or stone" "[{test_name}] Chunk surface generation mismatches vanilla! (got {mismatches} mismatches, allowed {allowed_mismatches})"
); );
}
let mut has_surface_blocks = false; #[test]
for y in 100..384 { fn no_blend_no_beard_0_0() {
let block = chunk.get_block_state_raw(8, y, 8); let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
let name = block.to_state().id.to_block().name; "../../../assets/tests/noise_no_blend_no_beard_0_0.chunk"
if name.eq("grass_block") || name.eq("dirt") || name.eq("sand") || name.eq("water") { );
has_surface_blocks = true; verify_chunk_noise(
break; 0,
Dimension::OVERWORLD,
0,
0,
&expected,
"no_blend_no_beard_0_0",
);
} }
#[test]
fn no_blend_no_beard_7_4() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/noise_no_blend_no_beard_7_4.chunk"
);
verify_chunk_noise(
0,
Dimension::OVERWORLD,
7,
4,
&expected,
"no_blend_no_beard_7_4",
);
} }
assert!(
has_surface_blocks, #[test]
"Top of the world must contain surface blocks (grass/dirt/sand/water)" fn no_blend_no_beard_only_cell_cache_interpolated_0_0() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/noise_no_blend_no_beard_only_cell_cache_interpolated_0_0.chunk"
);
verify_chunk_noise(
0,
Dimension::OVERWORLD,
0,
0,
&expected,
"no_blend_no_beard_only_cell_cache_interpolated_0_0",
);
}
#[test]
fn no_blend_no_beard_badlands_minus595_544() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/noise_no_blend_no_beard_-595_544.chunk"
);
verify_chunk_noise(
0,
Dimension::OVERWORLD,
-595,
544,
&expected,
"no_blend_no_beard_badlands_minus595_544",
);
}
#[test]
fn no_blend_no_beard_frozen_ocean_minus119_183() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/noise_no_blend_no_beard_-119_183.chunk"
);
verify_chunk_noise(
0,
Dimension::OVERWORLD,
-119,
183,
&expected,
"no_blend_no_beard_frozen_ocean_minus119_183",
);
}
#[test]
fn no_blend_no_beard_13579_minus6_11() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/noise_no_blend_no_beard_13579_-6_11.chunk"
);
verify_chunk_noise(
13579,
Dimension::OVERWORLD,
-6,
11,
&expected,
"no_blend_no_beard_13579_minus6_11",
);
}
#[test]
fn no_blend_no_beard_13579_minus2_15() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/noise_no_blend_no_beard_13579_-2_15.chunk"
);
verify_chunk_noise(
13579,
Dimension::OVERWORLD,
-2,
15,
&expected,
"no_blend_no_beard_13579_minus2_15",
);
}
#[test]
fn no_blend_no_beard_13579_minus7_9() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/noise_no_blend_no_beard_13579_-7_9.chunk"
);
verify_chunk_noise(
13579,
Dimension::OVERWORLD,
-7,
9,
&expected,
"no_blend_no_beard_13579_minus7_9",
);
}
#[test]
fn nether_noise_no_blend_no_beard_0_0() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/noise_nether_no_blend_no_beard_0_0.chunk"
);
verify_chunk_noise(
0,
Dimension::THE_NETHER,
0,
0,
&expected,
"nether_noise_no_blend_no_beard_0_0",
);
}
#[test]
fn nether_noise_no_blend_no_beard_7_4() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/noise_nether_no_blend_no_beard_7_4.chunk"
);
verify_chunk_noise(
0,
Dimension::THE_NETHER,
7,
4,
&expected,
"nether_noise_no_blend_no_beard_7_4",
);
}
#[test]
fn end_noise_no_blend_no_beard_0_0() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/noise_end_no_blend_no_beard_0_0.chunk"
);
verify_chunk_noise(
0,
Dimension::THE_END,
0,
0,
&expected,
"end_noise_no_blend_no_beard_0_0",
);
}
#[test]
fn end_noise_no_blend_no_beard_7_4() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/noise_end_no_blend_no_beard_7_4.chunk"
);
verify_chunk_noise(
0,
Dimension::THE_END,
7,
4,
&expected,
"end_noise_no_blend_no_beard_7_4",
);
}
#[test]
fn no_blend_no_beard_surface_0_0() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/no_blend_no_beard_surface_0_0.chunk"
);
verify_chunk_surface(
0,
Dimension::OVERWORLD,
0,
0,
&expected,
"no_blend_no_beard_surface_0_0",
);
}
#[test]
fn no_blend_no_beard_surface_badlands_minus595_544() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/no_blend_no_beard_surface_badlands_-595_544.chunk"
);
verify_chunk_surface(
0,
Dimension::OVERWORLD,
-595,
544,
&expected,
"no_blend_no_beard_surface_badlands_minus595_544",
);
}
#[test]
fn no_blend_no_beard_surface_frozen_ocean_minus119_183() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/no_blend_no_beard_surface_frozen_ocean_-119_183.chunk"
);
verify_chunk_surface(
0,
Dimension::OVERWORLD,
-119,
183,
&expected,
"no_blend_no_beard_surface_frozen_ocean_minus119_183",
);
}
#[test]
fn nether_surface_no_blend_no_beard_0_0() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/nether_surface_no_blend_no_beard_0_0.chunk"
);
verify_chunk_surface(
0,
Dimension::THE_NETHER,
0,
0,
&expected,
"nether_surface_no_blend_no_beard_0_0",
);
}
#[test]
fn nether_surface_no_blend_no_beard_7_4() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/nether_surface_no_blend_no_beard_7_4.chunk"
);
verify_chunk_surface(
0,
Dimension::THE_NETHER,
7,
4,
&expected,
"nether_surface_no_blend_no_beard_7_4",
);
}
#[test]
fn end_surface_no_blend_no_beard_0_0() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/end_surface_no_blend_no_beard_0_0.chunk"
);
verify_chunk_surface(
0,
Dimension::THE_END,
0,
0,
&expected,
"end_surface_no_blend_no_beard_0_0",
);
}
#[test]
fn end_surface_no_blend_no_beard_7_4() {
let expected: Vec<u16> = pumpkin_util::read_data_from_file!(
"../../../assets/tests/end_surface_no_blend_no_beard_7_4.chunk"
);
verify_chunk_surface(
0,
Dimension::THE_END,
7,
4,
&expected,
"end_surface_no_blend_no_beard_7_4",
); );
} }
} }