// 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 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms.
use core::cmp::{Eq, PartialEq}; use core::fmt; use core::hash::Hash; use core::marker::PhantomData; use core::ops::{Add, Div, Mul, Neg, Sub};
#[cfg(feature = "bytemuck")] use bytemuck::{Pod, Zeroable}; #[cfg(feature = "mint")] use mint; use num_traits::NumCast; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize};
/// A 3d transform stored as a column-major 4 by 4 matrix. /// /// Transforms can be parametrized over the source and destination units, to describe a /// transformation from a space to another. /// For example, `Transform3D<f32, WorldSpace, ScreenSpace>::transform_point3d` /// takes a `Point3D<f32, WorldSpace>` and returns a `Point3D<f32, ScreenSpace>`. /// /// Transforms expose a set of convenience methods for pre- and post-transformations. /// Pre-transformations (`pre_*` methods) correspond to adding an operation that is /// applied before the rest of the transformation, while post-transformations (`then_*` /// methods) add an operation that is applied after. /// /// When translating Transform3D into general matrix representations, consider that the /// representation follows the column major notation with column vectors. /// /// ```text /// |x'| | m11 m12 m13 m14 | |x| /// |y'| | m21 m22 m23 m24 | |y| /// |z'| = | m31 m32 m33 m34 | x |y| /// |w | | m41 m42 m43 m44 | |1| /// ``` /// /// The translation terms are m41, m42 and m43. #[repr(C)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(
feature = "serde",
serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
)] #[rustfmt::skip] pubstruct Transform3D<T, Src, Dst> { pub m11: T, pub m12: T, pub m13: T, pub m14: T, pub m21: T, pub m22: T, pub m23: T, pub m24: T, pub m31: T, pub m32: T, pub m33: T, pub m34: T, pub m41: T, pub m42: T, pub m43: T, pub m44: T, #[doc(hidden)] pub _unit: PhantomData<(Src, Dst)>,
}
#[cfg(feature = "arbitrary")] impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Transform3D<T, Src, Dst> where
T: arbitrary::Arbitrary<'a>,
{ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { let (m11, m12, m13, m14) = arbitrary::Arbitrary::arbitrary(u)?; let (m21, m22, m23, m24) = arbitrary::Arbitrary::arbitrary(u)?; let (m31, m32, m33, m34) = arbitrary::Arbitrary::arbitrary(u)?; let (m41, m42, m43, m44) = arbitrary::Arbitrary::arbitrary(u)?;
/// Returns `true` if this transform can be represented with a `Transform2D`. /// /// See <https://drafts.csswg.org/css-transforms/#2d-transform> #[inline] pubfn is_2d(&self) -> bool where
T: Zero + One + PartialEq,
{ let (_0, _1): (T, T) = (Zero::zero(), One::one()); self.m31 == _0
&& self.m32 == _0
&& self.m13 == _0
&& self.m23 == _0
&& self.m43 == _0
&& self.m14 == _0
&& self.m24 == _0
&& self.m34 == _0
&& self.m33 == _1
&& self.m44 == _1
}
}
impl<T: Copy, Src, Dst> Transform3D<T, Src, Dst> { /// Returns an array containing this transform's terms. /// /// The terms are laid out in the same order as they are /// specified in `Transform3D::new`, that is following the /// column-major-column-vector matrix notation. /// /// For example the translation terms are found on the /// 13th, 14th and 15th slots of the array. #[inline] #[rustfmt::skip] pubfn to_array(&self) -> [T; 16] {
[ 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 transform's terms transposed. /// /// The terms are laid out in transposed order from the same order of /// `Transform3D::new` and `Transform3D::to_array`, that is following /// the row-major-column-vector matrix notation. /// /// For example the translation terms are found at indices 3, 7 and 11 /// of the array. #[inline] #[rustfmt::skip] pubfn to_array_transposed(&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
]
}
/// Equivalent to `to_array` with elements packed four at a time /// in an array of arrays. #[inline] #[rustfmt::skip] pubfn to_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],
]
}
/// Equivalent to `to_array_transposed` with elements packed /// four at a time in an array of arrays. #[inline] #[rustfmt::skip] pubfn to_arrays_transposed(&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],
]
}
/// Create a transform providing its components via an array /// of 16 elements instead of as individual parameters. /// /// The order of the components corresponds to the /// column-major-column-vector matrix notation (the same order /// as `Transform3D::new`). #[inline] #[rustfmt::skip] pubfn from_array(array: [T; 16]) -> Self { Self::new(
array[0], array[1], array[2], array[3],
array[4], array[5], array[6], array[7],
array[8], array[9], array[10], array[11],
array[12], array[13], array[14], array[15],
)
}
/// Equivalent to `from_array` with elements packed four at a time /// in an array of arrays. /// /// The order of the components corresponds to the /// column-major-column-vector matrix notation (the same order /// as `Transform3D::new`). #[inline] #[rustfmt::skip] pubfn from_arrays(array: [[T; 4]; 4]) -> Self { Self::new(
array[0][0], array[0][1], array[0][2], array[0][3],
array[1][0], array[1][1], array[1][2], array[1][3],
array[2][0], array[2][1], array[2][2], array[2][3],
array[3][0], array[3][1], array[3][2], array[3][3],
)
}
/// Tag a unitless value with units. #[inline] #[rustfmt::skip] pubfn from_untyped(m: &Transform3D<T, UnknownUnit, UnknownUnit>) -> Self {
Transform3D::new(
m.m11, m.m12, m.m13, m.m14,
m.m21, m.m22, m.m23, m.m24,
m.m31, m.m32, m.m33, m.m34,
m.m41, m.m42, m.m43, m.m44,
)
}
/// Drop the units, preserving only the numeric value. #[inline] #[rustfmt::skip] pubfn to_untyped(&self) -> Transform3D<T, UnknownUnit, UnknownUnit> {
Transform3D::new( 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 transform with a different source unit. #[inline] #[rustfmt::skip] pubfn with_source<NewSrc>(&self) -> Transform3D<T, NewSrc, Dst> {
Transform3D::new( 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 transform with a different destination unit. #[inline] #[rustfmt::skip] pubfn with_destination<NewDst>(&self) -> Transform3D<T, Src, NewDst> {
Transform3D::new( 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,
)
}
/// Create a 2D transform picking the relevant terms from this transform. /// /// This method assumes that self represents a 2d transformation, callers /// should check that [`self.is_2d()`] returns `true` beforehand. /// /// [`self.is_2d()`]: #method.is_2d pubfn to_2d(&self) -> Transform2D<T, Src, Dst> {
Transform2D::new(self.m11, self.m12, self.m21, self.m22, self.m41, self.m42)
}
}
/// Intentional not public, because it checks for exact equivalence /// while most consumers will probably want some sort of approximate /// equivalence to deal with floating-point errors. #[inline] fn is_identity(&self) -> bool where
T: PartialEq,
{
*self == Self::identity()
}
/// Create a 2d skew transform. /// /// See <https://drafts.csswg.org/css-transforms/#funcdef-skew> #[rustfmt::skip] pubfn skew(alpha: Angle<T>, beta: Angle<T>) -> Self where
T: Trig,
{ let _0 = || T::zero(); let _1 = || T::one(); let (sx, sy) = (beta.radians.tan(), alpha.radians.tan());
/// Returns a transform with a scale applied before self's transformation. #[must_use] #[rustfmt::skip] pubfn pre_scale(&self, x: T, y: T, z: T) -> Self where
T: Copy + Add<Output = T> + Mul<Output = T>,
{
Transform3D::new( self.m11 * x, self.m12 * x, self.m13 * x, self.m14 * x, self.m21 * y, self.m22 * y, self.m23 * y, self.m24 * y, self.m31 * z, self.m32 * z, self.m33 * z, self.m34 * z, self.m41 , self.m42, self.m43, self.m44
)
}
/// Returns a transform with a scale applied after self's transformation. #[must_use] pubfn then_scale(&self, x: T, y: T, z: T) -> Self where
T: Copy + Add<Output = T> + Mul<Output = T>,
{ self.then(&Transform3D::scale(x, y, z))
}
}
/// Methods for apply transformations to objects impl<T, Src, Dst> Transform3D<T, Src, Dst> where
T: Copy + Add<Output = T> + Mul<Output = T>,
{ /// Returns the homogeneous vector corresponding to the transformed 2d point. /// /// The input point must be use the unit Src, and the returned point has the unit Dst. #[inline] #[rustfmt::skip] pubfn transform_point2d_homogeneous(
&self, p: Point2D<T, Src>
) -> HomogeneousVector<T, Dst> { 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 z = p.x * self.m13 + p.y * self.m23 + self.m43; let w = p.x * self.m14 + p.y * self.m24 + self.m44;
HomogeneousVector::new(x, y, z, w)
}
/// Returns the given 2d point transformed by this transform, if the transform makes sense, /// or `None` otherwise. /// /// The input point must be use the unit Src, and the returned point has the unit Dst. #[inline] pubfn transform_point2d(&self, p: Point2D<T, Src>) -> Option<Point2D<T, Dst>> where
T: Div<Output = T> + Zero + PartialOrd,
{ //Note: could use `transform_point2d_homogeneous()` but it would waste the calculus of `z` let w = p.x * self.m14 + p.y * self.m24 + self.m44; if w > T::zero() { let x = p.x * self.m11 + p.y * self.m21 + self.m41; let y = p.x * self.m12 + p.y * self.m22 + self.m42;
/// 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] pubfn transform_vector2d(&self, v: Vector2D<T, Src>) -> Vector2D<T, Dst> {
vec2(
v.x * self.m11 + v.y * self.m21,
v.x * self.m12 + v.y * self.m22,
)
}
/// Returns the homogeneous vector corresponding to the transformed 3d point. /// /// The input point must be use the unit Src, and the returned point has the unit Dst. #[inline] pubfn transform_point3d_homogeneous(&self, p: Point3D<T, Src>) -> HomogeneousVector<T, Dst> { 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;
HomogeneousVector::new(x, y, z, w)
}
/// Returns the given 3d point transformed by this transform, if the transform makes sense, /// or `None` otherwise. /// /// The input point must be use the unit Src, and the returned point has the unit Dst. #[inline] pubfn transform_point3d(&self, p: Point3D<T, Src>) -> Option<Point3D<T, Dst>> where
T: Div<Output = T> + Zero + PartialOrd,
{ self.transform_point3d_homogeneous(p).to_point3d()
}
/// 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] pubfn transform_vector3d(&self, v: Vector3D<T, Src>) -> Vector3D<T, Dst> {
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, if the transform makes sense for it, or `None` otherwise. pubfn outer_transformed_rect(&self, rect: &Rect<T, Src>) -> Option<Rect<T, Dst>> where
T: Sub<Output = T> + Div<Output = T> + Zero + PartialOrd,
{ let min = rect.min(); let max = rect.max();
Some(Rect::from_points(&[ self.transform_point2d(min)?, self.transform_point2d(max)?, self.transform_point2d(point2(max.x, min.y))?, self.transform_point2d(point2(min.x, max.y))?,
]))
}
/// Returns a 2d box that encompasses the result of transforming the given box by this /// transform, if the transform makes sense for it, or `None` otherwise. pubfn outer_transformed_box2d(&self, b: &Box2D<T, Src>) -> Option<Box2D<T, Dst>> where
T: Sub<Output = T> + Div<Output = T> + Zero + PartialOrd,
{
Some(Box2D::from_points(&[ self.transform_point2d(b.min)?, self.transform_point2d(b.max)?, self.transform_point2d(point2(b.max.x, b.min.y))?, self.transform_point2d(point2(b.min.x, b.max.y))?,
]))
}
/// Returns a 3d box that encompasses the result of transforming the given box by this /// transform, if the transform makes sense for it, or `None` otherwise. pubfn outer_transformed_box3d(&self, b: &Box3D<T, Src>) -> Option<Box3D<T, Dst>> where
T: Sub<Output = T> + Div<Output = T> + Zero + PartialOrd,
{
Some(Box3D::from_points(&[ self.transform_point3d(point3(b.min.x, b.min.y, b.min.z))?, self.transform_point3d(point3(b.min.x, b.min.y, b.max.z))?, self.transform_point3d(point3(b.min.x, b.max.y, b.min.z))?, self.transform_point3d(point3(b.min.x, b.max.y, b.max.z))?, self.transform_point3d(point3(b.max.x, b.min.y, b.min.z))?, self.transform_point3d(point3(b.max.x, b.min.y, b.max.z))?, self.transform_point3d(point3(b.max.x, b.max.y, b.min.z))?, self.transform_point3d(point3(b.max.x, b.max.y, b.max.z))?,
]))
}
}
/// Check whether shapes on the XY plane with Z pointing towards the /// screen transformed by this matrix would be facing back. #[rustfmt::skip] pubfn is_backface_visible(&self) -> bool { // inverse().m33 < 0; let det = self.determinant(); let m33 = self.m12 * self.m24 * self.m41 - self.m14 * self.m22 * self.m41 + self.m14 * self.m21 * self.m42 - self.m11 * self.m24 * self.m42 - self.m12 * self.m21 * self.m44 + self.m11 * self.m22 * self.m44; let _0: T = Zero::zero();
(m33 * det) < _0
}
/// Returns whether it is possible to compute the inverse transform. #[inline] pubfn is_invertible(&self) -> bool { self.determinant() != Zero::zero()
}
/// Returns the inverse transform if possible. pubfn inverse(&self) -> Option<Transform3D<T, Dst, Src>> { let det = self.determinant();
if det == Zero::zero() { return None;
}
// todo(gw): this could be made faster by special casing // for simpler transform types. #[rustfmt::skip] let m = Transform3D::new( 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,
/// Multiplies all of the transform's component by a scalar and returns the result. #[must_use] #[rustfmt::skip] pubfn mul_s(&self, x: T) -> Self {
Transform3D::new( 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, self.m41 * x, self.m42 * x, self.m43 * x, self.m44 * x
)
}
/// Convenience function to create a scale transform from a `Scale`. pubfn from_scale(scale: Scale<T, Src, Dst>) -> Self {
Transform3D::scale(scale.get(), scale.get(), scale.get())
}
}
impl<T, Src, Dst> Transform3D<T, Src, Dst> where
T: Copy + Mul<Output = T> + Div<Output = T> + Zero + One + PartialEq,
{ /// Returns a projection of this transform in 2d space. pubfn project_to_2d(&self) -> Self { let (_0, _1): (T, T) = (Zero::zero(), One::one());
// Try to normalize perspective when possible to convert to a 2d matrix. // Some matrices, such as those derived from perspective transforms, can // modify m44 from 1, while leaving the rest of the fourth column // (m14, m24) at 0. In this case, after resetting the third row and // third column above, the value of m44 functions only to scale the // coordinate transform divide by W. The matrix can be converted to // a true 2D matrix by normalizing out the scaling effect of m44 on // the remaining components ahead of time. ifself.m14 == _0 && self.m24 == _0 && self.m44 != _0 && self.m44 != _1 { let scale = _1 / self.m44;
result.m11 = result.m11 * scale;
result.m12 = result.m12 * scale;
result.m21 = result.m21 * scale;
result.m22 = result.m22 * scale;
result.m41 = result.m41 * scale;
result.m42 = result.m42 * scale;
result.m44 = _1;
}
result
}
}
impl<T: NumCast + Copy, Src, Dst> Transform3D<T, Src, Dst> { /// Cast from one numeric representation to another, preserving the units. #[inline] pubfn cast<NewT: NumCast>(&self) -> Transform3D<NewT, Src, Dst> { self.try_cast().unwrap()
}
impl<T: ApproxEq<T>, Src, Dst> Transform3D<T, Src, Dst> { /// Returns true is this transform is approximately equal to the other one, using /// T's default epsilon value. /// /// The same as [`ApproxEq::approx_eq()`] but available without importing trait. /// /// [`ApproxEq::approx_eq()`]: ./approxeq/trait.ApproxEq.html#method.approx_eq #[inline] pubfn approx_eq(&self, other: &Self) -> bool {
<Selfas ApproxEq<T>>::approx_eq(&self, &other)
}
/// Returns true is this transform is approximately equal to the other one, using /// a provided epsilon value. /// /// The same as [`ApproxEq::approx_eq_eps()`] but available without importing trait. /// /// [`ApproxEq::approx_eq_eps()`]: ./approxeq/trait.ApproxEq.html#method.approx_eq_eps #[inline] pubfn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
<Selfas ApproxEq<T>>::approx_eq_eps(&self, &other, &eps)
}
}
impl<T: ApproxEq<T>, Src, Dst> ApproxEq<T> for Transform3D<T, Src, Dst> { #[inline] fn approx_epsilon() -> T {
T::approx_epsilon()
}
let p = point3(1.0, 3.0, 5.0); let p1 = m1.then(&m2).transform_point3d(p).unwrap(); let p2 = m2.transform_point3d(m1.transform_point3d(p).unwrap()).unwrap();
assert!(p1.approx_eq(&p2));
}
#[test] pubfn test_is_identity() { let m1 = default::Transform3D::identity();
assert!(m1.is_identity()); let m2 = m1.then_translate(vec3(0.1, 0.0, 0.0));
assert!(!m2.is_identity());
}
#[test] pubfn test_transform_vector() { // Translation does not apply to vectors. let m = Mf32::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_ne!(Some(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_ne!(Some(v2.to_point()), m.transform_point2d(v2.to_point()));
}
#[test] pubfn test_is_backface_visible() { // backface is not visible for rotate-x 0 degree. let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(0.0));
assert!(!r1.is_backface_visible()); // backface is not visible for rotate-x 45 degree. let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(PI * 0.25));
assert!(!r1.is_backface_visible()); // backface is visible for rotate-x 180 degree. let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(PI));
assert!(r1.is_backface_visible()); // backface is visible for rotate-x 225 degree. let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(PI * 1.25));
assert!(r1.is_backface_visible()); // backface is not visible for non-inverseable matrix let r1 = Mf32::scale(2.0, 0.0, 2.0);
assert!(!r1.is_backface_visible());
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.