egml_core/model/geometry/primitives/
solid.rs1use crate::error::Error;
2use crate::model::base::HasAssociationAttributes;
3use crate::model::common::{
4 ApplyTransform, ComputeEnvelope, IterGeometries, Triangulate, Triangulation,
5};
6use crate::model::geometry::primitives::shell_property::ShellProperty;
7use crate::model::geometry::primitives::{
8 AbstractSolid, AsAbstractSolid, AsAbstractSolidMut, TriangulatedSurface,
9};
10use crate::model::geometry::refs::AbstractGeometryKindRef;
11use crate::model::geometry::{DirectPosition, Envelope};
12use crate::{impl_abstract_solid_mut_traits, impl_abstract_solid_traits, impl_has_geometry_type};
13use nalgebra::{Isometry3, Rotation3, Scale3, Transform3, Vector3};
14use rayon::prelude::*;
15
16#[derive(Debug, Clone, PartialEq)]
21pub struct Solid {
22 pub abstract_solid: AbstractSolid,
23 exterior: Option<ShellProperty>,
24 interior: Vec<ShellProperty>,
25}
26
27impl Solid {
28 pub fn new(exterior: Option<ShellProperty>) -> Result<Self, Error> {
34 Ok(Self {
35 abstract_solid: AbstractSolid::default(),
36 exterior,
37 interior: Vec::new(),
38 })
39 }
40
41 pub fn from_abstract_solid(
42 abstract_solid: AbstractSolid,
43 exterior: Option<ShellProperty>,
44 ) -> Self {
45 Self {
46 abstract_solid,
47 exterior,
48 interior: Vec::new(),
49 }
50 }
51
52 pub fn exterior(&self) -> Option<&ShellProperty> {
53 self.exterior.as_ref()
54 }
55
56 pub fn interior(&self) -> &[ShellProperty] {
57 &self.interior
58 }
59
60 pub fn set_interior(&mut self, interior: Vec<ShellProperty>) {
61 self.interior = interior;
62 }
63
64 pub fn push_interior(&mut self, interior: ShellProperty) {
65 self.interior.push(interior);
66 }
67
68 pub fn extend_interiors(&mut self, interiors: impl IntoIterator<Item = ShellProperty>) {
69 self.interior.extend(interiors);
70 }
71}
72
73impl Solid {
74 pub fn points(&self) -> Vec<&DirectPosition> {
75 if let Some(exterior) = &self.exterior
76 && let Some(object) = exterior.object()
77 {
78 object.points()
79 } else {
80 Vec::new()
81 }
82 }
83
84 pub fn volume_3d(&self) -> Result<f64, Error> {
93 let shell_property = self.exterior.as_ref().ok_or(Error::MissingExteriorShell)?;
94 let shell = shell_property
95 .object()
96 .ok_or_else(|| Error::UnresolvedShellReference {
97 href: shell_property.href().map(|h| h.to_string()),
98 })?;
99 shell.volume_3d()
100 }
101}
102
103impl ApplyTransform for Solid {
104 fn apply_transform(&mut self, transform: Transform3<f64>) {
105 if let Some(exterior) = self.exterior.as_mut()
106 && let Some(object) = exterior.object_mut()
107 {
108 object.apply_transform(transform)
109 }
110
111 self.interior.par_iter_mut().for_each(|p| {
112 if let Some(object) = p.object_mut() {
113 object.apply_transform(transform);
114 }
115 });
116 }
117
118 fn apply_isometry(&mut self, isometry: Isometry3<f64>) {
119 if let Some(exterior) = self.exterior.as_mut()
120 && let Some(object) = exterior.object_mut()
121 {
122 object.apply_isometry(isometry)
123 }
124
125 self.interior.par_iter_mut().for_each(|p| {
126 if let Some(object) = p.object_mut() {
127 object.apply_isometry(isometry);
128 }
129 });
130 }
131
132 fn apply_translation(&mut self, vector: Vector3<f64>) {
133 if let Some(exterior) = self.exterior.as_mut()
134 && let Some(object) = exterior.object_mut()
135 {
136 object.apply_translation(vector)
137 }
138
139 self.interior.par_iter_mut().for_each(|p| {
140 if let Some(object) = p.object_mut() {
141 object.apply_translation(vector);
142 }
143 });
144 }
145
146 fn apply_rotation(&mut self, rotation: Rotation3<f64>) {
147 if let Some(exterior) = self.exterior.as_mut()
148 && let Some(object) = exterior.object_mut()
149 {
150 object.apply_rotation(rotation)
151 }
152
153 self.interior.par_iter_mut().for_each(|p| {
154 if let Some(object) = p.object_mut() {
155 object.apply_rotation(rotation);
156 }
157 });
158 }
159
160 fn apply_scale(&mut self, scale: Scale3<f64>) {
161 if let Some(exterior) = self.exterior.as_mut()
162 && let Some(object) = exterior.object_mut()
163 {
164 object.apply_scale(scale)
165 }
166
167 self.interior.par_iter_mut().for_each(|p| {
168 if let Some(object) = p.object_mut() {
169 object.apply_scale(scale);
170 }
171 });
172 }
173}
174
175impl ComputeEnvelope for Solid {
176 fn compute_envelope(&self) -> Option<Envelope> {
178 if let Some(exterior) = &self.exterior
179 && let Some(object) = exterior.object()
180 {
181 object.compute_envelope()
182 } else {
183 None
184 }
185 }
186}
187
188impl AsAbstractSolid for Solid {
189 fn abstract_solid(&self) -> &AbstractSolid {
190 &self.abstract_solid
191 }
192}
193
194impl AsAbstractSolidMut for Solid {
195 fn abstract_solid_mut(&mut self) -> &mut AbstractSolid {
196 &mut self.abstract_solid
197 }
198}
199
200impl_abstract_solid_traits!(Solid);
201impl_abstract_solid_mut_traits!(Solid);
202impl_has_geometry_type!(Solid, Solid);
203
204impl IterGeometries for Solid {
205 fn iter_geometries(&self) -> Box<dyn Iterator<Item = AbstractGeometryKindRef<'_>> + '_> {
206 Box::new(
207 std::iter::once(self.into())
208 .chain(
209 self.exterior
210 .as_ref()
211 .and_then(|x| x.object())
212 .into_iter()
213 .flat_map(|x| x.iter_geometries()),
214 )
215 .chain(
216 self.interior
217 .iter()
218 .filter_map(|x| x.object())
219 .flat_map(|x| x.iter_geometries()),
220 ),
221 )
222 }
223}
224
225impl Triangulate for Solid {
226 fn triangulate(&self) -> Result<Triangulation, Error> {
236 let exterior_shell_property = self.exterior.as_ref().ok_or(Error::MissingExteriorShell)?;
237 let exterior_shell =
238 exterior_shell_property
239 .object()
240 .ok_or_else(|| Error::UnresolvedShellReference {
241 href: exterior_shell_property.href().map(|h| h.to_string()),
242 })?;
243
244 let (exterior_surface, mut skipped) = exterior_shell.triangulate()?.into_parts();
245 let mut surfaces = vec![exterior_surface];
246
247 for shell_property in &self.interior {
248 let shell = shell_property
249 .object()
250 .ok_or_else(|| Error::UnresolvedShellReference {
251 href: shell_property.href().map(|h| h.to_string()),
252 })?;
253 let (surface, nested_skipped) = shell.triangulate()?.into_parts();
254 surfaces.push(surface);
255 skipped.extend(nested_skipped);
256 }
257
258 let combined = TriangulatedSurface::from_triangulated_surfaces(surfaces)?;
259 Ok(Triangulation::new(combined, skipped))
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use crate::model::geometry::Envelope;
267 use crate::model::geometry::primitives::ShellProperty;
268
269 #[test]
270 fn volume_3d_unit_cube() {
271 let solid = Envelope::new(
272 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
273 DirectPosition::new(1.0, 1.0, 1.0).unwrap(),
274 )
275 .unwrap()
276 .to_solid()
277 .unwrap();
278 assert!((solid.volume_3d().unwrap() - 1.0).abs() < 1e-10);
279 }
280
281 #[test]
282 fn volume_3d_2x3x4_box() {
283 let solid = Envelope::new(
284 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
285 DirectPosition::new(2.0, 3.0, 4.0).unwrap(),
286 )
287 .unwrap()
288 .to_solid()
289 .unwrap();
290 assert!((solid.volume_3d().unwrap() - 24.0).abs() < 1e-10);
291 }
292
293 #[test]
294 fn apply_translation_transforms_interior_shells_too() {
295 let mut solid = Envelope::new(
296 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
297 DirectPosition::new(1.0, 1.0, 1.0).unwrap(),
298 )
299 .unwrap()
300 .to_solid()
301 .unwrap();
302
303 let interior_shell = solid.exterior().unwrap().object().cloned().unwrap();
304 solid.push_interior(ShellProperty::from_object(interior_shell));
305
306 let before = solid.interior()[0].object().unwrap().points()[0].x();
307
308 solid.apply_translation(Vector3::new(5.0, 0.0, 0.0));
309
310 let after = solid.interior()[0].object().unwrap().points()[0].x();
311
312 assert!((after - before - 5.0).abs() < 1e-10);
313 }
314
315 #[test]
316 fn iter_geometries_walks_shell_polygons_and_rings() {
317 let solid = Envelope::new(
318 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
319 DirectPosition::new(1.0, 1.0, 1.0).unwrap(),
320 )
321 .unwrap()
322 .to_solid()
323 .unwrap();
324
325 assert_eq!(solid.iter_geometries().count(), 14);
327 }
328
329 #[test]
330 fn volume_3d_missing_exterior_shell() {
331 let solid = Solid::new(None).unwrap();
332 assert_eq!(solid.volume_3d(), Err(Error::MissingExteriorShell));
333 }
334
335 #[test]
336 fn volume_3d_unresolved_shell_reference() {
337 let solid =
338 Solid::new(Some(ShellProperty::from_href("urn:example:shell-1".into()))).unwrap();
339 assert_eq!(
340 solid.volume_3d(),
341 Err(Error::UnresolvedShellReference {
342 href: Some("urn:example:shell-1".to_string())
343 })
344 );
345 }
346}