1use crate::shape::{RayHit, Shape};
7use oxiphysics_core::Aabb;
8use oxiphysics_core::math::{Mat3, Real, Vec3};
9
10#[derive(Debug, Clone)]
12pub struct BoxShape {
13 pub half_extents: Vec3,
15}
16
17impl BoxShape {
18 pub fn new(half_extents: Vec3) -> Self {
20 Self { half_extents }
21 }
22
23 #[allow(dead_code)]
25 pub fn volume_explicit(&self) -> Real {
26 8.0 * self.half_extents.x * self.half_extents.y * self.half_extents.z
27 }
28
29 #[allow(dead_code)]
31 pub fn surface_area(&self) -> Real {
32 let hx = self.half_extents.x;
33 let hy = self.half_extents.y;
34 let hz = self.half_extents.z;
35 let ax = 2.0 * hx;
36 let ay = 2.0 * hy;
37 let az = 2.0 * hz;
38 2.0 * (ax * ay + ay * az + ax * az)
39 }
40
41 #[allow(dead_code)]
44 pub fn inertia_tensor_array(&self, mass: f64) -> [[f64; 3]; 3] {
45 let hx = self.half_extents.x;
46 let hy = self.half_extents.y;
47 let hz = self.half_extents.z;
48 let ax2 = (2.0 * hx).powi(2);
49 let ay2 = (2.0 * hy).powi(2);
50 let az2 = (2.0 * hz).powi(2);
51 let k = mass / 12.0;
52 [
53 [k * (ay2 + az2), 0.0, 0.0],
54 [0.0, k * (ax2 + az2), 0.0],
55 [0.0, 0.0, k * (ax2 + ay2)],
56 ]
57 }
58
59 #[allow(dead_code)]
61 pub fn ray_cast_array(
62 &self,
63 origin: [f64; 3],
64 direction: [f64; 3],
65 max_toi: f64,
66 ) -> Option<(f64, [f64; 3])> {
67 let o = Vec3::new(origin[0], origin[1], origin[2]);
68 let d = Vec3::new(direction[0], direction[1], direction[2]);
69 let hit = self.ray_cast(&o, &d, max_toi)?;
70 Some((hit.toi, [hit.normal.x, hit.normal.y, hit.normal.z]))
71 }
72
73 #[allow(dead_code)]
75 pub fn support(&self, direction: [f64; 3]) -> [f64; 3] {
76 [
77 self.half_extents.x.copysign(direction[0]),
78 self.half_extents.y.copysign(direction[1]),
79 self.half_extents.z.copysign(direction[2]),
80 ]
81 }
82
83 #[allow(dead_code)]
85 pub fn vertex_list(&self) -> [[f64; 3]; 8] {
86 let hx = self.half_extents.x;
87 let hy = self.half_extents.y;
88 let hz = self.half_extents.z;
89 [
90 [-hx, -hy, -hz],
91 [hx, -hy, -hz],
92 [hx, hy, -hz],
93 [-hx, hy, -hz],
94 [-hx, -hy, hz],
95 [hx, -hy, hz],
96 [hx, hy, hz],
97 [-hx, hy, hz],
98 ]
99 }
100
101 #[allow(dead_code)]
105 pub fn face_normals() -> [[f64; 3]; 6] {
106 [
107 [1.0, 0.0, 0.0],
108 [-1.0, 0.0, 0.0],
109 [0.0, 1.0, 0.0],
110 [0.0, -1.0, 0.0],
111 [0.0, 0.0, 1.0],
112 [0.0, 0.0, -1.0],
113 ]
114 }
115
116 #[allow(dead_code)]
118 pub fn edge_list() -> [(usize, usize); 12] {
119 [
120 (0, 1),
122 (1, 5),
123 (5, 4),
124 (4, 0),
125 (2, 3),
127 (3, 7),
128 (7, 6),
129 (6, 2),
130 (0, 3),
132 (1, 2),
133 (5, 6),
134 (4, 7),
135 ]
136 }
137
138 #[allow(dead_code)]
141 pub fn face_vertex_indices() -> [[usize; 4]; 6] {
142 [
143 [1, 2, 6, 5], [0, 4, 7, 3], [2, 3, 7, 6], [0, 1, 5, 4], [4, 5, 6, 7], [0, 3, 2, 1], ]
150 }
151
152 #[allow(dead_code)]
154 pub fn face_areas(&self) -> [f64; 6] {
155 let hx = self.half_extents.x;
156 let hy = self.half_extents.y;
157 let hz = self.half_extents.z;
158 let yz = 4.0 * hy * hz; let xz = 4.0 * hx * hz; let xy = 4.0 * hx * hy; [yz, yz, xz, xz, xy, xy]
162 }
163
164 #[allow(dead_code)]
166 pub fn closest_point(&self, p: [f64; 3]) -> [f64; 3] {
167 [
168 p[0].clamp(-self.half_extents.x, self.half_extents.x),
169 p[1].clamp(-self.half_extents.y, self.half_extents.y),
170 p[2].clamp(-self.half_extents.z, self.half_extents.z),
171 ]
172 }
173
174 #[allow(dead_code)]
176 pub fn contains_point(&self, p: [f64; 3]) -> bool {
177 p[0].abs() <= self.half_extents.x
178 && p[1].abs() <= self.half_extents.y
179 && p[2].abs() <= self.half_extents.z
180 }
181
182 #[allow(dead_code)]
185 pub fn signed_distance(&self, p: [f64; 3]) -> f64 {
186 let dx = p[0].abs() - self.half_extents.x;
187 let dy = p[1].abs() - self.half_extents.y;
188 let dz = p[2].abs() - self.half_extents.z;
189
190 if dx <= 0.0 && dy <= 0.0 && dz <= 0.0 {
191 dx.max(dy).max(dz)
193 } else {
194 let cx = dx.max(0.0);
196 let cy = dy.max(0.0);
197 let cz = dz.max(0.0);
198 (cx * cx + cy * cy + cz * cz).sqrt()
199 }
200 }
201
202 #[allow(dead_code)]
206 pub fn clip_segment(&self, a: [f64; 3], b: [f64; 3]) -> Option<(f64, f64)> {
207 let dir = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
208 let half = [
209 self.half_extents.x,
210 self.half_extents.y,
211 self.half_extents.z,
212 ];
213
214 let mut tmin = 0.0_f64;
215 let mut tmax = 1.0_f64;
216
217 for i in 0..3 {
218 if dir[i].abs() < 1e-12 {
219 if a[i] < -half[i] || a[i] > half[i] {
220 return None;
221 }
222 } else {
223 let inv_d = 1.0 / dir[i];
224 let mut t1 = (-half[i] - a[i]) * inv_d;
225 let mut t2 = (half[i] - a[i]) * inv_d;
226 if t1 > t2 {
227 std::mem::swap(&mut t1, &mut t2);
228 }
229 tmin = tmin.max(t1);
230 tmax = tmax.min(t2);
231 if tmin > tmax {
232 return None;
233 }
234 }
235 }
236
237 Some((tmin.max(0.0), tmax.min(1.0)))
238 }
239
240 #[allow(dead_code)]
243 pub fn classify_face(&self, p: [f64; 3]) -> usize {
244 let dx_pos = (p[0] - self.half_extents.x).abs();
245 let dx_neg = (p[0] + self.half_extents.x).abs();
246 let dy_pos = (p[1] - self.half_extents.y).abs();
247 let dy_neg = (p[1] + self.half_extents.y).abs();
248 let dz_pos = (p[2] - self.half_extents.z).abs();
249 let dz_neg = (p[2] + self.half_extents.z).abs();
250
251 let dists = [dx_pos, dx_neg, dy_pos, dy_neg, dz_pos, dz_neg];
252 let mut min_idx = 0;
253 let mut min_val = dists[0];
254 for (i, &d) in dists.iter().enumerate().skip(1) {
255 if d < min_val {
256 min_val = d;
257 min_idx = i;
258 }
259 }
260 min_idx
261 }
262
263 #[allow(dead_code)]
265 pub fn diagonal_length(&self) -> f64 {
266 let hx = self.half_extents.x;
267 let hy = self.half_extents.y;
268 let hz = self.half_extents.z;
269 2.0 * (hx * hx + hy * hy + hz * hz).sqrt()
270 }
271
272 #[allow(dead_code)]
274 pub fn edge_lengths(&self) -> [f64; 3] {
275 [
276 2.0 * self.half_extents.x,
277 2.0 * self.half_extents.y,
278 2.0 * self.half_extents.z,
279 ]
280 }
281
282 #[allow(dead_code)]
284 pub fn project_on_axis(&self, axis: [f64; 3]) -> (f64, f64) {
285 let extent = self.half_extents.x * axis[0].abs()
287 + self.half_extents.y * axis[1].abs()
288 + self.half_extents.z * axis[2].abs();
289 (-extent, extent)
290 }
291}
292
293impl Shape for BoxShape {
294 fn bounding_box(&self) -> Aabb {
295 Aabb::new(-self.half_extents, self.half_extents)
296 }
297
298 fn support_point(&self, direction: &Vec3) -> Vec3 {
299 Vec3::new(
300 self.half_extents.x.copysign(direction.x),
301 self.half_extents.y.copysign(direction.y),
302 self.half_extents.z.copysign(direction.z),
303 )
304 }
305
306 fn volume(&self) -> Real {
307 8.0 * self.half_extents.x * self.half_extents.y * self.half_extents.z
308 }
309
310 fn center_of_mass(&self) -> Vec3 {
311 Vec3::zeros()
312 }
313
314 fn inertia_tensor(&self, mass: Real) -> Mat3 {
315 let hx = self.half_extents.x;
316 let hy = self.half_extents.y;
317 let hz = self.half_extents.z;
318 let ex2 = (2.0 * hx).powi(2);
320 let ey2 = (2.0 * hy).powi(2);
321 let ez2 = (2.0 * hz).powi(2);
322 let k = mass / 12.0;
323 Mat3::new(
324 k * (ey2 + ez2),
325 0.0,
326 0.0,
327 0.0,
328 k * (ex2 + ez2),
329 0.0,
330 0.0,
331 0.0,
332 k * (ex2 + ey2),
333 )
334 }
335
336 fn ray_cast(&self, ray_origin: &Vec3, ray_direction: &Vec3, max_toi: Real) -> Option<RayHit> {
337 let mut tmin = Real::NEG_INFINITY;
339 let mut tmax = Real::INFINITY;
340 let mut normal = Vec3::zeros();
341
342 for i in 0..3 {
343 let o = ray_origin[i];
344 let d = ray_direction[i];
345 let half = self.half_extents[i];
346
347 if d.abs() < 1e-12 {
348 if o < -half || o > half {
349 return None;
350 }
351 } else {
352 let inv_d = 1.0 / d;
353 let mut t1 = (-half - o) * inv_d;
354 let mut t2 = (half - o) * inv_d;
355 let mut n = Vec3::zeros();
356 n[i] = -1.0;
357
358 if t1 > t2 {
359 std::mem::swap(&mut t1, &mut t2);
360 n[i] = 1.0;
361 }
362
363 if t1 > tmin {
364 tmin = t1;
365 normal = n;
366 }
367 if t2 < tmax {
368 tmax = t2;
369 }
370
371 if tmin > tmax {
372 return None;
373 }
374 }
375 }
376
377 let t = if tmin >= 0.0 { tmin } else { tmax };
378 if t < 0.0 || t > max_toi {
379 return None;
380 }
381
382 let point = ray_origin + ray_direction * t;
383 Some(RayHit {
384 point,
385 normal,
386 toi: t,
387 })
388 }
389}
390
391#[allow(dead_code)]
397fn dot3b(a: [f64; 3], b: [f64; 3]) -> f64 {
398 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
399}
400
401#[allow(dead_code)]
403fn mat3_mul_vec(rot: [[f64; 3]; 3], v: [f64; 3]) -> [f64; 3] {
404 [dot3b(rot[0], v), dot3b(rot[1], v), dot3b(rot[2], v)]
405}
406
407#[allow(dead_code)]
409fn mat3_transpose(rot: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
410 [
411 [rot[0][0], rot[1][0], rot[2][0]],
412 [rot[0][1], rot[1][1], rot[2][1]],
413 [rot[0][2], rot[1][2], rot[2][2]],
414 ]
415}
416
417#[allow(dead_code)]
419fn mat3_mul(a: [[f64; 3]; 3], b: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
420 let bt = mat3_transpose(b);
421 [
422 [dot3b(a[0], bt[0]), dot3b(a[0], bt[1]), dot3b(a[0], bt[2])],
423 [dot3b(a[1], bt[0]), dot3b(a[1], bt[1]), dot3b(a[1], bt[2])],
424 [dot3b(a[2], bt[0]), dot3b(a[2], bt[1]), dot3b(a[2], bt[2])],
425 ]
426}
427
428#[allow(dead_code)]
432pub fn obb_inertia_tensor(half_extents: [f64; 3], mass: f64) -> [[f64; 3]; 3] {
433 let ax2 = (2.0 * half_extents[0]).powi(2);
434 let ay2 = (2.0 * half_extents[1]).powi(2);
435 let az2 = (2.0 * half_extents[2]).powi(2);
436 let k = mass / 12.0;
437 [
438 [k * (ay2 + az2), 0.0, 0.0],
439 [0.0, k * (ax2 + az2), 0.0],
440 [0.0, 0.0, k * (ax2 + ay2)],
441 ]
442}
443
444#[allow(dead_code)]
448pub fn obb_inertia_tensor_rotated(
449 half_extents: [f64; 3],
450 mass: f64,
451 rot: [[f64; 3]; 3],
452) -> [[f64; 3]; 3] {
453 let i_local = obb_inertia_tensor(half_extents, mass);
454 let ri = mat3_mul(rot, i_local);
456 mat3_mul(ri, mat3_transpose(rot))
457}
458
459#[allow(dead_code)]
465pub fn obb_closest_point(
466 p: [f64; 3],
467 center: [f64; 3],
468 half_extents: [f64; 3],
469 rot: [[f64; 3]; 3],
470) -> [f64; 3] {
471 let d = [p[0] - center[0], p[1] - center[1], p[2] - center[2]];
473 let local = mat3_mul_vec(rot, d);
474
475 let clamped = [
477 local[0].clamp(-half_extents[0], half_extents[0]),
478 local[1].clamp(-half_extents[1], half_extents[1]),
479 local[2].clamp(-half_extents[2], half_extents[2]),
480 ];
481
482 let rt = mat3_transpose(rot);
484 let world_offset = mat3_mul_vec(rt, clamped);
485 [
486 center[0] + world_offset[0],
487 center[1] + world_offset[1],
488 center[2] + world_offset[2],
489 ]
490}
491
492#[allow(dead_code)]
495pub fn obb_projection_extent(half_extents: [f64; 3], rot: [[f64; 3]; 3], axis: [f64; 3]) -> f64 {
496 let mut ext = 0.0_f64;
499 for i in 0..3 {
500 ext += half_extents[i] * dot3b(rot[i], axis).abs();
501 }
502 ext
503}
504
505#[allow(dead_code)]
510pub fn obb_ray_intersection(
511 ray_origin: [f64; 3],
512 ray_dir: [f64; 3],
513 center: [f64; 3],
514 half_extents: [f64; 3],
515 rot: [[f64; 3]; 3],
516 max_toi: f64,
517) -> Option<f64> {
518 let d_world = [
520 ray_origin[0] - center[0],
521 ray_origin[1] - center[1],
522 ray_origin[2] - center[2],
523 ];
524 let o_local = mat3_mul_vec(rot, d_world);
525 let dir_local = mat3_mul_vec(rot, ray_dir);
526
527 let mut tmin = f64::NEG_INFINITY;
528 let mut tmax = f64::INFINITY;
529
530 for i in 0..3 {
531 let o = o_local[i];
532 let d = dir_local[i];
533 let h = half_extents[i];
534
535 if d.abs() < 1e-12 {
536 if o < -h || o > h {
537 return None;
538 }
539 } else {
540 let inv_d = 1.0 / d;
541 let t1 = (-h - o) * inv_d;
542 let t2 = (h - o) * inv_d;
543 let (t_near, t_far) = if t1 <= t2 { (t1, t2) } else { (t2, t1) };
544 tmin = tmin.max(t_near);
545 tmax = tmax.min(t_far);
546 if tmin > tmax {
547 return None;
548 }
549 }
550 }
551
552 let t = if tmin >= 0.0 { tmin } else { tmax };
553 if t < 0.0 || t > max_toi {
554 None
555 } else {
556 Some(t)
557 }
558}
559
560#[allow(dead_code)]
564pub fn obb_plane_distance(
565 center: [f64; 3],
566 half_extents: [f64; 3],
567 rot: [[f64; 3]; 3],
568 plane: [f64; 4],
569) -> (f64, f64) {
570 let normal = [plane[0], plane[1], plane[2]];
571 let plane_d = plane[3];
572
573 let center_dist = dot3b(center, normal) + plane_d;
575
576 let extent = obb_projection_extent(half_extents, rot, normal);
578
579 (center_dist - extent, center_dist + extent)
580}
581
582#[allow(dead_code)]
584pub fn obb_vertices(center: [f64; 3], half_extents: [f64; 3], rot: [[f64; 3]; 3]) -> Vec<[f64; 3]> {
585 let rt = mat3_transpose(rot);
586 let signs = [
587 [-1.0_f64, -1.0, -1.0],
588 [1.0, -1.0, -1.0],
589 [1.0, 1.0, -1.0],
590 [-1.0, 1.0, -1.0],
591 [-1.0, -1.0, 1.0],
592 [1.0, -1.0, 1.0],
593 [1.0, 1.0, 1.0],
594 [-1.0, 1.0, 1.0],
595 ];
596 signs
597 .iter()
598 .map(|s| {
599 let local = [
600 s[0] * half_extents[0],
601 s[1] * half_extents[1],
602 s[2] * half_extents[2],
603 ];
604 let world = mat3_mul_vec(rt, local);
605 [
606 center[0] + world[0],
607 center[1] + world[1],
608 center[2] + world[2],
609 ]
610 })
611 .collect()
612}
613
614#[allow(dead_code)]
616pub fn obb_edges(
617 center: [f64; 3],
618 half_extents: [f64; 3],
619 rot: [[f64; 3]; 3],
620) -> Vec<([f64; 3], [f64; 3])> {
621 let verts = obb_vertices(center, half_extents, rot);
622 let pairs: [(usize, usize); 12] = [
624 (0, 1),
625 (1, 5),
626 (5, 4),
627 (4, 0),
628 (2, 3),
629 (3, 7),
630 (7, 6),
631 (6, 2),
632 (0, 3),
633 (1, 2),
634 (5, 6),
635 (4, 7),
636 ];
637 pairs.iter().map(|&(a, b)| (verts[a], verts[b])).collect()
638}
639
640#[allow(dead_code)]
643pub fn obb_face_centers(
644 center: [f64; 3],
645 half_extents: [f64; 3],
646 rot: [[f64; 3]; 3],
647) -> Vec<[f64; 3]> {
648 let rt = mat3_transpose(rot);
649 let mut fc = Vec::with_capacity(6);
650 for axis in 0..3 {
651 for sign in [1.0_f64, -1.0_f64] {
652 let mut local = [0.0_f64; 3];
653 local[axis] = sign * half_extents[axis];
654 let world = mat3_mul_vec(rt, local);
655 fc.push([
656 center[0] + world[0],
657 center[1] + world[1],
658 center[2] + world[2],
659 ]);
660 }
661 }
662 fc
663}
664
665#[allow(dead_code)]
667pub fn obb_support_fn(
668 center: [f64; 3],
669 half_extents: [f64; 3],
670 rot: [[f64; 3]; 3],
671 direction: [f64; 3],
672) -> [f64; 3] {
673 let local_dir = mat3_mul_vec(rot, direction);
675 let local_support = [
676 half_extents[0].copysign(local_dir[0]),
677 half_extents[1].copysign(local_dir[1]),
678 half_extents[2].copysign(local_dir[2]),
679 ];
680 let rt = mat3_transpose(rot);
681 let world_offset = mat3_mul_vec(rt, local_support);
682 [
683 center[0] + world_offset[0],
684 center[1] + world_offset[1],
685 center[2] + world_offset[2],
686 ]
687}
688
689#[allow(dead_code)]
691pub fn obb_aabb_bounds(
692 center: [f64; 3],
693 half_extents: [f64; 3],
694 rot: [[f64; 3]; 3],
695) -> ([f64; 3], [f64; 3]) {
696 let mut lo = center;
697 let mut hi = center;
698 let rt = mat3_transpose(rot);
699 for axis in 0..3 {
700 for k in 0..3 {
702 let contribution = rt[k][axis].abs() * half_extents[axis];
703 lo[k] -= contribution;
704 hi[k] += contribution;
705 }
706 }
707 (lo, hi)
708}
709
710#[cfg(test)]
711mod proptest_tests {
712 use super::*;
713 use crate::BoxShape;
714 use crate::box_shape::Vec3;
715
716 use proptest::prelude::*;
717
718 fn pos_half() -> impl Strategy<Value = f64> {
719 0.01_f64..100.0_f64
720 }
721
722 proptest! {
723 #[test]
724 fn prop_box_volume_nonneg(
725 hx in pos_half(),
726 hy in pos_half(),
727 hz in pos_half(),
728 ) {
729 let b = BoxShape::new(Vec3::new(hx, hy, hz));
730 prop_assert!(b.volume() >= 0.0, "volume negative: {}", b.volume());
731 }
732
733 #[test]
734 fn prop_box_inertia_positive_definite(
735 hx in pos_half(),
736 hy in pos_half(),
737 hz in pos_half(),
738 m in 0.01_f64..1000.0_f64,
739 ) {
740 let b = BoxShape::new(Vec3::new(hx, hy, hz));
741 let it = b.inertia_tensor(m);
742 prop_assert!(it[(0, 0)] > 0.0, "Ixx not positive: {}", it[(0, 0)]);
743 prop_assert!(it[(1, 1)] > 0.0, "Iyy not positive: {}", it[(1, 1)]);
744 prop_assert!(it[(2, 2)] > 0.0, "Izz not positive: {}", it[(2, 2)]);
745 }
746
747 #[test]
748 fn prop_box_volume_scales_cubically(
749 hx in pos_half(),
750 hy in pos_half(),
751 hz in pos_half(),
752 ) {
753 let b1 = BoxShape::new(Vec3::new(hx, hy, hz));
754 let b2 = BoxShape::new(Vec3::new(2.0 * hx, 2.0 * hy, 2.0 * hz));
755 let ratio = b2.volume() / b1.volume();
756 prop_assert!(
757 (ratio - 8.0).abs() < 1e-6,
758 "box volume ratio for 2x scale should be 8, got {}",
759 ratio
760 );
761 }
762 }
763}
764
765#[cfg(test)]
766mod tests {
767 use super::*;
768 use crate::BoxShape;
769 use crate::box_shape::Vec3;
770 use crate::box_shape::obb_aabb_bounds;
771 use crate::box_shape::obb_closest_point;
772 use crate::box_shape::obb_edges;
773 use crate::box_shape::obb_face_centers;
774 use crate::box_shape::obb_inertia_tensor;
775 use crate::box_shape::obb_inertia_tensor_rotated;
776 use crate::box_shape::obb_plane_distance;
777 use crate::box_shape::obb_projection_extent;
778 use crate::box_shape::obb_ray_intersection;
779 use crate::box_shape::obb_support_fn;
780 use crate::box_shape::obb_vertices;
781
782 #[test]
783 fn test_box_support() {
784 let b = BoxShape::new(Vec3::new(1.0, 2.0, 3.0));
785 let sp = b.support_point(&Vec3::new(1.0, 1.0, 1.0));
786 assert_eq!(sp, Vec3::new(1.0, 2.0, 3.0));
787 }
788
789 #[test]
790 fn test_box_support_array() {
791 let b = BoxShape::new(Vec3::new(1.0, 2.0, 3.0));
792 let sp = b.support([1.0, 1.0, 1.0]);
793 assert!((sp[0] - 1.0).abs() < 1e-10);
794 assert!((sp[1] - 2.0).abs() < 1e-10);
795 assert!((sp[2] - 3.0).abs() < 1e-10);
796 }
797
798 #[test]
799 fn test_box_volume() {
800 let b = BoxShape::new(Vec3::new(1.0, 2.0, 3.0));
801 assert!((b.volume() - 48.0).abs() < 1e-10);
802 }
803
804 #[test]
805 fn test_box_surface_area() {
806 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
808 assert!((b.surface_area() - 24.0).abs() < 1e-10);
809 }
810
811 #[test]
812 fn test_box_inertia() {
813 let b = BoxShape::new(Vec3::new(0.5, 0.5, 0.5));
815 let it = b.inertia_tensor(1.0);
816 assert!((it[(0, 0)] - 1.0 / 6.0).abs() < 1e-10);
818 }
819
820 #[test]
821 fn test_box_inertia_array() {
822 let b = BoxShape::new(Vec3::new(0.5, 0.5, 0.5));
823 let it = b.inertia_tensor_array(1.0);
824 assert!((it[0][0] - 1.0 / 6.0).abs() < 1e-10);
825 assert!(it[0][1].abs() < 1e-10);
826 }
827
828 #[test]
829 fn test_box_raycast() {
830 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
831 let origin = Vec3::new(-5.0, 0.0, 0.0);
832 let dir = Vec3::new(1.0, 0.0, 0.0);
833 let hit = b.ray_cast(&origin, &dir, 100.0).unwrap();
834 assert!((hit.toi - 4.0).abs() < 1e-10);
835 assert!((hit.point.x + 1.0).abs() < 1e-10);
836 }
837
838 #[test]
839 fn test_box_raycast_array() {
840 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
841 let (t, n) = b
842 .ray_cast_array([-5.0, 0.0, 0.0], [1.0, 0.0, 0.0], 100.0)
843 .unwrap();
844 assert!((t - 4.0).abs() < 1e-10);
845 assert!((n[0] + 1.0).abs() < 1e-10);
846 }
847
848 #[test]
849 fn test_box_vertex_list() {
850 let b = BoxShape::new(Vec3::new(1.0, 2.0, 3.0));
851 let verts = b.vertex_list();
852 assert_eq!(verts.len(), 8);
853 for v in &verts {
855 assert!((v[0].abs() - 1.0).abs() < 1e-10);
856 assert!((v[1].abs() - 2.0).abs() < 1e-10);
857 assert!((v[2].abs() - 3.0).abs() < 1e-10);
858 }
859 }
860
861 #[test]
862 fn test_box_raycast_miss() {
863 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
864 let origin = Vec3::new(5.0, 5.0, 0.0);
866 let dir = Vec3::new(1.0, 0.0, 0.0);
867 assert!(b.ray_cast(&origin, &dir, 100.0).is_none());
868 }
869
870 #[test]
873 fn test_box_face_normals() {
874 let normals = BoxShape::face_normals();
875 assert_eq!(normals.len(), 6);
876 for n in &normals {
878 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
879 assert!((len - 1.0).abs() < 1e-10);
880 }
881 }
882
883 #[test]
884 fn test_box_edge_list() {
885 let edges = BoxShape::edge_list();
886 assert_eq!(edges.len(), 12);
887 for (a, b) in &edges {
889 assert!(*a < 8);
890 assert!(*b < 8);
891 }
892 }
893
894 #[test]
895 fn test_box_face_vertex_indices() {
896 let faces = BoxShape::face_vertex_indices();
897 assert_eq!(faces.len(), 6);
898 for face in &faces {
899 for &idx in face {
900 assert!(idx < 8);
901 }
902 }
903 }
904
905 #[test]
906 fn test_box_face_areas() {
907 let b = BoxShape::new(Vec3::new(1.0, 2.0, 3.0));
908 let areas = b.face_areas();
909 assert!((areas[0] - 24.0).abs() < 1e-10);
911 assert!((areas[1] - 24.0).abs() < 1e-10);
912 assert!((areas[2] - 12.0).abs() < 1e-10);
914 assert!((areas[3] - 12.0).abs() < 1e-10);
915 assert!((areas[4] - 8.0).abs() < 1e-10);
917 assert!((areas[5] - 8.0).abs() < 1e-10);
918 }
919
920 #[test]
921 fn test_box_face_areas_sum_equals_surface_area() {
922 let b = BoxShape::new(Vec3::new(1.5, 2.5, 3.5));
923 let areas = b.face_areas();
924 let sum: f64 = areas.iter().sum();
925 assert!((sum - b.surface_area()).abs() < 1e-10);
926 }
927
928 #[test]
929 fn test_box_closest_point_inside() {
930 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
931 let cp = b.closest_point([0.5, 0.5, 0.5]);
932 assert_eq!(cp, [0.5, 0.5, 0.5]); }
934
935 #[test]
936 fn test_box_closest_point_outside() {
937 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
938 let cp = b.closest_point([3.0, 0.0, 0.0]);
939 assert!((cp[0] - 1.0).abs() < 1e-10);
940 assert!(cp[1].abs() < 1e-10);
941 assert!(cp[2].abs() < 1e-10);
942 }
943
944 #[test]
945 fn test_box_closest_point_corner() {
946 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
947 let cp = b.closest_point([3.0, 3.0, 3.0]);
948 assert!((cp[0] - 1.0).abs() < 1e-10);
949 assert!((cp[1] - 1.0).abs() < 1e-10);
950 assert!((cp[2] - 1.0).abs() < 1e-10);
951 }
952
953 #[test]
954 fn test_box_contains_point() {
955 let b = BoxShape::new(Vec3::new(1.0, 2.0, 3.0));
956 assert!(b.contains_point([0.0, 0.0, 0.0]));
957 assert!(b.contains_point([1.0, 2.0, 3.0])); assert!(!b.contains_point([1.1, 0.0, 0.0]));
959 }
960
961 #[test]
962 fn test_box_signed_distance_inside() {
963 let b = BoxShape::new(Vec3::new(2.0, 2.0, 2.0));
964 let d = b.signed_distance([0.0, 0.0, 0.0]);
965 assert!((d + 2.0).abs() < 1e-10); }
967
968 #[test]
969 fn test_box_signed_distance_outside() {
970 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
971 let d = b.signed_distance([3.0, 0.0, 0.0]);
972 assert!((d - 2.0).abs() < 1e-10);
973 }
974
975 #[test]
976 fn test_box_signed_distance_corner() {
977 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
978 let d = b.signed_distance([2.0, 2.0, 2.0]);
979 assert!((d - 3.0_f64.sqrt()).abs() < 1e-10);
981 }
982
983 #[test]
984 fn test_box_clip_segment_through() {
985 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
986 let result = b.clip_segment([-3.0, 0.0, 0.0], [3.0, 0.0, 0.0]);
987 assert!(result.is_some());
988 let (t_enter, t_exit) = result.unwrap();
989 assert!((t_enter - 1.0 / 3.0).abs() < 1e-10);
993 assert!((t_exit - 2.0 / 3.0).abs() < 1e-10);
994 }
995
996 #[test]
997 fn test_box_clip_segment_miss() {
998 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
999 let result = b.clip_segment([5.0, 5.0, 0.0], [6.0, 5.0, 0.0]);
1000 assert!(result.is_none());
1001 }
1002
1003 #[test]
1004 fn test_box_clip_segment_inside() {
1005 let b = BoxShape::new(Vec3::new(2.0, 2.0, 2.0));
1006 let result = b.clip_segment([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
1007 assert!(result.is_some());
1008 let (t_enter, t_exit) = result.unwrap();
1009 assert!(t_enter.abs() < 1e-10);
1010 assert!((t_exit - 1.0).abs() < 1e-10);
1011 }
1012
1013 #[test]
1014 fn test_box_classify_face() {
1015 let b = BoxShape::new(Vec3::new(1.0, 2.0, 3.0));
1016 assert_eq!(b.classify_face([1.0, 0.0, 0.0]), 0); assert_eq!(b.classify_face([-1.0, 0.0, 0.0]), 1); assert_eq!(b.classify_face([0.0, 2.0, 0.0]), 2); assert_eq!(b.classify_face([0.0, -2.0, 0.0]), 3); assert_eq!(b.classify_face([0.0, 0.0, 3.0]), 4); assert_eq!(b.classify_face([0.0, 0.0, -3.0]), 5); }
1023
1024 #[test]
1025 fn test_box_diagonal_length() {
1026 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
1027 let expected = 2.0 * 3.0_f64.sqrt();
1028 assert!((b.diagonal_length() - expected).abs() < 1e-10);
1029 }
1030
1031 #[test]
1032 fn test_box_edge_lengths() {
1033 let b = BoxShape::new(Vec3::new(1.0, 2.0, 3.0));
1034 let el = b.edge_lengths();
1035 assert!((el[0] - 2.0).abs() < 1e-10);
1036 assert!((el[1] - 4.0).abs() < 1e-10);
1037 assert!((el[2] - 6.0).abs() < 1e-10);
1038 }
1039
1040 #[test]
1041 fn test_box_project_on_axis() {
1042 let b = BoxShape::new(Vec3::new(1.0, 2.0, 3.0));
1043 let (lo, hi) = b.project_on_axis([1.0, 0.0, 0.0]);
1045 assert!((lo + 1.0).abs() < 1e-10);
1046 assert!((hi - 1.0).abs() < 1e-10);
1047 }
1048
1049 #[test]
1050 fn test_box_project_on_diagonal_axis() {
1051 let b = BoxShape::new(Vec3::new(1.0, 1.0, 1.0));
1052 let s = 1.0 / 3.0_f64.sqrt();
1054 let (lo, hi) = b.project_on_axis([s, s, s]);
1055 let expected = 3.0_f64.sqrt();
1057 assert!((hi - expected).abs() < 1e-10);
1058 assert!((lo + expected).abs() < 1e-10);
1059 }
1060
1061 #[test]
1062 fn test_box_volume_explicit_matches() {
1063 let b = BoxShape::new(Vec3::new(1.5, 2.5, 3.5));
1064 assert!((b.volume_explicit() - b.volume()).abs() < 1e-10);
1065 }
1066
1067 #[test]
1070 fn test_obb_inertia_tensor_unit_cube() {
1071 let it = obb_inertia_tensor([0.5, 0.5, 0.5], 1.0);
1074 assert!((it[0][0] - 1.0 / 6.0).abs() < 1e-10);
1075 assert!((it[1][1] - 1.0 / 6.0).abs() < 1e-10);
1076 assert!((it[2][2] - 1.0 / 6.0).abs() < 1e-10);
1077 assert!(it[0][1].abs() < 1e-10);
1079 assert!(it[0][2].abs() < 1e-10);
1080 }
1081
1082 #[test]
1083 fn test_obb_inertia_tensor_asymmetric() {
1084 let it = obb_inertia_tensor([1.0, 2.0, 3.0], 12.0);
1087 assert!((it[0][0] - 52.0).abs() < 1e-10);
1088 assert!((it[1][1] - 40.0).abs() < 1e-10);
1089 assert!((it[2][2] - 20.0).abs() < 1e-10);
1090 }
1091
1092 #[test]
1093 fn test_obb_inertia_tensor_rotated_identity() {
1094 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1096 let it = obb_inertia_tensor_rotated([1.0, 2.0, 3.0], 12.0, rot);
1097 let ref_it = obb_inertia_tensor([1.0, 2.0, 3.0], 12.0);
1098 for i in 0..3 {
1099 for j in 0..3 {
1100 assert!(
1101 (it[i][j] - ref_it[i][j]).abs() < 1e-10,
1102 "mismatch at [{i}][{j}]: {} vs {}",
1103 it[i][j],
1104 ref_it[i][j]
1105 );
1106 }
1107 }
1108 }
1109
1110 #[test]
1111 fn test_obb_closest_point_inside() {
1112 let center = [0.0, 0.0, 0.0];
1113 let half = [1.0, 1.0, 1.0];
1114 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1115 let cp = obb_closest_point([0.5, 0.3, -0.2], center, half, rot);
1116 assert!((cp[0] - 0.5).abs() < 1e-10);
1118 assert!((cp[1] - 0.3).abs() < 1e-10);
1119 assert!((cp[2] + 0.2).abs() < 1e-10);
1120 }
1121
1122 #[test]
1123 fn test_obb_closest_point_outside_x() {
1124 let center = [0.0, 0.0, 0.0];
1125 let half = [1.0, 1.0, 1.0];
1126 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1127 let cp = obb_closest_point([5.0, 0.0, 0.0], center, half, rot);
1128 assert!((cp[0] - 1.0).abs() < 1e-10);
1129 assert!(cp[1].abs() < 1e-10);
1130 assert!(cp[2].abs() < 1e-10);
1131 }
1132
1133 #[test]
1134 fn test_obb_closest_point_rotated() {
1135 let center = [0.0, 0.0, 0.0];
1137 let half = [2.0, 1.0, 1.0];
1138 let rot = [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]];
1140 let cp = obb_closest_point([0.0, 5.0, 0.0], center, half, rot);
1142 assert!((cp[0]).abs() < 1e-10);
1145 assert!((cp[1] - 2.0).abs() < 1e-10);
1146 }
1147
1148 #[test]
1149 fn test_obb_ray_intersection_hit() {
1150 let center = [0.0, 0.0, 0.0];
1151 let half = [1.0, 1.0, 1.0];
1152 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1153 let result =
1154 obb_ray_intersection([-5.0, 0.0, 0.0], [1.0, 0.0, 0.0], center, half, rot, 100.0);
1155 assert!(result.is_some());
1156 let t = result.unwrap();
1157 assert!((t - 4.0).abs() < 1e-10);
1158 }
1159
1160 #[test]
1161 fn test_obb_ray_intersection_miss() {
1162 let center = [0.0, 0.0, 0.0];
1163 let half = [1.0, 1.0, 1.0];
1164 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1165 let result =
1166 obb_ray_intersection([0.0, 5.0, 0.0], [1.0, 0.0, 0.0], center, half, rot, 100.0);
1167 assert!(result.is_none());
1168 }
1169
1170 #[test]
1171 fn test_obb_ray_intersection_rotated() {
1172 let center = [3.0, 0.0, 0.0];
1175 let half = [1.0, 1.0, 1.0];
1176 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1177 let result =
1178 obb_ray_intersection([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], center, half, rot, 100.0);
1179 assert!(result.is_some());
1180 let t = result.unwrap();
1181 assert!((t - 2.0).abs() < 1e-10);
1182 }
1183
1184 #[test]
1185 fn test_obb_plane_distance_above() {
1186 let center = [0.0, 0.0, 0.0];
1189 let half = [1.0, 1.0, 1.0];
1190 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1191 let plane = [0.0, 1.0, 0.0, -3.0]; let (min_d, max_d) = obb_plane_distance(center, half, rot, plane);
1193 assert!((min_d + 4.0).abs() < 1e-10, "min_d={min_d}");
1197 assert!((max_d + 2.0).abs() < 1e-10, "max_d={max_d}");
1198 }
1199
1200 #[test]
1201 fn test_obb_plane_distance_straddles() {
1202 let center = [0.0, 0.0, 0.0];
1204 let half = [2.0, 2.0, 2.0];
1205 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1206 let plane = [0.0, 1.0, 0.0, 0.0]; let (min_d, max_d) = obb_plane_distance(center, half, rot, plane);
1208 assert!(min_d < 0.0);
1209 assert!(max_d > 0.0);
1210 }
1211
1212 #[test]
1213 fn test_obb_vertices_count() {
1214 let center = [1.0, 2.0, 3.0];
1215 let half = [1.0, 1.0, 1.0];
1216 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1217 let verts = obb_vertices(center, half, rot);
1218 assert_eq!(verts.len(), 8);
1219 }
1220
1221 #[test]
1222 fn test_obb_vertices_aabb_bounds() {
1223 let center = [0.0, 0.0, 0.0];
1225 let half = [1.0, 2.0, 3.0];
1226 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1227 let verts = obb_vertices(center, half, rot);
1228 for v in &verts {
1229 assert!(v[0].abs() <= 1.0 + 1e-10);
1230 assert!(v[1].abs() <= 2.0 + 1e-10);
1231 assert!(v[2].abs() <= 3.0 + 1e-10);
1232 }
1233 }
1234
1235 #[test]
1236 fn test_obb_vertices_translated() {
1237 let center = [5.0, 0.0, 0.0];
1238 let half = [1.0, 1.0, 1.0];
1239 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1240 let verts = obb_vertices(center, half, rot);
1241 for v in &verts {
1243 assert!(
1244 (v[0] - 4.0).abs() < 1e-10 || (v[0] - 6.0).abs() < 1e-10,
1245 "unexpected x={}",
1246 v[0]
1247 );
1248 }
1249 }
1250
1251 #[test]
1252 fn test_obb_edges_count() {
1253 let center = [0.0, 0.0, 0.0];
1254 let half = [1.0, 1.0, 1.0];
1255 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1256 let edges = obb_edges(center, half, rot);
1257 assert_eq!(edges.len(), 12);
1258 }
1259
1260 #[test]
1261 fn test_obb_edges_lengths() {
1262 let center = [0.0, 0.0, 0.0];
1263 let half = [1.0, 2.0, 3.0];
1264 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1265 let edges = obb_edges(center, half, rot);
1266 for (a, b) in &edges {
1267 let dx = b[0] - a[0];
1268 let dy = b[1] - a[1];
1269 let dz = b[2] - a[2];
1270 let len = (dx * dx + dy * dy + dz * dz).sqrt();
1271 let is_valid =
1273 (len - 2.0).abs() < 1e-10 || (len - 4.0).abs() < 1e-10 || (len - 6.0).abs() < 1e-10;
1274 assert!(is_valid, "unexpected edge length {len}");
1275 }
1276 }
1277
1278 #[test]
1279 fn test_obb_face_centers_count() {
1280 let center = [0.0, 0.0, 0.0];
1281 let half = [1.0, 1.0, 1.0];
1282 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1283 let fc = obb_face_centers(center, half, rot);
1284 assert_eq!(fc.len(), 6);
1285 }
1286
1287 #[test]
1288 fn test_obb_face_centers_distance() {
1289 let center = [0.0, 0.0, 0.0];
1291 let half = [1.0, 2.0, 3.0];
1292 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1293 let fc = obb_face_centers(center, half, rot);
1294 assert!((fc[0][0].abs() - 1.0).abs() < 1e-10);
1296 assert!((fc[1][0].abs() - 1.0).abs() < 1e-10);
1297 assert!((fc[2][1].abs() - 2.0).abs() < 1e-10);
1299 assert!((fc[3][1].abs() - 2.0).abs() < 1e-10);
1300 assert!((fc[4][2].abs() - 3.0).abs() < 1e-10);
1302 assert!((fc[5][2].abs() - 3.0).abs() < 1e-10);
1303 }
1304
1305 #[test]
1306 fn test_obb_support_function_identity() {
1307 let center = [0.0, 0.0, 0.0];
1308 let half = [1.0, 2.0, 3.0];
1309 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1310 let sp = obb_support_fn(center, half, rot, [1.0, 0.0, 0.0]);
1312 assert!((sp[0] - 1.0).abs() < 1e-10);
1313 }
1314
1315 #[test]
1316 fn test_obb_support_function_diagonal() {
1317 let center = [0.0, 0.0, 0.0];
1318 let half = [1.0, 1.0, 1.0];
1319 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1320 let sp = obb_support_fn(center, half, rot, [1.0, 1.0, 1.0]);
1321 assert!((sp[0] - 1.0).abs() < 1e-10);
1323 assert!((sp[1] - 1.0).abs() < 1e-10);
1324 assert!((sp[2] - 1.0).abs() < 1e-10);
1325 }
1326
1327 #[test]
1328 fn test_obb_support_function_translated() {
1329 let center = [3.0, 0.0, 0.0];
1330 let half = [1.0, 1.0, 1.0];
1331 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1332 let sp = obb_support_fn(center, half, rot, [1.0, 0.0, 0.0]);
1333 assert!((sp[0] - 4.0).abs() < 1e-10);
1335 }
1336
1337 #[test]
1338 fn test_obb_projection_extent_identity() {
1339 let half = [1.0, 2.0, 3.0];
1341 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1342 let axis = [1.0, 0.0, 0.0];
1343 let ext = obb_projection_extent(half, rot, axis);
1344 assert!((ext - 1.0).abs() < 1e-10);
1345 }
1346
1347 #[test]
1348 fn test_obb_projection_extent_diagonal() {
1349 let half = [1.0, 1.0, 1.0];
1350 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1351 let s = 1.0 / 3.0_f64.sqrt();
1352 let ext = obb_projection_extent(half, rot, [s, s, s]);
1353 assert!((ext - 3.0_f64.sqrt()).abs() < 1e-10);
1354 }
1355
1356 #[test]
1357 fn test_obb_aabb_bounds_identity() {
1358 let center = [1.0, 2.0, 3.0];
1359 let half = [1.0, 2.0, 3.0];
1360 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1361 let (lo, hi) = obb_aabb_bounds(center, half, rot);
1362 assert!((lo[0] - 0.0).abs() < 1e-10);
1363 assert!((hi[0] - 2.0).abs() < 1e-10);
1364 assert!((lo[1] - 0.0).abs() < 1e-10);
1365 assert!((hi[1] - 4.0).abs() < 1e-10);
1366 }
1367
1368 #[test]
1369 fn test_obb_aabb_bounds_contains_all_vertices() {
1370 let center = [0.5, -1.0, 2.0];
1371 let half = [1.0, 0.5, 1.5];
1372 let rot = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1373 let (lo, hi) = obb_aabb_bounds(center, half, rot);
1374 let verts = obb_vertices(center, half, rot);
1375 for v in &verts {
1376 assert!(v[0] >= lo[0] - 1e-10 && v[0] <= hi[0] + 1e-10);
1377 assert!(v[1] >= lo[1] - 1e-10 && v[1] <= hi[1] + 1e-10);
1378 assert!(v[2] >= lo[2] - 1e-10 && v[2] <= hi[2] + 1e-10);
1379 }
1380 }
1381}