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