1use std::ops;
7use std::simd::cmp::{SimdPartialEq, SimdPartialOrd};
8use std::simd::num::SimdFloat;
9use std::simd::{Mask, Simd, SimdCast, SimdElement, StdFloat, f64x4};
10
11use crate::noise::ImprovedNoise;
12use crate::random::{PositionalRandom, Random, RandomSource, RandomSplitter, name_hash::NameHash};
13use steel_math::{wrap, wrap_simd};
14
15#[derive(Debug, Clone)]
20pub struct PerlinNoise {
21 noise_levels: Vec<Option<ImprovedNoise>>,
24 amplitudes: Vec<f64>,
27 active_octaves: Vec<ActiveOctave>,
31 lowest_freq_value_factor: f64,
34 max_value: f64,
36}
37
38#[derive(Debug, Clone)]
43struct ActiveOctave {
44 noise: ImprovedNoise,
45 input_factor: f64,
47 output_factor: f64,
51}
52
53impl PerlinNoise {
54 #[must_use]
60 pub fn create(splitter: &RandomSplitter, first_octave: i32, amplitudes: &[f64]) -> Self {
61 let octaves = amplitudes.len();
62 let zero_octave_index = (-first_octave) as usize;
63
64 let mut noise_levels = vec![None; octaves];
65
66 for i in 0..octaves {
67 if amplitudes[i] != 0.0 {
68 let octave = first_octave + i as i32;
69 let name = format!("octave_{octave}");
70 let mut octave_random = splitter.with_hash_of(&NameHash::new(&name));
71 noise_levels[i] = Some(ImprovedNoise::new(&mut octave_random));
72 }
73 }
74
75 Self::from_parts(noise_levels, amplitudes, zero_octave_index)
76 }
77
78 #[must_use]
88 pub fn create_from_random(
89 random: &mut RandomSource,
90 first_octave: i32,
91 amplitudes: &[f64],
92 ) -> Self {
93 let octaves = amplitudes.len();
94 let zero_octave_index = (-first_octave) as usize;
95
96 let splitter = random.next_positional();
99
100 let mut noise_levels = vec![None; octaves];
101
102 for i in 0..octaves {
103 if amplitudes[i] != 0.0 {
104 let octave = first_octave + i as i32;
105 let name = format!("octave_{octave}");
106 let mut octave_random = splitter.with_hash_of(&NameHash::new(&name));
107 noise_levels[i] = Some(ImprovedNoise::new(&mut octave_random));
108 }
109 }
110
111 Self::from_parts(noise_levels, amplitudes, zero_octave_index)
112 }
113
114 #[must_use]
120 pub fn create_legacy_for_nether(
121 random: &mut RandomSource,
122 first_octave: i32,
123 amplitudes: &[f64],
124 ) -> Self {
125 let octaves = amplitudes.len();
126 let zero_octave_index = (-first_octave) as usize;
127
128 let mut noise_levels = vec![None; octaves];
129
130 if zero_octave_index < octaves && amplitudes[zero_octave_index] != 0.0 {
131 noise_levels[zero_octave_index] = Some(ImprovedNoise::new(random));
132 } else {
133 random.consume_count(262);
134 }
135
136 for ix in (0..zero_octave_index).rev() {
137 if ix < octaves && amplitudes[ix] != 0.0 {
138 noise_levels[ix] = Some(ImprovedNoise::new(random));
139 } else {
140 random.consume_count(262);
141 }
142 }
143
144 Self::from_parts(noise_levels, amplitudes, zero_octave_index)
145 }
146
147 #[must_use]
149 fn from_parts(
150 noise_levels: Vec<Option<ImprovedNoise>>,
151 amplitudes: &[f64],
152 zero_octave_index: usize,
153 ) -> Self {
154 let octaves = amplitudes.len();
155
156 let lowest_freq_input_factor = 2.0_f64.powi(-(zero_octave_index as i32));
159
160 let lowest_freq_value_factor =
162 2.0_f64.powi((octaves - 1) as i32) / (2.0_f64.powi(octaves as i32) - 1.0);
163
164 let max_value = Self::edge_value(amplitudes, lowest_freq_value_factor, 2.0);
166
167 let mut active_octaves = Vec::with_capacity(noise_levels.len());
170 let mut input_factor = lowest_freq_input_factor;
171 let mut value_factor = lowest_freq_value_factor;
172 for (i, noise_opt) in noise_levels.iter().enumerate() {
173 if let Some(noise) = noise_opt {
174 active_octaves.push(ActiveOctave {
175 noise: noise.clone(),
176 input_factor,
177 output_factor: amplitudes[i] * value_factor,
178 });
179 }
180 input_factor *= 2.0;
181 value_factor /= 2.0;
182 }
183
184 Self {
185 noise_levels,
186 amplitudes: amplitudes.to_vec(),
187 active_octaves,
188 lowest_freq_value_factor,
189 max_value,
190 }
191 }
192
193 fn edge_value(amplitudes: &[f64], lowest_freq_value_factor: f64, noise_value: f64) -> f64 {
195 let mut value = 0.0;
196 let mut value_factor = lowest_freq_value_factor;
197
198 for &litude in amplitudes {
199 if amplitude != 0.0 {
200 value += amplitude * noise_value * value_factor;
201 }
202 value_factor /= 2.0;
203 }
204
205 value
206 }
207
208 #[inline]
210 #[must_use]
211 pub fn get_value(&self, x: f64, y: f64, z: f64) -> f64 {
212 let mut value = 0.0;
213
214 for octave in &self.active_octaves {
215 let input_factor = octave.input_factor;
216 let noise_val = octave.noise.noise(
217 wrap(x * input_factor),
218 wrap(y * input_factor),
219 wrap(z * input_factor),
220 );
221 value += octave.output_factor * noise_val;
222 }
223
224 value
225 }
226
227 #[inline]
229 #[must_use]
230 pub fn get_value_xz(&self, x: f64, z: f64) -> f64 {
231 let mut value = 0.0;
232
233 for octave in &self.active_octaves {
234 let input_factor = octave.input_factor;
235 let noise_val = octave
236 .noise
237 .noise_xz(wrap(x * input_factor), wrap(z * input_factor));
238 value += octave.output_factor * noise_val;
239 }
240
241 value
242 }
243
244 #[inline]
246 #[must_use]
247 pub fn get_value_xy(&self, x: f64, y: f64) -> f64 {
248 let mut value = 0.0;
249
250 for octave in &self.active_octaves {
251 let input_factor = octave.input_factor;
252 let noise_val = octave
253 .noise
254 .noise_xy(wrap(x * input_factor), wrap(y * input_factor));
255 value += octave.output_factor * noise_val;
256 }
257
258 value
259 }
260
261 #[inline]
263 #[must_use]
264 pub fn get_value_simd<F, const N: usize>(
265 &self,
266 x: Simd<F, N>,
267 y: Simd<F, N>,
268 z: Simd<F, N>,
269 ) -> Simd<F, N>
270 where
271 F: SimdElement + SimdCast,
272 Simd<F, N>: SimdFloat<Cast<i32> = Simd<i32, N>>
273 + SimdPartialOrd
274 + SimdPartialEq<Mask = Mask<<F as SimdElement>::Mask, N>>
275 + ops::Add<Output = Simd<F, N>>
276 + ops::Sub<Output = Simd<F, N>>
277 + ops::Mul<Output = Simd<F, N>>
278 + ops::Div<Output = Simd<F, N>>
279 + ops::Neg<Output = Simd<F, N>>
280 + StdFloat,
281 {
282 let mut value = Simd::splat(0.0).cast();
283
284 for octave in &self.active_octaves {
285 let input_factor = Simd::splat(octave.input_factor).cast();
286 let noise_val = octave.noise.noise_simd(
287 wrap_simd(x * input_factor),
288 wrap_simd(y * input_factor),
289 wrap_simd(z * input_factor),
290 );
291 value += Simd::splat(octave.output_factor).cast() * noise_val;
292 }
293
294 value
295 }
296
297 #[must_use]
305 pub fn get_value_with_y_params(
306 &self,
307 x: f64,
308 y: f64,
309 z: f64,
310 y_scale: f64,
311 y_fudge: f64,
312 y_flat_hack: bool,
313 ) -> f64 {
314 let mut value = 0.0;
315
316 for octave in &self.active_octaves {
317 let input_factor = octave.input_factor;
318 let noise = &octave.noise;
319 let noise_val = noise.noise_with_y_scale(
320 wrap(x * input_factor),
321 if y_flat_hack {
322 -noise.yo
323 } else {
324 wrap(y * input_factor)
325 },
326 wrap(z * input_factor),
327 y_scale * input_factor,
328 y_fudge * input_factor,
329 );
330 value += octave.output_factor * noise_val;
331 }
332
333 value
334 }
335
336 #[must_use]
344 pub fn get_value_with_y_params_4x(
345 &self,
346 x: f64,
347 ys: f64x4,
348 z: f64,
349 y_scale: f64,
350 y_fudge: f64,
351 y_flat_hack: bool,
352 ) -> f64x4 {
353 let mut value = f64x4::splat(0.0);
354
355 for octave in &self.active_octaves {
356 let input_factor = octave.input_factor;
357 let noise = &octave.noise;
358 let x_w = wrap(x * input_factor);
359 let z_w = wrap(z * input_factor);
360 let ys_for_call = if y_flat_hack {
361 f64x4::splat(-noise.yo)
362 } else {
363 wrap_simd(ys * f64x4::splat(input_factor))
364 };
365 let y_fudges = f64x4::splat(y_fudge * input_factor);
366 let noise_val = noise.noise_with_y_scale_simd(
367 x_w,
368 ys_for_call,
369 z_w,
370 y_scale * input_factor,
371 y_fudges,
372 );
373 value += f64x4::splat(octave.output_factor) * noise_val;
374 }
375
376 value
377 }
378
379 #[must_use]
382 pub fn get_value_with_y_params_simd<const N: usize>(
383 &self,
384 x: f64,
385 ys: Simd<f64, N>,
386 z: f64,
387 y_scale: f64,
388 y_fudge: f64,
389 y_flat_hack: bool,
390 ) -> Simd<f64, N> {
391 let mut value = Simd::splat(0.0);
392
393 for octave in &self.active_octaves {
394 let input_factor = octave.input_factor;
395 let noise = &octave.noise;
396 let x_w = wrap(x * input_factor);
397 let z_w = wrap(z * input_factor);
398 let ys_for_call = if y_flat_hack {
399 Simd::splat(-noise.yo)
400 } else {
401 wrap_simd(ys * Simd::splat(input_factor))
402 };
403 let y_fudges = Simd::splat(y_fudge * input_factor);
404 let noise_val = noise.noise_with_y_scale_simd(
405 x_w,
406 ys_for_call,
407 z_w,
408 y_scale * input_factor,
409 y_fudges,
410 );
411 value += Simd::splat(octave.output_factor) * noise_val;
412 }
413
414 value
415 }
416
417 #[inline]
419 #[must_use]
420 pub const fn max_value(&self) -> f64 {
421 self.max_value
422 }
423
424 #[must_use]
429 pub fn max_broken_value(&self, y_scale: f64) -> f64 {
430 Self::edge_value(
431 &self.amplitudes,
432 self.lowest_freq_value_factor,
433 y_scale + 2.0,
434 )
435 }
436
437 #[must_use]
441 pub fn get_octave_noise(&self, i: usize) -> Option<&ImprovedNoise> {
442 self.noise_levels
443 .get(self.noise_levels.len() - 1 - i)
444 .and_then(|opt| opt.as_ref())
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451 use crate::random::{Random, xoroshiro::Xoroshiro};
452 use std::simd::f64x4;
453
454 #[test]
455 fn test_perlin_noise_deterministic() {
456 let mut rng = Xoroshiro::from_seed(12345);
457 let splitter = rng.next_positional();
458
459 let amplitudes = [1.0, 1.0, 1.0];
460 let noise1 = PerlinNoise::create(&splitter, -3, &litudes);
461 let noise2 = PerlinNoise::create(&splitter, -3, &litudes);
462
463 let v1 = noise1.get_value(100.0, 64.0, 100.0);
464 let v2 = noise2.get_value(100.0, 64.0, 100.0);
465 assert!((v1 - v2).abs() < 1e-15);
466 }
467
468 #[test]
469 fn test_get_value_matches_zero_y_params_path() {
470 let mut rng = Xoroshiro::from_seed(12345);
471 let splitter = rng.next_positional();
472
473 let noise = PerlinNoise::create(&splitter, -4, &[1.0, 0.0, 1.0, 1.0]);
474
475 for (x, y, z) in [
476 (0.0, 0.0, 0.0),
477 (100.0, 64.0, -100.0),
478 (-4096.25, -32.5, 1024.75),
479 ] {
480 assert!(
481 (noise.get_value(x, y, z)
482 - noise.get_value_with_y_params(x, y, z, 0.0, 0.0, false))
483 .abs()
484 < 1e-15
485 );
486 }
487 }
488
489 #[test]
490 fn test_get_value_simd_matches_scalar() {
491 let mut rng = Xoroshiro::from_seed(12_345);
492 let splitter = rng.next_positional();
493 let noise = PerlinNoise::create(&splitter, -6, &[1.0, 0.0, 1.0, 1.0, 0.5]);
494 let xs = [0.0, 1.25, -1000.0, 33_554_431.5];
495 let ys = [0.0, 64.5, -32.25, 255.75];
496 let zs = [0.0, -30.75, 4096.5, -33_554_432.25];
497
498 let simd = noise.get_value_simd(
499 f64x4::from_array(xs),
500 f64x4::from_array(ys),
501 f64x4::from_array(zs),
502 );
503
504 for i in 0..4 {
505 let scalar = noise.get_value(xs[i], ys[i], zs[i]);
506 #[expect(
507 clippy::float_cmp,
508 reason = "SIMD path must be bit-identical to scalar noise for vanilla determinism"
509 )]
510 let matches = scalar == simd[i];
511 assert!(
512 matches,
513 "Mismatch at ({}, {}, {}): scalar={}, simd={}",
514 xs[i], ys[i], zs[i], scalar, simd[i],
515 );
516 }
517 }
518
519 #[test]
520 fn test_perlin_noise_spatial_variation() {
521 let mut rng = Xoroshiro::from_seed(42);
522 let splitter = rng.next_positional();
523
524 let noise = PerlinNoise::create(&splitter, -4, &[1.0, 1.0, 1.0, 1.0]);
525
526 let values: Vec<f64> = (0..10)
528 .map(|i| noise.get_value(f64::from(i) * 50.0, 64.0, f64::from(i) * 50.0))
529 .collect();
530
531 let min = values.iter().copied().fold(f64::INFINITY, f64::min);
533 let max = values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
534 assert!(max - min > 0.01, "Noise should have spatial variation");
535 }
536
537 #[test]
538 fn test_create_from_random_different_seeds() {
539 let mut rng = Xoroshiro::from_seed(12345);
540 let splitter = rng.next_positional();
541 let mut random = splitter.with_hash_of(&NameHash::new("test_noise"));
542
543 let amplitudes = [1.0, 1.0, 1.0];
544 let noise1 = PerlinNoise::create_from_random(&mut random, -3, &litudes);
545 let noise2 = PerlinNoise::create_from_random(&mut random, -3, &litudes);
546
547 let v1 = noise1.get_value(100.0, 64.0, 100.0);
549 let v2 = noise2.get_value(100.0, 64.0, 100.0);
550 assert!(
551 (v1 - v2).abs() > 0.001,
552 "Two PerlinNoise from sequential random should differ: v1={v1}, v2={v2}",
553 );
554 }
555
556 #[test]
557 fn test_zero_axis_helpers_match_full_noise() {
558 let mut rng = Xoroshiro::from_seed(98_765);
559 let splitter = rng.next_positional();
560 let noise = PerlinNoise::create(&splitter, -6, &[1.0, 0.0, 1.0, 1.0, 0.5]);
561 let samples = [
562 (0.0, 0.0),
563 (1.25, -30.75),
564 (-1000.0, 4096.5),
565 (33_554_431.5, -33_554_432.25),
566 (-0.000_000_1, 0.000_000_1),
567 ];
568
569 for &(a, b) in &samples {
570 #[expect(
571 clippy::float_cmp,
572 reason = "zero-axis helpers must be bit-identical to the full scalar path"
573 )]
574 {
575 assert_eq!(noise.get_value_xz(a, b), noise.get_value(a, 0.0, b));
576 assert_eq!(noise.get_value_xy(a, b), noise.get_value(a, b, 0.0));
577 }
578 }
579 }
580}