use crate::mesh::NavMesh;
use crate::path::{astar_with, ResolvedLink};
use crate::vec::NavVec;
use crate::funnel;
#[derive(Clone, Debug, PartialEq)]
pub struct NavObstacle {
pub outline: Vec<[f32; 2]>,
pub enabled: bool,
}
impl NavObstacle {
pub fn new(outline: Vec<[f32; 2]>) -> Self {
Self { outline, enabled: true }
}
fn contains_point(&self, point: [f32; 2]) -> bool {
if !self.enabled || self.outline.len() < 3 {
return false;
}
let mut inside = false;
let count = self.outline.len();
let mut j = count - 1;
for i in 0..count {
let a = self.outline[i];
let b = self.outline[j];
if (a[1] > point[1]) != (b[1] > point[1]) {
let t = (point[1] - a[1]) / (b[1] - a[1]);
if point[0] < a[0] + t * (b[0] - a[0]) {
inside = !inside;
}
}
j = i;
}
inside
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NavLink<V> {
pub start: V,
pub end: V,
pub bidirectional: bool,
pub cost: Option<f32>,
pub enabled: bool,
}
impl<V> NavLink<V> {
pub fn new(start: V, end: V) -> Self {
Self { start, end, bidirectional: false, cost: None, enabled: true }
}
}
#[derive(Clone, Debug)]
pub struct NavMap<V: NavVec> {
pub mesh: NavMesh<V>,
pub obstacles: Vec<NavObstacle>,
pub links: Vec<NavLink<V>>,
}
impl<V: NavVec> NavMap<V> {
pub fn new(mesh: NavMesh<V>) -> Self {
Self { mesh, obstacles: Vec::new(), links: Vec::new() }
}
pub fn add_obstacle(&mut self, obstacle: NavObstacle) -> usize {
self.obstacles.push(obstacle);
self.obstacles.len() - 1
}
pub fn add_link(&mut self, link: NavLink<V>) -> usize {
self.links.push(link);
self.links.len() - 1
}
fn blocked_point(&self, point: V) -> bool {
let plane = point.nav2();
self.obstacles.iter().any(|o| o.contains_point(plane))
}
fn blocked_polygon(&self, polygon: u32) -> bool {
match self.mesh.polygon(polygon) {
Some(region) => self.blocked_point(region.center),
None => false,
}
}
fn blocked_portal(&self, portal: [V; 2]) -> bool {
let a = portal[0].nav2();
let b = portal[1].nav2();
let midpoint = V::from_nav2([(a[0] + b[0]) * 0.5, (a[1] + b[1]) * 0.5], 0.0);
self.blocked_point(midpoint)
}
fn resolve_links(&self) -> Vec<ResolvedLink<V>> {
let mut resolved = Vec::new();
for link in &self.links {
if !link.enabled || self.blocked_point(link.start) || self.blocked_point(link.end) {
continue;
}
let from = self.mesh.locate(link.start).or_else(|| self.mesh.closest_polygon(link.start));
let to = self.mesh.locate(link.end).or_else(|| self.mesh.closest_polygon(link.end));
let (Some(from), Some(to)) = (from, to) else {
continue;
};
if from == to {
continue;
}
let cost = link.cost.unwrap_or(link.start.distance(link.end));
resolved.push(ResolvedLink {
from,
to,
portal: [link.start, link.end],
cost,
bidirectional: link.bidirectional,
});
}
resolved
}
pub fn find_path(&self, from: V, to: V) -> Option<Vec<V>> {
let start_polygon = self.mesh.locate(from).or_else(|| self.mesh.closest_polygon(from))?;
let end_polygon = self.mesh.locate(to).or_else(|| self.mesh.closest_polygon(to))?;
let start = self.mesh.closest_point_on_polygon(start_polygon, from);
let end = self.mesh.closest_point_on_polygon(end_polygon, to);
if start_polygon == end_polygon {
return Some(vec![start, end]);
}
let links = self.resolve_links();
let (_, portals) = astar_with(
&self.mesh,
start_polygon,
end_polygon,
start,
end,
|polygon| self.blocked_polygon(polygon),
|portal| self.blocked_portal(portal),
&links,
)?;
let mut path = funnel::funnel(start, end, &portals);
funnel::dedup(&mut path);
Some(path)
}
}