Merge pull request #315 from bjz/vector-dot

Vector API cleanups
This commit is contained in:
Brendan Zabarauskas 2016-04-06 16:48:18 +10:00
commit 10fe7e6107
7 changed files with 172 additions and 77 deletions

View file

@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Constrained conversion functions for assisting in situations where type - Constrained conversion functions for assisting in situations where type
inference is difficult. inference is difficult.
- An `ElementWise` trait for non-mathematical element-wise operations. - An `ElementWise` trait for non-mathematical element-wise operations.
- A default implementation for `EuclideanVector::angle`.
### Changed ### Changed
@ -21,13 +22,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
formatting. formatting.
- Marks vectors, points, matrices, and angles as `#[repr(C, packed)]`. - Marks vectors, points, matrices, and angles as `#[repr(C, packed)]`.
- Renames the `Vector::{length, length2}` functions to `Vector::{magnitude, magnitude2}`. - Renames the `Vector::{length, length2}` functions to `Vector::{magnitude, magnitude2}`.
- Moved `Angle::new` to be directly implemented on the `Rad` and `Deg` types. - Move `Angle::new` to be directly implemented on the `Rad` and `Deg` types.
- Move `Vector::dot` to `EuclideanVector` trait.
- Move `Vector::from_value` to `Array` trait.
### Removed ### Removed
- The non-mathematical operator trait implementations have been removed from - The non-mathematical operator trait implementations have been removed from
the `Vector` trait, in favor of the `ElementWise` trait. the `Vector` trait, in favor of the `ElementWise` trait.
- `Angle::equiv`. - `Angle::equiv`.
- Remove `neg_self` method on vectors and matrices.
## [v0.7.0] - 2015-12-23 ## [v0.7.0] - 2015-12-23

View file

@ -26,6 +26,17 @@ pub trait Array where
{ {
type Element: Copy; type Element: Copy;
/// Construct a vector from a single value, replicating it.
///
/// ```rust
/// use cgmath::prelude::*;
/// use cgmath::Vector3;
///
/// assert_eq!(Vector3::from_value(1),
/// Vector3::new(1, 1, 1));
/// ```
fn from_value(value: Self::Element) -> Self;
/// Get the pointer to the first element of the array. /// Get the pointer to the first element of the array.
#[inline] #[inline]
fn as_ptr(&self) -> *const Self::Element { fn as_ptr(&self) -> *const Self::Element {

View file

@ -100,15 +100,6 @@ impl<S: BaseFloat> Matrix2<S> {
} }
} }
impl<S: Copy + Neg<Output = S>> Matrix2<S> {
/// Negate this `Matrix2` in-place.
#[inline]
pub fn neg_self(&mut self) {
self[0].neg_self();
self[1].neg_self();
}
}
impl<S: BaseFloat> Matrix3<S> { impl<S: BaseFloat> Matrix3<S> {
/// Create a new matrix, providing values for each index. /// Create a new matrix, providing values for each index.
#[inline] #[inline]
@ -200,16 +191,6 @@ impl<S: BaseFloat> Matrix3<S> {
} }
} }
impl<S: Copy + Neg<Output = S>> Matrix3<S> {
/// Negate this `Matrix3` in-place.
#[inline]
pub fn neg_self(&mut self) {
self[0].neg_self();
self[1].neg_self();
self[2].neg_self();
}
}
impl<S: BaseFloat> Matrix4<S> { impl<S: BaseFloat> Matrix4<S> {
/// Create a new matrix, providing values for each index. /// Create a new matrix, providing values for each index.
#[inline] #[inline]
@ -267,17 +248,6 @@ impl<S: BaseFloat> Matrix4<S> {
} }
} }
impl<S: Copy + Neg<Output = S>> Matrix4<S> {
/// Negate this `Matrix4` in-place.
#[inline]
pub fn neg_self(&mut self) {
self[0].neg_self();
self[1].neg_self();
self[2].neg_self();
self[3].neg_self();
}
}
/// A column-major matrix of arbitrary dimensions. /// A column-major matrix of arbitrary dimensions.
pub trait Matrix where pub trait Matrix where
// FIXME: Ugly type signatures - blocked by rust-lang/rust#24092 // FIXME: Ugly type signatures - blocked by rust-lang/rust#24092

View file

@ -169,10 +169,30 @@ macro_rules! impl_point {
impl<S: BaseNum> Array for $PointN<S> { impl<S: BaseNum> Array for $PointN<S> {
type Element = S; type Element = S;
#[inline] fn sum(self) -> S { fold_array!(add, { $(self.$field),+ }) } #[inline]
#[inline] fn product(self) -> S { fold_array!(mul, { $(self.$field),+ }) } fn from_value(scalar: S) -> $PointN<S> {
#[inline] fn min(self) -> S { fold_array!(partial_min, { $(self.$field),+ }) } $PointN { $($field: scalar),+ }
#[inline] fn max(self) -> S { fold_array!(partial_max, { $(self.$field),+ }) } }
#[inline]
fn sum(self) -> S where S: Add<Output = S> {
fold_array!(add, { $(self.$field),+ })
}
#[inline]
fn product(self) -> S where S: Mul<Output = S> {
fold_array!(mul, { $(self.$field),+ })
}
#[inline]
fn min(self) -> S where S: PartialOrd {
fold_array!(partial_min, { $(self.$field),+ })
}
#[inline]
fn max(self) -> S where S: PartialOrd {
fold_array!(partial_max, { $(self.$field),+ })
}
} }
impl<S: BaseNum> Point for $PointN<S> { impl<S: BaseNum> Point for $PointN<S> {

View file

@ -22,7 +22,7 @@ use matrix::{Matrix2, Matrix3};
use num::BaseFloat; use num::BaseFloat;
use point::{Point, Point2, Point3}; use point::{Point, Point2, Point3};
use quaternion::Quaternion; use quaternion::Quaternion;
use vector::{Vector, Vector2, Vector3}; use vector::{EuclideanVector, Vector2, Vector3};
/// A trait for a generic rotation. A rotation is a transformation that /// A trait for a generic rotation. A rotation is a transformation that
/// creates a circular motion, and preserves at least one point in the space. /// creates a circular motion, and preserves at least one point in the space.

View file

@ -22,11 +22,10 @@
//! vector are also provided: //! vector are also provided:
//! //!
//! ```rust //! ```rust
//! use cgmath::{Vector, Vector2, Vector3, Vector4, vec2, vec3}; //! use cgmath::{Vector, Vector2, Vector3, Vector4, vec3};
//! //!
//! assert_eq!(Vector2::new(1.0f64, 0.0f64), Vector2::unit_x()); //! assert_eq!(Vector2::new(1.0f64, 0.0f64), Vector2::unit_x());
//! assert_eq!(vec3(0.0f64, 0.0f64, 0.0f64), Vector3::zero()); //! assert_eq!(vec3(0.0f64, 0.0f64, 0.0f64), Vector3::zero());
//! assert_eq!(Vector2::from_value(1.0f64), vec2(1.0, 1.0));
//! ``` //! ```
//! //!
//! Vectors can be manipulated with typical mathematical operations (addition, //! Vectors can be manipulated with typical mathematical operations (addition,
@ -62,7 +61,8 @@
//! and [cross products](http://en.wikipedia.org/wiki/Cross_product). //! and [cross products](http://en.wikipedia.org/wiki/Cross_product).
//! //!
//! ```rust //! ```rust
//! use cgmath::{Vector, Vector2, Vector3, Vector4, dot}; //! use cgmath::{Vector, EuclideanVector};
//! use cgmath::{Vector2, Vector3, Vector4};
//! //!
//! // All vectors implement the dot product as a method: //! // All vectors implement the dot product as a method:
//! let a: Vector2<f64> = Vector2::new(3.0, 6.0); //! let a: Vector2<f64> = Vector2::new(3.0, 6.0);
@ -70,7 +70,7 @@
//! assert_eq!(a.dot(b), 0.0); //! assert_eq!(a.dot(b), 0.0);
//! //!
//! // But there is also a top-level function: //! // But there is also a top-level function:
//! assert_eq!(a.dot(b), dot(a, b)); //! assert_eq!(a.dot(b), cgmath::dot(a, b));
//! //!
//! // Cross products are defined for 3-dimensional vectors: //! // Cross products are defined for 3-dimensional vectors:
//! let e: Vector3<f64> = Vector3::unit_x(); //! let e: Vector3<f64> = Vector3::unit_x();
@ -100,7 +100,49 @@ use approx::ApproxEq;
use array::{Array, ElementWise}; use array::{Array, ElementWise};
use num::{BaseNum, BaseFloat, PartialOrd}; use num::{BaseNum, BaseFloat, PartialOrd};
/// A trait that specifies a range of numeric operations for vectors. /// Vectors that can be [added](http://mathworld.wolfram.com/VectorAddition.html)
/// together and [multiplied](https://en.wikipedia.org/wiki/Scalar_multiplication)
/// by scalars.
///
/// # Required operators
///
/// ## Vector addition
///
/// Vectors can be added, subtracted, or negated via the following traits:
///
/// - `Add<Output = Self>`
/// - `Sub<Output = Self>`
/// - `Neg<Output = Self>`
///
/// ```rust
/// use cgmath::Vector3;
///
/// let velocity0 = Vector3::new(1, 2, 0);
/// let velocity1 = Vector3::new(1, 1, 0);
///
/// let total_velocity = velocity0 + velocity1;
/// let velocity_diff = velocity1 - velocity0;
/// let reversed_velocity0 = -velocity0;
/// ```
///
/// ## Scalar multiplication
///
/// Vectors can be multiplied or divided by their associated scalars via the
/// following traits:
///
/// - `Mul<Self::Scalar, Output = Self>`
/// - `Div<Self::Scalar, Output = Self>`
/// - `Rem<Self::Scalar, Output = Self>`
///
/// ```rust
/// use cgmath::Vector2;
///
/// let translation = Vector2::new(3.0, 4.0);
/// let scale_factor = 2.0;
///
/// let upscaled_translation = translation * scale_factor;
/// let downscaled_translation = translation / scale_factor;
/// ```
pub trait Vector: Copy + Clone where pub trait Vector: Copy + Clone where
// FIXME: Ugly type signatures - blocked by rust-lang/rust#24092 // FIXME: Ugly type signatures - blocked by rust-lang/rust#24092
Self: Array<Element = <Self as Vector>::Scalar>, Self: Array<Element = <Self as Vector>::Scalar>,
@ -115,27 +157,29 @@ pub trait Vector: Copy + Clone where
/// The associated scalar. /// The associated scalar.
type Scalar: BaseNum; type Scalar: BaseNum;
/// Construct a vector from a single value, replicating it. /// The additive identity.
fn from_value(scalar: Self::Scalar) -> Self; ///
/// Adding this to another `Self` value has no effect.
/// The additive identity vector. Adding this vector with another has no effect. ///
#[inline] /// ```rust
fn zero() -> Self { Self::from_value(Self::Scalar::zero()) } /// use cgmath::prelude::*;
/// use cgmath::Vector2;
/// Vector dot product ///
fn dot(self, other: Self) -> Self::Scalar; /// let v = Vector2::new(1, 2);
/// assert_eq!(v + Vector2::zero(), v);
/// ```
fn zero() -> Self;
} }
/// Dot product of two vectors.
#[inline] pub fn dot<V: Vector>(a: V, b: V) -> V::Scalar { V::dot(a, b) }
/// A 2-dimensional vector. /// A 2-dimensional vector.
/// ///
/// This type is marked as `#[repr(C, packed)]`. /// This type is marked as `#[repr(C, packed)]`.
#[repr(C, packed)] #[repr(C, packed)]
#[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)] #[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)]
pub struct Vector2<S> { pub struct Vector2<S> {
/// The x component of the vector.
pub x: S, pub x: S,
/// The y component of the vector.
pub y: S, pub y: S,
} }
@ -145,8 +189,11 @@ pub struct Vector2<S> {
#[repr(C, packed)] #[repr(C, packed)]
#[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)] #[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)]
pub struct Vector3<S> { pub struct Vector3<S> {
/// The x component of the vector.
pub x: S, pub x: S,
/// The y component of the vector.
pub y: S, pub y: S,
/// The z component of the vector.
pub z: S, pub z: S,
} }
@ -156,9 +203,13 @@ pub struct Vector3<S> {
#[repr(C, packed)] #[repr(C, packed)]
#[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)] #[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)]
pub struct Vector4<S> { pub struct Vector4<S> {
/// The x component of the vector.
pub x: S, pub x: S,
/// The y component of the vector.
pub y: S, pub y: S,
/// The z component of the vector.
pub z: S, pub z: S,
/// The w component of the vector.
pub w: S, pub w: S,
} }
@ -173,14 +224,6 @@ macro_rules! impl_vector {
} }
} }
impl<$S: Copy + Neg<Output = $S>> $VectorN<$S> {
/// Negate this vector in-place (multiply by -1).
#[inline]
pub fn neg_self(&mut self) {
$(self.$field = -self.$field);+
}
}
/// The short constructor. /// The short constructor.
#[inline] #[inline]
pub fn $constructor<S>($($field: S),+) -> $VectorN<S> { pub fn $constructor<S>($($field: S),+) -> $VectorN<S> {
@ -198,18 +241,39 @@ macro_rules! impl_vector {
impl<S: Copy> Array for $VectorN<S> { impl<S: Copy> Array for $VectorN<S> {
type Element = S; type Element = S;
#[inline] fn sum(self) -> S where S: Add<Output = S> { fold_array!(add, { $(self.$field),+ }) } #[inline]
#[inline] fn product(self) -> S where S: Mul<Output = S> { fold_array!(mul, { $(self.$field),+ }) } fn from_value(scalar: S) -> $VectorN<S> {
#[inline] fn min(self) -> S where S: PartialOrd { fold_array!(partial_min, { $(self.$field),+ }) } $VectorN { $($field: scalar),+ }
#[inline] fn max(self) -> S where S: PartialOrd { fold_array!(partial_max, { $(self.$field),+ }) } }
#[inline]
fn sum(self) -> S where S: Add<Output = S> {
fold_array!(add, { $(self.$field),+ })
}
#[inline]
fn product(self) -> S where S: Mul<Output = S> {
fold_array!(mul, { $(self.$field),+ })
}
#[inline]
fn min(self) -> S where S: PartialOrd {
fold_array!(partial_min, { $(self.$field),+ })
}
#[inline]
fn max(self) -> S where S: PartialOrd {
fold_array!(partial_max, { $(self.$field),+ })
}
} }
impl<S: BaseNum> Vector for $VectorN<S> { impl<S: BaseNum> Vector for $VectorN<S> {
type Scalar = S; type Scalar = S;
#[inline] fn from_value(scalar: S) -> $VectorN<S> { $VectorN { $($field: scalar),+ } } #[inline]
fn zero() -> Self {
#[inline] fn dot(self, other: $VectorN<S>) -> S { $VectorN::mul_element_wise(self, other).sum() } Self::from_value(Self::Scalar::zero())
}
} }
impl<S: Neg<Output = S>> Neg for $VectorN<S> { impl<S: Neg<Output = S>> Neg for $VectorN<S> {
@ -461,13 +525,19 @@ impl<S: BaseNum> Vector4<S> {
} }
} }
/// Specifies geometric operations for vectors. This is only implemented for /// Vectors that also have a [dot](https://en.wikipedia.org/wiki/Dot_product)
/// vectors of float types. /// (or [inner](https://en.wikipedia.org/wiki/Inner_product_space)) product.
///
/// The dot product allows for the definition of other useful operations, like
/// finding the magnitude of a vector or normalizing it.
pub trait EuclideanVector: Vector + Sized where pub trait EuclideanVector: Vector + Sized where
// FIXME: Ugly type signatures - blocked by rust-lang/rust#24092 // FIXME: Ugly type signatures - blocked by rust-lang/rust#24092
<Self as Vector>::Scalar: BaseFloat, <Self as Vector>::Scalar: BaseFloat,
Self: ApproxEq<Epsilon = <Self as Vector>::Scalar>, Self: ApproxEq<Epsilon = <Self as Vector>::Scalar>,
{ {
/// Vector dot (or inner) product.
fn dot(self, other: Self) -> Self::Scalar;
/// Returns `true` if the vector is perpendicular (at right angles) to the /// Returns `true` if the vector is perpendicular (at right angles) to the
/// other vector. /// other vector.
fn is_perpendicular(self, other: Self) -> bool { fn is_perpendicular(self, other: Self) -> bool {
@ -493,8 +563,10 @@ pub trait EuclideanVector: Vector + Sized where
Float::sqrt(self.magnitude2()) Float::sqrt(self.magnitude2())
} }
/// The angle between the vector and `other`, in radians. /// Returns the angle between two vectors in radians.
fn angle(self, other: Self) -> Rad<Self::Scalar>; fn angle(self, other: Self) -> Rad<Self::Scalar> {
Rad::acos(Self::dot(self, other) / (self.magnitude() * other.magnitude()))
}
/// Returns a vector with the same direction, but with a magnitude of `1`. /// Returns a vector with the same direction, but with a magnitude of `1`.
#[inline] #[inline]
@ -519,7 +591,20 @@ pub trait EuclideanVector: Vector + Sized where
} }
} }
/// Dot product of two vectors.
#[inline]
pub fn dot<V: EuclideanVector>(a: V, b: V) -> V::Scalar where
V::Scalar: BaseFloat,
{
V::dot(a, b)
}
impl<S: BaseFloat> EuclideanVector for Vector2<S> { impl<S: BaseFloat> EuclideanVector for Vector2<S> {
#[inline]
fn dot(self, other: Vector2<S>) -> S {
Vector2::mul_element_wise(self, other).sum()
}
#[inline] #[inline]
fn angle(self, other: Vector2<S>) -> Rad<S> { fn angle(self, other: Vector2<S>) -> Rad<S> {
Rad::atan2(Self::perp_dot(self, other), Self::dot(self, other)) Rad::atan2(Self::perp_dot(self, other), Self::dot(self, other))
@ -527,6 +612,11 @@ impl<S: BaseFloat> EuclideanVector for Vector2<S> {
} }
impl<S: BaseFloat> EuclideanVector for Vector3<S> { impl<S: BaseFloat> EuclideanVector for Vector3<S> {
#[inline]
fn dot(self, other: Vector3<S>) -> S {
Vector3::mul_element_wise(self, other).sum()
}
#[inline] #[inline]
fn angle(self, other: Vector3<S>) -> Rad<S> { fn angle(self, other: Vector3<S>) -> Rad<S> {
Rad::atan2(self.cross(other).magnitude(), Self::dot(self, other)) Rad::atan2(self.cross(other).magnitude(), Self::dot(self, other))
@ -535,8 +625,8 @@ impl<S: BaseFloat> EuclideanVector for Vector3<S> {
impl<S: BaseFloat> EuclideanVector for Vector4<S> { impl<S: BaseFloat> EuclideanVector for Vector4<S> {
#[inline] #[inline]
fn angle(self, other: Vector4<S>) -> Rad<S> { fn dot(self, other: Vector4<S>) -> S {
Rad::acos(Self::dot(self, other) / (self.magnitude() * other.magnitude())) Vector4::mul_element_wise(self, other).sum()
} }
} }

View file

@ -122,9 +122,9 @@ fn test_rem() {
#[test] #[test]
fn test_dot() { fn test_dot() {
assert_eq!(Vector2::new(1isize, 2isize).dot(Vector2::new(3isize, 4isize)), 11isize); assert_eq!(Vector2::new(1.0, 2.0).dot(Vector2::new(3.0, 4.0)), 11.0);
assert_eq!(Vector3::new(1isize, 2isize, 3isize).dot(Vector3::new(4isize, 5isize, 6isize)), 32isize); assert_eq!(Vector3::new(1.0, 2.0, 3.0).dot(Vector3::new(4.0, 5.0, 6.0)), 32.0);
assert_eq!(Vector4::new(1isize, 2isize, 3isize, 4isize).dot(Vector4::new(5isize, 6isize, 7isize, 8isize)), 70isize); assert_eq!(Vector4::new(1.0, 2.0, 3.0, 4.0).dot(Vector4::new(5.0, 6.0, 7.0, 8.0)), 70.0);
} }
#[test] #[test]