Skip to main content

egml_core/model/geometry/primitives/
shell.rs

1use crate::model::common::{
2    ApplyTransform, ComputeEnvelope, IterGeometries, Triangulate, Triangulation,
3};
4use crate::model::geometry::primitives::{
5    AbstractSurface, AbstractSurfaceProperty, AsAbstractSurface, AsAbstractSurfaceMut,
6    TriangulatedSurface,
7};
8use crate::model::geometry::refs::AbstractGeometryKindRef;
9use crate::model::geometry::{DirectPosition, Envelope};
10use crate::{
11    Error, impl_abstract_surface_mut_traits, impl_abstract_surface_traits, impl_has_geometry_type,
12};
13use nalgebra::{Isometry3, Rotation3, Scale3, Transform3, Vector3};
14use rayon::iter::IntoParallelRefMutIterator;
15use rayon::iter::ParallelIterator;
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct Shell {
19    abstract_surface: AbstractSurface,
20    members: Vec<AbstractSurfaceProperty>,
21}
22
23impl Shell {
24    pub fn new(members: impl IntoIterator<Item = AbstractSurfaceProperty>) -> Result<Self, Error> {
25        let members: Vec<AbstractSurfaceProperty> = members.into_iter().collect();
26        Self::validate(&members)?;
27
28        Ok(Self {
29            abstract_surface: AbstractSurface::default(),
30            members,
31        })
32    }
33
34    pub fn from_abstract_surface(
35        abstract_surface: AbstractSurface,
36        members: impl IntoIterator<Item = AbstractSurfaceProperty>,
37    ) -> Result<Self, Error> {
38        let members: Vec<AbstractSurfaceProperty> = members.into_iter().collect();
39        Self::validate(&members)?;
40
41        Ok(Self {
42            abstract_surface,
43            members,
44        })
45    }
46
47    fn validate(members: &[AbstractSurfaceProperty]) -> Result<(), Error> {
48        if members.is_empty() {
49            return Err(Error::TooFewElements {
50                geometry: "gml:Solid",
51                minimum: 1,
52                spec: Some("OGC 07-036 §10.6.4"),
53                id: None,
54                detail: None,
55            });
56        }
57        Ok(())
58    }
59
60    /// Returns the bounding surface members of this solid.
61    pub fn members(&self) -> &[AbstractSurfaceProperty] {
62        &self.members
63    }
64
65    /// Replaces the bounding surface members.
66    ///
67    /// # Errors
68    ///
69    /// Returns [`Error::TooFewElements`] if `val` is empty.
70    pub fn set_members(&mut self, val: Vec<AbstractSurfaceProperty>) -> Result<(), Error> {
71        Self::validate(&val)?;
72
73        self.members = val;
74        Ok(())
75    }
76
77    pub fn push_member(&mut self, member: AbstractSurfaceProperty) {
78        self.members.push(member);
79    }
80
81    pub fn extend_members(&mut self, members: impl IntoIterator<Item = AbstractSurfaceProperty>) {
82        self.members.extend(members);
83    }
84}
85
86impl AsAbstractSurface for Shell {
87    fn abstract_surface(&self) -> &AbstractSurface {
88        &self.abstract_surface
89    }
90}
91
92impl AsAbstractSurfaceMut for Shell {
93    fn abstract_surface_mut(&mut self) -> &mut AbstractSurface {
94        &mut self.abstract_surface
95    }
96}
97
98impl_abstract_surface_traits!(Shell);
99impl_abstract_surface_mut_traits!(Shell);
100impl_has_geometry_type!(Shell, Shell);
101
102impl IterGeometries for Shell {
103    fn iter_geometries(&self) -> Box<dyn Iterator<Item = AbstractGeometryKindRef<'_>> + '_> {
104        Box::new(
105            std::iter::once(self.into()).chain(
106                self.members
107                    .iter()
108                    .filter_map(|x| x.object())
109                    .flat_map(|x| x.iter_geometries()),
110            ),
111        )
112    }
113}
114
115impl Shell {
116    pub fn points(&self) -> Vec<&DirectPosition> {
117        self.members
118            .iter()
119            .flat_map(|x| x.object())
120            .fold(Vec::new(), |mut acc, x| {
121                acc.extend(x.points().iter());
122                acc
123            })
124    }
125
126    /// Returns the volume of the closed solid bounded by this shell.
127    ///
128    /// Uses the divergence theorem on the triangulated shell: `V = |Σ a·(b×c)| / 6`
129    /// per triangle. The shell must be closed and watertight for the result to be correct.
130    ///
131    /// # Errors
132    ///
133    /// Propagates any [`Error::TriangulationFailed`] from triangulating the bounding surfaces.
134    pub fn volume_3d(&self) -> Result<f64, Error> {
135        let triangulation = self.triangulate()?;
136        let triangles = triangulation.surface().triangles();
137        let Some(first) = triangles.first() else {
138            return Ok(0.0);
139        };
140
141        // Shift all vertices by the first triangle's vertex to avoid catastrophic
142        // cancellation when world-space coordinates are large (e.g. EPSG:25832).
143        let origin: Vector3<f64> = first.a().into();
144        let signed_vol: f64 = triangles
145            .iter()
146            .map(|t| {
147                let a: Vector3<f64> = Into::<Vector3<f64>>::into(t.a()) - origin;
148                let b: Vector3<f64> = Into::<Vector3<f64>>::into(t.b()) - origin;
149                let c: Vector3<f64> = Into::<Vector3<f64>>::into(t.c()) - origin;
150                a.dot(&b.cross(&c))
151            })
152            .sum();
153        Ok(signed_vol.abs() / 6.0)
154    }
155}
156
157impl ApplyTransform for Shell {
158    fn apply_transform(&mut self, transform: Transform3<f64>) {
159        self.members.par_iter_mut().for_each(|p| {
160            if let Some(x) = p.object_mut() {
161                x.apply_transform(transform);
162            }
163        });
164    }
165
166    fn apply_isometry(&mut self, isometry: Isometry3<f64>) {
167        self.members.par_iter_mut().for_each(|p| {
168            if let Some(x) = p.object_mut() {
169                x.apply_isometry(isometry);
170            }
171        });
172    }
173
174    fn apply_translation(&mut self, vector: Vector3<f64>) {
175        self.members.par_iter_mut().for_each(|p| {
176            if let Some(x) = p.object_mut() {
177                x.apply_translation(vector);
178            }
179        });
180    }
181
182    fn apply_rotation(&mut self, rotation: Rotation3<f64>) {
183        self.members.par_iter_mut().for_each(|p| {
184            if let Some(x) = p.object_mut() {
185                x.apply_rotation(rotation);
186            }
187        });
188    }
189
190    fn apply_scale(&mut self, scale: Scale3<f64>) {
191        self.members.par_iter_mut().for_each(|p| {
192            if let Some(x) = p.object_mut() {
193                x.apply_scale(scale);
194            }
195        });
196    }
197}
198
199impl ComputeEnvelope for Shell {
200    /// Returns the union of the bounding boxes of all surface members.
201    fn compute_envelope(&self) -> Option<Envelope> {
202        let envelopes: Vec<Envelope> = self
203            .members
204            .iter()
205            .flat_map(|x| x.object())
206            .flat_map(|x| x.compute_envelope())
207            .collect();
208
209        Envelope::from_envelopes(&envelopes)
210    }
211}
212
213impl Triangulate for Shell {
214    /// Members that fail to triangulate individually (e.g. a degenerate ring) are
215    /// skipped rather than failing the whole shell; see their errors via
216    /// [`Triangulation::skipped`].
217    ///
218    /// # Errors
219    ///
220    /// Returns [`Error::TooFewElements`] if no member could be triangulated.
221    fn triangulate(&self) -> Result<Triangulation, Error> {
222        let mut surfaces = Vec::new();
223        let mut skipped = Vec::new();
224
225        for member in self.members.iter().flat_map(|x| x.object()) {
226            match member.triangulate() {
227                Ok(triangulation) => {
228                    let (surface, nested_skipped) = triangulation.into_parts();
229                    surfaces.push(surface);
230                    skipped.extend(nested_skipped);
231                }
232                Err(error) => {
233                    skipped.push(error);
234                }
235            }
236        }
237
238        let combined = TriangulatedSurface::from_triangulated_surfaces(surfaces)?;
239        Ok(Triangulation::new(combined, skipped))
240    }
241}