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