1use std::cmp::Ordering;
14use std::collections::{BinaryHeap, HashMap};
15use std::hash::{Hash, Hasher};
16use std::rc::Rc;
17
18use scirs2_core::ndarray::ArrayView1;
19
20use crate::error::{SpatialError, SpatialResult};
21
22#[derive(Debug, Clone)]
24pub struct Path<N> {
25 pub nodes: Vec<N>,
27 pub cost: f64,
29}
30
31impl<N> Path<N> {
32 pub fn new(nodes: Vec<N>, cost: f64) -> Self {
34 Path { nodes, cost }
35 }
36
37 pub fn is_empty(&self) -> bool {
39 self.nodes.is_empty()
40 }
41
42 pub fn len(&self) -> usize {
44 self.nodes.len()
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct Node<N: Clone + Eq + Hash> {
51 pub state: N,
53 pub parent: Option<Rc<Node<N>>>,
55 pub g: f64,
57 pub h: f64,
59}
60
61impl<N: Clone + Eq + Hash> Node<N> {
62 pub fn new(state: N, parent: Option<Rc<Node<N>>>, g: f64, h: f64) -> Self {
64 Node {
65 state,
66 parent,
67 g,
68 h,
69 }
70 }
71
72 pub fn f(&mut self) -> f64 {
74 self.g + self.h
75 }
76}
77
78impl<N: Clone + Eq + Hash> PartialEq for Node<N> {
79 fn eq(&self, other: &Self) -> bool {
80 self.state == other.state
81 }
82}
83
84impl<N: Clone + Eq + Hash> Eq for Node<N> {}
85
86impl<N: Clone + Eq + Hash> Ord for Node<N> {
88 fn cmp(&self, other: &Self) -> Ordering {
89 let self_f = self.g + self.h;
92 let other_f = other.g + other.h;
93 other_f.partial_cmp(&self_f).unwrap_or(Ordering::Equal)
94 }
95}
96
97impl<N: Clone + Eq + Hash> PartialOrd for Node<N> {
98 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
99 Some(self.cmp(other))
100 }
101}
102
103pub type NeighborFn<N> = dyn Fn(&N) -> Vec<(N, f64)>;
105
106pub type HeuristicFn<N> = dyn Fn(&N, &N) -> f64;
108
109#[derive(Debug, Clone, Copy)]
112pub struct HashableFloat2D {
113 pub x: f64,
115 pub y: f64,
117}
118
119impl HashableFloat2D {
120 pub fn new(x: f64, y: f64) -> Self {
122 HashableFloat2D { x, y }
123 }
124
125 pub fn from_array(arr: [f64; 2]) -> Self {
127 HashableFloat2D {
128 x: arr[0],
129 y: arr[1],
130 }
131 }
132
133 pub fn to_array(&self) -> [f64; 2] {
135 [self.x, self.y]
136 }
137
138 pub fn distance(&self, other: &HashableFloat2D) -> f64 {
140 let dx = self.x - other.x;
141 let dy = self.y - other.y;
142 (dx * dx + dy * dy).sqrt()
143 }
144}
145
146impl PartialEq for HashableFloat2D {
147 fn eq(&self, other: &Self) -> bool {
148 const EPSILON: f64 = 1e-10;
150 (self.x - other.x).abs() < EPSILON && (self.y - other.y).abs() < EPSILON
151 }
152}
153
154impl Eq for HashableFloat2D {}
155
156impl Hash for HashableFloat2D {
157 fn hash<H: Hasher>(&self, state: &mut H) {
158 let precision = 1_000_000.0; let x_rounded = (self.x * precision).round() as i64;
161 let y_rounded = (self.y * precision).round() as i64;
162
163 x_rounded.hash(state);
164 y_rounded.hash(state);
165 }
166}
167
168#[derive(Debug)]
170pub struct AStarPlanner {
171 max_iterations: Option<usize>,
173 weight: f64,
174}
175
176impl Default for AStarPlanner {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182impl AStarPlanner {
183 pub fn new() -> Self {
185 AStarPlanner {
186 max_iterations: None,
187 weight: 1.0,
188 }
189 }
190
191 pub fn with_max_iterations(mut self, maxiterations: usize) -> Self {
193 self.max_iterations = Some(maxiterations);
194 self
195 }
196
197 pub fn with_weight(mut self, weight: f64) -> Self {
199 if weight < 0.0 {
200 self.weight = 0.0;
201 } else {
202 self.weight = weight;
203 }
204 self
205 }
206
207 pub fn search<N: Clone + Eq + Hash>(
222 &self,
223 start: N,
224 goal: N,
225 neighbors_fn: &dyn Fn(&N) -> Vec<(N, f64)>,
226 heuristic_fn: &dyn Fn(&N, &N) -> f64,
227 ) -> SpatialResult<Option<Path<N>>> {
228 let mut open_set = BinaryHeap::new();
230 let mut closed_set = HashMap::new();
231
232 let h_start = heuristic_fn(&start, &goal);
233 let start_node = Rc::new(Node::new(start, None, 0.0, self.weight * h_start));
234 open_set.push(Rc::clone(&start_node));
235
236 let mut g_values = HashMap::new();
238 g_values.insert(start_node.state.clone(), 0.0);
239
240 let mut iterations = 0;
241
242 while let Some(current) = open_set.pop() {
243 if current.state == goal {
245 return Ok(Some(AStarPlanner::reconstruct_path(goal.clone(), current)));
246 }
247
248 if let Some(max_iter) = self.max_iterations {
250 iterations += 1;
251 if iterations > max_iter {
252 return Ok(None);
253 }
254 }
255
256 if closed_set.contains_key(¤t.state) {
258 continue;
259 }
260
261 closed_set.insert(current.state.clone(), Rc::clone(¤t));
263
264 for (neighbor_state, cost) in neighbors_fn(¤t.state) {
266 if closed_set.contains_key(&neighbor_state) {
268 continue;
269 }
270
271 let tentative_g = current.g + cost;
273
274 let in_open_set = g_values.contains_key(&neighbor_state);
276 if in_open_set
277 && tentative_g >= *g_values.get(&neighbor_state).expect("Operation failed")
278 {
279 continue;
280 }
281
282 g_values.insert(neighbor_state.clone(), tentative_g);
284
285 let h = self.weight * heuristic_fn(&neighbor_state, &goal);
287 let neighbor_node = Rc::new(Node::new(
288 neighbor_state,
289 Some(Rc::clone(¤t)),
290 tentative_g,
291 h,
292 ));
293
294 open_set.push(neighbor_node);
296 }
297 }
298
299 Ok(None)
301 }
302
303 fn reconstruct_path<N: Clone + Eq + Hash>(goal: N, node: Rc<Node<N>>) -> Path<N> {
305 let mut path = Vec::new();
306 let mut current = Some(node);
307 let mut cost = 0.0;
308
309 while let Some(_node) = current {
310 path.push(_node.state.clone());
311 cost = _node.g;
312 current = _node.parent.clone();
313 }
314
315 path.reverse();
317
318 Path::new(path, cost)
319 }
320}
321
322#[allow(dead_code)]
326pub fn manhattan_distance(a: &[i32; 2], b: &[i32; 2]) -> f64 {
327 ((a[0] - b[0]).abs() + (a[1] - b[1]).abs()) as f64
328}
329
330#[allow(dead_code)]
332pub fn euclidean_distance_2d(a: &[f64; 2], b: &[f64; 2]) -> f64 {
333 let dx = a[0] - b[0];
334 let dy = a[1] - b[1];
335 (dx * dx + dy * dy).sqrt()
336}
337
338#[allow(dead_code)]
340pub fn euclidean_distance(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SpatialResult<f64> {
341 if a.len() != b.len() {
342 return Err(SpatialError::DimensionError(format!(
343 "Mismatched dimensions: {} and {}",
344 a.len(),
345 b.len()
346 )));
347 }
348
349 let mut sum = 0.0;
350 for i in 0..a.len() {
351 let diff = a[i] - b[i];
352 sum += diff * diff;
353 }
354
355 Ok(sum.sqrt())
356}
357
358#[derive(Clone)]
360pub struct GridAStarPlanner {
361 pub grid: Vec<Vec<bool>>, pub diagonalsallowed: bool,
363}
364
365impl GridAStarPlanner {
366 pub fn new(grid: Vec<Vec<bool>>, diagonalsallowed: bool) -> Self {
373 GridAStarPlanner {
374 grid,
375 diagonalsallowed,
376 }
377 }
378
379 pub fn height(&self) -> usize {
381 self.grid.len()
382 }
383
384 pub fn width(&self) -> usize {
386 if self.grid.is_empty() {
387 0
388 } else {
389 self.grid[0].len()
390 }
391 }
392
393 pub fn is_valid(&self, pos: &[i32; 2]) -> bool {
395 let (rows, cols) = (self.height() as i32, self.width() as i32);
396
397 if pos[0] < 0 || pos[0] >= rows || pos[1] < 0 || pos[1] >= cols {
398 return false;
399 }
400
401 !self.grid[pos[0] as usize][pos[1] as usize]
402 }
403
404 fn get_neighbors(&self, pos: &[i32; 2]) -> Vec<([i32; 2], f64)> {
406 let mut neighbors = Vec::new();
407 let directions = if self.diagonalsallowed {
408 vec![
410 [-1, 0],
411 [1, 0],
412 [0, -1],
413 [0, 1], [-1, -1],
415 [-1, 1],
416 [1, -1],
417 [1, 1], ]
419 } else {
420 vec![[-1, 0], [1, 0], [0, -1], [0, 1]]
422 };
423
424 for dir in directions {
425 let neighbor = [pos[0] + dir[0], pos[1] + dir[1]];
426 if self.is_valid(&neighbor) {
427 let cost = if dir[0] != 0 && dir[1] != 0 {
429 std::f64::consts::SQRT_2
430 } else {
431 1.0
432 };
433 neighbors.push((neighbor, cost));
434 }
435 }
436
437 neighbors
438 }
439
440 pub fn find_path(
442 &self,
443 start: [i32; 2],
444 goal: [i32; 2],
445 ) -> SpatialResult<Option<Path<[i32; 2]>>> {
446 if !self.is_valid(&start) {
448 return Err(SpatialError::ValueError(
449 "Start position is invalid or an obstacle".to_string(),
450 ));
451 }
452 if !self.is_valid(&goal) {
453 return Err(SpatialError::ValueError(
454 "Goal position is invalid or an obstacle".to_string(),
455 ));
456 }
457
458 let planner = AStarPlanner::new();
459 let grid_clone = self.clone();
460 let neighbors_fn = move |pos: &[i32; 2]| grid_clone.get_neighbors(pos);
461 let heuristic_fn = |a: &[i32; 2], b: &[i32; 2]| manhattan_distance(a, b);
462
463 planner.search(start, goal, &neighbors_fn, &heuristic_fn)
464 }
465}
466
467#[derive(Clone)]
469pub struct ContinuousAStarPlanner {
470 pub obstacles: Vec<Vec<[f64; 2]>>,
472 pub step_size: f64,
474 pub collisionthreshold: f64,
476}
477
478impl ContinuousAStarPlanner {
479 pub fn new(obstacles: Vec<Vec<[f64; 2]>>, step_size: f64, collisionthreshold: f64) -> Self {
481 ContinuousAStarPlanner {
482 obstacles,
483 step_size,
484 collisionthreshold,
485 }
486 }
487
488 pub fn is_in_collision(&self, point: &[f64; 2]) -> bool {
490 for obstacle in &self.obstacles {
491 if Self::point_in_polygon(point, obstacle) {
492 return true;
493 }
494 }
495 false
496 }
497
498 pub fn line_in_collision(&self, start: &[f64; 2], end: &[f64; 2]) -> bool {
500 let dx = end[0] - start[0];
502 let dy = end[1] - start[1];
503 let distance = (dx * dx + dy * dy).sqrt();
504 let steps = (distance / self.step_size).ceil() as usize;
505
506 if steps == 0 {
507 return self.is_in_collision(start) || self.is_in_collision(end);
508 }
509
510 for i in 0..=steps {
511 let t = i as f64 / steps as f64;
512 let x = start[0] + dx * t;
513 let y = start[1] + dy * t;
514 if self.is_in_collision(&[x, y]) {
515 return true;
516 }
517 }
518
519 false
520 }
521
522 fn point_in_polygon(point: &[f64; 2], polygon: &[[f64; 2]]) -> bool {
524 if polygon.len() < 3 {
525 return false;
526 }
527
528 let mut inside = false;
529 let mut j = polygon.len() - 1;
530
531 for i in 0..polygon.len() {
532 let xi = polygon[i][0];
533 let yi = polygon[i][1];
534 let xj = polygon[j][0];
535 let yj = polygon[j][1];
536
537 let intersect = ((yi > point[1]) != (yj > point[1]))
538 && (point[0] < (xj - xi) * (point[1] - yi) / (yj - yi) + xi);
539
540 if intersect {
541 inside = !inside;
542 }
543
544 j = i;
545 }
546
547 inside
548 }
549
550 fn get_neighbors(&self, pos: &[f64; 2], radius: f64) -> Vec<([f64; 2], f64)> {
552 let mut neighbors = Vec::new();
553
554 let num_samples = 8; for i in 0..num_samples {
558 let angle = 2.0 * std::f64::consts::PI * (i as f64) / (num_samples as f64);
559 let nx = pos[0] + radius * angle.cos();
560 let ny = pos[1] + radius * angle.sin();
561 let neighbor = [nx, ny];
562
563 if !self.line_in_collision(pos, &neighbor) {
565 let cost = radius; neighbors.push((neighbor, cost));
567 }
568 }
569
570 neighbors
571 }
572
573 pub fn find_path(
575 &self,
576 start: [f64; 2],
577 goal: [f64; 2],
578 neighbor_radius: f64,
579 ) -> SpatialResult<Option<Path<[f64; 2]>>> {
580 #[derive(Clone, Hash, PartialEq, Eq)]
582 struct Point2D {
583 x: i64,
584 y: i64,
585 }
586
587 let precision = 1000.0; let to_point = |p: [f64; 2]| -> Point2D {
590 Point2D {
591 x: (p[0] * precision).round() as i64,
592 y: (p[1] * precision).round() as i64,
593 }
594 };
595
596 let start_point = to_point(start);
597 let goal_point = to_point(goal);
598
599 if self.is_in_collision(&start) {
601 return Err(SpatialError::ValueError(
602 "Start position is in collision with an obstacle".to_string(),
603 ));
604 }
605 if self.is_in_collision(&goal) {
606 return Err(SpatialError::ValueError(
607 "Goal position is in collision with an obstacle".to_string(),
608 ));
609 }
610
611 if !self.line_in_collision(&start, &goal) {
613 let path = vec![start, goal];
614 let cost = euclidean_distance_2d(&start, &goal);
615 return Ok(Some(Path::new(path, cost)));
616 }
617
618 let planner = AStarPlanner::new();
619 let radius = neighbor_radius;
620 let planner_clone = self.clone();
621
622 let neighbors_fn = move |pos: &Point2D| {
624 let float_pos = [pos.x as f64 / precision, pos.y as f64 / precision];
625 planner_clone
626 .get_neighbors(&float_pos, radius)
627 .into_iter()
628 .map(|(neighbor, cost)| (to_point(neighbor), cost))
629 .collect()
630 };
631
632 let heuristic_fn = |a: &Point2D, b: &Point2D| {
633 let a_float = [a.x as f64 / precision, a.y as f64 / precision];
634 let b_float = [b.x as f64 / precision, b.y as f64 / precision];
635 euclidean_distance_2d(&a_float, &b_float)
636 };
637
638 let result = planner.search(start_point, goal_point, &neighbors_fn, &heuristic_fn)?;
640
641 if let Some(path) = result {
643 let float_path = path
644 .nodes
645 .into_iter()
646 .map(|p| [p.x as f64 / precision, p.y as f64 / precision])
647 .collect();
648 Ok(Some(Path::new(float_path, path.cost)))
649 } else {
650 Ok(None)
651 }
652 }
653}
654
655#[cfg(test)]
656mod tests {
657 use super::*;
658
659 #[test]
660 fn test_astar_grid_no_obstacles() {
661 let grid = vec![
663 vec![false, false, false, false, false],
664 vec![false, false, false, false, false],
665 vec![false, false, false, false, false],
666 vec![false, false, false, false, false],
667 vec![false, false, false, false, false],
668 ];
669
670 let planner = GridAStarPlanner::new(grid, false);
671 let start = [0, 0];
672 let goal = [4, 4];
673
674 let path = planner
675 .find_path(start, goal)
676 .expect("Operation failed")
677 .expect("Operation failed");
678
679 assert!(!path.is_empty());
681
682 assert_eq!(path.nodes.first().expect("Operation failed"), &start);
684 assert_eq!(path.nodes.last().expect("Operation failed"), &goal);
685
686 assert_eq!(path.len(), 9);
688
689 }
693
694 #[test]
695 fn test_astar_grid_with_obstacles() {
696 let grid = vec![
698 vec![false, false, false, false, false],
699 vec![false, false, false, false, false],
700 vec![false, true, true, true, false],
701 vec![false, false, false, false, false],
702 vec![false, false, false, false, false],
703 ];
704
705 let planner = GridAStarPlanner::new(grid, false);
706 let start = [1, 1];
707 let goal = [4, 3];
708
709 let path = planner
710 .find_path(start, goal)
711 .expect("Operation failed")
712 .expect("Operation failed");
713
714 assert!(!path.is_empty());
716
717 assert_eq!(path.nodes.first().expect("Operation failed"), &start);
719 assert_eq!(path.nodes.last().expect("Operation failed"), &goal);
720
721 for node in &path.nodes {
724 assert!(!planner.grid[node[0] as usize][node[1] as usize]);
725 }
726 }
727
728 #[test]
729 fn test_astar_grid_no_path() {
730 let grid = vec![
732 vec![false, false, false, false, false],
733 vec![false, false, false, false, false],
734 vec![true, true, true, true, true],
735 vec![false, false, false, false, false],
736 vec![false, false, false, false, false],
737 ];
738
739 let planner = GridAStarPlanner::new(grid, false);
740 let start = [1, 1];
741 let goal = [4, 1];
742
743 let path = planner.find_path(start, goal).expect("Operation failed");
744
745 assert!(path.is_none());
747 }
748
749 #[test]
750 fn test_astar_grid_with_diagonals() {
751 let grid = vec![
753 vec![false, false, false, false, false],
754 vec![false, false, false, false, false],
755 vec![false, false, false, false, false],
756 vec![false, false, false, false, false],
757 vec![false, false, false, false, false],
758 ];
759
760 let planner = GridAStarPlanner::new(grid, true);
761 let start = [0, 0];
762 let goal = [4, 4];
763
764 let path = planner
765 .find_path(start, goal)
766 .expect("Operation failed")
767 .expect("Operation failed");
768
769 assert!(!path.is_empty());
771
772 assert_eq!(path.nodes.first().expect("Operation failed"), &start);
774 assert_eq!(path.nodes.last().expect("Operation failed"), &goal);
775
776 assert_eq!(path.len(), 5);
778
779 }
783}