1use std::ops;
7use std::simd::cmp::{SimdPartialEq, SimdPartialOrd};
8use std::simd::f64x4;
9use std::simd::num::SimdFloat;
10use std::simd::{Mask, Simd, SimdCast, SimdElement, StdFloat};
11
12use crate::noise::PerlinNoise;
13use crate::random::{PositionalRandom, RandomSource, RandomSplitter, name_hash::NameHash};
14
15#[expect(
21 clippy::unreadable_literal,
22 reason = "exact vanilla constant; underscores would obscure precision"
23)]
24pub const INPUT_FACTOR: f64 = 1.0181268882175227;
25
26#[expect(
33 clippy::unreadable_literal,
34 reason = "exact vanilla constant; underscores would obscure precision"
35)]
36const VALUE_FACTOR_NUMERATOR: f64 = 0.16666666666666666;
37
38#[derive(Debug, Clone)]
43pub struct NormalNoise {
44 first: PerlinNoise,
46 second: PerlinNoise,
48 value_factor: f64,
50 max_value: f64,
52}
53
54impl NormalNoise {
55 #[must_use]
63 pub fn create_from_random(
64 random: &mut RandomSource,
65 first_octave: i32,
66 amplitudes: &[f64],
67 ) -> Self {
68 let first = PerlinNoise::create_from_random(random, first_octave, amplitudes);
69 let second = PerlinNoise::create_from_random(random, first_octave, amplitudes);
70
71 Self::finish(first, second, amplitudes)
72 }
73
74 #[must_use]
79 pub fn create(
80 splitter: &RandomSplitter,
81 noise_id: &str,
82 first_octave: i32,
83 amplitudes: &[f64],
84 ) -> Self {
85 let mut random = splitter.with_hash_of(&NameHash::new(noise_id));
86 Self::create_from_random(&mut random, first_octave, amplitudes)
87 }
88
89 #[must_use]
96 pub fn create_legacy_nether_biome(
97 random: &mut RandomSource,
98 first_octave: i32,
99 amplitudes: &[f64],
100 ) -> Self {
101 let first = PerlinNoise::create_legacy_for_nether(random, first_octave, amplitudes);
102 let second = PerlinNoise::create_legacy_for_nether(random, first_octave, amplitudes);
103
104 Self::finish(first, second, amplitudes)
105 }
106
107 fn finish(first: PerlinNoise, second: PerlinNoise, amplitudes: &[f64]) -> Self {
109 let mut min_octave = i32::MAX;
111 let mut max_octave = i32::MIN;
112 for (i, &) in amplitudes.iter().enumerate() {
113 if amp != 0.0 {
114 min_octave = min_octave.min(i as i32);
115 max_octave = max_octave.max(i as i32);
116 }
117 }
118
119 if min_octave == i32::MAX {
121 return Self {
122 first,
123 second,
124 value_factor: 0.0,
125 max_value: 0.0,
126 };
127 }
128
129 let octave_span = max_octave - min_octave;
131 let value_factor = VALUE_FACTOR_NUMERATOR / expected_deviation(octave_span);
132 let max_value = (first.max_value() + second.max_value()) * value_factor;
133
134 Self {
135 first,
136 second,
137 value_factor,
138 max_value,
139 }
140 }
141
142 #[inline]
150 #[must_use]
151 pub fn get_value(&self, x: f64, y: f64, z: f64) -> f64 {
152 let x2 = x * INPUT_FACTOR;
153 let y2 = y * INPUT_FACTOR;
154 let z2 = z * INPUT_FACTOR;
155 (self.first.get_value(x, y, z) + self.second.get_value(x2, y2, z2)) * self.value_factor
156 }
157
158 #[inline]
160 #[must_use]
161 pub fn get_value_simd<F, const N: usize>(
162 &self,
163 x: Simd<F, N>,
164 y: Simd<F, N>,
165 z: Simd<F, N>,
166 ) -> Simd<F, N>
167 where
168 F: SimdElement + SimdCast,
169 Simd<F, N>: SimdFloat<Cast<i32> = Simd<i32, N>>
170 + SimdPartialOrd
171 + SimdPartialEq<Mask = Mask<<F as SimdElement>::Mask, N>>
172 + ops::Add<Output = Simd<F, N>>
173 + ops::Sub<Output = Simd<F, N>>
174 + ops::Mul<Output = Simd<F, N>>
175 + ops::Div<Output = Simd<F, N>>
176 + ops::Neg<Output = Simd<F, N>>
177 + StdFloat,
178 {
179 let x2 = x * Simd::splat(INPUT_FACTOR).cast::<F>();
180 let y2 = y * Simd::splat(INPUT_FACTOR).cast::<F>();
181 let z2 = z * Simd::splat(INPUT_FACTOR).cast::<F>();
182 (self.first.get_value_simd(x, y, z) + self.second.get_value_simd(x2, y2, z2))
183 * Simd::splat(self.value_factor).cast()
184 }
185
186 #[inline]
188 #[must_use]
189 pub fn get_value_xz(&self, x: f64, z: f64) -> f64 {
190 let x2 = x * INPUT_FACTOR;
191 let z2 = z * INPUT_FACTOR;
192 (self.first.get_value_xz(x, z) + self.second.get_value_xz(x2, z2)) * self.value_factor
193 }
194
195 #[inline]
197 #[must_use]
198 pub fn get_value_xy(&self, x: f64, y: f64) -> f64 {
199 let x2 = x * INPUT_FACTOR;
200 let y2 = y * INPUT_FACTOR;
201 (self.first.get_value_xy(x, y) + self.second.get_value_xy(x2, y2)) * self.value_factor
202 }
203
204 #[inline]
206 #[must_use]
207 pub fn get_value_y_4x(&self, x: f64, ys: f64x4, z: f64) -> f64x4 {
208 let x2 = x * INPUT_FACTOR;
209 let ys2 = ys * f64x4::splat(INPUT_FACTOR);
210 let z2 = z * INPUT_FACTOR;
211 (self
212 .first
213 .get_value_with_y_params_4x(x, ys, z, 0.0, 0.0, false)
214 + self
215 .second
216 .get_value_with_y_params_4x(x2, ys2, z2, 0.0, 0.0, false))
217 * f64x4::splat(self.value_factor)
218 }
219
220 #[inline]
227 #[must_use]
228 pub fn get_value_y_simd<const N: usize>(
229 &self,
230 x: f64,
231 ys: Simd<f64, N>,
232 z: f64,
233 ) -> Simd<f64, N> {
234 let x2 = x * INPUT_FACTOR;
235 let ys2 = ys * Simd::splat(INPUT_FACTOR);
236 let z2 = z * INPUT_FACTOR;
237 (self
238 .first
239 .get_value_with_y_params_simd::<N>(x, ys, z, 0.0, 0.0, false)
240 + self
241 .second
242 .get_value_with_y_params_simd::<N>(x2, ys2, z2, 0.0, 0.0, false))
243 * Simd::splat(self.value_factor)
244 }
245
246 #[inline]
248 #[must_use]
249 pub const fn max_value(&self) -> f64 {
250 self.max_value
251 }
252}
253
254#[inline]
259fn expected_deviation(octave_span: i32) -> f64 {
260 0.1 * (1.0 + 1.0 / f64::from(octave_span + 1))
261}
262
263#[cfg(test)]
264#[expect(
265 clippy::unreadable_literal,
266 reason = "test vectors from vanilla; underscores would obscure precision"
267)]
268mod tests {
269 use super::*;
270 use crate::random::{Random, xoroshiro::Xoroshiro};
271 use std::simd::f64x4;
272
273 #[test]
274 fn test_normal_noise_deterministic() {
275 let mut rng = Xoroshiro::from_seed(12345);
276 let splitter = rng.next_positional();
277
278 let amplitudes = [1.0, 1.0, 1.0];
279 let noise1 = NormalNoise::create(&splitter, "test_noise", -3, &litudes);
280 let noise2 = NormalNoise::create(&splitter, "test_noise", -3, &litudes);
281
282 let v1 = noise1.get_value(100.0, 64.0, 100.0);
283 let v2 = noise2.get_value(100.0, 64.0, 100.0);
284 assert!((v1 - v2).abs() < 1e-15);
285 }
286
287 #[test]
288 fn test_normal_noise_spatial_variation() {
289 let mut rng = Xoroshiro::from_seed(42);
290 let splitter = rng.next_positional();
291
292 let noise = NormalNoise::create(&splitter, "test_noise", -4, &[1.0, 1.0, 1.0, 1.0]);
293
294 let values: Vec<f64> = (0..10)
296 .map(|i| noise.get_value(f64::from(i) * 50.0, 64.0, f64::from(i) * 50.0))
297 .collect();
298
299 let min = values.iter().copied().fold(f64::INFINITY, f64::min);
301 let max = values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
302 assert!(max - min > 0.01, "Noise should have spatial variation");
303 }
304
305 #[test]
306 fn test_first_and_second_differ() {
307 let mut rng = Xoroshiro::from_seed(12345);
308 let splitter = rng.next_positional();
309
310 let noise = NormalNoise::create(&splitter, "test_noise", -3, &[1.0, 1.0, 1.0]);
311
312 let v1 = noise.get_value(1000.0, 0.0, 1000.0);
315 let v2 = noise.get_value(1001.0, 0.0, 1000.0);
316 assert!((v1 - v2).abs() > 0.0001);
318 }
319
320 #[test]
321 fn test_get_value_simd_matches_scalar() {
322 let mut rng = Xoroshiro::from_seed(98_765);
323 let splitter = rng.next_positional();
324 let noise = NormalNoise::create(&splitter, "simd_xyz", -6, &[1.0, 0.0, 1.0, 1.0, 0.5]);
325 let xs = [0.0, 1.25, -1000.0, 33_554_431.5];
326 let ys = [0.0, 64.5, -32.25, 255.75];
327 let zs = [0.0, -30.75, 4096.5, -33_554_432.25];
328
329 let simd = noise.get_value_simd(
330 f64x4::from_array(xs),
331 f64x4::from_array(ys),
332 f64x4::from_array(zs),
333 );
334
335 for i in 0..4 {
336 let scalar = noise.get_value(xs[i], ys[i], zs[i]);
337 #[expect(
338 clippy::float_cmp,
339 reason = "SIMD path must be bit-identical to scalar noise for vanilla determinism"
340 )]
341 let matches = scalar == simd[i];
342 assert!(
343 matches,
344 "Mismatch at ({}, {}, {}): scalar={}, simd={}",
345 xs[i], ys[i], zs[i], scalar, simd[i],
346 );
347 }
348 }
349
350 #[test]
351 fn test_zero_axis_helpers_match_full_noise() {
352 let mut rng = Xoroshiro::from_seed(98_765);
353 let splitter = rng.next_positional();
354 let noise = NormalNoise::create(&splitter, "zero_axis", -6, &[1.0, 0.0, 1.0, 1.0, 0.5]);
355 let samples = [
356 (0.0, 0.0),
357 (1.25, -30.75),
358 (-1000.0, 4096.5),
359 (33_554_431.5, -33_554_432.25),
360 (-0.000_000_1, 0.000_000_1),
361 ];
362
363 for &(a, b) in &samples {
364 #[expect(
365 clippy::float_cmp,
366 reason = "zero-axis helpers must be bit-identical to the full scalar path"
367 )]
368 {
369 assert_eq!(noise.get_value_xz(a, b), noise.get_value(a, 0.0, b));
370 assert_eq!(noise.get_value_xy(a, b), noise.get_value(a, b, 0.0));
371 }
372 }
373 }
374
375 #[test]
376 fn test_expected_deviation() {
377 assert!((expected_deviation(0) - 0.2).abs() < 1e-10);
379 assert!((expected_deviation(1) - 0.15).abs() < 1e-10);
380 assert!((expected_deviation(2) - 0.13333333333333333).abs() < 1e-10);
381 }
382
383 #[test]
384 fn test_input_factor() {
385 assert!((INPUT_FACTOR - 1.0181268882175227).abs() < 1e-15);
387 }
388
389 #[test]
390 fn test_get_value_4x_matches_scalar() {
391 let mut rng = Xoroshiro::from_seed(54321);
392 let splitter = rng.next_positional();
393 let noise = NormalNoise::create(&splitter, "test_4x", -7, &[1.0; 8]);
394
395 let test_cases: &[(f64, [f64; 4], f64)] = &[
397 (0.0, [0.0, 8.0, 16.0, 24.0], 0.0),
398 (12.5, [-5.0, 10.0, 25.0, 40.0], 7.25),
399 (-100.5, [64.0, 65.0, 66.0, 67.0], 200.0),
400 (1.0, [0.0; 4], -1.0),
401 ];
402
403 for &(x, ys, z) in test_cases {
404 let ys_v = f64x4::from_array(ys);
405 let simd = noise.get_value_y_4x(x, ys_v, z);
406 let generic = noise.get_value_y_simd(x, ys_v, z);
407 for i in 0..4 {
408 let scalar = noise.get_value(x, ys[i], z);
409 let simd_val = simd[i];
410 let generic_val = generic[i];
411 #[expect(
412 clippy::float_cmp,
413 reason = "SIMD/scalar paths must produce bit-identical results for vanilla determinism"
414 )]
415 let bit_match = scalar == simd_val;
416 assert!(
417 bit_match,
418 "Mismatch at x={x}, y={}, z={z}: scalar={scalar}, simd={simd_val}",
419 ys[i]
420 );
421 #[expect(
422 clippy::float_cmp,
423 reason = "explicit 4x and generic SIMD paths should be equivalent"
424 )]
425 let generic_match = simd_val == generic_val;
426 assert!(
427 generic_match,
428 "Generic mismatch at x={x}, y={}, z={z}: 4x={simd_val}, generic={generic_val}",
429 ys[i]
430 );
431 }
432 }
433 }
434}