Skip to main content

terrain_forge/noise/
modifiers.rs

1use super::NoiseSource;
2
3/// Scale noise output by a factor
4pub struct Scale<S: NoiseSource> {
5    pub(crate) source: S,
6    pub(crate) factor: f64,
7}
8
9impl<S: NoiseSource> NoiseSource for Scale<S> {
10    fn sample(&self, x: f64, y: f64) -> f64 {
11        self.source.sample(x, y) * self.factor
12    }
13}
14
15/// Add offset to noise output
16pub struct Offset<S: NoiseSource> {
17    pub(crate) source: S,
18    pub(crate) amount: f64,
19}
20
21impl<S: NoiseSource> NoiseSource for Offset<S> {
22    fn sample(&self, x: f64, y: f64) -> f64 {
23        self.source.sample(x, y) + self.amount
24    }
25}
26
27/// Clamp noise output to range
28pub struct Clamp<S: NoiseSource> {
29    pub(crate) source: S,
30    pub(crate) min: f64,
31    pub(crate) max: f64,
32}
33
34impl<S: NoiseSource> NoiseSource for Clamp<S> {
35    fn sample(&self, x: f64, y: f64) -> f64 {
36        self.source.sample(x, y).clamp(self.min, self.max)
37    }
38}
39
40/// Absolute value of noise output
41pub struct Abs<S: NoiseSource> {
42    pub(crate) source: S,
43}
44
45impl<S: NoiseSource> NoiseSource for Abs<S> {
46    fn sample(&self, x: f64, y: f64) -> f64 {
47        self.source.sample(x, y).abs()
48    }
49}
50
51/// Blend two noise sources
52pub struct Blend<A: NoiseSource, B: NoiseSource, C: NoiseSource> {
53    pub source_a: A,
54    pub source_b: B,
55    pub control: C,
56}
57
58impl<A: NoiseSource, B: NoiseSource, C: NoiseSource> Blend<A, B, C> {
59    pub fn new(source_a: A, source_b: B, control: C) -> Self {
60        Self {
61            source_a,
62            source_b,
63            control,
64        }
65    }
66}
67
68impl<A: NoiseSource, B: NoiseSource, C: NoiseSource> NoiseSource for Blend<A, B, C> {
69    fn sample(&self, x: f64, y: f64) -> f64 {
70        let a = self.source_a.sample(x, y);
71        let b = self.source_b.sample(x, y);
72        let t = (self.control.sample(x, y) + 1.0) * 0.5; // Map [-1,1] to [0,1]
73        a + t * (b - a)
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::noise::{NoiseExt, Perlin};
81
82    #[test]
83    fn scale_modifier() {
84        let noise = Perlin::new(42).scale(2.0);
85        let base = Perlin::new(42);
86        let v1 = noise.sample(1.0, 1.0);
87        let v2 = base.sample(1.0, 1.0) * 2.0;
88        assert!((v1 - v2).abs() < 1e-10);
89    }
90
91    #[test]
92    fn offset_modifier() {
93        let noise = Perlin::new(42).offset(0.5);
94        let base = Perlin::new(42);
95        let v1 = noise.sample(1.0, 1.0);
96        let v2 = base.sample(1.0, 1.0) + 0.5;
97        assert!((v1 - v2).abs() < 1e-10);
98    }
99
100    #[test]
101    fn clamp_modifier() {
102        let noise = Perlin::new(42).clamp(-0.5, 0.5);
103        for i in 0..50 {
104            for j in 0..50 {
105                let v = noise.sample(i as f64 * 0.1, j as f64 * 0.1);
106                assert!((-0.5..=0.5).contains(&v));
107            }
108        }
109    }
110
111    #[test]
112    fn abs_modifier() {
113        let noise = Perlin::new(42).abs();
114        for i in 0..50 {
115            for j in 0..50 {
116                let v = noise.sample(i as f64 * 0.1, j as f64 * 0.1);
117                assert!(v >= 0.0);
118            }
119        }
120    }
121
122    #[test]
123    fn chained_modifiers() {
124        let noise = Perlin::new(42).scale(0.5).offset(0.5).clamp(0.0, 1.0);
125
126        for i in 0..50 {
127            for j in 0..50 {
128                let v = noise.sample(i as f64 * 0.1, j as f64 * 0.1);
129                assert!((0.0..=1.0).contains(&v));
130            }
131        }
132    }
133}