Skip to content
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
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Introduce the (Typed)Box2D type.

  • Loading branch information
nical committed Jun 29, 2017
commit 84897bac92af02e7bee0b665c14cd8422ae4dbbf
@@ -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 {

This comment has been minimized.

@kvark

kvark Jun 29, 2017

Member

that seems rather sad that we need those functions

This comment has been minimized.

@nical

nical Jun 29, 2017

Author Collaborator

Yeah, that's a more general rust std sadness topic. They were already there, I just moved them up so that Point::min/max can use that instead of depending on the Float trait.

if x <= y { x } else { y }
}

fn max<T: PartialOrd>(x: T, y: T) -> T {
if x >= y { x } else { y }
}
@@ -13,11 +13,12 @@ use length::Length;
use scale_factor::ScaleFactor;
use size::TypedSize2D;
use num::*;
use num_traits::{Float, NumCast};
use num_traits::NumCast;
use vector::{TypedVector2D, TypedVector3D, vec2, vec3};
use std::fmt;
use std::ops::{Add, Mul, Sub, Div, AddAssign, SubAssign, MulAssign, DivAssign};
use std::marker::PhantomData;
use {min, max};

define_matrix! {
/// 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,
}
}

impl<T: Float, U> TypedPoint2D<T, U> {
impl<T: Copy + PartialOrd, U> TypedPoint2D<T, U> {
#[inline]
pub fn min(self, other: Self) -> Self {
point2(self.x.min(other.x), self.y.min(other.y))
point2(min(self.x, other.x), min(self.y, other.y))
}

#[inline]
pub fn max(self, other: Self) -> Self {
point2(self.x.max(other.x), self.y.max(other.y))
point2(max(self.x, other.x), max(self.y, other.y))
}
}

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

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

#[inline]
pub fn max(self, other: Self) -> Self {
point3(self.x.max(other.x), self.y.max(other.y), self.z.max(other.z))
point3(max(self.x, other.x), max(self.y, other.y), max(self.z, other.z))
}
}

@@ -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() {

This comment has been minimized.

@jrmuizel

jrmuizel Jun 28, 2017

Contributor

Do we need to special case this or can we just rely on the maxes?

This comment has been minimized.

@nical

nical Jun 29, 2017

Author Collaborator

I don't think we can rely on the maxes for union (although it does work for intersection). We could turn this into a debug_assert and force people to use ensure_positive beforehand if this is a performance concern.

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());
}
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.