New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce the (Typed)Box2D type. #219
Closed
+232
−30
Closed
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.
| @@ -79,8 +79,10 @@ pub use vector::{ | ||
| Vector2D, TypedVector2D, vec2, | ||
| Vector3D, TypedVector3D, vec3, | ||
| }; | ||
|
|
||
| pub use rect::{Rect, TypedRect, rect}; | ||
| pub use rect::{ | ||
| Rect, TypedRect, rect, | ||
| Box2D, TypedBox2D, box2, | ||
| }; | ||
| pub use side_offsets::{SideOffsets2D, TypedSideOffsets2D}; | ||
| #[cfg(feature = "unstable")] pub use side_offsets::SideOffsets2DSimdI32; | ||
| pub use size::{Size2D, TypedSize2D, size2}; | ||
| @@ -133,3 +135,10 @@ pub type Matrix4D<T> = Transform3D<T>; | ||
| #[deprecated] | ||
| pub type TypedMatrix4D<T, Src, Dst> = TypedTransform3D<T, Src, Dst>; | ||
|
|
||
| fn min<T: PartialOrd>(x: T, y: T) -> T { | ||
nical
Author
Collaborator
|
||
| if x <= y { x } else { y } | ||
| } | ||
|
|
||
| fn max<T: PartialOrd>(x: T, y: T) -> T { | ||
| if x >= y { x } else { y } | ||
| } | ||
| @@ -11,9 +11,9 @@ use super::UnknownUnit; | ||
| use length::Length; | ||
| use scale_factor::ScaleFactor; | ||
| use num::*; | ||
| use point::TypedPoint2D; | ||
| use point::{TypedPoint2D, point2}; | ||
| use vector::TypedVector2D; | ||
| use size::TypedSize2D; | ||
| use size::{TypedSize2D, size2}; | ||
|
|
||
| use heapsize::HeapSizeOf; | ||
| use num_traits::NumCast; | ||
| @@ -22,8 +22,9 @@ use std::cmp::PartialOrd; | ||
| use std::fmt; | ||
| use std::hash::{Hash, Hasher}; | ||
| use std::ops::{Add, Sub, Mul, Div}; | ||
| use {min, max}; | ||
|
|
||
| /// A 2d Rectangle optionally tagged with a unit. | ||
| /// A 2d Rectangle represented using a point and a size, optionally tagged with a unit. | ||
| #[repr(C)] | ||
| pub struct TypedRect<T, U = UnknownUnit> { | ||
| pub origin: TypedPoint2D<T, U>, | ||
| @@ -165,6 +166,11 @@ where T: Copy + Clone + Zero + PartialOrd + PartialEq + Add<T, Output=T> + Sub<T | ||
| lower_right_y - upper_left.y))) | ||
| } | ||
|
|
||
| #[inline] | ||
| pub fn to_box(&self) -> TypedBox2D<T, U> { | ||
| TypedBox2D::new(self.origin, point2(self.max_x(), self.max_y())) | ||
| } | ||
|
|
||
| /// Returns the same rectangle, translated by a vector. | ||
| #[inline] | ||
| #[must_use] | ||
| @@ -317,15 +323,6 @@ impl<T: Copy + PartialEq + Zero, U> TypedRect<T, U> { | ||
| } | ||
| } | ||
|
|
||
|
|
||
| pub fn min<T: Clone + PartialOrd>(x: T, y: T) -> T { | ||
| if x <= y { x } else { y } | ||
| } | ||
|
|
||
| pub fn max<T: Clone + PartialOrd>(x: T, y: T) -> T { | ||
| if x >= y { x } else { y } | ||
| } | ||
|
|
||
| impl<T: Copy + Mul<T, Output=T>, U> Mul<T> for TypedRect<T, U> { | ||
| type Output = Self; | ||
| #[inline] | ||
| @@ -455,9 +452,153 @@ impl<T: NumCast + Copy, Unit> TypedRect<T, Unit> { | ||
| } | ||
| } | ||
|
|
||
| /// A 2d Rectangle represented using two points, optionally tagged with a unit. | ||
| #[repr(C)] | ||
| pub struct TypedBox2D<T, Unit = UnknownUnit> { | ||
| pub min: TypedPoint2D<T, Unit>, | ||
| pub max: TypedPoint2D<T, Unit>, | ||
| } | ||
|
|
||
| /// The default box type with no unit. | ||
| pub type Box2D<T> = TypedBox2D<T, UnknownUnit>; | ||
|
|
||
| impl<T, U> TypedBox2D<T, U> { | ||
| pub fn new(min: TypedPoint2D<T, U>, max: TypedPoint2D<T, U>) -> Self { | ||
| TypedBox2D { min, max } | ||
| } | ||
| } | ||
|
|
||
| impl<T, U> TypedBox2D<T, U> | ||
| where T: Copy + Clone + Zero + One + PartialOrd + PartialEq + Add<Output=T> + Sub<Output=T> + Mul<Output=T> { | ||
| /// Returns a rectangle if this box is positive. | ||
| #[inline] | ||
| pub fn to_rect(&self) -> Option<TypedRect<T, U>> { | ||
| if self.is_positive() { | ||
| return None | ||
| } | ||
|
|
||
| Some(TypedRect { origin: self.min, size: self.size() }) | ||
| } | ||
|
|
||
| #[inline] | ||
| pub fn size(&self) -> TypedSize2D<T, U> { | ||
| (self.max - self.min).to_size() | ||
| } | ||
|
|
||
| /// Returns true if both width and height are superior or equal to zero. | ||
| #[inline] | ||
| pub fn is_positive(&self) -> bool { | ||
| self.max.x >= self.min.x && self.max.y >= self.min.y | ||
| } | ||
|
|
||
| /// Returns true if either width or height are inferior to zero. | ||
| #[inline] | ||
| pub fn is_negative(&self) -> bool { | ||
| self.max.x < self.min.x || self.max.y < self.min.y | ||
| } | ||
|
|
||
| /// Returns true if either width or height are inferior or equal to zero. | ||
| #[inline] | ||
| pub fn is_empty_or_negative(&self) -> bool { | ||
| self.max.x <= self.min.x || self.max.y <= self.min.y | ||
| } | ||
|
|
||
| #[inline] | ||
| pub fn intersects(&self, other: &Self) -> bool { | ||
| self.intersection(other).is_positive() | ||
| } | ||
|
|
||
| #[inline] | ||
| pub fn intersection(&self, other: &Self) -> Self { | ||
| TypedBox2D { | ||
| min: point2(max(self.min.x, other.min.x), max(self.min.y, other.min.y)), | ||
| max: point2(min(self.max.x, other.max.x), min(self.max.y, other.max.y)), | ||
| } | ||
| } | ||
|
|
||
| #[inline] | ||
| pub fn union(&self, other: &Self) -> Self { | ||
| if other.is_empty_or_negative() { | ||
nical
Author
Collaborator
|
||
| return *self; | ||
| } | ||
|
|
||
| if self.is_empty_or_negative() { | ||
| return *other; | ||
| } | ||
|
|
||
| TypedBox2D { | ||
| min: point2(min(self.min.x, other.min.x), min(self.min.y, other.min.y)), | ||
| max: point2(max(self.max.x, other.max.x), max(self.max.y, other.max.y)), | ||
| } | ||
| } | ||
|
|
||
| /// Returns true if this box contains the point. Points are considered | ||
| /// in the box if they are on the left or top edge, but outside if they | ||
| /// are on the right or bottom edge. | ||
| #[inline] | ||
| pub fn contains(&self, point: &TypedPoint2D<T, U>) -> bool { | ||
| self.min.x <= point.x && point.x < self.max.x && | ||
| self.min.y <= point.y && point.y < self.max.y | ||
| } | ||
|
|
||
| /// Linearly interpolate between this box and another box. | ||
| /// | ||
| /// The box should be positive. | ||
| /// `t` is expected to be between zero and one. | ||
| #[inline] | ||
| pub fn lerp(&self, other: Self, t: T) -> Self { | ||
| debug_assert!(self.is_positive()); | ||
| debug_assert!(other.is_positive()); | ||
| Self::new( | ||
| self.min.lerp(other.min, t), | ||
| self.max.lerp(other.max, t), | ||
| ) | ||
| } | ||
|
|
||
| /// Return the same box if positive, or an empty box if negative. | ||
| /// | ||
| /// This is useful after computing intersections since the latter can produce negative boxes. | ||
| #[inline] | ||
| #[must_use] | ||
| pub fn ensure_positive(&self) -> Self { | ||
| TypedBox2D { min: self.min, max: self.max.max(self.min) } | ||
| } | ||
| } | ||
|
|
||
| impl<T: Copy, U> Copy for TypedBox2D<T, U> {} | ||
|
|
||
| impl<T: Copy, U> Clone for TypedBox2D<T, U> { | ||
| fn clone(&self) -> Self { *self } | ||
| } | ||
|
|
||
| impl<T: PartialEq, U> PartialEq<TypedBox2D<T, U>> for TypedBox2D<T, U> { | ||
| fn eq(&self, other: &Self) -> bool { | ||
| self.min.eq(&other.min) && self.max.eq(&other.max) | ||
| } | ||
| } | ||
|
|
||
| impl<T: Eq, U> Eq for TypedBox2D<T, U> {} | ||
|
|
||
| impl<T: fmt::Debug, U> fmt::Debug for TypedBox2D<T, U> { | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| write!(f, "Box2D({:?} to {:?})", self.min, self.max) | ||
| } | ||
| } | ||
|
|
||
| impl<T: fmt::Display, U> fmt::Display for TypedBox2D<T, U> { | ||
| fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { | ||
| write!(formatter, "Box2D({} to {})", self.min, self.max) | ||
| } | ||
| } | ||
|
|
||
| /// Shorthand for `TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h))`. | ||
| pub fn rect<T: Copy, U>(x: T, y: T, w: T, h: T) -> TypedRect<T, U> { | ||
| TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h)) | ||
| TypedRect::new(point2(x, y), size2(w, h)) | ||
| } | ||
|
|
||
| /// Shorthand for `TypedBox2D::new(TypedPoint2D::new(min_x, min_y), TypedBox2D::new(max_x, max_y))`. | ||
| pub fn box2<T: Copy, U>(min_x: T, min_y: T, max_x: T, max_y: T) -> TypedBox2D<T, U> { | ||
| TypedBox2D::new(point2(min_x, min_y), point2(max_x, max_y)) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| @@ -517,7 +658,7 @@ mod tests { | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_union() { | ||
| fn test_rect_union() { | ||
| let p = Rect::new(Point2D::new(0, 0), Size2D::new(50, 40)); | ||
| let q = Rect::new(Point2D::new(20,20), Size2D::new(5, 5)); | ||
| let r = Rect::new(Point2D::new(-15, -30), Size2D::new(200, 15)); | ||
| @@ -538,7 +679,27 @@ mod tests { | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_intersection() { | ||
| fn test_box_union() { | ||
| let p = Rect::new(point2(0, 0), size2(50, 40)).to_box(); | ||
| let q = Rect::new(point2(20, 20), size2(5, 5)).to_box(); | ||
| let r = Rect::new(point2(-15, -30), size2(200, 15)).to_box(); | ||
| let s = Rect::new(point2(20, -15), size2(250, 200)).to_box(); | ||
|
|
||
| let pq = p.union(&q); | ||
| assert_eq!(pq.min, point2(0, 0)); | ||
| assert_eq!(pq.size(), size2(50, 40)); | ||
|
|
||
| let pr = p.union(&r); | ||
| assert_eq!(pr.min, point2(-15, -30)); | ||
| assert_eq!(pr.size(), size2(200, 70)); | ||
|
|
||
| let ps = p.union(&s); | ||
| assert_eq!(ps.min, point2(0, -15)); | ||
| assert_eq!(ps.size(), size2(270, 200)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_rect_intersection() { | ||
| let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20)); | ||
| let q = Rect::new(Point2D::new(5, 15), Size2D::new(10, 10)); | ||
| let r = Rect::new(Point2D::new(-5, -5), Size2D::new(8, 8)); | ||
| @@ -559,6 +720,26 @@ mod tests { | ||
| assert!(qr.is_none()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_box_intersection() { | ||
| let p = Rect::new(point2(0, 0), size2(10, 20)).to_box(); | ||
| let q = Rect::new(point2(5, 15), size2(10, 10)).to_box(); | ||
| let r = Rect::new(point2(-5, -5), size2(8, 8)).to_box(); | ||
|
|
||
| let pq = p.intersection(&q); | ||
| assert!(pq.is_positive()); | ||
| assert_eq!(pq.min, point2(5, 15)); | ||
| assert_eq!(pq.size(), size2(5, 5)); | ||
|
|
||
| let pr = p.intersection(&r); | ||
| assert!(pr.is_positive()); | ||
| assert_eq!(pr.min, point2(0, 0)); | ||
| assert_eq!(pr.size(), size2(3, 3)); | ||
|
|
||
| let qr = q.intersection(&r); | ||
| assert!(qr.is_empty_or_negative()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_contains() { | ||
| let r = Rect::new(Point2D::new(-20, 15), Size2D::new(100, 200)); | ||
| @@ -697,4 +878,14 @@ mod tests { | ||
| x += 0.1 | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_box_negative() { | ||
| assert!(Box2D::new(point2(0.0, 0.0), point2(0.0, 0.0)).is_positive()); | ||
| assert!(Box2D::new(point2(0.0, 0.0), point2(0.0, 0.0)).is_empty_or_negative()); | ||
| assert!(Box2D::new(point2(-1.0, -2.0), point2(0.0, 0.0)).is_positive()); | ||
| assert!(Box2D::new(point2(1.0, 2.0), point2(0.0, 1.0)).is_negative()); | ||
| assert!(Box2D::new(point2(1.0, 2.0), point2(0.0, 2.0)).is_negative()); | ||
| assert!(Box2D::new(point2(1.0, 2.0), point2(1.0, 0.0)).is_negative()); | ||
| } | ||
| } | ||
Oops, something went wrong.
ProTip!
Use n and p to navigate between commits in a pull request.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
that seems rather sad that we need those functions