euclid/
box3d.rs

1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10use super::UnknownUnit;
11use crate::approxord::{max, min};
12use crate::num::*;
13use crate::point::{point3, Point3D};
14use crate::scale::Scale;
15use crate::size::Size3D;
16use crate::vector::Vector3D;
17
18use num_traits::NumCast;
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21
22use core::borrow::Borrow;
23use core::cmp::PartialOrd;
24use core::fmt;
25use core::hash::{Hash, Hasher};
26use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Sub};
27
28/// An axis aligned 3D box represented by its minimum and maximum coordinates.
29#[repr(C)]
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31#[cfg_attr(
32    feature = "serde",
33    serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
34)]
35pub struct Box3D<T, U> {
36    pub min: Point3D<T, U>,
37    pub max: Point3D<T, U>,
38}
39
40impl<T: Hash, U> Hash for Box3D<T, U> {
41    fn hash<H: Hasher>(&self, h: &mut H) {
42        self.min.hash(h);
43        self.max.hash(h);
44    }
45}
46
47impl<T: Copy, U> Copy for Box3D<T, U> {}
48
49impl<T: Clone, U> Clone for Box3D<T, U> {
50    fn clone(&self) -> Self {
51        Self::new(self.min.clone(), self.max.clone())
52    }
53}
54
55impl<T: PartialEq, U> PartialEq for Box3D<T, U> {
56    fn eq(&self, other: &Self) -> bool {
57        self.min.eq(&other.min) && self.max.eq(&other.max)
58    }
59}
60
61impl<T: Eq, U> Eq for Box3D<T, U> {}
62
63impl<T: fmt::Debug, U> fmt::Debug for Box3D<T, U> {
64    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65        f.debug_tuple("Box3D")
66            .field(&self.min)
67            .field(&self.max)
68            .finish()
69    }
70}
71
72impl<T, U> Box3D<T, U> {
73    /// Constructor.
74    #[inline]
75    pub const fn new(min: Point3D<T, U>, max: Point3D<T, U>) -> Self {
76        Box3D { min, max }
77    }
78}
79
80impl<T, U> Box3D<T, U>
81where
82    T: PartialOrd,
83{
84    /// Returns true if the box has a negative volume.
85    ///
86    /// The common interpretation for a negative box is to consider it empty. It can be obtained
87    /// by calculating the intersection of two boxes that do not intersect.
88    #[inline]
89    pub fn is_negative(&self) -> bool {
90        self.max.x < self.min.x || self.max.y < self.min.y || self.max.z < self.min.z
91    }
92
93    /// Returns true if the size is zero, negative or NaN.
94    #[inline]
95    pub fn is_empty(&self) -> bool {
96        !(self.max.x > self.min.x && self.max.y > self.min.y && self.max.z > self.min.z)
97    }
98
99    #[inline]
100    pub fn intersects(&self, other: &Self) -> bool {
101        self.min.x < other.max.x
102            && self.max.x > other.min.x
103            && self.min.y < other.max.y
104            && self.max.y > other.min.y
105            && self.min.z < other.max.z
106            && self.max.z > other.min.z
107    }
108
109    /// Returns `true` if this box3d contains the point. Points are considered
110    /// in the box3d if they are on the front, left or top faces, but outside if they
111    /// are on the back, right or bottom faces.
112    #[inline]
113    pub fn contains(&self, other: Point3D<T, U>) -> bool {
114        self.min.x <= other.x
115            && other.x < self.max.x
116            && self.min.y <= other.y
117            && other.y < self.max.y
118            && self.min.z <= other.z
119            && other.z < self.max.z
120    }
121
122    /// Returns `true` if this box3d contains the interior of the other box3d. Always
123    /// returns `true` if other is empty, and always returns `false` if other is
124    /// nonempty but this box3d is empty.
125    #[inline]
126    pub fn contains_box(&self, other: &Self) -> bool {
127        other.is_empty()
128            || (self.min.x <= other.min.x
129                && other.max.x <= self.max.x
130                && self.min.y <= other.min.y
131                && other.max.y <= self.max.y
132                && self.min.z <= other.min.z
133                && other.max.z <= self.max.z)
134    }
135}
136
137impl<T, U> Box3D<T, U>
138where
139    T: Copy + PartialOrd,
140{
141    #[inline]
142    pub fn to_non_empty(&self) -> Option<Self> {
143        if self.is_empty() {
144            return None;
145        }
146
147        Some(*self)
148    }
149
150    #[inline]
151    pub fn intersection(&self, other: &Self) -> Option<Self> {
152        let b = self.intersection_unchecked(other);
153
154        if b.is_empty() {
155            return None;
156        }
157
158        Some(b)
159    }
160
161    pub fn intersection_unchecked(&self, other: &Self) -> Self {
162        let intersection_min = Point3D::new(
163            max(self.min.x, other.min.x),
164            max(self.min.y, other.min.y),
165            max(self.min.z, other.min.z),
166        );
167
168        let intersection_max = Point3D::new(
169            min(self.max.x, other.max.x),
170            min(self.max.y, other.max.y),
171            min(self.max.z, other.max.z),
172        );
173
174        Box3D::new(intersection_min, intersection_max)
175    }
176
177    /// Returns the smallest box containing both of the provided boxes.
178    #[inline]
179    pub fn union(&self, other: &Self) -> Self {
180        Box3D::new(
181            Point3D::new(
182                min(self.min.x, other.min.x),
183                min(self.min.y, other.min.y),
184                min(self.min.z, other.min.z),
185            ),
186            Point3D::new(
187                max(self.max.x, other.max.x),
188                max(self.max.y, other.max.y),
189                max(self.max.z, other.max.z),
190            ),
191        )
192    }
193}
194
195impl<T, U> Box3D<T, U>
196where
197    T: Copy + Add<T, Output = T>,
198{
199    /// Returns the same box3d, translated by a vector.
200    #[inline]
201    #[must_use]
202    pub fn translate(&self, by: Vector3D<T, U>) -> Self {
203        Box3D {
204            min: self.min + by,
205            max: self.max + by,
206        }
207    }
208}
209
210impl<T, U> Box3D<T, U>
211where
212    T: Copy + Sub<T, Output = T>,
213{
214    #[inline]
215    pub fn size(&self) -> Size3D<T, U> {
216        Size3D::new(
217            self.max.x - self.min.x,
218            self.max.y - self.min.y,
219            self.max.z - self.min.z,
220        )
221    }
222
223    #[inline]
224    pub fn width(&self) -> T {
225        self.max.x - self.min.x
226    }
227
228    #[inline]
229    pub fn height(&self) -> T {
230        self.max.y - self.min.y
231    }
232
233    #[inline]
234    pub fn depth(&self) -> T {
235        self.max.z - self.min.z
236    }
237}
238
239impl<T, U> Box3D<T, U>
240where
241    T: Copy + Add<T, Output = T> + Sub<T, Output = T>,
242{
243    /// Inflates the box by the specified sizes on each side respectively.
244    #[inline]
245    #[must_use]
246    pub fn inflate(&self, width: T, height: T, depth: T) -> Self {
247        Box3D::new(
248            Point3D::new(self.min.x - width, self.min.y - height, self.min.z - depth),
249            Point3D::new(self.max.x + width, self.max.y + height, self.max.z + depth),
250        )
251    }
252}
253
254impl<T, U> Box3D<T, U>
255where
256    T: Copy + Zero + PartialOrd,
257{
258    /// Creates a Box3D of the given size, at offset zero.
259    #[inline]
260    pub fn from_size(size: Size3D<T, U>) -> Self {
261        let zero = Point3D::zero();
262        let point = size.to_vector().to_point();
263        Box3D::from_points(&[zero, point])
264    }
265
266    /// Returns the smallest box containing all of the provided points.
267    pub fn from_points<I>(points: I) -> Self
268    where
269        I: IntoIterator,
270        I::Item: Borrow<Point3D<T, U>>,
271    {
272        let mut points = points.into_iter();
273
274        let (mut min_x, mut min_y, mut min_z) = match points.next() {
275            Some(first) => first.borrow().to_tuple(),
276            None => return Box3D::zero(),
277        };
278        let (mut max_x, mut max_y, mut max_z) = (min_x, min_y, min_z);
279
280        for point in points {
281            let p = point.borrow();
282            if p.x < min_x {
283                min_x = p.x
284            }
285            if p.x > max_x {
286                max_x = p.x
287            }
288            if p.y < min_y {
289                min_y = p.y
290            }
291            if p.y > max_y {
292                max_y = p.y
293            }
294            if p.z < min_z {
295                min_z = p.z
296            }
297            if p.z > max_z {
298                max_z = p.z
299            }
300        }
301
302        Box3D {
303            min: point3(min_x, min_y, min_z),
304            max: point3(max_x, max_y, max_z),
305        }
306    }
307}
308
309impl<T, U> Box3D<T, U>
310where
311    T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
312{
313    /// Linearly interpolate between this box3d and another box3d.
314    #[inline]
315    pub fn lerp(&self, other: Self, t: T) -> Self {
316        Self::new(self.min.lerp(other.min, t), self.max.lerp(other.max, t))
317    }
318}
319
320impl<T, U> Box3D<T, U>
321where
322    T: Copy + One + Add<Output = T> + Div<Output = T>,
323{
324    pub fn center(&self) -> Point3D<T, U> {
325        let two = T::one() + T::one();
326        (self.min + self.max.to_vector()) / two
327    }
328}
329
330impl<T, U> Box3D<T, U>
331where
332    T: Copy + Mul<T, Output = T> + Sub<T, Output = T>,
333{
334    #[inline]
335    pub fn volume(&self) -> T {
336        let size = self.size();
337        size.width * size.height * size.depth
338    }
339
340    #[inline]
341    pub fn xy_area(&self) -> T {
342        let size = self.size();
343        size.width * size.height
344    }
345
346    #[inline]
347    pub fn yz_area(&self) -> T {
348        let size = self.size();
349        size.depth * size.height
350    }
351
352    #[inline]
353    pub fn xz_area(&self) -> T {
354        let size = self.size();
355        size.depth * size.width
356    }
357}
358
359impl<T, U> Box3D<T, U>
360where
361    T: Zero,
362{
363    /// Constructor, setting all sides to zero.
364    pub fn zero() -> Self {
365        Box3D::new(Point3D::zero(), Point3D::zero())
366    }
367}
368
369impl<T: Copy + Mul, U> Mul<T> for Box3D<T, U> {
370    type Output = Box3D<T::Output, U>;
371
372    #[inline]
373    fn mul(self, scale: T) -> Self::Output {
374        Box3D::new(self.min * scale, self.max * scale)
375    }
376}
377
378impl<T: Copy + MulAssign, U> MulAssign<T> for Box3D<T, U> {
379    #[inline]
380    fn mul_assign(&mut self, scale: T) {
381        self.min *= scale;
382        self.max *= scale;
383    }
384}
385
386impl<T: Copy + Div, U> Div<T> for Box3D<T, U> {
387    type Output = Box3D<T::Output, U>;
388
389    #[inline]
390    fn div(self, scale: T) -> Self::Output {
391        Box3D::new(self.min / scale.clone(), self.max / scale)
392    }
393}
394
395impl<T: Copy + DivAssign, U> DivAssign<T> for Box3D<T, U> {
396    #[inline]
397    fn div_assign(&mut self, scale: T) {
398        self.min /= scale;
399        self.max /= scale;
400    }
401}
402
403impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Box3D<T, U1> {
404    type Output = Box3D<T::Output, U2>;
405
406    #[inline]
407    fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
408        Box3D::new(self.min * scale.clone(), self.max * scale)
409    }
410}
411
412impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Box3D<T, U> {
413    #[inline]
414    fn mul_assign(&mut self, scale: Scale<T, U, U>) {
415        self.min *= scale.clone();
416        self.max *= scale;
417    }
418}
419
420impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Box3D<T, U2> {
421    type Output = Box3D<T::Output, U1>;
422
423    #[inline]
424    fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
425        Box3D::new(self.min / scale.clone(), self.max / scale)
426    }
427}
428
429impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Box3D<T, U> {
430    #[inline]
431    fn div_assign(&mut self, scale: Scale<T, U, U>) {
432        self.min /= scale.clone();
433        self.max /= scale;
434    }
435}
436
437impl<T, U> Box3D<T, U>
438where
439    T: Copy,
440{
441    /// Drop the units, preserving only the numeric value.
442    #[inline]
443    pub fn to_untyped(&self) -> Box3D<T, UnknownUnit> {
444        Box3D {
445            min: self.min.to_untyped(),
446            max: self.max.to_untyped(),
447        }
448    }
449
450    /// Tag a unitless value with units.
451    #[inline]
452    pub fn from_untyped(c: &Box3D<T, UnknownUnit>) -> Box3D<T, U> {
453        Box3D {
454            min: Point3D::from_untyped(c.min),
455            max: Point3D::from_untyped(c.max),
456        }
457    }
458
459    /// Cast the unit
460    #[inline]
461    pub fn cast_unit<V>(&self) -> Box3D<T, V> {
462        Box3D::new(self.min.cast_unit(), self.max.cast_unit())
463    }
464
465    #[inline]
466    pub fn scale<S: Copy>(&self, x: S, y: S, z: S) -> Self
467    where
468        T: Mul<S, Output = T>,
469    {
470        Box3D::new(
471            Point3D::new(self.min.x * x, self.min.y * y, self.min.z * z),
472            Point3D::new(self.max.x * x, self.max.y * y, self.max.z * z),
473        )
474    }
475}
476
477impl<T: NumCast + Copy, U> Box3D<T, U> {
478    /// Cast from one numeric representation to another, preserving the units.
479    ///
480    /// When casting from floating point to integer coordinates, the decimals are truncated
481    /// as one would expect from a simple cast, but this behavior does not always make sense
482    /// geometrically. Consider using round(), round_in or round_out() before casting.
483    #[inline]
484    pub fn cast<NewT: NumCast>(&self) -> Box3D<NewT, U> {
485        Box3D::new(self.min.cast(), self.max.cast())
486    }
487
488    /// Fallible cast from one numeric representation to another, preserving the units.
489    ///
490    /// When casting from floating point to integer coordinates, the decimals are truncated
491    /// as one would expect from a simple cast, but this behavior does not always make sense
492    /// geometrically. Consider using round(), round_in or round_out() before casting.
493    pub fn try_cast<NewT: NumCast>(&self) -> Option<Box3D<NewT, U>> {
494        match (self.min.try_cast(), self.max.try_cast()) {
495            (Some(a), Some(b)) => Some(Box3D::new(a, b)),
496            _ => None,
497        }
498    }
499
500    // Convenience functions for common casts
501
502    /// Cast into an `f32` box3d.
503    #[inline]
504    pub fn to_f32(&self) -> Box3D<f32, U> {
505        self.cast()
506    }
507
508    /// Cast into an `f64` box3d.
509    #[inline]
510    pub fn to_f64(&self) -> Box3D<f64, U> {
511        self.cast()
512    }
513
514    /// Cast into an `usize` box3d, truncating decimals if any.
515    ///
516    /// When casting from floating point cuboids, it is worth considering whether
517    /// to `round()`, `round_in()` or `round_out()` before the cast in order to
518    /// obtain the desired conversion behavior.
519    #[inline]
520    pub fn to_usize(&self) -> Box3D<usize, U> {
521        self.cast()
522    }
523
524    /// Cast into an `u32` box3d, truncating decimals if any.
525    ///
526    /// When casting from floating point cuboids, it is worth considering whether
527    /// to `round()`, `round_in()` or `round_out()` before the cast in order to
528    /// obtain the desired conversion behavior.
529    #[inline]
530    pub fn to_u32(&self) -> Box3D<u32, U> {
531        self.cast()
532    }
533
534    /// Cast into an `i32` box3d, truncating decimals if any.
535    ///
536    /// When casting from floating point cuboids, it is worth considering whether
537    /// to `round()`, `round_in()` or `round_out()` before the cast in order to
538    /// obtain the desired conversion behavior.
539    #[inline]
540    pub fn to_i32(&self) -> Box3D<i32, U> {
541        self.cast()
542    }
543
544    /// Cast into an `i64` box3d, truncating decimals if any.
545    ///
546    /// When casting from floating point cuboids, it is worth considering whether
547    /// to `round()`, `round_in()` or `round_out()` before the cast in order to
548    /// obtain the desired conversion behavior.
549    #[inline]
550    pub fn to_i64(&self) -> Box3D<i64, U> {
551        self.cast()
552    }
553}
554
555impl<T, U> Box3D<T, U>
556where
557    T: Round,
558{
559    /// Return a box3d with edges rounded to integer coordinates, such that
560    /// the returned box3d has the same set of pixel centers as the original
561    /// one.
562    /// Values equal to 0.5 round up.
563    /// Suitable for most places where integral device coordinates
564    /// are needed, but note that any translation should be applied first to
565    /// avoid pixel rounding errors.
566    /// Note that this is *not* rounding to nearest integer if the values are negative.
567    /// They are always rounding as floor(n + 0.5).
568    #[must_use]
569    pub fn round(&self) -> Self {
570        Box3D::new(self.min.round(), self.max.round())
571    }
572}
573
574impl<T, U> Box3D<T, U>
575where
576    T: Floor + Ceil,
577{
578    /// Return a box3d with faces/edges rounded to integer coordinates, such that
579    /// the original box3d contains the resulting box3d.
580    #[must_use]
581    pub fn round_in(&self) -> Self {
582        Box3D {
583            min: self.min.ceil(),
584            max: self.max.floor(),
585        }
586    }
587
588    /// Return a box3d with faces/edges rounded to integer coordinates, such that
589    /// the original box3d is contained in the resulting box3d.
590    #[must_use]
591    pub fn round_out(&self) -> Self {
592        Box3D {
593            min: self.min.floor(),
594            max: self.max.ceil(),
595        }
596    }
597}
598
599impl<T, U> From<Size3D<T, U>> for Box3D<T, U>
600where
601    T: Copy + Zero + PartialOrd,
602{
603    fn from(b: Size3D<T, U>) -> Self {
604        Self::from_size(b)
605    }
606}
607
608/// Shorthand for `Box3D::new(Point3D::new(x1, y1, z1), Point3D::new(x2, y2, z2))`.
609pub fn box3d<T: Copy, U>(
610    min_x: T,
611    min_y: T,
612    min_z: T,
613    max_x: T,
614    max_y: T,
615    max_z: T,
616) -> Box3D<T, U> {
617    Box3D::new(
618        Point3D::new(min_x, min_y, min_z),
619        Point3D::new(max_x, max_y, max_z),
620    )
621}
622
623#[cfg(test)]
624mod tests {
625    use crate::default::{Box3D, Point3D};
626    use crate::{point3, size3, vec3};
627
628    #[test]
629    fn test_new() {
630        let b = Box3D::new(point3(-1.0, -1.0, -1.0), point3(1.0, 1.0, 1.0));
631        assert!(b.min.x == -1.0);
632        assert!(b.min.y == -1.0);
633        assert!(b.min.z == -1.0);
634        assert!(b.max.x == 1.0);
635        assert!(b.max.y == 1.0);
636        assert!(b.max.z == 1.0);
637    }
638
639    #[test]
640    fn test_size() {
641        let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0));
642        assert!(b.size().width == 20.0);
643        assert!(b.size().height == 20.0);
644        assert!(b.size().depth == 20.0);
645    }
646
647    #[test]
648    fn test_width_height_depth() {
649        let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0));
650        assert!(b.width() == 20.0);
651        assert!(b.height() == 20.0);
652        assert!(b.depth() == 20.0);
653    }
654
655    #[test]
656    fn test_center() {
657        let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0));
658        assert!(b.center() == Point3D::zero());
659    }
660
661    #[test]
662    fn test_volume() {
663        let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0));
664        assert!(b.volume() == 8000.0);
665    }
666
667    #[test]
668    fn test_area() {
669        let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0));
670        assert!(b.xy_area() == 400.0);
671        assert!(b.yz_area() == 400.0);
672        assert!(b.xz_area() == 400.0);
673    }
674
675    #[test]
676    fn test_from_points() {
677        let b = Box3D::from_points(&[point3(50.0, 160.0, 12.5), point3(100.0, 25.0, 200.0)]);
678        assert!(b.min == point3(50.0, 25.0, 12.5));
679        assert!(b.max == point3(100.0, 160.0, 200.0));
680    }
681
682    #[test]
683    fn test_min_max() {
684        let b = Box3D::from_points(&[point3(50.0, 25.0, 12.5), point3(100.0, 160.0, 200.0)]);
685        assert!(b.min.x == 50.0);
686        assert!(b.min.y == 25.0);
687        assert!(b.min.z == 12.5);
688        assert!(b.max.x == 100.0);
689        assert!(b.max.y == 160.0);
690        assert!(b.max.z == 200.0);
691    }
692
693    #[test]
694    fn test_round_in() {
695        let b =
696            Box3D::from_points(&[point3(-25.5, -40.4, -70.9), point3(60.3, 36.5, 89.8)]).round_in();
697        assert!(b.min.x == -25.0);
698        assert!(b.min.y == -40.0);
699        assert!(b.min.z == -70.0);
700        assert!(b.max.x == 60.0);
701        assert!(b.max.y == 36.0);
702        assert!(b.max.z == 89.0);
703    }
704
705    #[test]
706    fn test_round_out() {
707        let b = Box3D::from_points(&[point3(-25.5, -40.4, -70.9), point3(60.3, 36.5, 89.8)])
708            .round_out();
709        assert!(b.min.x == -26.0);
710        assert!(b.min.y == -41.0);
711        assert!(b.min.z == -71.0);
712        assert!(b.max.x == 61.0);
713        assert!(b.max.y == 37.0);
714        assert!(b.max.z == 90.0);
715    }
716
717    #[test]
718    fn test_round() {
719        let b =
720            Box3D::from_points(&[point3(-25.5, -40.4, -70.9), point3(60.3, 36.5, 89.8)]).round();
721        assert!(b.min.x == -25.0);
722        assert!(b.min.y == -40.0);
723        assert!(b.min.z == -71.0);
724        assert!(b.max.x == 60.0);
725        assert!(b.max.y == 37.0);
726        assert!(b.max.z == 90.0);
727    }
728
729    #[test]
730    fn test_from_size() {
731        let b = Box3D::from_size(size3(30.0, 40.0, 50.0));
732        assert!(b.min == Point3D::zero());
733        assert!(b.size().width == 30.0);
734        assert!(b.size().height == 40.0);
735        assert!(b.size().depth == 50.0);
736    }
737
738    #[test]
739    fn test_translate() {
740        let size = size3(15.0, 15.0, 200.0);
741        let mut center = (size / 2.0).to_vector().to_point();
742        let b = Box3D::from_size(size);
743        assert!(b.center() == center);
744        let translation = vec3(10.0, 2.5, 9.5);
745        let b = b.translate(translation);
746        center += translation;
747        assert!(b.center() == center);
748        assert!(b.max.x == 25.0);
749        assert!(b.max.y == 17.5);
750        assert!(b.max.z == 209.5);
751        assert!(b.min.x == 10.0);
752        assert!(b.min.y == 2.5);
753        assert!(b.min.z == 9.5);
754    }
755
756    #[test]
757    fn test_union() {
758        let b1 = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(0.0, 20.0, 20.0)]);
759        let b2 = Box3D::from_points(&[point3(0.0, 20.0, 20.0), point3(20.0, -20.0, -20.0)]);
760        let b = b1.union(&b2);
761        assert!(b.max.x == 20.0);
762        assert!(b.max.y == 20.0);
763        assert!(b.max.z == 20.0);
764        assert!(b.min.x == -20.0);
765        assert!(b.min.y == -20.0);
766        assert!(b.min.z == -20.0);
767        assert!(b.volume() == (40.0 * 40.0 * 40.0));
768    }
769
770    #[test]
771    fn test_intersects() {
772        let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(10.0, 20.0, 20.0)]);
773        let b2 = Box3D::from_points(&[point3(-10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]);
774        assert!(b1.intersects(&b2));
775    }
776
777    #[test]
778    fn test_intersection_unchecked() {
779        let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(10.0, 20.0, 20.0)]);
780        let b2 = Box3D::from_points(&[point3(-10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]);
781        let b = b1.intersection_unchecked(&b2);
782        assert!(b.max.x == 10.0);
783        assert!(b.max.y == 20.0);
784        assert!(b.max.z == 20.0);
785        assert!(b.min.x == -10.0);
786        assert!(b.min.y == -20.0);
787        assert!(b.min.z == -20.0);
788        assert!(b.volume() == (20.0 * 40.0 * 40.0));
789    }
790
791    #[test]
792    fn test_intersection() {
793        let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(10.0, 20.0, 20.0)]);
794        let b2 = Box3D::from_points(&[point3(-10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]);
795        assert!(b1.intersection(&b2).is_some());
796
797        let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(-10.0, 20.0, 20.0)]);
798        let b2 = Box3D::from_points(&[point3(10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]);
799        assert!(b1.intersection(&b2).is_none());
800    }
801
802    #[test]
803    fn test_scale() {
804        let b = Box3D::from_points(&[point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0)]);
805        let b = b.scale(0.5, 0.5, 0.5);
806        assert!(b.max.x == 5.0);
807        assert!(b.max.y == 5.0);
808        assert!(b.max.z == 5.0);
809        assert!(b.min.x == -5.0);
810        assert!(b.min.y == -5.0);
811        assert!(b.min.z == -5.0);
812    }
813
814    #[test]
815    fn test_zero() {
816        let b = Box3D::<f64>::zero();
817        assert!(b.max.x == 0.0);
818        assert!(b.max.y == 0.0);
819        assert!(b.max.z == 0.0);
820        assert!(b.min.x == 0.0);
821        assert!(b.min.y == 0.0);
822        assert!(b.min.z == 0.0);
823    }
824
825    #[test]
826    fn test_lerp() {
827        let b1 = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(-10.0, -10.0, -10.0)]);
828        let b2 = Box3D::from_points(&[point3(10.0, 10.0, 10.0), point3(20.0, 20.0, 20.0)]);
829        let b = b1.lerp(b2, 0.5);
830        assert!(b.center() == Point3D::zero());
831        assert!(b.size().width == 10.0);
832        assert!(b.size().height == 10.0);
833        assert!(b.size().depth == 10.0);
834    }
835
836    #[test]
837    fn test_contains() {
838        let b = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(20.0, 20.0, 20.0)]);
839        assert!(b.contains(point3(-15.3, 10.5, 18.4)));
840    }
841
842    #[test]
843    fn test_contains_box() {
844        let b1 = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(20.0, 20.0, 20.0)]);
845        let b2 = Box3D::from_points(&[point3(-14.3, -16.5, -19.3), point3(6.7, 17.6, 2.5)]);
846        assert!(b1.contains_box(&b2));
847    }
848
849    #[test]
850    fn test_inflate() {
851        let b = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(20.0, 20.0, 20.0)]);
852        let b = b.inflate(10.0, 5.0, 2.0);
853        assert!(b.size().width == 60.0);
854        assert!(b.size().height == 50.0);
855        assert!(b.size().depth == 44.0);
856        assert!(b.center() == Point3D::zero());
857    }
858
859    #[test]
860    fn test_is_empty() {
861        for i in 0..3 {
862            let mut coords_neg = [-20.0, -20.0, -20.0];
863            let mut coords_pos = [20.0, 20.0, 20.0];
864            coords_neg[i] = 0.0;
865            coords_pos[i] = 0.0;
866            let b = Box3D::from_points(&[Point3D::from(coords_neg), Point3D::from(coords_pos)]);
867            assert!(b.is_empty());
868        }
869    }
870
871    #[test]
872    fn test_nan_empty_or_negative() {
873        use std::f32::NAN;
874        assert!(Box3D { min: point3(NAN, 2.0, 1.0), max: point3(1.0, 3.0, 5.0) }.is_empty());
875        assert!(Box3D { min: point3(0.0, NAN, 1.0), max: point3(1.0, 2.0, 5.0) }.is_empty());
876        assert!(Box3D { min: point3(1.0, -2.0, NAN), max: point3(3.0, 2.0, 5.0) }.is_empty());
877        assert!(Box3D { min: point3(1.0, -2.0, 1.0), max: point3(NAN, 2.0, 5.0) }.is_empty());
878        assert!(Box3D { min: point3(1.0, -2.0, 1.0), max: point3(0.0, NAN, 5.0) }.is_empty());
879        assert!(Box3D { min: point3(1.0, -2.0, 1.0), max: point3(0.0, 1.0, NAN) }.is_empty());
880    }
881}