terrain_forge/spatial/
pathfinding.rs

1//! Advanced pathfinding algorithms
2
3use crate::{Cell, Grid};
4use std::cmp::Ordering;
5use std::collections::{BinaryHeap, HashMap};
6
7/// Dijkstra map for multi-goal pathfinding
8#[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/// Flow field for AI movement
41#[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/// Pathfinding constraints
74#[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        // Standard 8-directional movement
84        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
124/// Generate Dijkstra map from multiple goals
125pub 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    // Initialize goals with cost 0
134    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
178/// Generate flow field from Dijkstra map
179pub fn flow_field_from_dijkstra(dijkstra: &DijkstraMap) -> FlowField {
180    let mut flow = FlowField::new(dijkstra.width(), dijkstra.height());
181
182    for y in 0..dijkstra.height() {
183        for x in 0..dijkstra.width() {
184            let current_cost = dijkstra.get(x, y);
185            if current_cost == f32::INFINITY {
186                continue;
187            }
188
189            let mut best_dir = (0, 0);
190            let mut best_cost = current_cost;
191
192            for dx in -1..=1 {
193                for dy in -1..=1 {
194                    if dx == 0 && dy == 0 {
195                        continue;
196                    }
197
198                    let nx = x as i32 + dx;
199                    let ny = y as i32 + dy;
200
201                    if nx >= 0
202                        && ny >= 0
203                        && (nx as usize) < dijkstra.width()
204                        && (ny as usize) < dijkstra.height()
205                    {
206                        let neighbor_cost = dijkstra.get(nx as usize, ny as usize);
207                        if neighbor_cost < best_cost {
208                            best_cost = neighbor_cost;
209                            best_dir = (dx, dy);
210                        }
211                    }
212                }
213            }
214
215            flow.set_direction(x, y, best_dir);
216        }
217    }
218
219    flow
220}