Skip to main content

geometry_model/
boxg.rs

1//! Default `model::Box<P>` — two corner points (min / max) stored
2//! inline. The module is named `boxg` because `box` is a Rust
3//! keyword; the type itself keeps the C++ name.
4//!
5//! Mirrors `boost::geometry::model::box<Point>` declared in
6//! `boost/geometry/geometries/box.hpp:64-172`, together with the
7//! trait specialisations the same header provides under
8//! `namespace traits` (`tag`, `point_type`, `indexed_access`) at
9//! `boost/geometry/geometries/box.hpp:187-247`. The Rust port
10//! collapses those specialisations into the three trait impls below
11//! ([`Geometry`], [`IndexedAccess`], [`BoxTrait`]), keyed off the
12//! generic point parameter `P`.
13//!
14//! Boost stores the two corners as `m_min_corner` / `m_max_corner`
15//! members (`box.hpp:165-170`); the Rust port stores them in a
16//! `[P; 2]` array so the `(I, D)` indexed access can write
17//! `corners[I].set::<D>(v)` without a `match` on `I`. The shared
18//! corner indices come from
19//! [`geometry_trait::corner::MIN`] / [`geometry_trait::corner::MAX`]
20//! (mirroring `boost::geometry::min_corner` / `max_corner` at
21//! `boost/geometry/core/access.hpp:36-39`).
22
23use geometry_tag::BoxTag;
24use geometry_trait::{
25    Box as BoxTrait, Geometry, IndexedAccess, Point as PointTrait, PointMut, corner,
26};
27
28/// Axis-aligned bounding box.
29///
30/// Mirrors `boost::geometry::model::box<P>` from
31/// `boost/geometry/geometries/box.hpp:64-172`. The two corners are
32/// addressed by [`corner::MIN`] / [`corner::MAX`], matching the
33/// `traits::indexed_access<box, min_corner|max_corner, D>`
34/// specialisations at `boost/geometry/geometries/box.hpp:213-247`.
35#[derive(Debug, Default, Clone, Copy, PartialEq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct Box<P: PointTrait> {
38    corners: [P; 2],
39}
40
41impl<P: PointTrait> Box<P> {
42    /// Construct a box from its minimum and maximum corners.
43    ///
44    /// Mirrors the `box(Point const& min_corner, Point const& max_corner)`
45    /// constructor at `boost/geometry/geometries/box.hpp:103-110`.
46    #[must_use]
47    pub const fn from_corners(min: P, max: P) -> Self {
48        Self {
49            corners: [min, max],
50        }
51    }
52
53    /// Borrow the minimum corner.
54    ///
55    /// Mirrors `b.min_corner()` on `model::box`
56    /// (`boost/geometry/geometries/box.hpp:138-144`); returns a
57    /// reference rather than copying.
58    #[must_use]
59    pub const fn min(&self) -> &P {
60        &self.corners[corner::MIN]
61    }
62
63    /// Borrow the maximum corner.
64    ///
65    /// Mirrors `b.max_corner()` on `model::box`
66    /// (`boost/geometry/geometries/box.hpp:149-155`).
67    #[must_use]
68    pub const fn max(&self) -> &P {
69        &self.corners[corner::MAX]
70    }
71}
72
73/// Tag this concrete type as a Box. Mirrors the
74/// `traits::tag<model::box<...>>` specialisation at
75/// `boost/geometry/geometries/box.hpp:189-192`.
76impl<P: PointTrait> Geometry for Box<P> {
77    type Kind = BoxTag;
78    type Point = P;
79}
80
81/// Two-axis access on the box. Collapses the
82/// `traits::indexed_access<box, min_corner|max_corner, D>`
83/// specialisations at `boost/geometry/geometries/box.hpp:213-247`
84/// into one Rust impl: the `I` axis selects the corner, `D` then
85/// dispatches to `Point::get` / `Point::set` on that corner.
86impl<P: PointMut> IndexedAccess for Box<P> {
87    #[inline]
88    fn get_indexed<const I: usize, const D: usize>(&self) -> P::Scalar {
89        self.corners[I].get::<D>()
90    }
91
92    #[inline]
93    fn set_indexed<const I: usize, const D: usize>(&mut self, value: P::Scalar) {
94        self.corners[I].set::<D>(value);
95    }
96}
97
98/// Wire the Box concept up. The trait body is empty — the
99/// concept is satisfied by the two super-bounds [`Geometry`] and
100/// [`IndexedAccess`] already proven above.
101impl<P: PointMut> BoxTrait for Box<P> {}
102
103#[cfg(test)]
104mod tests {
105    //! Round-trip + tag-resolution + `check_box` witness for
106    //! [`Box`]. Mirrors
107    //! `boost/geometry/test/core/access.cpp:55-86` (the
108    //! `test_indexed_get_set` helper applied to a box) and the
109    //! box-tag arm of `boost/geometry/test/core/tag.cpp`.
110
111    use super::Box;
112    use crate::point::Point2D;
113    use geometry_cs::Cartesian;
114    use geometry_tag::BoxTag;
115    use geometry_trait::{Geometry, IndexedAccess, check_box, corner};
116
117    #[test]
118    fn box_round_trip_all_four_slots() {
119        let mut b = Box::from_corners(
120            Point2D::<f64, Cartesian>::default(),
121            Point2D::<f64, Cartesian>::default(),
122        );
123        b.set_indexed::<{ corner::MIN }, 0>(1.0);
124        b.set_indexed::<{ corner::MIN }, 1>(2.0);
125        b.set_indexed::<{ corner::MAX }, 0>(3.0);
126        b.set_indexed::<{ corner::MAX }, 1>(4.0);
127
128        // Bit-equal comparison: same rationale as the segment test —
129        // values written and read back without arithmetic must
130        // round-trip exactly.
131        assert_eq!(
132            b.get_indexed::<{ corner::MIN }, 0>().to_bits(),
133            1.0_f64.to_bits()
134        );
135        assert_eq!(
136            b.get_indexed::<{ corner::MIN }, 1>().to_bits(),
137            2.0_f64.to_bits()
138        );
139        assert_eq!(
140            b.get_indexed::<{ corner::MAX }, 0>().to_bits(),
141            3.0_f64.to_bits()
142        );
143        assert_eq!(
144            b.get_indexed::<{ corner::MAX }, 1>().to_bits(),
145            4.0_f64.to_bits()
146        );
147    }
148
149    #[test]
150    fn box_min_and_max_accessors() {
151        use geometry_trait::Point as PointTrait;
152
153        let lo = Point2D::<f64, Cartesian>::new(1.0, 2.0);
154        let hi = Point2D::<f64, Cartesian>::new(3.0, 4.0);
155        let b = Box::from_corners(lo, hi);
156        // `Point<T, D, Cs>` derives `PartialEq`, but only when its
157        // phantom `Cs` parameter does — and `Cartesian` does not.
158        // Compare per-coordinate via the `Point` trait instead.
159        assert_eq!(b.min().get::<0>().to_bits(), 1.0_f64.to_bits());
160        assert_eq!(b.min().get::<1>().to_bits(), 2.0_f64.to_bits());
161        assert_eq!(b.max().get::<0>().to_bits(), 3.0_f64.to_bits());
162        assert_eq!(b.max().get::<1>().to_bits(), 4.0_f64.to_bits());
163    }
164
165    #[test]
166    fn box_kind_is_box_tag() {
167        fn k<T: Geometry<Kind = BoxTag>>() {}
168        k::<Box<Point2D<f64, Cartesian>>>();
169    }
170
171    #[test]
172    fn box_satisfies_concept() {
173        check_box::<Box<Point2D<f64, Cartesian>>>();
174    }
175}