1use crate::funnel::{dedup, funnel};
4use crate::mesh::{closest_on_segment, NavMesh};
5use crate::vec::NavVec;
6use std::cmp::Ordering;
7use std::collections::BinaryHeap;
8
9#[derive(Clone, Copy, Debug)]
11pub(crate) struct ResolvedLink<V> {
12 pub from: u32,
13 pub to: u32,
14 pub portal: [V; 2],
15 pub cost: f32,
16 pub bidirectional: bool,
17}
18
19#[derive(Clone, Copy, Debug)]
22struct OpenNode {
23 f: f32,
24 polygon: u32,
25}
26
27impl PartialEq for OpenNode {
28 fn eq(&self, other: &Self) -> bool {
29 self.f.to_bits() == other.f.to_bits() && self.polygon == other.polygon
30 }
31}
32impl Eq for OpenNode {}
33impl PartialOrd for OpenNode {
34 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
35 Some(self.cmp(other))
36 }
37}
38impl Ord for OpenNode {
39 fn cmp(&self, other: &Self) -> Ordering {
40 self.f.total_cmp(&other.f).then(self.polygon.cmp(&other.polygon))
41 }
42}
43
44fn heuristic<V: NavVec>(mesh: &NavMesh<V>, polygon: u32, target: V) -> f32 {
45 let region = &mesh.polygons[polygon as usize];
46 (region.center.distance(target) - region.radius).max(0.0)
47}
48
49pub(crate) fn astar_with<V: NavVec>(
56 mesh: &NavMesh<V>,
57 start_poly: u32,
58 end_poly: u32,
59 start_point: V,
60 end_point: V,
61 blocked_poly: impl Fn(u32) -> bool,
62 blocked_portal: impl Fn([V; 2]) -> bool,
63 links: &[ResolvedLink<V>],
64) -> Option<(Vec<u32>, Vec<[V; 2]>)> {
65 if start_poly == end_poly {
66 return Some((vec![start_poly], Vec::new()));
67 }
68 let count = mesh.polygons.len();
69 let mut g = vec![f32::INFINITY; count];
70 let mut came: Vec<Option<(u32, [V; 2])>> = vec![None; count];
71 let mut entry = vec![V::ZERO; count];
72 let mut visited = vec![false; count];
73
74 let mut open: BinaryHeap<std::cmp::Reverse<OpenNode>> = BinaryHeap::new();
75 g[start_poly as usize] = 0.0;
76 entry[start_poly as usize] = start_point;
77 open.push(std::cmp::Reverse(OpenNode {
78 f: heuristic(mesh, start_poly, end_point),
79 polygon: start_poly,
80 }));
81
82 while let Some(std::cmp::Reverse(node)) = open.pop() {
83 let polygon = node.polygon;
84 let pi = polygon as usize;
85 if visited[pi] {
86 continue;
87 }
88 visited[pi] = true;
89 if polygon == end_poly {
90 break;
91 }
92 let entry_point = entry[pi];
93
94 for edge in 0..mesh.polygons[pi].indices.len() as u32 {
95 let Some(neighbor) = mesh.neighbor(polygon, edge) else {
96 continue;
97 };
98 let q = neighbor.polygon;
99 if q == polygon {
100 continue;
101 }
102 if q != end_poly && blocked_poly(q) {
103 continue;
104 }
105 let Some(portal) = mesh.portal(polygon, edge) else {
106 continue;
107 };
108 if blocked_portal(portal) {
109 continue;
110 }
111 let q_entry = closest_on_segment(entry_point, portal[0], portal[1]);
112 let ng = g[pi] + entry_point.distance(q_entry);
113 if ng < g[q as usize] {
114 g[q as usize] = ng;
115 came[q as usize] = Some((polygon, portal));
116 entry[q as usize] = q_entry;
117 open.push(std::cmp::Reverse(OpenNode {
118 f: ng + heuristic(mesh, q, end_point),
119 polygon: q,
120 }));
121 }
122 }
123
124 for link in links {
125 let (q, portal, cost) = if link.from == polygon {
126 (link.to, link.portal, entry_point.distance(link.portal[0]) + link.cost)
127 } else if link.bidirectional && link.to == polygon {
128 (
129 link.from,
130 [link.portal[1], link.portal[0]],
131 entry_point.distance(link.portal[1]) + link.cost,
132 )
133 } else {
134 continue;
135 };
136 if q != end_poly && blocked_poly(q) {
137 continue;
138 }
139 let q_entry = portal[1];
140 let ng = g[pi] + cost;
141 if ng < g[q as usize] {
142 g[q as usize] = ng;
143 came[q as usize] = Some((polygon, portal));
144 entry[q as usize] = q_entry;
145 open.push(std::cmp::Reverse(OpenNode {
146 f: ng + heuristic(mesh, q, end_point),
147 polygon: q,
148 }));
149 }
150 }
151 }
152
153 let mut corridor = vec![end_poly];
154 let mut portals = Vec::new();
155 let mut current = end_poly;
156 while current != start_poly {
157 let (previous, portal) = came[current as usize]?;
158 portals.push(portal);
159 corridor.push(previous);
160 current = previous;
161 }
162 corridor.reverse();
163 portals.reverse();
164 Some((corridor, portals))
165}
166
167impl<V: NavVec> NavMesh<V> {
168 pub fn find_path(&self, from: V, to: V) -> Option<Vec<V>> {
174 let start_polygon = self.locate(from).or_else(|| self.closest_polygon(from))?;
175 let end_polygon = self.locate(to).or_else(|| self.closest_polygon(to))?;
176 let start = self.closest_point_on_polygon(start_polygon, from);
177 let end = self.closest_point_on_polygon(end_polygon, to);
178 if start_polygon == end_polygon {
179 return Some(vec![start, end]);
180 }
181 let (_, portals) =
182 astar_with(self, start_polygon, end_polygon, start, end, |_| false, |_| false, &[])?;
183 let mut path = funnel(start, end, &portals);
184 dedup(&mut path);
185 Some(path)
186 }
187}