Skip to main content

terrain_forge/algorithms/
percolation.rs

1use crate::{Algorithm, Grid, Rng, Tile};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5/// Configuration for percolation-based generation.
6pub struct PercolationConfig {
7    /// Probability of each cell being floor. Default: 0.45.
8    pub fill_probability: f64,
9    /// Keep only the largest connected region. Default: true.
10    pub keep_largest: bool,
11}
12
13impl Default for PercolationConfig {
14    fn default() -> Self {
15        Self {
16            fill_probability: 0.45,
17            keep_largest: true,
18        }
19    }
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23/// Percolation cluster generator.
24pub struct Percolation {
25    config: PercolationConfig,
26}
27
28impl Percolation {
29    /// Creates a new percolation generator with the given config.
30    pub fn new(config: PercolationConfig) -> Self {
31        Self { config }
32    }
33}
34
35impl Default for Percolation {
36    fn default() -> Self {
37        Self::new(PercolationConfig::default())
38    }
39}
40
41impl Algorithm<Tile> for Percolation {
42    fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
43        let mut rng = Rng::new(seed);
44        let (w, h) = (grid.width(), grid.height());
45
46        for y in 1..h - 1 {
47            for x in 1..w - 1 {
48                if rng.chance(self.config.fill_probability) {
49                    grid.set(x as i32, y as i32, Tile::Floor);
50                }
51            }
52        }
53
54        if !self.config.keep_largest {
55            return;
56        }
57
58        // Find and keep largest region
59        let regions = grid.flood_regions();
60        if regions.len() <= 1 {
61            return;
62        }
63
64        let largest_region = regions.iter().max_by_key(|r| r.len()).unwrap();
65        let keep: std::collections::HashSet<(usize, usize)> =
66            largest_region.iter().copied().collect();
67
68        for y in 0..h {
69            for x in 0..w {
70                if grid[(x, y)].is_floor() && !keep.contains(&(x, y)) {
71                    grid.set(x as i32, y as i32, Tile::Wall);
72                }
73            }
74        }
75    }
76
77    fn name(&self) -> &'static str {
78        "Percolation"
79    }
80}