1use std::collections::{BTreeMap, VecDeque};
26
27use crate::navmesh::{NavmeshValidationError, corridor::NavmeshCorridor};
28use crate::{Navmesh, NavmeshPortal, NavmeshQuery, NavmeshQueryResult, Point2};
29
30pub trait PreparedNavmeshBuilder {
37 type Map: PreparedNavmesh;
39
40 fn name(&self) -> &'static str;
42
43 fn preprocess(&self, navmesh: &Navmesh) -> Result<Self::Map, PreparedNavmeshBuildError>;
49}
50
51pub trait PreparedNavmesh {
56 fn name(&self) -> &'static str;
58
59 fn navmesh(&self) -> &Navmesh;
61
62 fn locate_point(&self, point: Point2) -> Option<usize> {
64 self.navmesh().locate_point(point)
65 }
66
67 fn locate_cells(&self, point: Point2) -> Vec<usize> {
69 self.navmesh().locate_cells(point)
70 }
71
72 fn query(&self, query: NavmeshQuery) -> NavmeshQueryResult {
74 self.navmesh().query(query)
75 }
76
77 fn neighbors(&self, cell_index: usize) -> Option<&[usize]>;
79
80 fn portals_from(&self, cell_index: usize) -> Option<&[NavmeshPortal]>;
82
83 fn portal_between(&self, left_cell: usize, right_cell: usize) -> Option<NavmeshPortal>;
88
89 fn cells_connected(&self, start_cell: usize, goal_cell: usize) -> bool {
94 let cell_count = self.navmesh().cells().len();
95 if start_cell >= cell_count || goal_cell >= cell_count {
96 return false;
97 }
98
99 let mut seen = vec![false; cell_count];
100 let mut frontier = VecDeque::from([start_cell]);
101 seen[start_cell] = true;
102
103 while let Some(cell_index) = frontier.pop_front() {
104 if cell_index == goal_cell {
105 return true;
106 }
107
108 let Some(neighbors) = self.neighbors(cell_index) else {
109 continue;
110 };
111
112 for &neighbor in neighbors {
113 if neighbor >= seen.len() || seen[neighbor] {
114 continue;
115 }
116 seen[neighbor] = true;
117 frontier.push_back(neighbor);
118 }
119 }
120
121 false
122 }
123
124 fn materialize_corridor(
128 &self,
129 start: Point2,
130 goal: Point2,
131 cells: &[usize],
132 ) -> Option<NavmeshCorridor> {
133 NavmeshCorridor::from_cells(self.navmesh(), start, goal, cells)
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, thiserror::Error)]
141#[non_exhaustive]
142pub enum PreparedNavmeshBuildError {
143 #[error("invalid navmesh: {source}")]
145 InvalidNavmesh {
146 #[from]
148 source: NavmeshValidationError,
149 },
150}
151
152#[derive(Debug, Clone, Copy, Default)]
157pub struct StaticPreparedNavmeshBuilder;
158
159impl PreparedNavmeshBuilder for StaticPreparedNavmeshBuilder {
160 type Map = StaticPreparedNavmesh;
161
162 fn name(&self) -> &'static str {
163 "static-prepared-navmesh"
164 }
165
166 fn preprocess(&self, navmesh: &Navmesh) -> Result<Self::Map, PreparedNavmeshBuildError> {
167 navmesh
168 .validate()
169 .map_err(PreparedNavmeshBuildError::from)?;
170
171 let mut neighbors = vec![Vec::new(); navmesh.cells().len()];
172 let mut portals_from = vec![Vec::new(); navmesh.cells().len()];
173 let mut portal_lookup = BTreeMap::new();
174
175 for &portal in navmesh.portals() {
176 neighbors[portal.left_cell].push(portal.right_cell);
177 neighbors[portal.right_cell].push(portal.left_cell);
178
179 portals_from[portal.left_cell].push(portal);
180 portals_from[portal.right_cell].push(portal);
181
182 portal_lookup
184 .entry((portal.left_cell, portal.right_cell))
185 .or_insert(portal);
186 portal_lookup
187 .entry((portal.right_cell, portal.left_cell))
188 .or_insert(portal);
189 }
190
191 Ok(StaticPreparedNavmesh {
192 navmesh: navmesh.clone(),
193 neighbors,
194 portals_from,
195 portal_lookup,
196 })
197 }
198}
199
200#[derive(Debug, Clone, PartialEq)]
205pub struct StaticPreparedNavmesh {
206 navmesh: Navmesh,
207 neighbors: Vec<Vec<usize>>,
208 portals_from: Vec<Vec<NavmeshPortal>>,
209 portal_lookup: BTreeMap<(usize, usize), NavmeshPortal>,
210}
211
212impl StaticPreparedNavmesh {
213 #[must_use]
215 pub fn builder() -> StaticPreparedNavmeshBuilder {
216 StaticPreparedNavmeshBuilder
217 }
218}
219
220impl PreparedNavmesh for StaticPreparedNavmesh {
221 fn name(&self) -> &'static str {
222 "static-prepared-navmesh"
223 }
224
225 fn navmesh(&self) -> &Navmesh {
226 &self.navmesh
227 }
228
229 fn neighbors(&self, cell_index: usize) -> Option<&[usize]> {
230 self.neighbors.get(cell_index).map(Vec::as_slice)
231 }
232
233 fn portals_from(&self, cell_index: usize) -> Option<&[NavmeshPortal]> {
234 self.portals_from.get(cell_index).map(Vec::as_slice)
235 }
236
237 fn portal_between(&self, left_cell: usize, right_cell: usize) -> Option<NavmeshPortal> {
238 self.portal_lookup.get(&(left_cell, right_cell)).copied()
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::{PreparedNavmesh, PreparedNavmeshBuilder, StaticPreparedNavmesh};
245 use crate::navmesh::corridor::NavmeshCorridor;
246 use crate::{Navmesh, NavmeshCell, NavmeshPortal, Point2};
247
248 #[test]
249 fn portal_between_selects_same_portal_as_corridor_for_duplicate_pair() {
250 let p0 = NavmeshPortal {
251 left_cell: 0,
252 right_cell: 1,
253 start: Point2::new(2.0, 0.0),
254 end: Point2::new(2.0, 2.0),
255 };
256 let p1 = NavmeshPortal {
257 left_cell: 0,
258 right_cell: 1,
259 start: Point2::new(2.0, 2.0),
260 end: Point2::new(2.0, 4.0),
261 };
262 let navmesh = Navmesh::new(
263 vec![
264 NavmeshCell::new(
265 "left",
266 vec![
267 Point2::new(0.0, 0.0),
268 Point2::new(2.0, 0.0),
269 Point2::new(2.0, 4.0),
270 Point2::new(0.0, 4.0),
271 ],
272 ),
273 NavmeshCell::new(
274 "right",
275 vec![
276 Point2::new(2.0, 0.0),
277 Point2::new(4.0, 0.0),
278 Point2::new(4.0, 4.0),
279 Point2::new(2.0, 4.0),
280 ],
281 ),
282 ],
283 vec![p0, p1],
284 );
285 navmesh
286 .validate()
287 .expect("two-portal navmesh should validate");
288
289 let prepared = StaticPreparedNavmesh::builder()
290 .preprocess(&navmesh)
291 .expect("preprocess should succeed");
292
293 assert_eq!(prepared.portal_between(0, 1), Some(p0));
294 assert_eq!(prepared.portal_between(1, 0), Some(p0));
295
296 let corridor = NavmeshCorridor::from_cells(
297 &navmesh,
298 Point2::new(1.0, 1.0),
299 Point2::new(3.0, 1.0),
300 &[0, 1],
301 )
302 .expect("corridor should build");
303 assert_eq!(corridor.portals[0], p0);
304 }
305}