#![forbid(unsafe_code)]
use axiolid_contracts::Sign;
use axiolid_core::Point2;
use axiolid_guarantees::Certified;
use axiolid_overlay::{Polygon, Ring};
use axiolid_predicates::orient2d;
pub const MAX_VERTICES: usize = 512;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Unreachable {
StartOutside,
GoalOutside,
DisconnectedComponents,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RouteError {
NonFinitePoint,
RingTooShort,
BarrierTooShort,
TooManyVertices {
supplied: usize,
},
Undecidable,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Route {
pub polyline: Vec<Point2>,
pub length: f64,
pub graph_vertices: usize,
}
pub fn shortest_path(
region: &[Polygon],
barriers: &[Vec<Point2>],
start: Point2,
goal: Point2,
) -> Result<Result<Route, Unreachable>, RouteError> {
validate(region, barriers, start, goal)?;
if !contains(region, start)? {
return Ok(Err(Unreachable::StartOutside));
}
if !contains(region, goal)? {
return Ok(Err(Unreachable::GoalOutside));
}
let mut nodes = vec![start, goal];
for polygon in region {
nodes.extend(polygon.outer.points.iter().copied());
for hole in &polygon.holes {
nodes.extend(hole.points.iter().copied());
}
}
for barrier in barriers {
nodes.extend(barrier.iter().copied());
}
dedup_points(&mut nodes);
if nodes.len() > MAX_VERTICES {
return Err(RouteError::TooManyVertices {
supplied: nodes.len(),
});
}
let obstacles = obstacle_segments(region, barriers);
let mut adjacency = vec![Vec::new(); nodes.len()];
for i in 0..nodes.len() {
for j in i + 1..nodes.len() {
if visible(nodes[i], nodes[j], region, &obstacles)? {
let length = (nodes[i] - nodes[j]).length();
adjacency[i].push((j, length));
adjacency[j].push((i, length));
}
}
}
let Some((length, path)) = dijkstra(&adjacency, 0, 1) else {
return Ok(Err(Unreachable::DisconnectedComponents));
};
Ok(Ok(Route {
polyline: path.into_iter().map(|index| nodes[index]).collect(),
length,
graph_vertices: nodes.len(),
}))
}
fn validate(
region: &[Polygon],
barriers: &[Vec<Point2>],
start: Point2,
goal: Point2,
) -> Result<(), RouteError> {
if !start.is_finite() || !goal.is_finite() {
return Err(RouteError::NonFinitePoint);
}
for polygon in region {
for ring in core::iter::once(&polygon.outer).chain(polygon.holes.iter()) {
if ring.points.len() < 3 {
return Err(RouteError::RingTooShort);
}
if !ring.points.iter().all(|p| p.is_finite()) {
return Err(RouteError::NonFinitePoint);
}
}
}
for barrier in barriers {
if barrier.len() < 2 {
return Err(RouteError::BarrierTooShort);
}
if !barrier.iter().all(|p| p.is_finite()) {
return Err(RouteError::NonFinitePoint);
}
}
Ok(())
}
fn side(a: Point2, b: Point2, c: Point2) -> Result<Sign, RouteError> {
match orient2d(a, b, c) {
Certified::Certain { sign, .. } => Ok(sign),
_ => Err(RouteError::Undecidable),
}
}
fn crosses(p: Point2, q: Point2, r: Point2, s: Point2) -> Result<bool, RouteError> {
let d1 = side(p, q, r)?;
let d2 = side(p, q, s)?;
let d3 = side(r, s, p)?;
let d4 = side(r, s, q)?;
Ok(d1 != Sign::Zero
&& d2 != Sign::Zero
&& d3 != Sign::Zero
&& d4 != Sign::Zero
&& d1 != d2
&& d3 != d4)
}
fn obstacle_segments(region: &[Polygon], barriers: &[Vec<Point2>]) -> Vec<(Point2, Point2)> {
let mut segments = Vec::new();
for polygon in region {
for ring in core::iter::once(&polygon.outer).chain(polygon.holes.iter()) {
segments.extend(ring_edges(ring));
}
}
for barrier in barriers {
for pair in barrier.windows(2) {
segments.push((pair[0], pair[1]));
}
}
segments
}
fn ring_edges(ring: &Ring) -> Vec<(Point2, Point2)> {
let count = ring.points.len();
(0..count)
.map(|index| (ring.points[index], ring.points[(index + 1) % count]))
.collect()
}
fn visible(
a: Point2,
b: Point2,
region: &[Polygon],
obstacles: &[(Point2, Point2)],
) -> Result<bool, RouteError> {
for (p, q) in obstacles {
if crosses(a, b, *p, *q)? {
return Ok(false);
}
}
let midpoint = Point2::new(a.x * 0.5 + b.x * 0.5, a.y * 0.5 + b.y * 0.5);
contains(region, midpoint)
}
fn contains(region: &[Polygon], point: Point2) -> Result<bool, RouteError> {
for polygon in region {
if !point_in_ring(&polygon.outer, point) && !on_boundary(&polygon.outer, point)? {
continue;
}
let mut in_hole = false;
for hole in &polygon.holes {
if point_in_ring(hole, point) && !on_boundary(hole, point)? {
in_hole = true;
break;
}
}
if !in_hole {
return Ok(true);
}
}
Ok(false)
}
fn on_boundary(ring: &Ring, point: Point2) -> Result<bool, RouteError> {
for (a, b) in ring_edges(ring) {
if side(a, b, point)? != Sign::Zero {
continue;
}
let within_x = point.x >= a.x.min(b.x) && point.x <= a.x.max(b.x);
let within_y = point.y >= a.y.min(b.y) && point.y <= a.y.max(b.y);
if within_x && within_y {
return Ok(true);
}
}
Ok(false)
}
fn point_in_ring(ring: &Ring, point: Point2) -> bool {
let mut inside = false;
let count = ring.points.len();
for index in 0..count {
let a = ring.points[index];
let b = ring.points[(index + 1) % count];
if (a.y > point.y) != (b.y > point.y) {
let crossing = (b.x - a.x) * (point.y - a.y) / (b.y - a.y) + a.x;
if point.x < crossing {
inside = !inside;
}
}
}
inside
}
fn dedup_points(points: &mut Vec<Point2>) {
let mut seen: Vec<Point2> = Vec::new();
points.retain(|point| {
if seen.iter().any(|other| other == point) {
false
} else {
seen.push(*point);
true
}
});
}
fn dijkstra(
adjacency: &[Vec<(usize, f64)>],
source: usize,
target: usize,
) -> Option<(f64, Vec<usize>)> {
let count = adjacency.len();
let mut distance = vec![f64::INFINITY; count];
let mut previous = vec![usize::MAX; count];
let mut settled = vec![false; count];
distance[source] = 0.0;
for _ in 0..count {
let mut current = None;
for index in 0..count {
if settled[index] || distance[index].is_infinite() {
continue;
}
if current.is_none_or(|best: usize| distance[index] < distance[best]) {
current = Some(index);
}
}
let Some(current) = current else { break };
if current == target {
break;
}
settled[current] = true;
for (neighbour, weight) in &adjacency[current] {
let candidate = distance[current] + weight;
if candidate < distance[*neighbour] {
distance[*neighbour] = candidate;
previous[*neighbour] = current;
}
}
}
if distance[target].is_infinite() {
return None;
}
let mut path = vec![target];
let mut node = target;
while node != source {
node = previous[node];
path.push(node);
}
path.reverse();
Some((distance[target], path))
}