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