1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::nav::pathfinder::{self, Edge};
6use crate::rng::Rng;
7
8const COEF_GRID_STEP: f32 = 2.0;
10
11#[derive(Clone, Default, Serialize, Deserialize)]
14pub struct NavigationSystem {
15 nav_grid: Vec<Vec<u8>>,
16 grid_step: f32,
17 nodes: Vec<[f32; 2]>,
18 edges: Vec<Vec<Edge>>,
19 node_grid: HashMap<(i32, i32), Vec<usize>>,
20 node_grid_cell_size: f32,
21}
22
23impl NavigationSystem {
24 pub fn generate(grid: &[Vec<i32>], physics_static: &[i32], step: f32) -> Self {
27 let mut nav = Self::default();
28
29 if grid.is_empty() || step <= 0.0 {
30 return nav;
31 }
32
33 nav.grid_step = step;
34 nav.nav_grid = grid
35 .iter()
36 .map(|row| {
37 row.iter()
38 .map(|tile| u8::from(physics_static.contains(tile)))
39 .collect()
40 })
41 .collect();
42
43 let node_placement_step = step * COEF_GRID_STEP;
44 let map_width = nav.nav_grid[0].len() as f32 * step;
45 let map_height = nav.nav_grid.len() as f32 * step;
46
47 let mut x = node_placement_step / 2.0;
49
50 while x < map_width {
51 let mut y = node_placement_step / 2.0;
52
53 while y < map_height {
54 if nav.is_walkable(x, y) {
55 nav.nodes.push([x, y]);
56 }
57
58 y += node_placement_step;
59 }
60
61 x += node_placement_step;
62 }
63
64 let max_connection_dist_sq =
66 node_placement_step * 1.5 * (node_placement_step * 1.5);
67
68 nav.edges = vec![Vec::new(); nav.nodes.len()];
69
70 for i in 0..nav.nodes.len() {
71 for j in (i + 1)..nav.nodes.len() {
72 let dx = nav.nodes[i][0] - nav.nodes[j][0];
73 let dy = nav.nodes[i][1] - nav.nodes[j][1];
74 let dist_sq = dx * dx + dy * dy;
75
76 if dist_sq <= max_connection_dist_sq
77 && !nav.has_obstacle_between(nav.nodes[i], nav.nodes[j])
78 {
79 let distance = dist_sq.sqrt();
80
81 nav.edges[i].push(Edge {
82 node: j,
83 weight: distance,
84 });
85 nav.edges[j].push(Edge {
86 node: i,
87 weight: distance,
88 });
89 }
90 }
91 }
92
93 nav.node_grid_cell_size = node_placement_step;
95
96 for (index, node) in nav.nodes.iter().enumerate() {
97 let cx = (node[0] / nav.node_grid_cell_size).floor() as i32;
98 let cy = (node[1] / nav.node_grid_cell_size).floor() as i32;
99
100 nav.node_grid.entry((cx, cy)).or_default().push(index);
101 }
102
103 nav
104 }
105
106 pub fn has_nodes(&self) -> bool {
107 !self.nodes.is_empty()
108 }
109
110 pub fn node_count(&self) -> usize {
113 self.nodes.len()
114 }
115
116 pub fn edge_count(&self) -> usize {
117 self.edges.iter().map(|edges| edges.len()).sum()
118 }
119
120 pub fn grid_step(&self) -> f32 {
121 self.grid_step
122 }
123
124 pub fn random_node(&self, rng: &mut Rng) -> Option<[f32; 2]> {
126 if self.nodes.is_empty() {
127 return None;
128 }
129
130 let index = (rng.next_f32() * self.nodes.len() as f32).floor() as usize;
131
132 self.nodes.get(index).copied()
133 }
134
135 pub fn is_walkable(&self, x: f32, y: f32) -> bool {
137 if self.nav_grid.is_empty() || self.grid_step == 0.0 {
138 return false;
139 }
140
141 let grid_x = (x / self.grid_step).floor();
142 let grid_y = (y / self.grid_step).floor();
143
144 if grid_x < 0.0 || grid_y < 0.0 {
145 return false;
146 }
147
148 self.nav_grid
149 .get(grid_y as usize)
150 .and_then(|row| row.get(grid_x as usize))
151 .is_some_and(|&cell| cell == 0)
152 }
153
154 pub fn has_obstacle_between(&self, start: [f32; 2], end: [f32; 2]) -> bool {
157 let mut x0 = (start[0] / self.grid_step).floor() as i64;
158 let mut y0 = (start[1] / self.grid_step).floor() as i64;
159 let x1 = (end[0] / self.grid_step).floor() as i64;
160 let y1 = (end[1] / self.grid_step).floor() as i64;
161
162 let dx = (x1 - x0).abs();
163 let dy = -(y1 - y0).abs();
164 let sx = if x0 < x1 { 1 } else { -1 };
165 let sy = if y0 < y1 { 1 } else { -1 };
166 let mut err = dx + dy;
167
168 loop {
169 let is_wall = y0 >= 0
170 && x0 >= 0
171 && self
172 .nav_grid
173 .get(y0 as usize)
174 .and_then(|row| row.get(x0 as usize))
175 .is_some_and(|&cell| cell == 1);
176
177 if is_wall {
178 return true;
179 }
180
181 if x0 == x1 && y0 == y1 {
182 break;
183 }
184
185 let e2 = 2 * err;
186
187 if e2 >= dy {
188 err += dy;
189 x0 += sx;
190 }
191
192 if e2 <= dx {
193 err += dx;
194 y0 += sy;
195 }
196 }
197
198 false
199 }
200
201 pub fn find_path(&self, start: [f32; 2], end: [f32; 2]) -> Option<Vec<[f32; 2]>> {
203 if self.nodes.is_empty() {
204 return None;
205 }
206
207 if !self.has_obstacle_between(start, end) {
208 return Some(vec![end]);
209 }
210
211 let start_node = self.closest_visible_node(start)?;
212 let end_node = self.closest_visible_node(end)?;
213
214 if start_node == end_node {
215 return None;
216 }
217
218 let path_indexes = pathfinder::find_path(start_node, end_node, &self.nodes, &self.edges)?;
219
220 let mut path: Vec<[f32; 2]> = path_indexes
221 .into_iter()
222 .map(|index| self.nodes[index])
223 .collect();
224
225 path.push(end);
226
227 Some(path)
228 }
229
230 fn closest_visible_node(&self, position: [f32; 2]) -> Option<usize> {
232 if self.nodes.is_empty() || self.node_grid_cell_size == 0.0 {
233 return None;
234 }
235
236 let center_cx = (position[0] / self.node_grid_cell_size).floor() as i32;
237 let center_cy = (position[1] / self.node_grid_cell_size).floor() as i32;
238 let mut candidates: Vec<usize> = Vec::new();
239
240 for cy in (center_cy - 1)..=(center_cy + 1) {
241 for cx in (center_cx - 1)..=(center_cx + 1) {
242 if let Some(cell) = self.node_grid.get(&(cx, cy)) {
243 candidates.extend_from_slice(cell);
244 }
245 }
246 }
247
248 let mut closest: Option<usize> = None;
249 let mut min_distance_sq = f32::INFINITY;
250
251 for index in candidates {
252 let node = self.nodes[index];
253
254 if !self.has_obstacle_between(position, node) {
255 let dx = position[0] - node[0];
256 let dy = position[1] - node[1];
257 let distance_sq = dx * dx + dy * dy;
258
259 if distance_sq < min_distance_sq {
260 min_distance_sq = distance_sq;
261 closest = Some(index);
262 }
263 }
264 }
265
266 closest
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 fn walled_grid() -> Vec<Vec<i32>> {
276 vec![
277 vec![1, 1, 1, 1, 1, 1],
278 vec![1, 0, 0, 0, 0, 1],
279 vec![1, 0, 0, 0, 0, 1],
280 vec![1, 0, 0, 0, 0, 1],
281 vec![1, 0, 0, 0, 0, 1],
282 vec![1, 1, 1, 1, 1, 1],
283 ]
284 }
285
286 #[test]
287 fn walkable_inside_not_on_walls() {
288 let nav = NavigationSystem::generate(&walled_grid(), &[1], 10.0);
289
290 assert!(nav.is_walkable(25.0, 25.0));
291 assert!(!nav.is_walkable(5.0, 5.0)); assert!(!nav.is_walkable(-5.0, 25.0)); }
294
295 #[test]
296 fn line_of_sight_blocked_by_wall() {
297 let grid = vec![
298 vec![0, 0, 0],
299 vec![0, 1, 0],
300 vec![0, 0, 0],
301 ];
302 let nav = NavigationSystem::generate(&grid, &[1], 10.0);
303
304 assert!(nav.has_obstacle_between([5.0, 5.0], [25.0, 25.0]));
306 assert!(!nav.has_obstacle_between([5.0, 5.0], [25.0, 5.0]));
308 }
309
310 #[test]
311 fn direct_path_when_visible() {
312 let nav = NavigationSystem::generate(&walled_grid(), &[1], 10.0);
313 let path = nav.find_path([15.0, 15.0], [45.0, 45.0]).unwrap();
314
315 assert_eq!(path, vec![[45.0, 45.0]]);
316 }
317}