From f33d6e2808a31b42bb013adafc967bacba140eca Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Fri, 19 May 2017 12:53:39 +0200 Subject: [PATCH] Add 2d and 3d vector types. --- src/lib.rs | 7 +- src/point.rs | 628 +++++++++------------------------- src/rect.rs | 27 +- src/size.rs | 4 + src/transform2d.rs | 34 +- src/transform3d.rs | 107 ++++-- src/vector.rs | 833 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1115 insertions(+), 525 deletions(-) create mode 100644 src/vector.rs diff --git a/src/lib.rs b/src/lib.rs index 6f12cb01..5cc0a195 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,8 +74,12 @@ pub use transform3d::{Transform3D, TypedTransform3D}; pub use point::{ Point2D, TypedPoint2D, Point3D, TypedPoint3D, - Point4D, TypedPoint4D, }; +pub use vector::{ + Vector2D, TypedVector2D, + Vector3D, TypedVector3D, +}; + pub use rect::{Rect, TypedRect}; pub use side_offsets::{SideOffsets2D, TypedSideOffsets2D}; #[cfg(feature = "unstable")] pub use side_offsets::SideOffsets2DSimdI32; @@ -94,6 +98,7 @@ pub mod scale_factor; pub mod side_offsets; pub mod size; pub mod trig; +pub mod vector; /// The default unit. #[derive(Clone, Copy)] diff --git a/src/point.rs b/src/point.rs index bd6b3b93..919644c5 100644 --- a/src/point.rs +++ b/src/point.rs @@ -14,8 +14,9 @@ use scale_factor::ScaleFactor; use size::TypedSize2D; use num::*; use num_traits::{Float, NumCast}; +use vector::{TypedVector2D, TypedVector3D, vec2, vec3}; use std::fmt; -use std::ops::{Add, Neg, Mul, Sub, Div, AddAssign}; +use std::ops::{Add, Mul, Sub, Div, AddAssign, SubAssign, MulAssign, DivAssign}; use std::marker::PhantomData; define_matrix! { @@ -34,14 +35,19 @@ pub type Point2D = TypedPoint2D; impl TypedPoint2D { /// Constructor, setting all components to zero. #[inline] - pub fn zero() -> TypedPoint2D { - TypedPoint2D::new(Zero::zero(), Zero::zero()) + pub fn origin() -> Self { + point2(Zero::zero(), Zero::zero()) + } + + #[inline] + pub fn zero() -> Self { + Self::origin() } /// Convert into a 3d point. #[inline] pub fn to_3d(&self) -> TypedPoint3D { - TypedPoint3D::new(self.x, self.y, Zero::zero()) + point3(self.x, self.y, Zero::zero()) } } @@ -60,14 +66,22 @@ impl fmt::Display for TypedPoint2D { impl TypedPoint2D { /// Constructor taking scalar values directly. #[inline] - pub fn new(x: T, y: T) -> TypedPoint2D { + pub fn new(x: T, y: T) -> Self { TypedPoint2D { x: x, y: y, _unit: PhantomData } } /// Constructor taking properly typed Lengths instead of scalar values. #[inline] - pub fn from_lengths(x: Length, y: Length) -> TypedPoint2D { - TypedPoint2D::new(x.0, y.0) + pub fn from_lengths(x: Length, y: Length) -> Self { + point2(x.0, y.0) + } + + /// Cast this point into a vector. + /// + /// Equivalent to substracting the origin to this point. + #[inline] + pub fn to_vector(&self) -> TypedVector2D { + vec2(self.x, self.y) } /// Returns self.x as a Length carrying the unit. @@ -81,13 +95,13 @@ impl TypedPoint2D { /// Drop the units, preserving only the numeric value. #[inline] pub fn to_untyped(&self) -> Point2D { - TypedPoint2D::new(self.x, self.y) + point2(self.x, self.y) } /// Tag a unitless value with units. #[inline] - pub fn from_untyped(p: &Point2D) -> TypedPoint2D { - TypedPoint2D::new(p.x, p.y) + pub fn from_untyped(p: &Point2D) -> Self { + point2(p.x, p.y) } #[inline] @@ -96,95 +110,98 @@ impl TypedPoint2D { } } -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].. +impl, U> TypedPoint2D { #[inline] - pub fn cross(self, other: TypedPoint2D) -> T { - self.x * other.y - self.y * other.x - } - - #[inline] - pub fn normalize(self) -> Self where T: Float + ApproxEq { - let dot = self.dot(self); - if dot.approx_eq(&T::zero()) { - self - } else { - self / dot.sqrt() - } + pub fn add_size(&self, other: &TypedSize2D) -> Self { + point2(self.x + other.width, self.y + other.height) } } -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) + #[inline] + fn add(self, other: TypedSize2D) -> Self { + point2(self.x + other.width, self.y + other.height) } } -impl, U> AddAssign for TypedPoint2D { - fn add_assign(&mut self, other: TypedPoint2D){ +impl, U> AddAssign> for TypedPoint2D { + #[inline] + fn add_assign(&mut self, other: TypedVector2D) { *self = *self + other } } -impl, U> Add> for TypedPoint2D { - type Output = TypedPoint2D; - fn add(self, other: TypedSize2D) -> TypedPoint2D { - TypedPoint2D::new(self.x + other.width, self.y + other.height) +impl, U> SubAssign> for TypedPoint2D { + #[inline] + fn sub_assign(&mut self, other: TypedVector2D) { + *self = *self - other } } -impl, U> TypedPoint2D { - pub fn add_size(&self, other: &TypedSize2D) -> TypedPoint2D { - TypedPoint2D::new(self.x + other.width, self.y + other.height) +impl, U> Add> for TypedPoint2D { + type Output = TypedPoint2D; + #[inline] + fn add(self, other: TypedVector2D) -> Self { + point2(self.x + other.x, self.y + other.y) } } impl, U> Sub for TypedPoint2D { - type Output = TypedPoint2D; - fn sub(self, other: TypedPoint2D) -> TypedPoint2D { - TypedPoint2D::new(self.x - other.x, self.y - other.y) + type Output = TypedVector2D; + #[inline] + fn sub(self, other: TypedPoint2D) -> TypedVector2D { + vec2(self.x - other.x, self.y - other.y) } } -impl , U> Neg for TypedPoint2D { - type Output = TypedPoint2D; +impl, U> Sub> for TypedPoint2D { + type Output = Self; #[inline] - fn neg(self) -> TypedPoint2D { - TypedPoint2D::new(-self.x, -self.y) + fn sub(self, other: TypedVector2D) -> Self { + point2(self.x - other.x, self.y - other.y) } } impl TypedPoint2D { - pub fn min(self, other: TypedPoint2D) -> TypedPoint2D { - TypedPoint2D::new(self.x.min(other.x), self.y.min(other.y)) + #[inline] + pub fn min(self, other: TypedPoint2D) -> Self { + point2(self.x.min(other.x), self.y.min(other.y)) } - pub fn max(self, other: TypedPoint2D) -> TypedPoint2D { - TypedPoint2D::new(self.x.max(other.x), self.y.max(other.y)) + #[inline] + pub fn max(self, other: TypedPoint2D) -> Self { + point2(self.x.max(other.x), self.y.max(other.y)) } } impl, U> Mul for TypedPoint2D { - type Output = TypedPoint2D; + type Output = Self; + #[inline] + fn mul(self, scale: T) -> Self { + point2(self.x * scale, self.y * scale) + } +} + +impl, U> MulAssign for TypedPoint2D { #[inline] - fn mul(self, scale: T) -> TypedPoint2D { - TypedPoint2D::new(self.x * scale, self.y * scale) + fn mul_assign(&mut self, scale: T) { + *self = *self * scale } } impl, U> Div for TypedPoint2D { - type Output = TypedPoint2D; + type Output = Self; + #[inline] + fn div(self, scale: T) -> Self { + point2(self.x / scale, self.y / scale) + } +} + +impl, U> DivAssign for TypedPoint2D { #[inline] - fn div(self, scale: T) -> TypedPoint2D { - TypedPoint2D::new(self.x / scale, self.y / scale) + fn div_assign(&mut self, scale: T) { + *self = *self / scale } } @@ -192,7 +209,7 @@ impl, U1, U2> Mul> for TypedPo type Output = TypedPoint2D; #[inline] fn mul(self, scale: ScaleFactor) -> TypedPoint2D { - TypedPoint2D::new(self.x * scale.get(), self.y * scale.get()) + point2(self.x * scale.get(), self.y * scale.get()) } } @@ -200,7 +217,7 @@ impl, U1, U2> Div> for TypedPo type Output = TypedPoint2D; #[inline] fn div(self, scale: ScaleFactor) -> TypedPoint2D { - TypedPoint2D::new(self.x / scale.get(), self.y / scale.get()) + point2(self.x / scale.get(), self.y / scale.get()) } } @@ -209,8 +226,9 @@ impl TypedPoint2D { /// /// This behavior is preserved for negative values (unlike the basic cast). /// For example `{ -0.1, -0.8 }.round() == { 0.0, -1.0 }`. + #[inline] pub fn round(&self) -> Self { - TypedPoint2D::new(self.x.round(), self.y.round()) + point2(self.x.round(), self.y.round()) } } @@ -219,8 +237,9 @@ impl TypedPoint2D { /// /// This behavior is preserved for negative values (unlike the basic cast). /// For example `{ -0.1, -0.8 }.ceil() == { 0.0, 0.0 }`. + #[inline] pub fn ceil(&self) -> Self { - TypedPoint2D::new(self.x.ceil(), self.y.ceil()) + point2(self.x.ceil(), self.y.ceil()) } } @@ -229,8 +248,9 @@ impl TypedPoint2D { /// /// This behavior is preserved for negative values (unlike the basic cast). /// For example `{ -0.1, -0.8 }.floor() == { -1.0, -1.0 }`. + #[inline] pub fn floor(&self) -> Self { - TypedPoint2D::new(self.x.floor(), self.y.floor()) + point2(self.x.floor(), self.y.floor()) } } @@ -240,9 +260,10 @@ impl TypedPoint2D { /// 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. + #[inline] pub fn cast(&self) -> Option> { match (NumCast::from(self.x), NumCast::from(self.y)) { - (Some(x), Some(y)) => Some(TypedPoint2D::new(x, y)), + (Some(x), Some(y)) => Some(point2(x, y)), _ => None } } @@ -250,6 +271,7 @@ impl TypedPoint2D { // Convenience functions for common casts /// Cast into an `f32` point. + #[inline] pub fn to_f32(&self) -> TypedPoint2D { self.cast().unwrap() } @@ -259,6 +281,7 @@ impl TypedPoint2D { /// When casting from floating point points, it is worth considering whether /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain /// the desired conversion behavior. + #[inline] pub fn to_usize(&self) -> TypedPoint2D { self.cast().unwrap() } @@ -268,6 +291,7 @@ impl TypedPoint2D { /// When casting from floating point points, it is worth considering whether /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain /// the desired conversion behavior. + #[inline] pub fn to_i32(&self) -> TypedPoint2D { self.cast().unwrap() } @@ -277,15 +301,16 @@ impl TypedPoint2D { /// When casting from floating point points, it is worth considering whether /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain /// the desired conversion behavior. + #[inline] pub fn to_i64(&self) -> TypedPoint2D { self.cast().unwrap() } } -impl, U> ApproxEq> for TypedPoint2D { +impl, U> ApproxEq for TypedPoint2D { #[inline] fn approx_epsilon() -> Self { - TypedPoint2D::new(T::approx_epsilon(), T::approx_epsilon()) + point2(T::approx_epsilon(), T::approx_epsilon()) } #[inline] @@ -316,8 +341,15 @@ pub type Point3D = TypedPoint3D; impl TypedPoint3D { /// Constructor, setting all copmonents to zero. #[inline] - pub fn zero() -> TypedPoint3D { - TypedPoint3D::new(Zero::zero(), Zero::zero(), Zero::zero()) + pub fn origin() -> Self { + point3(Zero::zero(), Zero::zero(), Zero::zero()) + } +} + +impl TypedPoint3D { + #[inline] + pub fn to_array_4d(&self) -> [T; 4] { + [self.x, self.y, self.z, One::one()] } } @@ -336,14 +368,22 @@ impl fmt::Display for TypedPoint3D { impl TypedPoint3D { /// Constructor taking scalar values directly. #[inline] - pub fn new(x: T, y: T, z: T) -> TypedPoint3D { + pub fn new(x: T, y: T, z: T) -> Self { TypedPoint3D { x: x, y: y, z: z, _unit: PhantomData } } /// Constructor taking properly typed Lengths instead of scalar values. #[inline] - pub fn from_lengths(x: Length, y: Length, z: Length) -> TypedPoint3D { - TypedPoint3D::new(x.0, y.0, z.0) + pub fn from_lengths(x: Length, y: Length, z: Length) -> Self { + point3(x.0, y.0, z.0) + } + + /// Cast this point into a vector. + /// + /// Equivalent to substracting the origin to this point. + #[inline] + pub fn to_vector(&self) -> TypedVector3D { + vec3(self.x, self.y, self.y) } /// Returns self.x as a Length carrying the unit. @@ -364,77 +404,57 @@ impl TypedPoint3D { /// Drop the units, preserving only the numeric value. #[inline] pub fn to_untyped(&self) -> Point3D { - TypedPoint3D::new(self.x, self.y, self.z) + point3(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) + pub fn from_untyped(p: &Point3D) -> Self { + point3(p.x, p.y, p.z) } /// Convert into a 2d point. #[inline] pub fn to_2d(&self) -> TypedPoint2D { - TypedPoint2D::new(self.x, self.y) + point2(self.x, self.y) } } -impl + - Add + - Sub + - Copy, U> TypedPoint3D { - - // Dot product. +impl, U> AddAssign> for TypedPoint3D { #[inline] - pub fn dot(self, other: TypedPoint3D) -> T { - self.x * other.x + - self.y * other.y + - self.z * other.z - } - - // Cross product. - #[inline] - pub fn cross(self, other: TypedPoint3D) -> TypedPoint3D { - TypedPoint3D::new(self.y * other.z - self.z * other.y, - self.z * other.x - self.x * other.z, - self.x * other.y - self.y * other.x) + fn add_assign(&mut self, other: TypedVector3D) { + *self = *self + other } +} +impl, U> SubAssign> for TypedPoint3D { #[inline] - pub fn normalize(self) -> Self where T: Float + ApproxEq { - let dot = self.dot(self); - if dot.approx_eq(&T::zero()) { - self - } else { - self / dot.sqrt() - } + fn sub_assign(&mut self, other: TypedVector3D) { + *self = *self - other } } -impl, U> Add for TypedPoint3D { - type Output = TypedPoint3D; - fn add(self, other: TypedPoint3D) -> TypedPoint3D { - TypedPoint3D::new(self.x + other.x, - self.y + other.y, - self.z + other.z) +impl, U> Add> for TypedPoint3D { + type Output = Self; + #[inline] + fn add(self, other: TypedVector3D) -> Self { + point3(self.x + other.x, self.y + other.y, self.z + other.z) } } impl, U> Sub for TypedPoint3D { - type Output = TypedPoint3D; - fn sub(self, other: TypedPoint3D) -> TypedPoint3D { - TypedPoint3D::new(self.x - other.x, - self.y - other.y, - self.z - other.z) + type Output = TypedVector3D; + #[inline] + fn sub(self, other: Self) -> TypedVector3D { + vec3(self.x - other.x, self.y - other.y, self.z - other.z) } } -impl , U> Neg for TypedPoint3D { - type Output = TypedPoint3D; +impl, U> Sub> for TypedPoint3D { + type Output = Self; #[inline] - fn neg(self) -> TypedPoint3D { - TypedPoint3D::new(-self.x, -self.y, -self.z) + fn sub(self, other: TypedVector3D) -> Self { + point3(self.x - other.x, self.y - other.y, self.z - other.z) } } @@ -442,7 +462,7 @@ impl, U> Mul for TypedPoint3D { type Output = Self; #[inline] fn mul(self, scale: T) -> Self { - Self::new(self.x * scale, self.y * scale, self.z * scale) + point3(self.x * scale, self.y * scale, self.z * scale) } } @@ -450,20 +470,19 @@ impl, U> Div for TypedPoint3D { type Output = Self; #[inline] fn div(self, scale: T) -> Self { - Self::new(self.x / scale, self.y / scale, self.z / scale) + point3(self.x / scale, self.y / scale, self.z / scale) } } impl TypedPoint3D { - pub fn min(self, other: TypedPoint3D) -> TypedPoint3D { - TypedPoint3D::new(self.x.min(other.x), - self.y.min(other.y), - self.z.min(other.z)) + #[inline] + pub fn min(self, other: Self) -> Self { + point3(self.x.min(other.x), self.y.min(other.y), self.z.min(other.z)) } - pub fn max(self, other: TypedPoint3D) -> TypedPoint3D { - TypedPoint3D::new(self.x.max(other.x), self.y.max(other.y), - self.z.max(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)) } } @@ -471,8 +490,9 @@ impl TypedPoint3D { /// Rounds each component to the nearest integer value. /// /// This behavior is preserved for negative values (unlike the basic cast). + #[inline] pub fn round(&self) -> Self { - TypedPoint3D::new(self.x.round(), self.y.round(), self.z.round()) + point3(self.x.round(), self.y.round(), self.z.round()) } } @@ -480,8 +500,9 @@ impl TypedPoint3D { /// Rounds each component to the smallest integer equal or greater than the original value. /// /// This behavior is preserved for negative values (unlike the basic cast). + #[inline] pub fn ceil(&self) -> Self { - TypedPoint3D::new(self.x.ceil(), self.y.ceil(), self.z.ceil()) + point3(self.x.ceil(), self.y.ceil(), self.z.ceil()) } } @@ -489,8 +510,9 @@ impl TypedPoint3D { /// Rounds each component to the biggest integer equal or lower than the original value. /// /// This behavior is preserved for negative values (unlike the basic cast). + #[inline] pub fn floor(&self) -> Self { - TypedPoint3D::new(self.x.floor(), self.y.floor(), self.z.floor()) + point3(self.x.floor(), self.y.floor(), self.z.floor()) } } @@ -500,11 +522,12 @@ impl TypedPoint3D { /// 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. + #[inline] 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)), + (Some(x), Some(y), Some(z)) => Some(point3(x, y, z)), _ => None } } @@ -512,6 +535,7 @@ impl TypedPoint3D { // Convenience functions for common casts /// Cast into an `f32` point. + #[inline] pub fn to_f32(&self) -> TypedPoint3D { self.cast().unwrap() } @@ -521,6 +545,7 @@ impl TypedPoint3D { /// When casting from floating point points, it is worth considering whether /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain /// the desired conversion behavior. + #[inline] pub fn to_usize(&self) -> TypedPoint3D { self.cast().unwrap() } @@ -530,6 +555,7 @@ impl TypedPoint3D { /// When casting from floating point points, it is worth considering whether /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain /// the desired conversion behavior. + #[inline] pub fn to_i32(&self) -> TypedPoint3D { self.cast().unwrap() } @@ -539,15 +565,16 @@ impl TypedPoint3D { /// When casting from floating point points, it is worth considering whether /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain /// the desired conversion behavior. + #[inline] pub fn to_i64(&self) -> TypedPoint3D { self.cast().unwrap() } } -impl, U> ApproxEq> for TypedPoint3D { +impl, U> ApproxEq for TypedPoint3D { #[inline] fn approx_epsilon() -> Self { - TypedPoint3D::new(T::approx_epsilon(), T::approx_epsilon(), T::approx_epsilon()) + point3(T::approx_epsilon(), T::approx_epsilon(), T::approx_epsilon()) } #[inline] @@ -565,239 +592,6 @@ impl, U> ApproxEq> for TypedPoint3D } } -define_matrix! { - /// A 4d Point tagged with a unit. - pub struct TypedPoint4D { - pub x: T, - pub y: T, - pub z: T, - pub w: T, - } -} - -/// Default 4d point with no unit. -/// -/// `Point4D` provides the same methods as `TypedPoint4D`. -pub type Point4D = TypedPoint4D; - -impl TypedPoint4D { - /// Constructor, setting all copmonents to zero. - #[inline] - pub fn zero() -> TypedPoint4D { - TypedPoint4D::new(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero()) - } -} - -impl fmt::Debug for TypedPoint4D { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "({:?},{:?},{:?},{:?})", self.x, self.y, self.z, self.w) - } -} - -impl fmt::Display for TypedPoint4D { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "({},{},{},{})", self.x, self.y, self.z, self.w) - } -} - -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 } - } - - /// 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.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, 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 { - type Output = TypedPoint4D; - fn add(self, other: TypedPoint4D) -> TypedPoint4D { - TypedPoint4D::new(self.x + other.x, - self.y + other.y, - self.z + other.z, - self.w + other.w) - } -} - -impl, U> Sub for TypedPoint4D { - type Output = TypedPoint4D; - fn sub(self, other: TypedPoint4D) -> TypedPoint4D { - TypedPoint4D::new(self.x - other.x, - self.y - other.y, - self.z - other.z, - self.w - other.w) - } -} - -impl , U> Neg for TypedPoint4D { - type Output = TypedPoint4D; - #[inline] - fn neg(self) -> TypedPoint4D { - TypedPoint4D::new(-self.x, -self.y, -self.z, -self.w) - } -} - -impl TypedPoint4D { - pub fn min(self, other: TypedPoint4D) -> TypedPoint4D { - TypedPoint4D::new(self.x.min(other.x), self.y.min(other.y), - self.z.min(other.z), self.w.min(other.w)) - } - - pub fn max(self, other: TypedPoint4D) -> TypedPoint4D { - TypedPoint4D::new(self.x.max(other.x), self.y.max(other.y), - self.z.max(other.z), self.w.max(other.w)) - } -} - -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()) - } -} - -impl TypedPoint4D { - /// Rounds each component to the smallest integer equal or greater than the original 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 original 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` point. - pub fn to_f32(&self) -> TypedPoint4D { - self.cast().unwrap() - } - - /// Cast into an `usize` point, truncating decimals if any. - /// - /// When casting from floating point points, it is worth considering whether - /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain - /// the desired conversion behavior. - pub fn to_usize(&self) -> TypedPoint4D { - self.cast().unwrap() - } - - /// Cast into an `i32` point, truncating decimals if any. - /// - /// When casting from floating point points, 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 points, 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() - } -} - -impl, U> ApproxEq for TypedPoint4D { - fn approx_epsilon() -> T { - T::approx_epsilon() - } - - fn approx_eq_eps(&self, other: &Self, approx_epsilon: &T) -> bool { - self.x.approx_eq_eps(&other.x, approx_epsilon) - && self.y.approx_eq_eps(&other.y, approx_epsilon) - && self.z.approx_eq_eps(&other.z, approx_epsilon) - && self.w.approx_eq_eps(&other.w, approx_epsilon) - } - - fn approx_eq(&self, other: &Self) -> bool { - self.approx_eq_eps(&other, &Self::approx_epsilon()) - } -} pub fn point2(x: T, y: T) -> TypedPoint2D { TypedPoint2D::new(x, y) @@ -807,10 +601,6 @@ pub fn point3(x: T, y: T, z: T) -> TypedPoint3D { TypedPoint3D::new(x, y, z) } -pub fn point4(x: T, y: T, z: T, w: T) -> TypedPoint4D { - TypedPoint4D::new(x, y, z, w) -} - #[cfg(test)] mod point2d { use super::Point2D; @@ -824,31 +614,6 @@ mod point2d { assert_eq!(result, Point2D::new(15.0, 25.0)); } - #[test] - pub fn test_dot() { - let p1: Point2D = Point2D::new(2.0, 7.0); - let p2: Point2D = Point2D::new(13.0, 11.0); - assert_eq!(p1.dot(p2), 103.0); - } - - #[test] - pub fn test_cross() { - let p1: Point2D = Point2D::new(4.0, 7.0); - let p2: Point2D = Point2D::new(13.0, 8.0); - let r = p1.cross(p2); - assert_eq!(r, -59.0); - } - - #[test] - pub fn test_normalize() { - let p0: Point2D = Point2D::zero(); - let p1: Point2D = Point2D::new(4.0, 0.0); - let p2: Point2D = Point2D::new(3.0, -4.0); - assert_eq!(p0.normalize(), p0); - assert_eq!(p1.normalize(), Point2D::new(1.0, 0.0)); - assert_eq!(p2.normalize(), Point2D::new(0.6, -0.8)); - } - #[test] pub fn test_min() { let p1 = Point2D::new(1.0, 3.0); @@ -874,6 +639,7 @@ mod point2d { mod typedpoint2d { use super::TypedPoint2D; use scale_factor::ScaleFactor; + use vector::vec2; pub enum Mm {} pub enum Cm {} @@ -884,7 +650,7 @@ mod typedpoint2d { #[test] pub fn test_add() { let p1 = Point2DMm::new(1.0, 2.0); - let p2 = Point2DMm::new(3.0, 4.0); + let p2 = vec2(3.0, 4.0); let result = p1 + p2; @@ -894,7 +660,7 @@ mod typedpoint2d { #[test] pub fn test_add_assign() { let mut p1 = Point2DMm::new(1.0, 2.0); - p1 += Point2DMm::new(3.0, 4.0); + p1 += vec2(3.0, 4.0); assert_eq!(p1, Point2DMm::new(4.0, 6.0)); } @@ -914,31 +680,6 @@ mod typedpoint2d { mod point3d { use super::Point3D; - #[test] - pub fn test_dot() { - let p1 = Point3D::new(7.0, 21.0, 32.0); - let p2 = Point3D::new(43.0, 5.0, 16.0); - assert_eq!(p1.dot(p2), 918.0); - } - - #[test] - pub fn test_cross() { - let p1 = Point3D::new(4.0, 7.0, 9.0); - let p2 = Point3D::new(13.0, 8.0, 3.0); - let p3 = p1.cross(p2); - assert_eq!(p3, Point3D::new(-51.0, 105.0, -59.0)); - } - - #[test] - pub fn test_normalize() { - let p0: Point3D = Point3D::zero(); - let p1: Point3D = Point3D::new(0.0, -6.0, 0.0); - let p2: Point3D = Point3D::new(1.0, 2.0, -2.0); - assert_eq!(p0.normalize(), p0); - assert_eq!(p1.normalize(), Point3D::new(0.0, -1.0, 0.0)); - assert_eq!(p2.normalize(), Point3D::new(1.0/3.0, 2.0/3.0, -2.0/3.0)); - } - #[test] pub fn test_min() { let p1 = Point3D::new(1.0, 3.0, 5.0); @@ -959,48 +700,3 @@ mod point3d { assert_eq!(result, Point3D::new(2.0, 3.0, 5.0)); } } - -#[cfg(test)] -mod point4d { - use super::Point4D; - - #[test] - pub fn test_add() { - let p1 = Point4D::new(7.0, 21.0, 32.0, 1.0); - let p2 = Point4D::new(43.0, 5.0, 16.0, 2.0); - - let result = p1 + p2; - - assert_eq!(result, Point4D::new(50.0, 26.0, 48.0, 3.0)); - } - - #[test] - pub fn test_sub() { - let p1 = Point4D::new(7.0, 21.0, 32.0, 1.0); - let p2 = Point4D::new(43.0, 5.0, 16.0, 2.0); - - let result = p1 - p2; - - assert_eq!(result, Point4D::new(-36.0, 16.0, 16.0, -1.0)); - } - - #[test] - pub fn test_min() { - let p1 = Point4D::new(1.0, 3.0, 5.0, 7.0); - let p2 = Point4D::new(2.0, 2.0, -1.0, 10.0); - - let result = p1.min(p2); - - assert_eq!(result, Point4D::new(1.0, 2.0, -1.0, 7.0)); - } - - #[test] - pub fn test_max() { - let p1 = Point4D::new(1.0, 3.0, 5.0, 7.0); - let p2 = Point4D::new(2.0, 2.0, -1.0, 10.0); - - let result = p1.max(p2); - - assert_eq!(result, Point4D::new(2.0, 3.0, 5.0, 10.0)); - } -} diff --git a/src/rect.rs b/src/rect.rs index ef71c155..9b512215 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -12,6 +12,7 @@ use length::Length; use scale_factor::ScaleFactor; use num::*; use point::TypedPoint2D; +use vector::TypedVector2D; use size::TypedSize2D; use heapsize::HeapSizeOf; @@ -154,13 +155,10 @@ where T: Copy + Clone + Zero + PartialOrd + PartialEq + Add + Sub) -> TypedRect { - TypedRect::new( - TypedPoint2D::new(self.origin.x + other.x, self.origin.y + other.y), - self.size - ) + pub fn translate(&self, by: &TypedVector2D) -> TypedRect { + Self::new(self.origin + *by, self.size) } /// Returns true if this rectangle contains the point. Points are considered @@ -212,7 +210,7 @@ where T: Copy + Clone + Zero + PartialOrd + PartialEq + Add + Sub) -> TypedRect { - self.translate(&TypedPoint2D::new(size.width, size.height)) + self.translate(&size.to_vector()) } /// Returns the smallest rectangle containing the four points. @@ -280,7 +278,7 @@ impl TypedRect { /// Constructor, setting all sides to zero. pub fn zero() -> TypedRect { TypedRect::new( - TypedPoint2D::zero(), + TypedPoint2D::origin(), TypedSize2D::zero(), ) } @@ -434,6 +432,7 @@ pub fn rect(x: T, y: T, w: T, h: T) -> TypedRect { #[cfg(test)] mod tests { use point::Point2D; + use vector::vec2; use size::Size2D; use super::*; @@ -449,7 +448,7 @@ mod tests { #[test] fn test_translate() { let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32)); - let pp = p.translate(&Point2D::new(10,15)); + let pp = p.translate(&vec2(10,15)); assert!(pp.size.width == 50); assert!(pp.size.height == 40); @@ -458,7 +457,7 @@ mod tests { let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40)); - let rr = r.translate(&Point2D::new(0,-10)); + let rr = r.translate(&vec2(0,-10)); assert!(rr.size.width == 50); assert!(rr.size.height == 40); @@ -561,10 +560,10 @@ mod tests { 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)))); + assert!(!r.contains_rect(&r.translate(&vec2( 0.1, 0.0)))); + assert!(!r.contains_rect(&r.translate(&vec2(-0.1, 0.0)))); + assert!(!r.contains_rect(&r.translate(&vec2( 0.0, 0.1)))); + assert!(!r.contains_rect(&r.translate(&vec2( 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); diff --git a/src/size.rs b/src/size.rs index 4d293584..7d465cb6 100644 --- a/src/size.rs +++ b/src/size.rs @@ -10,6 +10,7 @@ use super::UnknownUnit; use length::Length; use scale_factor::ScaleFactor; +use vector::{TypedVector2D, vec2}; use num::*; use num_traits::NumCast; @@ -167,6 +168,9 @@ impl TypedSize2D { #[inline] pub fn to_array(&self) -> [T; 2] { [self.width, self.height] } + #[inline] + pub fn to_vector(&self) -> TypedVector2D { vec2(self.width, self.height) } + /// Drop the units, preserving only the numeric value. pub fn to_untyped(&self) -> Size2D { TypedSize2D::new(self.width, self.height) diff --git a/src/transform2d.rs b/src/transform2d.rs index 127533e1..d5812194 100644 --- a/src/transform2d.rs +++ b/src/transform2d.rs @@ -10,6 +10,7 @@ use super::{UnknownUnit, Radians}; use num::{One, Zero}; use point::TypedPoint2D; +use vector::{TypedVector2D, vec2}; use rect::TypedRect; use std::ops::{Add, Mul, Div, Sub}; use std::marker::PhantomData; @@ -158,13 +159,13 @@ where T: Copy + Clone + } /// Applies a translation after self's transformation and returns the resulting transform. - pub fn post_translated(&self, x: T, y: T) -> TypedTransform2D { - self.post_mul(&TypedTransform2D::create_translation(x, y)) + pub fn post_translated(&self, v: TypedVector2D) -> TypedTransform2D { + self.post_mul(&TypedTransform2D::create_translation(v.x, v.y)) } /// Applies a translation before self's transformation and returns the resulting transform. - pub fn pre_translated(&self, x: T, y: T) -> TypedTransform2D { - self.pre_mul(&TypedTransform2D::create_translation(x, y)) + pub fn pre_translated(&self, v: TypedVector2D) -> TypedTransform2D { + self.pre_mul(&TypedTransform2D::create_translation(v.x, v.y)) } /// Returns a scale transform. @@ -220,6 +221,13 @@ where T: Copy + Clone + point.x * self.m12 + point.y * self.m22 + self.m32) } + /// Returns the given vector transformed by this matrix. + #[inline] + pub fn transform_vector(&self, vec: &TypedVector2D) -> TypedVector2D { + vec2(vec.x * self.m11 + vec.y * self.m21, + vec.x * self.m12 + vec.y * self.m22) + } + /// Returns a rectangle that encompasses the result of transforming the given rectangle by this /// transform. #[inline] @@ -317,8 +325,8 @@ mod test { #[test] pub fn test_translation() { let t1 = Mat::create_translation(1.0, 2.0); - let t2 = Mat::identity().pre_translated(1.0, 2.0); - let t3 = Mat::identity().post_translated(1.0, 2.0); + let t2 = Mat::identity().pre_translated(vec2(1.0, 2.0)); + let t3 = Mat::identity().post_translated(vec2(1.0, 2.0)); assert_eq!(t1, t2); assert_eq!(t1, t3); @@ -395,8 +403,8 @@ mod test { #[test] pub fn test_pre_post() { - let m1 = Transform2D::identity().post_scaled(1.0, 2.0).post_translated(1.0, 2.0); - let m2 = Transform2D::identity().pre_translated(1.0, 2.0).pre_scaled(1.0, 2.0); + let m1 = Transform2D::identity().post_scaled(1.0, 2.0).post_translated(vec2(1.0, 2.0)); + let m2 = Transform2D::identity().pre_translated(vec2(1.0, 2.0)).pre_scaled(1.0, 2.0); assert!(m1.approx_eq(&m2)); let r = Mat::create_rotation(rad(FRAC_PI_2)); @@ -424,7 +432,15 @@ mod test { pub fn test_is_identity() { let m1 = Transform2D::identity(); assert!(m1.is_identity()); - let m2 = m1.post_translated(0.1, 0.0); + let m2 = m1.post_translated(vec2(0.1, 0.0)); assert!(!m2.is_identity()); } + + #[test] + pub fn test_transform_vector() { + // Translation does not apply to vectors. + let m1 = Mat::create_translation(1.0, 1.0); + let v1 = vec2(10.0, -10.0); + assert_eq!(v1, m1.transform_vector(&v1)); + } } diff --git a/src/transform3d.rs b/src/transform3d.rs index a42216ef..056478ca 100644 --- a/src/transform3d.rs +++ b/src/transform3d.rs @@ -10,7 +10,8 @@ use super::{UnknownUnit, Radians}; use approxeq::ApproxEq; use trig::Trig; -use point::{TypedPoint2D, TypedPoint3D, TypedPoint4D}; +use point::{TypedPoint2D, TypedPoint3D, point2, point3}; +use vector::{TypedVector2D, TypedVector3D, vec2, vec3}; use rect::TypedRect; use transform2d::TypedTransform2D; use scale_factor::ScaleFactor; @@ -24,8 +25,8 @@ define_matrix! { /// /// Transforms can be parametrized over the source and destination units, to describe a /// transformation from a space to another. - /// For example, `TypedTransform3D::transform_point4d` - /// takes a `TypedPoint4D` and returns a `TypedPoint4D`. + /// For example, `TypedTransform3D::transform_point3d` + /// takes a `TypedPoint3D` and returns a `TypedPoint3D`. /// /// Transforms expose a set of convenience methods for pre- and post-transformations. /// A pre-transformation corresponds to adding an operation that is applied before @@ -387,8 +388,24 @@ where T: Copy + Clone + /// /// 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() + pub fn transform_point2d(&self, p: &TypedPoint2D) -> TypedPoint2D { + let x = p.x * self.m11 + p.y * self.m21 + self.m41; + let y = p.x * self.m12 + p.y * self.m22 + self.m42; + + let w = p.x * self.m14 + p.y * self.m24 + self.m44; + + point2(x/w, y/w) + } + + /// Returns the given 2d vector 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_vector2d(&self, v: &TypedVector2D) -> TypedVector2D { + vec2( + v.x * self.m11 + v.y * self.m21, + v.x * self.m12 + v.y * self.m22, + ) } /// Returns the given 3d point transformed by this transform. @@ -396,29 +413,34 @@ where T: Copy + Clone + /// The input point must be use the unit Src, and the returned point has the unit Dst. #[inline] pub fn transform_point3d(&self, p: &TypedPoint3D) -> TypedPoint3D { - self.transform_point4d(&TypedPoint4D::new(p.x, p.y, p.z, One::one())).to_3d() + let x = p.x * self.m11 + p.y * self.m21 + p.z * self.m31 + self.m41; + let y = p.x * self.m12 + p.y * self.m22 + p.z * self.m32 + self.m42; + let z = p.x * self.m13 + p.y * self.m23 + p.z * self.m33 + self.m43; + let w = p.x * self.m14 + p.y * self.m24 + p.z * self.m34 + self.m44; + + point3(x/w, y/w, z/w) } - /// Returns the given 4d point transformed by this transform. + /// Returns the given 3d vector 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 + p.w * self.m41; - let y = p.x * self.m12 + p.y * self.m22 + p.z * self.m32 + p.w * self.m42; - let z = p.x * self.m13 + p.y * self.m23 + p.z * self.m33 + p.w * self.m43; - let w = p.x * self.m14 + p.y * self.m24 + p.z * self.m34 + p.w * self.m44; - TypedPoint4D::new(x, y, z, w) + pub fn transform_vector3d(&self, v: &TypedVector3D) -> TypedVector3D { + vec3( + v.x * self.m11 + v.y * self.m21 + v.z * self.m31, + v.x * self.m12 + v.y * self.m22 + v.z * self.m32, + v.x * self.m13 + v.y * self.m23 + v.z * self.m33, + ) } /// Returns a rectangle that encompasses the result of transforming the given rectangle by this /// transform. pub fn transform_rect(&self, rect: &TypedRect) -> TypedRect { TypedRect::from_points(&[ - self.transform_point(&rect.origin), - self.transform_point(&rect.top_right()), - self.transform_point(&rect.bottom_left()), - self.transform_point(&rect.bottom_right()), + self.transform_point2d(&rect.origin), + self.transform_point2d(&rect.top_right()), + self.transform_point2d(&rect.bottom_left()), + self.transform_point2d(&rect.bottom_right()), ]) } @@ -434,13 +456,13 @@ where T: Copy + Clone + } /// Returns a transform with a translation applied before self's transformation. - pub fn pre_translated(&self, x: T, y: T, z: T) -> TypedTransform3D { - self.pre_mul(&TypedTransform3D::create_translation(x, y, z)) + pub fn pre_translated(&self, v: TypedVector3D) -> TypedTransform3D { + self.pre_mul(&TypedTransform3D::create_translation(v.x, v.y, v.z)) } /// Returns a transform with a translation applied after self's transformation. - pub fn post_translated(&self, x: T, y: T, z: T) -> TypedTransform3D { - self.post_mul(&TypedTransform3D::create_translation(x, y, z)) + pub fn post_translated(&self, v: TypedVector3D) -> TypedTransform3D { + self.post_mul(&TypedTransform3D::create_translation(v.x, v.y, v.z)) } /// Create a 3d scale transform @@ -608,7 +630,7 @@ where T: Copy + fmt::Debug + mod tests { use approxeq::ApproxEq; use transform2d::Transform2D; - use point::{Point2D, Point3D, Point4D}; + use point::{Point2D, Point3D}; use Radians; use super::*; @@ -622,13 +644,13 @@ mod tests { #[test] pub fn test_translation() { let t1 = Mf32::create_translation(1.0, 2.0, 3.0); - let t2 = Mf32::identity().pre_translated(1.0, 2.0, 3.0); - let t3 = Mf32::identity().post_translated(1.0, 2.0, 3.0); + let t2 = Mf32::identity().pre_translated(vec3(1.0, 2.0, 3.0)); + let t3 = Mf32::identity().post_translated(vec3(1.0, 2.0, 3.0)); assert_eq!(t1, t2); assert_eq!(t1, t3); assert_eq!(t1.transform_point3d(&Point3D::new(1.0, 1.0, 1.0)), Point3D::new(2.0, 3.0, 4.0)); - assert_eq!(t1.transform_point(&Point2D::new(1.0, 1.0)), Point2D::new(2.0, 3.0)); + assert_eq!(t1.transform_point2d(&Point2D::new(1.0, 1.0)), Point2D::new(2.0, 3.0)); assert_eq!(t1.post_mul(&t1), Mf32::create_translation(2.0, 4.0, 6.0)); @@ -645,7 +667,7 @@ mod tests { assert_eq!(r1, r3); assert!(r1.transform_point3d(&Point3D::new(1.0, 2.0, 3.0)).approx_eq(&Point3D::new(2.0, -1.0, 3.0))); - assert!(r1.transform_point(&Point2D::new(1.0, 2.0)).approx_eq(&Point2D::new(2.0, -1.0))); + assert!(r1.transform_point2d(&Point2D::new(1.0, 2.0)).approx_eq(&Point2D::new(2.0, -1.0))); assert!(r1.post_mul(&r1).approx_eq(&Mf32::create_rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2*2.0)))); @@ -662,7 +684,7 @@ mod tests { assert_eq!(s1, s3); assert!(s1.transform_point3d(&Point3D::new(2.0, 2.0, 2.0)).approx_eq(&Point3D::new(4.0, 6.0, 8.0))); - assert!(s1.transform_point(&Point2D::new(2.0, 2.0)).approx_eq(&Point2D::new(4.0, 6.0))); + assert!(s1.transform_point2d(&Point2D::new(2.0, 2.0)).approx_eq(&Point2D::new(4.0, 6.0))); assert_eq!(s1.post_mul(&s1), Mf32::create_scale(4.0, 9.0, 16.0)); @@ -757,10 +779,10 @@ mod tests { assert!(m1.pre_mul(&m2).approx_eq(&Mf32::identity())); let p1 = Point2D::new(1000.0, 2000.0); - let p2 = m1.transform_point(&p1); + let p2 = m1.transform_point2d(&p1); assert!(p2.eq(&Point2D::new(1100.0, 2200.0))); - let p3 = m2.transform_point(&p2); + let p3 = m2.transform_point2d(&p2); assert!(p3.eq(&p1)); } @@ -772,8 +794,8 @@ mod tests { #[test] pub fn test_pre_post() { - let m1 = Transform3D::identity().post_scaled(1.0, 2.0, 3.0).post_translated(1.0, 2.0, 3.0); - let m2 = Transform3D::identity().pre_translated(1.0, 2.0, 3.0).pre_scaled(1.0, 2.0, 3.0); + let m1 = Transform3D::identity().post_scaled(1.0, 2.0, 3.0).post_translated(vec3(1.0, 2.0, 3.0)); + let m2 = Transform3D::identity().pre_translated(vec3(1.0, 2.0, 3.0)).pre_scaled(1.0, 2.0, 3.0); assert!(m1.approx_eq(&m2)); let r = Mf32::create_rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2)); @@ -808,9 +830,9 @@ mod tests { 1.5, -2.0, 6.0, 0.0, -2.5, 6.0, 1.0, 1.0); - let p = Point4D::new(1.0, 3.0, 5.0, 1.0); - let p1 = m2.pre_mul(&m1).transform_point4d(&p); - let p2 = m2.transform_point4d(&m1.transform_point4d(&p)); + let p = Point3D::new(1.0, 3.0, 5.0); + let p1 = m2.pre_mul(&m1).transform_point3d(&p); + let p2 = m2.transform_point3d(&m1.transform_point3d(&p)); assert!(p1.approx_eq(&p2)); } @@ -818,7 +840,22 @@ mod tests { pub fn test_is_identity() { let m1 = Transform3D::identity(); assert!(m1.is_identity()); - let m2 = m1.post_translated(0.1, 0.0, 0.0); + let m2 = m1.post_translated(vec3(0.1, 0.0, 0.0)); assert!(!m2.is_identity()); } + + #[test] + pub fn test_transform_vector() { + // Translation does not apply to vectors. + let m = Mf32::create_translation(1.0, 2.0, 3.0); + let v1 = vec3(10.0, -10.0, 3.0); + assert_eq!(v1, m.transform_vector3d(&v1)); + // While it does apply to points. + assert!(v1.to_point() != m.transform_point3d(&v1.to_point())); + + // same thing with 2d vectors/points + let v2 = vec2(10.0, -5.0); + assert_eq!(v2, m.transform_vector2d(&v2)); + assert!(v2.to_point() != m.transform_point2d(&v2.to_point())); + } } diff --git a/src/vector.rs b/src/vector.rs new file mode 100644 index 00000000..7f33a2a5 --- /dev/null +++ b/src/vector.rs @@ -0,0 +1,833 @@ +// 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 super::UnknownUnit; +use approxeq::ApproxEq; +use length::Length; +use point::{TypedPoint2D, TypedPoint3D, point2, point3}; +use size::{TypedSize2D, size2}; +use scale_factor::ScaleFactor; +use num::*; +use num_traits::{Float, NumCast}; +use std::fmt; +use std::ops::{Add, Neg, Mul, Sub, Div, AddAssign, SubAssign, MulAssign, DivAssign}; +use std::marker::PhantomData; + +define_matrix! { + /// A 2d Vector tagged with a unit. + pub struct TypedVector2D { + pub x: T, + pub y: T, + } +} + +/// Default 2d vector type with no unit. +/// +/// `Vector2D` provides the same methods as `TypedVector2D`. +pub type Vector2D = TypedVector2D; + +impl TypedVector2D { + /// Constructor, setting all components to zero. + #[inline] + pub fn zero() -> Self { + TypedVector2D::new(Zero::zero(), Zero::zero()) + } + + /// Convert into a 3d vector. + #[inline] + pub fn to_3d(&self) -> TypedVector3D { + vec3(self.x, self.y, Zero::zero()) + } +} + +impl fmt::Debug for TypedVector2D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({:?},{:?})", self.x, self.y) + } +} + +impl fmt::Display for TypedVector2D { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "({},{})", self.x, self.y) + } +} + +impl TypedVector2D { + /// Constructor taking scalar values directly. + #[inline] + pub fn new(x: T, y: T) -> Self { + TypedVector2D { x: x, y: y, _unit: PhantomData } + } + + /// Constructor taking properly typed Lengths instead of scalar values. + #[inline] + pub fn from_lengths(x: Length, y: Length) -> Self { + vec2(x.0, y.0) + } + + /// Cast this vector into a point. + /// + /// Equivalent to adding this vector to the origin. + #[inline] + pub fn to_point(&self) -> TypedPoint2D { + point2(self.x, self.y) + } + + /// Cast this vector into a size. + #[inline] + pub fn to_size(&self) -> TypedSize2D { + size2(self.x, self.y) + } + + + /// 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) -> Vector2D { + vec2(self.x, self.y) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: &Vector2D) -> Self { + vec2(p.x, p.y) + } + + #[inline] + pub fn to_array(&self) -> [T; 2] { + [self.x, self.y] + } +} + +impl TypedVector2D +where T: Copy + Mul + Add + Sub { + /// Dot product. + #[inline] + pub fn dot(self, other: Self) -> 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: Self) -> T { + self.x * other.y - self.y * other.x + } + + #[inline] + pub fn normalize(self) -> Self where T: Float + ApproxEq { + let dot = self.dot(self); + if dot.approx_eq(&T::zero()) { + self + } else { + self / dot.sqrt() + } + } + + #[inline] + pub fn square_length(&self) -> T { + self.x * self.x + self.y * self.y + } + + #[inline] + pub fn length(&self) -> T where T: Float + ApproxEq { + self.square_length().sqrt() + } +} + +impl, U> Add for TypedVector2D { + type Output = Self; + fn add(self, other: Self) -> Self { + TypedVector2D::new(self.x + other.x, self.y + other.y) + } +} + +impl, U> AddAssign for TypedVector2D { + #[inline] + fn add_assign(&mut self, other: Self) { + *self = *self + other + } +} + +impl, U> SubAssign> for TypedVector2D { + #[inline] + fn sub_assign(&mut self, other: Self) { + *self = *self - other + } +} + +impl, U> Sub for TypedVector2D { + type Output = Self; + #[inline] + fn sub(self, other: TypedVector2D) -> Self { + vec2(self.x - other.x, self.y - other.y) + } +} + +impl , U> Neg for TypedVector2D { + type Output = Self; + #[inline] + fn neg(self) -> Self { + vec2(-self.x, -self.y) + } +} + +impl TypedVector2D { + #[inline] + pub fn min(self, other: TypedVector2D) -> Self { + vec2(self.x.min(other.x), self.y.min(other.y)) + } + + #[inline] + pub fn max(self, other: TypedVector2D) -> Self { + vec2(self.x.max(other.x), self.y.max(other.y)) + } +} + +impl, U> Mul for TypedVector2D { + type Output = Self; + #[inline] + fn mul(self, scale: T) -> Self { + vec2(self.x * scale, self.y * scale) + } +} + +impl, U> Div for TypedVector2D { + type Output = Self; + #[inline] + fn div(self, scale: T) -> Self { + vec2(self.x / scale, self.y / scale) + } +} + +impl, U> MulAssign for TypedVector2D { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self = *self * scale + } +} + +impl, U> DivAssign for TypedVector2D { + #[inline] + fn div_assign(&mut self, scale: T) { + *self = *self / scale + } +} + +impl, U1, U2> Mul> for TypedVector2D { + type Output = TypedVector2D; + #[inline] + fn mul(self, scale: ScaleFactor) -> TypedVector2D { + vec2(self.x * scale.get(), self.y * scale.get()) + } +} + +impl, U1, U2> Div> for TypedVector2D { + type Output = TypedVector2D; + #[inline] + fn div(self, scale: ScaleFactor) -> TypedVector2D { + vec2(self.x / scale.get(), self.y / scale.get()) + } +} + +impl TypedVector2D { + /// 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 }`. + #[inline] + pub fn round(&self) -> Self { + vec2(self.x.round(), self.y.round()) + } +} + +impl TypedVector2D { + /// Rounds each component to the smallest integer equal or greater than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// For example `{ -0.1, -0.8 }.ceil() == { 0.0, 0.0 }`. + #[inline] + pub fn ceil(&self) -> Self { + vec2(self.x.ceil(), self.y.ceil()) + } +} + +impl TypedVector2D { + /// Rounds each component to the biggest integer equal or lower than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + /// For example `{ -0.1, -0.8 }.floor() == { -1.0, -1.0 }`. + #[inline] + pub fn floor(&self) -> Self { + vec2(self.x.floor(), self.y.floor()) + } +} + +impl TypedVector2D { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating vector 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. + #[inline] + pub fn cast(&self) -> Option> { + match (NumCast::from(self.x), NumCast::from(self.y)) { + (Some(x), Some(y)) => Some(TypedVector2D::new(x, y)), + _ => None + } + } + + // Convenience functions for common casts + + /// Cast into an `f32` vector. + #[inline] + pub fn to_f32(&self) -> TypedVector2D { + self.cast().unwrap() + } + + /// Cast into an `usize` vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_usize(&self) -> TypedVector2D { + self.cast().unwrap() + } + + /// Cast into an i32 vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i32(&self) -> TypedVector2D { + self.cast().unwrap() + } + + /// Cast into an i64 vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i64(&self) -> TypedVector2D { + self.cast().unwrap() + } +} + +impl, U> ApproxEq> for TypedVector2D { + #[inline] + fn approx_epsilon() -> Self { + vec2(T::approx_epsilon(), T::approx_epsilon()) + } + + #[inline] + fn approx_eq(&self, other: &Self) -> bool { + self.x.approx_eq(&other.x) && self.y.approx_eq(&other.y) + } + + #[inline] + fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool { + self.x.approx_eq_eps(&other.x, &eps.x) && self.y.approx_eq_eps(&other.y, &eps.y) + } +} + +define_matrix! { + /// A 3d Vector tagged with a unit. + pub struct TypedVector3D { + pub x: T, + pub y: T, + pub z: T, + } +} + +/// Default 3d vector type with no unit. +/// +/// `Vector3D` provides the same methods as `TypedVector3D`. +pub type Vector3D = TypedVector3D; + +impl TypedVector3D { + /// Constructor, setting all copmonents to zero. + #[inline] + pub fn zero() -> Self { + vec3(Zero::zero(), Zero::zero(), Zero::zero()) + } + + #[inline] + pub fn to_array_4d(&self) -> [T; 4] { + [self.x, self.y, self.z, Zero::zero()] + } +} + +impl fmt::Debug for TypedVector3D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({:?},{:?},{:?})", self.x, self.y, self.z) + } +} + +impl fmt::Display for TypedVector3D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({},{},{})", self.x, self.y, self.z) + } +} + +impl TypedVector3D { + /// Constructor taking scalar values directly. + #[inline] + pub fn new(x: T, y: T, z: T) -> Self { + TypedVector3D { x: x, y: y, z: z, _unit: PhantomData } + } + + /// Constructor taking properly typed Lengths instead of scalar values. + #[inline] + pub fn from_lengths(x: Length, y: Length, z: Length) -> TypedVector3D { + vec3(x.0, y.0, z.0) + } + + /// Cast this vector into a point. + /// + /// Equivalent to adding this vector to the origin. + #[inline] + pub fn to_point(&self) -> TypedPoint3D { + point3(self.x, self.y, self.z) + } + + /// 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) -> Vector3D { + vec3(self.x, self.y, self.z) + } + + /// Tag a unitless value with units. + #[inline] + pub fn from_untyped(p: &Vector3D) -> Self { + vec3(p.x, p.y, p.z) + } + + /// Convert into a 2d vector. + #[inline] + pub fn to_2d(&self) -> TypedVector2D { + vec2(self.x, self.y) + } +} + +impl + + Add + + Sub + + Copy, U> TypedVector3D { + + // Dot product. + #[inline] + pub fn dot(self, other: Self) -> T { + self.x * other.x + + self.y * other.y + + self.z * other.z + } + + // Cross product. + #[inline] + pub fn cross(self, other: TypedVector3D) -> Self { + vec3( + self.y * other.z - self.z * other.y, + self.z * other.x - self.x * other.z, + self.x * other.y - self.y * other.x + ) + } + + #[inline] + pub fn normalize(self) -> Self where T: Float + ApproxEq { + let dot = self.dot(self); + if dot.approx_eq(&T::zero()) { + self + } else { + self / dot.sqrt() + } + } + + #[inline] + pub fn square_length(&self) -> T { + self.x * self.x + self.y * self.y + self.z * self.z + } + + #[inline] + pub fn length(&self) -> T where T: Float + ApproxEq { + self.square_length().sqrt() + } +} + +impl, U> Add for TypedVector3D { + type Output = TypedVector3D; + #[inline] + fn add(self, other: TypedVector3D) -> Self { + vec3(self.x + other.x, self.y + other.y, self.z + other.z) + } +} + +impl, U> Sub for TypedVector3D { + type Output = TypedVector3D; + #[inline] + fn sub(self, other: TypedVector3D) -> Self { + vec3(self.x - other.x, self.y - other.y, self.z - other.z) + } +} + +impl, U> AddAssign for TypedVector3D { + #[inline] + fn add_assign(&mut self, other: Self) { + *self = *self + other + } +} + +impl, U> SubAssign> for TypedVector3D { + #[inline] + fn sub_assign(&mut self, other: Self) { + *self = *self - other + } +} + +impl , U> Neg for TypedVector3D { + type Output = Self; + #[inline] + fn neg(self) -> Self { + vec3(-self.x, -self.y, -self.z) + } +} + +impl, U> Mul for TypedVector3D { + type Output = Self; + #[inline] + fn mul(self, scale: T) -> Self { + Self::new(self.x * scale, self.y * scale, self.z * scale) + } +} + +impl, U> Div for TypedVector3D { + type Output = Self; + #[inline] + fn div(self, scale: T) -> Self { + Self::new(self.x / scale, self.y / scale, self.z / scale) + } +} + +impl, U> MulAssign for TypedVector3D { + #[inline] + fn mul_assign(&mut self, scale: T) { + *self = *self * scale + } +} + +impl, U> DivAssign for TypedVector3D { + #[inline] + fn div_assign(&mut self, scale: T) { + *self = *self / scale + } +} + +impl TypedVector3D { + #[inline] + pub fn min(self, other: TypedVector3D) -> TypedVector3D { + vec3(self.x.min(other.x), self.y.min(other.y), self.z.min(other.z)) + } + + #[inline] + pub fn max(self, other: TypedVector3D) -> TypedVector3D { + vec3(self.x.max(other.x), self.y.max(other.y), self.z.max(other.z)) + } +} + +impl TypedVector3D { + /// Rounds each component to the nearest integer value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + #[inline] + pub fn round(&self) -> Self { + vec3(self.x.round(), self.y.round(), self.z.round()) + } +} + +impl TypedVector3D { + /// Rounds each component to the smallest integer equal or greater than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + #[inline] + pub fn ceil(&self) -> Self { + vec3(self.x.ceil(), self.y.ceil(), self.z.ceil()) + } +} + +impl TypedVector3D { + /// Rounds each component to the biggest integer equal or lower than the original value. + /// + /// This behavior is preserved for negative values (unlike the basic cast). + #[inline] + pub fn floor(&self) -> Self { + vec3(self.x.floor(), self.y.floor(), self.z.floor()) + } +} + +impl TypedVector3D { + /// Cast from one numeric representation to another, preserving the units. + /// + /// When casting from floating vector 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. + #[inline] + 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(vec3(x, y, z)), + _ => None + } + } + + // Convenience functions for common casts + + /// Cast into an `f32` vector. + #[inline] + pub fn to_f32(&self) -> TypedVector3D { + self.cast().unwrap() + } + + /// Cast into an `usize` vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_usize(&self) -> TypedVector3D { + self.cast().unwrap() + } + + /// Cast into an `i32` vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i32(&self) -> TypedVector3D { + self.cast().unwrap() + } + + /// Cast into an `i64` vector, truncating decimals if any. + /// + /// When casting from floating vector vectors, it is worth considering whether + /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain + /// the desired conversion behavior. + #[inline] + pub fn to_i64(&self) -> TypedVector3D { + self.cast().unwrap() + } +} + +impl, U> ApproxEq> for TypedVector3D { + #[inline] + fn approx_epsilon() -> Self { + vec3(T::approx_epsilon(), T::approx_epsilon(), T::approx_epsilon()) + } + + #[inline] + fn approx_eq(&self, other: &Self) -> bool { + self.x.approx_eq(&other.x) + && self.y.approx_eq(&other.y) + && self.z.approx_eq(&other.z) + } + + #[inline] + fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool { + self.x.approx_eq_eps(&other.x, &eps.x) + && self.y.approx_eq_eps(&other.y, &eps.y) + && self.z.approx_eq_eps(&other.z, &eps.z) + } +} + +/// Convenience constructor. +#[inline] +pub fn vec2(x: T, y: T) -> TypedVector2D { + TypedVector2D::new(x, y) +} + +/// Convenience constructor. +#[inline] +pub fn vec3(x: T, y: T, z: T) -> TypedVector3D { + TypedVector3D::new(x, y, z) +} + +#[cfg(test)] +mod vector2d { + use super::{Vector2D, vec2}; + type Vec2 = Vector2D; + + #[test] + pub fn test_scalar_mul() { + let p1: Vec2 = vec2(3.0, 5.0); + + let result = p1 * 5.0; + + assert_eq!(result, Vector2D::new(15.0, 25.0)); + } + + #[test] + pub fn test_dot() { + let p1: Vec2 = vec2(2.0, 7.0); + let p2: Vec2 = vec2(13.0, 11.0); + assert_eq!(p1.dot(p2), 103.0); + } + + #[test] + pub fn test_cross() { + let p1: Vec2 = vec2(4.0, 7.0); + let p2: Vec2 = vec2(13.0, 8.0); + let r = p1.cross(p2); + assert_eq!(r, -59.0); + } + + #[test] + pub fn test_normalize() { + let p0: Vec2 = Vec2::zero(); + let p1: Vec2 = vec2(4.0, 0.0); + let p2: Vec2 = vec2(3.0, -4.0); + assert_eq!(p0.normalize(), p0); + assert_eq!(p1.normalize(), vec2(1.0, 0.0)); + assert_eq!(p2.normalize(), vec2(0.6, -0.8)); + } + + #[test] + pub fn test_min() { + let p1: Vec2 = vec2(1.0, 3.0); + let p2: Vec2 = vec2(2.0, 2.0); + + let result = p1.min(p2); + + assert_eq!(result, vec2(1.0, 2.0)); + } + + #[test] + pub fn test_max() { + let p1: Vec2 = vec2(1.0, 3.0); + let p2: Vec2 = vec2(2.0, 2.0); + + let result = p1.max(p2); + + assert_eq!(result, vec2(2.0, 3.0)); + } +} + +#[cfg(test)] +mod typedvector2d { + use super::{TypedVector2D, vec2}; + use scale_factor::ScaleFactor; + + pub enum Mm {} + pub enum Cm {} + + pub type Vector2DMm = TypedVector2D; + pub type Vector2DCm = TypedVector2D; + + #[test] + pub fn test_add() { + let p1 = Vector2DMm::new(1.0, 2.0); + let p2 = Vector2DMm::new(3.0, 4.0); + + let result = p1 + p2; + + assert_eq!(result, vec2(4.0, 6.0)); + } + + #[test] + pub fn test_add_assign() { + let mut p1 = Vector2DMm::new(1.0, 2.0); + p1 += vec2(3.0, 4.0); + + assert_eq!(p1, vec2(4.0, 6.0)); + } + + #[test] + pub fn test_scalar_mul() { + let p1 = Vector2DMm::new(1.0, 2.0); + let cm_per_mm: ScaleFactor = ScaleFactor::new(0.1); + + let result: Vector2DCm = p1 * cm_per_mm; + + assert_eq!(result, vec2(0.1, 0.2)); + } +} + +#[cfg(test)] +mod vector3d { + use super::{Vector3D, vec3}; + type Vec3 = Vector3D; + + #[test] + pub fn test_dot() { + let p1: Vec3 = vec3(7.0, 21.0, 32.0); + let p2: Vec3 = vec3(43.0, 5.0, 16.0); + assert_eq!(p1.dot(p2), 918.0); + } + + #[test] + pub fn test_cross() { + let p1: Vec3 = vec3(4.0, 7.0, 9.0); + let p2: Vec3 = vec3(13.0, 8.0, 3.0); + let p3 = p1.cross(p2); + assert_eq!(p3, vec3(-51.0, 105.0, -59.0)); + } + + #[test] + pub fn test_normalize() { + let p0: Vec3 = Vec3::zero(); + let p1: Vec3 = vec3(0.0, -6.0, 0.0); + let p2: Vec3 = vec3(1.0, 2.0, -2.0); + assert_eq!(p0.normalize(), p0); + assert_eq!(p1.normalize(), vec3(0.0, -1.0, 0.0)); + assert_eq!(p2.normalize(), vec3(1.0/3.0, 2.0/3.0, -2.0/3.0)); + } + + #[test] + pub fn test_min() { + let p1: Vec3 = vec3(1.0, 3.0, 5.0); + let p2: Vec3 = vec3(2.0, 2.0, -1.0); + + let result = p1.min(p2); + + assert_eq!(result, vec3(1.0, 2.0, -1.0)); + } + + #[test] + pub fn test_max() { + let p1: Vec3 = vec3(1.0, 3.0, 5.0); + let p2: Vec3 = vec3(2.0, 2.0, -1.0); + + let result = p1.max(p2); + + assert_eq!(result, vec3(2.0, 3.0, 5.0)); + } +}