1use crate::grid::{line_points, Cell};
4use crate::semantic::{MarkerType, SemanticLayers};
5use crate::spatial::{shortest_path, PathfindingConstraints};
6use crate::{Grid, Rng, Tile};
7use std::collections::HashSet;
8use std::collections::VecDeque;
9
10#[derive(Debug, Clone, Copy)]
12pub enum MarkerConnectMethod {
14 Line,
16 Path,
18}
19
20pub fn label_regions(grid: &Grid<Tile>) -> (Vec<u32>, u32) {
22 let (w, h) = (grid.width(), grid.height());
23 let regions = grid.flood_regions();
24 let mut labels = vec![0u32; w * h];
25 for (i, region) in regions.iter().enumerate() {
26 let label = (i + 1) as u32;
27 for &(x, y) in region {
28 labels[y * w + x] = label;
29 }
30 }
31 (labels, regions.len() as u32)
32}
33
34pub fn carve_path(grid: &mut Grid<Tile>, path: &[(usize, usize)], radius: usize) {
37 if path.is_empty() {
38 return;
39 }
40
41 for &(x, y) in path {
42 carve_point(grid, x as i32, y as i32, radius);
43 }
44}
45
46pub fn clear_rect(grid: &mut Grid<Tile>, center: (usize, usize), w: usize, h: usize) {
49 if w == 0 || h == 0 {
50 return;
51 }
52
53 let x = center.0 as i32 - (w as i32 / 2);
54 let y = center.1 as i32 - (h as i32 / 2);
55 grid.fill_rect(x, y, w, h, Tile::Floor);
56}
57
58pub fn connect_markers(
61 grid: &mut Grid<Tile>,
62 layers: &SemanticLayers,
63 from: &MarkerType,
64 to: &MarkerType,
65 method: MarkerConnectMethod,
66 radius: usize,
67) -> bool {
68 let from_pos = crate::semantic::marker_positions(layers, from);
69 let to_pos = crate::semantic::marker_positions(layers, to);
70
71 let start = match from_pos.first() {
72 Some(pos) => *pos,
73 None => return false,
74 };
75 let end = match to_pos.first() {
76 Some(pos) => *pos,
77 None => return false,
78 };
79
80 match method {
81 MarkerConnectMethod::Line => {
82 let path = line_points(start, end);
83 carve_path(grid, &path, radius);
84 true
85 }
86 MarkerConnectMethod::Path => {
87 let constraints = PathfindingConstraints::default();
88 if let Some(path) = shortest_path(grid, start, end, &constraints) {
89 carve_path(grid, &path, radius);
90 true
91 } else {
92 false
93 }
94 }
95 }
96}
97
98pub fn connect_regions_spanning(
101 grid: &mut Grid<Tile>,
102 extra_connection_chance: f64,
103 rng: &mut Rng,
104) -> Vec<(usize, usize)> {
105 let (w, h) = (grid.width(), grid.height());
106 let (labels, region_count) = label_regions(grid);
107
108 if region_count <= 1 {
109 return Vec::new();
110 }
111
112 let mut regions: Vec<Vec<(usize, usize)>> = vec![Vec::new(); region_count as usize + 1];
114 for y in 0..h {
115 for x in 0..w {
116 if grid[(x, y)].is_floor() {
117 regions[labels[y * w + x] as usize].push((x, y));
118 }
119 }
120 }
121
122 let mut connectors = Vec::new();
124 for y in 1..h - 1 {
125 for x in 1..w - 1 {
126 if !grid[(x, y)].is_floor() {
127 let adjacent_regions: HashSet<u32> =
128 [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)]
129 .iter()
130 .filter_map(|&(nx, ny)| {
131 if grid[(nx, ny)].is_floor() {
132 Some(labels[ny * w + nx])
133 } else {
134 None
135 }
136 })
137 .collect();
138
139 if adjacent_regions.len() >= 2 {
140 connectors.push((x, y, adjacent_regions.into_iter().collect::<Vec<_>>()));
141 }
142 }
143 }
144 }
145
146 let mut connected_regions = vec![false; region_count as usize + 1];
148 let mut connections_made = Vec::new();
149 let mut edges_used = 0;
150
151 rng.shuffle(&mut connectors);
153
154 for (x, y, adjacent) in &connectors {
155 let unconnected: Vec<u32> = adjacent
157 .iter()
158 .filter(|&&r| !connected_regions[r as usize])
159 .copied()
160 .collect();
161
162 if !unconnected.is_empty() {
163 grid.set(*x as i32, *y as i32, Tile::Floor);
165 connections_made.push((*x, *y));
166
167 for ®ion in &unconnected {
168 connected_regions[region as usize] = true;
169 }
170
171 edges_used += 1;
172 if edges_used >= region_count - 1 {
173 break;
174 }
175 } else if rng.chance(extra_connection_chance) {
176 grid.set(*x as i32, *y as i32, Tile::Floor);
178 connections_made.push((*x, *y));
179 }
180 }
181
182 connections_made
183}
184
185pub fn bridge_gaps(grid: &mut Grid<Tile>, max_distance: usize) {
187 let regions = grid.flood_regions();
188 if regions.len() <= 1 {
189 return;
190 }
191
192 let perimeters: Vec<Vec<(usize, usize)>> = regions
195 .iter()
196 .map(|region| {
197 region
198 .iter()
199 .copied()
200 .filter(|&(x, y)| {
201 [(-1i32, 0), (1, 0), (0, -1), (0, 1)]
202 .iter()
203 .any(|&(dx, dy)| {
204 !grid
205 .get(x as i32 + dx, y as i32 + dy)
206 .is_some_and(|c| c.is_passable())
207 })
208 })
209 .collect()
210 })
211 .collect();
212
213 for r1 in 0..perimeters.len() {
214 for r2 in (r1 + 1)..perimeters.len() {
215 if let Some((x1, y1, x2, y2)) =
216 find_closest(&perimeters[r1], &perimeters[r2], max_distance)
217 {
218 carve_line(grid, x1, y1, x2, y2);
219 }
220 }
221 }
222}
223
224fn find_closest(
225 r1: &[(usize, usize)],
226 r2: &[(usize, usize)],
227 max_dist: usize,
228) -> Option<(usize, usize, usize, usize)> {
229 let mut best = None;
230 let mut best_dist = max_dist + 1;
231
232 for &(x1, y1) in r1 {
233 for &(x2, y2) in r2 {
234 let dist = ((x1 as i32 - x2 as i32).abs() + (y1 as i32 - y2 as i32).abs()) as usize;
235 if dist < best_dist {
236 best_dist = dist;
237 best = Some((x1, y1, x2, y2));
238 }
239 }
240 }
241 best
242}
243
244fn carve_line(grid: &mut Grid<Tile>, x1: usize, y1: usize, x2: usize, y2: usize) {
245 let path = line_points((x1, y1), (x2, y2));
246 carve_path(grid, &path, 0);
247}
248
249pub fn remove_dead_ends(grid: &mut Grid<Tile>, iterations: usize) {
251 let (w, h) = (grid.width(), grid.height());
252
253 for _ in 0..iterations {
254 let mut changed = false;
255 for y in 1..h - 1 {
256 for x in 1..w - 1 {
257 if !grid[(x, y)].is_floor() {
258 continue;
259 }
260 let neighbors = [
261 grid[(x - 1, y)].is_floor(),
262 grid[(x + 1, y)].is_floor(),
263 grid[(x, y - 1)].is_floor(),
264 grid[(x, y + 1)].is_floor(),
265 ];
266 if neighbors.iter().filter(|&&b| b).count() <= 1 {
267 grid.set(x as i32, y as i32, Tile::Wall);
268 changed = true;
269 }
270 }
271 }
272 if !changed {
273 break;
274 }
275 }
276}
277
278pub fn find_chokepoints(grid: &Grid<Tile>) -> Vec<(usize, usize)> {
280 let (w, h) = (grid.width(), grid.height());
281 let mut chokepoints = Vec::new();
282
283 for y in 1..h - 1 {
284 for x in 1..w - 1 {
285 if !grid[(x, y)].is_floor() {
286 continue;
287 }
288
289 let neighbors: Vec<(usize, usize)> = [
290 (x.wrapping_sub(1), y),
291 (x + 1, y),
292 (x, y.wrapping_sub(1)),
293 (x, y + 1),
294 ]
295 .into_iter()
296 .filter(|&(nx, ny)| nx < w && ny < h && grid[(nx, ny)].is_floor())
297 .collect();
298
299 if neighbors.len() >= 2 {
300 let mut visited = vec![false; w * h];
301 visited[y * w + x] = true;
302
303 let start = neighbors[0];
304 let mut queue = VecDeque::new();
305 queue.push_back(start);
306 visited[start.1 * w + start.0] = true;
307
308 while let Some((cx, cy)) = queue.pop_front() {
309 for (nx, ny) in [
310 (cx.wrapping_sub(1), cy),
311 (cx + 1, cy),
312 (cx, cy.wrapping_sub(1)),
313 (cx, cy + 1),
314 ] {
315 if nx < w && ny < h && !visited[ny * w + nx] && grid[(nx, ny)].is_floor() {
316 visited[ny * w + nx] = true;
317 queue.push_back((nx, ny));
318 }
319 }
320 }
321
322 if neighbors
323 .iter()
324 .skip(1)
325 .any(|&(nx, ny)| !visited[ny * w + nx])
326 {
327 chokepoints.push((x, y));
328 }
329 }
330 }
331 }
332 chokepoints
333}
334
335fn carve_point(grid: &mut Grid<Tile>, x: i32, y: i32, radius: usize) {
336 if radius == 0 {
337 grid.set(x, y, Tile::Floor);
338 return;
339 }
340
341 let r = radius as i32;
342 let r2 = r * r;
343 for dy in -r..=r {
344 for dx in -r..=r {
345 if dx * dx + dy * dy <= r2 {
346 grid.set(x + dx, y + dy, Tile::Floor);
347 }
348 }
349 }
350}