use std::simd::Simd;
use std::simd::cmp::SimdPartialOrd;
use crate::util::math::{clamped_lerp, clamped_lerp_simd, wrap, wrap_simd};
use crate::util::noise::PerlinNoise;
use crate::util::rng::RandomSource;
const COORDINATE_SCALE: f64 = 684.412;
#[derive(Debug, Clone)]
pub struct BlendedNoise {
min_limit_noise: PerlinNoise,
max_limit_noise: PerlinNoise,
main_noise: PerlinNoise,
xz_multiplier: f64,
y_multiplier: f64,
xz_factor: f64,
y_factor: f64,
smear_scale_multiplier: f64,
max_value: f64,
}
#[derive(Debug, Clone, Copy)]
pub struct BlendedNoiseSnapshot<'a> {
pub min_limit_noise: &'a PerlinNoise,
pub max_limit_noise: &'a PerlinNoise,
pub main_noise: &'a PerlinNoise,
pub xz_multiplier: f64,
pub y_multiplier: f64,
pub xz_factor: f64,
pub y_factor: f64,
pub smear_scale_multiplier: f64,
}
impl BlendedNoise {
pub fn new(
random: &mut RandomSource,
xz_scale: f64,
y_scale: f64,
xz_factor: f64,
y_factor: f64,
smear_scale_multiplier: f64,
) -> Self {
let min_limit_noise = PerlinNoise::create_legacy_for_nether(random, -15, &[1.0; 16]);
let max_limit_noise = PerlinNoise::create_legacy_for_nether(random, -15, &[1.0; 16]);
let main_noise = PerlinNoise::create_legacy_for_nether(random, -7, &[1.0; 8]);
let xz_multiplier = COORDINATE_SCALE * xz_scale;
let y_multiplier = COORDINATE_SCALE * y_scale;
let max_value = min_limit_noise.max_broken_value(y_multiplier);
Self {
min_limit_noise,
max_limit_noise,
main_noise,
xz_multiplier,
y_multiplier,
xz_factor,
y_factor,
smear_scale_multiplier,
max_value,
}
}
pub fn compute(&self, block_x: f64, block_y: f64, block_z: f64) -> f64 {
let limit_x = block_x * self.xz_multiplier;
let limit_y = block_y * self.y_multiplier;
let limit_z = block_z * self.xz_multiplier;
let main_x = limit_x / self.xz_factor;
let main_y = limit_y / self.y_factor;
let main_z = limit_z / self.xz_factor;
let limit_smear = self.y_multiplier * self.smear_scale_multiplier;
let main_smear = limit_smear / self.y_factor;
let mut main_noise_value = 0.0;
let mut octave_scale = 1.0;
for octave in 0..8 {
if let Some(noise) = self.main_noise.get_octave_noise(octave) {
main_noise_value += noise.noise_with_y_scale(
wrap(main_x * octave_scale),
wrap(main_y * octave_scale),
wrap(main_z * octave_scale),
main_smear * octave_scale,
main_y * octave_scale,
) / octave_scale;
}
octave_scale /= 2.0;
}
let factor = f64::midpoint(main_noise_value / 10.0, 1.0);
let is_max = factor >= 1.0;
let is_min = factor <= 0.0;
let mut blend_min = 0.0;
let mut blend_max = 0.0;
let mut pow = 1.0;
for i in 0..16 {
let wx = wrap(limit_x * pow);
let wy = wrap(limit_y * pow);
let wz = wrap(limit_z * pow);
let y_scale_pow = limit_smear * pow;
if !is_max && let Some(noise) = self.min_limit_noise.get_octave_noise(i) {
blend_min += noise.noise_with_y_scale(wx, wy, wz, y_scale_pow, limit_y * pow) / pow;
}
if !is_min && let Some(noise) = self.max_limit_noise.get_octave_noise(i) {
blend_max += noise.noise_with_y_scale(wx, wy, wz, y_scale_pow, limit_y * pow) / pow;
}
pow /= 2.0;
}
clamped_lerp(blend_min / 512.0, blend_max / 512.0, factor) / 128.0
}
#[inline]
pub fn compute_simd<const N: usize>(
&self,
block_x: f64,
block_ys: [f64; N],
block_z: f64,
) -> [f64; N] {
let limit_x = block_x * self.xz_multiplier;
let limit_ys = Simd::from_array(block_ys) * Simd::splat(self.y_multiplier);
let limit_z = block_z * self.xz_multiplier;
let main_x = limit_x / self.xz_factor;
let main_ys = limit_ys / Simd::splat(self.y_factor);
let main_z = limit_z / self.xz_factor;
let limit_smear = self.y_multiplier * self.smear_scale_multiplier;
let main_smear = limit_smear / self.y_factor;
let mut main_noise_values = Simd::splat(0.0);
let mut pow = 1.0;
for i in 0..8 {
if let Some(noise) = self.main_noise.get_octave_noise(i) {
let pow_v = Simd::splat(pow);
let scaled_ys = main_ys * pow_v;
main_noise_values += noise.noise_with_y_scale_simd(
wrap(main_x * pow),
wrap_simd(scaled_ys),
wrap(main_z * pow),
main_smear * pow,
scaled_ys,
) / pow_v;
}
pow /= 2.0;
}
let factors = (main_noise_values / Simd::splat(10.0) + Simd::splat(1.0)) / Simd::splat(2.0);
let all_max = factors.simd_ge(Simd::splat(1.0)).all();
let all_min = factors.simd_le(Simd::splat(0.0)).all();
let mut blend_min = Simd::splat(0.0);
let mut blend_max = Simd::splat(0.0);
pow = 1.0;
for i in 0..16 {
let pow_v = Simd::splat(pow);
let scaled_ys = limit_ys * pow_v;
let wx = wrap(limit_x * pow);
let wys = wrap_simd(scaled_ys);
let wz = wrap(limit_z * pow);
let y_scale_pow = limit_smear * pow;
if !all_max && let Some(noise) = self.min_limit_noise.get_octave_noise(i) {
blend_min +=
noise.noise_with_y_scale_simd(wx, wys, wz, y_scale_pow, scaled_ys) / pow_v;
}
if !all_min && let Some(noise) = self.max_limit_noise.get_octave_noise(i) {
blend_max +=
noise.noise_with_y_scale_simd(wx, wys, wz, y_scale_pow, scaled_ys) / pow_v;
}
pow /= 2.0;
}
let min_scaled = blend_min / Simd::splat(512.0);
let max_scaled = blend_max / Simd::splat(512.0);
let result = clamped_lerp_simd(min_scaled, max_scaled, factors) / Simd::splat(128.0);
result.to_array()
}
#[inline]
pub const fn max_value(&self) -> f64 {
self.max_value
}
#[inline]
pub fn min_value(&self) -> f64 {
-self.max_value
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::rng::xoroshiro::XoroshiroRandomSource;
fn rng(seed: u64) -> RandomSource {
RandomSource::Xoroshiro(XoroshiroRandomSource::from_seed(seed))
}
#[test]
fn deterministic_for_same_seed() {
let bn1 = BlendedNoise::new(&mut rng(12345), 1.0, 1.0, 80.0, 160.0, 8.0);
let bn2 = BlendedNoise::new(&mut rng(12345), 1.0, 1.0, 80.0, 160.0, 8.0);
assert!((bn1.compute(0.0, 64.0, 0.0) - bn2.compute(0.0, 64.0, 0.0)).abs() < 1e-15);
}
#[test]
fn compute_4x_matches_scalar() {
let bn = BlendedNoise::new(&mut rng(42), 1.0, 1.0, 80.0, 160.0, 8.0);
let cases: &[(f64, [f64; 4], f64)] = &[
(0.0, [0.0, 8.0, 16.0, 24.0], 0.0),
(16.0, [32.0, 40.0, 48.0, 56.0], 16.0),
(-8.0, [-64.0, -32.0, 0.0, 32.0], 8.0),
];
for &(x, ys, z) in cases {
let simd = bn.compute_simd(x, ys, z);
for i in 0..4 {
let scalar = bn.compute(x, ys[i], z);
assert!((scalar - simd[i]).abs() < 1e-12);
}
}
}
#[test]
fn spatial_variation() {
let bn = BlendedNoise::new(&mut rng(42), 1.0, 1.0, 80.0, 160.0, 8.0);
let values: Vec<f64> = (-5..5)
.map(|x| bn.compute(f64::from(x * 16), 64.0, 0.0))
.collect();
let min = values.iter().copied().fold(f64::INFINITY, f64::min);
let max = values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
assert!(max - min > 1e-6);
}
}