u_nesting_core/
geometry.rs1use crate::transform::{AABB2D, AABB3D};
4use crate::Result;
5use u_geometry::nalgebra_types::RealField;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10pub type GeometryId = String;
12
13#[derive(Debug, Clone, PartialEq)]
19#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
20#[derive(Default)]
21pub enum RotationConstraint<S> {
22 #[default]
24 None,
25 Discrete(Vec<S>),
29}
30
31impl<S: RealField + Copy> RotationConstraint<S> {
32 pub fn axis_aligned() -> Self {
34 let pi = S::pi();
35 let half_pi = pi / (S::one() + S::one());
36 Self::Discrete(vec![S::zero(), half_pi, pi, pi + half_pi])
37 }
38
39 pub fn steps(n: usize) -> Self {
45 if n == 0 {
46 return Self::None;
47 }
48 let two_pi = S::two_pi();
49 let step =
50 two_pi / S::from_usize(n).expect("n exceeds scalar precision (use n < 2^24 for f32)");
51 let angles: Vec<S> = (0..n)
52 .map(|i| step * S::from_usize(i).expect("index exceeds scalar precision"))
53 .collect();
54 Self::Discrete(angles)
55 }
56
57 pub fn is_fixed(&self) -> bool {
59 matches!(self, Self::None)
60 }
61
62 pub fn angles(&self) -> Vec<S> {
64 match self {
65 Self::None => vec![S::zero()],
66 Self::Discrete(angles) => angles.clone(),
67 }
68 }
69}
70
71pub trait Geometry: Clone + Send + Sync {
73 type Scalar: RealField + Copy;
75
76 fn id(&self) -> &GeometryId;
78
79 fn quantity(&self) -> usize;
81
82 fn measure(&self) -> Self::Scalar;
84
85 fn aabb(&self) -> ([Self::Scalar; 2], [Self::Scalar; 2]) {
87 let (min, max) = self.aabb_vec();
89 ([min[0], min[1]], [max[0], max[1]])
90 }
91
92 fn aabb_vec(&self) -> (Vec<Self::Scalar>, Vec<Self::Scalar>);
94
95 fn centroid(&self) -> Vec<Self::Scalar>;
97
98 fn validate(&self) -> Result<()>;
100
101 fn rotation_constraint(&self) -> &RotationConstraint<Self::Scalar>;
103
104 fn allow_mirror(&self) -> bool {
106 false
107 }
108
109 fn priority(&self) -> i32 {
111 0
112 }
113}
114
115pub trait Geometry2DExt: Geometry {
117 fn aabb_2d(&self) -> AABB2D<Self::Scalar>;
119
120 fn outer_ring(&self) -> &[(Self::Scalar, Self::Scalar)];
122
123 fn holes(&self) -> &[Vec<(Self::Scalar, Self::Scalar)>];
125
126 fn has_holes(&self) -> bool {
128 !self.holes().is_empty()
129 }
130
131 fn is_convex(&self) -> bool;
133
134 fn convex_hull(&self) -> Vec<(Self::Scalar, Self::Scalar)>;
136
137 fn perimeter(&self) -> Self::Scalar;
139}
140
141pub trait Geometry3DExt: Geometry {
143 fn aabb_3d(&self) -> AABB3D<Self::Scalar>;
145
146 fn surface_area(&self) -> Self::Scalar;
148
149 fn mass(&self) -> Option<Self::Scalar>;
151
152 fn center_of_mass(&self) -> (Self::Scalar, Self::Scalar, Self::Scalar);
154
155 fn stackable(&self) -> bool {
157 true
158 }
159
160 fn max_stack_load(&self) -> Option<Self::Scalar> {
162 None
163 }
164}
165
166pub trait Boundary: Clone + Send + Sync {
168 type Scalar: RealField + Copy;
170
171 fn measure(&self) -> Self::Scalar;
173
174 fn aabb(&self) -> ([Self::Scalar; 2], [Self::Scalar; 2]) {
176 let (min, max) = self.aabb_vec();
177 ([min[0], min[1]], [max[0], max[1]])
178 }
179
180 fn aabb_vec(&self) -> (Vec<Self::Scalar>, Vec<Self::Scalar>);
182
183 fn validate(&self) -> Result<()>;
185
186 fn contains_point(&self, point: &[Self::Scalar]) -> bool;
188}
189
190pub trait Boundary2DExt: Boundary {
192 fn aabb_2d(&self) -> AABB2D<Self::Scalar>;
194
195 fn vertices(&self) -> &[(Self::Scalar, Self::Scalar)];
197
198 fn contains_polygon(&self, polygon: &[(Self::Scalar, Self::Scalar)]) -> bool;
200
201 fn effective_area(&self, margin: Self::Scalar) -> Self::Scalar;
203}
204
205pub trait Boundary3DExt: Boundary {
207 fn aabb_3d(&self) -> AABB3D<Self::Scalar>;
209
210 fn max_mass(&self) -> Option<Self::Scalar>;
212
213 fn contains_box(&self, min: &[Self::Scalar; 3], max: &[Self::Scalar; 3]) -> bool;
215
216 fn effective_volume(&self, margin: Self::Scalar) -> Self::Scalar;
218}
219
220pub fn ensure_unique_ids<G: Geometry>(geometries: &[G]) -> Result<()> {
233 let mut first_at = std::collections::HashMap::with_capacity(geometries.len());
234 for (position, geometry) in geometries.iter().enumerate() {
235 if let Some(first) = first_at.insert(geometry.id(), position) {
236 return Err(crate::Error::InvalidGeometry(format!(
237 "the id '{}' is given twice, at positions {first} and {position} of \
238 geometries (counting from 0); placements name geometries by id, so \
239 every geometry needs its own",
240 geometry.id()
241 )));
242 }
243 }
244 Ok(())
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_rotation_constraint_axis_aligned() {
253 let constraint: RotationConstraint<f64> = RotationConstraint::axis_aligned();
254 let angles = constraint.angles();
255 assert_eq!(angles.len(), 4);
256 }
257
258 #[test]
259 fn test_rotation_constraint_steps() {
260 let constraint: RotationConstraint<f64> = RotationConstraint::steps(8);
261 let angles = constraint.angles();
262 assert_eq!(angles.len(), 8);
263 }
264}