Skip to main content

terrain_forge/algorithms/
noise_fill.rs

1use crate::noise::{NoiseExt, Perlin, Simplex, Value, Worley};
2use crate::{Algorithm, Grid, Tile};
3
4#[derive(Debug, Clone, Copy, Default)]
5pub enum NoiseType {
6    #[default]
7    Perlin,
8    Simplex,
9    Value,
10    Worley,
11}
12
13#[derive(Debug, Clone)]
14pub struct NoiseFillConfig {
15    pub noise: NoiseType,
16    /// Multiplies sample coordinates; higher = smaller features.
17    pub frequency: f64,
18    /// Feature size in tiles; higher = larger features (applied as frequency / scale).
19    pub scale: f64,
20    /// Output range after normalizing noise to [0, 1].
21    pub output_range: (f64, f64),
22    /// Fill if value <= threshold (after normalization + range mapping).
23    pub threshold: f64,
24    /// Optional inclusive fill range; overrides threshold when set.
25    pub fill_range: Option<(f64, f64)>,
26    /// Fractal octaves (1 = base noise).
27    pub octaves: u32,
28    /// Frequency multiplier between octaves.
29    pub lacunarity: f64,
30    /// Amplitude multiplier between octaves.
31    pub persistence: f64,
32}
33
34impl Default for NoiseFillConfig {
35    fn default() -> Self {
36        Self {
37            noise: NoiseType::Perlin,
38            frequency: 0.08,
39            scale: 1.0,
40            output_range: (0.0, 1.0),
41            threshold: 0.0,
42            fill_range: None,
43            octaves: 1,
44            lacunarity: 2.0,
45            persistence: 0.5,
46        }
47    }
48}
49
50pub struct NoiseFill {
51    config: NoiseFillConfig,
52}
53
54impl NoiseFill {
55    pub fn new(config: NoiseFillConfig) -> Self {
56        Self { config }
57    }
58}
59
60impl Default for NoiseFill {
61    fn default() -> Self {
62        Self::new(NoiseFillConfig::default())
63    }
64}
65
66impl Algorithm<Tile> for NoiseFill {
67    fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
68        let (w, h) = (grid.width(), grid.height());
69        let scale = if self.config.scale > 0.0 {
70            self.config.scale
71        } else {
72            1.0
73        };
74        let frequency = self.config.frequency / scale;
75
76        match self.config.noise {
77            NoiseType::Perlin => {
78                let noise = Perlin::new(seed).with_frequency(frequency);
79                fill_with_config(grid, noise, &self.config);
80            }
81            NoiseType::Simplex => {
82                let noise = Simplex::new(seed).with_frequency(frequency);
83                fill_with_config(grid, noise, &self.config);
84            }
85            NoiseType::Value => {
86                let noise = Value::new(seed).with_frequency(frequency);
87                fill_with_config(grid, noise, &self.config);
88            }
89            NoiseType::Worley => {
90                let noise = Worley::new(seed).with_frequency(frequency);
91                fill_with_config(grid, noise, &self.config);
92            }
93        }
94
95        // Keep borders as walls for consistency with standard algorithms.
96        if w > 0 && h > 0 {
97            for x in 0..w {
98                grid.set(x as i32, 0, Tile::Wall);
99                grid.set(x as i32, (h - 1) as i32, Tile::Wall);
100            }
101            for y in 0..h {
102                grid.set(0, y as i32, Tile::Wall);
103                grid.set((w - 1) as i32, y as i32, Tile::Wall);
104            }
105        }
106    }
107
108    fn name(&self) -> &'static str {
109        "NoiseFill"
110    }
111}
112
113fn fill_with_config<N: crate::noise::NoiseSource>(
114    grid: &mut Grid<Tile>,
115    noise: N,
116    config: &NoiseFillConfig,
117) {
118    let (mut out_min, mut out_max) = config.output_range;
119    if out_min > out_max {
120        std::mem::swap(&mut out_min, &mut out_max);
121    }
122    let range_span = out_max - out_min;
123    let fill_range = config
124        .fill_range
125        .map(|(a, b)| if a <= b { (a, b) } else { (b, a) });
126
127    if config.octaves > 1 {
128        let fbm = noise.fbm(config.octaves, config.lacunarity, config.persistence);
129        fill_from_noise(
130            grid,
131            &fbm,
132            out_min,
133            range_span,
134            fill_range,
135            config.threshold,
136        );
137    } else {
138        fill_from_noise(
139            grid,
140            &noise,
141            out_min,
142            range_span,
143            fill_range,
144            config.threshold,
145        );
146    }
147}
148
149fn fill_from_noise<N: crate::noise::NoiseSource>(
150    grid: &mut Grid<Tile>,
151    noise: &N,
152    out_min: f64,
153    range_span: f64,
154    fill_range: Option<(f64, f64)>,
155    threshold: f64,
156) {
157    let (w, h) = (grid.width(), grid.height());
158    for y in 0..h {
159        for x in 0..w {
160            let raw = noise.sample(x as f64, y as f64);
161            let mut value = (raw + 1.0) * 0.5;
162            value = out_min + value * range_span;
163
164            let fill = match fill_range {
165                Some((min, max)) => value >= min && value <= max,
166                None => value >= threshold,
167            };
168
169            let tile = if fill { Tile::Floor } else { Tile::Wall };
170            grid.set(x as i32, y as i32, tile);
171        }
172    }
173}