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