Skip to main content

terrain_forge/algorithms/
cellular.rs

1use crate::{Algorithm, Grid, Rng, Tile};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5/// Configuration for cellular automata cave generation.
6pub struct CellularConfig {
7    /// Probability of a cell starting as floor. Default: 0.45.
8    pub initial_floor_chance: f64,
9    /// Number of automata iterations. Default: 4.
10    pub iterations: usize,
11    /// Neighbor count to birth a floor cell. Default: 5.
12    pub birth_limit: usize,
13    /// Neighbor count below which a floor cell dies. Default: 4.
14    pub death_limit: usize,
15}
16
17impl Default for CellularConfig {
18    fn default() -> Self {
19        Self {
20            initial_floor_chance: 0.45,
21            iterations: 4,
22            birth_limit: 5,
23            death_limit: 4,
24        }
25    }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29/// Cellular automata cave generator.
30pub struct CellularAutomata {
31    config: CellularConfig,
32}
33
34impl CellularAutomata {
35    /// Creates a new cellular automata generator with the given config.
36    pub fn new(config: CellularConfig) -> Self {
37        Self { config }
38    }
39}
40
41impl Default for CellularAutomata {
42    fn default() -> Self {
43        Self::new(CellularConfig::default())
44    }
45}
46
47impl Algorithm<Tile> for CellularAutomata {
48    fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
49        let mut rng = Rng::new(seed);
50        let (w, h) = (grid.width(), grid.height());
51
52        for y in 1..h - 1 {
53            for x in 1..w - 1 {
54                if rng.chance(self.config.initial_floor_chance) {
55                    grid.set(x as i32, y as i32, Tile::Floor);
56                }
57            }
58        }
59
60        for _ in 0..self.config.iterations {
61            let snapshot: Vec<bool> = (0..w * h)
62                .map(|i| grid[(i % w, i / w)].is_floor())
63                .collect();
64
65            for y in 1..h - 1 {
66                for x in 1..w - 1 {
67                    let neighbors = count_neighbors(&snapshot, x, y, w);
68                    let is_floor = snapshot[y * w + x];
69                    let new_floor = if is_floor {
70                        neighbors >= self.config.death_limit
71                    } else {
72                        neighbors >= self.config.birth_limit
73                    };
74                    grid.set(
75                        x as i32,
76                        y as i32,
77                        if new_floor { Tile::Floor } else { Tile::Wall },
78                    );
79                }
80            }
81        }
82    }
83
84    fn name(&self) -> &'static str {
85        "CellularAutomata"
86    }
87}
88
89fn count_neighbors(cells: &[bool], x: usize, y: usize, w: usize) -> usize {
90    let mut count = 0;
91    for dy in -1i32..=1 {
92        for dx in -1i32..=1 {
93            if dx == 0 && dy == 0 {
94                continue;
95            }
96            let nx = (x as i32 + dx) as usize;
97            let ny = (y as i32 + dy) as usize;
98            if cells[ny * w + nx] {
99                count += 1;
100            }
101        }
102    }
103    count
104}