noise_functions/modifiers/
fbm.rs

1#[cfg(feature = "nightly-simd")]
2use core::simd::{LaneCount, Simd, SupportedLaneCount};
3
4use crate::{
5    math::{fast_min, lerp},
6    Noise, Sample,
7};
8
9#[cfg(feature = "nightly-simd")]
10use crate::math::splat;
11
12use super::{fractal_bounding, Weighted};
13
14/// Fractal Brownian motion (fBm) noise.
15///
16/// `fBm` noise is created by calling the base noise `octaves` amount of times with increasing frequency and decreasing amplitude.
17///
18/// **Note:** This modifier assumes the base noise returns values in the [-1, 1] range.
19#[derive(Debug, Clone, Copy, PartialEq)]
20pub struct Fbm<Noise> {
21    pub noise: Noise,
22    pub octaves: u32,
23    pub gain: f32,
24    pub lacunarity: f32,
25    pub fractal_bounding: f32,
26}
27
28impl<N> Noise for Fbm<N> {}
29
30impl<Noise> Fbm<Noise> {
31    #[inline(always)]
32    pub const fn new(noise: Noise, octaves: u32, gain: f32, lacunarity: f32) -> Self {
33        Self {
34            noise,
35            octaves,
36            gain,
37            lacunarity,
38            fractal_bounding: fractal_bounding(octaves, gain),
39        }
40    }
41
42    #[inline(always)]
43    pub const fn weighted(self, strength: f32) -> Weighted<Self> {
44        Weighted { fractal: self, strength }
45    }
46}
47
48impl<Noise, const DIM: usize> Sample<DIM> for Fbm<Noise>
49where
50    Noise: Sample<DIM>,
51{
52    #[inline]
53    fn sample_with_seed(&self, point: [f32; DIM], seed: i32) -> f32 {
54        let &Fbm {
55            ref noise,
56            octaves,
57            gain,
58            lacunarity,
59            fractal_bounding,
60        } = self;
61        fbm(noise, octaves, gain, lacunarity, fractal_bounding, seed, point)
62    }
63}
64
65impl<Noise, const DIM: usize> Sample<DIM> for Weighted<Fbm<Noise>>
66where
67    Noise: Sample<DIM>,
68{
69    #[inline]
70    fn sample_with_seed(&self, point: [f32; DIM], seed: i32) -> f32 {
71        let &Weighted {
72            fractal: Fbm {
73                ref noise,
74                octaves,
75                gain,
76                lacunarity,
77                fractal_bounding,
78            },
79            strength: weighted_strength,
80        } = self;
81        weighted_fbm(noise, octaves, gain, lacunarity, fractal_bounding, weighted_strength, seed, point)
82    }
83}
84
85#[cfg(feature = "nightly-simd")]
86impl<Noise, const DIM: usize, const LANES: usize> Sample<DIM, Simd<f32, LANES>> for Fbm<Noise>
87where
88    Noise: Sample<DIM, Simd<f32, LANES>>,
89    LaneCount<LANES>: SupportedLaneCount,
90{
91    #[inline]
92    fn sample_with_seed(&self, point: Simd<f32, LANES>, seed: i32) -> f32 {
93        let &Fbm {
94            ref noise,
95            octaves,
96            gain,
97            lacunarity,
98            fractal_bounding,
99        } = self;
100        fbm_a(noise, octaves, gain, lacunarity, fractal_bounding, seed, point)
101    }
102}
103
104#[cfg(feature = "nightly-simd")]
105impl<Noise, const DIM: usize, const LANES: usize> Sample<DIM, Simd<f32, LANES>> for Weighted<Fbm<Noise>>
106where
107    Noise: Sample<DIM, Simd<f32, LANES>>,
108    LaneCount<LANES>: SupportedLaneCount,
109{
110    #[inline]
111    fn sample_with_seed(&self, point: Simd<f32, LANES>, seed: i32) -> f32 {
112        let &Weighted {
113            fractal: Fbm {
114                ref noise,
115                octaves,
116                gain,
117                lacunarity,
118                fractal_bounding,
119            },
120            strength: weighted_strength,
121        } = self;
122
123        weighted_fbm_a(noise, octaves, gain, lacunarity, fractal_bounding, weighted_strength, seed, point)
124    }
125}
126
127#[inline(always)]
128fn fbm<Noise, const DIM: usize>(noise: &Noise, octaves: u32, gain: f32, lacunarity: f32, fractal_bounding: f32, mut seed: i32, mut point: [f32; DIM]) -> f32
129where
130    Noise: Sample<DIM>,
131{
132    let mut sum = 0.0;
133    let mut amp = fractal_bounding;
134
135    for _ in 0..octaves {
136        let noise = noise.sample_with_seed(point, seed);
137        seed = seed.wrapping_add(1);
138        sum += noise * amp;
139
140        for x in &mut point {
141            *x *= lacunarity;
142        }
143
144        amp *= gain;
145    }
146
147    sum
148}
149
150#[cfg(feature = "nightly-simd")]
151#[inline(always)]
152fn fbm_a<Noise, const DIM: usize, const LANES: usize>(noise: &Noise, octaves: u32, gain: f32, lacunarity: f32, fractal_bounding: f32, mut seed: i32, mut point: Simd<f32, LANES>) -> f32
153where
154    Noise: Sample<DIM, Simd<f32, LANES>>,
155    LaneCount<LANES>: SupportedLaneCount,
156{
157    let mut sum = 0.0;
158    let mut amp = fractal_bounding;
159
160    for _ in 0..octaves {
161        let noise = noise.sample_with_seed(point, seed);
162        seed = seed.wrapping_add(1);
163        sum += noise * amp;
164
165        point *= splat(lacunarity);
166        amp *= gain;
167    }
168
169    sum
170}
171
172#[inline(always)]
173fn weighted_fbm<Noise, const DIM: usize>(noise: &Noise, octaves: u32, gain: f32, lacunarity: f32, fractal_bounding: f32, weighted_strength: f32, mut seed: i32, mut point: [f32; DIM]) -> f32
174where
175    Noise: Sample<DIM>,
176{
177    let mut sum = 0.0;
178    let mut amp = fractal_bounding;
179
180    for _ in 0..octaves {
181        let noise = noise.sample_with_seed(point, seed);
182        seed = seed.wrapping_add(1);
183        sum += noise * amp;
184        amp *= lerp(1.0, fast_min(noise + 1.0, 2.0) * 0.5, weighted_strength);
185
186        for x in &mut point {
187            *x *= lacunarity;
188        }
189
190        amp *= gain;
191    }
192
193    sum
194}
195
196#[cfg(feature = "nightly-simd")]
197#[inline(always)]
198fn weighted_fbm_a<Noise, const DIM: usize, const LANES: usize>(
199    noise: &Noise,
200    octaves: u32,
201    gain: f32,
202    lacunarity: f32,
203    fractal_bounding: f32,
204    weighted_strength: f32,
205    mut seed: i32,
206    mut point: Simd<f32, LANES>,
207) -> f32
208where
209    Noise: Sample<DIM, Simd<f32, LANES>>,
210    LaneCount<LANES>: SupportedLaneCount,
211{
212    let mut sum = 0.0;
213    let mut amp = fractal_bounding;
214
215    for _ in 0..octaves {
216        let noise = noise.sample_with_seed(point, seed);
217        seed = seed.wrapping_add(1);
218        sum += noise * amp;
219        amp *= lerp(1.0, fast_min(noise + 1.0, 2.0) * 0.5, weighted_strength);
220
221        point *= splat(lacunarity);
222        amp *= gain;
223    }
224
225    sum
226}