axiolid_route/
map.rs

1//! Many-target shortest paths and the farthest point of a subregion (#186).
2//!
3//! # The map
4//!
5//! [`DistanceMap`] runs one Dijkstra from every target at once over the
6//! visibility graph, so each graph vertex knows its distance to the nearest
7//! target and the next vertex on the way. A query point then needs only the
8//! vertices it sees: its distance is the least `|x - v| + d(v)` over them.
9//! Visibility is decided exactly, as for [`crate::shortest_path`]; lengths
10//! are sums of square roots in binary64.
11//!
12//! # The farthest point, bracketed
13//!
14//! The distance `D` to the nearest target is 1-Lipschitz along any segment
15//! that stays in the free space: `D(y) <= D(a) + |y - a|`. So the free space
16//! is cut into triangles that each lie inside it -- a constrained
17//! triangulation whose constraints are every wall and barrier -- and on a
18//! triangle `T` with a point `a` of known distance,
19//!
20//! ```text
21//! max over T of D  <=  D(a) + (greatest distance from a to a corner of T).
22//! ```
23//!
24//! Every distance evaluated at a point of the subregion is a lower bound on
25//! the maximum. Triangles are split at their edge midpoints, the one with
26//! the greatest upper bound first, and dropped once their upper bound falls
27//! below the best lower one, until the two are within the tolerance or the
28//! cell budget runs out. The result is an interval that contains the true
29//! maximum: widened by the rounding of the lengths, and of the midpoints,
30//! which leave gaps between cells no wider than a few ulps.
31//!
32//! Anchors -- the points whose distance bounds a cell -- are only ever
33//! points proven inside the cell's original triangle, exactly: a rounded
34//! midpoint on a wall may fall just outside the free space, and its
35//! distance says nothing about the triangle. Nor may an anchor lie on a
36//! barrier: a point on a barrier is seen from both sides, so its distance
37//! is the nearer side's, and bounds nothing on the farther.
38
39use core::cmp::Ordering;
40use std::collections::{BinaryHeap, HashMap};
41
42use axiolid_contracts::Sign;
43use axiolid_core::Point2;
44use axiolid_overlay::{Polygon, Ring};
45use axiolid_triangulate::{triangulate, Constraint};
46
47use crate::graph::{self, Graph, Side};
48use crate::{
49    contains, crosses, dedup_points, obstacle_segments, ring_edges, side, validate_region, within,
50    Route, RouteError, Unreachable, MAX_VERTICES,
51};
52
53/// Cells [`farthest_point`] refines at most.
54pub const MAX_CELLS: usize = 20_000;
55
56/// Why no distance map was built.
57#[derive(Debug, Clone, Copy, PartialEq)]
58#[non_exhaustive]
59pub enum MapError {
60    /// The region, a barrier or a target is malformed, or the input is
61    /// over budget ([`RouteError::TooManyVertices`], whose lower bound is
62    /// zero here: there is no single pair of endpoints).
63    Route(RouteError),
64    /// No targets were given.
65    NoTargets,
66    /// A target lies outside the free-space region.
67    TargetOutside {
68        /// Its index in the targets given.
69        index: usize,
70    },
71    /// A cost region's factor is below 1 or not finite.
72    InvalidFactor {
73        /// Its index in the cost regions given.
74        index: usize,
75    },
76    /// A target's weight is negative or not finite.
77    InvalidWeight {
78        /// Its index in the targets given.
79        index: usize,
80    },
81    /// The spacing of points along cost edges is not positive and finite.
82    InvalidSpacing,
83    /// A cost region's edge properly crosses a region edge, a barrier or
84    /// another cost region's edge, or runs along a barrier. Cost regions
85    /// may nest, touch and share edges, and run along the region's
86    /// boundary; crossings would need rounded vertices, not supported yet.
87    CostCrossing {
88        /// Index of the cost region, in the cost regions given.
89        index: usize,
90    },
91}
92
93impl From<RouteError> for MapError {
94    fn from(error: RouteError) -> Self {
95        Self::Route(error)
96    }
97}
98
99/// Shortest-path distances from every point of a region to the nearest of
100/// several targets.
101#[derive(Debug, Clone)]
102pub struct DistanceMap {
103    region: Vec<Polygon>,
104    /// Barrier segments: zero-width, with free space on both sides.
105    walls: Vec<(Point2, Point2)>,
106    obstacles: Vec<(Point2, Point2)>,
107    nodes: Vec<Point2>,
108    graph: Graph,
109    /// Distance from each (vertex, sector) state to its nearest target;
110    /// infinite if none.
111    distance: Vec<f64>,
112    /// The next state towards that target; `usize::MAX` at a target.
113    next: Vec<usize>,
114    /// Which target, as an index into the targets given.
115    target: Vec<usize>,
116    /// The targets given.
117    sites: Vec<Point2>,
118    /// Their weights: the distance each starts at.
119    weights: Vec<f64>,
120}
121
122/// The nearest target from a point, and the route there.
123#[derive(Debug, Clone, PartialEq)]
124#[non_exhaustive]
125pub struct Reach {
126    /// Index of the nearest target in the targets given. Ties go to the
127    /// route through the lowest graph vertex, deterministically.
128    pub target: usize,
129    /// The route, from the query point to the target. Its `length` is the
130    /// route's own, without the target's weight.
131    pub route: Route,
132    /// The distance: the route's length plus the target's weight (see
133    /// [`distance_map_weighted`]); the route's length when all weights are
134    /// zero.
135    pub distance: f64,
136}
137
138/// A distance map from `targets` over `region`, avoiding `barriers`.
139///
140/// # Errors
141///
142/// [`MapError`] for malformed input, no targets, a target outside the
143/// region, or more than [`MAX_VERTICES`] graph vertices.
144pub fn distance_map(
145    region: &[Polygon],
146    barriers: &[Vec<Point2>],
147    targets: &[Point2],
148) -> Result<DistanceMap, MapError> {
149    distance_map_within(region, barriers, targets, MAX_VERTICES)
150}
151
152/// [`distance_map`] with a caller-chosen vertex budget.
153///
154/// # Errors
155///
156/// As [`distance_map`], with `budget` for [`MAX_VERTICES`].
157pub fn distance_map_within(
158    region: &[Polygon],
159    barriers: &[Vec<Point2>],
160    targets: &[Point2],
161    budget: usize,
162) -> Result<DistanceMap, MapError> {
163    let weighted: Vec<(Point2, f64)> = targets.iter().map(|t| (*t, 0.0)).collect();
164    distance_map_within_weighted(region, barriers, &weighted, budget)
165}
166
167/// A distance map whose targets each start at their own distance: the
168/// distance from a point is the least, over targets, of the route's
169/// length to the target plus the target's weight (#197). For a way out
170/// that carries the rest of a walk beyond it, such as a stair landing.
171/// With every weight zero it is [`distance_map`].
172///
173/// # Errors
174///
175/// As [`distance_map`], and [`MapError::InvalidWeight`] for a weight
176/// that is negative or not finite.
177pub fn distance_map_weighted(
178    region: &[Polygon],
179    barriers: &[Vec<Point2>],
180    targets: &[(Point2, f64)],
181) -> Result<DistanceMap, MapError> {
182    distance_map_within_weighted(region, barriers, targets, MAX_VERTICES)
183}
184
185/// [`distance_map_weighted`] with a caller-chosen vertex budget.
186///
187/// # Errors
188///
189/// As [`distance_map_weighted`], with `budget` for [`MAX_VERTICES`].
190pub fn distance_map_within_weighted(
191    region: &[Polygon],
192    barriers: &[Vec<Point2>],
193    weighted: &[(Point2, f64)],
194    budget: usize,
195) -> Result<DistanceMap, MapError> {
196    validate_region(region, barriers)?;
197    if weighted.is_empty() {
198        return Err(MapError::NoTargets);
199    }
200    let targets: Vec<Point2> = weighted.iter().map(|(t, _)| *t).collect();
201    let weights: Vec<f64> = weighted.iter().map(|(_, w)| *w).collect();
202    if !targets.iter().all(|t| t.is_finite()) {
203        return Err(RouteError::NonFinitePoint.into());
204    }
205    if let Some(index) = weights.iter().position(|w| !(w.is_finite() && *w >= 0.0)) {
206        return Err(MapError::InvalidWeight { index });
207    }
208    for (index, t) in targets.iter().enumerate() {
209        if !contains(region, *t)? {
210            return Err(MapError::TargetOutside { index });
211        }
212    }
213    let mut nodes = targets.to_vec();
214    for polygon in region {
215        for ring in core::iter::once(&polygon.outer).chain(polygon.holes.iter()) {
216            nodes.extend(ring.points.iter().copied());
217        }
218    }
219    for barrier in barriers {
220        nodes.extend(barrier.iter().copied());
221    }
222    dedup_points(&mut nodes);
223    if nodes.len() > budget {
224        return Err(RouteError::TooManyVertices {
225            supplied: nodes.len(),
226            budget,
227            lower_bound: 0.0,
228        }
229        .into());
230    }
231    let obstacles = obstacle_segments(region, barriers);
232    let graph = Graph::build(&nodes, region, barriers, &obstacles)?;
233    // Every free sector of every target at its weight, then one Dijkstra;
234    // ties go to the lowest state, as in `shortest_path`. Of targets on
235    // one point, the lightest (then the first) seeds it.
236    let mut sources = Vec::new();
237    let mut seed = vec![usize::MAX; graph.adjacency.len()];
238    for (i, node) in nodes.iter().enumerate() {
239        let lightest = (0..targets.len())
240            .filter(|&t| targets[t] == *node)
241            .min_by(|&a, &b| weights[a].total_cmp(&weights[b]).then(a.cmp(&b)));
242        if let Some(t) = lightest {
243            for state in graph.states(i) {
244                sources.push((state, weights[t]));
245                seed[state] = t;
246            }
247        }
248    }
249    let (distance, next, _) = graph::dijkstra_from(&graph.adjacency, &sources, |_| false);
250    // Follow each state's chain to its end, the target it is reached
251    // from. A heavy target may itself be reached from a lighter one, so
252    // being seeded does not end the chain.
253    let mut target = seed.clone();
254    for (state, t) in target.iter_mut().enumerate() {
255        let mut at = state;
256        let mut steps = 0;
257        while next[at] != usize::MAX && steps <= seed.len() {
258            at = next[at];
259            steps += 1;
260        }
261        *t = seed[at];
262    }
263    Ok(DistanceMap {
264        region: region.to_vec(),
265        walls: barriers
266            .iter()
267            .flat_map(|b| b.windows(2).map(|w| (w[0], w[1])))
268            .collect(),
269        obstacles,
270        nodes,
271        graph,
272        distance,
273        next,
274        target,
275        sites: targets,
276        weights,
277    })
278}
279
280impl DistanceMap {
281    /// Vertices of the visibility graph.
282    #[must_use]
283    pub fn graph_vertices(&self) -> usize {
284        self.nodes.len()
285    }
286
287    /// How many targets the map was built from.
288    #[must_use]
289    pub fn targets(&self) -> usize {
290        self.sites.len()
291    }
292
293    /// The graph's vertices.
294    pub(crate) fn nodes(&self) -> &[Point2] {
295        &self.nodes
296    }
297
298    /// Each graph vertex's distance to the nearest target, over all its
299    /// sectors; infinite when no target is reached.
300    pub(crate) fn vertex_distances(&self) -> Vec<f64> {
301        (0..self.nodes.len())
302            .map(|i| {
303                self.graph
304                    .states(i)
305                    .map(|s| self.distance[s])
306                    .fold(f64::INFINITY, f64::min)
307            })
308            .collect()
309    }
310
311    /// The free-space region.
312    pub(crate) fn region(&self) -> &[Polygon] {
313        &self.region
314    }
315
316    /// Region boundary and barrier edges.
317    pub(crate) fn obstacles(&self) -> &[(Point2, Point2)] {
318        &self.obstacles
319    }
320
321    /// The targets the map was built from.
322    pub(crate) fn sites(&self) -> &[Point2] {
323        &self.sites
324    }
325
326    /// The distance each target starts at.
327    pub(crate) fn weights(&self) -> &[f64] {
328        &self.weights
329    }
330
331    /// Whether two maps cover the same free space: the same region and the
332    /// same barriers.
333    pub(crate) fn same_space(&self, other: &Self) -> bool {
334        self.region == other.region && self.walls == other.walls
335    }
336
337    /// The nearest target from `point` and the route there.
338    ///
339    /// `Ok(Err(StartOutside))` for a point outside the region,
340    /// `Ok(Err(DisconnectedComponents))` for one no target can reach.
341    ///
342    /// # Errors
343    ///
344    /// [`RouteError`] for a non-finite point or an undecidable predicate.
345    pub fn nearest(&self, point: Point2) -> Result<Result<Reach, Unreachable>, RouteError> {
346        if !point.is_finite() {
347            return Err(RouteError::NonFinitePoint);
348        }
349        if !contains(&self.region, point)? {
350            return Ok(Err(Unreachable::StartOutside));
351        }
352        let Some((length, first)) = self.via(point)? else {
353            return Ok(Err(Unreachable::DisconnectedComponents));
354        };
355        let mut polyline = vec![point];
356        let mut state = first;
357        loop {
358            let at = self.nodes[self.graph.node(state)];
359            if polyline.last() != Some(&at) {
360                polyline.push(at);
361            }
362            if self.next[state] == usize::MAX {
363                break;
364            }
365            state = self.next[state];
366        }
367        let target = self.target[first];
368        Ok(Ok(Reach {
369            target,
370            route: Route {
371                polyline,
372                // Exactly the distance when the weight is zero.
373                length: length - self.weights[target],
374                graph_vertices: self.nodes.len(),
375            },
376            distance: length,
377        }))
378    }
379
380    /// The distance from `point`, known to be inside the region, and the
381    /// first state on the way; `None` when no target is reachable.
382    fn via(&self, point: Point2) -> Result<Option<(f64, usize)>, RouteError> {
383        let mut best: Option<(f64, usize)> = None;
384        let offer = |length: f64, state: usize, best: &mut Option<(f64, usize)>| {
385            if best.is_none_or(|(b, s)| length < b || (length == b && state < s)) {
386                *best = Some((length, state));
387            }
388        };
389        for (i, node) in self.nodes.iter().enumerate() {
390            let nearest = self
391                .graph
392                .states(i)
393                .map(|s| self.distance[s])
394                .fold(f64::INFINITY, f64::min);
395            if nearest.is_infinite() {
396                continue;
397            }
398            if *node == point {
399                for s in self.graph.states(i) {
400                    offer(self.distance[s], s, &mut best);
401                }
402                continue;
403            }
404            let leg = (*node - point).length();
405            if best.is_some_and(|(b, _)| leg + nearest > b) {
406                continue;
407            }
408            let ok = graph::sides(
409                point,
410                *node,
411                &self.region,
412                &self.obstacles,
413                &self.nodes,
414                &self.graph.stars,
415                &self.graph.rayed,
416            )?;
417            for (k, on) in [Side::Left, Side::Right].into_iter().enumerate() {
418                if !ok[k] {
419                    continue;
420                }
421                if let Some(s) = self.graph.arrival(i, point, on)? {
422                    if self.distance[s].is_finite() {
423                        offer(leg + self.distance[s], s, &mut best);
424                    }
425                }
426            }
427        }
428        Ok(best)
429    }
430
431    /// Distance to the nearest target, for a point inside the region.
432    pub(crate) fn at(&self, point: Point2) -> Result<Option<f64>, RouteError> {
433        Ok(self.via(point)?.map(|(length, _)| length))
434    }
435
436    /// The largest length of a path in the graph, in edges: bounds how many
437    /// roundings a distance carries.
438    pub(crate) fn hops(&self) -> usize {
439        let mut most = 0;
440        for start in 0..self.next.len() {
441            let mut state = start;
442            let mut hops = 0;
443            while self.next[state] != usize::MAX && hops <= self.next.len() {
444                state = self.next[state];
445                hops += 1;
446            }
447            most = most.max(hops);
448        }
449        most
450    }
451}
452
453/// A closed interval of lengths.
454#[derive(Debug, Clone, Copy, PartialEq)]
455pub struct LengthInterval {
456    /// No greater than the true value.
457    pub lower: f64,
458    /// No less than the true value.
459    pub upper: f64,
460}
461
462/// The greatest distance from a subregion to the nearest target.
463#[derive(Debug, Clone, Copy, PartialEq)]
464#[non_exhaustive]
465pub struct Farthest {
466    /// Contains the true maximum over the subregion's points in the free
467    /// space.
468    pub distance: LengthInterval,
469    /// A point of the subregion, in the free space, at least
470    /// `distance.lower` from every target; `None` only when the budget ran
471    /// out before any point of the subregion was sampled.
472    pub witness: Option<Point2>,
473    /// Whether the interval is no wider than the tolerance asked for. When
474    /// the cell budget runs out first it is still sound, only wider.
475    pub converged: bool,
476    /// Cells examined.
477    pub cells: usize,
478}
479
480/// Why no bracket was produced.
481#[derive(Debug, Clone, Copy, PartialEq)]
482#[non_exhaustive]
483pub enum FarthestError {
484    /// The subregion is malformed, or a predicate was undecidable.
485    Route(RouteError),
486    /// The tolerance is negative or not finite.
487    InvalidTolerance,
488    /// Two walls or barriers cross other than at a shared vertex. The free
489    /// space is triangulated with them as constraints, and crossings would
490    /// need rounded vertices; not supported yet.
491    CrossingObstacles,
492    /// The free space could not be triangulated, or a triangle of it is
493    /// too thin to hold a point strictly inside.
494    Triangulation,
495    /// Part of the subregion lies in free space that no target reaches, so
496    /// its farthest distance is infinite. The triangle lies in the free
497    /// space and meets the subregion; no target reaches any of it.
498    Unreachable {
499        /// The triangle's corners.
500        triangle: [Point2; 3],
501    },
502    /// The subregion does not meet the free space.
503    Empty,
504    /// Two distance maps cover different free space: their regions or
505    /// barriers differ.
506    MismatchedMaps,
507}
508
509impl From<RouteError> for FarthestError {
510    fn from(error: RouteError) -> Self {
511        Self::Route(error)
512    }
513}
514
515/// The greatest distance to the nearest target over the points of
516/// `subregion` in the map's free space, bracketed to within `tolerance`.
517///
518/// # Errors
519///
520/// [`FarthestError`], among them [`FarthestError::Unreachable`] with a
521/// triangle as evidence when part of the subregion reaches no target.
522pub fn farthest_point(
523    map: &DistanceMap,
524    subregion: &Polygon,
525    tolerance: f64,
526) -> Result<Farthest, FarthestError> {
527    farthest_point_within(map, subregion, tolerance, MAX_CELLS)
528}
529
530/// A cell: a triangle inside the free-space triangle `root`, with the best
531/// anchor known for it and the upper bound that gives.
532#[derive(Debug, Clone, Copy)]
533struct Cell {
534    corners: [Point2; 3],
535    root: usize,
536    anchor: (Point2, f64),
537    upper: f64,
538    depth: u32,
539    order: usize,
540}
541
542impl PartialEq for Cell {
543    fn eq(&self, other: &Self) -> bool {
544        self.cmp(other) == Ordering::Equal
545    }
546}
547
548impl Eq for Cell {}
549
550impl PartialOrd for Cell {
551    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
552        Some(self.cmp(other))
553    }
554}
555
556impl Ord for Cell {
557    /// Greatest upper bound first; the earlier cell on a tie.
558    fn cmp(&self, other: &Self) -> Ordering {
559        self.upper
560            .total_cmp(&other.upper)
561            .then(other.order.cmp(&self.order))
562    }
563}
564
565/// [`farthest_point`] with a caller-chosen cell budget.
566///
567/// # Errors
568///
569/// As [`farthest_point`].
570pub fn farthest_point_within(
571    map: &DistanceMap,
572    subregion: &Polygon,
573    tolerance: f64,
574    max_cells: usize,
575) -> Result<Farthest, FarthestError> {
576    if !(tolerance.is_finite() && tolerance >= 0.0) {
577        return Err(FarthestError::InvalidTolerance);
578    }
579    validate_region(core::slice::from_ref(subregion), &[])?;
580    let roots = free_triangles(map)?;
581    let scale = map
582        .nodes
583        .iter()
584        .chain(subregion.outer.points.iter())
585        .fold(0.0f64, |m, p| m.max(p.x.abs()).max(p.y.abs()));
586    // A distance is a sum of at most `hops + 1` rounded lengths, each a
587    // rounded square root of rounded squares of rounded differences.
588    let relative = (map.hops() as f64 + 8.0) * 2.0 * f64::EPSILON;
589    // Rounded midpoints leave gaps of about an ulp per level between cells,
590    // and the radius itself is rounded.
591    let slack = |depth: u32, radius: f64| {
592        f64::from(depth + 4) * 4.0 * f64::EPSILON * scale + 4.0 * f64::EPSILON * radius
593    };
594    let mut search = Search {
595        map,
596        subregion,
597        roots: &roots,
598        evaluated: HashMap::new(),
599        lower: None,
600    };
601    let mut heap = BinaryHeap::new();
602    let mut cells = 0usize;
603    for (root, corners) in roots.iter().enumerate() {
604        if search.outside(corners)? {
605            continue;
606        }
607        cells += 1;
608        // A root's centroid is strictly inside it, off every barrier,
609        // unless the triangle is too thin to hold a representable point.
610        let Some(cell) = search.cell(*corners, root, None, 0, cells, &slack)? else {
611            return Err(FarthestError::Triangulation);
612        };
613        heap.push(cell);
614    }
615    if cells == 0 {
616        return Err(FarthestError::Empty);
617    }
618    let best = |search: &Search| search.lower.map_or(f64::NEG_INFINITY, |(d, _)| d);
619    while let Some(top) = heap.peek() {
620        if top.upper <= best(&search) + tolerance || cells >= max_cells {
621            break;
622        }
623        let cell = heap.pop().expect("peeked");
624        let [a, b, c] = cell.corners;
625        let mid = |p: Point2, q: Point2| Point2::new(0.5 * p.x + 0.5 * q.x, 0.5 * p.y + 0.5 * q.y);
626        let (ab, bc, ca) = (mid(a, b), mid(b, c), mid(c, a));
627        for corners in [[a, ab, ca], [ab, b, bc], [ca, bc, c], [ab, bc, ca]] {
628            if search.outside(&corners)? {
629                continue;
630            }
631            cells += 1;
632            let child = search.cell(
633                corners,
634                cell.root,
635                Some(cell.anchor),
636                cell.depth + 1,
637                cells,
638                &slack,
639            )?;
640            if let Some(child) = child {
641                if child.upper > best(&search) {
642                    heap.push(child);
643                }
644            }
645        }
646    }
647    let Some((lower, witness)) = search.lower else {
648        if heap.is_empty() {
649            return Err(FarthestError::Empty);
650        }
651        let upper = heap.peek().map_or(0.0, |c| c.upper);
652        return Ok(Farthest {
653            distance: LengthInterval {
654                lower: 0.0,
655                upper: upper * (1.0 + relative),
656            },
657            witness: None,
658            converged: false,
659            cells,
660        });
661    };
662    let upper = heap.peek().map_or(lower, |c| c.upper.max(lower));
663    Ok(Farthest {
664        distance: LengthInterval {
665            lower: lower * (1.0 - relative),
666            upper: upper * (1.0 + relative),
667        },
668        witness: Some(witness),
669        converged: upper - lower <= tolerance,
670        cells,
671    })
672}
673
674struct Search<'a> {
675    map: &'a DistanceMap,
676    subregion: &'a Polygon,
677    roots: &'a [[Point2; 3]],
678    evaluated: HashMap<(u64, u64), Option<f64>>,
679    /// The greatest distance at a point of the subregion, and the point.
680    lower: Option<(f64, Point2)>,
681}
682
683impl Search<'_> {
684    fn outside(&self, t: &[Point2; 3]) -> Result<bool, RouteError> {
685        outside(self.subregion, t)
686    }
687
688    fn admissible(&self, root: usize, a: Point2) -> Result<bool, RouteError> {
689        admissible(self.map, &self.roots[root], a)
690    }
691
692    fn distance(&mut self, p: Point2) -> Result<Option<f64>, RouteError> {
693        let key = (p.x.to_bits(), p.y.to_bits());
694        if let Some(d) = self.evaluated.get(&key) {
695            return Ok(*d);
696        }
697        let d = self.map.at(p)?;
698        self.evaluated.insert(key, d);
699        Ok(d)
700    }
701
702    /// The cell's upper bound from its best anchor: its corners and
703    /// centroid where proven inside the root triangle, and the parent's.
704    /// Raises the lower bound from anchors in the subregion. `None` when
705    /// the cell has no anchor (never for a root).
706    fn cell(
707        &mut self,
708        corners: [Point2; 3],
709        root: usize,
710        inherited: Option<(Point2, f64)>,
711        depth: u32,
712        order: usize,
713        slack: &dyn Fn(u32, f64) -> f64,
714    ) -> Result<Option<Cell>, FarthestError> {
715        let centroid = Point2::new(
716            (corners[0].x + corners[1].x + corners[2].x) / 3.0,
717            (corners[0].y + corners[1].y + corners[2].y) / 3.0,
718        );
719        let radius = |a: Point2| {
720            corners
721                .iter()
722                .map(|c| (*c - a).length())
723                .fold(0.0, f64::max)
724        };
725        let mut best: Option<(f64, (Point2, f64))> =
726            inherited.map(|(a, d)| (d + radius(a), (a, d)));
727        for a in [corners[0], corners[1], corners[2], centroid] {
728            if !self.admissible(root, a)? {
729                continue;
730            }
731            let Some(d) = self.distance(a)? else {
732                return Err(FarthestError::Unreachable {
733                    triangle: self.roots[root],
734                });
735            };
736            if in_polygon(self.subregion, a)? && self.lower.is_none_or(|(l, _)| d > l) {
737                self.lower = Some((d, a));
738            }
739            let bound = d + radius(a);
740            if best.is_none_or(|(b, _)| bound < b) {
741                best = Some((bound, (a, d)));
742            }
743        }
744        Ok(best.map(|(bound, anchor)| Cell {
745            corners,
746            root,
747            anchor,
748            upper: bound + slack(depth, radius(anchor.0)),
749            depth,
750            order,
751        }))
752    }
753}
754
755/// Whether a triangle misses `subregion`, decided exactly: no edge of the
756/// subregion meets it, and a corner lies outside.
757pub(crate) fn outside(subregion: &Polygon, t: &[Point2; 3]) -> Result<bool, RouteError> {
758    for ring in core::iter::once(&subregion.outer).chain(subregion.holes.iter()) {
759        for (p, q) in ring_edges(ring) {
760            if meets_triangle(p, q, t)? {
761                return Ok(false);
762            }
763        }
764    }
765    Ok(!in_polygon(subregion, t[0])?)
766}
767
768/// Whether `a` may anchor a cell of the free-space triangle `root`: inside
769/// it, and not on a barrier of `map`.
770pub(crate) fn admissible(
771    map: &DistanceMap,
772    root: &[Point2; 3],
773    a: Point2,
774) -> Result<bool, RouteError> {
775    if !in_triangle(root, a)? {
776        return Ok(false);
777    }
778    for &(p, q) in &map.walls {
779        if side(p, q, a)? == Sign::Zero && within(p, q, a) {
780            return Ok(false);
781        }
782    }
783    Ok(true)
784}
785
786/// The triangles of the free space: a constrained triangulation of every
787/// wall and barrier, each segment first cut at the vertices lying on it,
788/// keeping the triangles inside the region.
789pub(crate) fn free_triangles(map: &DistanceMap) -> Result<Vec<[Point2; 3]>, FarthestError> {
790    free_triangles_in(&map.region, &map.obstacles)
791}
792
793/// [`free_triangles`] of a region and its obstacle segments.
794pub(crate) fn free_triangles_in(
795    region: &[Polygon],
796    obstacles: &[(Point2, Point2)],
797) -> Result<Vec<[Point2; 3]>, FarthestError> {
798    let mut points: Vec<Point2> = obstacles.iter().flat_map(|(p, q)| [*p, *q]).collect();
799    dedup_points(&mut points);
800    let mut pieces: Vec<(Point2, Point2)> = Vec::new();
801    for &(p, q) in obstacles {
802        let d = q - p;
803        let mut cuts = vec![p, q];
804        for &v in &points {
805            if v != p && v != q && side(p, q, v)? == Sign::Zero && within(p, q, v) {
806                cuts.push(v);
807            }
808        }
809        cuts.sort_by(|u, v| (*u - p).dot(d).total_cmp(&(*v - p).dot(d)));
810        for pair in cuts.windows(2) {
811            if pair[0] != pair[1] {
812                pieces.push((pair[0], pair[1]));
813            }
814        }
815    }
816    for (i, &(p, q)) in pieces.iter().enumerate() {
817        for &(r, s) in &pieces[i + 1..] {
818            if crosses(p, q, r, s)? {
819                return Err(FarthestError::CrossingObstacles);
820            }
821        }
822    }
823    let index = |v: Point2| {
824        u32::try_from(points.iter().position(|p| *p == v).expect("an endpoint")).unwrap_or(u32::MAX)
825    };
826    let mut constraints: Vec<Constraint> = pieces
827        .iter()
828        .map(|(p, q)| Constraint::new(index(*p), index(*q)))
829        .collect();
830    constraints.sort_unstable();
831    constraints.dedup();
832    let triangulation =
833        triangulate(&points, &constraints).map_err(|_| FarthestError::Triangulation)?;
834    let at = triangulation.points();
835    let mut out = Vec::new();
836    for t in triangulation.triangles().chunks_exact(3) {
837        let corners = [at[t[0] as usize], at[t[1] as usize], at[t[2] as usize]];
838        let centroid = Point2::new(
839            (corners[0].x + corners[1].x + corners[2].x) / 3.0,
840            (corners[0].y + corners[1].y + corners[2].y) / 3.0,
841        );
842        if contains(region, centroid)? {
843            out.push(corners);
844        }
845    }
846    Ok(out)
847}
848
849/// Whether `p` lies in the closed counter-clockwise triangle, exactly.
850pub(crate) fn in_triangle(t: &[Point2; 3], p: Point2) -> Result<bool, RouteError> {
851    for i in 0..3 {
852        if side(t[i], t[(i + 1) % 3], p)? == Sign::Negative {
853            return Ok(false);
854        }
855    }
856    Ok(true)
857}
858
859/// Whether the closed segment `pq` meets the closed triangle, exactly.
860pub(crate) fn meets_triangle(p: Point2, q: Point2, t: &[Point2; 3]) -> Result<bool, RouteError> {
861    if in_triangle(t, p)? || in_triangle(t, q)? {
862        return Ok(true);
863    }
864    for i in 0..3 {
865        if segments_meet(p, q, t[i], t[(i + 1) % 3])? {
866            return Ok(true);
867        }
868    }
869    Ok(false)
870}
871
872/// Whether two closed segments share a point, exactly.
873pub(crate) fn segments_meet(
874    p: Point2,
875    q: Point2,
876    r: Point2,
877    s: Point2,
878) -> Result<bool, RouteError> {
879    let (d1, d2) = (side(p, q, r)?, side(p, q, s)?);
880    let (d3, d4) = (side(r, s, p)?, side(r, s, q)?);
881    if d1 != d2
882        && d3 != d4
883        && d1 != Sign::Zero
884        && d2 != Sign::Zero
885        && d3 != Sign::Zero
886        && d4 != Sign::Zero
887    {
888        return Ok(true);
889    }
890    Ok((d1 == Sign::Zero && within(p, q, r))
891        || (d2 == Sign::Zero && within(p, q, s))
892        || (d3 == Sign::Zero && within(r, s, p))
893        || (d4 == Sign::Zero && within(r, s, q)))
894}
895
896/// Whether `p` lies in the closed polygon (outer ring minus the open
897/// holes), exactly.
898pub(crate) fn in_polygon(polygon: &Polygon, p: Point2) -> Result<bool, RouteError> {
899    if on_ring(&polygon.outer, p)? {
900        return Ok(true);
901    }
902    if winding(&polygon.outer, p)? == 0 {
903        return Ok(false);
904    }
905    for hole in &polygon.holes {
906        if !on_ring(hole, p)? && winding(hole, p)? != 0 {
907            return Ok(false);
908        }
909    }
910    Ok(true)
911}
912
913fn on_ring(ring: &Ring, p: Point2) -> Result<bool, RouteError> {
914    for (a, b) in ring_edges(ring) {
915        if side(a, b, p)? == Sign::Zero && within(a, b, p) {
916            return Ok(true);
917        }
918    }
919    Ok(false)
920}
921
922/// Winding number of a ring around a point off it, from exact sides.
923fn winding(ring: &Ring, p: Point2) -> Result<i32, RouteError> {
924    let mut w = 0;
925    for (a, b) in ring_edges(ring) {
926        if a.y <= p.y && b.y > p.y && side(a, b, p)? == Sign::Positive {
927            w += 1;
928        } else if b.y <= p.y && a.y > p.y && side(a, b, p)? == Sign::Negative {
929            w -= 1;
930        }
931    }
932    Ok(w)
933}
934
935#[cfg(test)]
936mod tests {
937    use super::*;
938
939    fn p(x: f64, y: f64) -> Point2 {
940        Point2::new(x, y)
941    }
942
943    fn square() -> Polygon {
944        Polygon {
945            outer: Ring {
946                points: vec![p(0.0, 0.0), p(4.0, 0.0), p(4.0, 4.0), p(0.0, 4.0)],
947            },
948            holes: Vec::new(),
949        }
950    }
951
952    #[test]
953    fn anchors_lie_in_their_triangle_and_off_every_barrier() {
954        let barrier = vec![vec![p(1.0, 1.0), p(3.0, 3.0)]];
955        let map = distance_map(&[square()], &barrier, &[p(0.5, 3.5)]).unwrap();
956        let roots = [[p(0.0, 0.0), p(4.0, 0.0), p(4.0, 4.0)]];
957        let subregion = square();
958        let search = Search {
959            map: &map,
960            subregion: &subregion,
961            roots: &roots,
962            evaluated: HashMap::new(),
963            lower: None,
964        };
965        assert!(search.admissible(0, p(3.0, 1.0)).unwrap());
966        // On the barrier, though inside the triangle.
967        assert!(!search.admissible(0, p(2.0, 2.0)).unwrap());
968        // Off the barrier, but outside the triangle.
969        assert!(!search.admissible(0, p(1.0, 3.0)).unwrap());
970    }
971
972    #[test]
973    fn a_triangle_is_outside_only_when_no_subregion_edge_meets_it() {
974        let map = distance_map(&[square()], &[], &[p(0.5, 0.5)]).unwrap();
975        let subregion = Polygon {
976            outer: Ring {
977                points: vec![p(1.0, 1.0), p(3.0, 1.0), p(3.0, 3.0), p(1.0, 3.0)],
978            },
979            holes: Vec::new(),
980        };
981        let search = Search {
982            map: &map,
983            subregion: &subregion,
984            roots: &[],
985            evaluated: HashMap::new(),
986            lower: None,
987        };
988        // First corner outside, but the triangle reaches in.
989        assert!(!search
990            .outside(&[p(0.0, 0.0), p(2.0, 0.0), p(2.0, 2.0)])
991            .unwrap());
992        assert!(search
993            .outside(&[p(0.0, 0.0), p(0.9, 0.0), p(0.0, 0.9)])
994            .unwrap());
995        assert!(!search
996            .outside(&[p(1.5, 1.5), p(2.0, 1.5), p(2.0, 2.0)])
997            .unwrap());
998    }
999}