Skip to main content

terrain_forge/noise/
value.rs

1use super::NoiseSource;
2
3/// Value noise generator (interpolated random values at grid points)
4pub struct Value {
5    seed: u64,
6    frequency: f64,
7}
8
9impl Value {
10    /// Creates a new noise generator from the given seed.
11    pub fn new(seed: u64) -> Self {
12        Self {
13            seed,
14            frequency: 1.0,
15        }
16    }
17
18    /// Sets the base frequency.
19    pub fn with_frequency(mut self, frequency: f64) -> Self {
20        self.frequency = frequency;
21        self
22    }
23
24    // Hash to get random value at grid point
25    fn hash(&self, x: i32, y: i32) -> f64 {
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        let h = (h ^ (h >> 13)).wrapping_mul(1274126177);
31        // Convert to [-1, 1]
32        (h as i64 as f64) / (i64::MAX as f64)
33    }
34
35    fn lerp(a: f64, b: f64, t: f64) -> f64 {
36        a + t * (b - a)
37    }
38
39    fn smoothstep(t: f64) -> f64 {
40        t * t * (3.0 - 2.0 * t)
41    }
42}
43
44impl NoiseSource for Value {
45    fn sample(&self, x: f64, y: f64) -> f64 {
46        let x = x * self.frequency;
47        let y = y * self.frequency;
48
49        let x0 = x.floor() as i32;
50        let y0 = y.floor() as i32;
51
52        let dx = Self::smoothstep(x - x0 as f64);
53        let dy = Self::smoothstep(y - y0 as f64);
54
55        let v00 = self.hash(x0, y0);
56        let v10 = self.hash(x0 + 1, y0);
57        let v01 = self.hash(x0, y0 + 1);
58        let v11 = self.hash(x0 + 1, y0 + 1);
59
60        let vx0 = Self::lerp(v00, v10, dx);
61        let vx1 = Self::lerp(v01, v11, dx);
62
63        Self::lerp(vx0, vx1, dy)
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn value_deterministic() {
73        let noise = Value::new(12345);
74        assert_eq!(noise.sample(1.5, 2.5), noise.sample(1.5, 2.5));
75    }
76
77    #[test]
78    fn value_range() {
79        let noise = Value::new(42);
80        for i in 0..100 {
81            for j in 0..100 {
82                let v = noise.sample(i as f64 * 0.1, j as f64 * 0.1);
83                assert!((-1.0..=1.0).contains(&v), "Value {} out of range", v);
84            }
85        }
86    }
87}