terrain_forge/algorithms/
percolation.rs1use crate::{Algorithm, Grid, Rng, Tile};
2
3#[derive(Debug, Clone)]
4pub struct PercolationConfig {
5 pub fill_probability: f64,
6 pub keep_largest: bool,
7}
8
9impl Default for PercolationConfig {
10 fn default() -> Self {
11 Self {
12 fill_probability: 0.45,
13 keep_largest: true,
14 }
15 }
16}
17
18pub struct Percolation {
19 config: PercolationConfig,
20}
21
22impl Percolation {
23 pub fn new(config: PercolationConfig) -> Self {
24 Self { config }
25 }
26}
27
28impl Default for Percolation {
29 fn default() -> Self {
30 Self::new(PercolationConfig::default())
31 }
32}
33
34impl Algorithm<Tile> for Percolation {
35 fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
36 let mut rng = Rng::new(seed);
37 let (w, h) = (grid.width(), grid.height());
38
39 for y in 1..h - 1 {
40 for x in 1..w - 1 {
41 if rng.chance(self.config.fill_probability) {
42 grid.set(x as i32, y as i32, Tile::Floor);
43 }
44 }
45 }
46
47 if !self.config.keep_largest {
48 return;
49 }
50
51 let mut labels = vec![0u32; w * h];
53 let mut label = 0u32;
54 let mut sizes = vec![0usize];
55
56 for y in 0..h {
57 for x in 0..w {
58 if grid[(x, y)].is_floor() && labels[y * w + x] == 0 {
59 label += 1;
60 let size = flood_fill(grid, &mut labels, x, y, label, w, h);
61 sizes.push(size);
62 }
63 }
64 }
65
66 if label > 0 {
67 let largest = sizes
68 .iter()
69 .enumerate()
70 .skip(1)
71 .max_by_key(|&(_, &s)| s)
72 .map(|(i, _)| i as u32)
73 .unwrap_or(1);
74
75 for y in 0..h {
76 for x in 0..w {
77 if labels[y * w + x] != largest && labels[y * w + x] != 0 {
78 grid.set(x as i32, y as i32, Tile::Wall);
79 }
80 }
81 }
82 }
83 }
84
85 fn name(&self) -> &'static str {
86 "Percolation"
87 }
88}
89
90fn flood_fill(
91 grid: &Grid<Tile>,
92 labels: &mut [u32],
93 sx: usize,
94 sy: usize,
95 label: u32,
96 w: usize,
97 h: usize,
98) -> usize {
99 let mut stack = vec![(sx, sy)];
100 let mut count = 0;
101
102 while let Some((x, y)) = stack.pop() {
103 let idx = y * w + x;
104 if labels[idx] != 0 || !grid[(x, y)].is_floor() {
105 continue;
106 }
107
108 labels[idx] = label;
109 count += 1;
110
111 if x > 0 {
112 stack.push((x - 1, y));
113 }
114 if x + 1 < w {
115 stack.push((x + 1, y));
116 }
117 if y > 0 {
118 stack.push((x, y - 1));
119 }
120 if y + 1 < h {
121 stack.push((x, y + 1));
122 }
123 }
124 count
125}