#![forbid(unsafe_code)]
use axiolid_contracts::Sign;
use axiolid_core::Point2;
use axiolid_guarantees::Certified;
use axiolid_overlay::{Polygon, Ring};
use axiolid_predicates::orient2d;
mod forced;
mod graph;
mod map;
mod skeleton;
mod weighted;
use graph::Graph;
pub use forced::{
forced_walk, forced_walk_within, weighted_forced_walk, weighted_forced_walk_within, ForcedWalk,
WeightedForcedWalk,
};
pub use map::{
distance_map, distance_map_weighted, distance_map_within, distance_map_within_weighted,
farthest_point, farthest_point_within, DistanceMap, Farthest, FarthestError, LengthInterval,
MapError, Reach, MAX_CELLS,
};
pub use skeleton::{skeleton, NodeKind, Skeleton, SkeletonError, SkeletonNode, Wall};
pub use weighted::{
weighted_distance_map, weighted_distance_map_seeded, weighted_distance_map_seeded_within,
weighted_distance_map_within, weighted_farthest_point, weighted_farthest_point_within,
CostRegion, WeightedMap, WeightedReach, MAX_WEIGHTED_NODES,
};
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)]
#[non_exhaustive]
pub enum RouteError {
NonFinitePoint,
RingTooShort,
BarrierTooShort,
TooManyVertices {
supplied: usize,
budget: usize,
lower_bound: f64,
},
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> {
shortest_path_within(region, barriers, start, goal, MAX_VERTICES)
}
pub fn shortest_path_within(
region: &[Polygon],
barriers: &[Vec<Point2>],
start: Point2,
goal: Point2,
budget: usize,
) -> 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() > budget {
return Err(RouteError::TooManyVertices {
supplied: nodes.len(),
budget,
lower_bound: (goal - start).length(),
});
}
let obstacles = obstacle_segments(region, barriers);
let graph = Graph::build(&nodes, region, barriers, &obstacles)?;
let target = nodes.iter().position(|n| *n == goal).unwrap_or(1);
let sources: Vec<usize> = graph.states(0).collect();
let (distance, previous, reached) = graph::dijkstra(&graph.adjacency, &sources, |state| {
graph.node(state) == target
});
let Some(end) = reached else {
return Ok(Err(Unreachable::DisconnectedComponents));
};
let mut path = vec![end];
let mut state = end;
while previous[state] != usize::MAX {
state = previous[state];
path.push(state);
}
path.reverse();
let mut polyline: Vec<Point2> = path.into_iter().map(|s| nodes[graph.node(s)]).collect();
if polyline.len() == 1 {
polyline.push(goal);
}
Ok(Ok(Route {
polyline,
length: distance[end],
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);
}
validate_region(region, barriers)
}
fn validate_region(region: &[Polygon], barriers: &[Vec<Point2>]) -> Result<(), RouteError> {
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> {
if p.x.max(q.x) < r.x.min(s.x)
|| r.x.max(s.x) < p.x.min(q.x)
|| p.y.max(q.y) < r.y.min(s.y)
|| r.y.max(s.y) < p.y.min(q.y)
{
return Ok(false);
}
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 d = b - a;
let mut cuts: Vec<Point2> = Vec::new();
for (p, q) in obstacles {
for v in [*p, *q] {
if v != a && v != b && side(a, b, v)? == Sign::Zero && within(a, b, v) {
cuts.push(v);
}
}
}
cuts.sort_by(|u, v| (*u - a).dot(d).total_cmp(&(*v - a).dot(d)));
cuts.dedup();
let mut stops = Vec::with_capacity(cuts.len() + 2);
stops.push(a);
stops.extend(cuts);
stops.push(b);
for pair in stops.windows(2) {
let (from, to) = (pair[0], pair[1]);
let along = obstacles.iter().any(|&(p, q)| {
matches!(side(p, q, from), Ok(Sign::Zero))
&& matches!(side(p, q, to), Ok(Sign::Zero))
&& within(p, q, from)
&& within(p, q, to)
});
if along {
continue;
}
let midpoint = Point2::new(from.x * 0.5 + to.x * 0.5, from.y * 0.5 + to.y * 0.5);
if !contains(region, midpoint)? {
return Ok(false);
}
}
Ok(true)
}
fn within(a: Point2, b: Point2, v: Point2) -> bool {
v.x >= a.x.min(b.x) && v.x <= a.x.max(b.x) && v.y >= a.y.min(b.y) && v.y <= a.y.max(b.y)
}
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
}
});
}