blitz_path/
jps.rs

1use std::collections::BinaryHeap;
2
3use movingai::Coords2D;
4use movingai::Map2D;
5
6use crate::node::Node;
7use crate::utils::{direction, distance, rewind};
8use crate::Route;
9
10#[derive(Copy, Clone)]
11enum Direction {
12    Vertical(i32),
13    Horizontal(i32),
14    Diagonal(i32, i32),
15}
16
17///Creates a new route using the JPS algorithm.
18///Returns a Route struct containing the distance to the goal and number of steps needed to get there.
19/// # Examples
20///
21/// ```
22/// # use std::io;
23/// use std::path::Path;
24///
25/// let map = movingai::parser::parse_map_file(Path::new("./tests/map/maze512-32-9.map"))?;
26/// let scenes = movingai::parser::parse_scen_file(Path::new("./tests/map/maze512-32-9.map.scen"))?;
27/// let scene = &scenes[0];
28///
29/// let path = blitz_path::jps_path(&map, scene.start_pos, scene.goal_pos);
30///
31/// // using as f32 as scene.optimal_length is stored as f64,
32/// // but only seems to have precision to f32
33/// if let Some(path) = path {
34///     assert_eq!(scene.optimal_length as f32, path.distance() as f32);  
35/// }
36/// # Ok::<(), io::Error>(())
37/// ```
38
39#[inline]
40pub fn jps_path<U, T: Map2D<U>>(map: &T, start: Coords2D, goal: Coords2D) -> Option<Route> {
41    if start == goal {
42        return Some(Route::from((0.0, vec![])));
43    }
44
45    //Push start node to open list
46    let start_node = Node::new(0.0, distance(start, goal), start, start);
47
48    //Add start's neighbours to open list, needs to add all surrounds as JPS relies on cutting corners
49    //and 8 directions of movement.
50    let prev_x = start_node.position.0 - 1;
51    let next_x = start_node.position.0 + 1;
52    let prev_y = start_node.position.1 - 1;
53    let next_y = start_node.position.1 + 1;
54
55    //Initialize open and closed lists
56    let capacity = (1 + next_x - prev_x) * (1 + next_y - prev_y);
57    let mut open = BinaryHeap::with_capacity(capacity);
58    let mut closed = Vec::with_capacity(capacity);
59
60    for x in prev_x..=next_x {
61        for y in prev_y..=next_y {
62            open.push(Node::from_parent(&start_node, (x, y), goal));
63        }
64    }
65
66    closed.push(start_node);
67
68    //Examine the nodes
69    while let Some(node_current) = open.pop() {
70        //If this is the target node return the distance to get there
71        if node_current.position == goal {
72            //Push all remaining to closed
73            closed.append(&mut open.into_vec());
74
75            //Unwind
76            let path = rewind(&node_current, &closed);
77            let route = Route::from((node_current.g, path));
78            return Some(route);
79        }
80
81        //Check if node is on closed list and continue if is
82        if closed.contains(&node_current) {
83            continue;
84        }
85
86        //Calculate direction
87        let direction = direction(node_current.position, node_current.parent);
88
89        if let Some(nodes) = check_jump(&node_current, map, direction, goal) {
90            for node in nodes {
91                open.push(node);
92            }
93        }
94
95        //Push current node to closed list
96        closed.push(node_current);
97    }
98
99    None
100}
101
102#[inline]
103fn check_jump<U, T: Map2D<U>>(
104    parent: &Node,
105    map: &T,
106    (dx, dy): (i32, i32),
107    goal: Coords2D,
108) -> Option<Vec<Node>> {
109    if dx != 0 {
110        if dy != 0 {
111            expand(map, &parent, Direction::Diagonal(dx, dy), goal)
112        } else {
113            expand(map, &parent, Direction::Horizontal(dx), goal)
114        }
115    } else if dy != 0 {
116        expand(map, &parent, Direction::Vertical(dy), goal)
117    } else {
118        None
119    }
120}
121
122#[inline]
123fn forced_horizontal<U, T: Map2D<U>>(
124    nodes: &mut Vec<Node>,
125    map: &T,
126    check_node: &Node,
127    direction: i32,
128    goal: Coords2D,
129) {
130    let (check_x, check_y) = check_node.position;
131    let next_x = (check_x as i32 + direction) as usize;
132    let up_y = (check_y as i32 - 1) as usize;
133    let down_y = (check_y as i32 + 1) as usize;
134
135    //Check if blocked up
136    if !map.is_traversable((check_x, up_y)) && map.is_traversable((next_x, up_y)) {
137        nodes.push(Node::from_parent(&check_node, (next_x, up_y), goal));
138    }
139
140    //Check if blocked down
141    if !map.is_traversable((check_x, down_y)) && map.is_traversable((next_x, down_y)) {
142        nodes.push(Node::from_parent(&check_node, (next_x, down_y), goal));
143    }
144}
145
146#[inline]
147fn forced_vertical<U, T: Map2D<U>>(
148    nodes: &mut Vec<Node>,
149    map: &T,
150    check_node: &Node,
151    direction: i32,
152    goal: Coords2D,
153) {
154    let (check_x, check_y) = check_node.position;
155    let left_x = (check_x as i32 - 1) as usize;
156    let right_x = (check_x as i32 + 1) as usize;
157    let next_y = (check_y as i32 + direction) as usize;
158
159    //Check if blocked left
160    if !map.is_traversable((left_x, check_y)) && map.is_traversable((left_x, next_y)) {
161        nodes.push(Node::from_parent(&check_node, (left_x, next_y), goal));
162    }
163
164    //Check if blocked right
165    if !map.is_traversable((right_x, check_y)) && map.is_traversable((right_x, next_y)) {
166        nodes.push(Node::from_parent(&check_node, (right_x, next_y), goal));
167    }
168}
169
170#[inline]
171fn expand<U, T: Map2D<U>>(
172    map: &T,
173    start_node: &Node,
174    direction: Direction,
175    goal: Coords2D,
176) -> Option<Vec<Node>> {
177    let mut current = *start_node;
178    let mut nodes = Vec::new();
179    loop {
180        //Check if goal
181        if current.position == goal {
182            nodes.push(current);
183
184            return Some(nodes);
185        }
186
187        //Check blocked
188        if !map.is_traversable(current.position) {
189            return None;
190        }
191
192        //Otherwise Expand depending on direction
193        let dir = match direction {
194            Direction::Vertical(vert) => {
195                //Check for forced neighbours
196                forced_vertical(&mut nodes, map, &current, vert, goal);
197
198                (0, vert)
199            }
200            Direction::Horizontal(hor) => {
201                //Check for forced neighbours
202                forced_horizontal(&mut nodes, map, &current, hor, goal);
203
204                (hor, 0)
205            }
206            Direction::Diagonal(hor, vert) => {
207                //Expand horizontally
208                if let Some(mut hor_nodes) = expand(map, &current, Direction::Horizontal(hor), goal)
209                {
210                    nodes.append(&mut hor_nodes);
211                }
212                //Expand vertically
213                if let Some(mut vert_nodes) = expand(map, &current, Direction::Vertical(vert), goal)
214                {
215                    nodes.append(&mut vert_nodes);
216                }
217
218                (hor, vert)
219            }
220        };
221
222        let next_x = (current.position.0 as i32 + dir.0) as usize;
223        let next_y = (current.position.1 as i32 + dir.1) as usize;
224        let next_position = (next_x, next_y);
225
226        //If forced neighbours found return them along with this node and next on to continue checking in this direction
227        if !nodes.is_empty() {
228            let next_node = Node::from_parent(&current, next_position, goal);
229            nodes.push(current);
230            nodes.push(next_node);
231
232            return Some(nodes);
233        }
234
235        //Else move onto next tile
236        current = Node::from_parent(start_node, next_position, goal);
237    }
238}