geometry_strategy/within.rs
1//! Per-CS strategy for point-in-polygon containment (`within` /
2//! `covered_by`).
3//!
4//! Mirrors the pieces of Boost.Geometry that collaborate to make
5//! `boost::geometry::within(p, g)` / `boost::geometry::covered_by(p, g)`
6//! work for any (point, polygonal | box) pair in any coordinate system:
7//!
8//! * `boost/geometry/strategies/within.hpp` — the per-CS
9//! `within`-strategy concept (apply/result two-phase),
10//! * `boost/geometry/strategies/covered_by.hpp` — same concept reused,
11//! * `boost/geometry/strategies/cartesian/point_in_poly_winding.hpp` —
12//! `cartesian_winding`, the default Cartesian PIP, implementing the
13//! classic winding-number algorithm with on-segment detection,
14//! * `boost/geometry/strategies/cartesian/point_in_box.hpp` —
15//! `cartesian_point_in_box`, the per-corner strict / non-strict
16//! comparisons used to fold "is the point inside this axis-aligned
17//! box" into the dispatch.
18//!
19//! The Boost concept exposes a stateful three-step API — construct a
20//! `state_type`, call `apply(point, s1, s2, state)` for every segment,
21//! then call `result(state)` to read off `-1` / `0` / `+1` (outside /
22//! boundary / interior). The Rust analogue collapses that three-step
23//! shape into a single `within` / `covered_by` pair on
24//! [`WithinStrategy`] because the per-segment walk is identical for
25//! every CS — only the per-segment kernel changes.
26//!
27//! ## Coherence note
28//!
29//! Boost dispatches on the geometry's tag via partial template
30//! specialisation — `dispatch::within<Point, Ring, _, ring_tag>` and
31//! `dispatch::within<Point, Polygon, _, polygon_tag>` are mutually
32//! exclusive because the C++ side can prove tags distinct. Rust's
33//! trait system cannot prove a downstream type does not implement
34//! several geometry traits at once, so two open blankets on one strategy
35//! struct would collide (E0119). The port reproduces Boost's tag
36//! dispatch instead: one **per-kind strategy struct** ([`WithinBox`],
37//! [`WithinRing`], [`WithinPoly`]) carries a single concept-bounded
38//! `WithinStrategy` impl — distinct `Self`, so no overlap — and the
39//! tag-keyed [`WithinStrategyForKind`] picker routes `G::Kind` to the
40//! right struct. Because the picker keys on the tag, any concept-adapted
41//! foreign type resolves through the same path as the equivalent
42//! `geometry-model` value.
43//!
44//! [`crate::intersects`] reaches point-in-polygon containment through
45//! the open [`WithinPoly`] strategy directly (not the algorithm-layer
46//! `covered_by` free fn — that would be an upward crate dependency /
47//! cycle), so both crates share the one open kernel.
48//!
49//! ## Result-code convention
50//!
51//! Mirrors Boost's `cartesian_winding::result` at
52//! `strategy/cartesian/point_in_poly_winding.hpp:69-74`:
53//!
54//! | Boost code | Meaning | `within` | `covered_by` |
55//! |-----------:|--------------------|---------:|-------------:|
56//! | `-1` | outside | `false` | `false` |
57//! | `0` | on the boundary | `false` | `true` |
58//! | `+1` | strict interior | `true` | `true` |
59//!
60//! ## Precision limit (Cartesian, `f64`)
61//!
62//! The winding kernel decides each point's side of a segment from the
63//! sign of a cross product of coordinate *differences*. For `f64` that
64//! sign is exact only while the operands stay within the mantissa: past
65//! roughly `±2^26` (`67_108_864`) the products no longer fit in 53 bits
66//! and the sign can flip, so a strict-interior / boundary / exterior
67//! classification may be wrong for coordinates beyond that magnitude.
68//! This is the same limit the overlay engine gates on with its
69//! `SAFE_ABS_MAX` range guard, and it matches Boost: the non-rescaled
70//! `cartesian_winding` shares the bound (Boost's rescaling only ever
71//! applied at the overlay/turn layer, not here). `within` does **not**
72//! reject out-of-range input — callers that work with coordinates beyond
73//! `±2^26` must scale down first.
74
75use geometry_coords::CoordinateScalar;
76use geometry_cs::{CartesianFamily, CoordinateSystem};
77use geometry_tag::{BoxTag, PolygonTag, RingTag, SameAs};
78use geometry_trait::{
79 Box as BoxTrait, Point as PointTrait, PointMut, Polygon as PolygonTrait, Ring as RingTrait,
80 corner,
81};
82
83/// A strategy for point-in-geometry containment.
84///
85/// Mirrors the per-CS `within` strategy concept declared in
86/// `boost/geometry/strategies/within.hpp` and refined per coordinate
87/// system in `strategies/cartesian/point_in_poly_winding.hpp` /
88/// `strategies/spherical/point_in_poly_winding.hpp`. The Boost concept
89/// exposes a stateful `apply(point, s1, s2, state)` accumulator plus a
90/// final `result(state)` reduction; the Rust analogue collapses the
91/// two phases into a single `within` / `covered_by` pair keyed on the
92/// geometry type, because the per-segment walk shape is identical for
93/// every CS — only the per-segment kernel changes.
94pub trait WithinStrategy<P: PointTrait, G> {
95 /// `true` iff `p` lies in the strict interior of `g`.
96 ///
97 /// Mirrors `boost::geometry::within(p, g, strategy)` from
98 /// `boost/geometry/algorithms/within.hpp` resolved through
99 /// `cartesian_winding::result == 1` at
100 /// `strategy/cartesian/point_in_poly_winding.hpp:69-74`.
101 fn within(&self, p: &P, g: &G) -> bool;
102
103 /// `true` iff `p` lies in the strict interior **or** on the
104 /// boundary of `g`.
105 ///
106 /// Mirrors `boost::geometry::covered_by(p, g, strategy)` from
107 /// `boost/geometry/algorithms/covered_by.hpp` resolved through
108 /// `cartesian_winding::result >= 0` at the same lines.
109 fn covered_by(&self, p: &P, g: &G) -> bool;
110}
111
112// =====================================================================
113// Per-kind strategy structs + tag-keyed picker
114// =====================================================================
115//
116// Each struct carries the kernel for one kind, bound on the *open*
117// concept (`G: Box`/`Ring`/`Polygon`) so any adapted foreign type
118// resolves. Distinct `Self` per kind ⇒ no overlap.
119//
120// * Box — `strategy::within::cartesian_point_in_box::apply`
121// (`strategy/cartesian/point_in_box.hpp:55-93`).
122// * Ring — `cartesian_winding_base::apply`
123// (`strategy/cartesian/point_in_poly_winding.hpp:91-131`).
124// * Polygon — `detail::within::point_in_polygon::apply`
125// (`algorithms/detail/within/point_in_geometry.hpp:200-244`):
126// within the exterior and not covered_by any hole.
127
128/// Open point-in-box strategy. See the [module docs](self).
129#[derive(Debug, Default, Clone, Copy)]
130pub struct WithinBox;
131/// Open point-in-ring (winding number) strategy. See the [module docs](self).
132#[derive(Debug, Default, Clone, Copy)]
133pub struct WithinRing;
134/// Open point-in-polygon (winding number, hole-aware) strategy. See the
135/// [module docs](self).
136#[derive(Debug, Default, Clone, Copy)]
137pub struct WithinPoly;
138
139impl<P, G> WithinStrategy<P, G> for WithinBox
140where
141 G: BoxTrait<Point = P>,
142 P: PointMut,
143 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
144{
145 #[inline]
146 fn within(&self, p: &P, b: &G) -> bool {
147 let x = p.get::<0>();
148 let y = p.get::<1>();
149 let xmin = b.get_indexed::<{ corner::MIN }, 0>();
150 let ymin = b.get_indexed::<{ corner::MIN }, 1>();
151 let xmax = b.get_indexed::<{ corner::MAX }, 0>();
152 let ymax = b.get_indexed::<{ corner::MAX }, 1>();
153 xmin < x && x < xmax && ymin < y && y < ymax
154 }
155
156 #[inline]
157 fn covered_by(&self, p: &P, b: &G) -> bool {
158 let x = p.get::<0>();
159 let y = p.get::<1>();
160 let xmin = b.get_indexed::<{ corner::MIN }, 0>();
161 let ymin = b.get_indexed::<{ corner::MIN }, 1>();
162 let xmax = b.get_indexed::<{ corner::MAX }, 0>();
163 let ymax = b.get_indexed::<{ corner::MAX }, 1>();
164 xmin <= x && x <= xmax && ymin <= y && y <= ymax
165 }
166}
167
168impl<P, G> WithinStrategy<P, G> for WithinRing
169where
170 G: RingTrait<Point = P>,
171 P: PointTrait,
172 P::Scalar: CoordinateScalar,
173 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
174{
175 #[inline]
176 fn within(&self, p: &P, r: &G) -> bool {
177 winding_result(p, r) == InOut::Interior
178 }
179
180 #[inline]
181 fn covered_by(&self, p: &P, r: &G) -> bool {
182 !matches!(winding_result(p, r), InOut::Exterior)
183 }
184}
185
186impl<P, G> WithinStrategy<P, G> for WithinPoly
187where
188 G: PolygonTrait<Point = P>,
189 P: PointTrait,
190 P::Scalar: CoordinateScalar,
191 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
192{
193 #[inline]
194 fn within(&self, p: &P, pg: &G) -> bool {
195 if !WithinRing.within(p, pg.exterior()) {
196 return false;
197 }
198 for hole in pg.interiors() {
199 if WithinRing.covered_by(p, hole) {
200 return false;
201 }
202 }
203 true
204 }
205
206 #[inline]
207 fn covered_by(&self, p: &P, pg: &G) -> bool {
208 if !WithinRing.covered_by(p, pg.exterior()) {
209 return false;
210 }
211 for hole in pg.interiors() {
212 if WithinRing.within(p, hole) {
213 return false;
214 }
215 }
216 true
217 }
218}
219
220/// Type-level "which `WithinStrategy` struct does this geometry *kind*
221/// use". One impl per [`geometry_tag`] kind tag, keyed on the tag (never a
222/// concept blanket — that would overlap, E0119). The
223/// [`crate::within`]/[`crate::covered_by`] free functions route
224/// `G → G::Kind → S` through this trait.
225#[doc(hidden)]
226pub trait WithinStrategyForKind {
227 /// The per-kind [`WithinStrategy`] struct this tag is computed with.
228 type S: Default;
229}
230
231impl WithinStrategyForKind for BoxTag {
232 type S = WithinBox;
233}
234impl WithinStrategyForKind for RingTag {
235 type S = WithinRing;
236}
237impl WithinStrategyForKind for PolygonTag {
238 type S = WithinPoly;
239}
240
241// ---- Winding-number kernel ------------------------------------------
242
243/// Tri-state outcome of the winding-number walk.
244///
245/// Mirrors the integer return of `cartesian_winding::result` at
246/// `strategy/cartesian/point_in_poly_winding.hpp:69-74`:
247/// `-1` outside, `0` on the boundary, `+1` strict interior.
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
249enum InOut {
250 Exterior,
251 Boundary,
252 Interior,
253}
254
255/// Walk the segments of `r` accumulating the winding count and the
256/// "on a segment" flag, then collapse to an [`InOut`].
257///
258/// Mirrors `cartesian_winding_base::apply` together with `result` at
259/// `strategy/cartesian/point_in_poly_winding.hpp:91-131, 69-74`. The
260/// closing edge for an open ring is added explicitly here — matches
261/// Boost's `closed_clockwise_view` wrap done one layer up at
262/// `algorithms/detail/within/point_in_geometry.hpp`.
263fn winding_result<P, R>(p: &P, r: &R) -> InOut
264where
265 P: PointTrait,
266 P::Scalar: CoordinateScalar,
267 R: RingTrait<Point = P>,
268{
269 let mut count: i32 = 0;
270 let mut it = r.points();
271 let Some(mut prev) = it.next() else {
272 // Empty ring — outside by convention. Mirrors the
273 // `boost::size(ring) < minimum_ring_size` guard at
274 // `algorithms/detail/within/point_in_geometry.hpp:198`.
275 return InOut::Exterior;
276 };
277 let first = prev;
278 let mut has_segment = false;
279 for curr in it {
280 has_segment = true;
281 match apply_segment(p, prev, curr) {
282 Step::Touches => return InOut::Boundary,
283 Step::Count(c) => count += c,
284 }
285 prev = curr;
286 }
287 // Close an open coordinate sequence explicitly. A repeated closing
288 // vertex already contributed through the preceding real edge.
289 let repeats_first =
290 has_segment && prev.get::<0>() == first.get::<0>() && prev.get::<1>() == first.get::<1>();
291 if !repeats_first {
292 match apply_segment(p, prev, first) {
293 Step::Touches => return InOut::Boundary,
294 Step::Count(c) => count += c,
295 }
296 }
297 if count == 0 {
298 InOut::Exterior
299 } else {
300 InOut::Interior
301 }
302}
303
304/// Per-segment outcome of the winding kernel.
305#[derive(Debug, Clone, Copy)]
306enum Step {
307 /// The point lies on this segment; the walk can short-circuit.
308 Touches,
309 /// Contribution to the running winding count.
310 Count(i32),
311}
312
313/// Single-segment contribution of the winding number, plus the
314/// on-segment short-circuit.
315///
316/// Mirrors `cartesian_winding_base::apply` at
317/// `strategy/cartesian/point_in_poly_winding.hpp:91-131` together
318/// with its `check_segment` / `check_touch` / `calculate_count` /
319/// `side_equal` helpers (lines 139-217). The cartesian side strategy
320/// reduces to the cross-product sign
321/// `(s2.x - s1.x) * (p.y - s1.y) - (s2.y - s1.y) * (p.x - s1.x)`
322/// (`strategy/cartesian/side_by_triangle.hpp:178-200`).
323fn apply_segment<P>(p: &P, s1: &P, s2: &P) -> Step
324where
325 P: PointTrait,
326 P::Scalar: CoordinateScalar,
327{
328 let px = p.get::<0>();
329 let py = p.get::<1>();
330 let s1x = s1.get::<0>();
331 let s2x = s2.get::<0>();
332 let s1y = s1.get::<1>();
333 let s2y = s2.get::<1>();
334
335 let eq1 = s1x == px;
336 let eq2 = s2x == px;
337
338 // check_touch: vertical segment exactly on the point's x.
339 // Mirrors lines 154-184 of point_in_poly_winding.hpp.
340 if eq1 && eq2 {
341 let (lo, hi) = if s1y <= s2y { (s1y, s2y) } else { (s2y, s1y) };
342 if lo <= py && py <= hi {
343 return Step::Touches;
344 }
345 return Step::Count(0);
346 }
347
348 // An endpoint on the ray contributes a half count only when it is
349 // vertically below the query. This is the direct form of Boost's
350 // `side_equal * count > 0` reduction.
351 if eq1 {
352 if py == s1y {
353 return Step::Touches;
354 }
355 return if py < s1y {
356 Step::Count(0)
357 } else if s2x > px {
358 Step::Count(1)
359 } else {
360 Step::Count(-1)
361 };
362 }
363 if eq2 {
364 if py == s2y {
365 return Step::Touches;
366 }
367 return if py < s2y {
368 Step::Count(0)
369 } else if s1x > px {
370 Step::Count(-1)
371 } else {
372 Step::Count(1)
373 };
374 }
375
376 let count = if s1x < px && s2x > px {
377 2
378 } else if s2x < px && s1x > px {
379 -2
380 } else {
381 return Step::Count(0);
382 };
383
384 // Cartesian side: sign of (s2 - s1) × (p - s1). A zero side is a
385 // boundary touch; otherwise it contributes only when its sign agrees
386 // with the crossing direction.
387 let cross = (s2x - s1x) * (py - s1y) - (s2y - s1y) * (px - s1x);
388 if cross > P::Scalar::ZERO {
389 if count > 0 {
390 Step::Count(count)
391 } else {
392 Step::Count(0)
393 }
394 } else if cross < P::Scalar::ZERO {
395 if count < 0 {
396 Step::Count(count)
397 } else {
398 Step::Count(0)
399 }
400 } else {
401 Step::Touches
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 //! Reference values from `geometry/test/strategies/winding.cpp:19-73`
408 //! (the Cartesian section). Each test cites the C++ line(s) it
409 //! mirrors.
410
411 use super::{Step, WithinBox, WithinPoly, WithinRing, WithinStrategy, apply_segment};
412 use geometry_cs::Cartesian;
413 use geometry_model::{Box, Point2D, Polygon, Ring, polygon};
414 use geometry_trait::Point as _;
415
416 type P = Point2D<f64, Cartesian>;
417
418 fn pt(x: f64, y: f64) -> P {
419 Point2D::new(x, y)
420 }
421
422 #[allow(clippy::float_cmp)]
423 fn reference_step(p: &P, s1: &P, s2: &P) -> i32 {
424 let px = p.get::<0>();
425 let py = p.get::<1>();
426 let s1x = s1.get::<0>();
427 let s2x = s2.get::<0>();
428 let s1y = s1.get::<1>();
429 let s2y = s2.get::<1>();
430
431 let eq1 = s1x == px;
432 let eq2 = s2x == px;
433 if eq1 && eq2 {
434 let (lo, hi) = if s1y <= s2y { (s1y, s2y) } else { (s2y, s1y) };
435 return if lo <= py && py <= hi { i32::MIN } else { 0 };
436 }
437
438 let count = if eq1 {
439 if s2x > px { 1 } else { -1 }
440 } else if eq2 {
441 if s1x > px { -1 } else { 1 }
442 } else if s1x < px && s2x > px {
443 2
444 } else if s2x < px && s1x > px {
445 -2
446 } else {
447 0
448 };
449 if count == 0 {
450 return 0;
451 }
452
453 let side = if count == 1 || count == -1 {
454 let sey = if eq1 { s1y } else { s2y };
455 if py == sey {
456 0
457 } else if py < sey {
458 -count
459 } else {
460 count
461 }
462 } else {
463 let cross = (s2x - s1x) * (py - s1y) - (s2y - s1y) * (px - s1x);
464 if cross > 0.0 {
465 1
466 } else if cross < 0.0 {
467 -1
468 } else {
469 0
470 }
471 };
472 if side == 0 {
473 i32::MIN
474 } else if side * count > 0 {
475 count
476 } else {
477 0
478 }
479 }
480
481 fn step_code(step: Step) -> i32 {
482 match step {
483 Step::Touches => i32::MIN,
484 Step::Count(count) => count,
485 }
486 }
487
488 #[test]
489 fn segment_step_matches_the_reference_branch_matrix() {
490 let values = [-2.0, -1.0, 0.0, 1.0, 2.0];
491 for &px in &values {
492 for &py in &values {
493 for &s1x in &values {
494 for &s1y in &values {
495 for &s2x in &values {
496 for &s2y in &values {
497 let point = pt(px, py);
498 let first = pt(s1x, s1y);
499 let second = pt(s2x, s2y);
500 assert_eq!(
501 step_code(apply_segment(&point, &first, &second)),
502 reference_step(&point, &first, &second),
503 "point=({px}, {py}), segment=({s1x}, {s1y})→({s2x}, {s2y})"
504 );
505 }
506 }
507 }
508 }
509 }
510 }
511 }
512
513 fn box_polygon() -> Polygon<P> {
514 polygon![[(0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0), (0.0, 0.0)]]
515 }
516
517 /// `winding.cpp:30` — `b1` interior point.
518 #[test]
519 fn box_b1_inside() {
520 assert!(WithinPoly.within(&pt(1.0, 1.0), &box_polygon()));
521 }
522
523 /// `winding.cpp:31` — `b2` exterior point.
524 #[test]
525 fn box_b2_outside() {
526 assert!(!WithinPoly.within(&pt(3.0, 3.0), &box_polygon()));
527 }
528
529 /// `winding.cpp:34-37` — all four corners are "officially false".
530 #[test]
531 fn box_corners_are_not_within() {
532 let p = box_polygon();
533 for (x, y) in [(0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0)] {
534 assert!(!WithinPoly.within(&pt(x, y), &p), "corner ({x},{y})");
535 }
536 }
537
538 /// `winding.cpp:40-43` — all four sides are "officially false".
539 #[test]
540 fn box_sides_are_not_within() {
541 let p = box_polygon();
542 for (x, y) in [(0.0, 1.0), (1.0, 2.0), (2.0, 1.0), (1.0, 0.0)] {
543 assert!(!WithinPoly.within(&pt(x, y), &p), "side ({x},{y})");
544 }
545 }
546
547 /// `winding.cpp:46-47` — triangle interior / exterior.
548 #[test]
549 fn triangle_interior_and_exterior() {
550 let t: Polygon<P> = polygon![[(0.0, 0.0), (0.0, 4.0), (6.0, 0.0), (0.0, 0.0)]];
551 assert!(WithinPoly.within(&pt(1.0, 1.0), &t));
552 assert!(!WithinPoly.within(&pt(3.0, 3.0), &t));
553 }
554
555 /// `winding.cpp:58-60` — polygon-with-hole semantics: inside the
556 /// outer-but-outside the hole is within; inside the hole is not.
557 #[test]
558 fn hole_semantics() {
559 let with_hole: Polygon<P> = polygon![
560 [(0.0, 0.0), (0.0, 3.0), (3.0, 3.0), (3.0, 0.0), (0.0, 0.0)],
561 [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0)]
562 ];
563 // h1
564 assert!(WithinPoly.within(&pt(0.5, 0.5), &with_hole));
565 // h2a — inside the hole
566 assert!(!WithinPoly.within(&pt(1.5, 1.5), &with_hole));
567 }
568
569 /// `covered_by` inverts the boundary rule: corners and sides are
570 /// covered, but external points are not. Mirrors the Boost
571 /// `result >= 0` projection at
572 /// `strategy/cartesian/point_in_poly_winding.hpp:69-74`.
573 #[test]
574 fn covered_by_includes_boundary() {
575 let p = box_polygon();
576 assert!(WithinPoly.covered_by(&pt(0.0, 0.0), &p));
577 assert!(WithinPoly.covered_by(&pt(0.0, 1.0), &p));
578 assert!(WithinPoly.covered_by(&pt(1.0, 1.0), &p));
579 assert!(!WithinPoly.covered_by(&pt(3.0, 3.0), &p));
580 }
581
582 /// `Box`-as-geometry path: strict-vs-non-strict per-dimension.
583 /// Mirrors `cartesian_point_in_box` at
584 /// `strategy/cartesian/point_in_box.hpp:55-93`.
585 #[test]
586 fn box_geometry_strict_vs_non_strict() {
587 let b = Box::from_corners(pt(0.0, 0.0), pt(2.0, 2.0));
588 // strict interior
589 assert!(WithinBox.within(&pt(1.0, 1.0), &b));
590 // boundary: corner
591 assert!(!WithinBox.within(&pt(0.0, 0.0), &b));
592 assert!(WithinBox.covered_by(&pt(0.0, 0.0), &b));
593 // boundary: side
594 assert!(!WithinBox.within(&pt(0.0, 1.0), &b));
595 assert!(WithinBox.covered_by(&pt(0.0, 1.0), &b));
596 // outside
597 assert!(!WithinBox.within(&pt(3.0, 3.0), &b));
598 assert!(!WithinBox.covered_by(&pt(3.0, 3.0), &b));
599 }
600
601 /// Ring-only path — same kernel, no exterior/interior split.
602 #[test]
603 fn ring_within_smoke() {
604 let r: Ring<P> = Ring::from_vec(vec![
605 pt(0.0, 0.0),
606 pt(0.0, 2.0),
607 pt(2.0, 2.0),
608 pt(2.0, 0.0),
609 pt(0.0, 0.0),
610 ]);
611 assert!(WithinRing.within(&pt(1.0, 1.0), &r));
612 assert!(!WithinRing.within(&pt(0.0, 0.0), &r));
613 assert!(WithinRing.covered_by(&pt(0.0, 0.0), &r));
614 }
615
616 /// Open ring (no repeated closing vertex): the kernel must add
617 /// the implicit `last -> first` edge so containment still works.
618 #[test]
619 fn open_ring_closes_implicitly() {
620 let mut r = Ring::<P, true, false>::new();
621 r.push(pt(0.0, 0.0));
622 r.push(pt(0.0, 2.0));
623 r.push(pt(2.0, 2.0));
624 r.push(pt(2.0, 0.0));
625 assert!(WithinRing.within(&pt(1.0, 1.0), &r));
626 assert!(!WithinRing.within(&pt(3.0, 3.0), &r));
627 }
628}