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}