use core::cmp::{Ordering, Reverse};
use std::collections::{BinaryHeap, HashMap};
use axiolid_contracts::Sign;
use axiolid_core::Point2;
use axiolid_overlay::Polygon;
use crate::graph::{region_left, Graph, Side};
use crate::map::{
free_triangles_in, in_polygon, in_triangle, meets_triangle, outside, segments_meet,
};
use crate::{
contains, crosses, dedup_points, obstacle_segments, ring_edges, side, validate_region, within,
Farthest, FarthestError, LengthInterval, MapError, Route, RouteError, Unreachable, MAX_CELLS,
};
const GRADING: u32 = 6;
pub const MAX_WEIGHTED_NODES: usize = 2048;
#[derive(Debug, Clone, PartialEq)]
pub struct CostRegion {
pub polygon: Polygon,
pub factor: f64,
}
impl CostRegion {
#[must_use]
pub fn new(polygon: Polygon, factor: f64) -> Self {
Self { polygon, factor }
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct WeightedReach {
pub target: usize,
pub cost: LengthInterval,
pub route: Route,
}
#[derive(Debug, Clone, Copy)]
enum Kind {
Vertex,
Interval {
a: Point2,
b: Point2,
p: Point2,
q: Point2,
line: u32,
piece: u32,
left: f64,
right: f64,
along: f64,
a_vertex: bool,
b_vertex: bool,
},
}
const NO_LINE: u32 = u32::MAX;
#[derive(Debug, Clone)]
pub struct WeightedMap {
region: Vec<Polygon>,
walls: Vec<(Point2, Point2)>,
obstacles: Vec<(Point2, Point2)>,
weights: Weights,
nodes: Vec<Point2>,
kinds: Vec<Kind>,
lines: Vec<Vec<u32>>,
graph: Graph,
upper: Vec<f64>,
next: Vec<usize>,
target: Vec<usize>,
lower: Vec<Vec<(Tag, f64)>>,
sites: Vec<Point2>,
seeds: Vec<f64>,
costs: Vec<CostRegion>,
scale: f64,
}
pub fn weighted_distance_map(
region: &[Polygon],
barriers: &[Vec<Point2>],
targets: &[Point2],
costs: &[CostRegion],
spacing: f64,
) -> Result<WeightedMap, MapError> {
weighted_distance_map_within(
region,
barriers,
targets,
costs,
spacing,
MAX_WEIGHTED_NODES,
)
}
pub fn weighted_distance_map_within(
region: &[Polygon],
barriers: &[Vec<Point2>],
targets: &[Point2],
costs: &[CostRegion],
spacing: f64,
budget: usize,
) -> Result<WeightedMap, MapError> {
let seeded: Vec<(Point2, f64)> = targets.iter().map(|t| (*t, 0.0)).collect();
weighted_distance_map_seeded_within(region, barriers, &seeded, costs, spacing, budget)
}
pub fn weighted_distance_map_seeded(
region: &[Polygon],
barriers: &[Vec<Point2>],
targets: &[(Point2, f64)],
costs: &[CostRegion],
spacing: f64,
) -> Result<WeightedMap, MapError> {
weighted_distance_map_seeded_within(
region,
barriers,
targets,
costs,
spacing,
MAX_WEIGHTED_NODES,
)
}
pub fn weighted_distance_map_seeded_within(
region: &[Polygon],
barriers: &[Vec<Point2>],
seeded: &[(Point2, f64)],
costs: &[CostRegion],
spacing: f64,
budget: usize,
) -> Result<WeightedMap, MapError> {
validate_region(region, barriers)?;
if seeded.is_empty() {
return Err(MapError::NoTargets);
}
let targets: Vec<Point2> = seeded.iter().map(|(t, _)| *t).collect();
let seeds: Vec<f64> = seeded.iter().map(|(_, w)| *w).collect();
if !targets.iter().all(|t| t.is_finite()) {
return Err(RouteError::NonFinitePoint.into());
}
if let Some(index) = seeds.iter().position(|w| !(w.is_finite() && *w >= 0.0)) {
return Err(MapError::InvalidWeight { index });
}
if !(spacing.is_finite() && spacing > 0.0) {
return Err(MapError::InvalidSpacing);
}
for (index, cost) in costs.iter().enumerate() {
if !(cost.factor.is_finite() && cost.factor >= 1.0) {
return Err(MapError::InvalidFactor { index });
}
}
validate_region(
&costs.iter().map(|c| c.polygon.clone()).collect::<Vec<_>>(),
&[],
)?;
let reach = touch_reach(region);
let costs = &touch_walls(costs, region, reach)?;
let polygons: Vec<Polygon> = costs.iter().map(|c| c.polygon.clone()).collect();
validate_region(&polygons, &[])?;
for (index, t) in targets.iter().enumerate() {
if !contains(region, *t)? {
return Err(MapError::TargetOutside { index });
}
}
let obstacles = obstacle_segments(region, barriers);
let walls: Vec<(Point2, Point2)> = barriers
.iter()
.flat_map(|b| b.windows(2).map(|w| (w[0], w[1])))
.collect();
let mut nodes = targets.clone();
for polygon in region.iter().chain(polygons.iter()) {
for ring in core::iter::once(&polygon.outer).chain(polygon.holes.iter()) {
nodes.extend(ring.points.iter().copied());
}
}
for barrier in barriers {
nodes.extend(barrier.iter().copied());
}
dedup_points(&mut nodes);
let weights = Weights::new(costs, &nodes, region)?;
weights.check_crossings(&obstacles, &walls, reach)?;
let mut kinds = vec![Kind::Vertex; nodes.len()];
let mut spans: Vec<(Point2, Point2, u32)> = Vec::new();
for piece in &weights.pieces {
let (p, q) = if (piece.p.x, piece.p.y) <= (piece.q.x, piece.q.y) {
(piece.p, piece.q)
} else {
(piece.q, piece.p)
};
if !spans.iter().any(|(u, v, _)| *u == p && *v == q) {
spans.push((p, q, piece.line));
}
}
for (piece, (p, q, line)) in spans.into_iter().enumerate() {
if weights.beyond_wall(p, q, reach)? {
continue;
}
let count = ((q - p).length() / spacing).ceil().max(1.0) as usize;
let mut cuts: Vec<f64> = (0..=count).map(|k| k as f64 / count as f64).collect();
let first = 1.0 / count as f64;
for level in 1..=GRADING {
let t = first / f64::from(1u32 << level);
cuts.push(t);
cuts.push(1.0 - t);
}
cuts.sort_by(f64::total_cmp);
cuts.dedup();
let count = cuts.len() - 1;
if nodes.len() + count > budget {
return Err(RouteError::TooManyVertices {
supplied: nodes.len() + count,
budget,
lower_bound: 0.0,
}
.into());
}
let at = |k: usize| {
if k == 0 {
p
} else if k == count {
q
} else {
let t = cuts[k];
Point2::new(p.x + (q.x - p.x) * t, p.y + (q.y - p.y) * t)
}
};
for k in 0..count {
let (a, b) = (at(k), at(k + 1));
nodes.push(Point2::new(0.5 * a.x + 0.5 * b.x, 0.5 * a.y + 0.5 * b.y));
let (left, right) = weights.sides(p, q, a, b)?;
let along = left.min(right);
kinds.push(Kind::Interval {
a,
b,
p,
q,
line,
piece: piece as u32,
left,
right,
along,
a_vertex: k == 0,
b_vertex: k + 1 == count,
});
}
}
if nodes.len() > budget {
return Err(RouteError::TooManyVertices {
supplied: nodes.len(),
budget,
lower_bound: 0.0,
}
.into());
}
let lines: Vec<Vec<u32>> = nodes
.iter()
.zip(&kinds)
.map(|(v, kind)| match kind {
Kind::Interval { line, .. } => Ok(vec![*line]),
Kind::Vertex => weights.lines_through(*v),
})
.collect::<Result<_, RouteError>>()?;
let graph = Graph::build(&nodes, region, barriers, &obstacles)?;
let scale = nodes
.iter()
.fold(1.0f64, |m, p| m.max(p.x.abs()).max(p.y.abs()));
let mut map = WeightedMap {
region: region.to_vec(),
walls,
obstacles,
weights,
nodes,
kinds,
lines,
graph,
upper: Vec::new(),
next: Vec::new(),
target: Vec::new(),
lower: Vec::new(),
sites: targets.clone(),
seeds: seeds.clone(),
costs: costs.to_vec(),
scale,
};
let mut sources = Vec::new();
let mut seed = vec![usize::MAX; map.graph.adjacency.len()];
for (i, node) in map.nodes.iter().enumerate() {
let lightest = (0..targets.len())
.filter(|&t| targets[t] == *node)
.min_by(|&a, &b| seeds[a].total_cmp(&seeds[b]).then(a.cmp(&b)));
if let Some(t) = lightest {
for state in map.graph.states(i) {
sources.push((state, seeds[t]));
seed[state] = t;
}
}
}
map.upper_bounds(&sources, &seed)?;
map.lower_bounds(&sources)?;
Ok(map)
}
impl WeightedMap {
#[must_use]
pub fn graph_vertices(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub fn targets(&self) -> usize {
self.sites.len()
}
pub fn nearest(&self, point: Point2) -> Result<Result<WeightedReach, Unreachable>, RouteError> {
if !point.is_finite() {
return Err(RouteError::NonFinitePoint);
}
if !contains(&self.region, point)? {
return Ok(Err(Unreachable::StartOutside));
}
let Some((upper, first)) = self.upper_at(point)? else {
return Ok(Err(Unreachable::DisconnectedComponents));
};
let lower = self.lower_at(point, upper)?.min(upper);
let mut polyline = vec![point];
let mut state = first;
loop {
let at = self.nodes[self.graph.node(state)];
if polyline.last() != Some(&at) {
polyline.push(at);
}
if self.next[state] == usize::MAX {
break;
}
state = self.next[state];
}
let length = polyline.windows(2).map(|w| (w[1] - w[0]).length()).sum();
Ok(Ok(WeightedReach {
target: self.target[first],
cost: LengthInterval { lower, upper },
route: Route {
polyline,
length,
graph_vertices: self.nodes.len(),
},
}))
}
pub(crate) fn region(&self) -> &[Polygon] {
&self.region
}
pub(crate) fn obstacles(&self) -> &[(Point2, Point2)] {
&self.obstacles
}
pub(crate) fn walls(&self) -> &[(Point2, Point2)] {
&self.walls
}
pub(crate) fn seeded(&self) -> impl Iterator<Item = (Point2, f64)> + '_ {
self.sites.iter().copied().zip(self.seeds.iter().copied())
}
pub(crate) fn same_space(&self, other: &Self) -> bool {
self.region == other.region && self.walls == other.walls && self.costs == other.costs
}
pub(crate) fn steepest(&self, t: &[Point2; 3]) -> Result<f64, RouteError> {
self.weights.steepest(t)
}
pub(crate) fn inside_factor(&self, t: &[Point2; 3]) -> Result<f64, RouteError> {
for piece in &self.weights.pieces {
if meets_triangle(piece.p, piece.q, t)? {
return Ok(1.0);
}
}
let mut best = 1.0f64;
for (polygon, &factor) in self.weights.polygons.iter().zip(&self.weights.factors) {
if factor > best && in_polygon(polygon, t[0])? {
best = factor;
}
}
Ok(best)
}
pub(crate) fn spans(&self) -> Vec<((Point2, Point2), f64)> {
(0..self.nodes.len())
.filter_map(|i| {
let least = self
.graph
.states(i)
.flat_map(|s| self.lower[s].iter().map(|(_, d)| *d))
.fold(f64::INFINITY, f64::min);
least.is_finite().then(|| (self.span(i), least))
})
.collect()
}
pub(crate) fn bracket(&self, point: Point2) -> Result<Option<(f64, f64)>, RouteError> {
let Some((upper, _)) = self.upper_at(point)? else {
return Ok(None);
};
Ok(Some((self.lower_at(point, upper)?.min(upper), upper)))
}
fn upper_bounds(&mut self, sources: &[(usize, f64)], seed: &[usize]) -> Result<(), RouteError> {
let states = self.graph.adjacency.len();
let mut cost: HashMap<(usize, usize), f64> = HashMap::new();
let mut adjacency: Vec<Vec<(usize, f64)>> = vec![Vec::new(); states];
for (u, edges) in self.graph.adjacency.iter().enumerate() {
let i = self.graph.node(u);
for &(w, _) in edges {
let j = self.graph.node(w);
let key = (i.min(j), i.max(j));
let c = match cost.get(&key) {
Some(c) => *c,
None => {
let c = self
.weights
.segment(self.nodes[i], self.nodes[j], self.scale)?
.1;
cost.insert(key, c);
c
}
};
adjacency[u].push((w, c));
}
}
let (distance, previous) = dijkstra(&adjacency, sources);
let mut target = seed.to_vec();
for (state, t) in target.iter_mut().enumerate() {
let mut at = state;
let mut steps = 0;
while previous[at] != usize::MAX && steps <= seed.len() {
at = previous[at];
steps += 1;
}
*t = seed[at];
}
self.upper = distance;
self.next = previous;
self.target = target;
Ok(())
}
fn hop_states(&self, i: usize, b1: Point2, b2: Point2) -> Result<Vec<usize>, RouteError> {
match self.kinds[i] {
Kind::Vertex => self.graph.cone_states(i, self.nodes[i], b1, b2),
Kind::Interval { .. } => Ok(self.graph.states(i).collect()),
}
}
fn span(&self, i: usize) -> (Point2, Point2) {
match self.kinds[i] {
Kind::Vertex => (self.nodes[i], self.nodes[i]),
Kind::Interval { a, b, .. } => (a, b),
}
}
fn shared_line(&self, i: usize, j: usize) -> u32 {
self.lines[i]
.iter()
.find(|l| self.lines[j].contains(l))
.copied()
.unwrap_or(NO_LINE)
}
fn hop(
&self,
(a1, a2): (Point2, Point2),
(b1, b2): (Point2, Point2),
ka: Kind,
kb: Kind,
) -> Result<Option<f64>, RouteError> {
let (ea, eb) = (vertex_ends(ka), vertex_ends(kb));
let blocks = |p: Point2, q: Point2| -> Result<bool, RouteError> {
for (u, skip_u) in [(a1, ea.0), (a2, ea.1)] {
for (v, skip_v) in [(b1, eb.0), (b2, eb.1)] {
if !crosses(u, v, p, q)? && !((skip_u || skip_v) && through_end(u, v, p, q)?) {
return Ok(false);
}
}
}
Ok(true)
};
for &(p, q) in &self.obstacles {
if blocks(p, q)? {
return Ok(None);
}
}
if convex_hull(&[a1, a2, b1, b2])?.is_none() {
let Some((u, v)) = gap((a1, a2), (b1, b2)) else {
return Ok(Some(0.0));
};
return Ok(Some(self.weights.segment(u, v, self.scale)?.0));
}
let own = |kind: Kind| match kind {
Kind::Interval { line, .. } => line,
Kind::Vertex => NO_LINE,
};
let (la, lb) = (own(ka), own(kb));
for piece in &self.weights.pieces {
if piece.line != la && piece.line != lb && blocks(piece.p, piece.q)? {
return Ok(None);
}
}
let factor = self
.weights
.hull_factor(&[a1, a2, b1, b2])?
.max(leaving(ka, (b1, b2), eb)?)
.max(leaving(kb, (a1, a2), ea)?);
let d = span_distance((a1, a2), (b1, b2))?;
Ok(Some(round_down(factor * d, self.scale)))
}
fn side_of(&self, k: usize, n: usize) -> Result<u8, RouteError> {
let (u, v) = self.span(n);
let ends = match self.kinds[n] {
Kind::Interval {
a_vertex, b_vertex, ..
} => (a_vertex, b_vertex),
Kind::Vertex => (false, false),
};
self.side_of_span(k, (u, v), ends)
}
fn side_of_span(
&self,
k: usize,
(u, v): (Point2, Point2),
(skip_u, skip_v): (bool, bool),
) -> Result<u8, RouteError> {
let Kind::Interval { p, q, .. } = self.kinds[k] else {
return Ok(NONE);
};
let (su, sv) = (side(p, q, u)?, side(p, q, v)?);
let su = if su == Sign::Zero && skip_u { sv } else { su };
let sv = if sv == Sign::Zero && skip_v { su } else { sv };
Ok(match (su, sv) {
(Sign::Positive, Sign::Positive) => LEFT,
(Sign::Negative, Sign::Negative) => RIGHT,
_ => NONE,
})
}
fn edge(&self, n: usize, k: usize, from: usize, weight: f64) -> Result<Edge, RouteError> {
let line = self.shared_line(n, k);
let (leave, arrive) = if line == NO_LINE {
(self.side_of(n, k)?, self.side_of(k, n)?)
} else {
(NONE, NONE)
};
let same_piece = match (self.kinds[n], self.kinds[k]) {
(Kind::Interval { piece: pn, .. }, Kind::Interval { piece: pk, .. }) => pn == pk,
_ => false,
};
Ok(Edge {
from,
weight,
line,
leave,
arrive,
same_piece,
})
}
fn lower_bounds(&mut self, sources: &[(usize, f64)]) -> Result<(), RouteError> {
let states = self.graph.adjacency.len();
let mut incoming: Vec<Vec<Edge>> = vec![Vec::new(); states];
let is_vertex = |i: usize, kinds: &[Kind]| matches!(kinds[i], Kind::Vertex);
let mut cost: HashMap<(usize, usize), f64> = HashMap::new();
for (u, edges) in self.graph.adjacency.iter().enumerate() {
let i = self.graph.node(u);
if !is_vertex(i, &self.kinds) {
continue;
}
for &(w, _) in edges {
let j = self.graph.node(w);
if !is_vertex(j, &self.kinds) {
continue;
}
let key = (i.min(j), i.max(j));
let c = match cost.get(&key) {
Some(c) => *c,
None => {
let c = self
.weights
.segment(self.nodes[i], self.nodes[j], self.scale)?
.0;
cost.insert(key, c);
c
}
};
incoming[w].push(self.edge(i, j, u, c)?);
}
}
let n = self.nodes.len();
for i in 0..n {
for j in i + 1..n {
if is_vertex(i, &self.kinds) && is_vertex(j, &self.kinds) {
continue;
}
let (si, sj) = (self.span(i), self.span(j));
let Some(c) = self.hop(si, sj, self.kinds[i], self.kinds[j])? else {
continue;
};
let from = self.hop_states(i, sj.0, sj.1)?;
let to = self.hop_states(j, si.0, si.1)?;
for &u in &from {
for &w in &to {
incoming[w].push(self.edge(i, j, u, c)?);
incoming[u].push(self.edge(j, i, w, c)?);
}
}
}
}
let graph = &self.graph;
let kinds = &self.kinds;
self.lower = tagged_dijkstra(&incoming, sources, |s| kinds[graph.node(s)]);
Ok(())
}
fn upper_at(&self, point: Point2) -> Result<Option<(f64, usize)>, RouteError> {
let mut best: Option<(f64, usize)> = None;
let offer = |value: f64, state: usize, best: &mut Option<(f64, usize)>| {
if best.is_none_or(|(b, s)| value < b || (value == b && state < s)) {
*best = Some((value, state));
}
};
for (i, node) in self.nodes.iter().enumerate() {
let nearest = self
.graph
.states(i)
.map(|s| self.upper[s])
.fold(f64::INFINITY, f64::min);
if nearest.is_infinite() {
continue;
}
if *node == point {
for s in self.graph.states(i) {
offer(self.upper[s], s, &mut best);
}
continue;
}
if best.is_some_and(|(b, _)| (*node - point).length() + nearest > b) {
continue;
}
let ok = crate::graph::sides(
point,
*node,
&self.region,
&self.obstacles,
&self.nodes,
&self.graph.stars,
&self.graph.rayed,
)?;
if ok == [false, false] {
continue;
}
let leg = self.weights.segment(point, *node, self.scale)?.1;
for (k, on) in [Side::Left, Side::Right].into_iter().enumerate() {
if !ok[k] {
continue;
}
if let Some(s) = self.graph.arrival(i, point, on)? {
if self.upper[s].is_finite() {
offer(leg + self.upper[s], s, &mut best);
}
}
}
}
Ok(best)
}
fn lower_at(&self, point: Point2, ceiling: f64) -> Result<f64, RouteError> {
let mut best = ceiling;
let point_lines = self.weights.lines_through(point)?;
for (i, node) in self.nodes.iter().enumerate() {
let least = self
.graph
.states(i)
.flat_map(|s| self.lower[s].iter().map(|(_, d)| *d))
.fold(f64::INFINITY, f64::min);
let kind = self.kinds[i];
if least.is_infinite() {
continue;
}
let line = self.lines[i]
.iter()
.find(|l| point_lines.contains(l))
.copied()
.unwrap_or(NO_LINE);
let first = Edge {
from: usize::MAX,
weight: 0.0,
line,
leave: NONE,
arrive: if line == NO_LINE {
self.side_of_span(i, (point, point), (false, false))?
} else {
NONE
},
same_piece: false,
};
let from = |states: &mut dyn Iterator<Item = usize>| -> f64 {
states
.flat_map(|s| self.lower[s].iter())
.filter(|(tag, _)| allowed(kind, *tag, &first))
.map(|(_, d)| *d)
.fold(f64::INFINITY, f64::min)
};
if *node == point {
let any = self
.graph
.states(i)
.flat_map(|s| self.lower[s].iter().map(|(_, d)| *d))
.fold(f64::INFINITY, f64::min);
best = best.min(any);
continue;
}
let (a, b) = self.span(i);
if span_distance((point, point), (a, b))? + least >= best {
continue;
}
match self.kinds[i] {
Kind::Vertex => {
let ok = crate::graph::sides(
point,
*node,
&self.region,
&self.obstacles,
&self.nodes,
&self.graph.stars,
&self.graph.rayed,
)?;
if ok == [false, false] {
continue;
}
let leg = self.weights.segment(point, *node, self.scale)?.0;
for (k, on) in [Side::Left, Side::Right].into_iter().enumerate() {
if !ok[k] {
continue;
}
if let Some(s) = self.graph.arrival(i, point, on)? {
best = best.min(leg + from(&mut core::iter::once(s)));
}
}
}
Kind::Interval { .. } => {
let Some(leg) =
self.hop((point, point), (a, b), Kind::Vertex, self.kinds[i])?
else {
continue;
};
best = best.min(leg + from(&mut self.graph.states(i)));
}
}
}
Ok(best)
}
}
fn dijkstra(adjacency: &[Vec<(usize, f64)>], sources: &[(usize, f64)]) -> (Vec<f64>, Vec<usize>) {
let n = adjacency.len();
let mut distance = vec![f64::INFINITY; n];
let mut previous = vec![usize::MAX; n];
let mut heap = BinaryHeap::new();
for &(s, start) in sources {
if start < distance[s] {
distance[s] = start;
heap.push(Reverse((Ordered(start), s)));
}
}
while let Some(Reverse((Ordered(d), u))) = heap.pop() {
if d > distance[u] {
continue;
}
for &(w, c) in &adjacency[u] {
let candidate = d + c;
if candidate < distance[w] || (candidate == distance[w] && u < previous[w]) {
if candidate < distance[w] {
heap.push(Reverse((Ordered(candidate), w)));
}
distance[w] = candidate;
previous[w] = u;
}
}
}
(distance, previous)
}
const NONE: u8 = 0;
const LEFT: u8 = 1;
const RIGHT: u8 = 2;
const ALONG: u8 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
struct Tag {
line: u32,
side: u8,
}
#[derive(Debug, Clone, Copy)]
struct Edge {
from: usize,
weight: f64,
line: u32,
leave: u8,
arrive: u8,
same_piece: bool,
}
fn allowed(kind: Kind, tag: Tag, edge: &Edge) -> bool {
if edge.line != NO_LINE && tag.line == edge.line {
return false;
}
if let Kind::Interval {
left, right, along, ..
} = kind
{
if edge.line == NO_LINE && edge.arrive != NONE {
let factor = if edge.arrive == LEFT { left } else { right };
if tag.side == edge.arrive {
return false;
}
if tag.side > ALONG && tag.side - ALONG == edge.arrive && along >= factor {
return false;
}
}
}
true
}
fn tag_before(source: Kind, tag: Tag, edge: &Edge) -> Tag {
let side = match source {
Kind::Vertex => NONE,
Kind::Interval { .. } if edge.line != NO_LINE => {
if edge.same_piece && (tag.side == LEFT || tag.side == RIGHT) {
ALONG + tag.side
} else {
ALONG
}
}
Kind::Interval { .. } => edge.leave,
};
Tag {
line: edge.line,
side,
}
}
fn tagged_dijkstra(
incoming: &[Vec<Edge>],
sources: &[(usize, f64)],
kind: impl Fn(usize) -> Kind,
) -> Vec<Vec<(Tag, f64)>> {
let mut settled: Vec<Vec<(Tag, f64)>> = vec![Vec::new(); incoming.len()];
let mut best: HashMap<(usize, Tag), f64> = HashMap::new();
let mut heap = BinaryHeap::new();
let start = Tag {
line: NO_LINE,
side: NONE,
};
for &(s, weight) in sources {
if best.get(&(s, start)).is_none_or(|b| weight < *b) {
best.insert((s, start), weight);
heap.push(Reverse((Ordered(weight), s, start)));
}
}
while let Some(Reverse((Ordered(d), k, tag))) = heap.pop() {
if settled[k].iter().any(|(t, _)| *t == tag) {
continue;
}
settled[k].push((tag, d));
let here = kind(k);
for edge in &incoming[k] {
if !allowed(here, tag, edge) {
continue;
}
let before = tag_before(kind(edge.from), tag, edge);
let candidate = d + edge.weight;
let key = (edge.from, before);
if best.get(&key).is_none_or(|b| candidate < *b) {
best.insert(key, candidate);
heap.push(Reverse((Ordered(candidate), edge.from, before)));
}
}
}
settled
}
#[derive(Debug, Clone, Copy, PartialEq)]
struct Ordered(f64);
impl Eq for Ordered {}
impl PartialOrd for Ordered {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Ordered {
fn cmp(&self, other: &Self) -> Ordering {
self.0.total_cmp(&other.0)
}
}
fn round_down(value: f64, scale: f64) -> f64 {
(value * (1.0 - 16.0 * f64::EPSILON) - 16.0 * f64::EPSILON * scale).max(0.0)
}
fn round_up(value: f64, scale: f64) -> f64 {
value * (1.0 + 16.0 * f64::EPSILON) + 16.0 * f64::EPSILON * scale
}
fn leaving(
kind: Kind,
(o1, o2): (Point2, Point2),
(skip1, skip2): (bool, bool),
) -> Result<f64, RouteError> {
let Kind::Interval {
p, q, left, right, ..
} = kind
else {
return Ok(1.0);
};
let (s1, s2) = (side(p, q, o1)?, side(p, q, o2)?);
let s1 = if s1 == Sign::Zero && skip1 { s2 } else { s1 };
let s2 = if s2 == Sign::Zero && skip2 { s1 } else { s2 };
Ok(match (s1, s2) {
(Sign::Positive, Sign::Positive) => left,
(Sign::Negative, Sign::Negative) => right,
_ => left.min(right),
})
}
fn vertex_ends(kind: Kind) -> (bool, bool) {
match kind {
Kind::Interval {
a_vertex, b_vertex, ..
} => (a_vertex, b_vertex),
Kind::Vertex => (false, false),
}
}
fn through_end(u: Point2, v: Point2, p: Point2, q: Point2) -> Result<bool, RouteError> {
if side(p, q, u)? == Sign::Zero || side(p, q, v)? == Sign::Zero {
return Ok(false);
}
for e in [p, q] {
if within(u, v, e) && side(u, v, e)? == Sign::Zero {
return Ok(true);
}
}
Ok(false)
}
fn gap((a1, a2): (Point2, Point2), (b1, b2): (Point2, Point2)) -> Option<(Point2, Point2)> {
let d = if a1 != a2 { a2 - a1 } else { b2 - b1 };
let key = |p: Point2| (p - a1).dot(d);
let (alo, ahi) = if key(a1) <= key(a2) {
(a1, a2)
} else {
(a2, a1)
};
let (blo, bhi) = if key(b1) <= key(b2) {
(b1, b2)
} else {
(b2, b1)
};
if key(ahi) < key(blo) {
Some((ahi, blo))
} else if key(bhi) < key(alo) {
Some((bhi, alo))
} else {
None
}
}
pub(crate) fn span_distance(
(a1, a2): (Point2, Point2),
(b1, b2): (Point2, Point2),
) -> Result<f64, RouteError> {
if segments_meet(a1, a2, b1, b2)? {
return Ok(0.0);
}
Ok(point_segment(a1, b1, b2)
.min(point_segment(a2, b1, b2))
.min(point_segment(b1, a1, a2))
.min(point_segment(b2, a1, a2)))
}
fn point_segment(v: Point2, a: Point2, b: Point2) -> f64 {
let d = b - a;
let length2 = d.dot(d);
let s = if length2 > 0.0 {
((v - a).dot(d) / length2).clamp(0.0, 1.0)
} else {
0.0
};
(v - (a + d * s)).length()
}
#[derive(Debug, Clone, Copy)]
struct Piece {
p: Point2,
q: Point2,
polygon: usize,
inside_left: bool,
line: u32,
}
#[derive(Debug, Clone)]
struct Weights {
polygons: Vec<Polygon>,
factors: Vec<f64>,
pieces: Vec<Piece>,
lines: Vec<(Point2, Point2)>,
walls: Vec<(Point2, Point2, bool)>,
greatest: f64,
}
impl Weights {
fn new(costs: &[CostRegion], cuts: &[Point2], region: &[Polygon]) -> Result<Self, RouteError> {
let mut walls = Vec::new();
for polygon in region {
for (hole, ring) in core::iter::once((false, &polygon.outer))
.chain(polygon.holes.iter().map(|h| (true, h)))
{
let left = region_left(ring, hole)?;
for (p, q) in ring_edges(ring) {
walls.push((p, q, left));
}
}
}
let polygons: Vec<Polygon> = costs.iter().map(|c| c.polygon.clone()).collect();
let factors: Vec<f64> = costs.iter().map(|c| c.factor).collect();
let mut vertices: Vec<Point2> = cuts.to_vec();
for polygon in &polygons {
for ring in core::iter::once(&polygon.outer).chain(polygon.holes.iter()) {
vertices.extend(ring.points.iter().copied());
}
}
dedup_points(&mut vertices);
let mut pieces = Vec::new();
let mut lines: Vec<(Point2, Point2)> = Vec::new();
for (index, polygon) in polygons.iter().enumerate() {
for (hole, ring) in core::iter::once((false, &polygon.outer))
.chain(polygon.holes.iter().map(|h| (true, h)))
{
let left = region_left(ring, hole)?;
for (p, q) in ring_edges(ring) {
if p == q {
continue;
}
let d = q - p;
let mut cuts = vec![p, q];
for &v in &vertices {
if v != p && v != q && within(p, q, v) && side(p, q, v)? == Sign::Zero {
cuts.push(v);
}
}
cuts.sort_by(|u, v| (*u - p).dot(d).total_cmp(&(*v - p).dot(d)));
let mut line = NO_LINE;
for (k, &(r, s)) in lines.iter().enumerate() {
if side(r, s, p)? == Sign::Zero && side(r, s, q)? == Sign::Zero {
line = k as u32;
break;
}
}
if line == NO_LINE {
line = lines.len() as u32;
lines.push((p, q));
}
for pair in cuts.windows(2) {
if pair[0] != pair[1] {
pieces.push(Piece {
p: pair[0],
q: pair[1],
polygon: index,
inside_left: left,
line,
});
}
}
}
}
}
let greatest = factors.iter().copied().fold(1.0, f64::max);
Ok(Self {
polygons,
factors,
pieces,
lines,
walls,
greatest,
})
}
fn check_crossings(
&self,
obstacles: &[(Point2, Point2)],
walls: &[(Point2, Point2)],
reach: f64,
) -> Result<(), MapError> {
for piece in &self.pieces {
let index = piece.polygon;
for &(r, s) in obstacles {
if crosses(piece.p, piece.q, r, s)? {
let region_edge = self
.walls
.iter()
.any(|&(p, q, _)| (p, q) == (r, s) || (p, q) == (s, r));
let near = point_segment(piece.p, r, s) <= reach
|| point_segment(piece.q, r, s) <= reach
|| point_segment(r, piece.p, piece.q) <= reach
|| point_segment(s, piece.p, piece.q) <= reach;
if region_edge && near {
continue;
}
return Err(MapError::CostCrossing { index });
}
}
for &(r, s) in walls {
let collinear =
side(r, s, piece.p)? == Sign::Zero && side(r, s, piece.q)? == Sign::Zero;
if collinear && segments_meet(piece.p, piece.q, r, s)? {
let overlap = [piece.p, piece.q, r, s]
.iter()
.filter(|v| within(r, s, **v) && within(piece.p, piece.q, **v))
.count();
if overlap >= 2 {
return Err(MapError::CostCrossing { index });
}
}
}
for other in &self.pieces {
if crosses(piece.p, piece.q, other.p, other.q)? {
return Err(MapError::CostCrossing { index });
}
}
}
Ok(())
}
fn sides(&self, p: Point2, q: Point2, a: Point2, b: Point2) -> Result<(f64, f64), RouteError> {
let m = Point2::new(0.5 * a.x + 0.5 * b.x, 0.5 * a.y + 0.5 * b.y);
let d = q - p;
let (a, b) = (p, q);
let (mut left, mut right) = (1.0f64, 1.0f64);
for (index, polygon) in self.polygons.iter().enumerate() {
let factor = self.factors[index];
let mut on = false;
for piece in self.pieces.iter().filter(|p| p.polygon == index) {
if side(piece.p, piece.q, a)? == Sign::Zero
&& side(piece.p, piece.q, b)? == Sign::Zero
&& within(piece.p, piece.q, m)
{
on = true;
let same = (piece.q - piece.p).dot(d) > 0.0;
if piece.inside_left == same {
left = left.max(factor);
} else {
right = right.max(factor);
}
}
}
if !on && in_polygon(polygon, m)? {
left = left.max(factor);
right = right.max(factor);
}
}
Ok((left, right))
}
fn beyond_wall(&self, p: Point2, q: Point2, reach: f64) -> Result<bool, RouteError> {
for &(r, s, left) in &self.walls {
let free = if left { Sign::Positive } else { Sign::Negative };
if point_segment(p, r, s) <= reach
&& point_segment(q, r, s) <= reach
&& side(r, s, p)? != free
&& side(r, s, q)? != free
{
return Ok(true);
}
}
Ok(false)
}
fn lines_through(&self, v: Point2) -> Result<Vec<u32>, RouteError> {
let mut out = Vec::new();
for (k, &(p, q)) in self.lines.iter().enumerate() {
if side(p, q, v)? == Sign::Zero {
out.push(k as u32);
}
}
Ok(out)
}
fn hull_factor(&self, corners: &[Point2]) -> Result<f64, RouteError> {
let Some(hull) = convex_hull(corners)? else {
return Ok(1.0);
};
let mut best = 1.0f64;
for (polygon, &factor) in self.polygons.iter().zip(&self.factors) {
if factor <= best {
continue;
}
if holds(polygon, &hull)? {
best = factor;
}
}
Ok(best)
}
fn steepest(&self, t: &[Point2; 3]) -> Result<f64, RouteError> {
let mut best = 1.0f64;
for (polygon, &factor) in self.polygons.iter().zip(&self.factors) {
if factor <= best {
continue;
}
let meets = t.iter().try_fold(false, |m, c| {
Ok::<_, RouteError>(m || in_polygon(polygon, *c)?)
})? || core::iter::once(&polygon.outer)
.chain(polygon.holes.iter())
.flat_map(ring_edges)
.try_fold(false, |m, (p, q)| {
Ok::<_, RouteError>(m || meets_triangle(p, q, t)?)
})?;
if meets {
best = factor;
}
}
Ok(best)
}
fn segment(&self, a: Point2, b: Point2, scale: f64) -> Result<(f64, f64), RouteError> {
let length = (b - a).length();
if length == 0.0 {
return Ok((0.0, 0.0));
}
if self.polygons.is_empty() {
return Ok((round_down(length, scale), round_up(length, scale)));
}
let d = b - a;
let param = |v: Point2| (v - a).dot(d) / d.dot(d);
let mut breaks = vec![0.0, 1.0];
let mut along: Vec<(f64, f64, usize, bool)> = Vec::new();
let mut near: Vec<(Point2, Point2, usize, bool)> = Vec::new();
let mut walled: Vec<(f64, f64, bool)> = Vec::new();
let mut near_walls: Vec<(Point2, Point2, bool)> = Vec::new();
for &(p, q, left) in &self.walls {
if p != q && side(a, b, p)? == Sign::Zero && side(a, b, q)? == Sign::Zero {
let (tp, tq) = (param(p), param(q));
let (lo, hi) = (tp.min(tq).max(0.0), tp.max(tq).min(1.0));
if hi > lo {
walled.push((lo, hi, left == ((q - p).dot(d) > 0.0)));
breaks.extend([lo, hi]);
}
} else if p != q {
near_walls.push((p, q, left));
}
}
for piece in &self.pieces {
let (sp, sq) = (side(a, b, piece.p)?, side(a, b, piece.q)?);
if sp == Sign::Zero && sq == Sign::Zero {
let (tp, tq) = (param(piece.p), param(piece.q));
let (lo, hi) = (tp.min(tq).max(0.0), tp.max(tq).min(1.0));
if hi > lo {
let same = (piece.q - piece.p).dot(d) > 0.0;
along.push((lo, hi, piece.polygon, piece.inside_left == same));
breaks.extend([lo, hi]);
}
continue;
}
near.push((piece.p, piece.q, piece.polygon, piece.inside_left));
let (sa, sb) = (side(piece.p, piece.q, a)?, side(piece.p, piece.q, b)?);
if sp != sq && sa != sb {
let e = piece.q - piece.p;
let denom = d.perp_dot(e);
if denom != 0.0 {
let t = (piece.p - a).perp_dot(e) / denom;
if t > 0.0 && t < 1.0 {
breaks.push(t);
}
}
}
}
breaks.sort_by(f64::total_cmp);
breaks.dedup();
let (mut lower, mut upper) = (0.0, 0.0);
let tiny = 64.0 * f64::EPSILON * scale;
let mut hugged = false;
for pair in breaks.windows(2) {
let (t0, t1) = (pair[0], pair[1]);
let stretch = (t1 - t0) * length;
if stretch <= 0.0 {
continue;
}
let tm = 0.5 * t0 + 0.5 * t1;
let at = |t: f64| Point2::new(a.x + d.x * t, a.y + d.y * t);
let m = at(tm);
let mut on: Vec<(usize, bool)> = along
.iter()
.filter(|(lo, hi, ..)| *lo <= tm && tm <= *hi)
.map(|(_, _, p, l)| (*p, *l))
.collect();
let mut free: Vec<bool> = walled
.iter()
.filter(|(lo, hi, _)| *lo <= tm && tm <= *hi)
.map(|(_, _, l)| *l)
.collect();
let doubtful = stretch <= tiny
|| near
.iter()
.any(|&(p, q, ..)| point_segment(m, p, q) <= tiny);
let mut resolved = !doubtful;
if doubtful {
let (s0, s1) = (at(t0), at(t1));
let hugs = |p: Point2, q: Point2| {
point_segment(s0, p, q) <= tiny && point_segment(s1, p, q) <= tiny
};
let mut all_hug = stretch > tiny;
let beyond = match (free.contains(&true), free.contains(&false)) {
(true, false) => Some(Sign::Negative),
(false, true) => Some(Sign::Positive),
_ => None,
};
let mut known = beyond.is_some();
for &(p, q, polygon, inside_left) in &near {
if point_segment(m, p, q) > tiny {
continue;
}
if !hugs(p, q) {
all_hug = false;
break;
}
if let Some(solid) = beyond {
for end in [p, q] {
let s = side(a, b, end)?;
known &= s == Sign::Zero || s == solid;
}
}
on.push((polygon, inside_left == ((q - p).dot(d) > 0.0)));
}
if !all_hug {
lower += stretch;
upper += self.greatest * stretch;
continue;
}
resolved = known;
if !resolved {
lower += stretch;
}
for &(p, q, left) in &near_walls {
if hugs(p, q) {
free.push(left == ((q - p).dot(d) > 0.0));
}
}
hugged = true;
}
let (mut left, mut right) = (0.0f64, 0.0f64);
for (index, polygon) in self.polygons.iter().enumerate() {
let extra = self.factors[index] - 1.0;
if extra <= left.min(right) {
continue;
}
let sides: Vec<bool> = on
.iter()
.filter(|(p, _)| *p == index)
.map(|(_, l)| *l)
.collect();
if sides.is_empty() {
if in_polygon(polygon, m)? {
left = left.max(extra);
right = right.max(extra);
}
} else {
if sides.contains(&true) {
left = left.max(extra);
}
if sides.contains(&false) {
right = right.max(extra);
}
}
}
let (free_left, free_right) = if free.is_empty() {
(true, true)
} else {
(free.contains(&true), free.contains(&false))
};
let factor = 1.0
+ if on.is_empty() {
left
} else {
match (free_left, free_right) {
(true, true) => left.min(right),
(true, false) => left,
(false, true) => right,
(false, false) => left.max(right),
}
};
if resolved {
lower += factor * stretch;
}
upper += factor * stretch;
}
if hugged {
upper += 4.0 * self.greatest * tiny * breaks.len() as f64;
}
let slack = (self.greatest - 1.0) * breaks.len() as f64 * 8.0 * f64::EPSILON * length;
Ok((
round_down(lower - slack, scale),
round_up(upper + slack, scale),
))
}
}
fn touch_reach(region: &[Polygon]) -> f64 {
let (mut lo, mut hi) = (
Point2::new(f64::INFINITY, f64::INFINITY),
Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
);
let mut scale = 0.0f64;
for polygon in region {
for p in core::iter::once(&polygon.outer)
.chain(polygon.holes.iter())
.flat_map(|r| r.points.iter())
{
lo = Point2::new(lo.x.min(p.x), lo.y.min(p.y));
hi = Point2::new(hi.x.max(p.x), hi.y.max(p.y));
scale = scale.max(p.x.abs()).max(p.y.abs());
}
}
let extent = (hi.x - lo.x).max(hi.y - lo.y).max(0.0);
extent / f64::from(1u32 << 24) + 64.0 * f64::EPSILON * scale
}
fn touch_walls(
costs: &[CostRegion],
region: &[Polygon],
reach: f64,
) -> Result<Vec<CostRegion>, RouteError> {
let mut edges = Vec::new();
for polygon in region {
for (hole, ring) in
core::iter::once((false, &polygon.outer)).chain(polygon.holes.iter().map(|h| (true, h)))
{
let left = region_left(ring, hole)?;
for (p, q) in ring_edges(ring) {
if p != q {
edges.push((p, q, left));
}
}
}
}
let mut out = costs.to_vec();
for cost in &mut out {
for ring in core::iter::once(&mut cost.polygon.outer).chain(cost.polygon.holes.iter_mut()) {
for v in &mut ring.points {
for &(p, q, left) in edges.iter().chain(edges.iter()) {
let free = if left { Sign::Positive } else { Sign::Negative };
if point_segment(*v, p, q) > reach || side(p, q, *v)? != free {
continue;
}
let d = q - p;
let t = (*v - p).dot(d) / d.dot(d);
let foot = Point2::new(p.x + d.x * t, p.y + d.y * t);
let out = if left {
Point2::new(d.y, -d.x)
} else {
Point2::new(-d.y, d.x)
};
let unit = Point2::new(out.x / d.length(), out.y / d.length());
let mut step = f64::EPSILON * foot.x.abs().max(foot.y.abs()).max(reach);
let mut moved = foot;
while side(p, q, moved)? == free {
moved = Point2::new(foot.x + unit.x * step, foot.y + unit.y * step);
step *= 2.0;
}
*v = moved;
}
}
}
}
Ok(out)
}
fn convex_hull(points: &[Point2]) -> Result<Option<Vec<Point2>>, RouteError> {
let mut pts: Vec<Point2> = points.to_vec();
pts.sort_by(|p, q| p.x.total_cmp(&q.x).then(p.y.total_cmp(&q.y)));
pts.dedup();
if pts.len() < 3 {
return Ok(None);
}
let mut hull: Vec<Point2> = Vec::new();
for pass in 0..2 {
let start = hull.len();
let iter: Box<dyn Iterator<Item = &Point2>> = if pass == 0 {
Box::new(pts.iter())
} else {
Box::new(pts.iter().rev())
};
for &p in iter {
while hull.len() >= start + 2
&& side(hull[hull.len() - 2], hull[hull.len() - 1], p)? != Sign::Positive
{
hull.pop();
}
hull.push(p);
}
hull.pop();
}
Ok((hull.len() >= 3).then_some(hull))
}
fn holds(polygon: &Polygon, hull: &[Point2]) -> Result<bool, RouteError> {
for &c in hull {
if !in_polygon(polygon, c)? {
return Ok(false);
}
}
let n = hull.len();
for ring in core::iter::once(&polygon.outer).chain(polygon.holes.iter()) {
for (p, q) in ring_edges(ring) {
if meets_open_convex(p, q, hull, n)? {
return Ok(false);
}
}
}
let inv = 1.0 / n as f64;
let centre = hull.iter().fold(Point2::ZERO, |sum, c| sum + *c * inv);
for i in 0..n {
if side(hull[i], hull[(i + 1) % n], centre)? != Sign::Positive {
return Ok(false);
}
}
let on_boundary = core::iter::once(&polygon.outer)
.chain(polygon.holes.iter())
.flat_map(ring_edges)
.try_fold(false, |on, (p, q)| {
Ok::<_, RouteError>(on || (within(p, q, centre) && side(p, q, centre)? == Sign::Zero))
})?;
Ok(!on_boundary && in_polygon(polygon, centre)?)
}
fn meets_open_convex(p: Point2, q: Point2, hull: &[Point2], n: usize) -> Result<bool, RouteError> {
for i in 0..n {
let (u, v) = (hull[i], hull[(i + 1) % n]);
if side(u, v, p)? != Sign::Positive && side(u, v, q)? != Sign::Positive {
return Ok(false);
}
}
if p != q {
let mut all_left = true;
let mut all_right = true;
for &c in hull {
let s = side(p, q, c)?;
all_left &= s != Sign::Negative;
all_right &= s != Sign::Positive;
}
if all_left || all_right {
return Ok(false);
}
}
Ok(true)
}
pub fn weighted_farthest_point(
map: &WeightedMap,
subregion: &Polygon,
tolerance: f64,
) -> Result<Farthest, FarthestError> {
weighted_farthest_point_within(map, subregion, tolerance, MAX_CELLS)
}
#[derive(Debug, Clone, Copy)]
struct Cell {
corners: [Point2; 3],
root: usize,
anchor: (Point2, f64),
upper: f64,
order: usize,
depth: u32,
}
impl PartialEq for Cell {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Cell {}
impl PartialOrd for Cell {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Cell {
fn cmp(&self, other: &Self) -> Ordering {
self.upper
.total_cmp(&other.upper)
.then(other.order.cmp(&self.order))
}
}
pub fn weighted_farthest_point_within(
map: &WeightedMap,
subregion: &Polygon,
tolerance: f64,
max_cells: usize,
) -> Result<Farthest, FarthestError> {
if !(tolerance.is_finite() && tolerance >= 0.0) {
return Err(FarthestError::InvalidTolerance);
}
validate_region(core::slice::from_ref(subregion), &[])?;
let roots = free_triangles_in(&map.region, &map.obstacles)?;
let steep: Vec<f64> = roots
.iter()
.map(|t| map.weights.steepest(t))
.collect::<Result<_, _>>()?;
let slack = |depth: u32, radius: f64| {
f64::from(depth + 4) * 4.0 * f64::EPSILON * map.scale + 4.0 * f64::EPSILON * radius
};
let mut evaluated: HashMap<(u64, u64), Option<(f64, f64)>> = HashMap::new();
let mut lower: Option<(f64, Point2)> = None;
let mut heap = BinaryHeap::new();
let mut cells = 0usize;
let mut cell = |corners: [Point2; 3],
root: usize,
inherited: Option<(Point2, f64)>,
depth: u32,
order: usize,
lower: &mut Option<(f64, Point2)>|
-> Result<Option<Cell>, FarthestError> {
let centroid = Point2::new(
(corners[0].x + corners[1].x + corners[2].x) / 3.0,
(corners[0].y + corners[1].y + corners[2].y) / 3.0,
);
let radius = |a: Point2| {
corners
.iter()
.map(|c| (*c - a).length())
.fold(0.0, f64::max)
};
let k = steep[root];
let mut best: Option<(f64, (Point2, f64))> =
inherited.map(|(a, d)| (d + k * radius(a), (a, d)));
for a in [corners[0], corners[1], corners[2], centroid] {
if !in_triangle(&roots[root], a)? {
continue;
}
if map.walls.iter().try_fold(false, |m, &(p, q)| {
Ok::<_, RouteError>(m || (within(p, q, a) && side(p, q, a)? == Sign::Zero))
})? {
continue;
}
let key = (a.x.to_bits(), a.y.to_bits());
let bracket = match evaluated.get(&key) {
Some(b) => *b,
None => {
let b = map.bracket(a)?;
evaluated.insert(key, b);
b
}
};
let Some((lo, hi)) = bracket else {
return Err(FarthestError::Unreachable {
triangle: roots[root],
});
};
if in_polygon(subregion, a)? && lower.is_none_or(|(l, _)| lo > l) {
*lower = Some((lo, a));
}
let bound = hi + k * radius(a);
if best.is_none_or(|(b, _)| bound < b) {
best = Some((bound, (a, hi)));
}
}
Ok(best.map(|(bound, anchor)| Cell {
corners,
root,
anchor,
upper: bound + k * slack(depth, radius(anchor.0)),
order,
depth,
}))
};
for (root, corners) in roots.iter().enumerate() {
if outside(subregion, corners)? {
continue;
}
cells += 1;
let Some(c) = cell(*corners, root, None, 0, cells, &mut lower)? else {
return Err(FarthestError::Triangulation);
};
heap.push(c);
}
if cells == 0 {
return Err(FarthestError::Empty);
}
let best = |lower: &Option<(f64, Point2)>| lower.map_or(f64::NEG_INFINITY, |(d, _)| d);
while let Some(top) = heap.peek() {
if top.upper <= best(&lower) + tolerance || cells >= max_cells {
break;
}
let parent = heap.pop().expect("peeked");
let [a, b, c] = parent.corners;
let mid = |p: Point2, q: Point2| Point2::new(0.5 * p.x + 0.5 * q.x, 0.5 * p.y + 0.5 * q.y);
let (ab, bc, ca) = (mid(a, b), mid(b, c), mid(c, a));
for corners in [[a, ab, ca], [ab, b, bc], [ca, bc, c], [ab, bc, ca]] {
if outside(subregion, &corners)? {
continue;
}
cells += 1;
if let Some(child) = cell(
corners,
parent.root,
Some(parent.anchor),
parent.depth + 1,
cells,
&mut lower,
)? {
if child.upper > best(&lower) {
heap.push(child);
}
}
}
}
let Some((lo, witness)) = lower else {
if heap.is_empty() {
return Err(FarthestError::Empty);
}
return Ok(Farthest {
distance: LengthInterval {
lower: 0.0,
upper: heap.peek().map_or(0.0, |c| c.upper),
},
witness: None,
converged: false,
cells,
});
};
let hi = heap.peek().map_or(lo, |c| c.upper.max(lo));
Ok(Farthest {
distance: LengthInterval {
lower: lo,
upper: hi,
},
witness: Some(witness),
converged: hi - lo <= tolerance,
cells,
})
}
#[cfg(test)]
mod tests {
use super::*;
use axiolid_overlay::Ring;
fn square() -> Weights {
let p = |x: f64, y: f64| Point2::new(x, y);
let polygon = Polygon {
outer: Ring {
points: vec![p(0.0, 0.0), p(1.0, 0.0), p(1.0, 1.0), p(0.0, 1.0)],
},
holes: Vec::new(),
};
Weights::new(&[CostRegion::new(polygon, 2.0)], &[], &[]).unwrap()
}
#[test]
fn a_stretch_within_rounding_of_a_cost_edge_is_bracketed_both_ways() {
let weights = square();
let tiny = 1e-17;
let (lo, hi) = weights
.segment(Point2::new(-1.0, tiny), Point2::new(2.0, tiny), 2.0)
.unwrap();
assert!(lo <= 3.0 && (hi - 3.0).abs() < 1e-12, "[{lo}, {hi}]");
let (lo, hi) = weights
.segment(Point2::new(-1.0, -tiny), Point2::new(2.0, -tiny), 2.0)
.unwrap();
assert!(lo <= 3.0 && hi >= 3.0, "[{lo}, {hi}]");
let p = |x: f64, y: f64| Point2::new(x, y);
let notched = Polygon {
outer: Ring {
points: vec![
p(0.0, 0.0),
p(4.0, 0.0),
p(4.0, 2.0),
p(2.5, 2.0),
p(2.0, 1.0 + f64::EPSILON),
p(1.5, 2.0),
p(0.0, 2.0),
],
},
holes: Vec::new(),
};
let weights = Weights::new(&[CostRegion::new(notched, 2.0)], &[], &[]).unwrap();
let (lo, hi) = weights.segment(p(0.5, 1.0), p(3.5, 1.0), 4.0).unwrap();
assert!(lo <= 6.0 && hi >= 6.0, "[{lo}, {hi}]");
let weights = square();
let (lo, hi) = weights
.segment(Point2::new(0.5, -tiny), Point2::new(0.5, tiny), 2.0)
.unwrap();
assert!(lo <= 2.0 * tiny && hi >= 2.0 * 2.0 * tiny, "[{lo}, {hi}]");
let (lo, hi) = weights
.segment(Point2::new(-1.0, 0.5), Point2::new(2.0, 0.5), 2.0)
.unwrap();
assert!(
(lo - 4.0).abs() < 1e-12 && (hi - 4.0).abs() < 1e-12,
"[{lo}, {hi}]"
);
}
fn p(x: f64, y: f64) -> Point2 {
Point2::new(x, y)
}
fn rect(x0: f64, y0: f64, x1: f64, y1: f64) -> Polygon {
Polygon {
outer: Ring {
points: vec![p(x0, y0), p(x1, y0), p(x1, y1), p(x0, y1)],
},
holes: Vec::new(),
}
}
fn interval(map: &WeightedMap, end: Point2, toward: Point2) -> usize {
(0..map.nodes.len())
.find(|&i| match map.kinds[i] {
Kind::Interval { a, b, .. } => {
(a == end && (b - a).dot(toward - a) > 0.0)
|| (b == end && (a - b).dot(toward - b) > 0.0)
}
Kind::Vertex => false,
})
.expect("an interval")
}
#[test]
fn a_touch_blocks_a_hop_only_at_a_vertex_end() {
let room = [rect(0.0, 0.0, 10.0, 4.0)];
let stair = CostRegion::new(rect(2.0, 0.0, 4.0, 4.0), 2.0);
let barrier = vec![vec![p(5.0, 1.0), p(5.0, 3.0)]];
let map = weighted_distance_map(&room, &barrier, &[p(0.5, 2.0)], &[stair], 1.0).unwrap();
let above = interval(&map, p(4.0, 1.0), p(4.0, 2.0));
let q = p(6.0, 1.0);
let hop = map
.hop(map.span(above), (q, q), map.kinds[above], Kind::Vertex)
.unwrap();
assert!(hop.is_some(), "the inner end's pieces graze the foot");
let walled = vec![vec![p(5.0, 3.0), p(5.0, 1.0)]];
let stair = CostRegion::new(rect(2.0, 0.0, 4.0, 4.0), 2.0);
let map = weighted_distance_map(&room, &walled, &[p(0.5, 2.0)], &[stair], 1.0).unwrap();
let corner = interval(&map, p(4.0, 4.0), p(4.0, 3.0));
let q = p(6.0, 2.0);
let hop = map
.hop(map.span(corner), (q, q), map.kinds[corner], Kind::Vertex)
.unwrap();
assert!(hop.is_none(), "{hop:?}");
}
#[test]
fn a_hull_is_held_only_whole() {
let square = rect(0.0, 0.0, 4.0, 4.0);
let hull = [p(1.0, 1.0), p(3.0, 1.0), p(3.0, 3.0)];
assert!(holds(&square, &hull).unwrap());
let notched = Polygon {
outer: Ring {
points: vec![
p(0.0, 0.0),
p(4.0, 0.0),
p(4.0, 4.0),
p(0.0, 4.0),
p(0.0, 1.9),
p(2.2, 1.6),
p(0.0, 1.5),
],
},
holes: Vec::new(),
};
for c in hull {
assert!(in_polygon(¬ched, c).unwrap());
}
assert!(in_polygon(¬ched, p(7.0 / 3.0, 5.0 / 3.0)).unwrap());
assert!(!holds(¬ched, &hull).unwrap());
}
#[test]
fn a_segment_along_a_blocker_does_not_pass_through_its_end() {
assert!(!through_end(p(0.0, 0.0), p(4.0, 0.0), p(2.0, 0.0), p(3.0, 0.0)).unwrap());
assert!(!through_end(p(0.0, 0.0), p(4.0, 0.0), p(2.0, 0.0), p(1.0, 0.0)).unwrap());
assert!(through_end(p(0.0, -1.0), p(0.0, 1.0), p(0.0, 0.0), p(1.0, 0.0)).unwrap());
}
}