1use std::ops;
7use std::simd::cmp::{SimdPartialEq, SimdPartialOrd};
8use std::simd::num::SimdFloat;
9use std::simd::{Mask, Simd, StdFloat, f32x4, 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 = f64::from(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 = f64::from(
236 octave
237 .noise
238 .noise_xz(wrap(x * input_factor), wrap(z * input_factor)),
239 );
240 value += octave.output_factor * noise_val;
241 }
242
243 value
244 }
245
246 #[inline]
248 #[must_use]
249 pub fn get_value_xy(&self, x: f64, y: f64) -> f64 {
250 let mut value = 0.0;
251
252 for octave in &self.active_octaves {
253 let input_factor = octave.input_factor;
254 let noise_val = f64::from(
255 octave
256 .noise
257 .noise_xy(wrap(x * input_factor), wrap(y * input_factor)),
258 );
259 value += octave.output_factor * noise_val;
260 }
261
262 value
263 }
264
265 #[inline]
267 #[must_use]
268 pub fn get_value_simd<const N: usize>(
269 &self,
270 x: Simd<f32, N>,
271 y: Simd<f32, N>,
272 z: Simd<f32, N>,
273 ) -> Simd<f32, N>
274 where
275 Simd<f32, N>: SimdFloat<Cast<i32> = Simd<i32, N>>
276 + SimdPartialEq<Mask = Mask<i32, N>>
277 + SimdPartialOrd<Mask = Mask<i32, N>>
278 + ops::Add<Output = Simd<f32, N>>
279 + ops::Sub<Output = Simd<f32, N>>
280 + ops::Mul<Output = Simd<f32, N>>
281 + ops::Div<Output = Simd<f32, N>>
282 + ops::Neg<Output = Simd<f32, N>>
283 + StdFloat,
284 {
285 let mut value = Simd::splat(0.0);
286
287 for octave in &self.active_octaves {
288 let input_factor = Simd::splat(octave.input_factor as f32);
289 let noise_val = octave.noise.noise_simd(
290 wrap_simd(x * input_factor),
291 wrap_simd(y * input_factor),
292 wrap_simd(z * input_factor),
293 );
294 value += Simd::splat(octave.output_factor as f32) * noise_val;
295 }
296
297 value
298 }
299
300 #[must_use]
308 pub fn get_value_with_y_params(
309 &self,
310 x: f64,
311 y: f64,
312 z: f64,
313 y_scale: f64,
314 y_fudge: f64,
315 y_flat_hack: bool,
316 ) -> f32 {
317 let mut value = 0.0_f32;
318
319 for octave in &self.active_octaves {
320 let input_factor = octave.input_factor;
321 let noise = &octave.noise;
322 let noise_val = noise.noise_with_y_scale(
323 wrap(x * input_factor),
324 if y_flat_hack {
325 -noise.yo
326 } else {
327 wrap(y * input_factor)
328 },
329 wrap(z * input_factor),
330 y_scale * input_factor,
331 y_fudge * input_factor,
332 );
333 value += (octave.output_factor as f32) * noise_val;
334 }
335
336 value
337 }
338
339 #[must_use]
347 pub fn get_value_with_y_params_4x(
348 &self,
349 x: f64,
350 ys: f64x4,
351 z: f64,
352 y_scale: f64,
353 y_fudge: f64,
354 y_flat_hack: bool,
355 ) -> f32x4 {
356 let mut value = f32x4::splat(0.0);
357
358 for octave in &self.active_octaves {
359 let input_factor = octave.input_factor;
360 let noise = &octave.noise;
361 let x_w = wrap(x * input_factor);
362 let z_w = wrap(z * input_factor);
363 let ys_for_call = if y_flat_hack {
364 f64x4::splat(-noise.yo)
365 } else {
366 wrap_simd(ys * f64x4::splat(input_factor))
367 };
368 let y_fudges = f64x4::splat(y_fudge * input_factor);
369 let noise_val = noise.noise_with_y_scale_simd(
370 x_w,
371 ys_for_call,
372 z_w,
373 y_scale * input_factor,
374 y_fudges,
375 );
376 value += f32x4::splat(octave.output_factor as f32) * noise_val;
377 }
378
379 value
380 }
381
382 #[must_use]
385 pub fn get_value_with_y_params_simd<const N: usize>(
386 &self,
387 x: f64,
388 ys: Simd<f64, N>,
389 z: f64,
390 y_scale: f64,
391 y_fudge: f64,
392 y_flat_hack: bool,
393 ) -> Simd<f32, N> {
394 let mut value = Simd::splat(0.0_f32);
395
396 for octave in &self.active_octaves {
397 let input_factor = octave.input_factor;
398 let noise = &octave.noise;
399 let x_w = wrap(x * input_factor);
400 let z_w = wrap(z * input_factor);
401 let ys_for_call = if y_flat_hack {
402 Simd::splat(-noise.yo)
403 } else {
404 wrap_simd(ys * Simd::splat(input_factor))
405 };
406 let y_fudges = Simd::splat(y_fudge * input_factor);
407 let noise_val = noise.noise_with_y_scale_simd(
408 x_w,
409 ys_for_call,
410 z_w,
411 y_scale * input_factor,
412 y_fudges,
413 );
414 value += Simd::splat(octave.output_factor as f32) * noise_val;
415 }
416
417 value
418 }
419
420 #[inline]
422 #[must_use]
423 pub const fn max_value(&self) -> f64 {
424 self.max_value
425 }
426
427 #[must_use]
432 pub fn max_broken_value(&self, y_scale: f64) -> f64 {
433 Self::edge_value(
434 &self.amplitudes,
435 self.lowest_freq_value_factor,
436 y_scale + 2.0,
437 )
438 }
439
440 #[must_use]
444 pub fn get_octave_noise(&self, i: usize) -> Option<&ImprovedNoise> {
445 self.noise_levels
446 .get(self.noise_levels.len() - 1 - i)
447 .and_then(|opt| opt.as_ref())
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454 use crate::random::{Random, xoroshiro::Xoroshiro};
455 use std::simd::f32x4;
456
457 #[test]
458 fn test_perlin_noise_deterministic() {
459 let mut rng = Xoroshiro::from_seed(12345);
460 let splitter = rng.next_positional();
461
462 let amplitudes = [1.0, 1.0, 1.0];
463 let noise1 = PerlinNoise::create(&splitter, -3, &litudes);
464 let noise2 = PerlinNoise::create(&splitter, -3, &litudes);
465
466 let v1 = noise1.get_value(100.0, 64.0, 100.0);
467 let v2 = noise2.get_value(100.0, 64.0, 100.0);
468 assert!((v1 - v2).abs() < 1e-15);
469 }
470
471 #[test]
472 fn test_get_value_matches_zero_y_params_path() {
473 let mut rng = Xoroshiro::from_seed(12345);
474 let splitter = rng.next_positional();
475
476 let noise = PerlinNoise::create(&splitter, -4, &[1.0, 0.0, 1.0, 1.0]);
477
478 for (x, y, z) in [
479 (0.0, 0.0, 0.0),
480 (100.0, 64.0, -100.0),
481 (-4096.25, -32.5, 1024.75),
482 ] {
483 assert!(
484 (noise.get_value(x, y, z)
485 - f64::from(noise.get_value_with_y_params(x, y, z, 0.0, 0.0, false)))
486 .abs()
487 < 1e-6
488 );
489 }
490 }
491
492 #[test]
493 fn test_get_value_simd_matches_scalar() {
494 let mut rng = Xoroshiro::from_seed(12_345);
495 let splitter = rng.next_positional();
496 let noise = PerlinNoise::create(&splitter, -6, &[1.0, 0.0, 1.0, 1.0, 0.5]);
497 let xs = [0.0_f32, 1.25, -1000.0, 4095.5];
498 let ys = [0.0_f32, 64.5, -32.25, 255.75];
499 let zs = [0.0_f32, -30.75, 4096.5, -4096.5];
500
501 let simd = noise.get_value_simd(
502 f32x4::from_array(xs),
503 f32x4::from_array(ys),
504 f32x4::from_array(zs),
505 );
506
507 for i in 0..4 {
508 let scalar = noise.get_value(f64::from(xs[i]), f64::from(ys[i]), f64::from(zs[i]));
509 assert!(
510 (scalar - f64::from(simd[i])).abs() < 1e-5,
511 "Mismatch at ({}, {}, {}): scalar={}, simd={}",
512 xs[i],
513 ys[i],
514 zs[i],
515 scalar,
516 simd[i],
517 );
518 }
519 }
520
521 #[test]
522 fn test_perlin_noise_spatial_variation() {
523 let mut rng = Xoroshiro::from_seed(42);
524 let splitter = rng.next_positional();
525
526 let noise = PerlinNoise::create(&splitter, -4, &[1.0, 1.0, 1.0, 1.0]);
527
528 let values: Vec<f64> = (0..10)
530 .map(|i| noise.get_value(f64::from(i) * 50.0, 64.0, f64::from(i) * 50.0))
531 .collect();
532
533 let min = values.iter().copied().fold(f64::INFINITY, f64::min);
535 let max = values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
536 assert!(max - min > 0.01, "Noise should have spatial variation");
537 }
538
539 #[test]
540 fn test_create_from_random_different_seeds() {
541 let mut rng = Xoroshiro::from_seed(12345);
542 let splitter = rng.next_positional();
543 let mut random = splitter.with_hash_of(&NameHash::new("test_noise"));
544
545 let amplitudes = [1.0, 1.0, 1.0];
546 let noise1 = PerlinNoise::create_from_random(&mut random, -3, &litudes);
547 let noise2 = PerlinNoise::create_from_random(&mut random, -3, &litudes);
548
549 let v1 = noise1.get_value(100.0, 64.0, 100.0);
551 let v2 = noise2.get_value(100.0, 64.0, 100.0);
552 assert!(
553 (v1 - v2).abs() > 0.001,
554 "Two PerlinNoise from sequential random should differ: v1={v1}, v2={v2}",
555 );
556 }
557
558 #[test]
559 fn test_zero_axis_helpers_match_full_noise() {
560 let mut rng = Xoroshiro::from_seed(98_765);
561 let splitter = rng.next_positional();
562 let noise = PerlinNoise::create(&splitter, -6, &[1.0, 0.0, 1.0, 1.0, 0.5]);
563 let samples = [
564 (0.0, 0.0),
565 (1.25, -30.75),
566 (-1000.0, 4096.5),
567 (33_554_431.5, -33_554_432.25),
568 (-0.000_000_1, 0.000_000_1),
569 ];
570
571 for &(a, b) in &samples {
572 assert_eq!(noise.get_value_xz(a, b), noise.get_value(a, 0.0, b));
573 assert_eq!(noise.get_value_xy(a, b), noise.get_value(a, b, 0.0));
574 }
575 }
576}