terrain_forge/algorithms/
glass_seam.rs1use crate::{Algorithm, Grid, Rng, Tile};
2use std::collections::{HashSet, VecDeque};
3
4pub struct GlassSeam {
5 pub coverage_threshold: f64,
6}
7
8impl Default for GlassSeam {
9 fn default() -> Self { Self { coverage_threshold: 0.75 } }
10}
11
12impl Algorithm<Tile> for GlassSeam {
13 fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
14 let mut rng = Rng::new(seed);
15 let (w, h) = (grid.width(), grid.height());
16
17 for y in 0..h {
19 for x in 0..w {
20 if (x + y) % 7 < 3 {
21 grid.set(x as i32, y as i32, Tile::Floor);
22 }
23 }
24 }
25
26 ensure_connectivity(grid, (5, 5), self.coverage_threshold, &mut rng);
28 }
29
30 fn name(&self) -> &'static str { "GlassSeam" }
31}
32
33fn ensure_connectivity(grid: &mut Grid<Tile>, spawn: (usize, usize), threshold: f64, _rng: &mut Rng) {
34 let regions = identify_regions(grid);
35 if regions.len() <= 1 { return; }
36
37 let spawn_region = regions.iter().position(|r| r.contains(&spawn)).unwrap_or(0);
38 let total_floor: usize = regions.iter().map(|r| r.len()).sum();
39 let mut coverage = regions[spawn_region].len() as f64 / total_floor as f64;
40
41 if coverage >= threshold { return; }
42
43 let mut connected = HashSet::new();
44 connected.insert(spawn_region);
45
46 while coverage < threshold && connected.len() < regions.len() {
47 let mut best = None;
48 let mut best_cost = usize::MAX;
49
50 for (i, region) in regions.iter().enumerate() {
51 if connected.contains(&i) { continue; }
52 for &ci in &connected {
53 let cost = connection_cost(®ions[ci], region);
54 if cost < best_cost {
55 best_cost = cost;
56 best = Some((i, ci));
57 }
58 }
59 }
60
61 if let Some((target, source)) = best {
62 connect_regions(grid, ®ions[source], ®ions[target]);
63 connected.insert(target);
64 coverage += regions[target].len() as f64 / total_floor as f64;
65 } else {
66 break;
67 }
68 }
69}
70
71fn identify_regions(grid: &Grid<Tile>) -> Vec<Vec<(usize, usize)>> {
72 let (w, h) = (grid.width(), grid.height());
73 let mut visited = vec![vec![false; h]; w];
74 let mut regions = Vec::new();
75
76 for x in 0..w {
77 for y in 0..h {
78 if !visited[x][y] && grid[(x, y)].is_floor() {
79 let region = flood_fill(grid, x, y, &mut visited);
80 if !region.is_empty() { regions.push(region); }
81 }
82 }
83 }
84 regions
85}
86
87fn flood_fill(grid: &Grid<Tile>, sx: usize, sy: usize, visited: &mut [Vec<bool>]) -> Vec<(usize, usize)> {
88 let (w, h) = (grid.width(), grid.height());
89 let mut region = Vec::new();
90 let mut queue = VecDeque::new();
91 queue.push_back((sx, sy));
92
93 while let Some((x, y)) = queue.pop_front() {
94 if x >= w || y >= h || visited[x][y] || !grid[(x, y)].is_floor() { continue; }
95 visited[x][y] = true;
96 region.push((x, y));
97
98 if x > 0 { queue.push_back((x - 1, y)); }
99 if x + 1 < w { queue.push_back((x + 1, y)); }
100 if y > 0 { queue.push_back((x, y - 1)); }
101 if y + 1 < h { queue.push_back((x, y + 1)); }
102 }
103 region
104}
105
106fn connection_cost(a: &[(usize, usize)], b: &[(usize, usize)]) -> usize {
107 let ca = centroid(a);
108 let cb = centroid(b);
109 ((ca.0 as i32 - cb.0 as i32).abs() + (ca.1 as i32 - cb.1 as i32).abs()) as usize
110}
111
112fn centroid(region: &[(usize, usize)]) -> (usize, usize) {
113 if region.is_empty() { return (0, 0); }
114 let sx: usize = region.iter().map(|p| p.0).sum();
115 let sy: usize = region.iter().map(|p| p.1).sum();
116 (sx / region.len(), sy / region.len())
117}
118
119fn connect_regions(grid: &mut Grid<Tile>, source: &[(usize, usize)], target: &[(usize, usize)]) {
120 let from = centroid(source);
121 let to = centroid(target);
122
123 let (mut x, mut y) = (from.0 as i32, from.1 as i32);
124 let (tx, ty) = (to.0 as i32, to.1 as i32);
125
126 while x != tx || y != ty {
127 grid.set(x, y, Tile::Floor);
128 if (x - tx).abs() > (y - ty).abs() {
129 x += if tx > x { 1 } else { -1 };
130 } else {
131 y += if ty > y { 1 } else { -1 };
132 }
133 }
134 grid.set(tx, ty, Tile::Floor);
135}