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