2016-12-05 05:44:42 +08:00
|
|
|
use std::any::Any;
|
|
|
|
use std::fmt::Debug;
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
|
2017-05-04 10:02:30 +08:00
|
|
|
#[cfg(feature = "serde-serialize")]
|
2018-10-22 13:00:10 +08:00
|
|
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
2017-05-04 10:02:30 +08:00
|
|
|
|
2017-08-03 01:37:44 +08:00
|
|
|
use alga::general::Real;
|
2016-12-05 05:44:42 +08:00
|
|
|
|
2018-10-22 13:00:10 +08:00
|
|
|
use base::allocator::Allocator;
|
2018-05-19 23:15:15 +08:00
|
|
|
use base::dimension::{DimName, DimNameAdd, DimNameSum, U1};
|
|
|
|
use base::storage::Owned;
|
2018-10-22 13:00:10 +08:00
|
|
|
use base::{DefaultAllocator, MatrixN};
|
2016-12-05 05:44:42 +08:00
|
|
|
|
|
|
|
/// Trait implemented by phantom types identifying the projective transformation type.
|
|
|
|
///
|
|
|
|
/// NOTE: this trait is not intended to be implementable outside of the `nalgebra` crate.
|
|
|
|
pub trait TCategory: Any + Debug + Copy + PartialEq + Send {
|
2017-02-13 01:17:09 +08:00
|
|
|
/// Indicates whether a `Transform` with the category `Self` has a bottom-row different from
|
|
|
|
/// `0 0 .. 1`.
|
2016-12-05 05:44:42 +08:00
|
|
|
#[inline]
|
|
|
|
fn has_normalizer() -> bool {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Checks that the given matrix is a valid homogeneous representation of an element of the
|
|
|
|
/// category `Self`.
|
2017-08-03 01:37:44 +08:00
|
|
|
fn check_homogeneous_invariants<N: Real, D: DimName>(mat: &MatrixN<N, D>) -> bool
|
2018-02-02 19:26:35 +08:00
|
|
|
where
|
|
|
|
N::Epsilon: Copy,
|
|
|
|
DefaultAllocator: Allocator<N, D, D>;
|
2016-12-05 05:44:42 +08:00
|
|
|
}
|
|
|
|
|
2017-02-13 01:17:09 +08:00
|
|
|
/// Traits that gives the `Transform` category that is compatible with the result of the
|
2016-12-05 05:44:42 +08:00
|
|
|
/// multiplication of transformations with categories `Self` and `Other`.
|
|
|
|
pub trait TCategoryMul<Other: TCategory>: TCategory {
|
2017-02-13 01:17:09 +08:00
|
|
|
/// The transform category that results from the multiplication of a `Transform<Self>` to a
|
|
|
|
/// `Transform<Other>`. This is usually equal to `Self` or `Other`, whichever is the most
|
|
|
|
/// general category.
|
2016-12-05 05:44:42 +08:00
|
|
|
type Representative: TCategory;
|
|
|
|
}
|
|
|
|
|
2017-02-13 01:17:09 +08:00
|
|
|
/// Indicates that `Self` is a more general `Transform` category than `Other`.
|
2018-02-02 19:26:35 +08:00
|
|
|
pub trait SuperTCategoryOf<Other: TCategory>: TCategory {}
|
2016-12-05 05:44:42 +08:00
|
|
|
|
2017-02-13 01:17:09 +08:00
|
|
|
/// Indicates that `Self` is a more specific `Transform` category than `Other`.
|
2016-12-05 05:44:42 +08:00
|
|
|
///
|
|
|
|
/// Automatically implemented based on `SuperTCategoryOf`.
|
2018-02-02 19:26:35 +08:00
|
|
|
pub trait SubTCategoryOf<Other: TCategory>: TCategory {}
|
2016-12-05 05:44:42 +08:00
|
|
|
impl<T1, T2> SubTCategoryOf<T2> for T1
|
2018-02-02 19:26:35 +08:00
|
|
|
where
|
|
|
|
T1: TCategory,
|
|
|
|
T2: SuperTCategoryOf<T1>,
|
|
|
|
{
|
2016-12-05 05:44:42 +08:00
|
|
|
}
|
|
|
|
|
2017-02-13 01:17:09 +08:00
|
|
|
/// Tag representing the most general (not necessarily inversible) `Transform` type.
|
2017-02-16 05:04:34 +08:00
|
|
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
2018-10-22 13:00:10 +08:00
|
|
|
pub enum TGeneral {}
|
2016-12-05 05:44:42 +08:00
|
|
|
|
2017-02-13 01:17:09 +08:00
|
|
|
/// Tag representing the most general inversible `Transform` type.
|
2017-02-16 05:04:34 +08:00
|
|
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
2018-10-22 13:00:10 +08:00
|
|
|
pub enum TProjective {}
|
2016-12-05 05:44:42 +08:00
|
|
|
|
2017-02-13 01:17:09 +08:00
|
|
|
/// Tag representing an affine `Transform`. Its bottom-row is equal to `(0, 0 ... 0, 1)`.
|
2017-02-16 05:04:34 +08:00
|
|
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
2018-10-22 13:00:10 +08:00
|
|
|
pub enum TAffine {}
|
2016-12-05 05:44:42 +08:00
|
|
|
|
|
|
|
impl TCategory for TGeneral {
|
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
fn check_homogeneous_invariants<N: Real, D: DimName>(_: &MatrixN<N, D>) -> bool
|
2018-02-02 19:26:35 +08:00
|
|
|
where
|
|
|
|
N::Epsilon: Copy,
|
|
|
|
DefaultAllocator: Allocator<N, D, D>,
|
|
|
|
{
|
2016-12-05 05:44:42 +08:00
|
|
|
true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TCategory for TProjective {
|
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
fn check_homogeneous_invariants<N: Real, D: DimName>(mat: &MatrixN<N, D>) -> bool
|
2018-02-02 19:26:35 +08:00
|
|
|
where
|
|
|
|
N::Epsilon: Copy,
|
|
|
|
DefaultAllocator: Allocator<N, D, D>,
|
|
|
|
{
|
2016-12-05 05:44:42 +08:00
|
|
|
mat.is_invertible()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TCategory for TAffine {
|
|
|
|
#[inline]
|
|
|
|
fn has_normalizer() -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
fn check_homogeneous_invariants<N: Real, D: DimName>(mat: &MatrixN<N, D>) -> bool
|
2018-02-02 19:26:35 +08:00
|
|
|
where
|
|
|
|
N::Epsilon: Copy,
|
|
|
|
DefaultAllocator: Allocator<N, D, D>,
|
|
|
|
{
|
2017-07-29 00:57:44 +08:00
|
|
|
let last = D::dim() - 1;
|
2018-10-22 13:00:10 +08:00
|
|
|
mat.is_invertible()
|
|
|
|
&& mat[(last, last)] == N::one()
|
2018-02-02 19:26:35 +08:00
|
|
|
&& (0..last).all(|i| mat[(last, i)].is_zero())
|
2016-12-05 05:44:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! category_mul_impl(
|
|
|
|
($($a: ident * $b: ident => $c: ty);* $(;)*) => {$(
|
|
|
|
impl TCategoryMul<$a> for $b {
|
|
|
|
type Representative = $c;
|
|
|
|
}
|
|
|
|
)*}
|
|
|
|
);
|
|
|
|
|
|
|
|
// We require stability uppon multiplication.
|
|
|
|
impl<T: TCategory> TCategoryMul<T> for T {
|
|
|
|
type Representative = T;
|
|
|
|
}
|
|
|
|
|
|
|
|
category_mul_impl!(
|
2017-08-03 01:37:44 +08:00
|
|
|
// TGeneral * TGeneral => TGeneral;
|
|
|
|
TGeneral * TProjective => TGeneral;
|
|
|
|
TGeneral * TAffine => TGeneral;
|
2016-12-05 05:44:42 +08:00
|
|
|
|
2017-08-03 01:37:44 +08:00
|
|
|
TProjective * TGeneral => TGeneral;
|
|
|
|
// TProjective * TProjective => TProjective;
|
|
|
|
TProjective * TAffine => TProjective;
|
2016-12-05 05:44:42 +08:00
|
|
|
|
2017-08-03 01:37:44 +08:00
|
|
|
TAffine * TGeneral => TGeneral;
|
|
|
|
TAffine * TProjective => TProjective;
|
|
|
|
// TAffine * TAffine => TAffine;
|
2016-12-05 05:44:42 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
macro_rules! super_tcategory_impl(
|
|
|
|
($($a: ident >= $b: ident);* $(;)*) => {$(
|
|
|
|
impl SuperTCategoryOf<$b> for $a { }
|
|
|
|
)*}
|
|
|
|
);
|
|
|
|
|
2018-02-02 19:26:35 +08:00
|
|
|
impl<T: TCategory> SuperTCategoryOf<T> for T {}
|
2016-12-05 05:44:42 +08:00
|
|
|
|
|
|
|
super_tcategory_impl!(
|
2017-08-03 01:37:44 +08:00
|
|
|
TGeneral >= TProjective;
|
|
|
|
TGeneral >= TAffine;
|
2016-12-05 05:44:42 +08:00
|
|
|
TProjective >= TAffine;
|
|
|
|
);
|
|
|
|
|
|
|
|
/// A transformation matrix in homogeneous coordinates.
|
|
|
|
///
|
|
|
|
/// It is stored as a matrix with dimensions `(D + 1, D + 1)`, e.g., it stores a 4x4 matrix for a
|
|
|
|
/// 3D transformation.
|
|
|
|
#[repr(C)]
|
2017-08-03 01:37:44 +08:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Transform<N: Real, D: DimNameAdd<U1>, C: TCategory>
|
2018-10-22 13:00:10 +08:00
|
|
|
where DefaultAllocator: Allocator<N, DimNameSum<D, U1>, DimNameSum<D, U1>>
|
2018-02-02 19:26:35 +08:00
|
|
|
{
|
|
|
|
matrix: MatrixN<N, DimNameSum<D, U1>>,
|
|
|
|
_phantom: PhantomData<C>,
|
2016-12-05 05:44:42 +08:00
|
|
|
}
|
|
|
|
|
2017-08-03 01:37:44 +08:00
|
|
|
// FIXME
|
|
|
|
// impl<N: Real + hash::Hash, D: DimNameAdd<U1> + hash::Hash, C: TCategory> hash::Hash for Transform<N, D, C>
|
|
|
|
// where DefaultAllocator: Allocator<N, DimNameSum<D, U1>, DimNameSum<D, U1>>,
|
|
|
|
// Owned<N, DimNameSum<D, U1>, DimNameSum<D, U1>>: hash::Hash {
|
|
|
|
// fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
|
|
|
// self.matrix.hash(state);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
impl<N: Real, D: DimNameAdd<U1> + Copy, C: TCategory> Copy for Transform<N, D, C>
|
2018-02-02 19:26:35 +08:00
|
|
|
where
|
|
|
|
DefaultAllocator: Allocator<N, DimNameSum<D, U1>, DimNameSum<D, U1>>,
|
|
|
|
Owned<N, DimNameSum<D, U1>, DimNameSum<D, U1>>: Copy,
|
2018-10-22 13:00:10 +08:00
|
|
|
{}
|
2017-08-03 01:37:44 +08:00
|
|
|
|
|
|
|
impl<N: Real, D: DimNameAdd<U1>, C: TCategory> Clone for Transform<N, D, C>
|
2018-10-22 13:00:10 +08:00
|
|
|
where DefaultAllocator: Allocator<N, DimNameSum<D, U1>, DimNameSum<D, U1>>
|
2018-02-02 19:26:35 +08:00
|
|
|
{
|
2017-08-03 01:37:44 +08:00
|
|
|
#[inline]
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
Transform::from_matrix_unchecked(self.matrix.clone())
|
2017-05-04 10:02:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "serde-serialize")]
|
2018-09-13 12:55:58 +08:00
|
|
|
impl<N: Real, D: DimNameAdd<U1>, C: TCategory> Serialize for Transform<N, D, C>
|
2018-02-02 19:26:35 +08:00
|
|
|
where
|
|
|
|
DefaultAllocator: Allocator<N, DimNameSum<D, U1>, DimNameSum<D, U1>>,
|
2018-09-13 12:55:58 +08:00
|
|
|
Owned<N, DimNameSum<D, U1>, DimNameSum<D, U1>>: Serialize,
|
2018-02-02 19:26:35 +08:00
|
|
|
{
|
2017-08-03 01:37:44 +08:00
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
2018-10-22 13:00:10 +08:00
|
|
|
where S: Serializer {
|
2018-02-02 19:26:35 +08:00
|
|
|
self.matrix.serialize(serializer)
|
|
|
|
}
|
2017-08-03 01:37:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "serde-serialize")]
|
2018-09-13 12:55:58 +08:00
|
|
|
impl<'a, N: Real, D: DimNameAdd<U1>, C: TCategory> Deserialize<'a> for Transform<N, D, C>
|
2018-02-02 19:26:35 +08:00
|
|
|
where
|
|
|
|
DefaultAllocator: Allocator<N, DimNameSum<D, U1>, DimNameSum<D, U1>>,
|
2018-09-13 12:55:58 +08:00
|
|
|
Owned<N, DimNameSum<D, U1>, DimNameSum<D, U1>>: Deserialize<'a>,
|
2018-02-02 19:26:35 +08:00
|
|
|
{
|
2017-08-03 01:37:44 +08:00
|
|
|
fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error>
|
2018-10-22 13:00:10 +08:00
|
|
|
where Des: Deserializer<'a> {
|
2018-02-02 19:26:35 +08:00
|
|
|
let matrix = MatrixN::<N, DimNameSum<D, U1>>::deserialize(deserializer)?;
|
2017-08-03 01:37:44 +08:00
|
|
|
|
2018-02-02 19:26:35 +08:00
|
|
|
Ok(Transform::from_matrix_unchecked(matrix))
|
|
|
|
}
|
2017-05-04 10:02:30 +08:00
|
|
|
}
|
|
|
|
|
2018-10-22 13:00:10 +08:00
|
|
|
impl<N: Real + Eq, D: DimNameAdd<U1>, C: TCategory> Eq for Transform<N, D, C> where DefaultAllocator: Allocator<N, DimNameSum<D, U1>, DimNameSum<D, U1>>
|
|
|
|
{}
|
2016-12-05 05:44:42 +08:00
|
|
|
|
2017-08-03 01:37:44 +08:00
|
|
|
impl<N: Real, D: DimNameAdd<U1>, C: TCategory> PartialEq for Transform<N, D, C>
|
2018-10-22 13:00:10 +08:00
|
|
|
where DefaultAllocator: Allocator<N, DimNameSum<D, U1>, DimNameSum<D, U1>>
|
2018-02-02 19:26:35 +08:00
|
|
|
{
|
2016-12-05 05:44:42 +08:00
|
|
|
#[inline]
|
|
|
|
fn eq(&self, right: &Self) -> bool {
|
|
|
|
self.matrix == right.matrix
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-03 01:37:44 +08:00
|
|
|
impl<N: Real, D: DimNameAdd<U1>, C: TCategory> Transform<N, D, C>
|
2018-10-22 13:00:10 +08:00
|
|
|
where DefaultAllocator: Allocator<N, DimNameSum<D, U1>, DimNameSum<D, U1>>
|
2018-02-02 19:26:35 +08:00
|
|
|
{
|
2016-12-05 05:44:42 +08:00
|
|
|
/// Creates a new transformation from the given homogeneous matrix. The transformation category
|
|
|
|
/// of `Self` is not checked to be verified by the given matrix.
|
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
pub fn from_matrix_unchecked(matrix: MatrixN<N, DimNameSum<D, U1>>) -> Self {
|
|
|
|
Transform {
|
2018-02-02 19:26:35 +08:00
|
|
|
matrix: matrix,
|
|
|
|
_phantom: PhantomData,
|
2016-12-05 05:44:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The underlying matrix.
|
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
pub fn unwrap(self) -> MatrixN<N, DimNameSum<D, U1>> {
|
2016-12-05 05:44:42 +08:00
|
|
|
self.matrix
|
|
|
|
}
|
|
|
|
|
2018-09-24 12:48:42 +08:00
|
|
|
/// A reference to the underlying matrix.
|
2016-12-05 05:44:42 +08:00
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
pub fn matrix(&self) -> &MatrixN<N, DimNameSum<D, U1>> {
|
2016-12-05 05:44:42 +08:00
|
|
|
&self.matrix
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A mutable reference to the underlying matrix.
|
|
|
|
///
|
|
|
|
/// It is `_unchecked` because direct modifications of this matrix may break invariants
|
|
|
|
/// identified by this transformation category.
|
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
pub fn matrix_mut_unchecked(&mut self) -> &mut MatrixN<N, DimNameSum<D, U1>> {
|
2016-12-05 05:44:42 +08:00
|
|
|
&mut self.matrix
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the category of this transform.
|
|
|
|
///
|
|
|
|
/// This can be done only if the new category is more general than the current one, e.g., a
|
|
|
|
/// transform with category `TProjective` cannot be converted to a transform with category
|
|
|
|
/// `TAffine` because not all projective transformations are affine (the other way-round is
|
|
|
|
/// valid though).
|
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
pub fn set_category<CNew: SuperTCategoryOf<C>>(self) -> Transform<N, D, CNew> {
|
|
|
|
Transform::from_matrix_unchecked(self.matrix)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Clones this transform into one that owns its data.
|
|
|
|
#[inline]
|
2017-08-14 01:53:02 +08:00
|
|
|
#[deprecated(note = "This method is a no-op and will be removed in a future release.")]
|
2017-08-03 01:37:44 +08:00
|
|
|
pub fn clone_owned(&self) -> Transform<N, D, C> {
|
|
|
|
Transform::from_matrix_unchecked(self.matrix.clone_owned())
|
2016-12-05 05:44:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Converts this transform into its equivalent homogeneous transformation matrix.
|
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
pub fn to_homogeneous(&self) -> MatrixN<N, DimNameSum<D, U1>> {
|
2016-12-05 05:44:42 +08:00
|
|
|
self.matrix().clone_owned()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempts to invert this transformation. You may use `.inverse` instead of this
|
|
|
|
/// transformation has a subcategory of `TProjective`.
|
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
pub fn try_inverse(self) -> Option<Transform<N, D, C>> {
|
2016-12-05 05:44:42 +08:00
|
|
|
if let Some(m) = self.matrix.try_inverse() {
|
2017-08-03 01:37:44 +08:00
|
|
|
Some(Transform::from_matrix_unchecked(m))
|
2018-02-02 19:26:35 +08:00
|
|
|
} else {
|
2016-12-05 05:44:42 +08:00
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Inverts this transformation. Use `.try_inverse` if this transform has the `TGeneral`
|
|
|
|
/// category (it may not be invertible).
|
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
pub fn inverse(self) -> Transform<N, D, C>
|
2018-10-22 13:00:10 +08:00
|
|
|
where C: SubTCategoryOf<TProjective> {
|
2016-12-05 05:44:42 +08:00
|
|
|
// FIXME: specialize for TAffine?
|
2017-08-03 01:37:44 +08:00
|
|
|
Transform::from_matrix_unchecked(self.matrix.try_inverse().unwrap())
|
2016-12-05 05:44:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempts to invert this transformation in-place. You may use `.inverse_mut` instead of this
|
|
|
|
/// transformation has a subcategory of `TProjective`.
|
|
|
|
#[inline]
|
|
|
|
pub fn try_inverse_mut(&mut self) -> bool {
|
|
|
|
self.matrix.try_inverse_mut()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Inverts this transformation in-place. Use `.try_inverse_mut` if this transform has the
|
|
|
|
/// `TGeneral` category (it may not be invertible).
|
|
|
|
#[inline]
|
|
|
|
pub fn inverse_mut(&mut self)
|
2018-10-22 13:00:10 +08:00
|
|
|
where C: SubTCategoryOf<TProjective> {
|
2016-12-05 05:44:42 +08:00
|
|
|
let _ = self.matrix.try_inverse_mut();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-03 01:37:44 +08:00
|
|
|
impl<N: Real, D: DimNameAdd<U1>> Transform<N, D, TGeneral>
|
2018-10-22 13:00:10 +08:00
|
|
|
where DefaultAllocator: Allocator<N, DimNameSum<D, U1>, DimNameSum<D, U1>>
|
2018-02-02 19:26:35 +08:00
|
|
|
{
|
2016-12-05 05:44:42 +08:00
|
|
|
/// A mutable reference to underlying matrix. Use `.matrix_mut_unchecked` instead if this
|
|
|
|
/// transformation category is not `TGeneral`.
|
|
|
|
#[inline]
|
2017-08-03 01:37:44 +08:00
|
|
|
pub fn matrix_mut(&mut self) -> &mut MatrixN<N, DimNameSum<D, U1>> {
|
2016-12-05 05:44:42 +08:00
|
|
|
self.matrix_mut_unchecked()
|
|
|
|
}
|
|
|
|
}
|
2017-07-29 00:57:44 +08:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2018-05-19 23:15:15 +08:00
|
|
|
use base::Matrix4;
|
2017-07-29 00:57:44 +08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn checks_homogeneous_invariants_of_square_identity_matrix() {
|
2018-02-02 19:26:35 +08:00
|
|
|
assert!(TAffine::check_homogeneous_invariants(
|
|
|
|
&Matrix4::<f32>::identity()
|
|
|
|
));
|
2017-07-29 00:57:44 +08:00
|
|
|
}
|
|
|
|
}
|