Skip to content

Commit 84897ba

Browse files
committed
Introduce the (Typed)Box2D type.
1 parent aac4131 commit 84897ba

File tree

4 files changed

+232
-30
lines changed

4 files changed

+232
-30
lines changed

src/lib.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,10 @@ pub use vector::{
7979
Vector2D, TypedVector2D, vec2,
8080
Vector3D, TypedVector3D, vec3,
8181
};
82-
83-
pub use rect::{Rect, TypedRect, rect};
82+
pub use rect::{
83+
Rect, TypedRect, rect,
84+
Box2D, TypedBox2D, box2,
85+
};
8486
pub use side_offsets::{SideOffsets2D, TypedSideOffsets2D};
8587
#[cfg(feature = "unstable")] pub use side_offsets::SideOffsets2DSimdI32;
8688
pub use size::{Size2D, TypedSize2D, size2};
@@ -133,3 +135,10 @@ pub type Matrix4D<T> = Transform3D<T>;
133135
#[deprecated]
134136
pub type TypedMatrix4D<T, Src, Dst> = TypedTransform3D<T, Src, Dst>;
135137

138+
fn min<T: PartialOrd>(x: T, y: T) -> T {
139+
if x <= y { x } else { y }
140+
}
141+
142+
fn max<T: PartialOrd>(x: T, y: T) -> T {
143+
if x >= y { x } else { y }
144+
}

src/point.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ use length::Length;
1313
use scale_factor::ScaleFactor;
1414
use size::TypedSize2D;
1515
use num::*;
16-
use num_traits::{Float, NumCast};
16+
use num_traits::NumCast;
1717
use vector::{TypedVector2D, TypedVector3D, vec2, vec3};
1818
use std::fmt;
1919
use std::ops::{Add, Mul, Sub, Div, AddAssign, SubAssign, MulAssign, DivAssign};
2020
use std::marker::PhantomData;
21+
use {min, max};
2122

2223
define_matrix! {
2324
/// A 2d Point tagged with a unit.
@@ -169,15 +170,15 @@ impl<T: Copy + Sub<T, Output=T>, U> Sub<TypedVector2D<T, U>> for TypedPoint2D<T,
169170
}
170171
}
171172

172-
impl<T: Float, U> TypedPoint2D<T, U> {
173+
impl<T: Copy + PartialOrd, U> TypedPoint2D<T, U> {
173174
#[inline]
174175
pub fn min(self, other: Self) -> Self {
175-
point2(self.x.min(other.x), self.y.min(other.y))
176+
point2(min(self.x, other.x), min(self.y, other.y))
176177
}
177178

178179
#[inline]
179180
pub fn max(self, other: Self) -> Self {
180-
point2(self.x.max(other.x), self.y.max(other.y))
181+
point2(max(self.x, other.x), max(self.y, other.y))
181182
}
182183
}
183184

@@ -527,15 +528,15 @@ impl<T: Copy + Div<T, Output=T>, U> Div<T> for TypedPoint3D<T, U> {
527528
}
528529
}
529530

530-
impl<T: Float, U> TypedPoint3D<T, U> {
531+
impl<T: Copy + PartialOrd, U> TypedPoint3D<T, U> {
531532
#[inline]
532533
pub fn min(self, other: Self) -> Self {
533-
point3(self.x.min(other.x), self.y.min(other.y), self.z.min(other.z))
534+
point3(min(self.x, other.x), min(self.y, other.y), min(self.z, other.z))
534535
}
535536

536537
#[inline]
537538
pub fn max(self, other: Self) -> Self {
538-
point3(self.x.max(other.x), self.y.max(other.y), self.z.max(other.z))
539+
point3(max(self.x, other.x), max(self.y, other.y), max(self.z, other.z))
539540
}
540541
}
541542

src/rect.rs

Lines changed: 206 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ use super::UnknownUnit;
1111
use length::Length;
1212
use scale_factor::ScaleFactor;
1313
use num::*;
14-
use point::TypedPoint2D;
14+
use point::{TypedPoint2D, point2};
1515
use vector::TypedVector2D;
16-
use size::TypedSize2D;
16+
use size::{TypedSize2D, size2};
1717

1818
use heapsize::HeapSizeOf;
1919
use num_traits::NumCast;
@@ -22,8 +22,9 @@ use std::cmp::PartialOrd;
2222
use std::fmt;
2323
use std::hash::{Hash, Hasher};
2424
use std::ops::{Add, Sub, Mul, Div};
25+
use {min, max};
2526

26-
/// A 2d Rectangle optionally tagged with a unit.
27+
/// A 2d Rectangle represented using a point and a size, optionally tagged with a unit.
2728
#[repr(C)]
2829
pub struct TypedRect<T, U = UnknownUnit> {
2930
pub origin: TypedPoint2D<T, U>,
@@ -165,6 +166,11 @@ where T: Copy + Clone + Zero + PartialOrd + PartialEq + Add<T, Output=T> + Sub<T
165166
lower_right_y - upper_left.y)))
166167
}
167168

169+
#[inline]
170+
pub fn to_box(&self) -> TypedBox2D<T, U> {
171+
TypedBox2D::new(self.origin, point2(self.max_x(), self.max_y()))
172+
}
173+
168174
/// Returns the same rectangle, translated by a vector.
169175
#[inline]
170176
#[must_use]
@@ -317,15 +323,6 @@ impl<T: Copy + PartialEq + Zero, U> TypedRect<T, U> {
317323
}
318324
}
319325

320-
321-
pub fn min<T: Clone + PartialOrd>(x: T, y: T) -> T {
322-
if x <= y { x } else { y }
323-
}
324-
325-
pub fn max<T: Clone + PartialOrd>(x: T, y: T) -> T {
326-
if x >= y { x } else { y }
327-
}
328-
329326
impl<T: Copy + Mul<T, Output=T>, U> Mul<T> for TypedRect<T, U> {
330327
type Output = Self;
331328
#[inline]
@@ -455,9 +452,153 @@ impl<T: NumCast + Copy, Unit> TypedRect<T, Unit> {
455452
}
456453
}
457454

455+
/// A 2d Rectangle represented using two points, optionally tagged with a unit.
456+
#[repr(C)]
457+
pub struct TypedBox2D<T, Unit = UnknownUnit> {
458+
pub min: TypedPoint2D<T, Unit>,
459+
pub max: TypedPoint2D<T, Unit>,
460+
}
461+
462+
/// The default box type with no unit.
463+
pub type Box2D<T> = TypedBox2D<T, UnknownUnit>;
464+
465+
impl<T, U> TypedBox2D<T, U> {
466+
pub fn new(min: TypedPoint2D<T, U>, max: TypedPoint2D<T, U>) -> Self {
467+
TypedBox2D { min, max }
468+
}
469+
}
470+
471+
impl<T, U> TypedBox2D<T, U>
472+
where T: Copy + Clone + Zero + One + PartialOrd + PartialEq + Add<Output=T> + Sub<Output=T> + Mul<Output=T> {
473+
/// Returns a rectangle if this box is positive.
474+
#[inline]
475+
pub fn to_rect(&self) -> Option<TypedRect<T, U>> {
476+
if self.is_positive() {
477+
return None
478+
}
479+
480+
Some(TypedRect { origin: self.min, size: self.size() })
481+
}
482+
483+
#[inline]
484+
pub fn size(&self) -> TypedSize2D<T, U> {
485+
(self.max - self.min).to_size()
486+
}
487+
488+
/// Returns true if both width and height are superior or equal to zero.
489+
#[inline]
490+
pub fn is_positive(&self) -> bool {
491+
self.max.x >= self.min.x && self.max.y >= self.min.y
492+
}
493+
494+
/// Returns true if either width or height are inferior to zero.
495+
#[inline]
496+
pub fn is_negative(&self) -> bool {
497+
self.max.x < self.min.x || self.max.y < self.min.y
498+
}
499+
500+
/// Returns true if either width or height are inferior or equal to zero.
501+
#[inline]
502+
pub fn is_empty_or_negative(&self) -> bool {
503+
self.max.x <= self.min.x || self.max.y <= self.min.y
504+
}
505+
506+
#[inline]
507+
pub fn intersects(&self, other: &Self) -> bool {
508+
self.intersection(other).is_positive()
509+
}
510+
511+
#[inline]
512+
pub fn intersection(&self, other: &Self) -> Self {
513+
TypedBox2D {
514+
min: point2(max(self.min.x, other.min.x), max(self.min.y, other.min.y)),
515+
max: point2(min(self.max.x, other.max.x), min(self.max.y, other.max.y)),
516+
}
517+
}
518+
519+
#[inline]
520+
pub fn union(&self, other: &Self) -> Self {
521+
if other.is_empty_or_negative() {
522+
return *self;
523+
}
524+
525+
if self.is_empty_or_negative() {
526+
return *other;
527+
}
528+
529+
TypedBox2D {
530+
min: point2(min(self.min.x, other.min.x), min(self.min.y, other.min.y)),
531+
max: point2(max(self.max.x, other.max.x), max(self.max.y, other.max.y)),
532+
}
533+
}
534+
535+
/// Returns true if this box contains the point. Points are considered
536+
/// in the box if they are on the left or top edge, but outside if they
537+
/// are on the right or bottom edge.
538+
#[inline]
539+
pub fn contains(&self, point: &TypedPoint2D<T, U>) -> bool {
540+
self.min.x <= point.x && point.x < self.max.x &&
541+
self.min.y <= point.y && point.y < self.max.y
542+
}
543+
544+
/// Linearly interpolate between this box and another box.
545+
///
546+
/// The box should be positive.
547+
/// `t` is expected to be between zero and one.
548+
#[inline]
549+
pub fn lerp(&self, other: Self, t: T) -> Self {
550+
debug_assert!(self.is_positive());
551+
debug_assert!(other.is_positive());
552+
Self::new(
553+
self.min.lerp(other.min, t),
554+
self.max.lerp(other.max, t),
555+
)
556+
}
557+
558+
/// Return the same box if positive, or an empty box if negative.
559+
///
560+
/// This is useful after computing intersections since the latter can produce negative boxes.
561+
#[inline]
562+
#[must_use]
563+
pub fn ensure_positive(&self) -> Self {
564+
TypedBox2D { min: self.min, max: self.max.max(self.min) }
565+
}
566+
}
567+
568+
impl<T: Copy, U> Copy for TypedBox2D<T, U> {}
569+
570+
impl<T: Copy, U> Clone for TypedBox2D<T, U> {
571+
fn clone(&self) -> Self { *self }
572+
}
573+
574+
impl<T: PartialEq, U> PartialEq<TypedBox2D<T, U>> for TypedBox2D<T, U> {
575+
fn eq(&self, other: &Self) -> bool {
576+
self.min.eq(&other.min) && self.max.eq(&other.max)
577+
}
578+
}
579+
580+
impl<T: Eq, U> Eq for TypedBox2D<T, U> {}
581+
582+
impl<T: fmt::Debug, U> fmt::Debug for TypedBox2D<T, U> {
583+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
584+
write!(f, "Box2D({:?} to {:?})", self.min, self.max)
585+
}
586+
}
587+
588+
impl<T: fmt::Display, U> fmt::Display for TypedBox2D<T, U> {
589+
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
590+
write!(formatter, "Box2D({} to {})", self.min, self.max)
591+
}
592+
}
593+
458594
/// Shorthand for `TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h))`.
459595
pub fn rect<T: Copy, U>(x: T, y: T, w: T, h: T) -> TypedRect<T, U> {
460-
TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h))
596+
TypedRect::new(point2(x, y), size2(w, h))
597+
}
598+
599+
/// Shorthand for `TypedBox2D::new(TypedPoint2D::new(min_x, min_y), TypedBox2D::new(max_x, max_y))`.
600+
pub fn box2<T: Copy, U>(min_x: T, min_y: T, max_x: T, max_y: T) -> TypedBox2D<T, U> {
601+
TypedBox2D::new(point2(min_x, min_y), point2(max_x, max_y))
461602
}
462603

463604
#[cfg(test)]
@@ -517,7 +658,7 @@ mod tests {
517658
}
518659

519660
#[test]
520-
fn test_union() {
661+
fn test_rect_union() {
521662
let p = Rect::new(Point2D::new(0, 0), Size2D::new(50, 40));
522663
let q = Rect::new(Point2D::new(20,20), Size2D::new(5, 5));
523664
let r = Rect::new(Point2D::new(-15, -30), Size2D::new(200, 15));
@@ -538,7 +679,27 @@ mod tests {
538679
}
539680

540681
#[test]
541-
fn test_intersection() {
682+
fn test_box_union() {
683+
let p = Rect::new(point2(0, 0), size2(50, 40)).to_box();
684+
let q = Rect::new(point2(20, 20), size2(5, 5)).to_box();
685+
let r = Rect::new(point2(-15, -30), size2(200, 15)).to_box();
686+
let s = Rect::new(point2(20, -15), size2(250, 200)).to_box();
687+
688+
let pq = p.union(&q);
689+
assert_eq!(pq.min, point2(0, 0));
690+
assert_eq!(pq.size(), size2(50, 40));
691+
692+
let pr = p.union(&r);
693+
assert_eq!(pr.min, point2(-15, -30));
694+
assert_eq!(pr.size(), size2(200, 70));
695+
696+
let ps = p.union(&s);
697+
assert_eq!(ps.min, point2(0, -15));
698+
assert_eq!(ps.size(), size2(270, 200));
699+
}
700+
701+
#[test]
702+
fn test_rect_intersection() {
542703
let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20));
543704
let q = Rect::new(Point2D::new(5, 15), Size2D::new(10, 10));
544705
let r = Rect::new(Point2D::new(-5, -5), Size2D::new(8, 8));
@@ -559,6 +720,26 @@ mod tests {
559720
assert!(qr.is_none());
560721
}
561722

723+
#[test]
724+
fn test_box_intersection() {
725+
let p = Rect::new(point2(0, 0), size2(10, 20)).to_box();
726+
let q = Rect::new(point2(5, 15), size2(10, 10)).to_box();
727+
let r = Rect::new(point2(-5, -5), size2(8, 8)).to_box();
728+
729+
let pq = p.intersection(&q);
730+
assert!(pq.is_positive());
731+
assert_eq!(pq.min, point2(5, 15));
732+
assert_eq!(pq.size(), size2(5, 5));
733+
734+
let pr = p.intersection(&r);
735+
assert!(pr.is_positive());
736+
assert_eq!(pr.min, point2(0, 0));
737+
assert_eq!(pr.size(), size2(3, 3));
738+
739+
let qr = q.intersection(&r);
740+
assert!(qr.is_empty_or_negative());
741+
}
742+
562743
#[test]
563744
fn test_contains() {
564745
let r = Rect::new(Point2D::new(-20, 15), Size2D::new(100, 200));
@@ -697,4 +878,14 @@ mod tests {
697878
x += 0.1
698879
}
699880
}
881+
882+
#[test]
883+
fn test_box_negative() {
884+
assert!(Box2D::new(point2(0.0, 0.0), point2(0.0, 0.0)).is_positive());
885+
assert!(Box2D::new(point2(0.0, 0.0), point2(0.0, 0.0)).is_empty_or_negative());
886+
assert!(Box2D::new(point2(-1.0, -2.0), point2(0.0, 0.0)).is_positive());
887+
assert!(Box2D::new(point2(1.0, 2.0), point2(0.0, 1.0)).is_negative());
888+
assert!(Box2D::new(point2(1.0, 2.0), point2(0.0, 2.0)).is_negative());
889+
assert!(Box2D::new(point2(1.0, 2.0), point2(1.0, 0.0)).is_negative());
890+
}
700891
}

0 commit comments

Comments
 (0)