Skip to main content

steel_worldgen/noise/
normal_noise.rs

1use std::simd::Simd;
2
3use crate::noise::ImprovedNoise;
4use crate::random::{PositionalRandom, Random, RandomSource, RandomSplitter, name_hash::NameHash};
5
6const INPUT_FACTOR: f64 = 1.018_126_888_217_522_7;
7const TARGET_DEVIATION: f64 = 1.0 / 3.0;
8const DEVIATION_COEFFICIENT: f64 = 0.270_224_783_124_521_1;
9
10#[derive(Debug, Clone)]
11struct Layer {
12    noise: ImprovedNoise,
13    frequency: f64,
14    amplitude: f32,
15}
16
17/// Vanilla's 26.3 normal-noise sampler.
18#[derive(Debug, Clone)]
19pub struct NormalNoise {
20    layers: Box<[Layer]>,
21    max_value: f64,
22}
23
24impl NormalNoise {
25    #[must_use]
26    /// Creates parity noise from a positional worldgen splitter.
27    pub fn create(
28        splitter: &RandomSplitter,
29        noise_id: &str,
30        first_octave: i32,
31        amplitudes: &[f64],
32    ) -> Self {
33        let mut random = splitter.with_hash_of(&NameHash::new(noise_id));
34        Self::create_from_random(&mut random, first_octave, amplitudes)
35    }
36
37    #[must_use]
38    /// Creates parity noise from a vanilla random source.
39    pub fn create_from_random(
40        random: &mut RandomSource,
41        first_octave: i32,
42        amplitudes: &[f64],
43    ) -> Self {
44        let base_amplitude = parity_base_amplitude(first_octave, amplitudes);
45        Self::create_from_random_with_params(
46            random,
47            first_octave,
48            base_amplitude,
49            amplitudes.len() as i32,
50            true,
51            amplitudes,
52        )
53    }
54
55    #[must_use]
56    /// Creates the legacy-Nether variant, whose octave sources are sequentially seeded.
57    ///
58    /// # Panics
59    /// Panics if no octaves are provided or a positive octave is requested.
60    pub fn create_legacy_nether_biome(
61        random: &mut RandomSource,
62        first_octave: i32,
63        amplitudes: &[f64],
64    ) -> Self {
65        assert!(!amplitudes.is_empty(), "Need at least one octave");
66        assert!(
67            first_octave <= 0 && -first_octave >= amplitudes.len() as i32 - 1,
68            "Positive octaves are disabled for legacy Nether noise"
69        );
70        let base_amplitude = parity_base_amplitude(first_octave, amplitudes);
71        let octaves = build_octaves(
72            first_octave,
73            base_amplitude,
74            amplitudes.len() as i32,
75            true,
76            amplitudes,
77        );
78        let target_amplitude = octaves
79            .iter()
80            .map(|octave| octave.amplitude.abs())
81            .sum::<f64>();
82        let value_factor =
83            (normalization_factor(target_amplitude, &octaves) * base_amplitude) as f32;
84        let mut layers = Vec::with_capacity(octaves.len() * 2);
85        Self::append_legacy_layers(
86            random,
87            first_octave,
88            amplitudes,
89            1.0,
90            value_factor,
91            &mut layers,
92        );
93        Self::append_legacy_layers(
94            random,
95            first_octave,
96            amplitudes,
97            INPUT_FACTOR,
98            value_factor,
99            &mut layers,
100        );
101        let max_value = f64::from(
102            layers
103                .iter()
104                .fold(0.0_f32, |value, layer| value + layer.amplitude.abs() * 2.0),
105        );
106        Self {
107            layers: layers.into_boxed_slice(),
108            max_value,
109        }
110    }
111
112    fn append_legacy_layers(
113        random: &mut RandomSource,
114        first_octave: i32,
115        amplitudes: &[f64],
116        stack_frequency: f64,
117        stack_amplitude: f32,
118        layers: &mut Vec<Layer>,
119    ) {
120        let zero_index = (-first_octave) as usize;
121        let mut noises = vec![None; amplitudes.len()];
122        // LegacyFbmInitializer always constructs the zero octave, even when unused.
123        let zero_noise = ImprovedNoise::new(random);
124        if zero_index < noises.len() && amplitudes[zero_index] != 0.0 {
125            noises[zero_index] = Some(zero_noise);
126        }
127        for index in (0..zero_index).rev() {
128            if index < noises.len() && amplitudes[index] != 0.0 {
129                noises[index] = Some(ImprovedNoise::new(random));
130            } else {
131                random.consume_count(262);
132            }
133        }
134        let mut frequency = 2.0_f64.powi(first_octave);
135        let mut amplitude = 2.0_f64.powi(amplitudes.len() as i32 - 1)
136            / (2.0_f64.powi(amplitudes.len() as i32) - 1.0);
137        for (noise, modifier) in noises.into_iter().zip(amplitudes) {
138            if let Some(noise) = noise {
139                layers.push(Layer {
140                    noise,
141                    frequency: frequency * stack_frequency,
142                    amplitude: (amplitude * modifier) as f32 * stack_amplitude,
143                });
144            }
145            frequency *= 2.0;
146            amplitude /= 2.0;
147        }
148    }
149
150    #[must_use]
151    /// Creates current datapack noise from a positional worldgen splitter.
152    pub fn create_with_params(
153        splitter: &RandomSplitter,
154        noise_id: &str,
155        base_octave: i32,
156        base_amplitude: f64,
157        octave_count: i32,
158        normalize: bool,
159        amplitude_modifiers: &[f64],
160    ) -> Self {
161        let mut random = splitter.with_hash_of(&NameHash::new(noise_id));
162        Self::create_from_random_with_params(
163            &mut random,
164            base_octave,
165            base_amplitude,
166            octave_count,
167            normalize,
168            amplitude_modifiers,
169        )
170    }
171
172    #[must_use]
173    /// Creates current datapack noise from a vanilla random source.
174    pub fn create_from_random_with_params(
175        random: &mut RandomSource,
176        base_octave: i32,
177        base_amplitude: f64,
178        octave_count: i32,
179        normalize: bool,
180        amplitude_modifiers: &[f64],
181    ) -> Self {
182        let octaves = build_octaves(
183            base_octave,
184            base_amplitude,
185            octave_count,
186            normalize,
187            amplitude_modifiers,
188        );
189        let target_amplitude = octaves
190            .iter()
191            .map(|octave| octave.amplitude.abs())
192            .sum::<f64>();
193        let normalization_factor = normalization_factor(target_amplitude, &octaves);
194
195        let first_random = random.next_positional();
196        let second_random = random.next_positional();
197        let mut layers = Vec::with_capacity(octaves.len() * 2);
198        for octave in octaves {
199            let name = format!("octave_{}", octave.index);
200            let mut first = first_random.with_hash_of(&NameHash::new(&name));
201            let mut second = second_random.with_hash_of(&NameHash::new(&name));
202            let amplitude = (normalization_factor * octave.amplitude) as f32;
203            layers.push(Layer {
204                noise: ImprovedNoise::new(&mut first),
205                frequency: octave.frequency,
206                amplitude,
207            });
208            layers.push(Layer {
209                noise: ImprovedNoise::new(&mut second),
210                frequency: octave.frequency * INPUT_FACTOR,
211                amplitude,
212            });
213        }
214
215        Self {
216            layers: layers.into_boxed_slice(),
217            max_value: target_amplitude * TARGET_DEVIATION * 6.0,
218        }
219    }
220
221    #[inline]
222    #[must_use]
223    /// Samples the float-valued vanilla noise stack.
224    pub fn get_value(&self, x: f64, y: f64, z: f64) -> f32 {
225        self.layers.iter().fold(0.0_f32, |value, layer| {
226            value
227                + layer.amplitude
228                    * layer.noise.noise(
229                        x * layer.frequency,
230                        y * layer.frequency,
231                        z * layer.frequency,
232                    )
233        })
234    }
235
236    #[inline]
237    #[must_use]
238    /// Samples the float-valued vanilla noise stack with a fixed Y coordinate.
239    pub fn get_value_xz(&self, x: f64, z: f64) -> f32 {
240        self.layers.iter().fold(0.0_f32, |value, layer| {
241            value
242                + layer.amplitude
243                    * layer
244                        .noise
245                        .noise_xz(x * layer.frequency, z * layer.frequency)
246        })
247    }
248
249    #[inline]
250    #[must_use]
251    /// Samples the float-valued vanilla noise stack with a fixed Z coordinate.
252    pub fn get_value_xy(&self, x: f64, y: f64) -> f32 {
253        self.layers.iter().fold(0.0_f32, |value, layer| {
254            value
255                + layer.amplitude
256                    * layer
257                        .noise
258                        .noise_xy(x * layer.frequency, y * layer.frequency)
259        })
260    }
261
262    #[inline]
263    #[must_use]
264    /// Samples a SIMD Y column with the same f32 accumulator as `get_value`.
265    pub fn get_value_y_simd<const N: usize>(
266        &self,
267        x: f64,
268        ys: Simd<f64, N>,
269        z: f64,
270    ) -> Simd<f32, N> {
271        let mut value = Simd::splat(0.0_f32);
272        for layer in &self.layers {
273            let noise = layer.noise.noise_y_simd(
274                x * layer.frequency,
275                ys * Simd::splat(layer.frequency),
276                z * layer.frequency,
277            );
278            value += Simd::splat(layer.amplitude) * noise;
279        }
280        value
281    }
282
283    #[inline]
284    #[must_use]
285    /// Returns the vanilla sampler's conservative range bound.
286    pub const fn max_value(&self) -> f64 {
287        self.max_value
288    }
289}
290
291#[derive(Debug, Clone, Copy)]
292struct Octave {
293    index: i32,
294    frequency: f64,
295    amplitude: f64,
296}
297
298fn build_octaves(
299    base_octave: i32,
300    base_amplitude: f64,
301    octave_count: i32,
302    normalize: bool,
303    amplitude_modifiers: &[f64],
304) -> Vec<Octave> {
305    let mut frequency = 2.0_f64.powi(base_octave);
306    let mut amplitude = base_amplitude;
307    if normalize {
308        amplitude *= normalization_constant(octave_count);
309    }
310
311    let mut octaves = Vec::with_capacity(octave_count as usize);
312    for index in 0..octave_count {
313        let modifier = amplitude_modifiers
314            .get(index as usize)
315            .copied()
316            .unwrap_or(1.0);
317        if modifier != 0.0 {
318            octaves.push(Octave {
319                index: base_octave + index,
320                frequency,
321                amplitude: amplitude * modifier,
322            });
323        }
324        frequency *= 2.0;
325        amplitude *= 0.5;
326    }
327    octaves
328}
329
330fn normalization_constant(octave_count: i32) -> f64 {
331    2.0_f64.powi(octave_count - 1) / (2.0_f64.powi(octave_count) - 1.0)
332}
333
334fn normalization_factor(target_amplitude: f64, octaves: &[Octave]) -> f64 {
335    let variance = octaves
336        .iter()
337        .map(|octave| (DEVIATION_COEFFICIENT * octave.amplitude.abs()).powi(2))
338        .sum::<f64>();
339    if variance == 0.0 {
340        return 0.0;
341    }
342    target_amplitude * TARGET_DEVIATION / (variance.sqrt() * 2.0_f64.sqrt())
343}
344
345fn parity_normalization_factor(base_amplitude: f64, amplitudes: &[f64]) -> f64 {
346    let mut range = None;
347    for (index, amplitude) in amplitudes.iter().enumerate() {
348        if *amplitude != 0.0 {
349            range = Some(match range {
350                Some((first, _)) => (first, index as i32),
351                None => (index as i32, index as i32),
352            });
353        }
354    }
355    let Some((first, last)) = range else {
356        return 0.0;
357    };
358    base_amplitude * 0.5 * TARGET_DEVIATION / (0.1 * (1.0 + 1.0 / f64::from(last - first + 1)))
359}
360
361fn parity_base_amplitude(first_octave: i32, amplitudes: &[f64]) -> f64 {
362    if amplitudes.is_empty() {
363        return 1.0;
364    }
365    let octaves = build_octaves(first_octave, 1.0, amplitudes.len() as i32, true, amplitudes);
366    let target_amplitude = octaves
367        .iter()
368        .map(|octave| octave.amplitude.abs())
369        .sum::<f64>();
370    let new_factor = normalization_factor(target_amplitude, &octaves);
371    if new_factor == 0.0 {
372        return 1.0;
373    }
374    parity_normalization_factor(1.0, amplitudes) / new_factor
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380    use crate::random::{RandomSource, legacy_random::LegacyRandom};
381    use std::simd::{f64x4, f64x8};
382
383    #[test]
384    fn zero_axis_helpers_match_full_noise() {
385        let samples = [
386            (0.0, -0.0),
387            (1.25, -30.75),
388            (-1000.0, 4096.5),
389            (33_554_431.5, -33_554_432.25),
390            (-0.000_000_1, 0.000_000_1),
391        ];
392        for seed in [0, 42, 98_765] {
393            for amplitudes in [&[1.0, 0.0, 1.0, 1.0, 0.5][..], &[0.0; 5][..]] {
394                let mut random = RandomSource::Legacy(LegacyRandom::from_seed(seed));
395                let noise = NormalNoise::create_from_random(&mut random, -6, amplitudes);
396                for (a, b) in samples {
397                    assert_eq!(
398                        noise.get_value_xz(a, b).to_bits(),
399                        noise.get_value(a, 0.0, b).to_bits(),
400                        "XZ: seed={seed}, ({a}, {b})"
401                    );
402                    assert_eq!(
403                        noise.get_value_xy(a, b).to_bits(),
404                        noise.get_value(a, b, 0.0).to_bits(),
405                        "XY: seed={seed}, ({a}, {b})"
406                    );
407                }
408            }
409        }
410    }
411
412    #[test]
413    fn value_y_simd_matches_scalar_f32_lanes() {
414        let mut random = RandomSource::Legacy(LegacyRandom::from_seed(42));
415        let noise = NormalNoise::create_from_random(&mut random, -7, &[1.0, 1.0, 1.0]);
416        let x = 123.25;
417        let z = -456.75;
418        let ys4 = [0.0, 64.5, -12.25, 255.75];
419        let simd4 = noise.get_value_y_simd(x, f64x4::from_array(ys4), z);
420
421        for (lane, y) in ys4.into_iter().enumerate() {
422            assert_eq!(simd4[lane].to_bits(), noise.get_value(x, y, z).to_bits());
423        }
424
425        let ys8 = [0.0, 64.5, -12.25, 255.75, -64.0, 1.25, 320.0, 4096.5];
426        let simd8 = noise.get_value_y_simd(x, f64x8::from_array(ys8), z);
427        for (lane, y) in ys8.into_iter().enumerate() {
428            assert_eq!(simd8[lane].to_bits(), noise.get_value(x, y, z).to_bits());
429        }
430    }
431}