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