mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat(chunk): improve chunk generation speed ~1.27x (#2847)
* feat(noise): Add benchmark for noise generation * feat(noise): fix extra noise libm calls * feat(noise): cache density fill * feat(aquifer): add skip sampling for aquifer
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3424,6 +3424,7 @@ dependencies = [
|
||||
"base64 0.23.1",
|
||||
"bytes",
|
||||
"colored",
|
||||
"criterion",
|
||||
"crypto-bigint 0.7.5",
|
||||
"ecdsa 0.17.0",
|
||||
"md5",
|
||||
|
||||
@@ -32,9 +32,16 @@ sha2.workspace = true
|
||||
crypto-bigint.workspace = true
|
||||
ureq.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true, features = ["html_reports"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
codegen = ["dep:syn", "dep:quote", "dep:proc-macro2"]
|
||||
|
||||
[[bench]]
|
||||
name = "perlin"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
54
crates/pumpkin-util/benches/perlin.rs
Normal file
54
crates/pumpkin-util/benches/perlin.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use pumpkin_util::noise::perlin::{OctavePerlinNoiseSampler, PerlinNoiseSampler};
|
||||
use pumpkin_util::random::{RandomImpl, xoroshiro128::Xoroshiro};
|
||||
use std::hint::black_box;
|
||||
|
||||
fn make_coords(count: usize) -> Vec<(f64, f64, f64)> {
|
||||
let mut rand = Xoroshiro::from_seed(0x5EED_C0DE_1234_5678);
|
||||
(0..count)
|
||||
.map(|_| {
|
||||
(
|
||||
(rand.next_f64() - 0.5) * 200_000.0,
|
||||
(rand.next_f64() - 0.5) * 4_000.0,
|
||||
(rand.next_f64() - 0.5) * 200_000.0,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn bench_perlin_sample(c: &mut Criterion) {
|
||||
let mut rand = Xoroshiro::from_seed(1);
|
||||
let sampler = PerlinNoiseSampler::new(&mut rand);
|
||||
let coords = make_coords(4096);
|
||||
|
||||
c.bench_function("perlin_sample_no_fade", |b| {
|
||||
b.iter(|| {
|
||||
let mut acc = 0.0f64;
|
||||
for &(x, y, z) in &coords {
|
||||
acc += black_box(sampler.sample_no_fade(black_box(x), y, z, 0.0, 0.0));
|
||||
}
|
||||
black_box(acc)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_octave_perlin_sample(c: &mut Criterion) {
|
||||
let mut rand = Xoroshiro::from_seed(1);
|
||||
let (start, amplitudes) =
|
||||
OctavePerlinNoiseSampler::calculate_amplitudes(&(-4..=2).collect::<Vec<i32>>());
|
||||
let sampler = OctavePerlinNoiseSampler::new(&mut rand, start, &litudes, false);
|
||||
let coords = make_coords(4096);
|
||||
|
||||
c.bench_function("octave_perlin_sample", |b| {
|
||||
b.iter(|| {
|
||||
let mut acc = 0.0f64;
|
||||
for &(x, y, z) in &coords {
|
||||
acc += black_box(sampler.sample(black_box(x), y, z));
|
||||
}
|
||||
black_box(acc)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_perlin_sample, bench_octave_perlin_sample);
|
||||
criterion_main!(benches);
|
||||
@@ -108,6 +108,10 @@ impl Gradient {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn dot(&self, x: f64, y: f64, z: f64) -> f64 {
|
||||
self.z.mul_add(z, self.x.mul_add(x, self.y * y))
|
||||
// When using mul_add without target-feature=+fma, you get a huge performance cost
|
||||
// because it lowers into a libm call 16x per Perlin sample.
|
||||
//
|
||||
// This improves performance by something crazy like 15%
|
||||
self.x * x + self.y * y + self.z * z
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1035,4 +1035,63 @@ mod tests {
|
||||
// assert_eq!(y, *expected_iter.next().unwrap());
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Hashes raw f64 outputs from `PerlinNoiseSampler`/`OctavePerlinNoiseSampler`.
|
||||
/// Performance changes to this file must not change any float value by a single
|
||||
/// ULP. This test just helps confirm that there really hasn't been a change.
|
||||
#[test]
|
||||
fn perlin_and_octave_fingerprint_is_stable() {
|
||||
fn fnv1a_hash_f64(values: impl Iterator<Item = f64>) -> u64 {
|
||||
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
|
||||
const FNV_PRIME: u64 = 0x0000_0100_0000_01B3;
|
||||
let mut hash = FNV_OFFSET;
|
||||
for value in values {
|
||||
for byte in value.to_bits().to_le_bytes() {
|
||||
hash ^= u64::from(byte);
|
||||
hash = hash.wrapping_mul(FNV_PRIME);
|
||||
}
|
||||
}
|
||||
hash
|
||||
}
|
||||
|
||||
let mut coord_rand = Xoroshiro::from_seed(0x5EED_C0DE_1234_5678);
|
||||
let coords: Vec<(f64, f64, f64)> = (0..2000)
|
||||
.map(|_| {
|
||||
(
|
||||
(coord_rand.next_f64() - 0.5) * 200_000.0,
|
||||
(coord_rand.next_f64() - 0.5) * 4_000.0,
|
||||
(coord_rand.next_f64() - 0.5) * 200_000.0,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut results = Vec::new();
|
||||
|
||||
for seed in [1u64, 2, 42, 12345, 987_654_321] {
|
||||
let mut rand = Xoroshiro::from_seed(seed);
|
||||
let sampler = PerlinNoiseSampler::new(&mut rand);
|
||||
for &(x, y, z) in &coords {
|
||||
results.push(sampler.sample_no_fade(x, y, z, 0.0, 0.0));
|
||||
results.push(sampler.sample_no_fade(x, y, z, 0.5, 100.0));
|
||||
}
|
||||
}
|
||||
|
||||
for seed in [7u64, 99, 555] {
|
||||
for legacy in [false, true] {
|
||||
let mut rand = Xoroshiro::from_seed(seed);
|
||||
let (start, amplitudes) =
|
||||
OctavePerlinNoiseSampler::calculate_amplitudes(&(-4..=2).collect::<Vec<i32>>());
|
||||
let sampler = OctavePerlinNoiseSampler::new(&mut rand, start, &litudes, legacy);
|
||||
for &(x, y, z) in coords.iter().take(500) {
|
||||
results.push(sampler.sample(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hash = fnv1a_hash_f64(results.into_iter());
|
||||
assert_eq!(
|
||||
hash, 0x2c36_e2de_2f21_9be7,
|
||||
"Perlin/OctavePerlin fingerprint changed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@ fn bench_noise_router_creation(c: &mut Criterion) {
|
||||
let proto_routers = ProtoNoiseRouters::generate(base_routers, &random_config);
|
||||
let proto_noise_router = proto_routers.noise;
|
||||
|
||||
// Production overworld cell geometry (384 block world height / 8 = 48 vertical
|
||||
// cells). the previous vertical_cell_count of 4 under-measured by ~12x.
|
||||
let builder_options =
|
||||
ChunkNoiseFunctionBuilderOptions::new(4, 8, 4, 4, 0, 0, 3, vec![], vec![], None);
|
||||
ChunkNoiseFunctionBuilderOptions::new(4, 8, 48, 4, 0, 0, 4, vec![], vec![], None);
|
||||
|
||||
// Benchmarking
|
||||
c.bench_function("noise_router_creation_with_pooling", |b| {
|
||||
|
||||
@@ -176,6 +176,18 @@ macro_rules! local_y {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! from_grid_xz {
|
||||
($grid:expr, $offset:expr) => {
|
||||
($grid << 4) + $offset
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! from_grid_y {
|
||||
($grid:expr, $offset:expr) => {
|
||||
$grid * 12 + $offset
|
||||
};
|
||||
}
|
||||
|
||||
pub struct WorldAquiferSampler {
|
||||
fluid_level_sampler: StandardChunkFluidLevelSampler,
|
||||
start_x: i32,
|
||||
@@ -185,6 +197,11 @@ pub struct WorldAquiferSampler {
|
||||
size_z: usize,
|
||||
levels: Box<[Option<FluidLevel>]>,
|
||||
packed_positions: Box<[i64]>,
|
||||
surface_level_sample_min_x: i32,
|
||||
surface_level_sample_min_z: i32,
|
||||
surface_level_sample_max_x: i32,
|
||||
surface_level_sample_max_z: i32,
|
||||
skip_sampling_above_y: Option<i32>,
|
||||
}
|
||||
|
||||
impl WorldAquiferSampler {
|
||||
@@ -225,6 +242,11 @@ impl WorldAquiferSampler {
|
||||
let end_z = local_xz!(chunk_pos::end_block_z(chunk_z)) + 1;
|
||||
let size_z = (end_z - start_z) as usize + 1;
|
||||
|
||||
let surface_level_sample_min_x = from_grid_xz!(start_x, 0);
|
||||
let surface_level_sample_min_z = from_grid_xz!(start_z, 0);
|
||||
let surface_level_sample_max_x = from_grid_xz!(end_x, 9);
|
||||
let surface_level_sample_max_z = from_grid_xz!(end_z, 9);
|
||||
|
||||
let cache_size = size_x * size_y * size_z;
|
||||
|
||||
let mut packed_positions = vec![0; cache_size];
|
||||
@@ -258,9 +280,48 @@ impl WorldAquiferSampler {
|
||||
size_z,
|
||||
levels: vec![None; cache_size as usize].into(),
|
||||
packed_positions: packed_positions.into(),
|
||||
surface_level_sample_min_x,
|
||||
surface_level_sample_min_z,
|
||||
surface_level_sample_max_x,
|
||||
surface_level_sample_max_z,
|
||||
skip_sampling_above_y: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_sampling_above_y(
|
||||
cache: &mut Option<i32>,
|
||||
surface_level_sample_min_x: i32,
|
||||
surface_level_sample_min_z: i32,
|
||||
surface_level_sample_max_x: i32,
|
||||
surface_level_sample_max_z: i32,
|
||||
height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||
) -> i32 {
|
||||
if let Some(y) = *cache {
|
||||
return y;
|
||||
}
|
||||
|
||||
let mut max_surface_level = i32::MIN;
|
||||
let mut z = surface_level_sample_min_z;
|
||||
while z <= surface_level_sample_max_z {
|
||||
let mut x = surface_level_sample_min_x;
|
||||
while x <= surface_level_sample_max_x {
|
||||
let level = height_estimator.estimate_height(x, z);
|
||||
if level > max_surface_level {
|
||||
max_surface_level = level;
|
||||
}
|
||||
x += 4;
|
||||
}
|
||||
z += 4;
|
||||
}
|
||||
|
||||
let adjusted_surface_level = max_surface_level + 8;
|
||||
let skip_sampling_above_grid_y = local_y!(adjusted_surface_level + 12) + 1;
|
||||
let y = from_grid_y!(skip_sampling_above_grid_y, 11) - 1;
|
||||
|
||||
*cache = Some(y);
|
||||
y
|
||||
}
|
||||
|
||||
fn checked_packed_position_index(&self, x: i32, y: i32, z: i32) -> Option<usize> {
|
||||
let local_x = usize::try_from(x - self.start_x).ok()?;
|
||||
let local_y = usize::try_from(y - self.start_y).ok()?;
|
||||
@@ -584,6 +645,19 @@ impl WorldAquiferSampler {
|
||||
let fluid_level = self
|
||||
.fluid_level_sampler
|
||||
.get_fluid_level(sample_x, sample_y, sample_z);
|
||||
let skip_sampling_above_y = Self::skip_sampling_above_y(
|
||||
&mut self.skip_sampling_above_y,
|
||||
self.surface_level_sample_min_x,
|
||||
self.surface_level_sample_min_z,
|
||||
self.surface_level_sample_max_x,
|
||||
self.surface_level_sample_max_z,
|
||||
height_estimator,
|
||||
);
|
||||
|
||||
if sample_y > skip_sampling_above_y {
|
||||
return (Some(fluid_level.get_block(sample_y).default_state), false);
|
||||
}
|
||||
|
||||
if fluid_level.get_block(sample_y) == &LAVA_BLOCK {
|
||||
return (Some(LAVA_BLOCK.default_state), false);
|
||||
}
|
||||
@@ -606,20 +680,21 @@ impl WorldAquiferSampler {
|
||||
let dy = block_pos::unpack_y(packed) - sample_y;
|
||||
let dz = block_pos::unpack_z(packed) - sample_z;
|
||||
let h = dx * dx + dy * dy + dz * dz;
|
||||
|
||||
if nearest[3].1 > h {
|
||||
nearest[3] = (packed, h);
|
||||
}
|
||||
if nearest[2].1 > h {
|
||||
nearest[3] = nearest[2];
|
||||
nearest[2] = (packed, h);
|
||||
}
|
||||
if nearest[1].1 > h {
|
||||
nearest[2] = nearest[1];
|
||||
nearest[1] = (packed, h);
|
||||
}
|
||||
if nearest[0].1 > h {
|
||||
nearest[1] = nearest[0];
|
||||
nearest[0] = (packed, h);
|
||||
if nearest[2].1 > h {
|
||||
nearest[3] = nearest[2];
|
||||
nearest[2] = (packed, h);
|
||||
}
|
||||
if nearest[1].1 > h {
|
||||
nearest[2] = nearest[1];
|
||||
nearest[1] = (packed, h);
|
||||
}
|
||||
if nearest[0].1 > h {
|
||||
nearest[1] = nearest[0];
|
||||
nearest[0] = (packed, h);
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::{
|
||||
chunk_noise_router::{ChunkNoiseFunctionComponent, MutableChunkNoiseFunctionComponentImpl},
|
||||
density_function::{IndexToNoisePos, NoiseFunctionComponentRange},
|
||||
};
|
||||
use pumpkin_util::math::{lerp, lerp3, vector3::Vector3};
|
||||
use pumpkin_util::math::{lerp, lerp2, lerp3, vector3::Vector3};
|
||||
|
||||
use crate::generation::{biome_coords, positions::chunk_pos};
|
||||
|
||||
@@ -369,10 +369,46 @@ impl MutableChunkNoiseFunctionComponentImpl for DensityInterpolator {
|
||||
sample_options: &mut ChunkNoiseFunctionSampleOptions,
|
||||
) {
|
||||
if sample_options.populating_caches {
|
||||
let mut cached_xy_delta: Option<(f64, f64)> = None;
|
||||
let mut cached_a = 0.0;
|
||||
let mut cached_b = 0.0;
|
||||
|
||||
array.iter_mut().enumerate().for_each(|(index, value)| {
|
||||
let pos = mapper.at(index, Some(sample_options));
|
||||
let result = self.sample(component_stack, &pos, sample_options);
|
||||
*value = result;
|
||||
|
||||
let SampleAction::CellCaches(WrapperData {
|
||||
x_delta,
|
||||
y_delta,
|
||||
z_delta,
|
||||
..
|
||||
}) = &sample_options.action
|
||||
else {
|
||||
*value = self.sample(component_stack, &pos, sample_options);
|
||||
return;
|
||||
};
|
||||
let (x_delta, y_delta, z_delta) = (*x_delta, *y_delta, *z_delta);
|
||||
|
||||
if cached_xy_delta != Some((x_delta, y_delta)) {
|
||||
cached_a = lerp2(
|
||||
x_delta,
|
||||
y_delta,
|
||||
self.first_pass[0],
|
||||
self.first_pass[4],
|
||||
self.first_pass[2],
|
||||
self.first_pass[6],
|
||||
);
|
||||
cached_b = lerp2(
|
||||
x_delta,
|
||||
y_delta,
|
||||
self.first_pass[1],
|
||||
self.first_pass[5],
|
||||
self.first_pass[3],
|
||||
self.first_pass[7],
|
||||
);
|
||||
cached_xy_delta = Some((x_delta, y_delta));
|
||||
}
|
||||
|
||||
*value = lerp(z_delta, cached_a, cached_b);
|
||||
});
|
||||
} else {
|
||||
ChunkNoiseFunctionComponent::fill_from_stack(
|
||||
@@ -721,19 +757,6 @@ impl CellCache {
|
||||
max_value,
|
||||
}
|
||||
}
|
||||
|
||||
/// Clones this instance, creating a new struct taking ownership of the cache and replacing the
|
||||
/// original with a dummy
|
||||
fn take_cache_clone(&mut self) -> Self {
|
||||
let mut cache: Box<[f64]> = Box::new([]);
|
||||
mem::swap(&mut cache, &mut self.cache);
|
||||
Self {
|
||||
input_index: self.input_index,
|
||||
cache,
|
||||
min_value: self.min_value,
|
||||
max_value: self.max_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ChunkSpecificNoiseFunctionComponent {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use pumpkin_data::noise_router::WrapperType;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
@@ -68,109 +66,10 @@ pub trait MutableChunkNoiseFunctionComponentImpl {
|
||||
pub enum ChunkNoiseFunctionComponent<'a> {
|
||||
Independent(&'a IndependentProtoNoiseFunctionComponent),
|
||||
Dependent(&'a DependentProtoNoiseFunctionComponent),
|
||||
// NOTE: The box here is intentional: we want to bring down the size to keep the component stack
|
||||
// smaller
|
||||
Chunk(ChunkSpecificNoiseFunctionComponent),
|
||||
PassThrough(PassThrough),
|
||||
//Panic(String),
|
||||
}
|
||||
|
||||
/*
|
||||
impl ChunkNoiseFunctionComponent<'_> {
|
||||
pub fn display_test(&self, stack: &[ChunkNoiseFunctionComponent<'_>]) -> String {
|
||||
match self {
|
||||
Self::Independent(independent) => match independent {
|
||||
IndependentProtoNoiseFunctionComponent::ClampedYGradient(_) => {
|
||||
"ClampedYGradient".into()
|
||||
}
|
||||
IndependentProtoNoiseFunctionComponent::InterpolatedNoise(_) => {
|
||||
"InterpolatedNoise".into()
|
||||
}
|
||||
IndependentProtoNoiseFunctionComponent::EndIsland(_) => "EndIsland".into(),
|
||||
IndependentProtoNoiseFunctionComponent::Constant(_) => "Constant".into(),
|
||||
IndependentProtoNoiseFunctionComponent::Noise(_) => "Noise".into(),
|
||||
IndependentProtoNoiseFunctionComponent::ShiftA(_) => "ShiftA".into(),
|
||||
IndependentProtoNoiseFunctionComponent::ShiftB(_) => "ShiftB".into(),
|
||||
},
|
||||
Self::Dependent(dependent) => match dependent {
|
||||
DependentProtoNoiseFunctionComponent::Spline(spine) => {
|
||||
let a = stack[spine.spline.input_index].display_test(stack);
|
||||
format!("Spline({})", a)
|
||||
}
|
||||
DependentProtoNoiseFunctionComponent::Unary(x) => {
|
||||
let a = stack[x.input_index].display_test(stack);
|
||||
format!("Unary({})", a)
|
||||
}
|
||||
DependentProtoNoiseFunctionComponent::ShiftedNoise(x) => {
|
||||
let a = stack[x.input_x_index].display_test(stack);
|
||||
let b = stack[x.input_y_index].display_test(stack);
|
||||
let c = stack[x.input_z_index].display_test(stack);
|
||||
format!("ShiftedNoise({}, {}, {})", a, b, c)
|
||||
}
|
||||
DependentProtoNoiseFunctionComponent::Linear(x) => {
|
||||
let a = stack[x.input_index].display_test(stack);
|
||||
format!("Linear({})", a)
|
||||
}
|
||||
DependentProtoNoiseFunctionComponent::Binary(x) => {
|
||||
let a = stack[x.input1_index].display_test(stack);
|
||||
let b = stack[x.input2_index].display_test(stack);
|
||||
format!("Binary({}, {})", a, b)
|
||||
}
|
||||
DependentProtoNoiseFunctionComponent::IntervalSelect(x) => {
|
||||
let a = stack[x.input_index].display_test(stack);
|
||||
format!("IntervalSelect({})", a)
|
||||
}
|
||||
DependentProtoNoiseFunctionComponent::Clamp(x) => {
|
||||
let a = stack[x.input_index].display_test(stack);
|
||||
format!("Clamp({})", a)
|
||||
}
|
||||
DependentProtoNoiseFunctionComponent::RangeChoice(x) => {
|
||||
let when_in = stack[x.when_in_index].display_test(stack);
|
||||
let when_out = stack[x.when_out_index].display_test(stack);
|
||||
format!("RangeChoice({}, {})", when_in, when_out)
|
||||
}
|
||||
DependentProtoNoiseFunctionComponent::FindTopSurface(_) => {
|
||||
format!("FindTopSurface")
|
||||
}
|
||||
},
|
||||
Self::Chunk(chunk) => match &**chunk {
|
||||
ChunkSpecificNoiseFunctionComponent::CellCache(x) => {
|
||||
let input = &stack[x.input_index];
|
||||
let input_display = input.display_test(stack);
|
||||
format!("CellCache({})", input_display)
|
||||
}
|
||||
ChunkSpecificNoiseFunctionComponent::Cache2D(x) => {
|
||||
let input = &stack[x.input_index];
|
||||
let input_display = input.display_test(stack);
|
||||
format!("Cache2D({})", input_display)
|
||||
}
|
||||
ChunkSpecificNoiseFunctionComponent::DensityInterpolator(x) => {
|
||||
let input = &stack[x.input_index];
|
||||
let input_display = input.display_test(stack);
|
||||
format!("DensityInterpolator({})", input_display)
|
||||
}
|
||||
ChunkSpecificNoiseFunctionComponent::FlatCache(x) => {
|
||||
let input = &stack[x.input_index];
|
||||
let input_display = input.display_test(stack);
|
||||
format!("FlatCache({})", input_display)
|
||||
}
|
||||
ChunkSpecificNoiseFunctionComponent::CacheOnce(x) => {
|
||||
let input = &stack[x.input_index];
|
||||
let input_display = input.display_test(stack);
|
||||
format!("CacheOnce({})", input_display)
|
||||
}
|
||||
},
|
||||
Self::PassThrough(x) => {
|
||||
let input = &stack[x.input_index()];
|
||||
let input_display = input.display_test(stack);
|
||||
format!("PassThrough({})", input_display)
|
||||
}
|
||||
Self::Panic(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl NoiseFunctionComponentRange for ChunkNoiseFunctionComponent<'_> {
|
||||
#[inline]
|
||||
fn min(&self) -> f64 {
|
||||
@@ -179,7 +78,6 @@ impl NoiseFunctionComponentRange for ChunkNoiseFunctionComponent<'_> {
|
||||
Self::Dependent(dependent) => dependent.min(),
|
||||
Self::Chunk(chunk) => chunk.min(),
|
||||
Self::PassThrough(pass_through) => pass_through.min(),
|
||||
//Self::Panic(message) => panic!("{}", message),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +88,6 @@ impl NoiseFunctionComponentRange for ChunkNoiseFunctionComponent<'_> {
|
||||
Self::Dependent(dependent) => dependent.max(),
|
||||
Self::Chunk(chunk) => chunk.max(),
|
||||
Self::PassThrough(pass_through) => pass_through.max(),
|
||||
//Self::Panic(message) => panic!("{}", message),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,7 +109,6 @@ impl MutableChunkNoiseFunctionComponentImpl for ChunkNoiseFunctionComponent<'_>
|
||||
pos,
|
||||
sample_options,
|
||||
),
|
||||
//Self::Panic(message) => panic!("{}", message),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,10 +136,6 @@ impl MutableChunkNoiseFunctionComponentImpl for ChunkNoiseFunctionComponent<'_>
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static TOPO_FILL_BUFFERS: RefCell<Vec<Vec<f64>>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
impl ChunkNoiseFunctionComponent<'_> {
|
||||
#[inline]
|
||||
pub fn sample_from_stack(
|
||||
|
||||
@@ -27,23 +27,6 @@ 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]
|
||||
fn calculate_min_and_max(&self, component_stack: &[ProtoNoiseFunctionComponent]) -> (f32, f32) {
|
||||
match self {
|
||||
@@ -217,75 +200,6 @@ impl Spline {
|
||||
|
||||
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 {
|
||||
|
||||
@@ -3,6 +3,8 @@ pub mod chunk_noise_router;
|
||||
pub mod density_function;
|
||||
pub mod find_top_surface;
|
||||
pub mod multi_noise_sampler;
|
||||
#[cfg(test)]
|
||||
mod parity_fingerprint_test;
|
||||
pub mod proto_noise_router;
|
||||
pub mod static_router;
|
||||
pub mod surface_height_sampler;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
use pumpkin_data::noise_router::OVERWORLD_BASE_NOISE_ROUTER;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
use crate::generation::GlobalRandomConfig;
|
||||
use crate::generation::noise::router::chunk_density_function::{
|
||||
ChunkNoiseFunctionBuilderOptions, ChunkNoiseFunctionSampleOptions, SampleAction,
|
||||
};
|
||||
use crate::generation::noise::router::chunk_noise_router::ChunkNoiseRouter;
|
||||
use crate::generation::noise::router::proto_noise_router::ProtoNoiseRouters;
|
||||
|
||||
fn fnv1a_hash_f64(values: impl Iterator<Item = f64>) -> u64 {
|
||||
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
|
||||
const FNV_PRIME: u64 = 0x0000_0100_0000_01B3;
|
||||
let mut hash = FNV_OFFSET;
|
||||
for value in values {
|
||||
for byte in value.to_bits().to_le_bytes() {
|
||||
hash ^= u64::from(byte);
|
||||
hash = hash.wrapping_mul(FNV_PRIME);
|
||||
}
|
||||
}
|
||||
hash
|
||||
}
|
||||
|
||||
/// This just detects if a mass set of hashes is the same as it was previously, should reliably detect regressions.
|
||||
#[test]
|
||||
fn overworld_density_fingerprint_is_stable() {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for seed in [0u64, 1, 42, 1_779_920_288_596_261_407] {
|
||||
let random_config = GlobalRandomConfig::new(seed, false);
|
||||
let proto_routers =
|
||||
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
|
||||
|
||||
let builder_options = ChunkNoiseFunctionBuilderOptions::new(
|
||||
4,
|
||||
8,
|
||||
48,
|
||||
4,
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
None,
|
||||
);
|
||||
let mut router = ChunkNoiseRouter::generate(&proto_routers.noise, &builder_options);
|
||||
|
||||
let options =
|
||||
ChunkNoiseFunctionSampleOptions::new(false, SampleAction::SkipCellCaches, 0, 0, 0);
|
||||
|
||||
for x in (-64..64).step_by(11) {
|
||||
for y in (-64..320).step_by(19) {
|
||||
for z in (-64..64).step_by(13) {
|
||||
let pos = Vector3::new(x, y, z);
|
||||
|
||||
results.push(router.final_density(&pos, &options));
|
||||
results.push(router.vein_toggle(&pos, &options));
|
||||
results.push(router.vein_ridged(&pos, &options));
|
||||
results.push(router.vein_gap(&pos, &options));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hash = fnv1a_hash_f64(results.into_iter());
|
||||
assert_eq!(
|
||||
hash, 0x31d6_54d7_22dd_0134,
|
||||
"Overworld density fingerprint changed"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user