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 (`specs/rust-port-proposal.md` §3.2):
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, PointMut};
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    impl PointMut for P2 {
131        fn set<const D: usize>(&mut self, v: f64) {
132            const {
133                assert!(D < <P2 as Point>::DIM, "Point::set: dimension out of range");
134            }
135            if D == 0 {
136                self.0 = v;
137            } else {
138                self.1 = v;
139            }
140        }
141    }
142
143    struct B {
144        corners: [[f64; 2]; 2],
145    }
146
147    impl Geometry for B {
148        type Kind = BoxTag;
149        type Point = P2;
150    }
151
152    impl IndexedAccess for B {
153        fn get_indexed<const I: usize, const D: usize>(&self) -> f64 {
154            const {
155                assert!(I < 2, "IndexedAccess::get_indexed: index out of range");
156                assert!(D < 2, "IndexedAccess::get_indexed: dimension out of range");
157            }
158            self.corners[I][D]
159        }
160
161        fn set_indexed<const I: usize, const D: usize>(&mut self, v: f64) {
162            const {
163                assert!(I < 2, "IndexedAccess::set_indexed: index out of range");
164                assert!(D < 2, "IndexedAccess::set_indexed: dimension out of range");
165            }
166            self.corners[I][D] = v;
167        }
168    }
169
170    #[test]
171    fn indexed_get_set_roundtrip() {
172        let mut b = B {
173            corners: [[0.0; 2]; 2],
174        };
175        b.set_indexed::<{ corner::MIN }, 0>(1.0);
176        b.set_indexed::<{ corner::MIN }, 1>(2.0);
177        b.set_indexed::<{ corner::MAX }, 0>(3.0);
178        b.set_indexed::<{ corner::MAX }, 1>(4.0);
179
180        // Bit-equal comparison: each slot was written with a literal
181        // f64 constant and read back without arithmetic, so the round
182        // trip must be exact. Using `to_bits()` sidesteps the
183        // `clippy::float_cmp` lint without weakening the assertion.
184        assert_eq!(
185            b.get_indexed::<{ corner::MIN }, 0>().to_bits(),
186            1.0_f64.to_bits()
187        );
188        assert_eq!(
189            b.get_indexed::<{ corner::MIN }, 1>().to_bits(),
190            2.0_f64.to_bits()
191        );
192        assert_eq!(
193            b.get_indexed::<{ corner::MAX }, 0>().to_bits(),
194            3.0_f64.to_bits()
195        );
196        assert_eq!(
197            b.get_indexed::<{ corner::MAX }, 1>().to_bits(),
198            4.0_f64.to_bits()
199        );
200    }
201
202    #[test]
203    fn corner_constants_match_boost() {
204        // `boost/geometry/core/access.hpp:36-39` pins MIN = 0, MAX = 1.
205        assert_eq!(corner::MIN, 0);
206        assert_eq!(corner::MAX, 1);
207    }
208}