egml_core/model/geometry/primitives/
solid.rs1use crate::error::Error;
2use crate::model::common::{
3 ApplyTransform, ComputeEnvelope, IterGeometries, Triangulate, Triangulation,
4};
5use crate::model::geometry::primitives::{
6 AbstractSolid, AsAbstractSolid, AsAbstractSolidMut, Shell, TriangulatedSurface,
7};
8use crate::model::geometry::refs::AbstractGeometryKindRef;
9use crate::model::geometry::{DirectPosition, Envelope};
10use crate::{impl_abstract_solid_mut_traits, impl_abstract_solid_traits, impl_has_geometry_type};
11use nalgebra::{Isometry3, Rotation3, Scale3, Transform3, Vector3};
12use rayon::prelude::*;
13
14#[derive(Debug, Clone, PartialEq)]
19pub struct Solid {
20 pub abstract_solid: AbstractSolid,
21 exterior: Option<Shell>,
22 interior: Vec<Shell>,
23}
24
25impl Solid {
26 pub fn new(exterior: Option<Shell>) -> Result<Self, Error> {
32 Ok(Self {
33 abstract_solid: AbstractSolid::default(),
34 exterior,
35 interior: Vec::new(),
36 })
37 }
38
39 pub fn from_abstract_solid(abstract_solid: AbstractSolid, exterior: Option<Shell>) -> Self {
40 Self {
41 abstract_solid,
42 exterior,
43 interior: Vec::new(),
44 }
45 }
46
47 pub fn exterior(&self) -> Option<&Shell> {
48 self.exterior.as_ref()
49 }
50
51 pub fn interior(&self) -> &[Shell] {
52 &self.interior
53 }
54
55 pub fn set_interior(&mut self, interior: Vec<Shell>) {
56 self.interior = interior;
57 }
58
59 pub fn push_interior(&mut self, interior: Shell) {
60 self.interior.push(interior);
61 }
62
63 pub fn extend_interiors(&mut self, interiors: impl IntoIterator<Item = Shell>) {
64 self.interior.extend(interiors);
65 }
66}
67
68impl Solid {
69 pub fn points(&self) -> Vec<&DirectPosition> {
70 self.exterior
71 .as_ref()
72 .map(|exterior| exterior.points())
73 .unwrap_or_default()
74 }
75
76 pub fn volume_3d(&self) -> Result<f64, Error> {
83 self.exterior
84 .as_ref()
85 .ok_or(Error::MissingExteriorShell)?
86 .volume_3d()
87 }
88}
89
90impl ApplyTransform for Solid {
91 fn apply_transform(&mut self, transform: Transform3<f64>) {
92 if let Some(exterior) = self.exterior.as_mut() {
93 exterior.apply_transform(transform);
94 }
95 self.interior
96 .par_iter_mut()
97 .for_each(|p| p.apply_transform(transform));
98 }
99
100 fn apply_isometry(&mut self, isometry: Isometry3<f64>) {
101 if let Some(exterior) = self.exterior.as_mut() {
102 exterior.apply_isometry(isometry);
103 }
104 self.interior
105 .par_iter_mut()
106 .for_each(|p| p.apply_isometry(isometry));
107 }
108
109 fn apply_translation(&mut self, vector: Vector3<f64>) {
110 if let Some(exterior) = self.exterior.as_mut() {
111 exterior.apply_translation(vector);
112 }
113 self.interior
114 .par_iter_mut()
115 .for_each(|p| p.apply_translation(vector));
116 }
117
118 fn apply_rotation(&mut self, rotation: Rotation3<f64>) {
119 if let Some(exterior) = self.exterior.as_mut() {
120 exterior.apply_rotation(rotation);
121 }
122 self.interior
123 .par_iter_mut()
124 .for_each(|p| p.apply_rotation(rotation));
125 }
126
127 fn apply_scale(&mut self, scale: Scale3<f64>) {
128 if let Some(exterior) = self.exterior.as_mut() {
129 exterior.apply_scale(scale);
130 }
131 self.interior
132 .par_iter_mut()
133 .for_each(|p| p.apply_scale(scale));
134 }
135}
136
137impl ComputeEnvelope for Solid {
138 fn compute_envelope(&self) -> Option<Envelope> {
140 self.exterior.as_ref()?.compute_envelope()
141 }
142}
143
144impl AsAbstractSolid for Solid {
145 fn abstract_solid(&self) -> &AbstractSolid {
146 &self.abstract_solid
147 }
148}
149
150impl AsAbstractSolidMut for Solid {
151 fn abstract_solid_mut(&mut self) -> &mut AbstractSolid {
152 &mut self.abstract_solid
153 }
154}
155
156impl_abstract_solid_traits!(Solid);
157impl_abstract_solid_mut_traits!(Solid);
158impl_has_geometry_type!(Solid, Solid);
159
160impl IterGeometries for Solid {
161 fn iter_geometries(&self) -> Box<dyn Iterator<Item = AbstractGeometryKindRef<'_>> + '_> {
162 Box::new(
163 std::iter::once(self.into())
164 .chain(
165 self.exterior
166 .as_ref()
167 .into_iter()
168 .flat_map(|x| x.iter_geometries()),
169 )
170 .chain(self.interior.iter().flat_map(|x| x.iter_geometries())),
171 )
172 }
173}
174
175impl Triangulate for Solid {
176 fn triangulate(&self) -> Result<Triangulation, Error> {
184 let exterior_shell = self.exterior.as_ref().ok_or(Error::MissingExteriorShell)?;
185
186 let (exterior_surface, mut skipped) = exterior_shell.triangulate()?.into_parts();
187 let mut surfaces = vec![exterior_surface];
188
189 for shell in &self.interior {
190 let (surface, nested_skipped) = shell.triangulate()?.into_parts();
191 surfaces.push(surface);
192 skipped.extend(nested_skipped);
193 }
194
195 let combined = TriangulatedSurface::from_triangulated_surfaces(surfaces)?;
196 Ok(Triangulation::new(combined, skipped))
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use crate::model::geometry::Envelope;
204
205 #[test]
206 fn volume_3d_unit_cube() {
207 let solid = Envelope::new(
208 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
209 DirectPosition::new(1.0, 1.0, 1.0).unwrap(),
210 )
211 .unwrap()
212 .to_solid()
213 .unwrap();
214 assert!((solid.volume_3d().unwrap() - 1.0).abs() < 1e-10);
215 }
216
217 #[test]
218 fn volume_3d_2x3x4_box() {
219 let solid = Envelope::new(
220 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
221 DirectPosition::new(2.0, 3.0, 4.0).unwrap(),
222 )
223 .unwrap()
224 .to_solid()
225 .unwrap();
226 assert!((solid.volume_3d().unwrap() - 24.0).abs() < 1e-10);
227 }
228
229 #[test]
230 fn apply_translation_transforms_interior_shells_too() {
231 let mut solid = Envelope::new(
232 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
233 DirectPosition::new(1.0, 1.0, 1.0).unwrap(),
234 )
235 .unwrap()
236 .to_solid()
237 .unwrap();
238
239 let interior_shell = solid.exterior().cloned().unwrap();
240 solid.push_interior(interior_shell);
241
242 let before = solid.interior()[0].points()[0].x();
243
244 solid.apply_translation(Vector3::new(5.0, 0.0, 0.0));
245
246 let after = solid.interior()[0].points()[0].x();
247
248 assert!((after - before - 5.0).abs() < 1e-10);
249 }
250
251 #[test]
252 fn iter_geometries_walks_shell_polygons_and_rings() {
253 let solid = Envelope::new(
254 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
255 DirectPosition::new(1.0, 1.0, 1.0).unwrap(),
256 )
257 .unwrap()
258 .to_solid()
259 .unwrap();
260
261 assert_eq!(solid.iter_geometries().count(), 14);
263 }
264
265 #[test]
266 fn volume_3d_missing_exterior_shell() {
267 let solid = Solid::new(None).unwrap();
268 assert_eq!(solid.volume_3d(), Err(Error::MissingExteriorShell));
269 }
270}