use crate::vec::NavVec;
use crate::NavError;
use std::collections::HashMap;
#[derive(Clone, Debug, PartialEq)]
pub struct Polygon<V> {
pub indices: Vec<u32>,
pub center: V,
pub radius: f32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Neighbor {
pub polygon: u32,
pub edge: u32,
}
#[derive(Clone, Debug)]
pub struct NavMesh<V: NavVec> {
pub vertices: Vec<V>,
pub polygons: Vec<Polygon<V>>,
neighbors: Vec<Vec<Option<Neighbor>>>,
}
impl<V: NavVec> NavMesh<V> {
pub fn from_polygons(vertices: Vec<V>, polygons: Vec<Vec<u32>>) -> Result<Self, NavError> {
if polygons.is_empty() {
return Err(NavError::EmptyRegion);
}
let mut regions = Vec::with_capacity(polygons.len());
for indices in polygons {
if indices.len() < 3 {
return Err(NavError::DegeneratePolygon);
}
let mut center = V::ZERO;
for &index in &indices {
let vertex = *vertices.get(index as usize).ok_or(NavError::BadIndex)?;
center = center.add(vertex);
}
center = center.mul(1.0 / indices.len() as f32);
let mut radius = 0.0f32;
for &index in &indices {
radius = radius.max(center.distance(vertices[index as usize]));
}
regions.push(Polygon { indices, center, radius });
}
let mut neighbors = vec![Vec::new(); regions.len()];
let mut directed: HashMap<(u32, u32), (u32, u32)> = HashMap::new();
for (polygon, region) in regions.iter().enumerate() {
let count = region.indices.len();
neighbors[polygon] = vec![None; count];
for edge in 0..count {
let a = region.indices[edge];
let b = region.indices[(edge + 1) % count];
directed.insert((a, b), (polygon as u32, edge as u32));
}
}
for (polygon, region) in regions.iter().enumerate() {
let count = region.indices.len();
for edge in 0..count {
let a = region.indices[edge];
let b = region.indices[(edge + 1) % count];
if let Some(&(other, other_edge)) = directed.get(&(b, a)) {
if other as usize != polygon {
neighbors[polygon][edge] = Some(Neighbor { polygon: other, edge: other_edge });
}
}
}
}
Ok(Self { vertices, polygons: regions, neighbors })
}
pub fn polygon_count(&self) -> usize {
self.polygons.len()
}
pub fn polygon(&self, index: u32) -> Option<&Polygon<V>> {
self.polygons.get(index as usize)
}
pub fn polygon_indices(&self, index: u32) -> Option<&[u32]> {
self.polygons.get(index as usize).map(|p| p.indices.as_slice())
}
pub fn neighbor(&self, polygon: u32, edge: u32) -> Option<Neighbor> {
self.neighbors.get(polygon as usize)?.get(edge as usize).copied().flatten()
}
pub fn portal(&self, polygon: u32, edge: u32) -> Option<[V; 2]> {
let region = self.polygons.get(polygon as usize)?;
let count = region.indices.len();
let a = *region.indices.get(edge as usize)?;
let b = region.indices[(edge as usize + 1) % count];
Some([self.vertices[a as usize], self.vertices[b as usize]])
}
pub fn locate(&self, point: V) -> Option<u32> {
let mut best: Option<(u32, f32)> = None;
for (index, region) in self.polygons.iter().enumerate() {
let region_vertices = self.region_vertices(region);
if point_in_polygon(®ion_vertices, point).is_none() {
continue;
}
let distance = region.center.distance(point);
match best {
Some((_, d)) if d <= distance => {}
_ => best = Some((index as u32, distance)),
}
}
best.map(|(index, _)| index)
}
pub fn closest_polygon(&self, point: V) -> Option<u32> {
let mut best: Option<(u32, f32)> = None;
for (index, region) in self.polygons.iter().enumerate() {
let candidate = closest_point_on_polygon(&self.vertices, region, point);
let distance = point.distance(candidate);
match best {
Some((_, d)) if d <= distance => {}
_ => best = Some((index as u32, distance)),
}
}
best.map(|(index, _)| index)
}
pub fn closest_point_on_polygon(&self, polygon: u32, point: V) -> V {
match self.polygons.get(polygon as usize) {
Some(region) => closest_point_on_polygon(&self.vertices, region, point),
None => point,
}
}
pub(crate) fn region_vertices(&self, region: &Polygon<V>) -> Vec<V> {
region
.indices
.iter()
.map(|&index| self.vertices[index as usize])
.collect()
}
}
pub(crate) fn barycentric(plane: [f32; 2], a: [f32; 2], b: [f32; 2], c: [f32; 2]) -> Option<[f32; 3]> {
let v0 = [b[0] - a[0], b[1] - a[1]];
let v1 = [c[0] - a[0], c[1] - a[1]];
let v2 = [plane[0] - a[0], plane[1] - a[1]];
let denom = v0[0] * v1[1] - v1[0] * v0[1];
if denom.abs() < 1e-12 {
return None;
}
let w1 = (v2[0] * v1[1] - v1[0] * v2[1]) / denom;
let w2 = (v0[0] * v2[1] - v2[0] * v0[1]) / denom;
let w0 = 1.0 - w1 - w2;
const EPS: f32 = 1e-5;
if w0 >= -EPS && w1 >= -EPS && w2 >= -EPS {
Some([w0, w1, w2])
} else {
None
}
}
pub(crate) fn point_in_polygon<V: NavVec>(vertices: &[V], point: V) -> Option<(usize, [f32; 3])> {
if vertices.len() < 3 {
return None;
}
let plane = point.nav2();
let a = vertices[0].nav2();
for i in 1..vertices.len() - 1 {
let b = vertices[i].nav2();
let c = vertices[i + 1].nav2();
if let Some(weights) = barycentric(plane, a, b, c) {
return Some((i, weights));
}
}
None
}
pub(crate) fn closest_on_segment<V: NavVec>(point: V, a: V, b: V) -> V {
let ab = b.sub(a);
let denom = ab.dot(ab);
if denom <= 1e-20 {
return a;
}
let t = (point.sub(a).dot(ab) / denom).clamp(0.0, 1.0);
a.add(ab.mul(t))
}
pub(crate) fn closest_point_on_polygon<V: NavVec>(
vertices: &[V],
region: &Polygon<V>,
point: V,
) -> V {
let region_vertices: Vec<V> =
region.indices.iter().map(|&i| vertices[i as usize]).collect();
if let Some((triangle, weights)) = point_in_polygon(®ion_vertices, point) {
let a = region_vertices[0].mul(weights[0]);
let b = region_vertices[triangle].mul(weights[1]);
let c = region_vertices[triangle + 1].mul(weights[2]);
return a.add(b).add(c);
}
let mut best = region_vertices[0];
let mut best_distance = point.distance(best);
for i in 0..region_vertices.len() {
let a = region_vertices[i];
let b = region_vertices[(i + 1) % region_vertices.len()];
let candidate = closest_on_segment(point, a, b);
let distance = point.distance(candidate);
if distance < best_distance {
best_distance = distance;
best = candidate;
}
}
best
}