11 3D变换模块(transform3d.rs)
transform3d.rs代码定义了一个名为 Transform3D 的 Rust 结构体,它用于表示一个3D变换矩阵。这个结构体是泛型的,包含三个类型参数:T、Src 和 Dst。其中,T 用于矩阵元素的数据类型,Src 和 Dst 用于表示变换的源和目标类型(虽然在这段代码中,Src 和 Dst 类型通过 PhantomData 引入,但并未在结构体功能上直接使用)。
一、transform3d.rs源码
// 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.#![allow(clippy::just_underscores_and_digits)]use super::{Angle, UnknownUnit};
use crate::approxeq::ApproxEq;
use crate::box2d::Box2D;
use crate::box3d::Box3D;
use crate::homogen::HomogeneousVector;
use crate::num::{One, Zero};
use crate::point::{point2, point3, Point2D, Point3D};
use crate::rect::Rect;
use crate::scale::Scale;
use crate::transform2d::Transform2D;
use crate::trig::Trig;
use crate::vector::{vec2, vec3, Vector2D, Vector3D};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]
pub struct 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>
whereT: 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)?;Ok(Transform3D {m11,m12,m13,m14,m21,m22,m23,m24,m31,m32,m33,m34,m41,m42,m43,m44,_unit: PhantomData,})}
}#[cfg(feature = "bytemuck")]
unsafe impl<T: Zeroable, Src, Dst> Zeroable for Transform3D<T, Src, Dst> {}#[cfg(feature = "bytemuck")]
unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Transform3D<T, Src, Dst> {}impl<T: Copy, Src, Dst> Copy for Transform3D<T, Src, Dst> {}impl<T: Clone, Src, Dst> Clone for Transform3D<T, Src, Dst> {fn clone(&self) -> Self {Transform3D {m11: self.m11.clone(),m12: self.m12.clone(),m13: self.m13.clone(),m14: self.m14.clone(),m21: self.m21.clone(),m22: self.m22.clone(),m23: self.m23.clone(),m24: self.m24.clone(),m31: self.m31.clone(),m32: self.m32.clone(),m33: self.m33.clone(),m34: self.m34.clone(),m41: self.m41.clone(),m42: self.m42.clone(),m43: self.m43.clone(),m44: self.m44.clone(),_unit: PhantomData,}}
}impl<T, Src, Dst> Eq for Transform3D<T, Src, Dst> where T: Eq {}impl<T, Src, Dst> PartialEq for Transform3D<T, Src, Dst>
whereT: PartialEq,
{fn eq(&self, other: &Self) -> bool {self.m11 == other.m11&& self.m12 == other.m12&& self.m13 == other.m13&& self.m14 == other.m14&& self.m21 == other.m21&& self.m22 == other.m22&& self.m23 == other.m23&& self.m24 == other.m24&& self.m31 == other.m31&& self.m32 == other.m32&& self.m33 == other.m33&& self.m34 == other.m34&& self.m41 == other.m41&& self.m42 == other.m42&& self.m43 == other.m43&& self.m44 == other.m44}
}impl<T, Src, Dst> Hash for Transform3D<T, Src, Dst>
whereT: Hash,
{fn hash<H: core::hash::Hasher>(&self, h: &mut H) {self.m11.hash(h);self.m12.hash(h);self.m13.hash(h);self.m14.hash(h);self.m21.hash(h);self.m22.hash(h);self.m23.hash(h);self.m24.hash(h);self.m31.hash(h);self.m32.hash(h);self.m33.hash(h);self.m34.hash(h);self.m41.hash(h);self.m42.hash(h);self.m43.hash(h);self.m44.hash(h);}
}impl<T, Src, Dst> Transform3D<T, Src, Dst> {/// Create a transform specifying all of it's component as a 4 by 4 matrix.////// Components are specified following column-major-column-vector matrix notation./// For example, the translation terms m41, m42, m43 are the 13rd, 14th and 15th parameters.////// ```/// use euclid::default::Transform3D;/// let tx = 1.0;/// let ty = 2.0;/// let tz = 3.0;/// let translation = Transform3D::new(/// 1.0, 0.0, 0.0, 0.0,/// 0.0, 1.0, 0.0, 0.0,/// 0.0, 0.0, 1.0, 0.0,/// tx, ty, tz, 1.0,/// );/// ```#[inline]#[allow(clippy::too_many_arguments)]#[rustfmt::skip]pub const fn new(m11: T, m12: T, m13: T, m14: T,m21: T, m22: T, m23: T, m24: T,m31: T, m32: T, m33: T, m34: T,m41: T, m42: T, m43: T, m44: T,) -> Self {Transform3D {m11, m12, m13, m14,m21, m22, m23, m24,m31, m32, m33, m34,m41, m42, m43, m44,_unit: PhantomData,}}/// Create a transform representing a 2d transformation from the components/// of a 2 by 3 matrix transformation.////// Components follow the column-major-column-vector notation (m41 and m42/// representing the translation terms).////// ```text/// m11 m12 0 0/// m21 m22 0 0/// 0 0 1 0/// m41 m42 0 1/// ```#[inline]#[rustfmt::skip]pub fn new_2d(m11: T, m12: T, m21: T, m22: T, m41: T, m42: T) -> SelfwhereT: Zero + One,{let _0 = || T::zero();let _1 = || T::one();Self::new(m11, m12, _0(), _0(),m21, m22, _0(), _0(),_0(), _0(), _1(), _0(),m41, m42, _0(), _1())}/// Returns `true` if this transform can be represented with a `Transform2D`.////// See <https://drafts.csswg.org/css-transforms/#2d-transform>#[inline]pub fn is_2d(&self) -> boolwhereT: 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]pub fn 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]pub fn 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]pub fn 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]pub fn 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]pub fn 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]pub fn 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]pub fn 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]pub fn 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]pub fn 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]pub fn 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 [`is_2d`] returns `true` beforehand.////// [`is_2d`]: Self::is_2dpub fn to_2d(&self) -> Transform2D<T, Src, Dst> {Transform2D::new(self.m11, self.m12, self.m21, self.m22, self.m41, self.m42)}
}impl<T, Src, Dst> Transform3D<T, Src, Dst>
whereT: Zero + One,
{/// Creates an identity matrix:////// ```text/// 1 0 0 0/// 0 1 0 0/// 0 0 1 0/// 0 0 0 1/// ```#[inline]pub fn identity() -> Self {Self::translation(T::zero(), T::zero(), T::zero())}/// 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) -> boolwhereT: PartialEq,{*self == Self::identity()}/// Create a 2d skew transform.////// See <https://drafts.csswg.org/css-transforms/#funcdef-skew>#[rustfmt::skip]pub fn skew(alpha: Angle<T>, beta: Angle<T>) -> SelfwhereT: Trig,{let _0 = || T::zero();let _1 = || T::one();let (sx, sy) = (beta.radians.tan(), alpha.radians.tan());Self::new(_1(), sx, _0(), _0(),sy, _1(), _0(), _0(),_0(), _0(), _1(), _0(),_0(), _0(), _0(), _1(),)}/// Create a simple perspective transform, projecting to the plane `z = -d`.////// ```text/// 1 0 0 0/// 0 1 0 0/// 0 0 1 -1/d/// 0 0 0 1/// ```////// See <https://drafts.csswg.org/css-transforms-2/#PerspectiveDefined>.pub fn perspective(d: T) -> SelfwhereT: Neg<Output = T> + Div<Output = T>,{let _0 = || T::zero();let _1 = || T::one();Self::new(_1(),_0(),_0(),_0(),_0(),_1(),_0(),_0(),_0(),_0(),_1(),-_1() / d,_0(),_0(),_0(),_1(),)}
}/// Methods for combining generic transformations
impl<T, Src, Dst> Transform3D<T, Src, Dst>
whereT: Copy + Add<Output = T> + Mul<Output = T>,
{/// Returns the multiplication of the two matrices such that mat's transformation/// applies after self's transformation.////// Assuming row vectors, this is equivalent to self * mat#[must_use]#[rustfmt::skip]pub fn then<NewDst>(&self, other: &Transform3D<T, Dst, NewDst>) -> Transform3D<T, Src, NewDst> {Transform3D::new(self.m11 * other.m11 + self.m12 * other.m21 + self.m13 * other.m31 + self.m14 * other.m41,self.m11 * other.m12 + self.m12 * other.m22 + self.m13 * other.m32 + self.m14 * other.m42,self.m11 * other.m13 + self.m12 * other.m23 + self.m13 * other.m33 + self.m14 * other.m43,self.m11 * other.m14 + self.m12 * other.m24 + self.m13 * other.m34 + self.m14 * other.m44,self.m21 * other.m11 + self.m22 * other.m21 + self.m23 * other.m31 + self.m24 * other.m41,self.m21 * other.m12 + self.m22 * other.m22 + self.m23 * other.m32 + self.m24 * other.m42,self.m21 * other.m13 + self.m22 * other.m23 + self.m23 * other.m33 + self.m24 * other.m43,self.m21 * other.m14 + self.m22 * other.m24 + self.m23 * other.m34 + self.m24 * other.m44,self.m31 * other.m11 + self.m32 * other.m21 + self.m33 * other.m31 + self.m34 * other.m41,self.m31 * other.m12 + self.m32 * other.m22 + self.m33 * other.m32 + self.m34 * other.m42,self.m31 * other.m13 + self.m32 * other.m23 + self.m33 * other.m33 + self.m34 * other.m43,self.m31 * other.m14 + self.m32 * other.m24 + self.m33 * other.m34 + self.m34 * other.m44,self.m41 * other.m11 + self.m42 * other.m21 + self.m43 * other.m31 + self.m44 * other.m41,self.m41 * other.m12 + self.m42 * other.m22 + self.m43 * other.m32 + self.m44 * other.m42,self.m41 * other.m13 + self.m42 * other.m23 + self.m43 * other.m33 + self.m44 * other.m43,self.m41 * other.m14 + self.m42 * other.m24 + self.m43 * other.m34 + self.m44 * other.m44,)}
}/// Methods for creating and combining translation transformations
impl<T, Src, Dst> Transform3D<T, Src, Dst>
whereT: Zero + One,
{/// Create a 3d translation transform:////// ```text/// 1 0 0 0/// 0 1 0 0/// 0 0 1 0/// x y z 1/// ```#[inline]#[rustfmt::skip]pub fn translation(x: T, y: T, z: T) -> Self {let _0 = || T::zero();let _1 = || T::one();Self::new(_1(), _0(), _0(), _0(),_0(), _1(), _0(), _0(),_0(), _0(), _1(), _0(),x, y, z, _1(),)}/// Returns a transform with a translation applied before self's transformation.#[must_use]pub fn pre_translate(&self, v: Vector3D<T, Src>) -> SelfwhereT: Copy + Add<Output = T> + Mul<Output = T>,{Transform3D::translation(v.x, v.y, v.z).then(self)}/// Returns a transform with a translation applied after self's transformation.#[must_use]pub fn then_translate(&self, v: Vector3D<T, Dst>) -> SelfwhereT: Copy + Add<Output = T> + Mul<Output = T>,{self.then(&Transform3D::translation(v.x, v.y, v.z))}
}/// Methods for creating and combining rotation transformations
impl<T, Src, Dst> Transform3D<T, Src, Dst>
whereT: Copy+ Add<Output = T>+ Sub<Output = T>+ Mul<Output = T>+ Div<Output = T>+ Zero+ One+ Trig,
{/// Create a 3d rotation transform from an angle / axis./// The supplied axis must be normalized.#[rustfmt::skip]pub fn rotation(x: T, y: T, z: T, theta: Angle<T>) -> Self {let (_0, _1): (T, T) = (Zero::zero(), One::one());let _2 = _1 + _1;let xx = x * x;let yy = y * y;let zz = z * z;let half_theta = theta.get() / _2;let sc = half_theta.sin() * half_theta.cos();let sq = half_theta.sin() * half_theta.sin();Transform3D::new(_1 - _2 * (yy + zz) * sq,_2 * (x * y * sq + z * sc),_2 * (x * z * sq - y * sc),_0,_2 * (x * y * sq - z * sc),_1 - _2 * (xx + zz) * sq,_2 * (y * z * sq + x * sc),_0,_2 * (x * z * sq + y * sc),_2 * (y * z * sq - x * sc),_1 - _2 * (xx + yy) * sq,_0,_0,_0,_0,_1)}/// Returns a transform with a rotation applied after self's transformation.#[must_use]pub fn then_rotate(&self, x: T, y: T, z: T, theta: Angle<T>) -> Self {self.then(&Transform3D::rotation(x, y, z, theta))}/// Returns a transform with a rotation applied before self's transformation.#[must_use]pub fn pre_rotate(&self, x: T, y: T, z: T, theta: Angle<T>) -> Self {Transform3D::rotation(x, y, z, theta).then(self)}
}/// Methods for creating and combining scale transformations
impl<T, Src, Dst> Transform3D<T, Src, Dst>
whereT: Zero + One,
{/// Create a 3d scale transform:////// ```text/// x 0 0 0/// 0 y 0 0/// 0 0 z 0/// 0 0 0 1/// ```#[inline]#[rustfmt::skip]pub fn scale(x: T, y: T, z: T) -> Self {let _0 = || T::zero();let _1 = || T::one();Self::new(x, _0(), _0(), _0(),_0(), y, _0(), _0(),_0(), _0(), z, _0(),_0(), _0(), _0(), _1(),)}/// Returns a transform with a scale applied before self's transformation.#[must_use]#[rustfmt::skip]pub fn pre_scale(&self, x: T, y: T, z: T) -> SelfwhereT: 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]pub fn then_scale(&self, x: T, y: T, z: T) -> SelfwhereT: 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>
whereT: 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]pub fn 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]pub fn transform_point2d(&self, p: Point2D<T, Src>) -> Option<Point2D<T, Dst>>whereT: 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;Some(Point2D::new(x / w, y / w))} else {None}}/// 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: 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]pub fn 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]pub fn transform_point3d(&self, p: Point3D<T, Src>) -> Option<Point3D<T, Dst>>whereT: 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]pub fn 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.pub fn outer_transformed_rect(&self, rect: &Rect<T, Src>) -> Option<Rect<T, Dst>>whereT: 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.pub fn outer_transformed_box2d(&self, b: &Box2D<T, Src>) -> Option<Box2D<T, Dst>>whereT: 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.pub fn outer_transformed_box3d(&self, b: &Box3D<T, Src>) -> Option<Box3D<T, Dst>>whereT: 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))?,]))}
}impl<T, Src, Dst> Transform3D<T, Src, Dst>
whereT: Copy+ Add<T, Output = T>+ Sub<T, Output = T>+ Mul<T, Output = T>+ Div<T, Output = T>+ Neg<Output = T>+ PartialOrd+ One+ Zero,
{/// Create an orthogonal projection transform.#[rustfmt::skip]pub fn ortho(left: T, right: T,bottom: T, top: T,near: T, far: T) -> Self {let tx = -((right + left) / (right - left));let ty = -((top + bottom) / (top - bottom));let tz = -((far + near) / (far - near));let (_0, _1): (T, T) = (Zero::zero(), One::one());let _2 = _1 + _1;Transform3D::new(_2 / (right - left), _0 , _0 , _0,_0 , _2 / (top - bottom), _0 , _0,_0 , _0 , -_2 / (far - near), _0,tx , ty , tz , _1)}/// Check whether shapes on the XY plane with Z pointing towards the/// screen transformed by this matrix would be facing back.#[rustfmt::skip]pub fn 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]pub fn is_invertible(&self) -> bool {self.determinant() != Zero::zero()}/// Returns the inverse transform if possible.pub fn 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,self.m14*self.m33*self.m42 - self.m13*self.m34*self.m42 -self.m14*self.m32*self.m43 + self.m12*self.m34*self.m43 +self.m13*self.m32*self.m44 - self.m12*self.m33*self.m44,self.m13*self.m24*self.m42 - self.m14*self.m23*self.m42 +self.m14*self.m22*self.m43 - self.m12*self.m24*self.m43 -self.m13*self.m22*self.m44 + self.m12*self.m23*self.m44,self.m14*self.m23*self.m32 - self.m13*self.m24*self.m32 -self.m14*self.m22*self.m33 + self.m12*self.m24*self.m33 +self.m13*self.m22*self.m34 - self.m12*self.m23*self.m34,self.m24*self.m33*self.m41 - self.m23*self.m34*self.m41 -self.m24*self.m31*self.m43 + self.m21*self.m34*self.m43 +self.m23*self.m31*self.m44 - self.m21*self.m33*self.m44,self.m13*self.m34*self.m41 - self.m14*self.m33*self.m41 +self.m14*self.m31*self.m43 - self.m11*self.m34*self.m43 -self.m13*self.m31*self.m44 + self.m11*self.m33*self.m44,self.m14*self.m23*self.m41 - self.m13*self.m24*self.m41 -self.m14*self.m21*self.m43 + self.m11*self.m24*self.m43 +self.m13*self.m21*self.m44 - self.m11*self.m23*self.m44,self.m13*self.m24*self.m31 - self.m14*self.m23*self.m31 +self.m14*self.m21*self.m33 - self.m11*self.m24*self.m33 -self.m13*self.m21*self.m34 + self.m11*self.m23*self.m34,self.m22*self.m34*self.m41 - self.m24*self.m32*self.m41 +self.m24*self.m31*self.m42 - self.m21*self.m34*self.m42 -self.m22*self.m31*self.m44 + self.m21*self.m32*self.m44,self.m14*self.m32*self.m41 - self.m12*self.m34*self.m41 -self.m14*self.m31*self.m42 + self.m11*self.m34*self.m42 +self.m12*self.m31*self.m44 - self.m11*self.m32*self.m44,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,self.m14*self.m22*self.m31 - self.m12*self.m24*self.m31 -self.m14*self.m21*self.m32 + self.m11*self.m24*self.m32 +self.m12*self.m21*self.m34 - self.m11*self.m22*self.m34,self.m23*self.m32*self.m41 - self.m22*self.m33*self.m41 -self.m23*self.m31*self.m42 + self.m21*self.m33*self.m42 +self.m22*self.m31*self.m43 - self.m21*self.m32*self.m43,self.m12*self.m33*self.m41 - self.m13*self.m32*self.m41 +self.m13*self.m31*self.m42 - self.m11*self.m33*self.m42 -self.m12*self.m31*self.m43 + self.m11*self.m32*self.m43,self.m13*self.m22*self.m41 - self.m12*self.m23*self.m41 -self.m13*self.m21*self.m42 + self.m11*self.m23*self.m42 +self.m12*self.m21*self.m43 - self.m11*self.m22*self.m43,self.m12*self.m23*self.m31 - self.m13*self.m22*self.m31 +self.m13*self.m21*self.m32 - self.m11*self.m23*self.m32 -self.m12*self.m21*self.m33 + self.m11*self.m22*self.m33);let _1: T = One::one();Some(m.mul_s(_1 / det))}/// Compute the determinant of the transform.#[rustfmt::skip]pub fn determinant(&self) -> T {self.m14 * self.m23 * self.m32 * self.m41 -self.m13 * self.m24 * self.m32 * self.m41 -self.m14 * self.m22 * self.m33 * self.m41 +self.m12 * self.m24 * self.m33 * self.m41 +self.m13 * self.m22 * self.m34 * self.m41 -self.m12 * self.m23 * self.m34 * self.m41 -self.m14 * self.m23 * self.m31 * self.m42 +self.m13 * self.m24 * self.m31 * self.m42 +self.m14 * self.m21 * self.m33 * self.m42 -self.m11 * self.m24 * self.m33 * self.m42 -self.m13 * self.m21 * self.m34 * self.m42 +self.m11 * self.m23 * self.m34 * self.m42 +self.m14 * self.m22 * self.m31 * self.m43 -self.m12 * self.m24 * self.m31 * self.m43 -self.m14 * self.m21 * self.m32 * self.m43 +self.m11 * self.m24 * self.m32 * self.m43 +self.m12 * self.m21 * self.m34 * self.m43 -self.m11 * self.m22 * self.m34 * self.m43 -self.m13 * self.m22 * self.m31 * self.m44 +self.m12 * self.m23 * self.m31 * self.m44 +self.m13 * self.m21 * self.m32 * self.m44 -self.m11 * self.m23 * self.m32 * self.m44 -self.m12 * self.m21 * self.m33 * self.m44 +self.m11 * self.m22 * self.m33 * self.m44}/// Multiplies all of the transform's component by a scalar and returns the result.#[must_use]#[rustfmt::skip]pub fn 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`.pub fn from_scale(scale: Scale<T, Src, Dst>) -> Self {Transform3D::scale(scale.get(), scale.get(), scale.get())}
}impl<T, Src, Dst> Transform3D<T, Src, Dst>
whereT: Copy + Mul<Output = T> + Div<Output = T> + Zero + One + PartialEq,
{/// Returns a projection of this transform in 2d space.pub fn project_to_2d(&self) -> Self {let (_0, _1): (T, T) = (Zero::zero(), One::one());let mut result = self.clone();result.m31 = _0;result.m32 = _0;result.m13 = _0;result.m23 = _0;result.m33 = _1;result.m43 = _0;result.m34 = _0;// 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.if self.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]pub fn cast<NewT: NumCast>(&self) -> Transform3D<NewT, Src, Dst> {self.try_cast().unwrap()}/// Fallible cast from one numeric representation to another, preserving the units.#[rustfmt::skip]pub fn try_cast<NewT: NumCast>(&self) -> Option<Transform3D<NewT, Src, Dst>> {match (NumCast::from(self.m11), NumCast::from(self.m12),NumCast::from(self.m13), NumCast::from(self.m14),NumCast::from(self.m21), NumCast::from(self.m22),NumCast::from(self.m23), NumCast::from(self.m24),NumCast::from(self.m31), NumCast::from(self.m32),NumCast::from(self.m33), NumCast::from(self.m34),NumCast::from(self.m41), NumCast::from(self.m42),NumCast::from(self.m43), NumCast::from(self.m44)) {(Some(m11), Some(m12), Some(m13), Some(m14),Some(m21), Some(m22), Some(m23), Some(m24),Some(m31), Some(m32), Some(m33), Some(m34),Some(m41), Some(m42), Some(m43), Some(m44)) => {Some(Transform3D::new(m11, m12, m13, m14,m21, m22, m23, m24,m31, m32, m33, m34,m41, m42, m43, m44))},_ => None}}
}impl<T: ApproxEq<T>, Src, Dst> Transform3D<T, Src, Dst> {/// Returns `true` if 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.#[inline]pub fn approx_eq(&self, other: &Self) -> bool {<Self as ApproxEq<T>>::approx_eq(self, other)}/// Returns `true` if 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.#[inline]pub fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {<Self as 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()}#[rustfmt::skip]fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {self.m11.approx_eq_eps(&other.m11, eps) && self.m12.approx_eq_eps(&other.m12, eps) &&self.m13.approx_eq_eps(&other.m13, eps) && self.m14.approx_eq_eps(&other.m14, eps) &&self.m21.approx_eq_eps(&other.m21, eps) && self.m22.approx_eq_eps(&other.m22, eps) &&self.m23.approx_eq_eps(&other.m23, eps) && self.m24.approx_eq_eps(&other.m24, eps) &&self.m31.approx_eq_eps(&other.m31, eps) && self.m32.approx_eq_eps(&other.m32, eps) &&self.m33.approx_eq_eps(&other.m33, eps) && self.m34.approx_eq_eps(&other.m34, eps) &&self.m41.approx_eq_eps(&other.m41, eps) && self.m42.approx_eq_eps(&other.m42, eps) &&self.m43.approx_eq_eps(&other.m43, eps) && self.m44.approx_eq_eps(&other.m44, eps)}
}impl<T, Src, Dst> Default for Transform3D<T, Src, Dst>
whereT: Zero + One,
{/// Returns the [identity transform](Self::identity).fn default() -> Self {Self::identity()}
}impl<T, Src, Dst> fmt::Debug for Transform3D<T, Src, Dst>
whereT: Copy + fmt::Debug + PartialEq + One + Zero,
{fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {if self.is_identity() {write!(f, "[I]")} else {self.to_array().fmt(f)}}
}#[cfg(feature = "mint")]
impl<T, Src, Dst> From<mint::RowMatrix4<T>> for Transform3D<T, Src, Dst> {#[rustfmt::skip]fn from(m: mint::RowMatrix4<T>) -> Self {Transform3D {m11: m.x.x, m12: m.x.y, m13: m.x.z, m14: m.x.w,m21: m.y.x, m22: m.y.y, m23: m.y.z, m24: m.y.w,m31: m.z.x, m32: m.z.y, m33: m.z.z, m34: m.z.w,m41: m.w.x, m42: m.w.y, m43: m.w.z, m44: m.w.w,_unit: PhantomData,}}
}
#[cfg(feature = "mint")]
impl<T, Src, Dst> From<Transform3D<T, Src, Dst>> for mint::RowMatrix4<T> {#[rustfmt::skip]fn from(t: Transform3D<T, Src, Dst>) -> Self {mint::RowMatrix4 {x: mint::Vector4 { x: t.m11, y: t.m12, z: t.m13, w: t.m14 },y: mint::Vector4 { x: t.m21, y: t.m22, z: t.m23, w: t.m24 },z: mint::Vector4 { x: t.m31, y: t.m32, z: t.m33, w: t.m34 },w: mint::Vector4 { x: t.m41, y: t.m42, z: t.m43, w: t.m44 },}}
}#[cfg(test)]
mod tests {use super::*;use crate::approxeq::ApproxEq;use crate::default;use crate::{point2, point3};use core::f32::consts::{FRAC_PI_2, PI};type Mf32 = default::Transform3D<f32>;// For convenience.fn rad(v: f32) -> Angle<f32> {Angle::radians(v)}#[test]pub fn test_translation() {let t1 = Mf32::translation(1.0, 2.0, 3.0);let t2 = Mf32::identity().pre_translate(vec3(1.0, 2.0, 3.0));let t3 = Mf32::identity().then_translate(vec3(1.0, 2.0, 3.0));assert_eq!(t1, t2);assert_eq!(t1, t3);assert_eq!(t1.transform_point3d(point3(1.0, 1.0, 1.0)),Some(point3(2.0, 3.0, 4.0)));assert_eq!(t1.transform_point2d(point2(1.0, 1.0)),Some(point2(2.0, 3.0)));assert_eq!(t1.then(&t1), Mf32::translation(2.0, 4.0, 6.0));assert!(!t1.is_2d());assert_eq!(Mf32::translation(1.0, 2.0, 3.0).to_2d(),Transform2D::translation(1.0, 2.0));}#[test]pub fn test_rotation() {let r1 = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2));let r2 = Mf32::identity().pre_rotate(0.0, 0.0, 1.0, rad(FRAC_PI_2));let r3 = Mf32::identity().then_rotate(0.0, 0.0, 1.0, rad(FRAC_PI_2));assert_eq!(r1, r2);assert_eq!(r1, r3);assert!(r1.transform_point3d(point3(1.0, 2.0, 3.0)).unwrap().approx_eq(&point3(-2.0, 1.0, 3.0)));assert!(r1.transform_point2d(point2(1.0, 2.0)).unwrap().approx_eq(&point2(-2.0, 1.0)));assert!(r1.then(&r1).approx_eq(&Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2 * 2.0))));assert!(r1.is_2d());assert!(r1.to_2d().approx_eq(&Transform2D::rotation(rad(FRAC_PI_2))));}#[test]pub fn test_scale() {let s1 = Mf32::scale(2.0, 3.0, 4.0);let s2 = Mf32::identity().pre_scale(2.0, 3.0, 4.0);let s3 = Mf32::identity().then_scale(2.0, 3.0, 4.0);assert_eq!(s1, s2);assert_eq!(s1, s3);assert!(s1.transform_point3d(point3(2.0, 2.0, 2.0)).unwrap().approx_eq(&point3(4.0, 6.0, 8.0)));assert!(s1.transform_point2d(point2(2.0, 2.0)).unwrap().approx_eq(&point2(4.0, 6.0)));assert_eq!(s1.then(&s1), Mf32::scale(4.0, 9.0, 16.0));assert!(!s1.is_2d());assert_eq!(Mf32::scale(2.0, 3.0, 0.0).to_2d(),Transform2D::scale(2.0, 3.0));}#[test]pub fn test_pre_then_scale() {let m = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2)).then_translate(vec3(6.0, 7.0, 8.0));let s = Mf32::scale(2.0, 3.0, 4.0);assert_eq!(m.then(&s), m.then_scale(2.0, 3.0, 4.0));}#[test]#[rustfmt::skip]pub fn test_ortho() {let (left, right, bottom, top) = (0.0f32, 1.0f32, 0.1f32, 1.0f32);let (near, far) = (-1.0f32, 1.0f32);let result = Mf32::ortho(left, right, bottom, top, near, far);let expected = Mf32::new(2.0, 0.0, 0.0, 0.0,0.0, 2.22222222, 0.0, 0.0,0.0, 0.0, -1.0, 0.0,-1.0, -1.22222222, -0.0, 1.0);assert!(result.approx_eq(&expected));}#[test]pub fn test_is_2d() {assert!(Mf32::identity().is_2d());assert!(Mf32::rotation(0.0, 0.0, 1.0, rad(0.7854)).is_2d());assert!(!Mf32::rotation(0.0, 1.0, 0.0, rad(0.7854)).is_2d());}#[test]#[rustfmt::skip]pub fn test_new_2d() {let m1 = Mf32::new_2d(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);let m2 = Mf32::new(1.0, 2.0, 0.0, 0.0,3.0, 4.0, 0.0, 0.0,0.0, 0.0, 1.0, 0.0,5.0, 6.0, 0.0, 1.0);assert_eq!(m1, m2);}#[test]pub fn test_inverse_simple() {let m1 = Mf32::identity();let m2 = m1.inverse().unwrap();assert!(m1.approx_eq(&m2));}#[test]pub fn test_inverse_scale() {let m1 = Mf32::scale(1.5, 0.3, 2.1);let m2 = m1.inverse().unwrap();assert!(m1.then(&m2).approx_eq(&Mf32::identity()));assert!(m2.then(&m1).approx_eq(&Mf32::identity()));}#[test]pub fn test_inverse_translate() {let m1 = Mf32::translation(-132.0, 0.3, 493.0);let m2 = m1.inverse().unwrap();assert!(m1.then(&m2).approx_eq(&Mf32::identity()));assert!(m2.then(&m1).approx_eq(&Mf32::identity()));}#[test]pub fn test_inverse_rotate() {let m1 = Mf32::rotation(0.0, 1.0, 0.0, rad(1.57));let m2 = m1.inverse().unwrap();assert!(m1.then(&m2).approx_eq(&Mf32::identity()));assert!(m2.then(&m1).approx_eq(&Mf32::identity()));}#[test]pub fn test_inverse_transform_point_2d() {let m1 = Mf32::translation(100.0, 200.0, 0.0);let m2 = m1.inverse().unwrap();assert!(m1.then(&m2).approx_eq(&Mf32::identity()));assert!(m2.then(&m1).approx_eq(&Mf32::identity()));let p1 = point2(1000.0, 2000.0);let p2 = m1.transform_point2d(p1);assert_eq!(p2, Some(point2(1100.0, 2200.0)));let p3 = m2.transform_point2d(p2.unwrap());assert_eq!(p3, Some(p1));}#[test]fn test_inverse_none() {assert!(Mf32::scale(2.0, 0.0, 2.0).inverse().is_none());assert!(Mf32::scale(2.0, 2.0, 2.0).inverse().is_some());}#[test]pub fn test_pre_post() {let m1 = default::Transform3D::identity().then_scale(1.0, 2.0, 3.0).then_translate(vec3(1.0, 2.0, 3.0));let m2 = default::Transform3D::identity().pre_translate(vec3(1.0, 2.0, 3.0)).pre_scale(1.0, 2.0, 3.0);assert!(m1.approx_eq(&m2));let r = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2));let t = Mf32::translation(2.0, 3.0, 0.0);let a = point3(1.0, 1.0, 1.0);assert!(r.then(&t).transform_point3d(a).unwrap().approx_eq(&point3(1.0, 4.0, 1.0)));assert!(t.then(&r).transform_point3d(a).unwrap().approx_eq(&point3(-4.0, 3.0, 1.0)));assert!(t.then(&r).transform_point3d(a).unwrap().approx_eq(&r.transform_point3d(t.transform_point3d(a).unwrap()).unwrap()));}#[test]fn test_size_of() {use core::mem::size_of;assert_eq!(size_of::<default::Transform3D<f32>>(),16 * size_of::<f32>());assert_eq!(size_of::<default::Transform3D<f64>>(),16 * size_of::<f64>());}#[test]#[rustfmt::skip]pub fn test_transform_associativity() {let m1 = Mf32::new(3.0, 2.0, 1.5, 1.0,0.0, 4.5, -1.0, -4.0,0.0, 3.5, 2.5, 40.0,0.0, 3.0, 0.0, 1.0);let m2 = Mf32::new(1.0, -1.0, 3.0, 0.0,-1.0, 0.5, 0.0, 2.0,1.5, -2.0, 6.0, 0.0,-2.5, 6.0, 1.0, 1.0);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]pub fn 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]pub fn 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/pointslet 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]pub fn 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 matrixlet r1 = Mf32::scale(2.0, 0.0, 2.0);assert!(!r1.is_backface_visible());}#[test]pub fn test_homogeneous() {#[rustfmt::skip]let m = Mf32::new(1.0, 2.0, 0.5, 5.0,3.0, 4.0, 0.25, 6.0,0.5, -1.0, 1.0, -1.0,-1.0, 1.0, -1.0, 2.0,);assert_eq!(m.transform_point2d_homogeneous(point2(1.0, 2.0)),HomogeneousVector::new(6.0, 11.0, 0.0, 19.0),);assert_eq!(m.transform_point3d_homogeneous(point3(1.0, 2.0, 4.0)),HomogeneousVector::new(8.0, 7.0, 4.0, 15.0),);}#[test]pub fn test_perspective_division() {let p = point2(1.0, 2.0);let mut m = Mf32::identity();assert!(m.transform_point2d(p).is_some());m.m44 = 0.0;assert_eq!(None, m.transform_point2d(p));m.m44 = 1.0;m.m24 = -1.0;assert_eq!(None, m.transform_point2d(p));}#[cfg(feature = "mint")]#[test]pub fn test_mint() {let m1 = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2));let mm: mint::RowMatrix4<_> = m1.into();let m2 = Mf32::from(mm);assert_eq!(m1, m2);}
}
二、结构体定义
- #[repr©]: 这个属性确保结构体在内存中的布局与C语言中的结构体兼容。这对于与C语言库进行互操作时非常有用,可以确保数据的二进制表示一致。
- 条件编译属性 #[cfg_attr(feature = “serde”, derive(Serialize, Deserialize))]:
- 这个属性是条件编译的一部分,它检查是否启用了名为 serde 的特性(feature)。
- 如果启用了 serde 特性,那么 Transform3D 结构体将派生(derive)Serialize 和 Deserialize 这两个 trait,使得结构体可以被序列化和反序列化。这对于将数据保存到文件、通过网络传输数据等场景非常有用。
- #[cfg_attr(feature = “serde”, serde(bound(serialize = “T: Serialize”, deserialize = “T: Deserialize<'de>”)) )]:
- 这个属性进一步指定了当 serde 特性启用时,对于 T 类型有额外的约束。
- 在序列化时,T 必须实现 Serialize trait;在反序列化时,T 必须实现 Deserialize trait(注意这里 'de 是一个生命周期参数,用于泛型反序列化)。
- #[rustfmt::skip]: 这个属性告诉 Rust 的代码格式化工具 rustfmt 忽略此结构体的格式化。这可能是因为结构体布局有特殊需求,或者为了保持某些手动调整的格式。
- 结构体定义:
- Transform3D 结构体有16个公开字段,分别表示4x4矩阵的行和列(m11 到 m44)。
- 最后一个字段 _unit 是一个 PhantomData<(Src, Dst)> 类型的实例。PhantomData 用于在类型系统中表示某种类型的存在,而不占用实际的内存空间。这里它用于保留 Src 和 Dst 类型信息,可能是为了泛型代码的类型安全性或约束,尽管在这段代码中它们并没有直接的功能作用。
- 总结
这个结构体可以用于图形编程、物理模拟等领域,其中3D变换矩阵非常常见,用于表示旋转、缩放、平移等变换。
相关文章:
11 3D变换模块(transform3d.rs)
transform3d.rs代码定义了一个名为 Transform3D 的 Rust 结构体,它用于表示一个3D变换矩阵。这个结构体是泛型的,包含三个类型参数:T、Src 和 Dst。其中,T 用于矩阵元素的数据类型,Src 和 Dst 用于表示变换的源和目标类…...
昆仑万维Java开发面试题及参考答案
进程和线程的区别是什么? 进程和线程都是操作系统中非常重要的概念,它们在多个方面存在显著的区别。 从定义上看,进程是操作系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间,包括代码段、数据段、堆栈段等。例如,当你在电脑上同时打开浏览器和音乐播放…...
vscode命令面板输入 CMake:build不执行提示输入
CMake:build或rebuild不编译了,弹出:> [Add a new preset] , 提示输入发现settings.jsons设置有问题 { "workbench.colorTheme": "Default Light", "cmake.pinnedCommands": [ "workbench.action.tasks.configu…...
Fastdds学习分享_xtpes_发布订阅模式及rpc模式
在之前的博客中我们介绍了dds的大致功能,与组成结构。本篇博文主要介绍的是xtypes.分为理论和实际运用两部分.理论主要用于梳理hzy大佬的知识,对于某些一带而过的部分作出更为详细的阐释,并在之后通过实际案例便于理解。案例分为普通发布订阅…...
什么叫DeepSeek-V3,以及与GPT-4o的区别
1. DeepSeek 的故事 1.1 DeepSeek 是什么? DeepSeek 是一家专注于人工智能技术研发的公司,致力于打造高性能、低成本的 AI 模型。它的目标是让 AI 技术更加普惠,让更多人能够用上强大的 AI 工具。 1.2 DeepSeek-V3 的问世 DeepSeek-V3 是…...
【C#】Process、ProcessStartInfo启动外部exe
在C#中使用 Process 和 ProcessStartInfo 类启动外部 .exe 文件,可以按照以下步骤进行: 创建 ProcessStartInfo 实例:配置进程启动信息,包括可执行文件的路径、传递给该程序的参数等。 设置启动选项:根据需要配置 Pro…...
android 音视频系列引导
音视频这块的知识点自己工作中有用到,一直没有好好做一个总结,原因有客观和主观的。 客观是工作太忙,没有成段时间做总结。 主观自己懒。 趁着这次主动离职拿了n1的钱,休息一下,对自己的人生做一下总结,…...
Mac电脑上最新的好用邮件软件比较
在Mac电脑上,选择一款好用的邮件软件需要根据个人需求、功能偏好以及与系统生态的兼容性来决定。以下是基于我搜索到的资料,对当前市场上一些优秀的邮件客户端进行比较和推荐: 1. Apple Mail Apple Mail是Mac系统自带的邮件客户端ÿ…...
C#,入门教程(11)——枚举(Enum)的基础知识和高级应用
上一篇: C#,入门教程(10)——常量、变量与命名规则的基础知识https://blog.csdn.net/beijinghorn/article/details/123913570 不会枚举,就不会编程! 枚举 一个有组织的常量系列 比如:一个星期每一天的名字…...
Spring 实现注入的方式
一、XML配置文件注入 概念:这是一种传统的依赖注入方式,通过在XML文件中配置bean的相关信息来实现依赖注入。在Spring框架中,需要在applicationContext.xml或spring-config.xml等配置文件中定义bean,并通过<property>标签或…...
【论文复现】粘菌算法在最优经济排放调度中的发展与应用
目录 1.摘要2.黏菌算法SMA原理3.改进策略4.结果展示5.参考文献6.代码获取 1.摘要 本文提出了一种改进粘菌算法(ISMA),并将其应用于考虑阀点效应的单目标和双目标经济与排放调度(EED)问题。为提升传统粘菌算法…...
【LLM-agent】(task6)构建教程编写智能体
note 构建教程编写智能体 文章目录 note一、功能需求二、相关代码(1)定义生成教程的目录 Action 类(2)定义生成教程内容的 Action 类(3)定义教程编写智能体(4)交互式操作调用教程编…...
04树 + 堆 + 优先队列 + 图(D1_树(D10_决策树))
目录 一、引言 二、算法原理 三、算法实现 四、知识小结 一、引言 决策树算法是一种常用的机器学习算法,可用于分类和回归问题。它基于特征之间的条件判断来构 建一棵树,树的每个节点代表一个特征,每个叶节点代表一个类别或回归值。决策…...
JavaScript模块化
什么是JavaScript的模块化? JavaScript的模块化是指将代码分割成独立的、可重用的模块,每个模块具有自己的功能和作用,可以单独进行开发、测试和维护。不同的目的是提升代码的可维护性、可复用性和可扩展性,同时避免不同模块间的…...
排序算法--插入排序
插入排序是一种简单且稳定的排序算法,适合小规模数据或部分有序数据。 // 插入排序函数 void insertionSort(int arr[], int n) {for (int i 1; i < n; i) { // 从第二个元素开始int key arr[i]; // 当前需要插入的元素int j i - 1;// 将比 key 大的元素向后移…...
【C语言篇】“三子棋”
一、游戏介绍 三子棋,英文名为 Tic - Tac - Toe,是一款简单而经典的棋类游戏。游戏在一个 33 的棋盘上进行,两名玩家轮流在棋盘的空位上放置自己的棋子(通常用 * 和 # 表示),率先在横、竖或斜方向上连成三个…...
【大模型理论篇】DeepSeek-R1:引入冷启动的强化学习
1. 背景 首先给出DeepSeek-V3、DeepSeek-R1-Zero、DeepSeek-R1的关系图【1】。 虽然DeepSeek-R1-Zero推理能力很强,但它也面临一些问题。例如,DeepSeek-R1-Zero存在可读性差和语言混杂等问题。为了使推理过程更具可读性,进而推出了DeepSee…...
Linux基础 ——tmux vim 以及基本的shell语法
Linux 基础 ACWING y总的Linux基础课,看讲义作作笔记。 tmux tmux 可以干嘛? tmux可以分屏多开窗口,可以进行多个任务,断线,不会自动杀掉正在进行的进程。 tmux – session(会话,多个) – window(多个…...
使用 Kotlin 将 Vertx 和 Springboot 整合
本篇文章目的是将 Springboot 和 Vertx 进行简单整合。整合目的仅仅是为了整活,因为两个不同的东西整合在一起提升的性能并没有只使用 Vertx 性能高,因此追求高性能的话这是在我来说不推荐。而且他们不仅没有提高很多性能甚至增加了学习成本 一、整合流…...
【单层神经网络】softmax回归的从零开始实现(图像分类)
softmax回归 该回归分析为后续的多层感知机做铺垫 基本概念 softmax回归用于离散模型预测(分类问题,含标签) softmax运算本质上是对网络的多个输出进行了归一化,使结果有一个统一的判断标准,不必纠结为什么要这么算…...
课题推荐——基于自适应滤波技术的多传感器融合在无人机组合导航中的应用研究
无人机在现代航空、农业和监测等领域的应用日益广泛。为了提高导航精度,通常采用多传感器融合技术,将来自GPS、惯性测量单元(IMU)、磁力计等不同传感器的数据整合。然而,传感器的量测偏差、环境干扰以及非线性特性使得…...
【基于SprintBoot+Mybatis+Mysql】电脑商城项目之用户登录
🧸安清h:个人主页 🎥个人专栏:【Spring篇】【计算机网络】【Mybatis篇】 🚦作者简介:一个有趣爱睡觉的intp,期待和更多人分享自己所学知识的真诚大学生。 目录 🎯1.登录-持久层 &…...
Mono里运行C#脚本40—mono_magic_trampoline函数的参数设置
前面介绍过跳板代码,它是用来切换托管代码与非托管的代码,以及运行时与C#代码的交互。实现从运行时切换到C#代码来运行,再从C#代码返回运行时。 要想理解整个切换的细节,那么就需要理解mono_magic_trampoline函数, 而要理解此函数,就必须了解这个函数的参数来源。 要理…...
Verilog基础(三):过程
过程(Procedures) - Always块 – 组合逻辑 (Always blocks – Combinational) 由于数字电路是由电线相连的逻辑门组成的,所以任何电路都可以表示为模块和赋值语句的某种组合. 然而,有时这不是描述电路最方便的方法. 两种always block是十分有用的: 组合逻辑: always @(…...
实际操作 检测缺陷刀片
号he 找到目标图像的缺陷位置,首先思路为对图像进行预处理,灰度-二值化-针对图像进行轮廓分析 //定义结构元素 Mat se getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); morphologyEx(thre, tc, MORPH_OPEN, se, Point(-1, -1), 1); …...
DeepSeek 阐述 2025年前端发展趋势
预测2025年前端的发展趋势。首先,我需要考虑当前的前端 技术发展情况,以及近几年的变化趋势。比如,框架方面,React、Vue、Angular这些主流框架的更新方向和社区活跃度。可能用户想知道未来哪些技术会更流行,或者需要学…...
Elasticsearch基本使用详解
文章目录 Elasticsearch基本使用详解一、引言二、环境搭建1、安装 Elasticsearch2、安装 Kibana(可选) 三、索引操作1、创建索引2、查看索引3、删除索引 四、数据操作1、插入数据2、查询数据(1)简单查询(2)…...
【Rust自学】17.3. 实现面向对象的设计模式
喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 17.3.1. 状态模式 状态模式(state pattern) 是一种面向对象设计模式,指的是一个值拥有的内部状态由数个状态对象(…...
司库建设-融资需求分析与计划制定
当企业现金流紧张时,企业需要考虑外部融资,从财务营运角度来考虑和分析如何确定输入和输出,进行整体解决方案设计。 融资需求分析与计划制定 功能点: 现金流预测工具:集成历史数据和业务计划,自动生成未来1…...
2. 【.NET Aspire 从入门到实战】--理论入门与环境搭建--.NET Aspire 概览
在当今快速发展的软件开发领域,构建高效、可靠且易于维护的云原生应用程序已成为开发者和企业的核心需求。.NET Aspire 作为一款专为云原生应用设计的开发框架,旨在简化分布式系统的构建和管理,提供了一整套工具、模板和集成包,帮…...
【Elasticsearch】allow_no_indices
- **allow_no_indices 参数的作用**: 该参数用于控制当请求的目标索引(通过通配符、别名或 _all 指定)不存在或已关闭时,Elasticsearch 的行为。 - **默认行为**: 如果未显式设置该参数,默认值为 …...
26.useScript
在 Web 应用开发中,动态加载外部脚本是一个常见需求,特别是在需要集成第三方库或服务时。然而,在 React 应用中管理脚本加载可能会变得复杂。useScript 钩子提供了一种优雅的方式来处理外部脚本的加载、错误处理和清理,使得在 Rea…...
跨域问题和解决方案
跨域问题及解决方案 同源策略及跨域问题 同源策略是一套浏览器安全机制,当一个源的文档和脚本,与另一个源的资源进行通信时,同源策略就会对这个通信做出不同程度的限制。 简单来说,同源策略对 同源资源 放行,对 异源…...
springboot中路径默认配置与重定向/转发所存在的域对象
Spring Boot 是一种简化 Spring 应用开发的框架,它提供了多种默认配置和方便的开发特性。在 Web 开发中,路径配置和请求的重定向/转发是常见操作。本文将详细介绍 Spring Boot 中的路径默认配置,并解释重定向和转发过程中存在的域对象。 一、…...
【OS】AUTOSAR架构下的Interrupt详解(下篇)
目录 3.代码分析 3.1中断配置代码 3.2 OS如何找到中断处理函数 3.3 Os_InitialEnableInterruptSources实现 3.4 Os_EnableInterruptSource 3.5 DisableAllInterrupts 3.5.1Os_IntSuspendCat1 3.5.2 Os_InterruptDisableAllEnter 3.5.3 Disable二类中断 3.5.4 Disable一…...
基于遗传算法的256QAM星座图的最优概率整形matlab仿真,对比优化前后整形星座图和误码率
目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印): GA优化曲线: 优化前后星座图对比 优化前后误码率对比 仿真操作步骤…...
Android学习21 -- launcher
1 前言 之前在工作中,第一次听到launcher有点蒙圈,不知道是啥,当时还赶鸭子上架去和客户PK launcher的事。后来才知道其实就是安卓的桌面。本来还以为很复杂,毕竟之前接触过windows的桌面,那叫一个复杂。。。 后面查了…...
小程序越来越智能化,作为设计师要如何进行创新设计
一、用户体验至上 (一)简洁高效的界面设计 小程序的特点之一是轻便快捷,用户期望能够在最短的时间内找到所需功能并完成操作。因此,设计师应致力于打造简洁高效的界面。避免过多的装饰元素和复杂的布局,采用清晰的导航…...
【实践案例】基于大语言模型的海龟汤游戏
文章目录 项目背景提示词构建海龟汤主持人真相判断专家 具体实现流程文心一言大语言模型“海龟汤”插件参考 项目背景 “海龟汤”作为一种聚会类桌游,又称情境推理游戏,是一种猜测情境还原事件真相的智力游戏。其玩法是由出题者提出一个难以理解的事件&…...
基于多智能体强化学习的医疗AI中RAG系统程序架构优化研究
一、引言 1.1 研究背景与意义 在数智化医疗飞速发展的当下,医疗人工智能(AI)已成为提升医疗服务质量、优化医疗流程以及推动医学研究进步的关键力量。医疗 AI 借助机器学习、深度学习等先进技术,能够处理和分析海量的医疗数据,从而辅助医生进行疾病诊断、制定治疗方案以…...
【Unity2D 2022:UI】创建滚动视图
一、创建Scroll View游戏对象 在Canvas画布下新建Scroll View游戏对象 二、为Content游戏对象添加Grid Layout Group(网格布局组)组件 选中Content游戏物体,点击Add Competent添加组件,搜索Grid Layout Group组件 三、调整Grid La…...
C++ Primer 多维数组
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
怀旧经典:1200+款红白机游戏合集,Windows版一键畅玩
沉浸在怀旧的海洋中,体验经典红白机游戏的魅力!我们为您精心准备了超过1200款经典游戏的合集,每一款都是时代的印记,每一场都是回忆的旅程。这个合集不仅包含了丰富的游戏资源,还内置了多个Windows版的NES模拟器&…...
【数据采集】案例02:基于Selenium采集豆瓣电影Top250的详细数据
基于Selenium采集豆瓣电影Top250的详细数据 Selenium官网:https://www.selenium.dev/blog/ 豆瓣电影Top250官网:https://movie.douban.com/top250 写在前面 实验目标:基于Selenium框架采集豆瓣电影Top250的详细数据。 电脑系统:Windows 使用软件:PyCharm、Navicat 技术需求…...
Spring 面试题【每日20道】【其二】
1、Spring MVC 具体的工作原理? 中等 Spring MVC 是 Spring 框架的一部分,专门用于构建基于Java的Web应用程序。它采用模型-视图-控制器(MVC)架构模式,有助于分离应用程序的不同方面,如输入逻辑、业务逻辑…...
算法设计-0-1背包动态规划(C++)
一、问题阐述 0-1 背包问题的目标是在给定背包容量 W 的情况下,从 n 个物品中选择一些物品放入背包,使得背包中物品的总价值最大。每个物品只能选择一次(即要么放入背包,要么不放入)。 二、代码 #include <iostr…...
【Java知识】使用Java实现地址逆向解析到区划信息
文章目录 1. 实现 FST1.1 定义 FST 节点1.2 定义 FST 2. 实现地址逆向查询2.1 定义区划信息2.2 构建 FST 3. 运行结果4. 代码说明5. 进一步优化6. 总结 实现一个 FST(Finite State Transducer,有限状态转换器) 并用于 地址逆向查询区划信息…...
单机伪分布Hadoop详细配置
目录 1. 引言2. 配置单机Hadoop2.1 下载并解压JDK1.8、Hadoop3.3.62.2 配置环境变量2.3 验证JDK、Hadoop配置 3. 伪分布Hadoop3.1 配置ssh免密码登录3.2 配置伪分布Hadoop3.2.1 修改hadoop-env.sh3.2.2 修改core-site.xml3.2.3 修改hdfs-site.xml3.2.4 修改yarn-site.xml3.2.5 …...
[250204] Mistral Small 3:小巧、快速、强大 | asdf 0.16.0 发布:Golang 重写带来性能飞跃
目录 Mistral AI 发布开源模型 Mistral Small 3:小巧、快速、强大asdf 0.16.0 版本发布:Golang 重写带来性能飞跃! Mistral AI 发布开源模型 Mistral Small 3:小巧、快速、强大 法国人工智能初创公司 Mistral AI 发布了最新的开源…...
解读“大语言模型(LLM)安全性测评基准”
1. 引入 OWASP,全称为Open Web Application Security Project,即开放式Web应用程序安全项目,是一个致力于提高软件安全性的非营利国际组织。 由于庞大的规模和复杂的结构,大语言模型也存在多种安全风险,如prompt误导…...