Skip to main content

geometry_model/
segment.rs

1//! Default `model::Segment<P>` — two endpoint points stored inline.
2//!
3//! Mirrors `boost::geometry::model::segment<Point>` declared in
4//! `boost/geometry/geometries/segment.hpp:55-71`, together with the
5//! trait specialisations the same header provides under
6//! `namespace traits` (`tag`, `point_type`, `indexed_access`) at
7//! `boost/geometry/geometries/segment.hpp:119-183`. The Rust port
8//! collapses those specialisations into the four trait impls below
9//! ([`Geometry`], [`IndexedAccess`], [`SegmentTrait`]), keyed off the
10//! generic point parameter `P`.
11//!
12//! Boost derives `model::segment` from `std::pair<Point, Point>`; the
13//! Rust port stores the same two points in a `[P; 2]` array so the
14//! `(I, D)` indexed access can write
15//! `endpoints[I].set::<D>(v)` without a `match` on `I`.
16
17use geometry_tag::SegmentTag;
18use geometry_trait::{
19    Geometry, IndexedAccess, Point as PointTrait, PointMut, Segment as SegmentTrait,
20};
21
22/// Two-point segment.
23///
24/// Mirrors `boost::geometry::model::segment<P>` from
25/// `boost/geometry/geometries/segment.hpp:55-71`. The two endpoints
26/// are addressed by index `0` (start) and `1` (end), matching the
27/// `traits::indexed_access<segment, 0|1, D>` specialisations at
28/// `boost/geometry/geometries/segment.hpp:136-169`.
29#[derive(Debug, Default, Clone, Copy, PartialEq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31pub struct Segment<P: PointTrait> {
32    endpoints: [P; 2],
33}
34
35impl<P: PointTrait> Segment<P> {
36    /// Construct a segment from its two endpoints.
37    ///
38    /// Mirrors the `segment(Point const& p1, Point const& p2)`
39    /// constructor at `boost/geometry/geometries/segment.hpp:67-70`.
40    #[must_use]
41    pub const fn new(start: P, end: P) -> Self {
42        Self {
43            endpoints: [start, end],
44        }
45    }
46
47    /// Borrow the start endpoint (`I = 0`).
48    ///
49    /// Mirrors reading `s.first` on `model::segment` — the Boost
50    /// `traits::indexed_access<segment, 0, D>::get(...)` accessor at
51    /// `boost/geometry/geometries/segment.hpp:142-150` returns a
52    /// coordinate of that endpoint; this method returns the whole
53    /// point.
54    #[must_use]
55    pub const fn start(&self) -> &P {
56        &self.endpoints[0]
57    }
58
59    /// Borrow the end endpoint (`I = 1`).
60    ///
61    /// Mirrors reading `s.second` on `model::segment` — the Boost
62    /// `traits::indexed_access<segment, 1, D>::get(...)` accessor at
63    /// `boost/geometry/geometries/segment.hpp:160-168`.
64    #[must_use]
65    pub const fn end(&self) -> &P {
66        &self.endpoints[1]
67    }
68}
69
70/// Tag this concrete type as a Segment. Mirrors the
71/// `traits::tag<model::segment<...>>` specialisation at
72/// `boost/geometry/geometries/segment.hpp:121-124`.
73impl<P: PointTrait> Geometry for Segment<P> {
74    type Kind = SegmentTag;
75    type Point = P;
76}
77
78/// Two-axis access on the segment. Collapses the
79/// `traits::indexed_access<segment, 0|1, D>` specialisations at
80/// `boost/geometry/geometries/segment.hpp:136-169` into one Rust
81/// impl: the `I` axis selects the endpoint, `D` then dispatches to
82/// `Point::get` / `Point::set` on that endpoint.
83impl<P: PointMut> IndexedAccess for Segment<P> {
84    #[inline]
85    fn get_indexed<const I: usize, const D: usize>(&self) -> P::Scalar {
86        self.endpoints[I].get::<D>()
87    }
88
89    #[inline]
90    fn set_indexed<const I: usize, const D: usize>(&mut self, value: P::Scalar) {
91        self.endpoints[I].set::<D>(value);
92    }
93}
94
95/// Wire the Segment concept up. The trait body is empty — the
96/// concept is satisfied by the two super-bounds [`Geometry`] and
97/// [`IndexedAccess`] already proven above.
98impl<P: PointMut> SegmentTrait for Segment<P> {}
99
100#[cfg(test)]
101mod tests {
102    //! Round-trip + tag-resolution + `check_segment` witness for
103    //! [`Segment`]. Mirrors
104    //! `boost/geometry/test/core/access.cpp:55-86` (the
105    //! `test_indexed_get_set` helper applied to a segment) and the
106    //! segment-tag arm of `boost/geometry/test/core/tag.cpp`.
107
108    use super::Segment;
109    use crate::point::Point2D;
110    use geometry_cs::Cartesian;
111    use geometry_tag::SegmentTag;
112    use geometry_trait::{Geometry, IndexedAccess, check_segment};
113
114    #[test]
115    fn segment_round_trip_all_four_slots() {
116        let mut s = Segment::new(
117            Point2D::<f64, Cartesian>::default(),
118            Point2D::<f64, Cartesian>::default(),
119        );
120        s.set_indexed::<0, 0>(1.0);
121        s.set_indexed::<0, 1>(2.0);
122        s.set_indexed::<1, 0>(3.0);
123        s.set_indexed::<1, 1>(4.0);
124
125        // Bit-equal comparison: each slot was written with a literal
126        // f64 constant and read back without arithmetic, so the round
127        // trip must be exact. `to_bits()` sidesteps `clippy::float_cmp`.
128        assert_eq!(s.get_indexed::<0, 0>().to_bits(), 1.0_f64.to_bits());
129        assert_eq!(s.get_indexed::<0, 1>().to_bits(), 2.0_f64.to_bits());
130        assert_eq!(s.get_indexed::<1, 0>().to_bits(), 3.0_f64.to_bits());
131        assert_eq!(s.get_indexed::<1, 1>().to_bits(), 4.0_f64.to_bits());
132    }
133
134    #[test]
135    fn segment_start_and_end_accessors() {
136        use geometry_trait::Point as PointTrait;
137
138        let a = Point2D::<f64, Cartesian>::new(1.0, 2.0);
139        let b = Point2D::<f64, Cartesian>::new(3.0, 4.0);
140        let s = Segment::new(a, b);
141        // `Point<T, D, Cs>` derives `PartialEq`, but only when its
142        // phantom `Cs` parameter does — and `Cartesian` does not.
143        // Compare per-coordinate via the `Point` trait instead.
144        assert_eq!(s.start().get::<0>().to_bits(), 1.0_f64.to_bits());
145        assert_eq!(s.start().get::<1>().to_bits(), 2.0_f64.to_bits());
146        assert_eq!(s.end().get::<0>().to_bits(), 3.0_f64.to_bits());
147        assert_eq!(s.end().get::<1>().to_bits(), 4.0_f64.to_bits());
148    }
149
150    #[test]
151    fn segment_kind_is_segment_tag() {
152        fn k<T: Geometry<Kind = SegmentTag>>() {}
153        k::<Segment<Point2D<f64, Cartesian>>>();
154    }
155
156    #[test]
157    fn segment_satisfies_concept() {
158        check_segment::<Segment<Point2D<f64, Cartesian>>>();
159    }
160}