1use std::collections::{HashMap, HashSet};
2
3use serde::{Deserialize, Serialize};
4
5use crate::map::MapLevels;
6use crate::nav::pathfinder::{self, Edge};
7use crate::rng::Rng;
8
9const COEF_GRID_STEP: f32 = 2.0;
11
12const LEDGE_PENALTY: f32 = 1500.0;
16
17#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
20pub struct PathPoint {
21 pub pos: [f32; 2],
22 pub level: u8,
23}
24
25#[derive(Clone, Default, Serialize, Deserialize)]
28pub struct NavigationSystem {
29 nav_grid: Vec<Vec<u8>>,
30 grid_step: f32,
31 nodes: Vec<[f32; 2]>,
32 edges: Vec<Vec<Edge>>,
33 node_grid: HashMap<(i32, i32), Vec<usize>>,
34 node_grid_cell_size: f32,
35 #[serde(default)]
38 node_levels: Vec<u8>,
39 #[serde(default)]
42 upper_grids: Vec<Vec<Vec<u8>>>,
43 #[serde(default)]
45 ramp_edges: usize,
46 #[serde(default)]
47 ledge_edges: usize,
48}
49
50impl NavigationSystem {
51 pub fn generate(grid: &[Vec<i32>], physics_static: &[i32], step: f32) -> Self {
54 let mut nav = Self::default();
55
56 if grid.is_empty() || step <= 0.0 {
57 return nav;
58 }
59
60 nav.grid_step = step;
61 nav.nav_grid = grid
62 .iter()
63 .map(|row| {
64 row.iter()
65 .map(|tile| u8::from(physics_static.contains(tile)))
66 .collect()
67 })
68 .collect();
69
70 let node_placement_step = step * COEF_GRID_STEP;
71 let map_width = nav.nav_grid[0].len() as f32 * step;
72 let map_height = nav.nav_grid.len() as f32 * step;
73
74 let mut x = node_placement_step / 2.0;
76
77 while x < map_width {
78 let mut y = node_placement_step / 2.0;
79
80 while y < map_height {
81 if nav.is_walkable(x, y) {
82 nav.nodes.push([x, y]);
83 }
84
85 y += node_placement_step;
86 }
87
88 x += node_placement_step;
89 }
90
91 let max_connection_dist_sq =
93 node_placement_step * 1.5 * (node_placement_step * 1.5);
94
95 nav.edges = vec![Vec::new(); nav.nodes.len()];
96
97 for i in 0..nav.nodes.len() {
98 for j in (i + 1)..nav.nodes.len() {
99 let dx = nav.nodes[i][0] - nav.nodes[j][0];
100 let dy = nav.nodes[i][1] - nav.nodes[j][1];
101 let dist_sq = dx * dx + dy * dy;
102
103 if dist_sq <= max_connection_dist_sq
104 && !nav.has_obstacle_between(nav.nodes[i], nav.nodes[j])
105 {
106 let distance = dist_sq.sqrt();
107
108 nav.edges[i].push(Edge {
109 node: j,
110 weight: distance,
111 });
112 nav.edges[j].push(Edge {
113 node: i,
114 weight: distance,
115 });
116 }
117 }
118 }
119
120 nav.node_grid_cell_size = node_placement_step;
122
123 for (index, node) in nav.nodes.iter().enumerate() {
124 let cx = (node[0] / nav.node_grid_cell_size).floor() as i32;
125 let cy = (node[1] / nav.node_grid_cell_size).floor() as i32;
126
127 nav.node_grid.entry((cx, cy)).or_default().push(index);
128 }
129
130 nav
131 }
132
133 pub fn generate_layered(levels: &MapLevels, step: f32) -> Self {
136 let mut nav = Self::default();
137 let Some(grid0) = levels.grid(0) else {
138 return nav;
139 };
140
141 if grid0.is_empty() || step <= 0.0 {
142 return nav;
143 }
144
145 nav.grid_step = step;
146 nav.nav_grid = grid0
147 .iter()
148 .map(|row| {
149 row.iter()
150 .map(|tile| u8::from(levels.solid(0).contains(tile)))
151 .collect()
152 })
153 .collect();
154
155 for level in 1..levels.level_count() as u8 {
157 let Some(grid) = levels.grid(level) else {
158 continue;
159 };
160
161 nav.upper_grids.push(
162 grid.iter()
163 .map(|row| {
164 row.iter()
165 .map(|tile| {
166 u8::from(
167 !levels.floor(level).contains(tile)
168 || levels.solid(level).contains(tile),
169 )
170 })
171 .collect()
172 })
173 .collect(),
174 );
175 }
176
177 let node_placement_step = step * COEF_GRID_STEP;
178 let map_width = nav.nav_grid[0].len() as f32 * step;
179 let map_height = nav.nav_grid.len() as f32 * step;
180
181 for level in 0..nav.level_count() as u8 {
184 let mut x = node_placement_step / 2.0;
185
186 while x < map_width {
187 let mut y = node_placement_step / 2.0;
188
189 while y < map_height {
190 if nav.is_walkable_on(level, x, y) {
191 nav.nodes.push([x, y]);
192 nav.node_levels.push(level);
193 }
194
195 y += node_placement_step;
196 }
197
198 x += node_placement_step;
199 }
200 }
201
202 let max_connection_dist_sq = node_placement_step * 1.5 * (node_placement_step * 1.5);
204
205 nav.edges = vec![Vec::new(); nav.nodes.len()];
206
207 for i in 0..nav.nodes.len() {
208 for j in (i + 1)..nav.nodes.len() {
209 if nav.node_levels[i] != nav.node_levels[j] {
210 continue;
211 }
212
213 let dx = nav.nodes[i][0] - nav.nodes[j][0];
214 let dy = nav.nodes[i][1] - nav.nodes[j][1];
215 let dist_sq = dx * dx + dy * dy;
216
217 if dist_sq <= max_connection_dist_sq
218 && !nav.has_obstacle_between_on(nav.node_levels[i], nav.nodes[i], nav.nodes[j])
219 {
220 let distance = dist_sq.sqrt();
221
222 nav.edges[i].push(Edge {
223 node: j,
224 weight: distance,
225 });
226 nav.edges[j].push(Edge {
227 node: i,
228 weight: distance,
229 });
230 }
231 }
232 }
233
234 nav.node_grid_cell_size = node_placement_step;
236
237 for (index, node) in nav.nodes.iter().enumerate() {
238 let cx = (node[0] / nav.node_grid_cell_size).floor() as i32;
239 let cy = (node[1] / nav.node_grid_cell_size).floor() as i32;
240
241 nav.node_grid.entry((cx, cy)).or_default().push(index);
242 }
243
244 nav.connect_ramps(levels);
245 nav.connect_ledges(levels);
246
247 nav
248 }
249
250 fn connect_ramps(&mut self, levels: &MapLevels) {
252 let half = levels.tile_size() / 2.0;
253
254 for run in levels.runs() {
255 let cross = (run.cross_min + run.cross_max) / 2.0;
256 let (bottom_along, top_along) = if run.sign > 0 {
260 (run.min - half, run.max + half)
261 } else {
262 (run.max + half, run.min - half)
263 };
264
265 let point = |along: f32| {
266 if run.axis == 0 {
267 [along, cross]
268 } else {
269 [cross, along]
270 }
271 };
272
273 let (Some(bottom), Some(top)) = (
274 self.closest_visible_node_on(run.from, point(bottom_along)),
275 self.closest_visible_node_on(run.to, point(top_along)),
276 ) else {
277 continue;
278 };
279
280 let weight = distance(self.nodes[bottom], self.nodes[top]);
281
282 self.edges[bottom].push(Edge {
283 node: top,
284 weight,
285 });
286 self.edges[top].push(Edge {
287 node: bottom,
288 weight,
289 });
290 self.ramp_edges += 2;
291 }
292 }
293
294 fn connect_ledges(&mut self, levels: &MapLevels) {
296 let size = levels.tile_size();
297 let half = size / 2.0;
298 let rows = self.nav_grid.len();
299 let cols = self.nav_grid.first().map(|row| row.len()).unwrap_or(0);
300 let mut seen: HashSet<(usize, usize)> = HashSet::new();
301
302 for level in 1..self.level_count() as u8 {
303 for cy in 0..rows {
304 for cx in 0..cols {
305 let x = cx as f32 * size + half;
306 let y = cy as f32 * size + half;
307
308 if !self.is_walkable_on(level, x, y) {
309 continue;
310 }
311
312 for (dx, dy) in [(1i64, 0i64), (-1, 0), (0, 1), (0, -1)] {
313 let nx = cx as i64 + dx;
314 let ny = cy as i64 + dy;
315
316 if nx < 0 || ny < 0 || nx >= cols as i64 || ny >= rows as i64 {
317 continue;
318 }
319
320 let wx = nx as f32 * size + half;
321 let wy = ny as f32 * size + half;
322
323 if levels.has_floor(level, wx, wy) {
324 continue;
325 }
326
327 let (Some(top), Some(bottom)) = (
328 self.closest_visible_node_on(level, [x, y]),
329 self.closest_visible_node_on(0, [wx, wy]),
330 ) else {
331 continue;
332 };
333
334 if !seen.insert((top, bottom)) {
335 continue;
336 }
337
338 self.edges[top].push(Edge {
339 node: bottom,
340 weight: distance(self.nodes[top], self.nodes[bottom]) + LEDGE_PENALTY,
341 });
342 self.ledge_edges += 1;
343 }
344 }
345 }
346 }
347 }
348
349 pub fn has_nodes(&self) -> bool {
350 !self.nodes.is_empty()
351 }
352
353 pub fn node_count(&self) -> usize {
356 self.nodes.len()
357 }
358
359 pub fn edge_count(&self) -> usize {
360 self.edges.iter().map(|edges| edges.len()).sum()
361 }
362
363 pub fn grid_step(&self) -> f32 {
364 self.grid_step
365 }
366
367 pub fn random_node(&self, rng: &mut Rng) -> Option<[f32; 2]> {
369 if self.nodes.is_empty() {
370 return None;
371 }
372
373 let index = (rng.next_f32() * self.nodes.len() as f32).floor() as usize;
374
375 self.nodes.get(index).copied()
376 }
377
378 fn grid_of(&self, level: u8) -> Option<&Vec<Vec<u8>>> {
380 if level == 0 {
381 Some(&self.nav_grid)
382 } else {
383 self.upper_grids.get(level as usize - 1)
384 }
385 }
386
387 pub fn is_walkable(&self, x: f32, y: f32) -> bool {
389 self.is_walkable_on(0, x, y)
390 }
391
392 pub fn is_walkable_on(&self, level: u8, x: f32, y: f32) -> bool {
394 let Some(grid) = self.grid_of(level) else {
395 return false;
396 };
397
398 if grid.is_empty() || self.grid_step == 0.0 {
399 return false;
400 }
401
402 let grid_x = (x / self.grid_step).floor();
403 let grid_y = (y / self.grid_step).floor();
404
405 if grid_x < 0.0 || grid_y < 0.0 {
406 return false;
407 }
408
409 grid.get(grid_y as usize)
410 .and_then(|row| row.get(grid_x as usize))
411 .is_some_and(|&cell| cell == 0)
412 }
413
414 pub fn has_obstacle_between(&self, start: [f32; 2], end: [f32; 2]) -> bool {
417 self.has_obstacle_between_on(0, start, end)
418 }
419
420 pub fn has_obstacle_between_on(&self, level: u8, start: [f32; 2], end: [f32; 2]) -> bool {
422 let Some(grid) = self.grid_of(level) else {
423 return true;
424 };
425
426 let mut x0 = (start[0] / self.grid_step).floor() as i64;
427 let mut y0 = (start[1] / self.grid_step).floor() as i64;
428 let x1 = (end[0] / self.grid_step).floor() as i64;
429 let y1 = (end[1] / self.grid_step).floor() as i64;
430
431 let dx = (x1 - x0).abs();
432 let dy = -(y1 - y0).abs();
433 let sx = if x0 < x1 { 1 } else { -1 };
434 let sy = if y0 < y1 { 1 } else { -1 };
435 let mut err = dx + dy;
436
437 loop {
438 let is_wall = y0 >= 0
439 && x0 >= 0
440 && grid
441 .get(y0 as usize)
442 .and_then(|row| row.get(x0 as usize))
443 .is_some_and(|&cell| cell == 1);
444
445 if is_wall {
446 return true;
447 }
448
449 if x0 == x1 && y0 == y1 {
450 break;
451 }
452
453 let e2 = 2 * err;
454
455 if e2 >= dy {
456 err += dy;
457 x0 += sx;
458 }
459
460 if e2 <= dx {
461 err += dx;
462 y0 += sy;
463 }
464 }
465
466 false
467 }
468
469 pub fn find_path(&self, start: [f32; 2], end: [f32; 2]) -> Option<Vec<[f32; 2]>> {
471 if self.nodes.is_empty() {
472 return None;
473 }
474
475 if !self.has_obstacle_between(start, end) {
476 return Some(vec![end]);
477 }
478
479 let start_node = self.closest_visible_node(start)?;
480 let end_node = self.closest_visible_node(end)?;
481
482 if start_node == end_node {
483 return None;
484 }
485
486 let path_indexes = pathfinder::find_path(start_node, end_node, &self.nodes, &self.edges)?;
487
488 let mut path: Vec<[f32; 2]> = path_indexes
489 .into_iter()
490 .map(|index| self.nodes[index])
491 .collect();
492
493 path.push(end);
494
495 Some(path)
496 }
497
498 pub fn find_path_on(&self, start: PathPoint, end: PathPoint) -> Option<Vec<PathPoint>> {
500 if self.nodes.is_empty() {
501 return None;
502 }
503
504 if start.level == end.level
507 && !self.has_obstacle_between_on(start.level, start.pos, end.pos)
508 {
509 return Some(vec![end]);
510 }
511
512 let start_node = self.closest_visible_node_on(start.level, start.pos)?;
513 let end_node = self.closest_visible_node_on(end.level, end.pos)?;
514
515 if start_node == end_node {
516 return None;
517 }
518
519 let path_indexes = pathfinder::find_path(start_node, end_node, &self.nodes, &self.edges)?;
520
521 let mut path: Vec<PathPoint> = path_indexes
522 .into_iter()
523 .map(|index| PathPoint {
524 pos: self.nodes[index],
525 level: self.node_level(index),
526 })
527 .collect();
528
529 path.push(end);
530
531 Some(path)
532 }
533
534 pub fn random_point(&self, rng: &mut Rng) -> Option<PathPoint> {
536 if self.nodes.is_empty() {
537 return None;
538 }
539
540 let index = (rng.next_f32() * self.nodes.len() as f32).floor() as usize;
541
542 self.nodes.get(index).map(|&pos| PathPoint {
543 pos,
544 level: self.node_level(index),
545 })
546 }
547
548 pub fn node_level(&self, index: usize) -> u8 {
551 self.node_levels.get(index).copied().unwrap_or(0)
552 }
553
554 pub fn level_count(&self) -> usize {
556 self.upper_grids.len() + 1
557 }
558
559 pub fn nodes_by_level(&self) -> Vec<usize> {
561 let mut counts = vec![0usize; self.level_count()];
562
563 for index in 0..self.nodes.len() {
564 let level = self.node_level(index) as usize;
565
566 if let Some(count) = counts.get_mut(level) {
567 *count += 1;
568 }
569 }
570
571 counts
572 }
573
574 pub fn ramp_edge_count(&self) -> usize {
575 self.ramp_edges
576 }
577
578 pub fn ledge_edge_count(&self) -> usize {
579 self.ledge_edges
580 }
581
582 fn closest_visible_node(&self, position: [f32; 2]) -> Option<usize> {
584 self.closest_visible_node_on(0, position)
585 }
586
587 fn closest_visible_node_on(&self, level: u8, position: [f32; 2]) -> Option<usize> {
589 if self.nodes.is_empty() || self.node_grid_cell_size == 0.0 {
590 return None;
591 }
592
593 let center_cx = (position[0] / self.node_grid_cell_size).floor() as i32;
594 let center_cy = (position[1] / self.node_grid_cell_size).floor() as i32;
595 let mut candidates: Vec<usize> = Vec::new();
596
597 for cy in (center_cy - 1)..=(center_cy + 1) {
598 for cx in (center_cx - 1)..=(center_cx + 1) {
599 if let Some(cell) = self.node_grid.get(&(cx, cy)) {
600 candidates.extend_from_slice(cell);
601 }
602 }
603 }
604
605 let mut closest: Option<usize> = None;
606 let mut min_distance_sq = f32::INFINITY;
607
608 for index in candidates {
609 if self.node_level(index) != level {
610 continue;
611 }
612
613 let node = self.nodes[index];
614
615 if !self.has_obstacle_between_on(level, position, node) {
616 let dx = position[0] - node[0];
617 let dy = position[1] - node[1];
618 let distance_sq = dx * dx + dy * dy;
619
620 if distance_sq < min_distance_sq {
621 min_distance_sq = distance_sq;
622 closest = Some(index);
623 }
624 }
625 }
626
627 closest
628 }
629}
630
631fn distance(a: [f32; 2], b: [f32; 2]) -> f32 {
633 (a[0] - b[0]).hypot(a[1] - b[1])
634}
635
636#[cfg(test)]
637mod tests {
638 use indexmap::IndexMap;
639
640 use super::*;
641
642 use crate::map::MapLevels;
643
644 fn walled_grid() -> Vec<Vec<i32>> {
646 vec![
647 vec![1, 1, 1, 1, 1, 1],
648 vec![1, 0, 0, 0, 0, 1],
649 vec![1, 0, 0, 0, 0, 1],
650 vec![1, 0, 0, 0, 0, 1],
651 vec![1, 0, 0, 0, 0, 1],
652 vec![1, 1, 1, 1, 1, 1],
653 ]
654 }
655
656 fn layered(with_ramp: bool) -> MapLevels {
659 use crate::map::{MapLevelConfig, RampConfig, RampDir};
660
661 let mut grid0 = vec![vec![0; 8]; 8];
662
663 if with_ramp {
664 grid0[4][3] = 3;
665 }
666
667 let grid1: Vec<Vec<i32>> = (0..8)
668 .map(|_| (0..8).map(|x| if x >= 4 { 9 } else { 0 }).collect())
669 .collect();
670
671 let mut levels = IndexMap::new();
672
673 levels.insert(
674 "1".to_string(),
675 MapLevelConfig {
676 map: grid1,
677 floor: vec![9],
678 walls: vec![],
679 layers: IndexMap::new(),
680 },
681 );
682
683 let ramps = if with_ramp {
684 vec![RampConfig {
685 tile: 3,
686 dir: RampDir::East,
687 from: 0,
688 to: 1,
689 }]
690 } else {
691 vec![]
692 };
693
694 MapLevels::build(&grid0, &[], &levels, &ramps, 10.0)
695 }
696
697 #[test]
698 fn layered_graph_places_nodes_on_both_levels() {
699 let nav = NavigationSystem::generate_layered(&layered(true), 10.0);
700 let counts = nav.nodes_by_level();
701
702 assert_eq!(nav.level_count(), 2);
703 assert!(counts[0] > 0 && counts[1] > 0, "{counts:?}");
704 assert_eq!(nav.node_levels.len(), nav.node_count());
705 }
706
707 #[test]
708 fn upper_level_nodes_only_on_floor() {
709 let nav = NavigationSystem::generate_layered(&layered(true), 10.0);
710
711 for index in 0..nav.node_count() {
712 if nav.node_level(index) == 1 {
713 assert!(nav.nodes[index][0] >= 40.0, "{:?}", nav.nodes[index]);
714 }
715 }
716 }
717
718 #[test]
719 fn ramp_edge_connects_levels() {
720 let nav = NavigationSystem::generate_layered(&layered(true), 10.0);
721
722 assert!(nav.ramp_edge_count() > 0);
723
724 let path = nav
725 .find_path_on(
726 PathPoint {
727 pos: [15.0, 15.0],
728 level: 0,
729 },
730 PathPoint {
731 pos: [75.0, 45.0],
732 level: 1,
733 },
734 )
735 .expect("путь через рампу не найден");
736
737 assert!(path.iter().any(|point| point.level == 1));
738 assert!(path.iter().any(|point| point.level == 0));
739 }
740
741 #[test]
742 fn no_path_between_levels_without_ramp() {
743 let nav = NavigationSystem::generate_layered(&layered(false), 10.0);
744
745 assert_eq!(nav.ramp_edge_count(), 0);
746 assert!(
747 nav.find_path_on(
748 PathPoint {
749 pos: [15.0, 15.0],
750 level: 0,
751 },
752 PathPoint {
753 pos: [75.0, 45.0],
754 level: 1,
755 },
756 )
757 .is_none()
758 );
759 }
760
761 #[test]
762 fn ledge_edge_is_one_way() {
763 let nav = NavigationSystem::generate_layered(&layered(false), 10.0);
764
765 assert!(nav.ledge_edge_count() > 0);
766 assert!(
768 nav.find_path_on(
769 PathPoint {
770 pos: [75.0, 45.0],
771 level: 1,
772 },
773 PathPoint {
774 pos: [15.0, 15.0],
775 level: 0,
776 },
777 )
778 .is_some()
779 );
780 assert!(
782 nav.find_path_on(
783 PathPoint {
784 pos: [15.0, 15.0],
785 level: 0,
786 },
787 PathPoint {
788 pos: [75.0, 45.0],
789 level: 1,
790 },
791 )
792 .is_none()
793 );
794 }
795
796 #[test]
797 fn legacy_generate_unchanged() {
798 let nav = NavigationSystem::generate(&walled_grid(), &[1], 10.0);
799
800 assert_eq!(nav.level_count(), 1);
802 assert!(nav.node_levels.is_empty());
803 assert_eq!(nav.node_count(), 4);
804 assert_eq!(nav.edge_count(), 12);
805 assert_eq!(nav.ramp_edge_count(), 0);
806 assert_eq!(nav.ledge_edge_count(), 0);
807 }
808
809 #[test]
810 fn walkable_inside_not_on_walls() {
811 let nav = NavigationSystem::generate(&walled_grid(), &[1], 10.0);
812
813 assert!(nav.is_walkable(25.0, 25.0));
814 assert!(!nav.is_walkable(5.0, 5.0)); assert!(!nav.is_walkable(-5.0, 25.0)); }
817
818 #[test]
819 fn line_of_sight_blocked_by_wall() {
820 let grid = vec![
821 vec![0, 0, 0],
822 vec![0, 1, 0],
823 vec![0, 0, 0],
824 ];
825 let nav = NavigationSystem::generate(&grid, &[1], 10.0);
826
827 assert!(nav.has_obstacle_between([5.0, 5.0], [25.0, 25.0]));
829 assert!(!nav.has_obstacle_between([5.0, 5.0], [25.0, 5.0]));
831 }
832
833 #[test]
834 fn direct_path_when_visible() {
835 let nav = NavigationSystem::generate(&walled_grid(), &[1], 10.0);
836 let path = nav.find_path([15.0, 15.0], [45.0, 45.0]).unwrap();
837
838 assert_eq!(path, vec![[45.0, 45.0]]);
839 }
840}