u_nesting_core/
geometry.rs1use crate::transform::{AABB2D, AABB3D};
4use crate::Result;
5use nalgebra::RealField;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10pub type GeometryId = String;
12
13#[derive(Debug, Clone, PartialEq)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16#[derive(Default)]
17pub enum RotationConstraint<S> {
18 #[default]
20 None,
21 Free,
23 Discrete(Vec<S>),
25}
26
27impl<S: RealField + Copy> RotationConstraint<S> {
28 pub fn axis_aligned() -> Self {
30 let pi = S::pi();
31 let half_pi = pi / (S::one() + S::one());
32 Self::Discrete(vec![S::zero(), half_pi, pi, pi + half_pi])
33 }
34
35 pub fn steps(n: usize) -> Self {
41 if n == 0 {
42 return Self::None;
43 }
44 let two_pi = S::two_pi();
45 let step =
46 two_pi / S::from_usize(n).expect("n exceeds scalar precision (use n < 2^24 for f32)");
47 let angles: Vec<S> = (0..n)
48 .map(|i| step * S::from_usize(i).expect("index exceeds scalar precision"))
49 .collect();
50 Self::Discrete(angles)
51 }
52
53 pub fn is_fixed(&self) -> bool {
55 matches!(self, Self::None)
56 }
57
58 pub fn angles(&self) -> Vec<S> {
60 match self {
61 Self::None => vec![S::zero()],
62 Self::Free => vec![], Self::Discrete(angles) => angles.clone(),
64 }
65 }
66}
67
68pub trait Geometry: Clone + Send + Sync {
70 type Scalar: RealField + Copy;
72
73 fn id(&self) -> &GeometryId;
75
76 fn quantity(&self) -> usize;
78
79 fn measure(&self) -> Self::Scalar;
81
82 fn aabb(&self) -> ([Self::Scalar; 2], [Self::Scalar; 2]) {
84 let (min, max) = self.aabb_vec();
86 ([min[0], min[1]], [max[0], max[1]])
87 }
88
89 fn aabb_vec(&self) -> (Vec<Self::Scalar>, Vec<Self::Scalar>);
91
92 fn centroid(&self) -> Vec<Self::Scalar>;
94
95 fn validate(&self) -> Result<()>;
97
98 fn rotation_constraint(&self) -> &RotationConstraint<Self::Scalar>;
100
101 fn allow_mirror(&self) -> bool {
103 false
104 }
105
106 fn priority(&self) -> i32 {
108 0
109 }
110}
111
112pub trait Geometry2DExt: Geometry {
114 fn aabb_2d(&self) -> AABB2D<Self::Scalar>;
116
117 fn outer_ring(&self) -> &[(Self::Scalar, Self::Scalar)];
119
120 fn holes(&self) -> &[Vec<(Self::Scalar, Self::Scalar)>];
122
123 fn has_holes(&self) -> bool {
125 !self.holes().is_empty()
126 }
127
128 fn is_convex(&self) -> bool;
130
131 fn convex_hull(&self) -> Vec<(Self::Scalar, Self::Scalar)>;
133
134 fn perimeter(&self) -> Self::Scalar;
136}
137
138pub trait Geometry3DExt: Geometry {
140 fn aabb_3d(&self) -> AABB3D<Self::Scalar>;
142
143 fn surface_area(&self) -> Self::Scalar;
145
146 fn mass(&self) -> Option<Self::Scalar>;
148
149 fn center_of_mass(&self) -> (Self::Scalar, Self::Scalar, Self::Scalar);
151
152 fn stackable(&self) -> bool {
154 true
155 }
156
157 fn max_stack_load(&self) -> Option<Self::Scalar> {
159 None
160 }
161}
162
163pub trait Boundary: Clone + Send + Sync {
165 type Scalar: RealField + Copy;
167
168 fn measure(&self) -> Self::Scalar;
170
171 fn aabb(&self) -> ([Self::Scalar; 2], [Self::Scalar; 2]) {
173 let (min, max) = self.aabb_vec();
174 ([min[0], min[1]], [max[0], max[1]])
175 }
176
177 fn aabb_vec(&self) -> (Vec<Self::Scalar>, Vec<Self::Scalar>);
179
180 fn validate(&self) -> Result<()>;
182
183 fn contains_point(&self, point: &[Self::Scalar]) -> bool;
185}
186
187pub trait Boundary2DExt: Boundary {
189 fn aabb_2d(&self) -> AABB2D<Self::Scalar>;
191
192 fn vertices(&self) -> &[(Self::Scalar, Self::Scalar)];
194
195 fn contains_polygon(&self, polygon: &[(Self::Scalar, Self::Scalar)]) -> bool;
197
198 fn effective_area(&self, margin: Self::Scalar) -> Self::Scalar;
200}
201
202pub trait Boundary3DExt: Boundary {
204 fn aabb_3d(&self) -> AABB3D<Self::Scalar>;
206
207 fn max_mass(&self) -> Option<Self::Scalar>;
209
210 fn contains_box(&self, min: &[Self::Scalar; 3], max: &[Self::Scalar; 3]) -> bool;
212
213 fn effective_volume(&self, margin: Self::Scalar) -> Self::Scalar;
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
220#[derive(Default)]
221pub enum Orientation3D {
222 Fixed,
224 #[default]
226 AxisAligned,
227 Orthogonal,
229 Free,
231}
232
233impl Orientation3D {
234 pub fn count(&self) -> usize {
236 match self {
237 Self::Fixed => 1,
238 Self::AxisAligned => 6,
239 Self::Orthogonal => 24,
240 Self::Free => usize::MAX, }
242 }
243
244 pub fn is_fixed(&self) -> bool {
246 matches!(self, Self::Fixed)
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn test_rotation_constraint_axis_aligned() {
256 let constraint: RotationConstraint<f64> = RotationConstraint::axis_aligned();
257 let angles = constraint.angles();
258 assert_eq!(angles.len(), 4);
259 }
260
261 #[test]
262 fn test_rotation_constraint_steps() {
263 let constraint: RotationConstraint<f64> = RotationConstraint::steps(8);
264 let angles = constraint.angles();
265 assert_eq!(angles.len(), 8);
266 }
267
268 #[test]
269 fn test_orientation_3d_count() {
270 assert_eq!(Orientation3D::Fixed.count(), 1);
271 assert_eq!(Orientation3D::AxisAligned.count(), 6);
272 assert_eq!(Orientation3D::Orthogonal.count(), 24);
273 }
274}