Skip to main content

terrain_forge/algorithms/
noise_fill.rs

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