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 cost = node.g;
313 let mut current = Some(node);
314
315 while let Some(_node) = current {
316 path.push(_node.state.clone());
317 current = _node.parent.clone();
318 }
319
320 path.reverse();
322
323 Path::new(path, cost)
324 }
325}
326
327#[allow(dead_code)]
331pub fn manhattan_distance(a: &[i32; 2], b: &[i32; 2]) -> f64 {
332 ((a[0] - b[0]).abs() + (a[1] - b[1]).abs()) as f64
333}
334
335#[allow(dead_code)]
337pub fn euclidean_distance_2d(a: &[f64; 2], b: &[f64; 2]) -> f64 {
338 let dx = a[0] - b[0];
339 let dy = a[1] - b[1];
340 (dx * dx + dy * dy).sqrt()
341}
342
343#[allow(dead_code)]
345pub fn euclidean_distance(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SpatialResult<f64> {
346 if a.len() != b.len() {
347 return Err(SpatialError::DimensionError(format!(
348 "Mismatched dimensions: {} and {}",
349 a.len(),
350 b.len()
351 )));
352 }
353
354 let mut sum = 0.0;
355 for i in 0..a.len() {
356 let diff = a[i] - b[i];
357 sum += diff * diff;
358 }
359
360 Ok(sum.sqrt())
361}
362
363#[derive(Clone)]
365pub struct GridAStarPlanner {
366 pub grid: Vec<Vec<bool>>, pub diagonalsallowed: bool,
368}
369
370impl GridAStarPlanner {
371 pub fn new(grid: Vec<Vec<bool>>, diagonalsallowed: bool) -> Self {
378 GridAStarPlanner {
379 grid,
380 diagonalsallowed,
381 }
382 }
383
384 pub fn height(&self) -> usize {
386 self.grid.len()
387 }
388
389 pub fn width(&self) -> usize {
391 if self.grid.is_empty() {
392 0
393 } else {
394 self.grid[0].len()
395 }
396 }
397
398 pub fn is_valid(&self, pos: &[i32; 2]) -> bool {
400 let (rows, cols) = (self.height() as i32, self.width() as i32);
401
402 if pos[0] < 0 || pos[0] >= rows || pos[1] < 0 || pos[1] >= cols {
403 return false;
404 }
405
406 !self.grid[pos[0] as usize][pos[1] as usize]
407 }
408
409 fn get_neighbors(&self, pos: &[i32; 2]) -> Vec<([i32; 2], f64)> {
411 let mut neighbors = Vec::new();
412 let directions = if self.diagonalsallowed {
413 vec![
415 [-1, 0],
416 [1, 0],
417 [0, -1],
418 [0, 1], [-1, -1],
420 [-1, 1],
421 [1, -1],
422 [1, 1], ]
424 } else {
425 vec![[-1, 0], [1, 0], [0, -1], [0, 1]]
427 };
428
429 for dir in directions {
430 let neighbor = [pos[0] + dir[0], pos[1] + dir[1]];
431 if self.is_valid(&neighbor) {
432 let cost = if dir[0] != 0 && dir[1] != 0 {
434 std::f64::consts::SQRT_2
435 } else {
436 1.0
437 };
438 neighbors.push((neighbor, cost));
439 }
440 }
441
442 neighbors
443 }
444
445 pub fn find_path(
447 &self,
448 start: [i32; 2],
449 goal: [i32; 2],
450 ) -> SpatialResult<Option<Path<[i32; 2]>>> {
451 if !self.is_valid(&start) {
453 return Err(SpatialError::ValueError(
454 "Start position is invalid or an obstacle".to_string(),
455 ));
456 }
457 if !self.is_valid(&goal) {
458 return Err(SpatialError::ValueError(
459 "Goal position is invalid or an obstacle".to_string(),
460 ));
461 }
462
463 let planner = AStarPlanner::new();
464 let grid_clone = self.clone();
465 let neighbors_fn = move |pos: &[i32; 2]| grid_clone.get_neighbors(pos);
466 let heuristic_fn = |a: &[i32; 2], b: &[i32; 2]| manhattan_distance(a, b);
467
468 planner.search(start, goal, &neighbors_fn, &heuristic_fn)
469 }
470}
471
472#[derive(Clone)]
474pub struct ContinuousAStarPlanner {
475 pub obstacles: Vec<Vec<[f64; 2]>>,
477 pub step_size: f64,
479 pub collisionthreshold: f64,
481}
482
483impl ContinuousAStarPlanner {
484 pub fn new(obstacles: Vec<Vec<[f64; 2]>>, step_size: f64, collisionthreshold: f64) -> Self {
486 ContinuousAStarPlanner {
487 obstacles,
488 step_size,
489 collisionthreshold,
490 }
491 }
492
493 pub fn is_in_collision(&self, point: &[f64; 2]) -> bool {
495 for obstacle in &self.obstacles {
496 if Self::point_in_polygon(point, obstacle) {
497 return true;
498 }
499 }
500 false
501 }
502
503 pub fn line_in_collision(&self, start: &[f64; 2], end: &[f64; 2]) -> bool {
505 let dx = end[0] - start[0];
507 let dy = end[1] - start[1];
508 let distance = (dx * dx + dy * dy).sqrt();
509 let steps = (distance / self.step_size).ceil() as usize;
510
511 if steps == 0 {
512 return self.is_in_collision(start) || self.is_in_collision(end);
513 }
514
515 for i in 0..=steps {
516 let t = i as f64 / steps as f64;
517 let x = start[0] + dx * t;
518 let y = start[1] + dy * t;
519 if self.is_in_collision(&[x, y]) {
520 return true;
521 }
522 }
523
524 false
525 }
526
527 fn point_in_polygon(point: &[f64; 2], polygon: &[[f64; 2]]) -> bool {
529 if polygon.len() < 3 {
530 return false;
531 }
532
533 let mut inside = false;
534 let mut j = polygon.len() - 1;
535
536 for i in 0..polygon.len() {
537 let xi = polygon[i][0];
538 let yi = polygon[i][1];
539 let xj = polygon[j][0];
540 let yj = polygon[j][1];
541
542 let intersect = ((yi > point[1]) != (yj > point[1]))
543 && (point[0] < (xj - xi) * (point[1] - yi) / (yj - yi) + xi);
544
545 if intersect {
546 inside = !inside;
547 }
548
549 j = i;
550 }
551
552 inside
553 }
554
555 fn get_neighbors(&self, pos: &[f64; 2], radius: f64) -> Vec<([f64; 2], f64)> {
557 let mut neighbors = Vec::new();
558
559 let num_samples = 8; for i in 0..num_samples {
563 let angle = 2.0 * std::f64::consts::PI * (i as f64) / (num_samples as f64);
564 let nx = pos[0] + radius * angle.cos();
565 let ny = pos[1] + radius * angle.sin();
566 let neighbor = [nx, ny];
567
568 if !self.line_in_collision(pos, &neighbor) {
570 let cost = radius; neighbors.push((neighbor, cost));
572 }
573 }
574
575 neighbors
576 }
577
578 pub fn find_path(
580 &self,
581 start: [f64; 2],
582 goal: [f64; 2],
583 neighbor_radius: f64,
584 ) -> SpatialResult<Option<Path<[f64; 2]>>> {
585 #[derive(Clone, Hash, PartialEq, Eq)]
587 struct Point2D {
588 x: i64,
589 y: i64,
590 }
591
592 let precision = 1000.0; let to_point = |p: [f64; 2]| -> Point2D {
595 Point2D {
596 x: (p[0] * precision).round() as i64,
597 y: (p[1] * precision).round() as i64,
598 }
599 };
600
601 let start_point = to_point(start);
602 let goal_point = to_point(goal);
603
604 if self.is_in_collision(&start) {
606 return Err(SpatialError::ValueError(
607 "Start position is in collision with an obstacle".to_string(),
608 ));
609 }
610 if self.is_in_collision(&goal) {
611 return Err(SpatialError::ValueError(
612 "Goal position is in collision with an obstacle".to_string(),
613 ));
614 }
615
616 if !self.line_in_collision(&start, &goal) {
618 let path = vec![start, goal];
619 let cost = euclidean_distance_2d(&start, &goal);
620 return Ok(Some(Path::new(path, cost)));
621 }
622
623 let planner = AStarPlanner::new();
624 let radius = neighbor_radius;
625 let planner_clone = self.clone();
626
627 let neighbors_fn = move |pos: &Point2D| {
629 let float_pos = [pos.x as f64 / precision, pos.y as f64 / precision];
630 planner_clone
631 .get_neighbors(&float_pos, radius)
632 .into_iter()
633 .map(|(neighbor, cost)| (to_point(neighbor), cost))
634 .collect()
635 };
636
637 let heuristic_fn = |a: &Point2D, b: &Point2D| {
638 let a_float = [a.x as f64 / precision, a.y as f64 / precision];
639 let b_float = [b.x as f64 / precision, b.y as f64 / precision];
640 euclidean_distance_2d(&a_float, &b_float)
641 };
642
643 let result = planner.search(start_point, goal_point, &neighbors_fn, &heuristic_fn)?;
645
646 if let Some(path) = result {
648 let float_path = path
649 .nodes
650 .into_iter()
651 .map(|p| [p.x as f64 / precision, p.y as f64 / precision])
652 .collect();
653 Ok(Some(Path::new(float_path, path.cost)))
654 } else {
655 Ok(None)
656 }
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663
664 #[test]
665 fn test_astar_grid_no_obstacles() {
666 let grid = vec![
668 vec![false, false, false, false, false],
669 vec![false, false, false, false, false],
670 vec![false, false, false, false, false],
671 vec![false, false, false, false, false],
672 vec![false, false, false, false, false],
673 ];
674
675 let planner = GridAStarPlanner::new(grid, false);
676 let start = [0, 0];
677 let goal = [4, 4];
678
679 let path = planner
680 .find_path(start, goal)
681 .expect("Operation failed")
682 .expect("Operation failed");
683
684 assert!(!path.is_empty());
686
687 assert_eq!(path.nodes.first().expect("Operation failed"), &start);
689 assert_eq!(path.nodes.last().expect("Operation failed"), &goal);
690
691 assert_eq!(path.len(), 9);
693
694 assert_eq!(path.cost, 8.0);
695 }
696
697 #[test]
698 fn test_astar_grid_with_obstacles() {
699 let grid = vec![
701 vec![false, false, false, false, false],
702 vec![false, false, false, false, false],
703 vec![false, true, true, true, false],
704 vec![false, false, false, false, false],
705 vec![false, false, false, false, false],
706 ];
707
708 let planner = GridAStarPlanner::new(grid, false);
709 let start = [1, 1];
710 let goal = [4, 3];
711
712 let path = planner
713 .find_path(start, goal)
714 .expect("Operation failed")
715 .expect("Operation failed");
716
717 assert!(!path.is_empty());
719
720 assert_eq!(path.nodes.first().expect("Operation failed"), &start);
722 assert_eq!(path.nodes.last().expect("Operation failed"), &goal);
723
724 for node in &path.nodes {
727 assert!(!planner.grid[node[0] as usize][node[1] as usize]);
728 }
729 }
730
731 #[test]
732 fn test_astar_grid_no_path() {
733 let grid = vec![
735 vec![false, false, false, false, false],
736 vec![false, false, false, false, false],
737 vec![true, true, true, true, true],
738 vec![false, false, false, false, false],
739 vec![false, false, false, false, false],
740 ];
741
742 let planner = GridAStarPlanner::new(grid, false);
743 let start = [1, 1];
744 let goal = [4, 1];
745
746 let path = planner.find_path(start, goal).expect("Operation failed");
747
748 assert!(path.is_none());
750 }
751
752 #[test]
753 fn test_astar_grid_with_diagonals() {
754 let grid = vec![
756 vec![false, false, false, false, false],
757 vec![false, false, false, false, false],
758 vec![false, false, false, false, false],
759 vec![false, false, false, false, false],
760 vec![false, false, false, false, false],
761 ];
762
763 let planner = GridAStarPlanner::new(grid, true);
764 let start = [0, 0];
765 let goal = [4, 4];
766
767 let path = planner
768 .find_path(start, goal)
769 .expect("Operation failed")
770 .expect("Operation failed");
771
772 assert!(!path.is_empty());
774
775 assert_eq!(path.nodes.first().expect("Operation failed"), &start);
777 assert_eq!(path.nodes.last().expect("Operation failed"), &goal);
778
779 assert_eq!(path.len(), 5);
781
782 assert!((path.cost - 4.0 * std::f64::consts::SQRT_2).abs() < 1e-6);
783 }
784}