use geometry_coords::CoordinateScalar;
use geometry_cs::{CartesianFamily, CoordinateSystem};
use geometry_tag::{BoxTag, PolygonTag, RingTag, SameAs};
use geometry_trait::{
Box as BoxTrait, Point as PointTrait, PointMut, Polygon as PolygonTrait, Ring as RingTrait,
corner,
};
pub trait WithinStrategy<P: PointTrait, G> {
fn within(&self, p: &P, g: &G) -> bool;
fn covered_by(&self, p: &P, g: &G) -> bool;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct WithinBox;
#[derive(Debug, Default, Clone, Copy)]
pub struct WithinRing;
#[derive(Debug, Default, Clone, Copy)]
pub struct WithinPoly;
impl<P, G> WithinStrategy<P, G> for WithinBox
where
G: BoxTrait<Point = P>,
P: PointMut,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
#[inline]
fn within(&self, p: &P, b: &G) -> bool {
let x = p.get::<0>();
let y = p.get::<1>();
let xmin = b.get_indexed::<{ corner::MIN }, 0>();
let ymin = b.get_indexed::<{ corner::MIN }, 1>();
let xmax = b.get_indexed::<{ corner::MAX }, 0>();
let ymax = b.get_indexed::<{ corner::MAX }, 1>();
xmin < x && x < xmax && ymin < y && y < ymax
}
#[inline]
fn covered_by(&self, p: &P, b: &G) -> bool {
let x = p.get::<0>();
let y = p.get::<1>();
let xmin = b.get_indexed::<{ corner::MIN }, 0>();
let ymin = b.get_indexed::<{ corner::MIN }, 1>();
let xmax = b.get_indexed::<{ corner::MAX }, 0>();
let ymax = b.get_indexed::<{ corner::MAX }, 1>();
xmin <= x && x <= xmax && ymin <= y && y <= ymax
}
}
impl<P, G> WithinStrategy<P, G> for WithinRing
where
G: RingTrait<Point = P>,
P: PointTrait,
P::Scalar: CoordinateScalar,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
#[inline]
fn within(&self, p: &P, r: &G) -> bool {
winding_result(p, r) == InOut::Interior
}
#[inline]
fn covered_by(&self, p: &P, r: &G) -> bool {
!matches!(winding_result(p, r), InOut::Exterior)
}
}
impl<P, G> WithinStrategy<P, G> for WithinPoly
where
G: PolygonTrait<Point = P>,
P: PointTrait,
P::Scalar: CoordinateScalar,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
#[inline]
fn within(&self, p: &P, pg: &G) -> bool {
if !WithinRing.within(p, pg.exterior()) {
return false;
}
for hole in pg.interiors() {
if WithinRing.covered_by(p, hole) {
return false;
}
}
true
}
#[inline]
fn covered_by(&self, p: &P, pg: &G) -> bool {
if !WithinRing.covered_by(p, pg.exterior()) {
return false;
}
for hole in pg.interiors() {
if WithinRing.within(p, hole) {
return false;
}
}
true
}
}
#[doc(hidden)]
pub trait WithinStrategyForKind {
type S: Default;
}
impl WithinStrategyForKind for BoxTag {
type S = WithinBox;
}
impl WithinStrategyForKind for RingTag {
type S = WithinRing;
}
impl WithinStrategyForKind for PolygonTag {
type S = WithinPoly;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum InOut {
Exterior,
Boundary,
Interior,
}
fn winding_result<P, R>(p: &P, r: &R) -> InOut
where
P: PointTrait,
P::Scalar: CoordinateScalar,
R: RingTrait<Point = P>,
{
let mut count: i32 = 0;
let mut it = r.points();
let Some(mut prev) = it.next() else {
return InOut::Exterior;
};
let first = prev;
let mut has_segment = false;
for curr in it {
has_segment = true;
match apply_segment(p, prev, curr) {
Step::Touches => return InOut::Boundary,
Step::Count(c) => count += c,
}
prev = curr;
}
let repeats_first =
has_segment && prev.get::<0>() == first.get::<0>() && prev.get::<1>() == first.get::<1>();
if !repeats_first {
match apply_segment(p, prev, first) {
Step::Touches => return InOut::Boundary,
Step::Count(c) => count += c,
}
}
if count == 0 {
InOut::Exterior
} else {
InOut::Interior
}
}
#[derive(Debug, Clone, Copy)]
enum Step {
Touches,
Count(i32),
}
fn apply_segment<P>(p: &P, s1: &P, s2: &P) -> Step
where
P: PointTrait,
P::Scalar: CoordinateScalar,
{
let px = p.get::<0>();
let py = p.get::<1>();
let s1x = s1.get::<0>();
let s2x = s2.get::<0>();
let s1y = s1.get::<1>();
let s2y = s2.get::<1>();
let eq1 = s1x == px;
let eq2 = s2x == px;
if eq1 && eq2 {
let (lo, hi) = if s1y <= s2y { (s1y, s2y) } else { (s2y, s1y) };
if lo <= py && py <= hi {
return Step::Touches;
}
return Step::Count(0);
}
if eq1 {
if py == s1y {
return Step::Touches;
}
return if py < s1y {
Step::Count(0)
} else if s2x > px {
Step::Count(1)
} else {
Step::Count(-1)
};
}
if eq2 {
if py == s2y {
return Step::Touches;
}
return if py < s2y {
Step::Count(0)
} else if s1x > px {
Step::Count(-1)
} else {
Step::Count(1)
};
}
let count = if s1x < px && s2x > px {
2
} else if s2x < px && s1x > px {
-2
} else {
return Step::Count(0);
};
let cross = (s2x - s1x) * (py - s1y) - (s2y - s1y) * (px - s1x);
if cross > P::Scalar::ZERO {
if count > 0 {
Step::Count(count)
} else {
Step::Count(0)
}
} else if cross < P::Scalar::ZERO {
if count < 0 {
Step::Count(count)
} else {
Step::Count(0)
}
} else {
Step::Touches
}
}
#[cfg(test)]
mod tests {
use super::{Step, WithinBox, WithinPoly, WithinRing, WithinStrategy, apply_segment};
use geometry_cs::Cartesian;
use geometry_model::{Box, Point2D, Polygon, Ring, polygon};
use geometry_trait::Point as _;
type P = Point2D<f64, Cartesian>;
fn pt(x: f64, y: f64) -> P {
Point2D::new(x, y)
}
#[allow(clippy::float_cmp)]
fn reference_step(p: &P, s1: &P, s2: &P) -> i32 {
let px = p.get::<0>();
let py = p.get::<1>();
let s1x = s1.get::<0>();
let s2x = s2.get::<0>();
let s1y = s1.get::<1>();
let s2y = s2.get::<1>();
let eq1 = s1x == px;
let eq2 = s2x == px;
if eq1 && eq2 {
let (lo, hi) = if s1y <= s2y { (s1y, s2y) } else { (s2y, s1y) };
return if lo <= py && py <= hi { i32::MIN } else { 0 };
}
let count = if eq1 {
if s2x > px { 1 } else { -1 }
} else if eq2 {
if s1x > px { -1 } else { 1 }
} else if s1x < px && s2x > px {
2
} else if s2x < px && s1x > px {
-2
} else {
0
};
if count == 0 {
return 0;
}
let side = if count == 1 || count == -1 {
let sey = if eq1 { s1y } else { s2y };
if py == sey {
0
} else if py < sey {
-count
} else {
count
}
} else {
let cross = (s2x - s1x) * (py - s1y) - (s2y - s1y) * (px - s1x);
if cross > 0.0 {
1
} else if cross < 0.0 {
-1
} else {
0
}
};
if side == 0 {
i32::MIN
} else if side * count > 0 {
count
} else {
0
}
}
fn step_code(step: Step) -> i32 {
match step {
Step::Touches => i32::MIN,
Step::Count(count) => count,
}
}
#[test]
fn segment_step_matches_the_reference_branch_matrix() {
let values = [-2.0, -1.0, 0.0, 1.0, 2.0];
for &px in &values {
for &py in &values {
for &s1x in &values {
for &s1y in &values {
for &s2x in &values {
for &s2y in &values {
let point = pt(px, py);
let first = pt(s1x, s1y);
let second = pt(s2x, s2y);
assert_eq!(
step_code(apply_segment(&point, &first, &second)),
reference_step(&point, &first, &second),
"point=({px}, {py}), segment=({s1x}, {s1y})→({s2x}, {s2y})"
);
}
}
}
}
}
}
}
fn box_polygon() -> Polygon<P> {
polygon![[(0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0), (0.0, 0.0)]]
}
#[test]
fn box_b1_inside() {
assert!(WithinPoly.within(&pt(1.0, 1.0), &box_polygon()));
}
#[test]
fn box_b2_outside() {
assert!(!WithinPoly.within(&pt(3.0, 3.0), &box_polygon()));
}
#[test]
fn box_corners_are_not_within() {
let p = box_polygon();
for (x, y) in [(0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0)] {
assert!(!WithinPoly.within(&pt(x, y), &p), "corner ({x},{y})");
}
}
#[test]
fn box_sides_are_not_within() {
let p = box_polygon();
for (x, y) in [(0.0, 1.0), (1.0, 2.0), (2.0, 1.0), (1.0, 0.0)] {
assert!(!WithinPoly.within(&pt(x, y), &p), "side ({x},{y})");
}
}
#[test]
fn triangle_interior_and_exterior() {
let t: Polygon<P> = polygon![[(0.0, 0.0), (0.0, 4.0), (6.0, 0.0), (0.0, 0.0)]];
assert!(WithinPoly.within(&pt(1.0, 1.0), &t));
assert!(!WithinPoly.within(&pt(3.0, 3.0), &t));
}
#[test]
fn hole_semantics() {
let with_hole: Polygon<P> = polygon![
[(0.0, 0.0), (0.0, 3.0), (3.0, 3.0), (3.0, 0.0), (0.0, 0.0)],
[(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0)]
];
assert!(WithinPoly.within(&pt(0.5, 0.5), &with_hole));
assert!(!WithinPoly.within(&pt(1.5, 1.5), &with_hole));
}
#[test]
fn covered_by_includes_boundary() {
let p = box_polygon();
assert!(WithinPoly.covered_by(&pt(0.0, 0.0), &p));
assert!(WithinPoly.covered_by(&pt(0.0, 1.0), &p));
assert!(WithinPoly.covered_by(&pt(1.0, 1.0), &p));
assert!(!WithinPoly.covered_by(&pt(3.0, 3.0), &p));
}
#[test]
fn box_geometry_strict_vs_non_strict() {
let b = Box::from_corners(pt(0.0, 0.0), pt(2.0, 2.0));
assert!(WithinBox.within(&pt(1.0, 1.0), &b));
assert!(!WithinBox.within(&pt(0.0, 0.0), &b));
assert!(WithinBox.covered_by(&pt(0.0, 0.0), &b));
assert!(!WithinBox.within(&pt(0.0, 1.0), &b));
assert!(WithinBox.covered_by(&pt(0.0, 1.0), &b));
assert!(!WithinBox.within(&pt(3.0, 3.0), &b));
assert!(!WithinBox.covered_by(&pt(3.0, 3.0), &b));
}
#[test]
fn ring_within_smoke() {
let r: Ring<P> = Ring::from_vec(vec![
pt(0.0, 0.0),
pt(0.0, 2.0),
pt(2.0, 2.0),
pt(2.0, 0.0),
pt(0.0, 0.0),
]);
assert!(WithinRing.within(&pt(1.0, 1.0), &r));
assert!(!WithinRing.within(&pt(0.0, 0.0), &r));
assert!(WithinRing.covered_by(&pt(0.0, 0.0), &r));
}
#[test]
fn open_ring_closes_implicitly() {
let mut r = Ring::<P, true, false>::new();
r.push(pt(0.0, 0.0));
r.push(pt(0.0, 2.0));
r.push(pt(2.0, 2.0));
r.push(pt(2.0, 0.0));
assert!(WithinRing.within(&pt(1.0, 1.0), &r));
assert!(!WithinRing.within(&pt(3.0, 3.0), &r));
}
}