1use 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
53pub const MAX_CELLS: usize = 20_000;
55
56#[derive(Debug, Clone, Copy, PartialEq)]
58#[non_exhaustive]
59pub enum MapError {
60 Route(RouteError),
64 NoTargets,
66 TargetOutside {
68 index: usize,
70 },
71 InvalidFactor {
73 index: usize,
75 },
76 InvalidWeight {
78 index: usize,
80 },
81 InvalidSpacing,
83 CostCrossing {
88 index: usize,
90 },
91}
92
93impl From<RouteError> for MapError {
94 fn from(error: RouteError) -> Self {
95 Self::Route(error)
96 }
97}
98
99#[derive(Debug, Clone)]
102pub struct DistanceMap {
103 region: Vec<Polygon>,
104 walls: Vec<(Point2, Point2)>,
106 obstacles: Vec<(Point2, Point2)>,
107 nodes: Vec<Point2>,
108 graph: Graph,
109 distance: Vec<f64>,
112 next: Vec<usize>,
114 target: Vec<usize>,
116 sites: Vec<Point2>,
118 weights: Vec<f64>,
120}
121
122#[derive(Debug, Clone, PartialEq)]
124#[non_exhaustive]
125pub struct Reach {
126 pub target: usize,
129 pub route: Route,
132 pub distance: f64,
136}
137
138pub 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
152pub 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
167pub 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
185pub 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 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 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 #[must_use]
283 pub fn graph_vertices(&self) -> usize {
284 self.nodes.len()
285 }
286
287 #[must_use]
289 pub fn targets(&self) -> usize {
290 self.sites.len()
291 }
292
293 pub(crate) fn nodes(&self) -> &[Point2] {
295 &self.nodes
296 }
297
298 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 pub(crate) fn region(&self) -> &[Polygon] {
313 &self.region
314 }
315
316 pub(crate) fn obstacles(&self) -> &[(Point2, Point2)] {
318 &self.obstacles
319 }
320
321 pub(crate) fn sites(&self) -> &[Point2] {
323 &self.sites
324 }
325
326 pub(crate) fn weights(&self) -> &[f64] {
328 &self.weights
329 }
330
331 pub(crate) fn same_space(&self, other: &Self) -> bool {
334 self.region == other.region && self.walls == other.walls
335 }
336
337 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 length: length - self.weights[target],
374 graph_vertices: self.nodes.len(),
375 },
376 distance: length,
377 }))
378 }
379
380 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 pub(crate) fn at(&self, point: Point2) -> Result<Option<f64>, RouteError> {
433 Ok(self.via(point)?.map(|(length, _)| length))
434 }
435
436 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#[derive(Debug, Clone, Copy, PartialEq)]
455pub struct LengthInterval {
456 pub lower: f64,
458 pub upper: f64,
460}
461
462#[derive(Debug, Clone, Copy, PartialEq)]
464#[non_exhaustive]
465pub struct Farthest {
466 pub distance: LengthInterval,
469 pub witness: Option<Point2>,
473 pub converged: bool,
476 pub cells: usize,
478}
479
480#[derive(Debug, Clone, Copy, PartialEq)]
482#[non_exhaustive]
483pub enum FarthestError {
484 Route(RouteError),
486 InvalidTolerance,
488 CrossingObstacles,
492 Triangulation,
495 Unreachable {
499 triangle: [Point2; 3],
501 },
502 Empty,
504 MismatchedMaps,
507}
508
509impl From<RouteError> for FarthestError {
510 fn from(error: RouteError) -> Self {
511 Self::Route(error)
512 }
513}
514
515pub 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#[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 fn cmp(&self, other: &Self) -> Ordering {
559 self.upper
560 .total_cmp(&other.upper)
561 .then(other.order.cmp(&self.order))
562 }
563}
564
565pub 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 let relative = (map.hops() as f64 + 8.0) * 2.0 * f64::EPSILON;
589 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 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 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 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
755pub(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
768pub(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
786pub(crate) fn free_triangles(map: &DistanceMap) -> Result<Vec<[Point2; 3]>, FarthestError> {
790 free_triangles_in(&map.region, &map.obstacles)
791}
792
793pub(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
849pub(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
859pub(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
872pub(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
896pub(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
922fn 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 assert!(!search.admissible(0, p(2.0, 2.0)).unwrap());
968 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 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}