1use crate::{Cell, Grid};
4use std::cmp::Ordering;
5use std::collections::{BinaryHeap, HashMap};
6
7#[derive(Debug, Clone)]
9pub struct DijkstraMap {
10 costs: Vec<f32>,
11 width: usize,
12 height: usize,
13}
14
15impl DijkstraMap {
16 pub fn new(width: usize, height: usize) -> Self {
17 Self {
18 costs: vec![f32::INFINITY; width * height],
19 width,
20 height,
21 }
22 }
23
24 pub fn get(&self, x: usize, y: usize) -> f32 {
25 self.costs[y * self.width + x]
26 }
27
28 pub fn set(&mut self, x: usize, y: usize, cost: f32) {
29 self.costs[y * self.width + x] = cost;
30 }
31
32 pub fn width(&self) -> usize {
33 self.width
34 }
35 pub fn height(&self) -> usize {
36 self.height
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct FlowField {
43 directions: Vec<(i32, i32)>,
44 width: usize,
45 height: usize,
46}
47
48impl FlowField {
49 pub fn new(width: usize, height: usize) -> Self {
50 Self {
51 directions: vec![(0, 0); width * height],
52 width,
53 height,
54 }
55 }
56
57 pub fn get_direction(&self, x: usize, y: usize) -> (i32, i32) {
58 self.directions[y * self.width + x]
59 }
60
61 pub fn set_direction(&mut self, x: usize, y: usize, dir: (i32, i32)) {
62 self.directions[y * self.width + x] = dir;
63 }
64
65 pub fn width(&self) -> usize {
66 self.width
67 }
68 pub fn height(&self) -> usize {
69 self.height
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct PathfindingConstraints {
76 pub movement_cost: HashMap<(i32, i32), f32>,
77 pub blocked_cells: Vec<(usize, usize)>,
78}
79
80impl Default for PathfindingConstraints {
81 fn default() -> Self {
82 let mut movement_cost = HashMap::new();
83 movement_cost.insert((-1, 0), 1.0);
85 movement_cost.insert((1, 0), 1.0);
86 movement_cost.insert((0, -1), 1.0);
87 movement_cost.insert((0, 1), 1.0);
88 movement_cost.insert((-1, -1), 1.414);
89 movement_cost.insert((-1, 1), 1.414);
90 movement_cost.insert((1, -1), 1.414);
91 movement_cost.insert((1, 1), 1.414);
92
93 Self {
94 movement_cost,
95 blocked_cells: Vec::new(),
96 }
97 }
98}
99
100#[derive(Debug, PartialEq)]
101struct Node {
102 cost: f32,
103 x: usize,
104 y: usize,
105}
106
107impl Eq for Node {}
108
109impl Ord for Node {
110 fn cmp(&self, other: &Self) -> Ordering {
111 other
112 .cost
113 .partial_cmp(&self.cost)
114 .unwrap_or(Ordering::Equal)
115 }
116}
117
118impl PartialOrd for Node {
119 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
120 Some(self.cmp(other))
121 }
122}
123
124pub fn dijkstra_map<C: Cell>(
126 grid: &Grid<C>,
127 goals: &[(usize, usize)],
128 constraints: &PathfindingConstraints,
129) -> DijkstraMap {
130 let mut map = DijkstraMap::new(grid.width(), grid.height());
131 let mut heap = BinaryHeap::new();
132
133 for &(x, y) in goals {
135 map.set(x, y, 0.0);
136 heap.push(Node { cost: 0.0, x, y });
137 }
138
139 while let Some(Node { cost, x, y }) = heap.pop() {
140 if cost > map.get(x, y) {
141 continue;
142 }
143
144 for (&(dx, dy), &move_cost) in &constraints.movement_cost {
145 let nx = x as i32 + dx;
146 let ny = y as i32 + dy;
147
148 if nx >= 0 && ny >= 0 && (nx as usize) < grid.width() && (ny as usize) < grid.height() {
149 let nx = nx as usize;
150 let ny = ny as usize;
151
152 if constraints.blocked_cells.contains(&(nx, ny)) {
153 continue;
154 }
155
156 if let Some(cell) = grid.get(nx as i32, ny as i32) {
157 if !cell.is_passable() {
158 continue;
159 }
160 }
161
162 let new_cost = cost + move_cost;
163 if new_cost < map.get(nx, ny) {
164 map.set(nx, ny, new_cost);
165 heap.push(Node {
166 cost: new_cost,
167 x: nx,
168 y: ny,
169 });
170 }
171 }
172 }
173 }
174
175 map
176}
177
178pub fn shortest_path<C: Cell>(
180 grid: &Grid<C>,
181 start: (usize, usize),
182 end: (usize, usize),
183 constraints: &PathfindingConstraints,
184) -> Option<Vec<(usize, usize)>> {
185 if !grid.in_bounds(start.0 as i32, start.1 as i32)
186 || !grid.in_bounds(end.0 as i32, end.1 as i32)
187 {
188 return None;
189 }
190
191 if constraints.blocked_cells.contains(&start) || constraints.blocked_cells.contains(&end) {
192 return None;
193 }
194
195 if !grid
196 .get(start.0 as i32, start.1 as i32)
197 .is_some_and(|cell| cell.is_passable())
198 {
199 return None;
200 }
201
202 if !grid
203 .get(end.0 as i32, end.1 as i32)
204 .is_some_and(|cell| cell.is_passable())
205 {
206 return None;
207 }
208
209 let dijkstra = dijkstra_map(grid, &[end], constraints);
210 if dijkstra.get(start.0, start.1) == f32::INFINITY {
211 return None;
212 }
213
214 if start == end {
215 return Some(vec![start]);
216 }
217
218 let mut path = vec![start];
219 let mut current = start;
220 let mut remaining_steps = grid.width() * grid.height();
221
222 while current != end && remaining_steps > 0 {
223 let (x, y) = current;
224 let current_cost = dijkstra.get(x, y);
225 let mut best = None;
226 let mut best_cost = current_cost;
227
228 for &(dx, dy) in constraints.movement_cost.keys() {
229 let nx = x as i32 + dx;
230 let ny = y as i32 + dy;
231 if !grid.in_bounds(nx, ny) {
232 continue;
233 }
234
235 let npos = (nx as usize, ny as usize);
236 if constraints.blocked_cells.contains(&npos) {
237 continue;
238 }
239
240 if !grid.get(nx, ny).is_some_and(|cell| cell.is_passable()) {
241 continue;
242 }
243
244 let cost = dijkstra.get(npos.0, npos.1);
245 if cost < best_cost {
246 best_cost = cost;
247 best = Some(npos);
248 }
249 }
250
251 let next = best?;
252 path.push(next);
253 current = next;
254 remaining_steps = remaining_steps.saturating_sub(1);
255 }
256
257 if current == end {
258 Some(path)
259 } else {
260 None
261 }
262}
263
264pub fn flow_field_from_dijkstra(dijkstra: &DijkstraMap) -> FlowField {
266 let mut flow = FlowField::new(dijkstra.width(), dijkstra.height());
267
268 for y in 0..dijkstra.height() {
269 for x in 0..dijkstra.width() {
270 let current_cost = dijkstra.get(x, y);
271 if current_cost == f32::INFINITY {
272 continue;
273 }
274
275 let mut best_dir = (0, 0);
276 let mut best_cost = current_cost;
277
278 for dx in -1..=1 {
279 for dy in -1..=1 {
280 if dx == 0 && dy == 0 {
281 continue;
282 }
283
284 let nx = x as i32 + dx;
285 let ny = y as i32 + dy;
286
287 if nx >= 0
288 && ny >= 0
289 && (nx as usize) < dijkstra.width()
290 && (ny as usize) < dijkstra.height()
291 {
292 let neighbor_cost = dijkstra.get(nx as usize, ny as usize);
293 if neighbor_cost < best_cost {
294 best_cost = neighbor_cost;
295 best_dir = (dx, dy);
296 }
297 }
298 }
299 }
300
301 flow.set_direction(x, y, best_dir);
302 }
303 }
304
305 flow
306}