1use crate::geometry::Geometry3D;
7use nalgebra::Vector3;
8use u_nesting_core::geometry::Geometry;
9
10#[derive(Debug, Clone, Copy)]
12pub struct Aabb3D {
13 pub min: [f64; 3],
15 pub max: [f64; 3],
17}
18
19impl Aabb3D {
20 pub fn new(min: [f64; 3], max: [f64; 3]) -> Self {
22 Self { min, max }
23 }
24
25 pub fn from_position_and_size(position: [f64; 3], size: [f64; 3]) -> Self {
27 Self {
28 min: position,
29 max: [
30 position[0] + size[0],
31 position[1] + size[1],
32 position[2] + size[2],
33 ],
34 }
35 }
36
37 pub fn intersects(&self, other: &Aabb3D) -> bool {
39 self.min[0] < other.max[0]
40 && self.max[0] > other.min[0]
41 && self.min[1] < other.max[1]
42 && self.max[1] > other.min[1]
43 && self.min[2] < other.max[2]
44 && self.max[2] > other.min[2]
45 }
46
47 pub fn contains_point(&self, point: [f64; 3]) -> bool {
49 point[0] >= self.min[0]
50 && point[0] <= self.max[0]
51 && point[1] >= self.min[1]
52 && point[1] <= self.max[1]
53 && point[2] >= self.min[2]
54 && point[2] <= self.max[2]
55 }
56
57 pub fn is_within(&self, other: &Aabb3D) -> bool {
59 self.min[0] >= other.min[0]
60 && self.min[1] >= other.min[1]
61 && self.min[2] >= other.min[2]
62 && self.max[0] <= other.max[0]
63 && self.max[1] <= other.max[1]
64 && self.max[2] <= other.max[2]
65 }
66
67 pub fn expand(&self, margin: f64) -> Self {
69 Self {
70 min: [
71 self.min[0] - margin,
72 self.min[1] - margin,
73 self.min[2] - margin,
74 ],
75 max: [
76 self.max[0] + margin,
77 self.max[1] + margin,
78 self.max[2] + margin,
79 ],
80 }
81 }
82
83 pub fn volume(&self) -> f64 {
85 (self.max[0] - self.min[0]) * (self.max[1] - self.min[1]) * (self.max[2] - self.min[2])
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct SpatialEntry3D {
92 pub index: usize,
94 pub id: String,
96 pub aabb: Aabb3D,
98}
99
100impl SpatialEntry3D {
101 pub fn new(index: usize, id: String, aabb: Aabb3D) -> Self {
103 Self { index, id, aabb }
104 }
105
106 pub fn from_placed(
108 index: usize,
109 geometry: &Geometry3D,
110 position: Vector3<f64>,
111 orientation: usize,
112 ) -> Self {
113 let aabb = compute_transformed_aabb(geometry, position, orientation);
114 Self {
115 index,
116 id: geometry.id().clone(),
117 aabb,
118 }
119 }
120}
121
122#[derive(Debug)]
128pub struct SpatialIndex3D {
129 entries: Vec<SpatialEntry3D>,
130}
131
132impl SpatialIndex3D {
133 pub fn new() -> Self {
135 Self {
136 entries: Vec::new(),
137 }
138 }
139
140 pub fn with_capacity(capacity: usize) -> Self {
142 Self {
143 entries: Vec::with_capacity(capacity),
144 }
145 }
146
147 pub fn insert(&mut self, entry: SpatialEntry3D) {
149 self.entries.push(entry);
150 }
151
152 pub fn insert_geometry(
154 &mut self,
155 index: usize,
156 geometry: &Geometry3D,
157 position: Vector3<f64>,
158 orientation: usize,
159 ) {
160 let entry = SpatialEntry3D::from_placed(index, geometry, position, orientation);
161 self.insert(entry);
162 }
163
164 pub fn len(&self) -> usize {
166 self.entries.len()
167 }
168
169 pub fn is_empty(&self) -> bool {
171 self.entries.is_empty()
172 }
173
174 pub fn clear(&mut self) {
176 self.entries.clear();
177 }
178
179 pub fn query_aabb(&self, query_aabb: &Aabb3D) -> Vec<&SpatialEntry3D> {
181 self.entries
182 .iter()
183 .filter(|entry| entry.aabb.intersects(query_aabb))
184 .collect()
185 }
186
187 pub fn query_geometry(
189 &self,
190 geometry: &Geometry3D,
191 position: Vector3<f64>,
192 orientation: usize,
193 ) -> Vec<&SpatialEntry3D> {
194 let query_aabb = compute_transformed_aabb(geometry, position, orientation);
195 self.query_aabb(&query_aabb)
196 }
197
198 pub fn query_with_margin(
201 &self,
202 geometry: &Geometry3D,
203 position: Vector3<f64>,
204 orientation: usize,
205 margin: f64,
206 ) -> Vec<&SpatialEntry3D> {
207 let base_aabb = compute_transformed_aabb(geometry, position, orientation);
208 let expanded_aabb = base_aabb.expand(margin);
209 self.query_aabb(&expanded_aabb)
210 }
211
212 pub fn iter(&self) -> impl Iterator<Item = &SpatialEntry3D> {
214 self.entries.iter()
215 }
216
217 pub fn get_potential_collisions(
219 &self,
220 geometry: &Geometry3D,
221 position: Vector3<f64>,
222 orientation: usize,
223 spacing: f64,
224 ) -> Vec<usize> {
225 self.query_with_margin(geometry, position, orientation, spacing)
226 .iter()
227 .map(|entry| entry.index)
228 .collect()
229 }
230
231 pub fn has_collision(
233 &self,
234 geometry: &Geometry3D,
235 position: Vector3<f64>,
236 orientation: usize,
237 spacing: f64,
238 ) -> bool {
239 let base_aabb = compute_transformed_aabb(geometry, position, orientation);
240 let expanded_aabb = base_aabb.expand(spacing);
241
242 self.entries
243 .iter()
244 .any(|entry| entry.aabb.intersects(&expanded_aabb))
245 }
246
247 pub fn can_place(
250 &self,
251 geometry: &Geometry3D,
252 position: Vector3<f64>,
253 orientation: usize,
254 boundary_aabb: &Aabb3D,
255 spacing: f64,
256 ) -> bool {
257 let geom_aabb = compute_transformed_aabb(geometry, position, orientation);
258
259 if !geom_aabb.is_within(boundary_aabb) {
261 return false;
262 }
263
264 !self.has_collision(geometry, position, orientation, spacing)
266 }
267}
268
269impl Default for SpatialIndex3D {
270 fn default() -> Self {
271 Self::new()
272 }
273}
274
275pub fn compute_transformed_aabb(
277 geometry: &Geometry3D,
278 position: Vector3<f64>,
279 orientation: usize,
280) -> Aabb3D {
281 let dims = geometry.dimensions_for_orientation(orientation);
282
283 Aabb3D::from_position_and_size(
284 [position.x, position.y, position.z],
285 [dims.x, dims.y, dims.z],
286 )
287}
288
289pub fn boundary_aabb(width: f64, depth: f64, height: f64, margin: f64) -> Aabb3D {
291 Aabb3D::new(
292 [margin, margin, margin],
293 [width - margin, depth - margin, height - margin],
294 )
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_aabb_intersects() {
303 let a = Aabb3D::new([0.0, 0.0, 0.0], [10.0, 10.0, 10.0]);
304 let b = Aabb3D::new([5.0, 5.0, 5.0], [15.0, 15.0, 15.0]);
305 let c = Aabb3D::new([20.0, 20.0, 20.0], [30.0, 30.0, 30.0]);
306
307 assert!(a.intersects(&b));
308 assert!(b.intersects(&a));
309 assert!(!a.intersects(&c));
310 assert!(!c.intersects(&a));
311 }
312
313 #[test]
314 fn test_aabb_is_within() {
315 let outer = Aabb3D::new([0.0, 0.0, 0.0], [100.0, 100.0, 100.0]);
316 let inner = Aabb3D::new([10.0, 10.0, 10.0], [20.0, 20.0, 20.0]);
317 let partial = Aabb3D::new([90.0, 90.0, 90.0], [110.0, 110.0, 110.0]);
318
319 assert!(inner.is_within(&outer));
320 assert!(!partial.is_within(&outer));
321 assert!(!outer.is_within(&inner));
322 }
323
324 #[test]
325 fn test_spatial_index_new() {
326 let index = SpatialIndex3D::new();
327 assert!(index.is_empty());
328 assert_eq!(index.len(), 0);
329 }
330
331 #[test]
332 fn test_spatial_index_insert() {
333 let mut index = SpatialIndex3D::new();
334 let aabb = Aabb3D::new([0.0, 0.0, 0.0], [10.0, 10.0, 10.0]);
335 let entry = SpatialEntry3D::new(0, "test".to_string(), aabb);
336 index.insert(entry);
337
338 assert!(!index.is_empty());
339 assert_eq!(index.len(), 1);
340 }
341
342 #[test]
343 fn test_spatial_index_query_aabb() {
344 let mut index = SpatialIndex3D::new();
345
346 index.insert(SpatialEntry3D::new(
348 0,
349 "b1".to_string(),
350 Aabb3D::new([0.0, 0.0, 0.0], [10.0, 10.0, 10.0]),
351 ));
352 index.insert(SpatialEntry3D::new(
353 1,
354 "b2".to_string(),
355 Aabb3D::new([20.0, 0.0, 0.0], [30.0, 10.0, 10.0]),
356 ));
357 index.insert(SpatialEntry3D::new(
358 2,
359 "b3".to_string(),
360 Aabb3D::new([0.0, 20.0, 0.0], [10.0, 30.0, 10.0]),
361 ));
362
363 let query = Aabb3D::new([5.0, 5.0, 5.0], [15.0, 15.0, 15.0]);
365 let results = index.query_aabb(&query);
366 assert_eq!(results.len(), 1);
367 assert_eq!(results[0].index, 0);
368
369 let query = Aabb3D::new([5.0, 0.0, 0.0], [25.0, 10.0, 10.0]);
371 let results = index.query_aabb(&query);
372 assert_eq!(results.len(), 2);
373
374 let query = Aabb3D::new([50.0, 50.0, 50.0], [60.0, 60.0, 60.0]);
376 let results = index.query_aabb(&query);
377 assert!(results.is_empty());
378
379 let query = Aabb3D::new([-10.0, -10.0, -10.0], [40.0, 40.0, 40.0]);
381 let results = index.query_aabb(&query);
382 assert_eq!(results.len(), 3);
383 }
384
385 #[test]
386 fn test_spatial_index_with_geometry() {
387 let mut index = SpatialIndex3D::new();
388
389 let geom1 = Geometry3D::new("B1", 10.0, 10.0, 10.0);
390 let geom2 = Geometry3D::new("B2", 10.0, 10.0, 10.0);
391
392 index.insert_geometry(0, &geom1, Vector3::new(0.0, 0.0, 0.0), 0);
393 index.insert_geometry(1, &geom2, Vector3::new(20.0, 0.0, 0.0), 0);
394
395 assert_eq!(index.len(), 2);
396
397 let query_geom = Geometry3D::new("Q", 10.0, 10.0, 10.0);
399 let results = index.query_geometry(&query_geom, Vector3::new(5.0, 0.0, 0.0), 0);
400
401 assert_eq!(results.len(), 1);
403 assert_eq!(results[0].index, 0);
404 }
405
406 #[test]
407 fn test_has_collision() {
408 let mut index = SpatialIndex3D::new();
409
410 let geom1 = Geometry3D::new("B1", 10.0, 10.0, 10.0);
411 index.insert_geometry(0, &geom1, Vector3::new(0.0, 0.0, 0.0), 0);
412
413 let query_geom = Geometry3D::new("Q", 5.0, 5.0, 5.0);
414
415 assert!(index.has_collision(&query_geom, Vector3::new(5.0, 5.0, 5.0), 0, 0.0));
417
418 assert!(!index.has_collision(&query_geom, Vector3::new(50.0, 0.0, 0.0), 0, 0.0));
420
421 assert!(index.has_collision(&query_geom, Vector3::new(12.0, 0.0, 0.0), 0, 3.0));
423
424 assert!(!index.has_collision(&query_geom, Vector3::new(12.0, 0.0, 0.0), 0, 0.0));
426 }
427
428 #[test]
429 fn test_can_place() {
430 let mut index = SpatialIndex3D::new();
431
432 let geom1 = Geometry3D::new("B1", 10.0, 10.0, 10.0);
433 index.insert_geometry(0, &geom1, Vector3::new(5.0, 5.0, 5.0), 0);
434
435 let boundary = Aabb3D::new([0.0, 0.0, 0.0], [100.0, 100.0, 100.0]);
436 let query_geom = Geometry3D::new("Q", 10.0, 10.0, 10.0);
437
438 assert!(index.can_place(
440 &query_geom,
441 Vector3::new(20.0, 20.0, 20.0),
442 0,
443 &boundary,
444 0.0
445 ));
446
447 assert!(!index.can_place(&query_geom, Vector3::new(5.0, 5.0, 5.0), 0, &boundary, 0.0));
449
450 assert!(!index.can_place(
452 &query_geom,
453 Vector3::new(95.0, 95.0, 95.0),
454 0,
455 &boundary,
456 0.0
457 ));
458
459 assert!(!index.can_place(&query_geom, Vector3::new(-5.0, 5.0, 5.0), 0, &boundary, 0.0));
461 }
462
463 #[test]
464 fn test_transformed_aabb() {
465 let geom = Geometry3D::new("B", 10.0, 20.0, 30.0);
466 let aabb = compute_transformed_aabb(&geom, Vector3::new(5.0, 5.0, 5.0), 0);
467
468 assert!((aabb.min[0] - 5.0).abs() < 1e-10);
469 assert!((aabb.min[1] - 5.0).abs() < 1e-10);
470 assert!((aabb.min[2] - 5.0).abs() < 1e-10);
471 assert!((aabb.max[0] - 15.0).abs() < 1e-10);
472 assert!((aabb.max[1] - 25.0).abs() < 1e-10);
473 assert!((aabb.max[2] - 35.0).abs() < 1e-10);
474 }
475
476 #[test]
477 fn test_boundary_aabb() {
478 let aabb = boundary_aabb(100.0, 80.0, 50.0, 5.0);
479
480 assert!((aabb.min[0] - 5.0).abs() < 1e-10);
481 assert!((aabb.min[1] - 5.0).abs() < 1e-10);
482 assert!((aabb.min[2] - 5.0).abs() < 1e-10);
483 assert!((aabb.max[0] - 95.0).abs() < 1e-10);
484 assert!((aabb.max[1] - 75.0).abs() < 1e-10);
485 assert!((aabb.max[2] - 45.0).abs() < 1e-10);
486 }
487
488 #[test]
489 fn test_get_potential_collisions() {
490 let mut index = SpatialIndex3D::new();
491
492 let geom1 = Geometry3D::new("B1", 10.0, 10.0, 10.0);
493 let geom2 = Geometry3D::new("B2", 10.0, 10.0, 10.0);
494 let geom3 = Geometry3D::new("B3", 10.0, 10.0, 10.0);
495
496 index.insert_geometry(0, &geom1, Vector3::new(0.0, 0.0, 0.0), 0);
497 index.insert_geometry(1, &geom2, Vector3::new(50.0, 0.0, 0.0), 0);
498 index.insert_geometry(2, &geom3, Vector3::new(0.0, 50.0, 0.0), 0);
499
500 let query_geom = Geometry3D::new("Q", 5.0, 5.0, 5.0);
501
502 let collisions =
504 index.get_potential_collisions(&query_geom, Vector3::new(5.0, 5.0, 5.0), 0, 2.0);
505 assert_eq!(collisions.len(), 1);
506 assert_eq!(collisions[0], 0);
507 }
508}