terrain_forge/noise/
simplex.rs1use super::NoiseSource;
2
3pub struct Simplex {
5 seed: u64,
6 frequency: f64,
7}
8
9impl Simplex {
10 const F2: f64 = 0.3660254037844386; const G2: f64 = 0.21132486540518713; pub fn new(seed: u64) -> Self {
14 Self {
15 seed,
16 frequency: 1.0,
17 }
18 }
19
20 pub fn with_frequency(mut self, frequency: f64) -> Self {
21 self.frequency = frequency;
22 self
23 }
24
25 fn hash(&self, x: i32, y: i32) -> usize {
26 let h = (x as u64)
27 .wrapping_mul(374761393)
28 .wrapping_add((y as u64).wrapping_mul(668265263))
29 .wrapping_add(self.seed);
30 (h ^ (h >> 13)).wrapping_mul(1274126177) as usize % 12
31 }
32
33 fn grad(hash: usize, x: f64, y: f64) -> f64 {
34 const GRAD: [(f64, f64); 12] = [
35 (1.0, 1.0),
36 (-1.0, 1.0),
37 (1.0, -1.0),
38 (-1.0, -1.0),
39 (1.0, 0.0),
40 (-1.0, 0.0),
41 (0.0, 1.0),
42 (0.0, -1.0),
43 (1.0, 1.0),
44 (-1.0, 1.0),
45 (1.0, -1.0),
46 (-1.0, -1.0),
47 ];
48 let (gx, gy) = GRAD[hash];
49 gx * x + gy * y
50 }
51}
52
53impl NoiseSource for Simplex {
54 fn sample(&self, x: f64, y: f64) -> f64 {
55 let x = x * self.frequency;
56 let y = y * self.frequency;
57
58 let s = (x + y) * Self::F2;
59 let i = (x + s).floor() as i32;
60 let j = (y + s).floor() as i32;
61
62 let t = (i + j) as f64 * Self::G2;
63 let x0 = x - (i as f64 - t);
64 let y0 = y - (j as f64 - t);
65
66 let (i1, j1) = if x0 > y0 { (1, 0) } else { (0, 1) };
67
68 let x1 = x0 - i1 as f64 + Self::G2;
69 let y1 = y0 - j1 as f64 + Self::G2;
70 let x2 = x0 - 1.0 + 2.0 * Self::G2;
71 let y2 = y0 - 1.0 + 2.0 * Self::G2;
72
73 let mut n = 0.0;
74 for &(dx, dy, di, dj) in &[(x0, y0, 0, 0), (x1, y1, i1, j1), (x2, y2, 1, 1)] {
75 let t = 0.5 - dx * dx - dy * dy;
76 if t > 0.0 {
77 let t2 = t * t;
78 n += t2 * t2 * Self::grad(self.hash(i + di, j + dj), dx, dy);
79 }
80 }
81 70.0 * n
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn simplex_deterministic() {
91 let noise = Simplex::new(12345);
92 assert_eq!(noise.sample(1.5, 2.5), noise.sample(1.5, 2.5));
93 }
94
95 #[test]
96 fn simplex_range() {
97 let noise = Simplex::new(42);
98 for i in 0..50 {
99 for j in 0..50 {
100 let v = noise.sample(i as f64 * 0.1, j as f64 * 0.1);
101 assert!((-1.0..=1.0).contains(&v), "Value {} out of range", v);
102 }
103 }
104 }
105}