Skip to main content

geometry_trait/
polyhedral.rs

1//! The [`PolyhedralSurface`] concept: a contiguous collection of
2//! polygons sharing common boundary segments.
3//!
4//! Mirrors `doc/concept/polyhedral_surface.qbk`; the canonical model is
5//! `boost::geometry::model::polyhedral_surface` in
6//! `boost/geometry/geometries/polyhedral_surface.hpp`, which derives
7//! from `std::vector<Polygon>` and asserts `concepts::Polygon<Polygon>`
8//! on its face type. The Rust port mirrors that assertion by bounding
9//! [`PolyhedralSurface::Face`] on the [`Polygon`] concept, and pins
10//! `Face::Point = Self::Point` so the surface's `point_type<PS>`
11//! projection stays consistent across every face.
12
13use crate::geometry::Geometry;
14use crate::polygon::Polygon;
15use geometry_tag::PolyhedralSurfaceTag;
16
17/// A polyhedral surface — a contiguous collection of polygons in
18/// 3-dimensional space that share common boundary segments.
19///
20/// Mirrors the `PolyhedralSurface` concept
21/// (`doc/concept/polyhedral_surface.qbk`); the canonical model is
22/// `boost::geometry::model::polyhedral_surface` in
23/// `boost/geometry/geometries/polyhedral_surface.hpp`.
24///
25/// The OGC spec requires the faces to be 3D Cartesian and to share
26/// boundary segments. As with the C++ model — whose constructors do
27/// not check the shared-boundary invariant and defer to `is_valid()`
28/// — neither invariant is enforced at the trait level. Both are
29/// checked by `check_polyhedral_surface::<T>()` in T15.
30///
31/// # Examples
32///
33/// ```
34/// use geometry_trait::PolyhedralSurface;
35/// fn face_count<P: PolyhedralSurface>(p: &P) -> usize { p.faces().len() }
36/// ```
37pub trait PolyhedralSurface: Geometry<Kind = PolyhedralSurfaceTag> {
38    /// The face polygon type.
39    ///
40    /// Mirrors the `Polygon` template parameter on
41    /// `boost::geometry::model::polyhedral_surface<Polygon, …>`
42    /// (`boost/geometry/geometries/polyhedral_surface.hpp`).
43    type Face: Polygon<Point = Self::Point>;
44
45    /// The faces of this polyhedral surface, in declared order.
46    ///
47    /// Plays the role of `boost::begin(ps)` / `boost::end(ps)` from
48    /// `boost/geometry/geometries/polyhedral_surface.hpp` when read
49    /// together. The returned iterator is `ExactSizeIterator` so
50    /// callers can ask for the face count without consuming it.
51    fn faces(&self) -> impl ExactSizeIterator<Item = &Self::Face>;
52}
53
54#[cfg(test)]
55mod tests {
56    extern crate alloc;
57
58    use super::*;
59    use crate::point::{Point, PointMut};
60    use crate::ring::Ring;
61    use alloc::vec;
62    use alloc::vec::Vec;
63    use geometry_cs::Cartesian;
64    use geometry_tag::{PointTag, PolygonTag, RingTag};
65
66    fn accepts_ps<P: PolyhedralSurface>() {}
67
68    #[derive(Clone)]
69    struct Xyz(f64, f64, f64);
70
71    impl Geometry for Xyz {
72        type Kind = PointTag;
73        type Point = Self;
74    }
75
76    impl Point for Xyz {
77        type Scalar = f64;
78        type Cs = Cartesian;
79        const DIM: usize = 3;
80
81        fn get<const D: usize>(&self) -> f64 {
82            match D {
83                0 => self.0,
84                1 => self.1,
85                _ => self.2,
86            }
87        }
88    }
89
90    impl PointMut for Xyz {
91        fn set<const D: usize>(&mut self, v: f64) {
92            match D {
93                0 => self.0 = v,
94                1 => self.1 = v,
95                _ => self.2 = v,
96            }
97        }
98    }
99
100    struct VRing(Vec<Xyz>);
101
102    impl Geometry for VRing {
103        type Kind = RingTag;
104        type Point = Xyz;
105    }
106
107    impl Ring for VRing {
108        fn points(&self) -> impl ExactSizeIterator<Item = &Xyz> + Clone {
109            self.0.iter()
110        }
111    }
112
113    struct VPoly {
114        outer: VRing,
115        inners: Vec<VRing>,
116    }
117
118    impl Geometry for VPoly {
119        type Kind = PolygonTag;
120        type Point = Xyz;
121    }
122
123    impl Polygon for VPoly {
124        type Ring = VRing;
125
126        fn exterior(&self) -> &VRing {
127            &self.outer
128        }
129
130        fn interiors(&self) -> impl ExactSizeIterator<Item = &VRing> {
131            self.inners.iter()
132        }
133    }
134
135    struct VPolyhedral(Vec<VPoly>);
136
137    impl Geometry for VPolyhedral {
138        type Kind = PolyhedralSurfaceTag;
139        type Point = Xyz;
140    }
141
142    impl PolyhedralSurface for VPolyhedral {
143        type Face = VPoly;
144
145        fn faces(&self) -> impl ExactSizeIterator<Item = &VPoly> {
146            self.0.iter()
147        }
148    }
149
150    /// Build a unit cube's four side faces as a synthetic polyhedral
151    /// surface. v1 carries no algorithm for polyhedral surfaces, so
152    /// this test only checks the trait surface compiles and `faces()`
153    /// yields the right count.
154    #[test]
155    fn vec_backed_polyhedral_surface_satisfies_trait() {
156        fn quad(a: Xyz, b: Xyz, c: Xyz, d: Xyz) -> VPoly {
157            let a2 = a.clone();
158            VPoly {
159                outer: VRing(vec![a, b, c, d, a2]),
160                inners: vec![],
161            }
162        }
163
164        let ps = VPolyhedral(vec![
165            quad(
166                Xyz(0.0, 0.0, 0.0),
167                Xyz(1.0, 0.0, 0.0),
168                Xyz(1.0, 0.0, 1.0),
169                Xyz(0.0, 0.0, 1.0),
170            ),
171            quad(
172                Xyz(1.0, 0.0, 0.0),
173                Xyz(1.0, 1.0, 0.0),
174                Xyz(1.0, 1.0, 1.0),
175                Xyz(1.0, 0.0, 1.0),
176            ),
177            quad(
178                Xyz(1.0, 1.0, 0.0),
179                Xyz(0.0, 1.0, 0.0),
180                Xyz(0.0, 1.0, 1.0),
181                Xyz(1.0, 1.0, 1.0),
182            ),
183            quad(
184                Xyz(0.0, 1.0, 0.0),
185                Xyz(0.0, 0.0, 0.0),
186                Xyz(0.0, 0.0, 1.0),
187                Xyz(0.0, 1.0, 1.0),
188            ),
189        ]);
190
191        accepts_ps::<VPolyhedral>();
192        assert_eq!(ps.faces().len(), 4);
193        let ring_lens: Vec<usize> = ps.faces().map(|f| f.exterior().points().count()).collect();
194        assert_eq!(ring_lens, vec![5, 5, 5, 5]);
195
196        // Read each ordinate of the first face's first vertex through
197        // `Point::get`, and confirm every face reports zero interior
198        // rings (`interiors()` is empty for these quads).
199        let first_face = ps.faces().next().unwrap();
200        let v0 = first_face.exterior().points().next().unwrap();
201        assert_eq!(
202            (v0.get::<0>(), v0.get::<1>(), v0.get::<2>()),
203            (0.0, 0.0, 0.0)
204        );
205        for face in ps.faces() {
206            assert_eq!(face.interiors().count(), 0);
207        }
208    }
209
210    /// `Xyz` is a full `PointMut`: writing each ordinate through `set`
211    /// and reading it back through `get` round-trips.
212    #[test]
213    fn xyz_point_get_set_round_trips_every_ordinate() {
214        let mut p = Xyz(0.0, 0.0, 0.0);
215        p.set::<0>(1.0);
216        p.set::<1>(2.0);
217        p.set::<2>(3.0);
218        assert_eq!((p.get::<0>(), p.get::<1>(), p.get::<2>()), (1.0, 2.0, 3.0));
219    }
220}