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}