From fe20f19d95c4c8355fa83060c6783e6a90a96686 Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Tue, 26 Jul 2016 14:31:25 +0200 Subject: [PATCH] Expose pre/post operations and transpositions explicitly. --- src/length.rs | 3 - src/lib.rs | 63 ++++++- src/matrix.rs | 14 -- src/matrix2d.rs | 269 +++++++++++++++++++++++----- src/matrix4d.rs | 375 +++++++++++++++++++++++++++------------ src/num.rs | 37 ++++ src/point.rs | 423 ++++++++++++++++++++++++++++++++++++-------- src/rect.rs | 154 ++++++++++++++-- src/side_offsets.rs | 57 ++++-- src/size.rs | 98 ++++++++-- 10 files changed, 1204 insertions(+), 289 deletions(-) delete mode 100644 src/matrix.rs diff --git a/src/length.rs b/src/length.rs index b9e48ee..8b0a2a5 100644 --- a/src/length.rs +++ b/src/length.rs @@ -20,9 +20,6 @@ use std::ops::{AddAssign, SubAssign}; use std::marker::PhantomData; use std::fmt; -#[derive(Clone, Copy, RustcDecodable, RustcEncodable)] -pub struct UnknownUnit; - /// A one-dimensional distance, with value represented by `T` and unit of measurement `Unit`. /// /// `T` can be any numeric type, for example a primitive type like u64 or f32. diff --git a/src/lib.rs b/src/lib.rs index 28a1c19..3bad47e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,49 @@ #![cfg_attr(feature = "unstable", feature(asm, repr_simd, test))] +//! A collection of strongly typed math tools for computer graphics with an inclination +//! towards 2d graphics and layout. +//! +//! All types are generic over the the scalar type of their component (f32, i32, etc.), +//! and tagged with a generic Unit parameter which is useful to prevent mixing +//! values from different spaces. For example it should not be legal to translate +//! a screen-space position by a world-space vector and this can be expressed using +//! the generic Unit parameter. +//! +//! This unit system is not mandatory and all Typed* structures have an alias +//! with the default unit: UnknownUnit. +//! for example ```Point2D``` is equivalent to ```TypedPoint2D```. +//! Client code typically creates a set of aliases for each type and doesn't need +//! to deal with the specifics of typed units further. For example: +//! +//! ```rust +//! use euclid::*; +//! pub struct ScreenSpace; +//! pub type ScreenPoint = TypedPoint2D; +//! pub type ScreenSize = TypedSize2D; +//! pub struct WorldSpace; +//! pub type WorldPoint = TypedPoint3D; +//! pub type ProjectionMatrix = TypedMatrix4D; +//! // etc... +//! ``` +//! +//! Components are accessed in their scalar form by default for convenience, and most +//! types additionally implement strongly typed accessors which return typed ```Length``` wrappers. +//! For example: +//! +//! ```rust +//! # use euclid::*; +//! # pub struct WorldSpace; +//! # pub type WorldPoint = TypedPoint3D; +//! let p = WorldPoint::new(0.0, 1.0, 1.0); +//! // p.x is an f32. +//! println!("p.x = {:?} ", p.x); +//! // p.x is a Length. +//! println!("p.x_typed() = {:?} ", p.x_typed()); +//! // Length::get returns the scalar value (f32). +//! assert_eq!(p.x, p.x_typed().get()); +//! ``` + extern crate heapsize; #[macro_use] @@ -22,7 +65,8 @@ extern crate rand; extern crate test; extern crate num_traits; -pub use matrix::Matrix4; +pub use length::Length; +pub use scale_factor::ScaleFactor; pub use matrix2d::{Matrix2D, TypedMatrix2D}; pub use matrix4d::{Matrix4D, TypedMatrix4D}; pub use point::{ @@ -39,7 +83,6 @@ pub mod approxeq; pub mod length; #[macro_use] mod macros; -pub mod matrix; pub mod matrix2d; pub mod matrix4d; pub mod num; @@ -49,3 +92,19 @@ pub mod scale_factor; pub mod side_offsets; pub mod size; mod trig; + +/// The default unit. +#[derive(Clone, Copy, RustcDecodable, RustcEncodable)] +pub struct UnknownUnit; + +/// Unit for angles in radians. +pub struct Rad; + +/// Unit for angles in degrees. +pub struct Deg; + +/// A value in radians. +pub type Radians = Length; + +/// A value in Degrees. +pub type Degrees = Length; diff --git a/src/matrix.rs b/src/matrix.rs deleted file mode 100644 index e07354f..0000000 --- a/src/matrix.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2013 The Servo Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use matrix4d::TypedMatrix4D; -use length::UnknownUnit; - -#[cfg_attr(feature = "unstable", deprecated(note = "Use matrix4d::Matrix4D instead"))] -pub type Matrix4 = TypedMatrix4D; diff --git a/src/matrix2d.rs b/src/matrix2d.rs index 9570742..aa9f80d 100644 --- a/src/matrix2d.rs +++ b/src/matrix2d.rs @@ -7,15 +7,29 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use super::{UnknownUnit, Radians}; use num::{One, Zero}; use point::TypedPoint2D; use rect::TypedRect; use size::TypedSize2D; -use length::UnknownUnit; -use std::ops::{Add, Mul, Sub}; +use std::ops::{Add, Mul, Div, Sub}; use std::marker::PhantomData; +use approxeq::ApproxEq; +use trig::Trig; define_matrix! { + /// A 2d transform stored as a 2 by 3 matrix in row-major order in memory, + /// useful to represent 2d transformations. + /// + /// Matrices can be parametrized over the source and destination units, to describe a + /// transformation from a space to another. + /// For example, TypedMatrix2D::transform_point4d + /// takes a TypedPoint2D and returns a TypedPoint2D. + /// + /// Matrices expose a set of convenience methods for pre- and post-transformations. + /// A pre-transformation corresponds to adding an operation that is applied before + /// the rest of the transformation, while a post-transformation adds an operation + /// that is appled after. pub struct TypedMatrix2D { pub m11: T, pub m12: T, pub m21: T, pub m22: T, @@ -23,10 +37,12 @@ define_matrix! { } } +/// The default 2d matrix type with no units. pub type Matrix2D = TypedMatrix2D; -impl TypedMatrix2D { - pub fn new(m11: T, m12: T, m21: T, m22: T, m31: T, m32: T) -> TypedMatrix2D { +impl TypedMatrix2D { + /// Create a matrix specifying its components in row-major order. + pub fn row_major(m11: T, m12: T, m21: T, m22: T, m31: T, m32: T) -> TypedMatrix2D { TypedMatrix2D { m11: m11, m12: m12, m21: m21, m22: m22, @@ -34,56 +50,156 @@ impl TypedMatrix2D { _unit: PhantomData, } } -} -impl TypedMatrix2D { - pub fn to_array(&self) -> [T; 6] { + /// Create a matrix specifying its components in column-major order. + pub fn column_major(m11: T, m21: T, m31: T, m12: T, m22: T, m32: T) -> TypedMatrix2D { + TypedMatrix2D { + m11: m11, m12: m12, + m21: m21, m22: m22, + m31: m31, m32: m32, + _unit: PhantomData, + } + } + + /// Returns an array containing this matrix's terms in row-major order (the order + /// in which the matrix is actually laid out in memory). + pub fn to_row_major_array(&self) -> [T; 6] { [ self.m11, self.m12, self.m21, self.m22, self.m31, self.m32 ] } + + /// Returns an array containing this matrix's terms in column-major order. + pub fn to_column_major_array(&self) -> [T; 6] { + [ + self.m11, self.m21, self.m31, + self.m12, self.m22, self.m32 + ] + } + + /// Drop the units, preserving only the numeric value. + pub fn to_untyped(&self) -> Matrix2D { + Matrix2D::row_major( + self.m11, self.m12, + self.m21, self.m22, + self.m31, self.m32 + ) + } + + /// Tag a unitless value with units. + pub fn from_untyped(p: &Matrix2D) -> TypedMatrix2D { + TypedMatrix2D::row_major( + p.m11, p.m12, + p.m21, p.m22, + p.m31, p.m32 + ) + } } impl TypedMatrix2D where T: Copy + Clone + Add + Mul + + Div + Sub + + Trig + PartialOrd + One + Zero { - pub fn mul(&self, m: &TypedMatrix2D) -> TypedMatrix2D { - TypedMatrix2D::new( - m.m11*self.m11 + m.m12*self.m21, - m.m11*self.m12 + m.m12*self.m22, - m.m21*self.m11 + m.m22*self.m21, - m.m21*self.m12 + m.m22*self.m22, - m.m31*self.m11 + m.m32*self.m21 + self.m31, - m.m31*self.m12 + m.m32*self.m22 + self.m32 + pub fn identity() -> TypedMatrix2D { + let (_0, _1) = (Zero::zero(), One::one()); + TypedMatrix2D::row_major( + _1, _0, + _0, _1, + _0, _0 ) } - pub fn translate(&self, x: T, y: T) -> TypedMatrix2D { + /// Returns the multiplication of the two matrices such that mat's transformation + /// applies after self's transformation. + pub fn post_mul(&self, mat: &TypedMatrix2D) -> TypedMatrix2D { + TypedMatrix2D::row_major( + self.m11 * mat.m11 + self.m12 * mat.m21, + self.m11 * mat.m12 + self.m12 * mat.m22, + self.m21 * mat.m11 + self.m22 * mat.m21, + self.m21 * mat.m12 + self.m22 * mat.m22, + self.m31 * mat.m11 + self.m32 * mat.m21 + mat.m31, + self.m31 * mat.m12 + self.m32 * mat.m22 + mat.m32, + ) + } + + /// Returns the multiplication of the two matrices such that mat's transformation + /// applies before self's transformation. + pub fn pre_mul(&self, mat: &TypedMatrix2D) -> TypedMatrix2D { + mat.post_mul(self) + } + + /// Returns a translation matrix. + pub fn create_translation(x: T, y: T) -> TypedMatrix2D { let (_0, _1): (T, T) = (Zero::zero(), One::one()); - let matrix = TypedMatrix2D::new(_1, _0, - _0, _1, - x, y); - self.mul(&matrix) + TypedMatrix2D::row_major( + _1, _0, + _0, _1, + x, y + ) } - pub fn scale(&self, x: T, y: T) -> TypedMatrix2D { - TypedMatrix2D::new(self.m11 * x, self.m12, - self.m21, self.m22 * y, - self.m31, self.m32) + /// Applies a translation after self's transformation and returns the resulting matrix. + pub fn post_translated(&self, x: T, y: T) -> TypedMatrix2D { + self.post_mul(&TypedMatrix2D::create_translation(x, y)) } - pub fn identity() -> TypedMatrix2D { - let (_0, _1) = (Zero::zero(), One::one()); - TypedMatrix2D::new(_1, _0, - _0, _1, - _0, _0) + /// Applies a translation before self's transformation and returns the resulting matrix. + pub fn pre_translated(&self, x: T, y: T) -> TypedMatrix2D { + self.pre_mul(&TypedMatrix2D::create_translation(x, y)) + } + + /// Returns a scale matrix. + pub fn create_scale(x: T, y: T) -> TypedMatrix2D { + let _0 = Zero::zero(); + TypedMatrix2D::row_major( + x, _0, + _0, y, + _0, _0 + ) + } + + /// Applies a scale after self's transformation and returns the resulting matrix. + pub fn post_scaled(&self, x: T, y: T) -> TypedMatrix2D { + self.post_mul(&TypedMatrix2D::create_scale(x, y)) + } + + /// Applies a scale before self's transformation and returns the resulting matrix. + pub fn pre_scaled(&self, x: T, y: T) -> TypedMatrix2D { + TypedMatrix2D::row_major( + self.m11 * x, self.m12, + self.m21, self.m22 * y, + self.m31, self.m32 + ) + } + + /// Returns a rotation matrix. + pub fn create_rotation(theta: Radians) -> TypedMatrix2D { + let _0 = Zero::zero(); + let cos = theta.get().cos(); + let sin = theta.get().sin(); + TypedMatrix2D::row_major( + cos, _0 - sin, + sin, cos, + _0, _0 + ) + } + + /// Applies a rotation after self's transformation and returns the resulting matrix. + pub fn post_rotated(&self, theta: Radians) -> TypedMatrix2D { + self.post_mul(&TypedMatrix2D::create_rotation(theta)) + } + + /// Applies a rotation after self's transformation and returns the resulting matrix. + pub fn pre_rotated(&self, theta: Radians) -> TypedMatrix2D { + self.pre_mul(&TypedMatrix2D::create_rotation(theta)) } /// Returns the given point transformed by this matrix. @@ -120,25 +236,94 @@ where T: Copy + Clone + TypedRect::new(TypedPoint2D::new(min_x, min_y), TypedSize2D::new(max_x - min_x, max_y - min_y)) } -} -// Convenient aliases for TypedPoint2D with typed units -impl TypedMatrix2D { - /// Drop the units, preserving only the numeric value. - pub fn to_untyped(&self) -> Matrix2D { - Matrix2D::new( + /// Computes and returns the determinant of this matrix. + pub fn determinant(&self) -> T { + self.m11 * self.m22 - self.m12 * self.m21 + } + + /// Returns the inverse matrix if possible. + pub fn inverse(&self) -> Option> { + let det = self.determinant(); + + let _0: T = Zero::zero(); + let _1: T = One::one(); + + if det == _0 { + return None; + } + + let inv_det = _1 / det; + Some(TypedMatrix2D::row_major( + inv_det * self.m22, + inv_det * (_0 - self.m12), + inv_det * (_0 - self.m21), + inv_det * self.m11, + inv_det * (self.m21 * self.m32 - self.m22 * self.m31), + inv_det * (self.m31 * self.m12 - self.m11 * self.m32), + )) + } + + /// Returns the same matrix with a different destination unit. + #[inline] + pub fn with_destination(&self) -> TypedMatrix2D { + TypedMatrix2D::row_major( self.m11, self.m12, self.m21, self.m22, - self.m31, self.m32 + self.m31, self.m32, ) } - /// Tag a unitless value with units. - pub fn from_untyped(p: &Matrix2D) -> TypedMatrix2D { - TypedMatrix2D::new( - p.m11, p.m12, - p.m21, p.m22, - p.m31, p.m32 + /// Returns the same matrix with a different source unit. + #[inline] + pub fn with_source(&self) -> TypedMatrix2D { + TypedMatrix2D::row_major( + self.m11, self.m12, + self.m21, self.m22, + self.m31, self.m32, ) } } + +impl, Src, Dst> TypedMatrix2D { + pub fn approx_eq(&self, other: &Self) -> bool { + self.m11.approx_eq(&other.m11) && self.m12.approx_eq(&other.m12) && + self.m21.approx_eq(&other.m21) && self.m22.approx_eq(&other.m22) && + self.m31.approx_eq(&other.m31) && self.m32.approx_eq(&other.m32) + } +} + +#[cfg(test)] +mod test { + use super::*; + + type Mat = Matrix2D; + + #[test] + pub fn test_inverse_simple() { + let m1 = Mat::identity(); + let m2 = m1.inverse().unwrap(); + assert!(m1.approx_eq(&m2)); + } + + #[test] + pub fn test_inverse_scale() { + let m1 = Mat::create_scale(1.5, 0.3); + let m2 = m1.inverse().unwrap(); + assert!(m1.pre_mul(&m2).approx_eq(&Mat::identity())); + } + + #[test] + pub fn test_inverse_translate() { + let m1 = Mat::create_translation(-132.0, 0.3); + let m2 = m1.inverse().unwrap(); + assert!(m1.pre_mul(&m2).approx_eq(&Mat::identity())); + } + + #[test] + pub fn test_pre_post() { + let m1 = Matrix2D::identity().post_scaled(1.0, 2.0).post_translated(1.0, 2.0); + let m2 = Matrix2D::identity().pre_translated(1.0, 2.0).pre_scaled(1.0, 2.0); + assert!(m1.approx_eq(&m2)); + } +} \ No newline at end of file diff --git a/src/matrix4d.rs b/src/matrix4d.rs index 489c362..148865b 100644 --- a/src/matrix4d.rs +++ b/src/matrix4d.rs @@ -7,11 +7,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use super::{UnknownUnit, Radians}; use approxeq::ApproxEq; use trig::Trig; -use point::{TypedPoint2D, TypedPoint4D}; +use point::{TypedPoint2D, TypedPoint3D, TypedPoint4D}; use matrix2d::TypedMatrix2D; -use length::UnknownUnit; use scale_factor::ScaleFactor; use num::{One, Zero}; use std::ops::{Add, Mul, Sub, Div, Neg}; @@ -19,6 +19,18 @@ use std::marker::PhantomData; use std::fmt; define_matrix! { + /// A 4 by 4 matrix stored in row-major order in memory, useful to represent + /// 3d transformations. + /// + /// Matrices can be parametrized over the source and destination units, to describe a + /// transformation from a space to another. + /// For example, TypedMatrix4D::transform_point4d + /// takes a TypedPoint4D and returns a TypedPoint4D. + /// + /// Matrices expose a set of convenience methods for pre- and post-transformations. + /// A pre-transformation corresponds to adding an operation that is applied before + /// the rest of the transformation, while a post-transformation adds an operation + /// that is appled after. pub struct TypedMatrix4D { pub m11: T, pub m12: T, pub m13: T, pub m14: T, pub m21: T, pub m22: T, pub m23: T, pub m24: T, @@ -27,11 +39,16 @@ define_matrix! { } } +/// The default 4d matrix type with no units. pub type Matrix4D = TypedMatrix4D; impl TypedMatrix4D { + /// Create a matrix specifying its components in row-major order. + /// + /// For example, the translation terms m41, m42, m43 on the last row with the + /// row-major convention) are the 13rd, 14th and 15th parameters. #[inline] - pub fn new( + pub fn row_major( m11: T, m12: T, m13: T, m14: T, m21: T, m22: T, m23: T, m24: T, m31: T, m32: T, m33: T, m34: T, @@ -45,6 +62,26 @@ impl TypedMatrix4D { _unit: PhantomData, } } + + /// Create a matrix specifying its components in column-major order. + /// + /// For example, the translation terms m41, m42, m43 on the last column with the + /// column-major convention) are the 4th, 8th and 12nd parameters. + #[inline] + pub fn column_major( + m11: T, m21: T, m31: T, m41: T, + m12: T, m22: T, m32: T, m42: T, + m13: T, m23: T, m33: T, m43: T, + m14: T, m24: T, m34: T, m44: T) + -> TypedMatrix4D { + TypedMatrix4D { + m11: m11, m12: m12, m13: m13, m14: m14, + m21: m21, m22: m22, m23: m23, m24: m24, + m31: m31, m32: m32, m33: m33, m34: m34, + m41: m41, m42: m42, m43: m43, m44: m44, + _unit: PhantomData, + } + } } impl TypedMatrix4D @@ -59,10 +96,12 @@ where T: Copy + Clone + Trig + One + Zero { + /// Create a 4 by 4 matrix representing a 2d transformation, specifying its components + /// in row-major order. #[inline] - pub fn new_2d(m11: T, m12: T, m21: T, m22: T, m41: T, m42: T) -> TypedMatrix4D { + pub fn row_major_2d(m11: T, m12: T, m21: T, m22: T, m41: T, m42: T) -> TypedMatrix4D { let (_0, _1): (T, T) = (Zero::zero(), One::one()); - TypedMatrix4D::new( + TypedMatrix4D::row_major( m11, m12, _0, _0, m21, m22, _0, _0, _0, _0, _1, _0, @@ -70,6 +109,7 @@ where T: Copy + Clone + ) } + /// Create an orthogonal projection matrix. pub fn ortho(left: T, right: T, bottom: T, top: T, near: T, far: T) -> TypedMatrix4D { @@ -79,31 +119,18 @@ where T: Copy + Clone + let (_0, _1): (T, T) = (Zero::zero(), One::one()); let _2 = _1 + _1; - TypedMatrix4D::new(_2 / (right - left), - _0, - _0, - _0, - - _0, - _2 / (top - bottom), - _0, - _0, - - _0, - _0, - -_2 / (far - near), - _0, - - tx, - ty, - tz, - _1) + TypedMatrix4D::row_major( + _2 / (right - left), _0 , _0 , _0, + _0 , _2 / (top - bottom), _0 , _0, + _0 , _0 , -_2 / (far - near), _0, + tx , ty , tz , _1 + ) } #[inline] pub fn identity() -> TypedMatrix4D { let (_0, _1): (T, T) = (Zero::zero(), One::one()); - TypedMatrix4D::new( + TypedMatrix4D::row_major( _1, _0, _0, _0, _0, _1, _0, _0, _0, _0, _1, _0, @@ -111,8 +138,9 @@ where T: Copy + Clone + ) } - - // See https://drafts.csswg.org/css-transforms/#2d-matrix + /// Returns true if this matrix can be represented with a TypedMatrix2D. + /// + /// See https://drafts.csswg.org/css-transforms/#2d-matrix #[inline] pub fn is_2d(&self) -> bool { let (_0, _1): (T, T) = (Zero::zero(), One::one()); @@ -123,8 +151,12 @@ where T: Copy + Clone + self.m33 == _1 && self.m44 == _1 } + /// Create a 2D matrix picking the relevent terms from this matrix. + /// + /// This method assumes that self represents a 2d transformation, callers + /// should check that self.is_2d() returns true beforehand. pub fn to_2d(&self) -> TypedMatrix2D { - TypedMatrix2D::new( + TypedMatrix2D::row_major( self.m11, self.m12, self.m21, self.m22, self.m41, self.m42 @@ -142,8 +174,21 @@ where T: Copy + Clone + self.m43.approx_eq(&other.m43) && self.m44.approx_eq(&other.m44) } - pub fn to(&self) -> TypedMatrix4D { - TypedMatrix4D::new( + /// Returns the same matrix with a different destination unit. + #[inline] + pub fn with_destination(&self) -> TypedMatrix4D { + TypedMatrix4D::row_major( + self.m11, self.m12, self.m13, self.m14, + self.m21, self.m22, self.m23, self.m24, + self.m31, self.m32, self.m33, self.m34, + self.m41, self.m42, self.m43, self.m44, + ) + } + + /// Returns the same matrix with a different source unit. + #[inline] + pub fn with_source(&self) -> TypedMatrix4D { + TypedMatrix4D::row_major( self.m11, self.m12, self.m13, self.m14, self.m21, self.m22, self.m23, self.m24, self.m31, self.m32, self.m33, self.m34, @@ -151,37 +196,46 @@ where T: Copy + Clone + ) } - pub fn mul(&self, mat: &TypedMatrix4D) -> TypedMatrix4D { - TypedMatrix4D::new( - mat.m11*self.m11 + mat.m12*self.m21 + mat.m13*self.m31 + mat.m14*self.m41, - mat.m11*self.m12 + mat.m12*self.m22 + mat.m13*self.m32 + mat.m14*self.m42, - mat.m11*self.m13 + mat.m12*self.m23 + mat.m13*self.m33 + mat.m14*self.m43, - mat.m11*self.m14 + mat.m12*self.m24 + mat.m13*self.m34 + mat.m14*self.m44, - mat.m21*self.m11 + mat.m22*self.m21 + mat.m23*self.m31 + mat.m24*self.m41, - mat.m21*self.m12 + mat.m22*self.m22 + mat.m23*self.m32 + mat.m24*self.m42, - mat.m21*self.m13 + mat.m22*self.m23 + mat.m23*self.m33 + mat.m24*self.m43, - mat.m21*self.m14 + mat.m22*self.m24 + mat.m23*self.m34 + mat.m24*self.m44, - mat.m31*self.m11 + mat.m32*self.m21 + mat.m33*self.m31 + mat.m34*self.m41, - mat.m31*self.m12 + mat.m32*self.m22 + mat.m33*self.m32 + mat.m34*self.m42, - mat.m31*self.m13 + mat.m32*self.m23 + mat.m33*self.m33 + mat.m34*self.m43, - mat.m31*self.m14 + mat.m32*self.m24 + mat.m33*self.m34 + mat.m34*self.m44, - mat.m41*self.m11 + mat.m42*self.m21 + mat.m43*self.m31 + mat.m44*self.m41, - mat.m41*self.m12 + mat.m42*self.m22 + mat.m43*self.m32 + mat.m44*self.m42, - mat.m41*self.m13 + mat.m42*self.m23 + mat.m43*self.m33 + mat.m44*self.m43, - mat.m41*self.m14 + mat.m42*self.m24 + mat.m43*self.m34 + mat.m44*self.m44 + /// Returns the multiplication of the two matrices such that mat's transformation + /// applies after self's transformation. + pub fn post_mul(&self, mat: &TypedMatrix4D) -> TypedMatrix4D { + TypedMatrix4D::row_major( + self.m11 * mat.m11 + self.m12 * mat.m21 + self.m13 * mat.m31 + self.m14 * mat.m41, + self.m21 * mat.m11 + self.m22 * mat.m21 + self.m23 * mat.m31 + self.m24 * mat.m41, + self.m31 * mat.m11 + self.m32 * mat.m21 + self.m33 * mat.m31 + self.m34 * mat.m41, + self.m41 * mat.m11 + self.m42 * mat.m21 + self.m43 * mat.m31 + self.m44 * mat.m41, + self.m11 * mat.m12 + self.m12 * mat.m22 + self.m13 * mat.m32 + self.m14 * mat.m42, + self.m21 * mat.m12 + self.m22 * mat.m22 + self.m23 * mat.m32 + self.m24 * mat.m42, + self.m31 * mat.m12 + self.m32 * mat.m22 + self.m33 * mat.m32 + self.m34 * mat.m42, + self.m41 * mat.m12 + self.m42 * mat.m22 + self.m43 * mat.m32 + self.m44 * mat.m42, + self.m11 * mat.m13 + self.m12 * mat.m23 + self.m13 * mat.m33 + self.m14 * mat.m43, + self.m21 * mat.m13 + self.m22 * mat.m23 + self.m23 * mat.m33 + self.m24 * mat.m43, + self.m31 * mat.m13 + self.m32 * mat.m23 + self.m33 * mat.m33 + self.m34 * mat.m43, + self.m41 * mat.m13 + self.m42 * mat.m23 + self.m43 * mat.m33 + self.m44 * mat.m43, + self.m11 * mat.m14 + self.m12 * mat.m24 + self.m13 * mat.m34 + self.m14 * mat.m44, + self.m21 * mat.m14 + self.m22 * mat.m24 + self.m23 * mat.m34 + self.m24 * mat.m44, + self.m31 * mat.m14 + self.m32 * mat.m24 + self.m33 * mat.m34 + self.m34 * mat.m44, + self.m41 * mat.m14 + self.m42 * mat.m24 + self.m43 * mat.m34 + self.m44 * mat.m44, ) } - pub fn invert(&self) -> TypedMatrix4D { + /// Returns the multiplication of the two matrices such that mat's transformation + /// applies before self's transformation. + pub fn pre_mul(&self, mat: &TypedMatrix4D) -> TypedMatrix4D { + mat.post_mul(self) + } + + /// Returns the inverse matrix if possible. + pub fn inverse(&self) -> Option> { let det = self.determinant(); if det == Zero::zero() { - return TypedMatrix4D::identity(); + return None; } // todo(gw): this could be made faster by special casing // for simpler matrix types. - let m = TypedMatrix4D::new( + let m = TypedMatrix4D::row_major( self.m23*self.m34*self.m42 - self.m24*self.m33*self.m42 + self.m24*self.m32*self.m43 - self.m22*self.m34*self.m43 - self.m23*self.m32*self.m44 + self.m22*self.m33*self.m44, @@ -248,9 +302,10 @@ where T: Copy + Clone + ); let _1: T = One::one(); - m.mul_s(_1 / det) + Some(m.mul_s(_1 / det)) } + /// Compute the determinant of the matrix. pub fn determinant(&self) -> T { self.m14 * self.m23 * self.m32 * self.m41 - self.m13 * self.m24 * self.m32 * self.m41 - @@ -278,8 +333,9 @@ where T: Copy + Clone + self.m11 * self.m22 * self.m33 * self.m44 } + /// Multiplies all of the matrix's component by a scalar and returns the result. pub fn mul_s(&self, x: T) -> TypedMatrix4D { - TypedMatrix4D::new( + TypedMatrix4D::row_major( self.m11 * x, self.m12 * x, self.m13 * x, self.m14 * x, self.m21 * x, self.m22 * x, self.m23 * x, self.m24 * x, self.m31 * x, self.m32 * x, self.m33 * x, self.m34 * x, @@ -287,26 +343,30 @@ where T: Copy + Clone + ) } + /// Convenience function to create a scale matrix from a ScaleFactor. pub fn from_scale_factor(scale: ScaleFactor) -> TypedMatrix4D { TypedMatrix4D::create_scale(scale.get(), scale.get(), scale.get()) } - pub fn scale(&self, x: T, y: T, z: T) -> TypedMatrix4D { - TypedMatrix4D::new( - self.m11 * x, self.m12, self.m13, self.m14, - self.m21 , self.m22 * y, self.m23, self.m24, - self.m31 , self.m32, self.m33 * z, self.m34, - self.m41 , self.m42, self.m43, self.m44 - ) + /// Returns the given 2d point transformed by this matrix. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. + #[inline] + pub fn transform_point(&self, p: &TypedPoint2D) -> TypedPoint2D { + self.transform_point4d(&TypedPoint4D::new(p.x, p.y, Zero::zero(), One::one())).to_2d() } - /// Returns the given point transformed by this matrix. + /// Returns the given 3d point transformed by this matrix. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. #[inline] - pub fn transform_point(&self, p: &TypedPoint2D) -> TypedPoint2D { - TypedPoint2D::new(p.x * self.m11 + p.y * self.m21 + self.m41, - p.x * self.m12 + p.y * self.m22 + self.m42) + pub fn transform_point3d(&self, p: &TypedPoint3D) -> TypedPoint3D { + self.transform_point4d(&TypedPoint4D::new(p.x, p.y, p.z, One::one())).to_3d() } + /// Returns the given 4d point transformed by this matrix. + /// + /// The input point must be use the unit Src, and the returned point has the unit Dst. #[inline] pub fn transform_point4d(&self, p: &TypedPoint4D) -> TypedPoint4D { let x = p.x * self.m11 + p.y * self.m21 + p.z * self.m31 + self.m41; @@ -316,14 +376,10 @@ where T: Copy + Clone + TypedPoint4D::new(x, y, z, w) } - pub fn translate(&self, x: T, y: T, z: T) -> TypedMatrix4D { - self.mul(&TypedMatrix4D::create_translation(x, y, z)) - } - /// Create a 3d translation matrix pub fn create_translation(x: T, y: T, z: T) -> TypedMatrix4D { let (_0, _1): (T, T) = (Zero::zero(), One::one()); - TypedMatrix4D::new( + TypedMatrix4D::row_major( _1, _0, _0, _0, _0, _1, _0, _0, _0, _0, _1, _0, @@ -331,10 +387,20 @@ where T: Copy + Clone + ) } + /// Returns a matrix with a translation applied before self's transformation. + pub fn pre_translated(&self, x: T, y: T, z: T) -> TypedMatrix4D { + self.pre_mul(&TypedMatrix4D::create_translation(x, y, z)) + } + + /// Returns a matrix with a translation applied after self's transformation. + pub fn post_translated(&self, x: T, y: T, z: T) -> TypedMatrix4D { + self.post_mul(&TypedMatrix4D::create_translation(x, y, z)) + } + /// Create a 3d scale matrix pub fn create_scale(x: T, y: T, z: T) -> TypedMatrix4D { let (_0, _1): (T, T) = (Zero::zero(), One::one()); - TypedMatrix4D::new( + TypedMatrix4D::row_major( x, _0, _0, _0, _0, y, _0, _0, _0, _0, z, _0, @@ -342,9 +408,24 @@ where T: Copy + Clone + ) } + /// Returns a matrix with a scale applied before self's transformation. + pub fn pre_scaled(&self, x: T, y: T, z: T) -> TypedMatrix4D { + TypedMatrix4D::row_major( + self.m11 * x, self.m12, self.m13, self.m14, + self.m21 , self.m22 * y, self.m23, self.m24, + self.m31 , self.m32, self.m33 * z, self.m34, + self.m41 , self.m42, self.m43, self.m44 + ) + } + + /// Returns a matrix with a scale applied after self's transformation. + pub fn post_scaled(&self, x: T, y: T, z: T) -> TypedMatrix4D { + self.post_mul(&TypedMatrix4D::create_scale(x, y, z)) + } + /// Create a 3d rotation matrix from an angle / axis. /// The supplied axis must be normalized. - pub fn create_rotation(x: T, y: T, z: T, theta: T) -> TypedMatrix4D { + pub fn create_rotation(x: T, y: T, z: T, theta: Radians) -> TypedMatrix4D { let (_0, _1): (T, T) = (Zero::zero(), One::one()); let _2 = _1 + _1; @@ -352,11 +433,11 @@ where T: Copy + Clone + let yy = y * y; let zz = z * z; - let half_theta = theta / _2; + let half_theta = theta.get() / _2; let sc = half_theta.sin() * half_theta.cos(); let sq = half_theta.sin() * half_theta.sin(); - TypedMatrix4D::new( + TypedMatrix4D::row_major( _1 - _2 * (yy + zz) * sq, _2 * (x * y * sq - z * sc), _2 * (x * z * sq + y * sc), @@ -379,29 +460,46 @@ where T: Copy + Clone + ) } + /// Returns a matrix with a rotation applied after self's transformation. + pub fn post_rotated(&self, x: T, y: T, z: T, theta: Radians) -> TypedMatrix4D { + self.post_mul(&TypedMatrix4D::create_rotation(x, y, z, theta)) + } + + /// Returns a matrix with a rotation applied before self's transformation. + pub fn pre_rotated(&self, x: T, y: T, z: T, theta: Radians) -> TypedMatrix4D { + self.pre_mul(&TypedMatrix4D::create_rotation(x, y, z, theta)) + } + /// Create a 2d skew matrix. - /// https://drafts.csswg.org/css-transforms/#funcdef-skew - pub fn create_skew(alpha: T, beta: T) -> TypedMatrix4D { + /// + /// See https://drafts.csswg.org/css-transforms/#funcdef-skew + pub fn create_skew(alpha: Radians, beta: Radians) -> TypedMatrix4D { let (_0, _1): (T, T) = (Zero::zero(), One::one()); - let (sx, sy) = (beta.tan(), alpha.tan()); - TypedMatrix4D::new(_1, sx, _0, _0, - sy, _1, _0, _0, - _0, _0, _1, _0, - _0, _0, _0, _1) + let (sx, sy) = (beta.get().tan(), alpha.get().tan()); + TypedMatrix4D::row_major( + _1, sx, _0, _0, + sy, _1, _0, _0, + _0, _0, _1, _0, + _0, _0, _0, _1 + ) } /// Create a simple perspective projection matrix pub fn create_perspective(d: T) -> TypedMatrix4D { let (_0, _1): (T, T) = (Zero::zero(), One::one()); - TypedMatrix4D::new(_1, _0, _0, _0, - _0, _1, _0, _0, - _0, _0, _1, -_1 / d, - _0, _0, _0, _1) + TypedMatrix4D::row_major( + _1, _0, _0, _0, + _0, _1, _0, _0, + _0, _0, _1, -_1 / d, + _0, _0, _0, _1 + ) } } impl TypedMatrix4D { - pub fn to_array(&self) -> [T; 16] { + /// Returns an array containing this matrix's terms in row-major order (the order + /// in which the matrix is actually laid out in memory). + pub fn to_row_major_array(&self) -> [T; 16] { [ self.m11, self.m12, self.m13, self.m14, self.m21, self.m22, self.m23, self.m24, @@ -409,30 +507,72 @@ impl TypedMatrix4D { self.m41, self.m42, self.m43, self.m44 ] } + + /// Returns an array containing this matrix's terms in column-major order. + pub fn to_column_major_array(&self) -> [T; 16] { + [ + self.m11, self.m21, self.m31, self.m41, + self.m12, self.m22, self.m32, self.m42, + self.m13, self.m23, self.m33, self.m43, + self.m14, self.m24, self.m34, self.m44 + ] + } + + /// Returns an array containing this matrix's 4 rows in (in row-major order) + /// as arrays. + /// + /// This is a convenience method to interface with other libraries like glium. + pub fn to_row_arrays(&self) -> [[T; 4];4] { + [ + [self.m11, self.m12, self.m13, self.m14], + [self.m21, self.m22, self.m23, self.m24], + [self.m31, self.m32, self.m33, self.m34], + [self.m41, self.m42, self.m43, self.m44] + ] + } + + /// Returns an array containing this matrix's 4 columns in (in row-major order, + /// or 4 rows in column-major order) as arrays. + /// + /// This is a convenience method to interface with other libraries like glium. + pub fn to_column_arrays(&self) -> [[T; 4]; 4] { + [ + [self.m11, self.m21, self.m31, self.m41], + [self.m12, self.m22, self.m32, self.m42], + [self.m13, self.m23, self.m33, self.m43], + [self.m14, self.m24, self.m34, self.m44] + ] + } } impl fmt::Debug for TypedMatrix4D { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_array().fmt(f) + self.to_row_major_array().fmt(f) } } #[cfg(test)] mod tests { use point::Point2D; + use Radians; use super::*; type Mf32 = Matrix4D; + // For convenience. + fn rad(v: f32) -> Radians { Radians::new(v) } + #[test] pub fn test_ortho() { let (left, right, bottom, top) = (0.0f32, 1.0f32, 0.1f32, 1.0f32); let (near, far) = (-1.0f32, 1.0f32); let result = Mf32::ortho(left, right, bottom, top, near, far); - let expected = Mf32::new(2.0, 0.0, 0.0, 0.0, - 0.0, 2.22222222, 0.0, 0.0, - 0.0, 0.0, -1.0, 0.0, - -1.0, -1.22222222, -0.0, 1.0); + let expected = Mf32::row_major( + 2.0, 0.0, 0.0, 0.0, + 0.0, 2.22222222, 0.0, 0.0, + 0.0, 0.0, -1.0, 0.0, + -1.0, -1.22222222, -0.0, 1.0 + ); debug!("result={:?} expected={:?}", result, expected); assert!(result.approx_eq(&expected)); } @@ -440,53 +580,55 @@ mod tests { #[test] pub fn test_is_2d() { assert!(Mf32::identity().is_2d()); - assert!(Mf32::create_rotation(0.0, 0.0, 1.0, 0.7854).is_2d()); - assert!(!Mf32::create_rotation(0.0, 1.0, 0.0, 0.7854).is_2d()); + assert!(Mf32::create_rotation(0.0, 0.0, 1.0, rad(0.7854)).is_2d()); + assert!(!Mf32::create_rotation(0.0, 1.0, 0.0, rad(0.7854)).is_2d()); } #[test] - pub fn test_new_2d() { - let m1 = Mf32::new_2d(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); - let m2 = Mf32::new(1.0, 2.0, 0.0, 0.0, - 3.0, 4.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 5.0, 6.0, 0.0, 1.0); + pub fn test_row_major_2d() { + let m1 = Mf32::row_major_2d(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); + let m2 = Mf32::row_major( + 1.0, 2.0, 0.0, 0.0, + 3.0, 4.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 5.0, 6.0, 0.0, 1.0 + ); assert_eq!(m1, m2); } #[test] - pub fn test_invert_simple() { + pub fn test_inverse_simple() { let m1 = Mf32::identity(); - let m2 = m1.invert(); + let m2 = m1.inverse().unwrap(); assert!(m1.approx_eq(&m2)); } #[test] - pub fn test_invert_scale() { + pub fn test_inverse_scale() { let m1 = Mf32::create_scale(1.5, 0.3, 2.1); - let m2 = m1.invert(); - assert!(m1.mul(&m2).approx_eq(&Mf32::identity())); + let m2 = m1.inverse().unwrap(); + assert!(m1.pre_mul(&m2).approx_eq(&Mf32::identity())); } #[test] - pub fn test_invert_translate() { + pub fn test_inverse_translate() { let m1 = Mf32::create_translation(-132.0, 0.3, 493.0); - let m2 = m1.invert(); - assert!(m1.mul(&m2).approx_eq(&Mf32::identity())); + let m2 = m1.inverse().unwrap(); + assert!(m1.pre_mul(&m2).approx_eq(&Mf32::identity())); } #[test] - pub fn test_invert_rotate() { - let m1 = Mf32::create_rotation(0.0, 1.0, 0.0, 1.57); - let m2 = m1.invert(); - assert!(m1.mul(&m2).approx_eq(&Mf32::identity())); + pub fn test_inverse_rotate() { + let m1 = Mf32::create_rotation(0.0, 1.0, 0.0, rad(1.57)); + let m2 = m1.inverse().unwrap(); + assert!(m1.pre_mul(&m2).approx_eq(&Mf32::identity())); } #[test] - pub fn test_invert_transform_point_2d() { + pub fn test_inverse_transform_point_2d() { let m1 = Mf32::create_translation(100.0, 200.0, 0.0); - let m2 = m1.invert(); - assert!(m1.mul(&m2).approx_eq(&Mf32::identity())); + let m2 = m1.inverse().unwrap(); + assert!(m1.pre_mul(&m2).approx_eq(&Mf32::identity())); let p1 = Point2D::new(1000.0, 2000.0); let p2 = m1.transform_point(&p1); @@ -495,4 +637,11 @@ mod tests { let p3 = m2.transform_point(&p2); assert!(p3.eq(&p1)); } + + #[test] + pub fn test_pre_post() { + let m1 = Matrix4D::identity().post_scaled(1.0, 2.0, 3.0).post_translated(1.0, 2.0, 3.0); + let m2 = Matrix4D::identity().pre_translated(1.0, 2.0, 3.0).pre_scaled(1.0, 2.0, 3.0); + assert!(m1.approx_eq(&m2)); + } } diff --git a/src/num.rs b/src/num.rs index 336a3de..b612624 100644 --- a/src/num.rs +++ b/src/num.rs @@ -27,3 +27,40 @@ impl One for T { fn one() -> T { num_traits::One::one() } } +pub trait Round : Copy { fn round(self) -> Self; } +pub trait Floor : Copy { fn floor(self) -> Self; } +pub trait Ceil : Copy { fn ceil(self) -> Self; } + +impl Round for f32 { fn round(self) -> Self { self.round() } } +impl Round for f64 { fn round(self) -> Self { self.round() } } +impl Round for i16 { fn round(self) -> Self { self } } +impl Round for u16 { fn round(self) -> Self { self } } +impl Round for i32 { fn round(self) -> Self { self } } +impl Round for i64 { fn round(self) -> Self { self } } +impl Round for u32 { fn round(self) -> Self { self } } +impl Round for u64 { fn round(self) -> Self { self } } +impl Round for usize { fn round(self) -> Self { self } } +impl Round for isize { fn round(self) -> Self { self } } + +impl Floor for f32 { fn floor(self) -> Self { self.floor() } } +impl Floor for f64 { fn floor(self) -> Self { self.floor() } } +impl Floor for i16 { fn floor(self) -> Self { self } } +impl Floor for u16 { fn floor(self) -> Self { self } } +impl Floor for i32 { fn floor(self) -> Self { self } } +impl Floor for i64 { fn floor(self) -> Self { self } } +impl Floor for u32 { fn floor(self) -> Self { self } } +impl Floor for u64 { fn floor(self) -> Self { self } } +impl Floor for usize { fn floor(self) -> Self { self } } +impl Floor for isize { fn floor(self) -> Self { self } } + +impl Ceil for f32 { fn ceil(self) -> Self { self.ceil() } } +impl Ceil for f64 { fn ceil(self) -> Self { self.ceil() } } +impl Ceil for i16 { fn ceil(self) -> Self { self } } +impl Ceil for u16 { fn ceil(self) -> Self { self } } +impl Ceil for i32 { fn ceil(self) -> Self { self } } +impl Ceil for i64 { fn ceil(self) -> Self { self } } +impl Ceil for u32 { fn ceil(self) -> Self { self } } +impl Ceil for u64 { fn ceil(self) -> Self { self } } +impl Ceil for usize { fn ceil(self) -> Self { self } } +impl Ceil for isize { fn ceil(self) -> Self { self } } + diff --git a/src/point.rs b/src/point.rs index f2afa64..0cec438 100644 --- a/src/point.rs +++ b/src/point.rs @@ -7,10 +7,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use length::{Length, UnknownUnit}; +use super::UnknownUnit; +use length::Length; use scale_factor::ScaleFactor; use size::TypedSize2D; -use num::Zero; +use num::*; use num_traits::{Float, NumCast}; use std::fmt; @@ -18,6 +19,7 @@ use std::ops::{Add, Neg, Mul, Sub, Div}; use std::marker::PhantomData; define_matrix! { + /// A 2d Point tagged with a unit. #[derive(RustcDecodable, RustcEncodable)] pub struct TypedPoint2D { pub x: T, @@ -25,9 +27,14 @@ define_matrix! { } } +/// Default 2d point type with no unit. +/// +/// Point2D provides the same methods as TypedPoint2D. pub type Point2D = TypedPoint2D; -impl TypedPoint2D { +impl TypedPoint2D { + /// Constructor, setting all components to zero. + #[inline] pub fn zero() -> TypedPoint2D { TypedPoint2D::new(Zero::zero(), Zero::zero()) } @@ -45,44 +52,68 @@ impl fmt::Display for TypedPoint2D { } } -impl TypedPoint2D { +impl TypedPoint2D { + /// Constructor taking scalar values directly. + #[inline] pub fn new(x: T, y: T) -> TypedPoint2D { TypedPoint2D { x: x, y: y, _unit: PhantomData } } -} -impl TypedPoint2D { + /// Constructor taking properly typed Lengths instead of scalar values. + #[inline] pub fn from_lengths(x: Length, y: Length) -> TypedPoint2D { - TypedPoint2D::new(x.get(), y.get()) + TypedPoint2D::new(x.0, y.0) } -} -impl TypedPoint2D { - pub fn x_typed(&self) -> Length { Length::new(self.x.clone()) } - pub fn y_typed(&self) -> Length { Length::new(self.y.clone()) } + /// Returns self.x as a Length carrying the unit. + #[inline] + pub fn x_typed(&self) -> Length { Length::new(self.x) } + + /// Returns self.y as a Length carrying the unit. + #[inline] + pub fn y_typed(&self) -> Length { Length::new(self.y) } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Point2D { + TypedPoint2D::new(self.x, self.y) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: &Point2D) -> TypedPoint2D { + TypedPoint2D::new(p.x, p.y) + } + + #[inline] + pub fn to_array(&self) -> [T; 2] { + [self.x, self.y] + } } impl TypedPoint2D where T: Copy + Mul + Add + Sub { + /// Dot product. #[inline] pub fn dot(self, other: TypedPoint2D) -> T { self.x * other.x + self.y * other.y } + /// Returns the norm of the cross product [self.x, self.y, 0] x [other.x, other.y, 0].. #[inline] pub fn cross(self, other: TypedPoint2D) -> T { self.x * other.y - self.y * other.x } } -impl, U> Add for TypedPoint2D { +impl, U> Add for TypedPoint2D { type Output = TypedPoint2D; fn add(self, other: TypedPoint2D) -> TypedPoint2D { TypedPoint2D::new(self.x + other.x, self.y + other.y) } } -impl, U> Add> for TypedPoint2D { +impl, U> Add> for TypedPoint2D { type Output = TypedPoint2D; fn add(self, other: TypedSize2D) -> TypedPoint2D { TypedPoint2D::new(self.x + other.width, self.y + other.height) @@ -95,14 +126,14 @@ impl, U> TypedPoint2D { } } -impl, U> Sub for TypedPoint2D { +impl, U> Sub for TypedPoint2D { type Output = TypedPoint2D; fn sub(self, other: TypedPoint2D) -> TypedPoint2D { TypedPoint2D::new(self.x - other.x, self.y - other.y) } } -impl , U> Neg for TypedPoint2D { +impl , U> Neg for TypedPoint2D { type Output = TypedPoint2D; #[inline] fn neg(self) -> TypedPoint2D { @@ -152,42 +183,86 @@ impl, U1, U2> Div> for TypedPo } } -// Convenient aliases for TypedPoint2D with typed units +impl TypedPoint2D { + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// For example { -0.1, -0.8 }.round() == { 0.0, -1.0 } + pub fn round(&self) -> Self { + TypedPoint2D::new(self.x.round(), self.y.round()) + } +} -impl TypedPoint2D { - /// Drop the units, preserving only the numeric value. - pub fn to_untyped(&self) -> Point2D { - TypedPoint2D::new(self.x.clone(), self.y.clone()) +impl TypedPoint2D { + /// Rounds each component to the smallest integer equal or greater than the orginal value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// For example { -0.1, -0.8 }.ceil() == { 0.0, 0.0 }. + pub fn ceil(&self) -> Self { + TypedPoint2D::new(self.x.ceil(), self.y.ceil()) } +} - /// Tag a unitless value with units. - pub fn from_untyped(p: &Point2D) -> TypedPoint2D { - TypedPoint2D::new(p.x.clone(), p.y.clone()) +impl TypedPoint2D { + /// Rounds each component to the biggest integer equal or lower than the orginal value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// For example { -0.1, -0.8 }.floor() == { -1.0, -1.0 }. + pub fn floor(&self) -> Self { + TypedPoint2D::new(self.x.floor(), self.y.floor()) } } -impl TypedPoint2D { +impl TypedPoint2D { /// Cast from one numeric representation to another, preserving the units. - pub fn cast(&self) -> Option> { - match (NumCast::from(self.x.clone()), NumCast::from(self.y.clone())) { + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using round(), ceil or floor() before casting. + pub fn cast(&self) -> Option> { + match (NumCast::from(self.x), NumCast::from(self.y)) { (Some(x), Some(y)) => Some(TypedPoint2D::new(x, y)), _ => None } } -} -// Convenience functions for common casts -impl TypedPoint2D { - pub fn as_f32(&self) -> TypedPoint2D { + // Convenience functions for common casts + + /// Cast into an f32 vector. + pub fn to_f32(&self) -> TypedPoint2D { + self.cast().unwrap() + } + + /// Cast into an usize point, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_uint(&self) -> TypedPoint2D { + self.cast().unwrap() + } + + /// Cast into an i32 point, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_i32(&self) -> TypedPoint2D { self.cast().unwrap() } - pub fn as_uint(&self) -> TypedPoint2D { + /// Cast into an i64 point, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_i64(&self) -> TypedPoint2D { self.cast().unwrap() } } define_matrix! { + /// A 3d Point tagged with a unit. #[derive(RustcDecodable, RustcEncodable)] pub struct TypedPoint3D { pub x: T, @@ -196,9 +271,13 @@ define_matrix! { } } +/// Default 3d point type with no unit. +/// +/// Point3D provides the same methods as TypedPoint3D. pub type Point3D = TypedPoint3D; -impl TypedPoint3D { +impl TypedPoint3D { + /// Constructor, setting all copmonents to zero. #[inline] pub fn zero() -> TypedPoint3D { TypedPoint3D::new(Zero::zero(), Zero::zero(), Zero::zero()) @@ -217,29 +296,53 @@ impl fmt::Display for TypedPoint3D { } } -impl TypedPoint3D { +impl TypedPoint3D { + /// Constructor taking scalar values directly. #[inline] pub fn new(x: T, y: T, z: T) -> TypedPoint3D { TypedPoint3D { x: x, y: y, z: z, _unit: PhantomData } } -} -impl TypedPoint3D { + /// Constructor taking properly typed Lengths instead of scalar values. + #[inline] pub fn from_lengths(x: Length, y: Length, z: Length) -> TypedPoint3D { - TypedPoint3D::new(x.get(), y.get(), z.get()) + TypedPoint3D::new(x.0, y.0, z.0) } -} -impl TypedPoint3D { - pub fn x_typed(&self) -> Length { Length::new(self.x.clone()) } - pub fn y_typed(&self) -> Length { Length::new(self.y.clone()) } - pub fn z_typed(&self) -> Length { Length::new(self.z.clone()) } + /// Returns self.x as a Length carrying the unit. + #[inline] + pub fn x_typed(&self) -> Length { Length::new(self.x) } + + /// Returns self.y as a Length carrying the unit. + #[inline] + pub fn y_typed(&self) -> Length { Length::new(self.y) } + + /// Returns self.z as a Length carrying the unit. + #[inline] + pub fn z_typed(&self) -> Length { Length::new(self.z) } + + #[inline] + pub fn to_array(&self) -> [T; 3] { [self.x, self.y, self.z] } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Point3D { + TypedPoint3D::new(self.x, self.y, self.z) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: &Point3D) -> TypedPoint3D { + TypedPoint3D::new(p.x, p.y, p.z) + } } impl + Add + Sub + Copy, U> TypedPoint3D { + + // Dot product. #[inline] pub fn dot(self, other: TypedPoint3D) -> T { self.x * other.x + @@ -247,6 +350,7 @@ impl + self.z * other.z } + // Cross product. #[inline] pub fn cross(self, other: TypedPoint3D) -> TypedPoint3D { TypedPoint3D::new(self.y * other.z - self.z * other.y, @@ -255,7 +359,7 @@ impl + } } -impl, U> Add for TypedPoint3D { +impl, U> Add for TypedPoint3D { type Output = TypedPoint3D; fn add(self, other: TypedPoint3D) -> TypedPoint3D { TypedPoint3D::new(self.x + other.x, @@ -264,7 +368,7 @@ impl, U> Add for TypedPoint3D { } } -impl, U> Sub for TypedPoint3D { +impl, U> Sub for TypedPoint3D { type Output = TypedPoint3D; fn sub(self, other: TypedPoint3D) -> TypedPoint3D { TypedPoint3D::new(self.x - other.x, @@ -273,7 +377,7 @@ impl, U> Sub for TypedPoint3D { } } -impl , U> Neg for TypedPoint3D { +impl , U> Neg for TypedPoint3D { type Output = TypedPoint3D; #[inline] fn neg(self) -> TypedPoint3D { @@ -294,19 +398,85 @@ impl TypedPoint3D { } } -impl TypedPoint3D { - /// Drop the units, preserving only the numeric value. - pub fn to_untyped(&self) -> Point3D { - TypedPoint3D::new(self.x.clone(), self.y.clone(), self.z.clone()) +impl TypedPoint3D { + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + pub fn round(&self) -> Self { + TypedPoint3D::new(self.x.round(), self.y.round(), self.z.round()) } +} - /// Tag a unitless value with units. - pub fn from_untyped(p: &Point3D) -> TypedPoint3D { - TypedPoint3D::new(p.x.clone(), p.y.clone(), p.z.clone()) +impl TypedPoint3D { + /// Rounds each component to the smallest integer equal or greater than the orginal value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + pub fn ceil(&self) -> Self { + TypedPoint3D::new(self.x.ceil(), self.y.ceil(), self.z.ceil()) + } +} + +impl TypedPoint3D { + /// Rounds each component to the biggest integer equal or lower than the orginal value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + pub fn floor(&self) -> Self { + TypedPoint3D::new(self.x.floor(), self.y.floor(), self.z.floor()) + } +} + +impl TypedPoint3D { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using round(), ceil or floor() before casting. + pub fn cast(&self) -> Option> { + match (NumCast::from(self.x), + NumCast::from(self.y), + NumCast::from(self.z)) { + (Some(x), Some(y), Some(z)) => Some(TypedPoint3D::new(x, y, z)), + _ => None + } + } + + // Convenience functions for common casts + + /// Cast into an f32 vector. + pub fn to_f32(&self) -> TypedPoint3D { + self.cast().unwrap() + } + + /// Cast into an usize point, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_uint(&self) -> TypedPoint3D { + self.cast().unwrap() + } + + /// Cast into an i32 point, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_i32(&self) -> TypedPoint3D { + self.cast().unwrap() + } + + /// Cast into an i64 point, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_i64(&self) -> TypedPoint3D { + self.cast().unwrap() } } define_matrix! { + /// A 4d Point tagged with a unit. #[derive(RustcDecodable, RustcEncodable)] pub struct TypedPoint4D { pub x: T, @@ -316,9 +486,13 @@ define_matrix! { } } +/// Default 4d point with no unit. +/// +/// Point4D provides the same methods as TypedPoint4D. pub type Point4D = TypedPoint4D; -impl TypedPoint4D { +impl TypedPoint4D { + /// Constructor, setting all copmonents to zero. #[inline] pub fn zero() -> TypedPoint4D { TypedPoint4D::new(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero()) @@ -337,30 +511,71 @@ impl fmt::Display for TypedPoint4D { } } -impl TypedPoint4D { +impl TypedPoint4D { + /// Constructor taking scalar values directly. #[inline] pub fn new(x: T, y: T, z: T, w: T) -> TypedPoint4D { TypedPoint4D { x: x, y: y, z: z, w: w, _unit: PhantomData } } -} -impl TypedPoint4D { + /// Constructor taking properly typed Lengths instead of scalar values. + #[inline] pub fn from_lengths(x: Length, y: Length, z: Length, w: Length) -> TypedPoint4D { - TypedPoint4D::new(x.get(), y.get(), z.get(), w.get()) + TypedPoint4D::new(x.0, y.0, z.0, w.0) + } + + /// Returns self.x as a Length carrying the unit. + #[inline] + pub fn x_typed(&self) -> Length { Length::new(self.x) } + + /// Returns self.y as a Length carrying the unit. + #[inline] + pub fn y_typed(&self) -> Length { Length::new(self.y) } + + /// Returns self.z as a Length carrying the unit. + #[inline] + pub fn z_typed(&self) -> Length { Length::new(self.z) } + + /// Returns self.w as a Length carrying the unit. + #[inline] + pub fn w_typed(&self) -> Length { Length::new(self.w) } + + /// Drop the units, preserving only the numeric value. + #[inline] + pub fn to_untyped(&self) -> Point4D { + TypedPoint4D::new(self.x, self.y, self.z, self.w) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: &Point4D) -> TypedPoint4D { + TypedPoint4D::new(p.x, p.y, p.z, p.w) + } + + #[inline] + pub fn to_array(&self) -> [T; 4] { + [self.x, self.y, self.z, self.w] } } -impl TypedPoint4D { - pub fn x_typed(&self) -> Length { Length::new(self.x.clone()) } - pub fn y_typed(&self) -> Length { Length::new(self.y.clone()) } - pub fn z_typed(&self) -> Length { Length::new(self.z.clone()) } - pub fn w_typed(&self) -> Length { Length::new(self.w.clone()) } +impl, U> TypedPoint4D { + /// Convert into a 2d point. + #[inline] + pub fn to_2d(self) -> TypedPoint2D { + TypedPoint2D::new(self.x / self.w, self.y / self.w) + } + + /// Convert into a 3d point. + #[inline] + pub fn to_3d(self) -> TypedPoint3D { + TypedPoint3D::new(self.x / self.w, self.y / self.w, self.z / self.w) + } } -impl, U> Add for TypedPoint4D { +impl, U> Add for TypedPoint4D { type Output = TypedPoint4D; fn add(self, other: TypedPoint4D) -> TypedPoint4D { TypedPoint4D::new(self.x + other.x, @@ -370,7 +585,7 @@ impl, U> Add for TypedPoint4D { } } -impl, U> Sub for TypedPoint4D { +impl, U> Sub for TypedPoint4D { type Output = TypedPoint4D; fn sub(self, other: TypedPoint4D) -> TypedPoint4D { TypedPoint4D::new(self.x - other.x, @@ -380,7 +595,7 @@ impl, U> Sub for TypedPoint4D { } } -impl , U> Neg for TypedPoint4D { +impl , U> Neg for TypedPoint4D { type Output = TypedPoint4D; #[inline] fn neg(self) -> TypedPoint4D { @@ -400,15 +615,81 @@ impl TypedPoint4D { } } -impl TypedPoint4D { - /// Drop the units, preserving only the numeric value. - pub fn to_untyped(&self) -> Point4D { - TypedPoint4D::new(self.x.clone(), self.y.clone(), self.z.clone(), self.w.clone()) +impl TypedPoint4D { + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + pub fn round(&self) -> Self { + TypedPoint4D::new(self.x.round(), self.y.round(), self.z.round(), self.w.round()) } +} - /// Tag a unitless value with units. - pub fn from_untyped(p: &Point4D) -> TypedPoint4D { - TypedPoint4D::new(p.x.clone(), p.y.clone(), p.z.clone(), p.w.clone()) +impl TypedPoint4D { + /// Rounds each component to the smallest integer equal or greater than the orginal value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + pub fn ceil(&self) -> Self { + TypedPoint4D::new(self.x.ceil(), self.y.ceil(), self.z.ceil(), self.w.ceil()) + } +} + +impl TypedPoint4D { + /// Rounds each component to the biggest integer equal or lower than the orginal value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + pub fn floor(&self) -> Self { + TypedPoint4D::new(self.x.floor(), self.y.floor(), self.z.floor(), self.w.floor()) + } +} + +impl TypedPoint4D { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using round(), ceil or floor() before casting. + pub fn cast(&self) -> Option> { + match (NumCast::from(self.x), + NumCast::from(self.y), + NumCast::from(self.z), + NumCast::from(self.w)) { + (Some(x), Some(y), Some(z), Some(w)) => Some(TypedPoint4D::new(x, y, z, w)), + _ => None + } + } + + // Convenience functions for common casts + + /// Cast into an f32 vector. + pub fn to_f32(&self) -> TypedPoint4D { + self.cast().unwrap() + } + + /// Cast into an usize point, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_uint(&self) -> TypedPoint4D { + self.cast().unwrap() + } + + /// Cast into an i32 point, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_i32(&self) -> TypedPoint4D { + self.cast().unwrap() + } + + /// Cast into an i64 point, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_i64(&self) -> TypedPoint4D { + self.cast().unwrap() } } diff --git a/src/rect.rs b/src/rect.rs index ebe0d6e..ad56035 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -7,9 +7,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use length::{Length, UnknownUnit}; +use super::UnknownUnit; +use length::Length; use scale_factor::ScaleFactor; -use num::Zero; +use num::*; use point::TypedPoint2D; use size::TypedSize2D; @@ -20,12 +21,14 @@ use std::cmp::PartialOrd; use std::fmt; use std::ops::{Add, Sub, Mul, Div}; +/// A 2d Rectangle optionally tagged with a unit. #[derive(RustcDecodable, RustcEncodable)] pub struct TypedRect { pub origin: TypedPoint2D, pub size: TypedSize2D, } +/// The default rectangle type with no unit. pub type Rect = TypedRect; impl HeapSizeOf for TypedRect { @@ -34,7 +37,7 @@ impl HeapSizeOf for TypedRect { } } -impl Deserialize for TypedRect { +impl Deserialize for TypedRect { fn deserialize(deserializer: &mut D) -> Result where D: Deserializer { @@ -53,10 +56,8 @@ impl Serialize for TypedRect { impl Copy for TypedRect {} -impl Clone for TypedRect { - fn clone(&self) -> TypedRect { - TypedRect::new(self.origin.clone(), self.size.clone()) - } +impl Clone for TypedRect { + fn clone(&self) -> TypedRect { *self } } impl PartialEq> for TypedRect { @@ -80,6 +81,7 @@ impl fmt::Display for TypedRect { } impl TypedRect { + /// Constructor. pub fn new(origin: TypedPoint2D, size: TypedSize2D) -> TypedRect { TypedRect { origin: origin, @@ -89,7 +91,7 @@ impl TypedRect { } impl TypedRect -where T: Copy + Clone + PartialOrd + Add + Sub { +where T: Copy + Clone + Zero + PartialOrd + PartialEq + Add + Sub { #[inline] pub fn intersects(&self, other: &TypedRect) -> bool { self.origin.x < other.origin.x + other.size.width && @@ -153,6 +155,7 @@ where T: Copy + Clone + PartialOrd + Add + Sub { lower_right_y - upper_left.y))) } + /// Translates the rect by a vector. #[inline] pub fn translate(&self, other: &TypedPoint2D) -> TypedRect { TypedRect::new( @@ -161,12 +164,25 @@ where T: Copy + Clone + PartialOrd + Add + Sub { ) } + /// Returns true if this rectangle contains the point. Points are considered + /// in the rectangle 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, other: &TypedPoint2D) -> bool { self.origin.x <= other.x && other.x < self.origin.x + self.size.width && self.origin.y <= other.y && other.y < self.origin.y + self.size.height } + /// Returns true if this rectangle contains the interior of rect. Always + /// returns true if rect is empty, and always returns false if rect is + /// nonempty but this rectangle is empty. + #[inline] + pub fn contains_rect(&self, rect: &TypedRect) -> bool { + rect.is_empty() || + (self.min_x() <= rect.min_x() && rect.max_x() <= self.max_x() && + self.min_y() <= rect.min_y() && rect.max_y() <= self.max_y()) + } + #[inline] pub fn inflate(&self, width: T, height: T) -> TypedRect { TypedRect::new( @@ -201,7 +217,8 @@ where T: Copy + Clone + PartialOrd + Add + Sub { } } -impl + Sub + Zero, U> TypedRect { +impl TypedRect +where T: Copy + Clone + PartialOrd + Add + Sub + Zero { #[inline] pub fn union(&self, other: &TypedRect) -> TypedRect { if self.size == Zero::zero() { @@ -235,7 +252,8 @@ impl TypedRect { } } -impl TypedRect { +impl TypedRect { + /// Constructor, setting all sides to zero. pub fn zero() -> TypedRect { TypedRect::new( TypedPoint2D::zero(), @@ -243,6 +261,7 @@ impl TypedRect { ) } + /// Returns true if the size is zero, regardless of the origin's value. pub fn is_empty(&self) -> bool { self.size.width == Zero::zero() || self.size.height == Zero::zero() } @@ -289,7 +308,7 @@ impl, U1, U2> Div> for TypedRe } } -impl TypedRect { +impl TypedRect { /// Drop the units, preserving only the numeric value. pub fn to_untyped(&self) -> Rect { TypedRect::new(self.origin.to_untyped(), self.size.to_untyped()) @@ -301,9 +320,13 @@ impl TypedRect { } } -impl TypedRect { +impl TypedRect { /// Cast from one numeric representation to another, preserving the units. - pub fn cast(&self) -> Option> { + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always make sense + /// geometrically. Consider using round(), round_in or round_out() before casting. + pub fn cast(&self) -> Option> { match (self.origin.cast(), self.size.cast()) { (Some(origin), Some(size)) => Some(TypedRect::new(origin, size)), _ => None @@ -311,13 +334,70 @@ impl TypedRect { } } +impl + Sub, U> TypedRect { + /// Return a rectangle with edges rounded to integer coordinates, such that + /// the returned rectangle has the same set of pixel centers as the original + /// one. + /// Edges at offset 0.5 round up. + /// Suitable for most places where integral device coordinates + /// are needed, but note that any translation should be applied first to + /// avoid pixel rounding errors. + /// Note that this is *not* rounding to nearest integer if the values are negative. + /// They are always rounding as floor(n + 0.5). + pub fn round(&self) -> Self { + let origin = self.origin.round(); + let size = self.origin.add_size(&self.size).round() - origin; + TypedRect::new(origin, TypedSize2D::new(size.x, size.y)) + } + + /// Return a rectangle with edges rounded to integer coordinates, such that + /// the original rectangle contains the resulting rectangle. + pub fn round_in(&self) -> Self { + let origin = self.origin.ceil(); + let size = self.origin.add_size(&self.size).floor() - origin; + TypedRect::new(origin, TypedSize2D::new(size.x, size.y)) + } + + /// Return a rectangle with edges rounded to integer coordinates, such that + /// the original rectangle is contained in the resulting rectangle. + pub fn round_out(&self) -> Self { + let origin = self.origin.floor(); + let size = self.origin.add_size(&self.size).ceil() - origin; + TypedRect::new(origin, TypedSize2D::new(size.x, size.y)) + } +} + // Convenience functions for common casts -impl TypedRect { - pub fn as_f32(&self) -> TypedRect { +impl TypedRect { + /// Cast into an f32 vector. + pub fn to_f32(&self) -> TypedRect { + self.cast().unwrap() + } + + /// Cast into an usize vector, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), round_in() or round_out() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_uint(&self) -> TypedRect { + self.cast().unwrap() + } + + /// Cast into an i32 vector, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), round_in() or round_out() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_i32(&self) -> TypedRect { self.cast().unwrap() } - pub fn as_uint(&self) -> TypedRect { + /// Cast into an i64 vector, truncating decimals if any. + /// + /// When casting from floating point vectors, it is worth considering whether + /// to round(), round_in() or round_out() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_i64(&self) -> TypedRect { self.cast().unwrap() } } @@ -450,6 +530,18 @@ mod tests { // Points beyond the bottom-left corner. assert!(!r.contains(&Point2D::new(-25, 210))); assert!(!r.contains(&Point2D::new(-15, 220))); + + let r = Rect::new(Point2D::new(-20.0, 15.0), Size2D::new(100.0, 200.0)); + assert!(r.contains_rect(&r)); + assert!(!r.contains_rect(&r.translate(&Point2D::new( 0.1, 0.0)))); + assert!(!r.contains_rect(&r.translate(&Point2D::new(-0.1, 0.0)))); + assert!(!r.contains_rect(&r.translate(&Point2D::new( 0.0, 0.1)))); + assert!(!r.contains_rect(&r.translate(&Point2D::new( 0.0, -0.1)))); + // Empty rectangles are always considered as contained in other rectangles, + // even if their origin is not. + let p = Point2D::new(1.0, 1.0); + assert!(!r.contains(&p)); + assert!(r.contains_rect(&Rect::new(p, Size2D::zero()))); } #[test] @@ -517,4 +609,34 @@ mod tests { assert!(!Rect::new(Point2D::new(10u32, 10u32), Size2D::new(1u32, 1u32)).is_empty()); } + #[test] + fn test_round() { + let mut x = -2.0; + let mut y = -2.0; + let mut w = -2.0; + let mut h = -2.0; + while x < 2.0 { + while y < 2.0 { + while w < 2.0 { + while h < 2.0 { + let rect = Rect::new(Point2D::new(x, y), Size2D::new(w, h)); + + assert!(rect.contains_rect(&rect.round_in())); + assert!(rect.round_in().inflate(1.0, 1.0).contains_rect(&rect)); + + assert!(rect.round_out().contains_rect(&rect)); + assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round_out())); + + assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round())); + assert!(rect.round().inflate(1.0, 1.0).contains_rect(&rect)); + + h += 0.1; + } + w += 0.1; + } + y += 0.1; + } + x += 0.1 + } + } } diff --git a/src/side_offsets.rs b/src/side_offsets.rs index fc42110..7a1a748 100644 --- a/src/side_offsets.rs +++ b/src/side_offsets.rs @@ -10,17 +10,18 @@ //! A group of side offsets, which correspond to top/left/bottom/right for borders, padding, //! and margins in CSS. +use super::UnknownUnit; +use length::Length; use num::Zero; use std::fmt; use std::ops::Add; use std::marker::PhantomData; -use length::{Length, UnknownUnit}; #[cfg(feature = "unstable")] use heapsize::HeapSizeOf; /// A group of side offsets, which correspond to top/left/bottom/right for borders, padding, -/// and margins in CSS. +/// and margins in CSS, optionally tagged with a unit. define_matrix! { pub struct TypedSideOffsets2D { pub top: T, @@ -37,9 +38,11 @@ impl fmt::Debug for TypedSideOffsets2D { } } +/// The default side offset type with no unit. pub type SideOffsets2D = TypedSideOffsets2D; -impl TypedSideOffsets2D { +impl TypedSideOffsets2D { + /// Constructor taking a scalar for each side. pub fn new(top: T, right: T, bottom: T, left: T) -> TypedSideOffsets2D { TypedSideOffsets2D { top: top, @@ -49,18 +52,35 @@ impl TypedSideOffsets2D { _unit: PhantomData, } } -} -impl TypedSideOffsets2D { - pub fn get_top(&self) -> Length { Length::new(self.top.clone()) } - pub fn get_right(&self) -> Length { Length::new(self.right.clone()) } - pub fn get_bottom(&self) -> Length { Length::new(self.bottom.clone()) } - pub fn get_left(&self) -> Length { Length::new(self.left.clone()) } -} + /// Constructor taking a typed Length for each side. + pub fn from_lengths(top: Length, + right: Length, + bottom: Length, + left: Length) -> TypedSideOffsets2D { + TypedSideOffsets2D::new(top.0, right.0, bottom.0, left.0) + } + + /// Access self.top as a typed Length instead of a scalar value. + pub fn top_typed(&self) -> Length { Length::new(self.top) } + + /// Access self.right as a typed Length instead of a scalar value. + pub fn right_typed(&self) -> Length { Length::new(self.right) } + + /// Access self.bottom as a typed Length instead of a scalar value. + pub fn bottom_typed(&self) -> Length { Length::new(self.bottom) } + + /// Access self.left as a typed Length instead of a scalar value. + pub fn left_typed(&self) -> Length { Length::new(self.left) } -impl TypedSideOffsets2D { + /// Constructor setting the same value to all sides, taking a scalar value directly. pub fn new_all_same(all: T) -> TypedSideOffsets2D { - TypedSideOffsets2D::new(all.clone(), all.clone(), all.clone(), all.clone()) + TypedSideOffsets2D::new(all, all, all, all) + } + + /// Constructor setting the same value to all sides, taking a typed Length. + pub fn from_length_all_same(all: Length) -> TypedSideOffsets2D { + TypedSideOffsets2D::new_all_same(all.0) } } @@ -72,9 +92,17 @@ impl TypedSideOffsets2D where T: Add + Copy { pub fn vertical(&self) -> T { self.top + self.bottom } + + pub fn horizontal_typed(&self) -> Length { + Length::new(self.horizontal()) + } + + pub fn vertical_typed(&self) -> Length { + Length::new(self.vertical()) + } } -impl, U> Add for TypedSideOffsets2D { +impl Add for TypedSideOffsets2D where T : Copy + Add { type Output = TypedSideOffsets2D; fn add(self, other: TypedSideOffsets2D) -> TypedSideOffsets2D { TypedSideOffsets2D::new( @@ -86,7 +114,8 @@ impl, U> Add for TypedSideOffsets2D { } } -impl TypedSideOffsets2D { +impl TypedSideOffsets2D { + /// Constructor, setting all sides to zero. pub fn zero() -> TypedSideOffsets2D { TypedSideOffsets2D::new( Zero::zero(), diff --git a/src/size.rs b/src/size.rs index d8a53be..2dbb79d 100644 --- a/src/size.rs +++ b/src/size.rs @@ -7,15 +7,17 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use length::{Length, UnknownUnit}; +use super::UnknownUnit; +use length::Length; use scale_factor::ScaleFactor; -use num::Zero; +use num::*; use num_traits::NumCast; use std::fmt; use std::ops::{Mul, Div}; use std::marker::PhantomData; +/// A 2d size tagged with a unit. define_matrix! { #[derive(RustcDecodable, RustcEncodable)] pub struct TypedSize2D { @@ -24,6 +26,9 @@ define_matrix! { } } +/// Default 2d size type with no unit. +/// +/// Size2D provides the same methods as TypedSize2D. pub type Size2D = TypedSize2D; impl fmt::Debug for TypedSize2D { @@ -39,6 +44,7 @@ impl fmt::Display for TypedSize2D { } impl TypedSize2D { + /// Constructor taing scalar values. pub fn new(width: T, height: T) -> TypedSize2D { TypedSize2D { width: width, @@ -49,11 +55,39 @@ impl TypedSize2D { } impl TypedSize2D { + /// Constructor taing scalar stronlgy typed lengths. pub fn from_lengths(width: Length, height: Length) -> TypedSize2D { TypedSize2D::new(width.get(), height.get()) } } +impl TypedSize2D { + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + pub fn round(&self) -> Self { + TypedSize2D::new(self.width.round(), self.height.round()) + } +} + +impl TypedSize2D { + /// Rounds each component to the smallest integer equal or greater than the orginal value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + pub fn ceil(&self) -> Self { + TypedSize2D::new(self.height.ceil(), self.width.ceil()) + } +} + +impl TypedSize2D { + /// Rounds each component to the biggest integer equal or lower than the orginal value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + pub fn floor(&self) -> Self { + TypedSize2D::new(self.width.floor(), self.height.floor()) + } +} + impl, U> TypedSize2D { pub fn area(&self) -> U { self.width * self.height } } @@ -108,37 +142,73 @@ impl, U1, U2> Div> for TypedSi } } -// Convenient aliases for TypedSize2D with typed units +impl TypedSize2D { + /// Returns self.width as a Length carrying the unit. + #[inline] + pub fn width_typed(&self) -> Length { Length::new(self.width) } + + /// Returns self.height as a Length carrying the unit. + #[inline] + pub fn height_typed(&self) -> Length { Length::new(self.height) } + + #[inline] + pub fn to_array(&self) -> [T; 2] { [self.width, self.height] } -impl TypedSize2D { /// Drop the units, preserving only the numeric value. pub fn to_untyped(&self) -> Size2D { - TypedSize2D::new(self.width.clone(), self.height.clone()) + TypedSize2D::new(self.width, self.height) } /// Tag a unitless value with units. - pub fn from_untyped(p: &Size2D) -> TypedSize2D { - TypedSize2D::new(p.width.clone(), p.height.clone()) + pub fn from_untyped(p: &Size2D) -> TypedSize2D { + TypedSize2D::new(p.width, p.height) } } -impl TypedSize2D { +impl TypedSize2D { /// Cast from one numeric representation to another, preserving the units. - pub fn cast(&self) -> Option> { + /// + /// When casting from floating point to integer coordinates, the decimals are truncated + /// as one would expect from a simple cast, but this behavior does not always marke sense + /// geometrically. Consider using round(), ceil or floor() before casting. + pub fn cast(&self) -> Option> { match (NumCast::from(self.width.clone()), NumCast::from(self.height.clone())) { (Some(w), Some(h)) => Some(TypedSize2D::new(w, h)), _ => None } } -} -// Convenience functions for common casts -impl TypedSize2D { - pub fn as_f32(&self) -> TypedSize2D { + // Convenience functions for common casts + + /// Cast into an f32 size. + pub fn to_f32(&self) -> TypedSize2D { + self.cast().unwrap() + } + + /// Cast into an usize size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_uint(&self) -> TypedSize2D { + self.cast().unwrap() + } + + /// Cast into an i32 size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_i32(&self) -> TypedSize2D { self.cast().unwrap() } - pub fn as_uint(&self) -> TypedSize2D { + /// Cast into an i64 size, truncating decimals if any. + /// + /// When casting from floating point sizes, it is worth considering whether + /// to round(), ceil() or floor() before the cast in order to obtain the desired + /// conversion behavior. + pub fn to_i64(&self) -> TypedSize2D { self.cast().unwrap() } }