Skip to main content

geometry_trait/
indexed_access.rs

1//! Two-axis indexed access for geometries that are addressed by an
2//! `(index, dim)` pair rather than `dim` alone (Box, Segment).
3//!
4//! Mirrors `boost::geometry::traits::indexed_access<G, Index, Dimension>`
5//! from `boost/geometry/core/access.hpp:79-84`, together with the
6//! `min_corner` / `max_corner` constants declared in the same header
7//! at `boost/geometry/core/access.hpp:36-39`.
8//!
9//! Design notes:
10//! * Both axes — the corner index `I` and the dimension `D` — are
11//!   const generics, matching Boost's `template <std::size_t Index,
12//!   std::size_t Dimension>` on `traits::indexed_access`. Out-of-range
13//!   `I` or `D` is rejected at the call site rather than at runtime
14//!   (see `tests/ui/indexed_out_of_range.rs`).
15//! * The Boost C++ side specialises `traits::indexed_access` separately
16//!   per `(Geometry, Index, Dimension)` triple. On the Rust side a
17//!   single trait with two const generics replaces all of those
18//!   specialisations.
19
20use crate::geometry::Geometry;
21use crate::point::Point;
22
23/// Two-axis (`Index`, `Dimension`) access for geometries built out of
24/// a pair of corner points — Box and Segment.
25///
26/// Mirrors `boost::geometry::traits::indexed_access<G, Index, Dimension>`
27/// from `boost/geometry/core/access.hpp:79-84`. The associated
28/// scalar type is projected through the geometry's `Point`, matching
29/// `boost::geometry::coordinate_type<G>::type` for indexed geometries.
30///
31/// For a Box, `I` is one of [`corner::MIN`] / [`corner::MAX`]; for a
32/// Segment, `I` is the endpoint index `0` / `1`. `D` is the
33/// coordinate dimension, with the same `0 <= D < Point::DIM`
34/// invariant as [`Point::get`].
35///
36/// # Examples
37///
38/// ```
39/// use geometry_trait::{corner, IndexedAccess};
40///
41/// // Generic helpers can require `IndexedAccess` and use the corner
42/// // constants — same Box and Segment surface.
43/// fn x_min<G>(g: &G) -> <G::Point as geometry_trait::Point>::Scalar
44/// where G: IndexedAccess { g.get_indexed::<{ corner::MIN }, 0>() }
45/// ```
46pub trait IndexedAccess: Geometry {
47    /// Read coordinate `D` of corner `I`.
48    ///
49    /// Mirrors `boost::geometry::traits::indexed_access<G, I, D>::get(g)`
50    /// (`boost/geometry/core/access.hpp:79-84`). Both `I` and `D` are
51    /// const generics: passing an out-of-range value is rejected at
52    /// the call site rather than at runtime.
53    fn get_indexed<const I: usize, const D: usize>(&self) -> <Self::Point as Point>::Scalar;
54
55    /// Write coordinate `D` of corner `I`.
56    ///
57    /// Mirrors `boost::geometry::traits::indexed_access<G, I, D>::set(g, v)`
58    /// (`boost/geometry/core/access.hpp:79-84`).
59    fn set_indexed<const I: usize, const D: usize>(
60        &mut self,
61        value: <Self::Point as Point>::Scalar,
62    );
63}
64
65/// Indices used when addressing the two corners of a Box.
66///
67/// # Examples
68///
69/// ```
70/// use geometry_trait::corner;
71/// assert_eq!(corner::MIN, 0);
72/// assert_eq!(corner::MAX, 1);
73/// ```
74///
75/// Mirrors `boost::geometry::min_corner` and `boost::geometry::max_corner`
76/// declared at `boost/geometry/core/access.hpp:36-39`. For Segment,
77/// the same `0` / `1` integers select the first / second endpoint;
78/// segment endpoints are not "min" or "max", so call sites that read
79/// segments should write the literal `0` / `1` rather than these
80/// constants — matching the Boost convention where `min_corner` /
81/// `max_corner` are box-specific names even though the underlying
82/// integers are shared.
83pub mod corner {
84    /// Index of the minimum corner of a Box.
85    ///
86    /// Mirrors `boost::geometry::min_corner`
87    /// (`boost/geometry/core/access.hpp:36`).
88    pub const MIN: usize = 0;
89
90    /// Index of the maximum corner of a Box.
91    ///
92    /// Mirrors `boost::geometry::max_corner`
93    /// (`boost/geometry/core/access.hpp:39`).
94    pub const MAX: usize = 1;
95}
96
97#[cfg(test)]
98mod tests {
99    //! Smoke test mirroring `boost/geometry/test/core/access.cpp:55-86`
100    //! (`test_indexed_get_set` / `test_indexed_get`): set all four
101    //! `(I, D)` slots of a 2D Box-like value, read them back, assert
102    //! the values round-trip.
103
104    use super::{IndexedAccess, corner};
105    use crate::geometry::Geometry;
106    use crate::point::Point;
107    use geometry_cs::Cartesian;
108    use geometry_tag::{BoxTag, PointTag};
109
110    struct P2(f64, f64);
111
112    impl Geometry for P2 {
113        type Kind = PointTag;
114        type Point = Self;
115    }
116
117    impl Point for P2 {
118        type Scalar = f64;
119        type Cs = Cartesian;
120        const DIM: usize = 2;
121
122        fn get<const D: usize>(&self) -> f64 {
123            const {
124                assert!(D < <P2 as Point>::DIM, "Point::get: dimension out of range");
125            }
126            if D == 0 { self.0 } else { self.1 }
127        }
128    }
129
130    struct B {
131        corners: [[f64; 2]; 2],
132    }
133
134    impl Geometry for B {
135        type Kind = BoxTag;
136        type Point = P2;
137    }
138
139    impl IndexedAccess for B {
140        fn get_indexed<const I: usize, const D: usize>(&self) -> f64 {
141            const {
142                assert!(I < 2, "IndexedAccess::get_indexed: index out of range");
143                assert!(D < 2, "IndexedAccess::get_indexed: dimension out of range");
144            }
145            self.corners[I][D]
146        }
147
148        fn set_indexed<const I: usize, const D: usize>(&mut self, v: f64) {
149            const {
150                assert!(I < 2, "IndexedAccess::set_indexed: index out of range");
151                assert!(D < 2, "IndexedAccess::set_indexed: dimension out of range");
152            }
153            self.corners[I][D] = v;
154        }
155    }
156
157    #[test]
158    fn indexed_get_set_roundtrip() {
159        let point = P2(5.0, 6.0);
160        assert_eq!(point.get::<0>().to_bits(), 5.0_f64.to_bits());
161        assert_eq!(point.get::<1>().to_bits(), 6.0_f64.to_bits());
162
163        let mut b = B {
164            corners: [[0.0; 2]; 2],
165        };
166        b.set_indexed::<{ corner::MIN }, 0>(1.0);
167        b.set_indexed::<{ corner::MIN }, 1>(2.0);
168        b.set_indexed::<{ corner::MAX }, 0>(3.0);
169        b.set_indexed::<{ corner::MAX }, 1>(4.0);
170
171        // Bit-equal comparison: each slot was written with a literal
172        // f64 constant and read back without arithmetic, so the round
173        // trip must be exact. Using `to_bits()` sidesteps the
174        // `clippy::float_cmp` lint without weakening the assertion.
175        assert_eq!(
176            b.get_indexed::<{ corner::MIN }, 0>().to_bits(),
177            1.0_f64.to_bits()
178        );
179        assert_eq!(
180            b.get_indexed::<{ corner::MIN }, 1>().to_bits(),
181            2.0_f64.to_bits()
182        );
183        assert_eq!(
184            b.get_indexed::<{ corner::MAX }, 0>().to_bits(),
185            3.0_f64.to_bits()
186        );
187        assert_eq!(
188            b.get_indexed::<{ corner::MAX }, 1>().to_bits(),
189            4.0_f64.to_bits()
190        );
191    }
192
193    #[test]
194    fn corner_constants_match_boost() {
195        // `boost/geometry/core/access.hpp:36-39` pins MIN = 0, MAX = 1.
196        assert_eq!(corner::MIN, 0);
197        assert_eq!(corner::MAX, 1);
198    }
199}