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    /// Creates a new blend of two noise sources controlled by a third.
60    pub fn new(source_a: A, source_b: B, control: C) -> Self {
61        Self {
62            source_a,
63            source_b,
64            control,
65        }
66    }
67}
68
69impl<A: NoiseSource, B: NoiseSource, C: NoiseSource> NoiseSource for Blend<A, B, C> {
70    fn sample(&self, x: f64, y: f64) -> f64 {
71        let a = self.source_a.sample(x, y);
72        let b = self.source_b.sample(x, y);
73        let t = (self.control.sample(x, y) + 1.0) * 0.5; // Map [-1,1] to [0,1]
74        a + t * (b - a)
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use crate::noise::{NoiseExt, Perlin};
82
83    #[test]
84    fn scale_modifier() {
85        let noise = Perlin::new(42).scale(2.0);
86        let base = Perlin::new(42);
87        let v1 = noise.sample(1.0, 1.0);
88        let v2 = base.sample(1.0, 1.0) * 2.0;
89        assert!((v1 - v2).abs() < 1e-10);
90    }
91
92    #[test]
93    fn offset_modifier() {
94        let noise = Perlin::new(42).offset(0.5);
95        let base = Perlin::new(42);
96        let v1 = noise.sample(1.0, 1.0);
97        let v2 = base.sample(1.0, 1.0) + 0.5;
98        assert!((v1 - v2).abs() < 1e-10);
99    }
100
101    #[test]
102    fn clamp_modifier() {
103        let noise = Perlin::new(42).clamp(-0.5, 0.5);
104        for i in 0..50 {
105            for j in 0..50 {
106                let v = noise.sample(i as f64 * 0.1, j as f64 * 0.1);
107                assert!((-0.5..=0.5).contains(&v));
108            }
109        }
110    }
111
112    #[test]
113    fn abs_modifier() {
114        let noise = Perlin::new(42).abs();
115        for i in 0..50 {
116            for j in 0..50 {
117                let v = noise.sample(i as f64 * 0.1, j as f64 * 0.1);
118                assert!(v >= 0.0);
119            }
120        }
121    }
122
123    #[test]
124    fn chained_modifiers() {
125        let noise = Perlin::new(42).scale(0.5).offset(0.5).clamp(0.0, 1.0);
126
127        for i in 0..50 {
128            for j in 0..50 {
129                let v = noise.sample(i as f64 * 0.1, j as f64 * 0.1);
130                assert!((0.0..=1.0).contains(&v));
131            }
132        }
133    }
134}