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        let old = grid.solid.clone();
125        for z in 1..d - 1 {
126            for y in 1..h - 1 {
127                for x in 1..w - 1 {
128                    let neighbors = grid.count_neighbors(x, y, z);
129                    let idx = grid.idx(x, y, z);
130                    grid.solid[idx] = if old[idx] {
131                        // Survival: stay solid if >= 13 solid neighbors
132                        neighbors >= 13
133                    } else {
134                        // Birth: become solid if >= 14 solid neighbors
135                        neighbors >= 14
136                    };
137                }
138            }
139        }
140    }
141
142    // 3. Detect features
143    let features = detect_features(&grid);
144
145    // 4. Find entrances (top layer openings)
146    let mut entrances = Vec::new();
147    let top_z = d - 2;
148    for y in 1..h - 1 {
149        for x in 1..w - 1 {
150            if !grid.get(x as i32, y as i32, top_z as i32) {
151                entrances.push((x, y, top_z));
152                if entrances.len() >= 4 { break; }
153            }
154        }
155        if entrances.len() >= 4 { break; }
156    }
157
158    CaveSystem { grid, features, entrances }
159}
160
161/// Simple feature detection.
162fn detect_features(grid: &VoxelGrid) -> Vec<CaveFeature> {
163    let mut features = Vec::new();
164    let w = grid.width;
165    let h = grid.height;
166    let d = grid.depth;
167
168    // Find chambers: connected open regions larger than threshold
169    let mut visited = vec![false; w * h * d];
170    for z in 1..d - 1 {
171        for y in 1..h - 1 {
172            for x in 1..w - 1 {
173                let idx = grid.idx(x, y, z);
174                if grid.solid[idx] || visited[idx] { continue; }
175
176                // Flood fill
177                let mut stack = vec![(x, y, z)];
178                let mut cells = Vec::new();
179                while let Some((cx, cy, cz)) = stack.pop() {
180                    let ci = grid.idx(cx, cy, cz);
181                    if visited[ci] || grid.solid[ci] { continue; }
182                    visited[ci] = true;
183                    cells.push((cx, cy, cz));
184
185                    for &(dx, dy, dz) in &[
186                        (1i32, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)
187                    ] {
188                        let nx = cx as i32 + dx;
189                        let ny = cy as i32 + dy;
190                        let nz = cz as i32 + dz;
191                        if nx > 0 && ny > 0 && nz > 0
192                            && (nx as usize) < w - 1 && (ny as usize) < h - 1 && (nz as usize) < d - 1
193                        {
194                            let ni = grid.idx(nx as usize, ny as usize, nz as usize);
195                            if !visited[ni] && !grid.solid[ni] {
196                                stack.push((nx as usize, ny as usize, nz as usize));
197                            }
198                        }
199                    }
200                }
201
202                if cells.len() > 50 {
203                    // Compute center
204                    let (sx, sy, sz): (usize, usize, usize) = cells.iter()
205                        .fold((0, 0, 0), |(ax, ay, az), (x, y, z)| (ax + x, ay + y, az + z));
206                    let n = cells.len();
207                    features.push(CaveFeature::Chamber {
208                        center: (sx / n, sy / n, sz / n),
209                        volume: n,
210                    });
211                }
212
213                // Detect lakes (open cells at z=1)
214                let lake_cells: Vec<_> = cells.iter().filter(|&&(_, _, z)| z <= 2).cloned().collect();
215                if lake_cells.len() > 10 {
216                    features.push(CaveFeature::Lake { cells: lake_cells });
217                }
218            }
219        }
220    }
221
222    features
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn test_cave_generation() {
231        let mut rng = Rng::new(42);
232        let systems = generate(32, 2, &mut rng);
233        assert_eq!(systems.len(), 2);
234        for sys in &systems {
235            assert!(sys.grid.open_count() > 0, "cave should have open spaces");
236        }
237    }
238
239    #[test]
240    fn test_voxel_grid() {
241        let mut g = VoxelGrid::new(4, 4, 4);
242        assert!(g.get(0, 0, 0)); // default solid
243        g.set(2, 2, 2, false);
244        assert!(!g.get(2, 2, 2));
245    }
246}