1use std::collections::BinaryHeap;
5
6use std::cmp::Ordering;
7use std::f32::consts::SQRT_2;
8use std::f32::EPSILON;
9use std::ops::Sub;
10
11use ndarray::Array;
12use ndarray::Array2;
13
14use fnv::FnvHashMap;
15#[allow(dead_code)]
18fn absdiff<T>(x: T, y: T) -> T
19where
20 T: Sub<Output = T> + PartialOrd,
21{
22 if x < y {
23 y - x
24 } else {
25 x - y
26 }
27}
28
29fn manhattan_heuristic(source: &Point2d, target: &Point2d) -> f32 {
30 (absdiff(source.x, target.x) + absdiff(source.y, target.y)) as f32
31}
32
33static SQRT_2_MINUS_2: f32 = SQRT_2 - 2.0;
34
35fn octal_heuristic(source: &Point2d, target: &Point2d) -> f32 {
36 let dx = absdiff(source.x, target.x);
37 let dy = absdiff(source.y, target.y);
38 let min = std::cmp::min(dx, dy);
39 dx as f32 + dy as f32 + SQRT_2_MINUS_2 * min as f32
40}
41
42fn euclidean_heuristic(source: &Point2d, target: &Point2d) -> f32 {
43 let x = source.x as i32 - target.x as i32;
44 let xx = x * x;
45 let y = source.y as i32 - target.y as i32;
46 let yy = y * y;
47 let sum = xx + yy;
48 (sum as f32).sqrt()
49}
50
51#[derive(Debug, Copy, Clone, PartialEq)]
57struct Direction {
58 x: i32,
59 y: i32,
60}
61
62impl Direction {
63 fn is_diagonal(self) -> bool {
65 match self {
66 Direction { x: 0, y: 1 }
68 | Direction { x: 1, y: 0 }
69 | Direction { x: -1, y: 0 }
70 | Direction { x: 0, y: -1 } => false,
71 _ => true,
72 }
73 }
74
75 fn left(self) -> Direction {
77 match (self.x, self.y) {
78 (1, 0) => Direction { x: 0, y: 1 },
79 (0, 1) => Direction { x: -1, y: 0 },
80 (-1, 0) => Direction { x: 0, y: -1 },
81 (0, -1) => Direction { x: 1, y: 0 },
82 (1, 1) => Direction { x: -1, y: 1 },
84 (-1, 1) => Direction { x: -1, y: -1 },
85 (-1, -1) => Direction { x: 1, y: -1 },
86 (1, -1) => Direction { x: 1, y: 1 },
87 _ => panic!("This shouldnt happen"),
88 }
89 }
90
91 fn right(self) -> Direction {
93 match (self.x, self.y) {
94 (1, 0) => Direction { x: 0, y: -1 },
95 (0, 1) => Direction { x: 1, y: 0 },
96 (-1, 0) => Direction { x: 0, y: 1 },
97 (0, -1) => Direction { x: -1, y: 0 },
98 (1, 1) => Direction { x: 1, y: -1 },
100 (-1, 1) => Direction { x: 1, y: 1 },
101 (-1, -1) => Direction { x: -1, y: 1 },
102 (1, -1) => Direction { x: -1, y: -1 },
103 _ => panic!("This shouldnt happen"),
104 }
105 }
106
107 fn half_left(self) -> Direction {
109 match (self.x, self.y) {
110 (1, 0) => Direction { x: 1, y: 1 },
111 (0, 1) => Direction { x: -1, y: 1 },
112 (-1, 0) => Direction { x: -1, y: -1 },
113 (0, -1) => Direction { x: 1, y: -1 },
114 (1, 1) => Direction { x: 0, y: 1 },
116 (-1, 1) => Direction { x: -1, y: 0 },
117 (-1, -1) => Direction { x: 0, y: -1 },
118 (1, -1) => Direction { x: 1, y: 0 },
119 _ => panic!("This shouldnt happen"),
120 }
121 }
122
123 fn half_right(self) -> Direction {
125 match (self.x, self.y) {
126 (1, 0) => Direction { x: 1, y: -1 },
127 (0, 1) => Direction { x: 1, y: 1 },
128 (-1, 0) => Direction { x: -1, y: 1 },
129 (0, -1) => Direction { x: -1, y: -1 },
130 (1, 1) => Direction { x: 1, y: 0 },
132 (-1, 1) => Direction { x: 0, y: 1 },
133 (-1, -1) => Direction { x: -1, y: 0 },
134 (1, -1) => Direction { x: 0, y: -1 },
135 _ => panic!("This shouldnt happen"),
136 }
137 }
138
139 fn left135(self) -> Direction {
141 match (self.x, self.y) {
142 (1, 1) => Direction { x: -1, y: 0 },
144 (-1, 1) => Direction { x: 0, y: -1 },
145 (-1, -1) => Direction { x: 1, y: 0 },
146 (1, -1) => Direction { x: 0, y: 1 },
147 _ => panic!("This shouldnt happen"),
148 }
149 }
150
151 fn right135(self) -> Direction {
153 match (self.x, self.y) {
154 (1, 1) => Direction { x: 0, y: -1 },
156 (-1, 1) => Direction { x: 1, y: 0 },
157 (-1, -1) => Direction { x: 0, y: 1 },
158 (1, -1) => Direction { x: -1, y: 0 },
159 _ => panic!("This shouldnt happen"),
160 }
161 }
162}
163
164#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
166pub struct Point2d {
167 x: usize,
168 y: usize,
169}
170
171impl Point2d {
172 pub fn new(x: usize, y: usize) -> Self {
173 Point2d { x, y }
174 }
175
176 pub fn unpack(&self) -> (usize, usize) {
177 (self.x, self.y)
178 }
179
180 fn add_direction(&self, other: Direction) -> Point2d {
182 Point2d {
183 x: (self.x as i32 + other.x) as usize,
184 y: (self.y as i32 + other.y) as usize,
185 }
186 }
187
188 fn get_direction(&self, target: &Point2d) -> Direction {
190 let x: i32;
191 let y: i32;
192 match self.x.cmp(&target.x) {
193 Ordering::Greater => x = -1,
194 Ordering::Less => x = 1,
195 Ordering::Equal => x = 0,
196 }
197 match self.y.cmp(&target.y) {
198 Ordering::Greater => y = -1,
199 Ordering::Less => y = 1,
200 Ordering::Equal => y = 0,
201 }
202 Direction { x, y }
203 }
204}
205
206#[derive(Debug)]
208struct JumpPoint {
209 start: Point2d,
210 direction: Direction,
211 cost_to_start: f32,
212 total_cost_estimate: f32,
213}
214
215impl PartialEq for JumpPoint {
217 fn eq(&self, other: &Self) -> bool {
218 absdiff(self.total_cost_estimate, other.total_cost_estimate) < EPSILON
219 }
220}
221
222impl PartialOrd for JumpPoint {
224 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
225 other
226 .total_cost_estimate
227 .partial_cmp(&self.total_cost_estimate)
228 }
229}
230
231impl Ord for JumpPoint {
233 fn cmp(&self, other: &Self) -> Ordering {
234 other
235 .total_cost_estimate
236 .partial_cmp(&self.total_cost_estimate)
237 .unwrap()
238 }
239}
240
241impl Eq for JumpPoint {}
243
244pub struct PathFinder {
246 grid: Array2<u8>,
254 heuristic: String,
256 jump_points: BinaryHeap<JumpPoint>,
257 came_from: FnvHashMap<Point2d, Point2d>,
259}
260
261impl PathFinder {
262 fn add_came_from(&mut self, p1: &Point2d, p2: &Point2d) -> bool {
264 if !self.came_from.contains_key(p1) {
265 self.came_from.insert(*p1, *p2);
266 return false;
267 }
268 true
269 }
270
271 fn is_in_grid_bounds(&self, point: &Point2d) -> bool {
273 let dim = self.grid.raw_dim();
274 let (height, width) = (dim[0], dim[1]);
275 return point.x < width && point.y < height;
277 }
278
279 fn is_in_grid(&self, point: &Point2d) -> bool {
281 self.grid[[point.y, point.x]] == 1
282 }
283
284 fn new_point_in_grid(&self, point: &Point2d, direction: Direction) -> Option<Point2d> {
286 let new_point = point.add_direction(direction);
287 if self.is_in_grid(&new_point) {
288 return Some(new_point);
289 }
290 None
291 }
292
293 fn goal_reached(&self, target: &Point2d) -> bool {
295 self.came_from.contains_key(&target)
296 }
297
298 fn construct_path(
319 &self,
320 source: &Point2d,
321 target: &Point2d,
322 construct_full_path: bool,
323 ) -> Vec<Point2d> {
324 if construct_full_path {
325 let mut path: Vec<Point2d> = Vec::with_capacity(100);
326 let mut pos = *target;
327 path.push(pos);
328 while &pos != source {
329 let temp_target = *self.came_from.get(&pos).unwrap();
330 let dir = pos.get_direction(&temp_target);
331 let mut temp_pos = pos.add_direction(dir);
332 while temp_pos != temp_target {
333 path.push(temp_pos);
334 temp_pos = temp_pos.add_direction(dir);
335 }
336 pos = temp_target;
337 }
338 path.push(*source);
339 path.reverse();
340 path
341 } else {
342 let mut path: Vec<Point2d> = Vec::with_capacity(20);
343 path.push(*target);
344 let mut pos = self.came_from.get(target).unwrap();
345 while pos != source {
346 pos = self.came_from.get(&pos).unwrap();
347 path.push(*pos);
348 }
349 path.reverse();
350 path
351 }
352 }
353
354 pub fn find_path(&mut self, source: &Point2d, target: &Point2d) -> Vec<Point2d> {
356 if !self.is_in_grid_bounds(source) {
357 println!(
358 "Returning early, source position is not within grid bounds: {:?}",
359 source
360 );
361 return vec![];
362 }
363
364 if !self.is_in_grid_bounds(target) {
365 println!(
366 "Returning early, target position is not within grid bounds: {:?}",
367 target
368 );
369 return vec![];
370 }
371
372 if !self.is_in_grid(&source) {
373 println!(
374 "Returning early, source position is at the position of a wall: {:?}",
375 source
376 );
377 return vec![];
378 }
379 if !self.is_in_grid(&target) {
380 println!(
381 "Returning early, target position is at the position of a wall: {:?}",
382 target
383 );
384 return vec![];
385 }
386
387 let heuristic: fn(&Point2d, &Point2d) -> f32;
388 match self.heuristic.as_ref() {
389 "manhattan" => heuristic = manhattan_heuristic,
390 "octal" => heuristic = octal_heuristic,
391 "euclidean" => heuristic = euclidean_heuristic,
392 _ => heuristic = octal_heuristic,
395 }
396
397 self.jump_points.clear();
399 self.came_from.clear();
401
402 for dir in [
404 Direction { x: 1, y: 1 },
405 Direction { x: -1, y: 1 },
406 Direction { x: -1, y: -1 },
407 Direction { x: 1, y: -1 },
408 ]
409 .iter()
410 {
411 self.jump_points.push(JumpPoint {
412 start: *source,
413 direction: *dir,
414 cost_to_start: 0.0,
415 total_cost_estimate: 0.0 + heuristic(&source, target),
416 });
417 }
418
419 while let Some(JumpPoint {
420 start,
421 direction,
422 cost_to_start,
423 ..
424 }) = self.jump_points.pop()
425 {
426 if self.goal_reached(&target) {
427 return self.construct_path(source, target, false);
428 }
429
430 self.traverse(&start, direction, &target, cost_to_start, heuristic);
431 }
432
433 vec![]
434 }
435
436 fn traverse(
440 &mut self,
441 start: &Point2d,
442 direction: Direction,
443 target: &Point2d,
444 cost_to_start: f32,
445 heuristic: fn(&Point2d, &Point2d) -> f32,
446 ) {
447 let mut traversed_count: u32 = 0;
449 let add_nodes: Vec<(Direction, Direction)> = if direction.is_diagonal() {
450 let (half_left, half_right) = (direction.half_left(), direction.half_right());
454 vec![
455 (direction.left135(), direction.left()),
456 (direction.right135(), direction.right()),
457 (half_left, half_left),
458 (half_right, half_right),
459 ]
460 } else {
461 vec![
462 (direction.left(), direction.half_left()),
463 (direction.right(), direction.half_right()),
464 ]
465 };
466 let mut current_point = *start;
467 let (mut left_blocked, mut right_blocked) = (false, false);
469 loop {
470 if current_point == *target {
472 self.add_came_from(¤t_point, &start);
473 return;
477 }
478 for (index, (check_dir, traversal_dir)) in add_nodes.iter().enumerate() {
482 let check_point_is_in_grid =
484 self.is_in_grid(¤t_point.add_direction(*check_dir));
485
486 if (index == 0 && left_blocked || index == 1 && right_blocked || index > 1)
487 && traversed_count != 0
488 && check_point_is_in_grid
489 {
490 let new_cost_to_start = if traversal_dir.is_diagonal() {
492 cost_to_start + SQRT_2 * traversed_count as f32
493 } else {
494 cost_to_start + traversed_count as f32
495 };
496
497 if index < 2 {
498 if self.add_came_from(¤t_point, &start) {
499 break;
501 }
502 self.jump_points.push(JumpPoint {
504 start: current_point,
505 direction: *traversal_dir,
506 cost_to_start: new_cost_to_start,
507 total_cost_estimate: new_cost_to_start
508 + heuristic(¤t_point, target),
509 });
510
511 if index == 0 {
513 left_blocked = false;
514 } else {
515 right_blocked = false;
516 }
517 } else {
519 self.traverse(
521 ¤t_point,
522 *traversal_dir,
523 target,
524 new_cost_to_start,
525 heuristic,
526 );
527 self.add_came_from(¤t_point, &start);
529 }
530 } else if index == 0 && !check_point_is_in_grid {
531 left_blocked = true;
533 } else if index == 1 && !check_point_is_in_grid {
534 right_blocked = true
536 }
537 }
538
539 current_point = current_point.add_direction(direction);
540 if !self.is_in_grid(¤t_point) {
541 break;
543 }
544 traversed_count += 1;
546 }
547 }
548
549 pub fn new(grid: Array2<u8>, heuristic: &String) -> Self {
551 PathFinder {
552 grid,
553 heuristic: heuristic.clone(),
554 jump_points: BinaryHeap::with_capacity(50),
555 came_from: FnvHashMap::default(),
556 }
557 }
558
559 pub fn create_square_grid(size: usize) -> Array2<u8> {
561 let mut ndarray = Array2::<u8>::ones((size, size));
563 for y in 0..size {
565 ndarray[[y, 0]] = 0;
566 ndarray[[y, size - 1]] = 0;
567 }
568 for x in 0..size {
569 ndarray[[0, x]] = 0;
570 ndarray[[size - 1, x]] = 0;
571 }
572 ndarray
573 }
574}
575
576pub fn jps_test(pf: &mut PathFinder, source: &Point2d, target: &Point2d) -> Vec<Point2d> {
578 pf.find_path(&source, &target)
579}
580
581
582use std::fs::File;
583use std::io::Read;
584
585pub fn read_grid_from_file(path: String) -> Result<(Array2<u8>, u32, u32), std::io::Error> {
587 let mut file = File::open(path).unwrap();
588 let mut data = String::new();
589
590 file.read_to_string(&mut data).unwrap();
591 let mut height = 0;
592 let mut width = 0;
593 let mut my_vec = Vec::new();
595 for line in data.lines() {
596 width = line.len();
597 height += 1;
598 for char in line.chars() {
599 my_vec.push(char as u8 - 48);
600 }
601 }
602
603 let array = Array::from(my_vec).into_shape((height, width)).unwrap();
604 Ok((array, height as u32, width as u32))
605}
606
607#[cfg(test)] mod tests {
609 use super::*;
610 #[allow(unused_imports)]
611 use test::Bencher;
612
613 #[bench]
614 fn bench_jps_test_from_file(b: &mut Bencher) {
615 let result = read_grid_from_file(String::from("AutomatonLE.txt"));
617 let (array, _height, _width) = result.unwrap();
618 let source = Point2d { x: 32, y: 51 };
620 let target = Point2d { x: 150, y: 129 };
621
622 let mut pf = PathFinder::new(array, &String::from("octal"));
626 let path = jps_test(&mut pf, &source, &target);
627 assert_ne!(0, path.len());
628 b.iter(|| jps_test(&mut pf, &source, &target));
630 }
631
632 #[bench]
633 fn bench_jps_test_from_file_no_path(b: &mut Bencher) {
634 let result = read_grid_from_file(String::from("AutomatonLE.txt"));
636 let (mut array, _height, _width) = result.unwrap();
637 let source = Point2d { x: 32, y: 51 };
639 let target = Point2d { x: 150, y: 129 };
640
641 for x in 145..=150 {
643 for y in 129..=135 {
644 array[[y, x]] = 0;
645 }
646 }
647
648 let mut pf = PathFinder::new(array, &String::from("octal"));
649 let path = jps_test(&mut pf, &source, &target);
650 assert_eq!(0, path.len());
651 b.iter(|| jps_test(&mut pf, &source, &target));
653 }
654
655 #[bench]
656 fn bench_jps_test_out_of_bounds1(b: &mut Bencher) {
657 let grid = PathFinder::create_square_grid(30);
658 let mut pf = PathFinder::new(grid, &String::from("octal"));
659 let source: Point2d = Point2d { x: 500, y: 5 };
660 let target: Point2d = Point2d { x: 10, y: 12 };
661 let path = jps_test(&mut pf, &source, &target);
662 assert_eq!(0, path.len());
663 b.iter(|| jps_test(&mut pf, &source, &target));
664 }
665
666 #[bench]
667 fn bench_jps_test_out_of_bounds2(b: &mut Bencher) {
668 let grid = PathFinder::create_square_grid(30);
669 let mut pf = PathFinder::new(grid, &String::from("octal"));
670 let source: Point2d = Point2d { x: 5, y: 5 };
671 let target: Point2d = Point2d { x: 500, y: 12 };
672 let path = jps_test(&mut pf, &source, &target);
673 assert_eq!(0, path.len());
674 b.iter(|| jps_test(&mut pf, &source, &target));
675 }
676
677 #[bench]
678 fn bench_jps_test(b: &mut Bencher) {
679 let grid = PathFinder::create_square_grid(30);
680 let mut pf = PathFinder::new(grid, &String::from("octal"));
681 let source: Point2d = Point2d { x: 5, y: 5 };
682 let target: Point2d = Point2d { x: 10, y: 12 };
683 b.iter(|| jps_test(&mut pf, &source, &target));
684 }
685}