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 && tentative_g >= *g_values.get(&neighbor_state).unwrap() {
277 continue;
278 }
279
280 g_values.insert(neighbor_state.clone(), tentative_g);
282
283 let h = self.weight * heuristic_fn(&neighbor_state, &goal);
285 let neighbor_node = Rc::new(Node::new(
286 neighbor_state,
287 Some(Rc::clone(¤t)),
288 tentative_g,
289 h,
290 ));
291
292 open_set.push(neighbor_node);
294 }
295 }
296
297 Ok(None)
299 }
300
301 fn reconstruct_path<N: Clone + Eq + Hash>(goal: N, node: Rc<Node<N>>) -> Path<N> {
303 let mut path = Vec::new();
304 let mut current = Some(node);
305 let mut cost = 0.0;
306
307 while let Some(_node) = current {
308 path.push(_node.state.clone());
309 cost = _node.g;
310 current = _node.parent.clone();
311 }
312
313 path.reverse();
315
316 Path::new(path, cost)
317 }
318}
319
320#[allow(dead_code)]
324pub fn manhattan_distance(a: &[i32; 2], b: &[i32; 2]) -> f64 {
325 ((a[0] - b[0]).abs() + (a[1] - b[1]).abs()) as f64
326}
327
328#[allow(dead_code)]
330pub fn euclidean_distance_2d(a: &[f64; 2], b: &[f64; 2]) -> f64 {
331 let dx = a[0] - b[0];
332 let dy = a[1] - b[1];
333 (dx * dx + dy * dy).sqrt()
334}
335
336#[allow(dead_code)]
338pub fn euclidean_distance(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SpatialResult<f64> {
339 if a.len() != b.len() {
340 return Err(SpatialError::DimensionError(format!(
341 "Mismatched dimensions: {} and {}",
342 a.len(),
343 b.len()
344 )));
345 }
346
347 let mut sum = 0.0;
348 for i in 0..a.len() {
349 let diff = a[i] - b[i];
350 sum += diff * diff;
351 }
352
353 Ok(sum.sqrt())
354}
355
356#[derive(Clone)]
358pub struct GridAStarPlanner {
359 pub grid: Vec<Vec<bool>>, pub diagonalsallowed: bool,
361}
362
363impl GridAStarPlanner {
364 pub fn new(grid: Vec<Vec<bool>>, diagonalsallowed: bool) -> Self {
371 GridAStarPlanner {
372 grid,
373 diagonalsallowed,
374 }
375 }
376
377 pub fn height(&self) -> usize {
379 self.grid.len()
380 }
381
382 pub fn width(&self) -> usize {
384 if self.grid.is_empty() {
385 0
386 } else {
387 self.grid[0].len()
388 }
389 }
390
391 pub fn is_valid(&self, pos: &[i32; 2]) -> bool {
393 let (rows, cols) = (self.height() as i32, self.width() as i32);
394
395 if pos[0] < 0 || pos[0] >= rows || pos[1] < 0 || pos[1] >= cols {
396 return false;
397 }
398
399 !self.grid[pos[0] as usize][pos[1] as usize]
400 }
401
402 fn get_neighbors(&self, pos: &[i32; 2]) -> Vec<([i32; 2], f64)> {
404 let mut neighbors = Vec::new();
405 let directions = if self.diagonalsallowed {
406 vec![
408 [-1, 0],
409 [1, 0],
410 [0, -1],
411 [0, 1], [-1, -1],
413 [-1, 1],
414 [1, -1],
415 [1, 1], ]
417 } else {
418 vec![[-1, 0], [1, 0], [0, -1], [0, 1]]
420 };
421
422 for dir in directions {
423 let neighbor = [pos[0] + dir[0], pos[1] + dir[1]];
424 if self.is_valid(&neighbor) {
425 let cost = if dir[0] != 0 && dir[1] != 0 {
427 std::f64::consts::SQRT_2
428 } else {
429 1.0
430 };
431 neighbors.push((neighbor, cost));
432 }
433 }
434
435 neighbors
436 }
437
438 pub fn find_path(
440 &self,
441 start: [i32; 2],
442 goal: [i32; 2],
443 ) -> SpatialResult<Option<Path<[i32; 2]>>> {
444 if !self.is_valid(&start) {
446 return Err(SpatialError::ValueError(
447 "Start position is invalid or an obstacle".to_string(),
448 ));
449 }
450 if !self.is_valid(&goal) {
451 return Err(SpatialError::ValueError(
452 "Goal position is invalid or an obstacle".to_string(),
453 ));
454 }
455
456 let planner = AStarPlanner::new();
457 let grid_clone = self.clone();
458 let neighbors_fn = move |pos: &[i32; 2]| grid_clone.get_neighbors(pos);
459 let heuristic_fn = |a: &[i32; 2], b: &[i32; 2]| manhattan_distance(a, b);
460
461 planner.search(start, goal, &neighbors_fn, &heuristic_fn)
462 }
463}
464
465#[derive(Clone)]
467pub struct ContinuousAStarPlanner {
468 pub obstacles: Vec<Vec<[f64; 2]>>,
470 pub step_size: f64,
472 pub collisionthreshold: f64,
474}
475
476impl ContinuousAStarPlanner {
477 pub fn new(obstacles: Vec<Vec<[f64; 2]>>, step_size: f64, collisionthreshold: f64) -> Self {
479 ContinuousAStarPlanner {
480 obstacles,
481 step_size,
482 collisionthreshold,
483 }
484 }
485
486 pub fn is_in_collision(&self, point: &[f64; 2]) -> bool {
488 for obstacle in &self.obstacles {
489 if Self::point_in_polygon(point, obstacle) {
490 return true;
491 }
492 }
493 false
494 }
495
496 pub fn line_in_collision(&self, start: &[f64; 2], end: &[f64; 2]) -> bool {
498 let dx = end[0] - start[0];
500 let dy = end[1] - start[1];
501 let distance = (dx * dx + dy * dy).sqrt();
502 let steps = (distance / self.step_size).ceil() as usize;
503
504 if steps == 0 {
505 return self.is_in_collision(start) || self.is_in_collision(end);
506 }
507
508 for i in 0..=steps {
509 let t = i as f64 / steps as f64;
510 let x = start[0] + dx * t;
511 let y = start[1] + dy * t;
512 if self.is_in_collision(&[x, y]) {
513 return true;
514 }
515 }
516
517 false
518 }
519
520 fn point_in_polygon(point: &[f64; 2], polygon: &[[f64; 2]]) -> bool {
522 if polygon.len() < 3 {
523 return false;
524 }
525
526 let mut inside = false;
527 let mut j = polygon.len() - 1;
528
529 for i in 0..polygon.len() {
530 let xi = polygon[i][0];
531 let yi = polygon[i][1];
532 let xj = polygon[j][0];
533 let yj = polygon[j][1];
534
535 let intersect = ((yi > point[1]) != (yj > point[1]))
536 && (point[0] < (xj - xi) * (point[1] - yi) / (yj - yi) + xi);
537
538 if intersect {
539 inside = !inside;
540 }
541
542 j = i;
543 }
544
545 inside
546 }
547
548 fn get_neighbors(&self, pos: &[f64; 2], radius: f64) -> Vec<([f64; 2], f64)> {
550 let mut neighbors = Vec::new();
551
552 let num_samples = 8; for i in 0..num_samples {
556 let angle = 2.0 * std::f64::consts::PI * (i as f64) / (num_samples as f64);
557 let nx = pos[0] + radius * angle.cos();
558 let ny = pos[1] + radius * angle.sin();
559 let neighbor = [nx, ny];
560
561 if !self.line_in_collision(pos, &neighbor) {
563 let cost = radius; neighbors.push((neighbor, cost));
565 }
566 }
567
568 neighbors
569 }
570
571 pub fn find_path(
573 &self,
574 start: [f64; 2],
575 goal: [f64; 2],
576 neighbor_radius: f64,
577 ) -> SpatialResult<Option<Path<[f64; 2]>>> {
578 #[derive(Clone, Hash, PartialEq, Eq)]
580 struct Point2D {
581 x: i64,
582 y: i64,
583 }
584
585 let precision = 1000.0; let to_point = |p: [f64; 2]| -> Point2D {
588 Point2D {
589 x: (p[0] * precision).round() as i64,
590 y: (p[1] * precision).round() as i64,
591 }
592 };
593
594 let start_point = to_point(start);
595 let goal_point = to_point(goal);
596
597 if self.is_in_collision(&start) {
599 return Err(SpatialError::ValueError(
600 "Start position is in collision with an obstacle".to_string(),
601 ));
602 }
603 if self.is_in_collision(&goal) {
604 return Err(SpatialError::ValueError(
605 "Goal position is in collision with an obstacle".to_string(),
606 ));
607 }
608
609 if !self.line_in_collision(&start, &goal) {
611 let path = vec![start, goal];
612 let cost = euclidean_distance_2d(&start, &goal);
613 return Ok(Some(Path::new(path, cost)));
614 }
615
616 let planner = AStarPlanner::new();
617 let radius = neighbor_radius;
618 let planner_clone = self.clone();
619
620 let neighbors_fn = move |pos: &Point2D| {
622 let float_pos = [pos.x as f64 / precision, pos.y as f64 / precision];
623 planner_clone
624 .get_neighbors(&float_pos, radius)
625 .into_iter()
626 .map(|(neighbor, cost)| (to_point(neighbor), cost))
627 .collect()
628 };
629
630 let heuristic_fn = |a: &Point2D, b: &Point2D| {
631 let a_float = [a.x as f64 / precision, a.y as f64 / precision];
632 let b_float = [b.x as f64 / precision, b.y as f64 / precision];
633 euclidean_distance_2d(&a_float, &b_float)
634 };
635
636 let result = planner.search(start_point, goal_point, &neighbors_fn, &heuristic_fn)?;
638
639 if let Some(path) = result {
641 let float_path = path
642 .nodes
643 .into_iter()
644 .map(|p| [p.x as f64 / precision, p.y as f64 / precision])
645 .collect();
646 Ok(Some(Path::new(float_path, path.cost)))
647 } else {
648 Ok(None)
649 }
650 }
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656
657 #[test]
658 fn test_astar_grid_no_obstacles() {
659 let grid = vec![
661 vec![false, false, false, false, false],
662 vec![false, false, false, false, false],
663 vec![false, false, false, false, false],
664 vec![false, false, false, false, false],
665 vec![false, false, false, false, false],
666 ];
667
668 let planner = GridAStarPlanner::new(grid, false);
669 let start = [0, 0];
670 let goal = [4, 4];
671
672 let path = planner.find_path(start, goal).unwrap().unwrap();
673
674 assert!(!path.is_empty());
676
677 assert_eq!(path.nodes.first().unwrap(), &start);
679 assert_eq!(path.nodes.last().unwrap(), &goal);
680
681 assert_eq!(path.len(), 9);
683
684 }
688
689 #[test]
690 fn test_astar_grid_with_obstacles() {
691 let grid = vec![
693 vec![false, false, false, false, false],
694 vec![false, false, false, false, false],
695 vec![false, true, true, true, false],
696 vec![false, false, false, false, false],
697 vec![false, false, false, false, false],
698 ];
699
700 let planner = GridAStarPlanner::new(grid, false);
701 let start = [1, 1];
702 let goal = [4, 3];
703
704 let path = planner.find_path(start, goal).unwrap().unwrap();
705
706 assert!(!path.is_empty());
708
709 assert_eq!(path.nodes.first().unwrap(), &start);
711 assert_eq!(path.nodes.last().unwrap(), &goal);
712
713 for node in &path.nodes {
716 assert!(!planner.grid[node[0] as usize][node[1] as usize]);
717 }
718 }
719
720 #[test]
721 fn test_astar_grid_no_path() {
722 let grid = vec![
724 vec![false, false, false, false, false],
725 vec![false, false, false, false, false],
726 vec![true, true, true, true, true],
727 vec![false, false, false, false, false],
728 vec![false, false, false, false, false],
729 ];
730
731 let planner = GridAStarPlanner::new(grid, false);
732 let start = [1, 1];
733 let goal = [4, 1];
734
735 let path = planner.find_path(start, goal).unwrap();
736
737 assert!(path.is_none());
739 }
740
741 #[test]
742 fn test_astar_grid_with_diagonals() {
743 let grid = vec![
745 vec![false, false, false, false, false],
746 vec![false, false, false, false, false],
747 vec![false, false, false, false, false],
748 vec![false, false, false, false, false],
749 vec![false, false, false, false, false],
750 ];
751
752 let planner = GridAStarPlanner::new(grid, true);
753 let start = [0, 0];
754 let goal = [4, 4];
755
756 let path = planner.find_path(start, goal).unwrap().unwrap();
757
758 assert!(!path.is_empty());
760
761 assert_eq!(path.nodes.first().unwrap(), &start);
763 assert_eq!(path.nodes.last().unwrap(), &goal);
764
765 assert_eq!(path.len(), 5);
767
768 }
772}