Skip to main content

geometry_trait/
segment.rs

1//! The [`Segment`] concept and the [`segment_start`] / [`segment_end`]
2//! materialiser helpers.
3//!
4//! Mirrors `doc/concept/segment.qbk` and the model in
5//! `boost/geometry/geometries/segment.hpp`. The two-axis access
6//! pattern (endpoint index, then dimension) is provided by
7//! [`crate::IndexedAccess`]; this module layers the concept marker on
8//! top and exposes the two endpoints as full [`crate::Point`] values
9//! reconstructed on demand from the indexed accessors.
10//!
11//! Design notes:
12//! * Boost spells the two endpoints as `s.first` / `s.second` on
13//!   `model::segment` (see `boost/geometry/geometries/segment.hpp:55-71`)
14//!   and reads/writes them through
15//!   `traits::indexed_access<segment, 0, D>` /
16//!   `traits::indexed_access<segment, 1, D>`
17//!   (`boost/geometry/geometries/segment.hpp:136-169`).
18//! * The Rust port keeps only the indexed view as the *required* shape
19//!   on the concept — implementers do not need to surface
20//!   `&Self::Point` accessors directly. The free helpers below
21//!   reconstruct a [`Point`] value via `Default::default()` followed
22//!   by per-dimension `set::<D>` writes driven by a private
23//!   const-recursive iterator. This matches the Boost behaviour where
24//!   `indexed_access::set` writes the dimension on the underlying
25//!   endpoint point.
26
27use crate::geometry::Geometry;
28use crate::indexed_access::IndexedAccess;
29use crate::point::{Point, PointMut};
30use geometry_tag::SegmentTag;
31
32/// A two-point segment.
33///
34/// Models `doc/concept/segment.qbk` — equivalent in C++ to the trait
35/// specialisations on `model::segment` in
36/// `boost/geometry/geometries/segment.hpp:119-183`. Implementers
37/// provide [`IndexedAccess`] for `I` ∈ {`0`, `1`}, where `0` is the
38/// start endpoint and `1` is the end endpoint. The endpoints can but
39/// need not be materialised as `Point` values; [`segment_start`] /
40/// [`segment_end`] reconstruct them from the indexed accessors on
41/// demand.
42///
43/// # Examples
44///
45/// ```
46/// use geometry_trait::Segment;
47/// fn _accepts<S: Segment>(_s: &S) {}
48/// ```
49pub trait Segment: Geometry<Kind = SegmentTag> + IndexedAccess {}
50
51/// Materialise the start endpoint of a segment as a [`Point`] value.
52///
53/// Mirrors reading `s.first` on `model::segment`
54/// (`boost/geometry/geometries/segment.hpp:142-150`). The point is
55/// built via [`Default::default`] and one `set::<D>` call per
56/// dimension, with each value sourced from
57/// `s.get_indexed::<0, D>()`.
58///
59/// # Examples
60///
61/// ```
62/// use geometry_trait::{segment_start, PointMut, Segment};
63/// fn _ex<S: Segment>(s: &S) where S::Point: Default + PointMut { let _ = segment_start(s); }
64/// ```
65pub fn segment_start<S: Segment>(s: &S) -> S::Point
66where
67    S::Point: Default + PointMut,
68{
69    materialise::<S, 0>(s)
70}
71
72/// Materialise the end endpoint of a segment as a [`Point`] value.
73///
74/// Mirrors reading `s.second` on `model::segment`
75/// (`boost/geometry/geometries/segment.hpp:160-168`). The point is
76/// built via [`Default::default`] and one `set::<D>` call per
77/// dimension, with each value sourced from
78/// `s.get_indexed::<1, D>()`.
79///
80/// # Examples
81///
82/// ```
83/// use geometry_trait::{segment_end, PointMut, Segment};
84/// fn _ex<S: Segment>(s: &S) where S::Point: Default + PointMut { let _ = segment_end(s); }
85/// ```
86pub fn segment_end<S: Segment>(s: &S) -> S::Point
87where
88    S::Point: Default + PointMut,
89{
90    materialise::<S, 1>(s)
91}
92
93/// Reconstruct a [`Point`] from the indexed accessors of a geometry.
94///
95/// `I` is the corner / endpoint index (`0` / `1` for a segment;
96/// [`crate::corner::MIN`] / [`crate::corner::MAX`] for a box). The
97/// returned point has `set::<D>(get_indexed::<I, D>())` written for
98/// each `D` in `0..Point::DIM`.
99///
100/// Shared by [`segment_start`] / [`segment_end`] in this module and
101/// by `box_min` / `box_max` in [`crate::boxg`]: both concepts use the
102/// same `(I, D)` two-axis access from
103/// `boost/geometry/core/access.hpp:79-84`, so the materialiser is
104/// also shared.
105pub(crate) fn materialise<G, const I: usize>(g: &G) -> G::Point
106where
107    G: IndexedAccess,
108    G::Point: Default + PointMut,
109{
110    let mut p: G::Point = Default::default();
111    match <G::Point as Point>::DIM {
112        0 => {}
113        1 => <Step<0, 1> as WriteDims<0, 1>>::run::<G, I>(g, &mut p),
114        2 => <Step<0, 2> as WriteDims<0, 2>>::run::<G, I>(g, &mut p),
115        3 => <Step<0, 3> as WriteDims<0, 3>>::run::<G, I>(g, &mut p),
116        4 => <Step<0, 4> as WriteDims<0, 4>>::run::<G, I>(g, &mut p),
117        _ => panic!("materialise: Point::DIM exceeds MAX_DIM (4)"),
118    }
119    p
120}
121
122/// Marker carrying a `(cursor, end)` pair of dimensions for the
123/// indexed-access materialiser. Private; reachable only via
124/// [`materialise`].
125struct Step<const D: usize, const N: usize>;
126
127/// Sealed const-recursive iterator that writes each dimension of a
128/// point by copying from `get_indexed::<I, D>()`. Mirrors the shape
129/// of the [`crate::point::fold_dims`] recursion, but with `set::<D>`
130/// as the operation rather than a user-supplied closure.
131trait WriteDims<const D: usize, const N: usize>: sealed::Sealed<D, N> {
132    fn run<G, const I: usize>(g: &G, p: &mut G::Point)
133    where
134        G: IndexedAccess,
135        G::Point: PointMut;
136}
137
138mod sealed {
139    pub trait Sealed<const D: usize, const N: usize> {}
140}
141
142/// Base case: `D == N`, nothing left to write.
143impl<const N: usize> sealed::Sealed<N, N> for Step<N, N> {}
144impl<const N: usize> WriteDims<N, N> for Step<N, N> {
145    #[inline]
146    fn run<G, const I: usize>(_g: &G, _p: &mut G::Point)
147    where
148        G: IndexedAccess,
149        G::Point: PointMut,
150    {
151    }
152}
153
154/// Inductive step for one fixed `(D, N)` pair. Writes dimension `D`
155/// of the destination point from `get_indexed::<I, D>()`, then
156/// descends to `Step<D + 1, N>`. We cannot write `D + 1` in a generic
157/// bound on stable, so the macro unrolls one impl per pair.
158macro_rules! impl_write_dims {
159    ($d:expr, $n:expr) => {
160        impl sealed::Sealed<$d, $n> for Step<$d, $n> {}
161        impl WriteDims<$d, $n> for Step<$d, $n> {
162            #[inline]
163            fn run<G, const I: usize>(g: &G, p: &mut G::Point)
164            where
165                G: IndexedAccess,
166                G::Point: PointMut,
167            {
168                let v = g.get_indexed::<I, $d>();
169                <G::Point as PointMut>::set::<$d>(p, v);
170                <Step<{ $d + 1 }, $n> as WriteDims<{ $d + 1 }, $n>>::run::<G, I>(g, p);
171            }
172        }
173    };
174}
175
176// All (D, N) pairs with 0 <= D < N <= 4. Keep in sync with the match
177// arms in `materialise` and with `crate::point::MAX_DIM`.
178impl_write_dims!(0, 1);
179impl_write_dims!(0, 2);
180impl_write_dims!(1, 2);
181impl_write_dims!(0, 3);
182impl_write_dims!(1, 3);
183impl_write_dims!(2, 3);
184impl_write_dims!(0, 4);
185impl_write_dims!(1, 4);
186impl_write_dims!(2, 4);
187impl_write_dims!(3, 4);
188
189#[cfg(test)]
190mod tests {
191    //! Synthetic Segment round-trip — mirrors
192    //! `boost/geometry/test/core/access.cpp:55-86` for the segment
193    //! case. The `MySegment` here is *not* `geometry-model::segment`;
194    //! that lands in T17.
195
196    use super::*;
197    use geometry_cs::Cartesian;
198    use geometry_tag::PointTag;
199
200    #[derive(Default)]
201    struct Xy {
202        x: f64,
203        y: f64,
204    }
205
206    impl Geometry for Xy {
207        type Kind = PointTag;
208        type Point = Self;
209    }
210
211    impl Point for Xy {
212        type Scalar = f64;
213        type Cs = Cartesian;
214        const DIM: usize = 2;
215
216        fn get<const D: usize>(&self) -> f64 {
217            if D == 0 { self.x } else { self.y }
218        }
219    }
220
221    impl PointMut for Xy {
222        fn set<const D: usize>(&mut self, v: f64) {
223            if D == 0 {
224                self.x = v;
225            } else {
226                self.y = v;
227            }
228        }
229    }
230
231    struct MySegment {
232        e: [[f64; 2]; 2],
233    }
234
235    impl Geometry for MySegment {
236        type Kind = SegmentTag;
237        type Point = Xy;
238    }
239
240    impl IndexedAccess for MySegment {
241        fn get_indexed<const I: usize, const D: usize>(&self) -> f64 {
242            self.e[I][D]
243        }
244        fn set_indexed<const I: usize, const D: usize>(&mut self, v: f64) {
245            self.e[I][D] = v;
246        }
247    }
248
249    impl Segment for MySegment {}
250
251    #[test]
252    fn segment_indexed_round_trip() {
253        let mut s = MySegment { e: [[0.0; 2]; 2] };
254        s.set_indexed::<0, 0>(7.0);
255        s.set_indexed::<1, 1>(8.0);
256        assert_eq!(s.get_indexed::<0, 0>().to_bits(), 7.0_f64.to_bits());
257        assert_eq!(s.get_indexed::<1, 1>().to_bits(), 8.0_f64.to_bits());
258    }
259
260    #[test]
261    fn segment_endpoints_materialise() {
262        let mut s = MySegment { e: [[0.0; 2]; 2] };
263        s.set_indexed::<0, 0>(1.0);
264        s.set_indexed::<0, 1>(2.0);
265        s.set_indexed::<1, 0>(3.0);
266        s.set_indexed::<1, 1>(4.0);
267
268        let a = segment_start(&s);
269        let b = segment_end(&s);
270        assert_eq!(a.get::<0>().to_bits(), 1.0_f64.to_bits());
271        assert_eq!(a.get::<1>().to_bits(), 2.0_f64.to_bits());
272        assert_eq!(b.get::<0>().to_bits(), 3.0_f64.to_bits());
273        assert_eq!(b.get::<1>().to_bits(), 4.0_f64.to_bits());
274    }
275}