Skip to content

Commit a5c958a

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

File tree

4 files changed

+234
-30
lines changed

4 files changed

+234
-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: 208 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,165 @@ 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+
let size = self.size();
477+
478+
let _0 = T::zero();
479+
if size.width <= _0 || size.height <= _0 {
480+
return None
481+
}
482+
483+
Some(TypedRect { origin: self.min, size })
484+
}
485+
486+
#[inline]
487+
pub fn size(&self) -> TypedSize2D<T, U> {
488+
(self.max - self.min).to_size()
489+
}
490+
491+
/// Returns true if both width and height are superior or equal to zero.
492+
#[inline]
493+
pub fn is_positive(&self) -> bool {
494+
let size = self.size();
495+
let _0 = T::zero();
496+
497+
size.width >= _0 && size.height >= _0
498+
}
499+
500+
/// Returns true if either width or height are inferior to zero.
501+
#[inline]
502+
pub fn is_negative(&self) -> bool {
503+
let size = self.size();
504+
let _0 = T::zero();
505+
506+
size.width < _0 || size.height < _0
507+
}
508+
509+
/// Returns true if either width or height are inferior or equal to zero.
510+
#[inline]
511+
pub fn is_empty_or_negative(&self) -> bool {
512+
let size = self.size();
513+
let _0 = T::zero();
514+
515+
size.width <= _0 || size.height <= _0
516+
}
517+
518+
#[inline]
519+
pub fn intersects(&self, other: &Self) -> bool {
520+
self.intersection(other).is_positive()
521+
}
522+
523+
#[inline]
524+
pub fn intersection(&self, other: &Self) -> Self {
525+
TypedBox2D {
526+
min: point2(max(self.min.x, other.min.x), max(self.min.y, other.min.y)),
527+
max: point2(min(self.max.x, other.max.x), min(self.max.y, other.max.y)),
528+
}
529+
}
530+
531+
#[inline]
532+
pub fn union(&self, other: &Self) -> Self {
533+
if other.is_empty_or_negative() {
534+
return *self;
535+
}
536+
537+
if self.is_empty_or_negative() {
538+
return *other;
539+
}
540+
541+
TypedBox2D {
542+
min: point2(min(self.min.x, other.min.x), min(self.min.y, other.min.y)),
543+
max: point2(max(self.max.x, other.max.x), max(self.max.y, other.max.y)),
544+
}
545+
}
546+
547+
/// Returns true if this box contains the point. Points are considered
548+
/// in the box if they are on the left or top edge, but outside if they
549+
/// are on the right or bottom edge.
550+
#[inline]
551+
pub fn contains(&self, point: &TypedPoint2D<T, U>) -> bool {
552+
self.min.x <= point.x && point.x < self.max.x &&
553+
self.min.y <= point.y && point.y < self.max.y
554+
}
555+
556+
/// Linearly interpolate between this box and another box.
557+
///
558+
/// The box should be positive.
559+
/// `t` is expected to be between zero and one.
560+
#[inline]
561+
pub fn lerp(&self, other: Self, t: T) -> Self {
562+
debug_assert!(!self.is_positive());
563+
debug_assert!(!other.is_positive());
564+
Self::new(
565+
self.min.lerp(other.min, t),
566+
self.max.lerp(other.max, t),
567+
)
568+
}
569+
570+
/// Return the same box if positive, or an empty box if negative.
571+
///
572+
/// This is useful after computing intersections since the latter can produce negative boxes.
573+
#[inline]
574+
#[must_use]
575+
pub fn ensure_positive(&self) -> Self {
576+
TypedBox2D { min: self.min, max: self.max.max(self.min) }
577+
}
578+
}
579+
580+
impl<T: Copy, U> Copy for TypedBox2D<T, U> {}
581+
582+
impl<T: Copy, U> Clone for TypedBox2D<T, U> {
583+
fn clone(&self) -> Self { *self }
584+
}
585+
586+
impl<T: PartialEq, U> PartialEq<TypedBox2D<T, U>> for TypedBox2D<T, U> {
587+
fn eq(&self, other: &Self) -> bool {
588+
self.min.eq(&other.min) && self.max.eq(&other.max)
589+
}
590+
}
591+
592+
impl<T: Eq, U> Eq for TypedBox2D<T, U> {}
593+
594+
impl<T: fmt::Debug, U> fmt::Debug for TypedBox2D<T, U> {
595+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
596+
write!(f, "Box2D({:?} to {:?})", self.min, self.max)
597+
}
598+
}
599+
600+
impl<T: fmt::Display, U> fmt::Display for TypedBox2D<T, U> {
601+
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
602+
write!(formatter, "Box2D({} to {})", self.min, self.max)
603+
}
604+
}
605+
458606
/// Shorthand for `TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h))`.
459607
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))
608+
TypedRect::new(point2(x, y), size2(w, h))
609+
}
610+
611+
/// Shorthand for `TypedBox2D::new(TypedPoint2D::new(min_x, min_y), TypedBox2D::new(max_x, max_y))`.
612+
pub fn box2<T: Copy, U>(min_x: T, min_y: T, max_x: T, max_y: T) -> TypedBox2D<T, U> {
613+
TypedBox2D::new(point2(min_x, min_y), point2(max_x, max_y))
461614
}
462615

463616
#[cfg(test)]
@@ -517,7 +670,7 @@ mod tests {
517670
}
518671

519672
#[test]
520-
fn test_union() {
673+
fn test_rect_union() {
521674
let p = Rect::new(Point2D::new(0, 0), Size2D::new(50, 40));
522675
let q = Rect::new(Point2D::new(20,20), Size2D::new(5, 5));
523676
let r = Rect::new(Point2D::new(-15, -30), Size2D::new(200, 15));
@@ -538,7 +691,27 @@ mod tests {
538691
}
539692

540693
#[test]
541-
fn test_intersection() {
694+
fn test_box_union() {
695+
let p = Rect::new(point2(0, 0), size2(50, 40)).to_box();
696+
let q = Rect::new(point2(20, 20), size2(5, 5)).to_box();
697+
let r = Rect::new(point2(-15, -30), size2(200, 15)).to_box();
698+
let s = Rect::new(point2(20, -15), size2(250, 200)).to_box();
699+
700+
let pq = p.union(&q);
701+
assert!(pq.min == point2(0, 0));
702+
assert!(pq.size() == size2(50, 40));
703+
704+
let pr = p.union(&r);
705+
assert!(pr.min == point2(-15, -30));
706+
assert!(pr.size() == size2(200, 70));
707+
708+
let ps = p.union(&s);
709+
assert!(ps.min == point2(0, -15));
710+
assert!(ps.size() == size2(270, 200));
711+
}
712+
713+
#[test]
714+
fn test_rect_intersection() {
542715
let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20));
543716
let q = Rect::new(Point2D::new(5, 15), Size2D::new(10, 10));
544717
let r = Rect::new(Point2D::new(-5, -5), Size2D::new(8, 8));
@@ -559,6 +732,26 @@ mod tests {
559732
assert!(qr.is_none());
560733
}
561734

735+
#[test]
736+
fn test_box_intersection() {
737+
let p = Rect::new(point2(0, 0), size2(10, 20)).to_box();
738+
let q = Rect::new(point2(5, 15), size2(10, 10)).to_box();
739+
let r = Rect::new(point2(-5, -5), size2(8, 8)).to_box();
740+
741+
let pq = p.intersection(&q);
742+
assert!(pq.is_positive());
743+
assert!(pq.min == point2(5, 15));
744+
assert!(pq.size() == size2(5, 5));
745+
746+
let pr = p.intersection(&r);
747+
assert!(pr.is_positive());
748+
assert!(pr.min == point2(0, 0));
749+
assert!(pr.size() == size2(3, 3));
750+
751+
let qr = q.intersection(&r);
752+
assert!(qr.is_empty_or_negative());
753+
}
754+
562755
#[test]
563756
fn test_contains() {
564757
let r = Rect::new(Point2D::new(-20, 15), Size2D::new(100, 200));

0 commit comments

Comments
 (0)