Skip to main content

proof_engine/worldgen/
caves.rs

1//! Cave system generation — 3D cellular automata.
2//!
3//! Uses 3D cellular automata (B5678/S45678 variant) to create organic
4//! cave networks, then connects components and identifies features
5//! (chambers, tunnels, lakes).
6
7use super::Rng;
8
9/// A 3D voxel grid for cave generation.
10#[derive(Debug, Clone)]
11pub struct VoxelGrid {
12    pub width: usize,
13    pub height: usize,
14    pub depth: usize,
15    /// true = solid rock, false = open space
16    pub solid: Vec<bool>,
17}
18
19impl VoxelGrid {
20    pub fn new(w: usize, h: usize, d: usize) -> Self {
21        Self { width: w, height: h, depth: d, solid: vec![true; w * h * d] }
22    }
23
24    #[inline]
25    pub fn idx(&self, x: usize, y: usize, z: usize) -> usize {
26        z * self.width * self.height + y * self.width + x
27    }
28
29    pub fn get(&self, x: i32, y: i32, z: i32) -> bool {
30        if x < 0 || y < 0 || z < 0 { return true; }
31        let (ux, uy, uz) = (x as usize, y as usize, z as usize);
32        if ux >= self.width || uy >= self.height || uz >= self.depth { return true; }
33        self.solid[self.idx(ux, uy, uz)]
34    }
35
36    pub fn set(&mut self, x: usize, y: usize, z: usize, solid: bool) {
37        let i = self.idx(x, y, z);
38        self.solid[i] = solid;
39    }
40
41    /// Count solid neighbors in a 3×3×3 cube (26-neighborhood).
42    pub fn count_neighbors(&self, x: usize, y: usize, z: usize) -> u32 {
43        let mut count = 0u32;
44        for dz in -1..=1i32 {
45            for dy in -1..=1i32 {
46                for dx in -1..=1i32 {
47                    if dx == 0 && dy == 0 && dz == 0 { continue; }
48                    if self.get(x as i32 + dx, y as i32 + dy, z as i32 + dz) {
49                        count += 1;
50                    }
51                }
52            }
53        }
54        count
55    }
56
57    /// Count open (air) cells.
58    pub fn open_count(&self) -> usize {
59        self.solid.iter().filter(|&&s| !s).count()
60    }
61}
62
63/// A feature detected in the cave.
64#[derive(Debug, Clone)]
65pub enum CaveFeature {
66    /// Large open area.
67    Chamber { center: (usize, usize, usize), volume: usize },
68    /// Narrow passage.
69    Tunnel { cells: Vec<(usize, usize, usize)> },
70    /// Water-filled area at the bottom.
71    Lake { cells: Vec<(usize, usize, usize)> },
72    /// Vertical shaft.
73    Shaft { x: usize, z: usize, y_min: usize, y_max: usize },
74}
75
76/// A complete cave system.
77#[derive(Debug, Clone)]
78pub struct CaveSystem {
79    pub grid: VoxelGrid,
80    pub features: Vec<CaveFeature>,
81    /// Entrance positions on the surface.
82    pub entrances: Vec<(usize, usize, usize)>,
83}
84
85/// Generate cave systems.
86pub fn generate(grid_size: usize, num_systems: usize, rng: &mut Rng) -> Vec<CaveSystem> {
87    let mut systems = Vec::with_capacity(num_systems);
88    let cave_size = grid_size.min(48); // Cap cave size for performance
89
90    for _ in 0..num_systems {
91        let system = generate_single(cave_size, cave_size, cave_size / 2, rng);
92        systems.push(system);
93    }
94
95    systems
96}
97
98/// Generate a single cave system.
99fn generate_single(w: usize, h: usize, d: usize, rng: &mut Rng) -> CaveSystem {
100    let mut grid = VoxelGrid::new(w, h, d);
101
102    // 1. Random fill (~45% air)
103    for z in 0..d {
104        for y in 0..h {
105            for x in 0..w {
106                grid.set(x, y, z, rng.coin(0.55));
107            }
108        }
109    }
110
111    // Force border solid
112    for z in 0..d {
113        for y in 0..h {
114            for x in 0..w {
115                if x == 0 || y == 0 || z == 0 || x == w - 1 || y == h - 1 || z == d - 1 {
116                    grid.set(x, y, z, true);
117                }
118            }
119        }
120    }
121
122    // 2. Cellular automata (4 iterations)
123    for _ in 0..4 {
124        // Count neighbours in the previous generation. Counting in `grid`
125        // while writing to it (as this did) let cells already turned solid in
126        // this pass vote for their neighbours, and the cave filled in solid.
127        let old_grid = grid.clone();
128        let old = &old_grid.solid;
129        for z in 1..d - 1 {
130            for y in 1..h - 1 {
131                for x in 1..w - 1 {
132                    let neighbors = old_grid.count_neighbors(x, y, z);
133                    let idx = grid.idx(x, y, z);
134                    grid.solid[idx] = if old[idx] {
135                        // Survival: stay solid if >= 13 solid neighbors
136                        neighbors >= 13
137                    } else {
138                        // Birth: become solid if >= 14 solid neighbors
139                        neighbors >= 14
140                    };
141                }
142            }
143        }
144    }
145
146    // 3. Detect features
147    let features = detect_features(&grid);
148
149    // 4. Find entrances (top layer openings)
150    let mut entrances = Vec::new();
151    let top_z = d - 2;
152    for y in 1..h - 1 {
153        for x in 1..w - 1 {
154            if !grid.get(x as i32, y as i32, top_z as i32) {
155                entrances.push((x, y, top_z));
156                if entrances.len() >= 4 { break; }
157            }
158        }
159        if entrances.len() >= 4 { break; }
160    }
161
162    CaveSystem { grid, features, entrances }
163}
164
165/// Simple feature detection.
166fn detect_features(grid: &VoxelGrid) -> Vec<CaveFeature> {
167    let mut features = Vec::new();
168    let w = grid.width;
169    let h = grid.height;
170    let d = grid.depth;
171
172    // Find chambers: connected open regions larger than threshold
173    let mut visited = vec![false; w * h * d];
174    for z in 1..d - 1 {
175        for y in 1..h - 1 {
176            for x in 1..w - 1 {
177                let idx = grid.idx(x, y, z);
178                if grid.solid[idx] || visited[idx] { continue; }
179
180                // Flood fill
181                let mut stack = vec![(x, y, z)];
182                let mut cells = Vec::new();
183                while let Some((cx, cy, cz)) = stack.pop() {
184                    let ci = grid.idx(cx, cy, cz);
185                    if visited[ci] || grid.solid[ci] { continue; }
186                    visited[ci] = true;
187                    cells.push((cx, cy, cz));
188
189                    for &(dx, dy, dz) in &[
190                        (1i32, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)
191                    ] {
192                        let nx = cx as i32 + dx;
193                        let ny = cy as i32 + dy;
194                        let nz = cz as i32 + dz;
195                        if nx > 0 && ny > 0 && nz > 0
196                            && (nx as usize) < w - 1 && (ny as usize) < h - 1 && (nz as usize) < d - 1
197                        {
198                            let ni = grid.idx(nx as usize, ny as usize, nz as usize);
199                            if !visited[ni] && !grid.solid[ni] {
200                                stack.push((nx as usize, ny as usize, nz as usize));
201                            }
202                        }
203                    }
204                }
205
206                if cells.len() > 50 {
207                    // Compute center
208                    let (sx, sy, sz): (usize, usize, usize) = cells.iter()
209                        .fold((0, 0, 0), |(ax, ay, az), (x, y, z)| (ax + x, ay + y, az + z));
210                    let n = cells.len();
211                    features.push(CaveFeature::Chamber {
212                        center: (sx / n, sy / n, sz / n),
213                        volume: n,
214                    });
215                }
216
217                // Detect lakes (open cells at z=1)
218                let lake_cells: Vec<_> = cells.iter().filter(|&&(_, _, z)| z <= 2).cloned().collect();
219                if lake_cells.len() > 10 {
220                    features.push(CaveFeature::Lake { cells: lake_cells });
221                }
222            }
223        }
224    }
225
226    features
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_cave_generation() {
235        let mut rng = Rng::new(42);
236        let systems = generate(32, 2, &mut rng);
237        assert_eq!(systems.len(), 2);
238        for sys in &systems {
239            assert!(sys.grid.open_count() > 0, "cave should have open spaces");
240        }
241    }
242
243    #[test]
244    fn test_voxel_grid() {
245        let mut g = VoxelGrid::new(4, 4, 4);
246        assert!(g.get(0, 0, 0)); // default solid
247        g.set(2, 2, 2, false);
248        assert!(!g.get(2, 2, 2));
249    }
250}