Browse Source

Math: add Matrix[34]::rotationShear(), scalingSquared() and scaling().

Important: the rotation() accessor now allows non-uniform scaling but
expects orthogonality (previously it allowed non-orthogonal rotation
axes but disallowed non-uniform scaling).

Documentation of all these accessors is further improved now as well.
pull/191/head
Vladimír Vondruš 8 years ago
parent
commit
fbd3dedeb3
  1. 18
      doc/changelog.dox
  2. 288
      src/Magnum/Math/Matrix3.h
  3. 308
      src/Magnum/Math/Matrix4.h
  4. 128
      src/Magnum/Math/Test/Matrix3Test.cpp
  5. 137
      src/Magnum/Math/Test/Matrix4Test.cpp

18
doc/changelog.dox

@ -51,6 +51,11 @@ See also:
@ref Math::Intersection::sphereCone(),
@ref Math::Intersection::aabbCone(),
@ref Math::Intersection::rangeCone()
- Added @ref Math::Matrix3::rotationShear(),
@ref Math::Matrix4::rotationShear(), @ref Math::Matrix3::scalingSquared(),
@ref Math::Matrix4::scalingSquared(), @ref Math::Matrix3::scaling() const
and @ref Math::Matrix4::scaling() const getters for extracting further
properties from the rotation/scaling part of a matrix
- Added @ref Math::Constants::piQuarter()
- Added a convenience function @ref Math::select() as a constant
interpolation counterpart to @ref Math::lerp()
@ -104,6 +109,12 @@ See also:
- The @ref GL::BufferUsage parameter in @ref GL::Buffer::setData() is now
optional, defaults to @ref GL::BufferUsage::StaticDraw
@subsubsection changelog-latest-changes-math Math library
- @ref Math::Matrix3::rotation() const and @ref Math::Matrix4::rotation() const
now allow non-uniform scaling, but expect the roation/scaling part to be
orthogonal after normalization
@subsubsection changelog-latest-changes-meshtools MeshTools library
- @ref MeshTools::compile() API got simplified to make use of the new buffer
@ -213,6 +224,13 @@ See also:
- @ref Audio::Playable is now by default omnidirectional instead of having
source in direction of negative Z axis (the direction has effect only when
setting inner and outer cone properties of @ref Audio::Source).
- @ref Math::Matrix3::rotation() const and @ref Math::Matrix4::rotation() const
now allow non-uniform scaling, but expect the roation/scaling part to be
orthogonal after normalization (before it required uniform scaling but not
orthogonal axes) --- this might cause imprecise data that were previously
working to fail with an assertion. See the new
@ref Math::Matrix3::rotationShear() and @ref Math::Matrix4::rotationShear()
accessors for a possible solution.
- @ref Trade::PhongMaterialData::ambientColor(),
@ref Trade::PhongMaterialData::diffuseColor() "diffuseColor()" and
@ref Trade::PhongMaterialData::specularColor() "specularColor()" now return

288
src/Magnum/Math/Matrix3.h

@ -77,8 +77,7 @@ template<class T> class Matrix3: public Matrix3x3<T> {
* 0 & 0 & 1
* \end{pmatrix}
* @f]
* @see @ref rotationScaling(),
* @ref Matrix4::scaling(const Vector3<T>&),
* @see @ref scaling() const, @ref Matrix4::scaling(const Vector3<T>&),
* @ref Vector2::xScale(), @ref Vector2::yScale()
*/
constexpr static Matrix3<T> scaling(const Vector2<T>& vector) {
@ -259,7 +258,7 @@ template<class T> class Matrix3: public Matrix3x3<T> {
/**
* @brief 2D rotation and scaling part of the matrix
*
* Upper-left 2x2 part of the matrix. @f[
* Unchanged upper-left 2x2 part of the matrix. @f[
* \begin{pmatrix}
* \color{m-danger} a_x & \color{m-success} b_x & t_x \\
* \color{m-danger} a_y & \color{m-success} b_y & t_y \\
@ -267,10 +266,16 @@ template<class T> class Matrix3: public Matrix3x3<T> {
* \end{pmatrix}
* @f]
*
* Note that an arbitrary combination of rotation and scaling can also
* represent shear and reflection. Especially when non-uniform scaling
* is involved, decomposition of the result into primary linear
* transformations may have multiple equivalent solutions. See
* @ref Algorithms::svd() and @ref Algorithms::qr() for further info.
* See also @ref rotationShear(), @ref rotation() const and
* @ref scaling() const for extracting further properties.
*
* @see @ref from(const Matrix2x2<T>&, const Vector2<T>&),
* @ref rotation() const, @ref rotationNormalized(),
* @ref uniformScaling(), @ref rotation(Rad<T>),
* @ref Matrix4::rotationScaling()
* @ref rotation(Rad<T>), @ref Matrix4::rotationScaling()
*/
constexpr Matrix2x2<T> rotationScaling() const {
return {(*this)[0].xy(),
@ -278,10 +283,11 @@ template<class T> class Matrix3: public Matrix3x3<T> {
}
/**
* @brief 2D rotation part of the matrix assuming there is no scaling
* @brief 2D rotation and scaling part of the matrix
*
* Similar to @ref rotationScaling(), but additionally checks that the
* base vectors are normalized. Check its documentation for caveats. @f[
* Normalized upper-left 2x2 part of the matrix. Assuming the following
* matrix, with the upper-left 2x2 part represented by column vectors
* @f$ \boldsymbol{a} @f$ and @f$ \boldsymbol{b} @f$: @f[
* \begin{pmatrix}
* \color{m-danger} a_x & \color{m-success} b_x & t_x \\
* \color{m-danger} a_y & \color{m-success} b_y & t_y \\
@ -289,50 +295,173 @@ template<class T> class Matrix3: public Matrix3x3<T> {
* \end{pmatrix}
* @f]
*
* @see @ref rotation() const, @ref uniformScaling(),
* @ref Matrix4::rotationNormalized()
* @todo assert also orthogonality or this is good enough?
* @m_class{m-noindent}
*
* the resulting rotation is extracted as: @f[
* \boldsymbol{R} = \begin{pmatrix}
* \cfrac{\boldsymbol{a}}{|\boldsymbol{a}|} &
* \cfrac{\boldsymbol{b}}{|\boldsymbol{b}|}
* \end{pmatrix}
* @f]
*
* This function is a counterpart to @ref rotation() const that does
* not require orthogonal input. See also @ref rotationScaling() and
* @ref scaling() const for extracting other properties. The
* @ref Algorithms::svd() and @ref Algorithms::qr() can be used to
* separate the rotation / shear properties.
*
* @see @ref from(const Matrix2x2<T>&, const Vector2<T>&),
* @ref rotation(Rad), @ref Matrix4::rotationShear() const
*/
Matrix2x2<T> rotationNormalized() const {
CORRADE_ASSERT((*this)[0].xy().isNormalized() && (*this)[1].xy().isNormalized(),
"Math::Matrix3::rotationNormalized(): the rotation part is not normalized", {});
return {(*this)[0].xy(),
(*this)[1].xy()};
Matrix2x2<T> rotationShear() const {
return {(*this)[0].xy().normalized(),
(*this)[1].xy().normalized()};
}
/**
* @brief 2D rotation part of the matrix
*
* Normalized upper-left 2x2 part of the matrix. Expects uniform
* scaling. @f[
* Normalized upper-left 2x2 part of the matrix. Expects that the
* normalized part is orthogonal. Assuming the following matrix, with
* the upper-left 2x2 part represented by column vectors
* @f$ \boldsymbol{a} @f$ and @f$ \boldsymbol{b} @f$: @f[
* \begin{pmatrix}
* \color{m-danger} a_x & \color{m-success} b_x & t_x \\
* \color{m-danger} a_y & \color{m-success} b_y & t_y \\
* \color{m-warning} a_x & \color{m-warning} b_x & t_x \\
* \color{m-warning} a_y & \color{m-warning} b_y & t_y \\
* \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 1
* \end{pmatrix}
* @f]
*
* @note Extracting rotation part of a matrix this way will cause
* @m_class{m-noindent}
*
* the resulting rotation is extracted as: @f[
* \boldsymbol{R} = \begin{pmatrix}
* \cfrac{\boldsymbol{a}}{|\boldsymbol{a}|} &
* \cfrac{\boldsymbol{b}}{|\boldsymbol{b}|}
* \end{pmatrix}
* @f]
*
* This function is equivalent to @ref rotationShear() but with the
* added orthogonality requirement. See also @ref rotationScaling() and
* @ref scaling() const for extracting other properties.
*
* @note Extracting rotation part of a matrix this way may cause
* assertions in case you have unsanitized input (for example a
* model transformation loaded from an external source) or when
* you accumulate many transformations together (for example when
* controlling a FPS camera). To mitigate this, either renormalize
* the matrix using @ref Algorithms::gramSchmidtOrthogonalize() or
* @ref Algorithms::svd() first, use a different transformation
* controlling a FPS camera). To mitigate this, either first
* reorthogonalize the matrix using
* @ref Algorithms::gramSchmidtOrthogonalize(), decompose it to
* basic linear transformations using @ref Algorithms::svd() or
* @ref Algorithms::qr() or use a different transformation
* representation that suffers less floating point error and can
* be easier renormalized such as @ref DualComplex, or, ignore the
* error and extract combined non-uniform rotation and scaling
* with @ref rotationScaling().
* be easier renormalized such as @ref DualComplex. Another
* possibility is to ignore the error and extract combined
* rotation and scaling / shear with @ref rotationScaling() /
* @ref rotationShear().
*
* @see @ref rotationNormalized(), @ref rotationScaling(),
* @ref uniformScaling(), @ref rotation(Rad<T>),
* @ref Matrix4::rotation() const
* @see @ref rotationNormalized(), @ref scaling() const,
* @ref rotation(Rad<T>), @ref Matrix4::rotation() const
*/
Matrix2x2<T> rotation() const {
CORRADE_ASSERT(TypeTraits<T>::equals((*this)[0].xy().dot(), (*this)[1].xy().dot()),
"Math::Matrix3::rotation(): the matrix doesn't have uniform scaling", {});
return {(*this)[0].xy().normalized(),
(*this)[1].xy().normalized()};
Matrix2x2<T> rotation() const;
/**
* @brief 2D rotation part of the matrix assuming there is no scaling
*
* Similar to @ref rotation(), but expects that the rotation part is
* orthogonal, saving the extra renormalization. Assuming the
* following matrix, with the upper-left 2x2 part represented by column
* vectors @f$ \boldsymbol{a} @f$ and @f$ \boldsymbol{b} @f$: @f[
* \begin{pmatrix}
* \color{m-danger} a_x & \color{m-success} b_x & t_x \\
* \color{m-danger} a_y & \color{m-success} b_y & t_y \\
* \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 1
* \end{pmatrix}
* @f]
*
* @m_class{m-noindent}
*
* the resulting rotation is extracted as: @f[
* \boldsymbol{R} = \begin{pmatrix}
* \cfrac{\boldsymbol{a}}{|\boldsymbol{a}|} &
* \cfrac{\boldsymbol{b}}{|\boldsymbol{b}|}
* \end{pmatrix} = \begin{pmatrix}
* \boldsymbol{a} &
* \boldsymbol{b}
* \end{pmatrix}
* @f]
*
* In particular, for an orthogonal matrix, @ref rotationScaling(),
* @ref rotationShear(), @ref rotation() const and
* @ref rotationNormalized() all return the same value.
*
* @see @ref isOrthogonal(), @ref uniformScaling(),
* @ref Matrix4::rotationNormalized()
*/
Matrix2x2<T> rotationNormalized() const;
/**
* @brief Non-uniform scaling part of the matrix, squared
*
* Squared length of vectors in upper-left 2x2 part of the matrix.
* Faster alternative to @ref scaling() const, because it doesn't
* calculate the square root. Assuming the following matrix, with the
* upper-left 2x2 part represented by column vectors
* @f$ \boldsymbol{a} @f$ and @f$ \boldsymbol{b} @f$: @f[
* \begin{pmatrix}
* \color{m-warning} a_x & \color{m-warning} b_x & t_x \\
* \color{m-warning} a_y & \color{m-warning} b_y & t_y \\
* \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 1
* \end{pmatrix}
* @f]
*
* @m_class{m-noindent}
*
* the resulting scaling vector, squared, is: @f[
* \boldsymbol{s}^2 = \begin{pmatrix}
* \boldsymbol{a} \cdot \boldsymbol{a} \\
* \boldsymbol{b} \cdot \boldsymbol{b}
* \end{pmatrix}
* @f]
*
* @see @ref scaling() const, @ref uniformScalingSquared(),
* @ref rotation() const, @ref Matrix4::scalingSquared()
*/
Vector2<T> scalingSquared() const {
return {(*this)[0].xy().dot(),
(*this)[1].xy().dot()};
}
/**
* @brief Non-uniform scaling part of the matrix, squared
*
* Length of vectors in upper-left 2x2 part of the matrix. Use the
* faster alternative @ref scalingSquared() where possible. Assuming
* the following matrix, with the upper-left 2x2 part represented by
* column vectors @f$ \boldsymbol{a} @f$ and @f$ \boldsymbol{b} @f$:
* @f[
* \begin{pmatrix}
* \color{m-warning} a_x & \color{m-warning} b_x & t_x \\
* \color{m-warning} a_y & \color{m-warning} b_y & t_y \\
* \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 1
* \end{pmatrix}
* @f]
*
* @m_class{m-noindent}
*
* the resulting scaling vector is: @f[
* \boldsymbol{s} = \begin{pmatrix}
* | \boldsymbol{a} | \\
* | \boldsymbol{b} |
* \end{pmatrix}
* @f]
*
* @see @ref scalingSquared(), @ref uniformScaling(),
* @ref rotation() const, @ref Matrix4::scaling() const
*/
Vector2<T> scaling() const {
return {(*this)[0].xy().length(),
(*this)[1].xy().length()};
}
/**
@ -341,7 +470,9 @@ template<class T> class Matrix3: public Matrix3x3<T> {
* Squared length of vectors in upper-left 2x2 part of the matrix.
* Expects that the scaling is the same in all axes. Faster alternative
* to @ref uniformScaling(), because it doesn't compute the square
* root. See its documentation for caveats. @f[
* root. Assuming the following matrix, with the upper-left 2x2 part
* represented by column vectors @f$ \boldsymbol{a} @f$ and
* @f$ \boldsymbol{b} @f$: @f[
* \begin{pmatrix}
* \color{m-warning} a_x & \color{m-warning} b_x & t_x \\
* \color{m-warning} a_y & \color{m-warning} b_y & t_y \\
@ -349,23 +480,38 @@ template<class T> class Matrix3: public Matrix3x3<T> {
* \end{pmatrix}
* @f]
*
* @see @ref rotationScaling(), @ref rotation() const,
* @ref rotationNormalized(), @ref scaling(const Vector2<T>&),
* @ref Matrix4::uniformScaling()
* @m_class{m-noindent}
*
* the resulting uniform scaling, squared, is: @f[
* s^2 = \boldsymbol{a} \cdot \boldsymbol{a}
* = \boldsymbol{b} \cdot \boldsymbol{b}
* @f]
*
* @note Extracting uniform scaling of a matrix this way may cause
* assertions in case you have unsanitized input (for example a
* model transformation loaded from an external source) or when
* you accumulate many transformations together (for example when
* controlling a FPS camera). To mitigate this, either first
* reorthogonalize the matrix using
* @ref Algorithms::gramSchmidtOrthogonalize(), decompose it to
* basic linear transformations using @ref Algorithms::svd() or
* @ref Algorithms::qr() or extract a non-uniform scaling using
* @ref scalingSquared().
*
* @see @ref rotation() const, @ref scaling() const,
* @ref scaling(const Vector2<T>&),
* @ref Matrix4::uniformScalingSquared()
*/
T uniformScalingSquared() const {
const T scalingSquared = (*this)[0].xy().dot();
CORRADE_ASSERT(TypeTraits<T>::equals((*this)[1].xy().dot(), scalingSquared),
"Math::Matrix3::uniformScaling(): the matrix doesn't have uniform scaling", {});
return scalingSquared;
}
T uniformScalingSquared() const;
/**
* @brief Uniform scaling part of the matrix
*
* Length of vectors in upper-left 2x2 part of the matrix. Expects that
* the scaling is the same in all axes. Use faster alternative
* @ref uniformScalingSquared() where possible. @f[
* @ref uniformScalingSquared() where possible. Assuming the following
* matrix, with the upper-left 3x3 part represented by column vectors
* @f$ \boldsymbol{a} @f$ and @f$ \boldsymbol{b} @f$: @f[
* \begin{pmatrix}
* \color{m-warning} a_x & \color{m-warning} b_x & t_x \\
* \color{m-warning} a_y & \color{m-warning} b_y & t_y \\
@ -373,18 +519,25 @@ template<class T> class Matrix3: public Matrix3x3<T> {
* \end{pmatrix}
* @f]
*
* @note Extracting uniform scaling of a matrix this way will cause
* @m_class{m-noindent}
*
* the resulting uniform scaling is: @f[
* s = | \boldsymbol{a} | = | \boldsymbol{b} |
* @f]
*
* @note Extracting uniform scaling of a matrix this way may cause
* assertions in case you have unsanitized input (for example a
* model transformation loaded from an external source) or when
* you accumulate many transformations together (for example when
* controlling a FPS camera). To mitigate this, either renormalize
* the matrix using @ref Algorithms::gramSchmidtOrthogonalize() or
* @ref Algorithms::svd() first or extract the scaling manually by
* calculating lengths of @ref right() and @ref up() vectors.
*
* @see @ref rotationScaling(), @ref rotation() const,
* @ref rotationNormalized(), @ref scaling(const Vector2<T>&),
* @ref Matrix4::uniformScaling()
* controlling a FPS camera). To mitigate this, either first
* reorthogonalize the matrix using
* @ref Algorithms::gramSchmidtOrthogonalize(), decompose it to
* basic linear transformations using @ref Algorithms::svd() or
* @ref Algorithms::qr() or extract a non-uniform scaling using
* @ref scaling() const.
*
* @see @ref rotation() const, @ref scalingSquared() const,
* @ref scaling(const Vector2<T>&), @ref Matrix4::uniformScaling()
*/
T uniformScaling() const { return std::sqrt(uniformScalingSquared()); }
@ -499,6 +652,29 @@ template<class T> Matrix3<T> Matrix3<T>::rotation(const Rad<T> angle) {
{ T(0), T(0), T(1)}};
}
template<class T> Matrix2x2<T> Matrix3<T>::rotation() const {
Matrix2x2<T> rotation{(*this)[0].xy().normalized(),
(*this)[1].xy().normalized()};
CORRADE_ASSERT(rotation.isOrthogonal(),
"Math::Matrix3::rotation(): the normalized rotation part is not orthogonal", {});
return rotation;
}
template<class T> Matrix2x2<T> Matrix3<T>::rotationNormalized() const {
Matrix2x2<T> rotation{(*this)[0].xy(),
(*this)[1].xy()};
CORRADE_ASSERT(rotation.isOrthogonal(),
"Math::Matrix3::rotationNormalized(): the rotation part is not orthogonal", {});
return rotation;
}
template<class T> T Matrix3<T>::uniformScalingSquared() const {
const T scalingSquared = (*this)[0].xy().dot();
CORRADE_ASSERT(TypeTraits<T>::equals((*this)[1].xy().dot(), scalingSquared),
"Math::Matrix3::uniformScaling(): the matrix doesn't have uniform scaling", {});
return scalingSquared;
}
template<class T> inline Matrix3<T> Matrix3<T>::invertedRigid() const {
CORRADE_ASSERT(isRigidTransformation(),
"Math::Matrix3::invertedRigid(): the matrix doesn't represent rigid transformation", {});

308
src/Magnum/Math/Matrix4.h

@ -62,7 +62,7 @@ template<class T> class Matrix4: public Matrix4x4<T> {
* 0 & 0 & 0 & 1
* \end{pmatrix}
* @f]
* @see @ref translation(), @ref DualQuaternion::translation(),
* @see @ref translation() const, @ref DualQuaternion::translation(),
* @ref Matrix3::translation(const Vector2<T>&),
* @ref Vector3::xAxis(), @ref Vector3::yAxis(),
* @ref Vector3::zAxis()
@ -86,8 +86,7 @@ template<class T> class Matrix4: public Matrix4x4<T> {
* 0 & 0 & 0 & 1
* \end{pmatrix}
* @f]
* @see @ref rotationScaling(),
* @ref Matrix3::scaling(const Vector2<T>&),
* @see @ref scaling() const, @ref Matrix3::scaling(const Vector2<T>&),
* @ref Vector3::xScale(), @ref Vector3::yScale(),
* @ref Vector3::zScale()
*/
@ -433,7 +432,7 @@ template<class T> class Matrix4: public Matrix4x4<T> {
/**
* @brief 3D rotation and scaling part of the matrix
*
* Upper-left 3x3 part of the matrix. @f[
* Unchanged upper-left 3x3 part of the matrix. @f[
* \begin{pmatrix}
* \color{m-danger} a_x & \color{m-success} b_x & \color{m-info} c_x & t_x \\
* \color{m-danger} a_y & \color{m-success} b_y & \color{m-info} c_y & t_y \\
@ -442,9 +441,16 @@ template<class T> class Matrix4: public Matrix4x4<T> {
* \end{pmatrix}
* @f]
*
* Note that an arbitrary combination of rotation and scaling can also
* represent shear and reflection. Especially when non-uniform scaling
* is involved, decomposition of the result into primary linear
* transformations may have multiple equivalent solutions. See
* @ref Algorithms::svd() and @ref Algorithms::qr() for further info.
* See also @ref rotationShear(), @ref rotation() const and
* @ref scaling() const for extracting further properties.
*
* @see @ref from(const Matrix3x3<T>&, const Vector3<T>&),
* @ref rotation() const, @ref rotationNormalized(),
* @ref uniformScaling(), @ref rotation(Rad, const Vector3<T>&),
* @ref rotation(Rad, const Vector3<T>&),
* @ref Matrix3::rotationScaling() const
*/
constexpr Matrix3x3<T> rotationScaling() const {
@ -454,68 +460,216 @@ template<class T> class Matrix4: public Matrix4x4<T> {
}
/**
* @brief 3D rotation part of the matrix assuming there is no scaling
* @brief 3D rotation and scaling part of the matrix
*
* Similar to @ref rotationScaling(), but additionally checks that the
* base vectors are normalized. Check its documentation for caveats. @f[
* Normalized upper-left 3x3 part of the matrix. Assuming the following
* matrix, with the upper-left 3x3 part represented by column vectors
* @f$ \boldsymbol{a} @f$, @f$ \boldsymbol{b} @f$ and
* @f$ \boldsymbol{c} @f$: @f[
* \begin{pmatrix}
* \color{m-danger} a_x & \color{m-success} b_x & \color{m-info} c_x & t_x \\
* \color{m-danger} a_y & \color{m-success} b_y & \color{m-info} c_y & t_y \\
* \color{m-danger} a_z & \color{m-success} b_z & \color{m-info} c_z & t_z \\
* \color{m-warning} a_x & \color{m-warning} b_x & \color{m-warning} c_x & t_x \\
* \color{m-warning} a_y & \color{m-warning} b_y & \color{m-warning} c_y & t_y \\
* \color{m-warning} a_z & \color{m-warning} b_z & \color{m-warning} c_z & t_z \\
* \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 1
* \end{pmatrix}
* @f]
*
* @see @ref rotation() const, @ref uniformScaling(),
* @ref Matrix3::rotationNormalized()
* @todo assert also orthogonality or this is good enough?
* @m_class{m-noindent}
*
* the resulting rotation is extracted as: @f[
* \boldsymbol{R} = \begin{pmatrix}
* \cfrac{\boldsymbol{a}}{|\boldsymbol{a}|} &
* \cfrac{\boldsymbol{b}}{|\boldsymbol{b}|} &
* \cfrac{\boldsymbol{c}}{|\boldsymbol{c}|}
* \end{pmatrix}
* @f]
*
* This function is a counterpart to @ref rotation() const that does
* not require orthogonal input. See also @ref rotationScaling() and
* @ref scaling() const for extracting other properties. The
* @ref Algorithms::svd() and @ref Algorithms::qr() can be used to
* separate the rotation / shear properties.
*
* @see @ref from(const Matrix3x3<T>&, const Vector3<T>&),
* @ref rotation(Rad, const Vector3<T>&),
* @ref Matrix3::rotationShear() const
*/
Matrix3x3<T> rotationNormalized() const {
CORRADE_ASSERT((*this)[0].xyz().isNormalized() && (*this)[1].xyz().isNormalized() && (*this)[2].xyz().isNormalized(),
"Math::Matrix4::rotationNormalized(): the rotation part is not normalized", {});
return {(*this)[0].xyz(),
(*this)[1].xyz(),
(*this)[2].xyz()};
Matrix3x3<T> rotationShear() const {
return {(*this)[0].xyz().normalized(),
(*this)[1].xyz().normalized(),
(*this)[2].xyz().normalized()};
}
/**
* @brief 3D rotation part of the matrix
*
* Normalized upper-left 3x3 part of the matrix. Expects uniform
* scaling. @f[
* Normalized upper-left 3x3 part of the matrix. Expects that the
* normalized part is orthogonal. Assuming the following matrix, with
* the upper-left 3x3 part represented by column vectors
* @f$ \boldsymbol{a} @f$, @f$ \boldsymbol{b} @f$ and
* @f$ \boldsymbol{c} @f$: @f[
* \begin{pmatrix}
* \color{m-danger} a_x & \color{m-success} b_x & \color{m-info} c_x & t_x \\
* \color{m-danger} a_y & \color{m-success} b_y & \color{m-info} c_y & t_y \\
* \color{m-danger} a_z & \color{m-success} b_z & \color{m-info} c_z & t_z \\
* \color{m-warning} a_x & \color{m-warning} b_x & \color{m-warning} c_x & t_x \\
* \color{m-warning} a_y & \color{m-warning} b_y & \color{m-warning} c_y & t_y \\
* \color{m-warning} a_z & \color{m-warning} b_z & \color{m-warning} c_z & t_z \\
* \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 1
* \end{pmatrix}
* @f]
*
* @note Extracting rotation part of a matrix this way will cause
* @m_class{m-noindent}
*
* the resulting rotation is extracted as: @f[
* \boldsymbol{R} = \begin{pmatrix}
* \cfrac{\boldsymbol{a}}{|\boldsymbol{a}|} &
* \cfrac{\boldsymbol{b}}{|\boldsymbol{b}|} &
* \cfrac{\boldsymbol{c}}{|\boldsymbol{c}|}
* \end{pmatrix}
* @f]
*
* This function is equivalent to @ref rotationShear() but with the
* added orthogonality requirement. See also @ref rotationScaling() and
* @ref scaling() const for extracting other properties.
*
* @note Extracting rotation part of a matrix this way may cause
* assertions in case you have unsanitized input (for example a
* model transformation loaded from an external source) or when
* you accumulate many transformations together (for example when
* controlling a FPS camera). To mitigate this, either renormalize
* the matrix using @ref Algorithms::gramSchmidtOrthogonalize() or
* @ref Algorithms::svd() first, use a different transformation
* controlling a FPS camera). To mitigate this, either first
* reorthogonalize the matrix using
* @ref Algorithms::gramSchmidtOrthogonalize(), decompose it to
* basic linear transformations using @ref Algorithms::svd() or
* @ref Algorithms::qr() or use a different transformation
* representation that suffers less floating point error and can
* be easier renormalized such as @ref DualQuaternion, or, ignore
* the error and extract combined non-uniform rotation and scaling
* with @ref rotationScaling().
* be easier renormalized such as @ref DualQuaternion. Another
* possibility is to ignore the error and extract combined
* rotation and scaling / shear with @ref rotationScaling() /
* @ref rotationShear().
*
* @see @ref rotationNormalized(), @ref rotationScaling(),
* @ref uniformScaling(), @ref rotation(Rad, const Vector3<T>&),
* @see @ref rotationNormalized(), @ref scaling() const,
* @ref rotation(Rad, const Vector3<T>&),
* @ref Matrix3::rotation() const
*/
Matrix3x3<T> rotation() const;
/**
* @brief 3D rotation part of the matrix assuming there is no scaling
*
* Similar to @ref rotation(), but expects that the rotation part is
* orthogonal, saving the extra renormalization. Assuming the
* following matrix, with the upper-left 3x3 part represented by column
* vectors @f$ \boldsymbol{a} @f$, @f$ \boldsymbol{b} @f$ and
* @f$ \boldsymbol{c} @f$: @f[
* \begin{pmatrix}
* \color{m-danger} a_x & \color{m-success} b_x & \color{m-info} c_x & t_x \\
* \color{m-danger} a_y & \color{m-success} b_y & \color{m-info} c_y & t_y \\
* \color{m-danger} a_z & \color{m-success} b_z & \color{m-info} c_z & t_z \\
* \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 1
* \end{pmatrix}
* @f]
*
* @m_class{m-noindent}
*
* the resulting rotation is extracted as: @f[
* \boldsymbol{R} = \begin{pmatrix}
* \cfrac{\boldsymbol{a}}{|\boldsymbol{a}|} &
* \cfrac{\boldsymbol{b}}{|\boldsymbol{b}|} &
* \cfrac{\boldsymbol{c}}{|\boldsymbol{c}|}
* \end{pmatrix} = \begin{pmatrix}
* \boldsymbol{a} &
* \boldsymbol{b} &
* \boldsymbol{c}
* \end{pmatrix}
* @f]
*
* In particular, for an orthogonal matrix, @ref rotationScaling(),
* @ref rotationShear(), @ref rotation() const and
* @ref rotationNormalized() all return the same value.
*
* @see @ref isOrthogonal(), @ref uniformScaling(),
* @ref Matrix3::rotationNormalized()
*/
Matrix3x3<T> rotationNormalized() const;
/**
* @brief Non-uniform scaling part of the matrix, squared
*
* Squared length of vectors in upper-left 3x3 part of the matrix.
* Faster alternative to @ref scaling() const, because it doesn't
* calculate the square root. Assuming the following matrix, with the
* upper-left 3x3 part represented by column vectors
* @f$ \boldsymbol{a} @f$, @f$ \boldsymbol{b} @f$ and
* @f$ \boldsymbol{c} @f$: @f[
* \begin{pmatrix}
* \color{m-warning} a_x & \color{m-warning} b_x & \color{m-warning} c_x & t_x \\
* \color{m-warning} a_y & \color{m-warning} b_y & \color{m-warning} c_y & t_y \\
* \color{m-warning} a_z & \color{m-warning} b_z & \color{m-warning} c_z & t_z \\
* \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 1
* \end{pmatrix}
* @f]
*
* @m_class{m-noindent}
*
* the resulting scaling vector, squared, is: @f[
* \boldsymbol{s}^2 = \begin{pmatrix}
* \boldsymbol{a} \cdot \boldsymbol{a} \\
* \boldsymbol{b} \cdot \boldsymbol{b} \\
* \boldsymbol{c} \cdot \boldsymbol{c}
* \end{pmatrix}
* @f]
*
* @see @ref scaling() const, @ref uniformScalingSquared(),
* @ref rotation() const, @ref Matrix3::scalingSquared()
*/
Vector3<T> scalingSquared() const {
return {(*this)[0].xyz().dot(),
(*this)[1].xyz().dot(),
(*this)[2].xyz().dot()};
}
/**
* @brief Non-uniform scaling part of the matrix, squared
*
* Length of vectors in upper-left 3x3 part of the matrix. Use the
* faster alternative @ref scalingSquared() where possible. Assuming
* the following matrix, with the upper-left 3x3 part represented by
* column vectors @f$ \boldsymbol{a} @f$, @f$ \boldsymbol{b} @f$ and
* @f$ \boldsymbol{c} @f$: @f[
* \begin{pmatrix}
* \color{m-warning} a_x & \color{m-warning} b_x & \color{m-warning} c_x & t_x \\
* \color{m-warning} a_y & \color{m-warning} b_y & \color{m-warning} c_y & t_y \\
* \color{m-warning} a_z & \color{m-warning} b_z & \color{m-warning} c_z & t_z \\
* \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 0 & \color{m-dim} 1
* \end{pmatrix}
* @f]
*
* @m_class{m-noindent}
*
* the resulting scaling vector is: @f[
* \boldsymbol{s} = \begin{pmatrix}
* | \boldsymbol{a} | \\
* | \boldsymbol{b} | \\
* | \boldsymbol{c} |
* \end{pmatrix}
* @f]
*
* @see @ref scalingSquared(), @ref uniformScaling(),
* @ref rotation() const, @ref Matrix3::scaling() const
*/
Vector3<T> scaling() const {
return {(*this)[0].xyz().length(),
(*this)[1].xyz().length(),
(*this)[2].xyz().length()};
}
/**
* @brief Uniform scaling part of the matrix, squared
*
* Squared length of vectors in upper-left 3x3 part of the matrix.
* Expects that the scaling is the same in all axes. Faster alternative
* to @ref uniformScaling(), because it doesn't compute the square
* root. See its documentation for caveats. @f[
* to @ref uniformScaling(), because it doesn't calculate the square
* root. Assuming the following matrix, with the upper-left 3x3 part
* represented by column vectors @f$ \boldsymbol{a} @f$,
* @f$ \boldsymbol{b} @f$ and @f$ \boldsymbol{c} @f$: @f[
* \begin{pmatrix}
* \color{m-warning} a_x & \color{m-warning} b_x & \color{m-warning} c_x & t_x \\
* \color{m-warning} a_y & \color{m-warning} b_y & \color{m-warning} c_y & t_y \\
@ -524,9 +678,28 @@ template<class T> class Matrix4: public Matrix4x4<T> {
* \end{pmatrix}
* @f]
*
* @see @ref rotationScaling(), @ref rotation() const,
* @ref rotationNormalized(), @ref scaling(const Vector3<T>&),
* @ref Matrix3::uniformScaling()
* @m_class{m-noindent}
*
* the resulting uniform scaling, squared, is: @f[
* s^2 = \boldsymbol{a} \cdot \boldsymbol{a}
* = \boldsymbol{b} \cdot \boldsymbol{b}
* = \boldsymbol{c} \cdot \boldsymbol{c}
* @f]
*
* @note Extracting uniform scaling of a matrix this way may cause
* assertions in case you have unsanitized input (for example a
* model transformation loaded from an external source) or when
* you accumulate many transformations together (for example when
* controlling a FPS camera). To mitigate this, either first
* reorthogonalize the matrix using
* @ref Algorithms::gramSchmidtOrthogonalize(), decompose it to
* basic linear transformations using @ref Algorithms::svd() or
* @ref Algorithms::qr() or extract a non-uniform scaling using
* @ref scalingSquared().
*
* @see @ref rotation() const, @ref scaling() const,
* @ref scaling(const Vector3<T>&),
* @ref Matrix3::uniformScalingSquared()
*/
T uniformScalingSquared() const;
@ -534,8 +707,11 @@ template<class T> class Matrix4: public Matrix4x4<T> {
* @brief Uniform scaling part of the matrix
*
* Length of vectors in upper-left 3x3 part of the matrix. Expects that
* the scaling is the same in all axes. Use faster alternative
* @ref uniformScalingSquared() where possible. @f[
* the scaling is the same in all axes. Use the faster alternative
* @ref uniformScalingSquared() where possible. Assuming the following
* matrix, with the upper-left 3x3 part represented by column vectors
* @f$ \boldsymbol{a} @f$, @f$ \boldsymbol{b} @f$ and
* @f$ \boldsymbol{c} @f$: @f[
* \begin{pmatrix}
* \color{m-warning} a_x & \color{m-warning} b_x & \color{m-warning} c_x & t_x \\
* \color{m-warning} a_y & \color{m-warning} b_y & \color{m-warning} c_y & t_y \\
@ -544,18 +720,25 @@ template<class T> class Matrix4: public Matrix4x4<T> {
* \end{pmatrix}
* @f]
*
* @note Extracting uniform scaling of a matrix this way will cause
* @m_class{m-noindent}
*
* the resulting uniform scaling is: @f[
* s = | \boldsymbol{a} | = | \boldsymbol{b} | = | \boldsymbol{c} |
* @f]
*
* @note Extracting uniform scaling of a matrix this way may cause
* assertions in case you have unsanitized input (for example a
* model transformation loaded from an external source) or when
* you accumulate many transformations together (for example when
* controlling a FPS camera). To mitigate this, either renormalize
* the matrix using @ref Algorithms::gramSchmidtOrthogonalize() or
* @ref Algorithms::svd() first or extract the scaling manually by
* calculating lengths of @ref right(), @ref up() and
* @ref backward() vectors.
*
* @see @ref rotationScaling(), @ref rotation() const,
* @ref rotationNormalized(), @ref scaling(const Vector3<T>&),
* controlling a FPS camera). To mitigate this, either first
* reorthogonalize the matrix using
* @ref Algorithms::gramSchmidtOrthogonalize(), decompose it to
* basic linear transformations using @ref Algorithms::svd() or
* @ref Algorithms::qr() or extract a non-uniform scaling using
* @ref scaling() const.
*
* @see @ref rotation() const, @ref scaling() const,
* @ref scaling(const Vector3<T>&),
* @ref Matrix3::uniformScaling()
*/
T uniformScaling() const { return std::sqrt(uniformScalingSquared()); }
@ -787,13 +970,22 @@ template<class T> Matrix4<T> Matrix4<T>::lookAt(const Vector3<T>& eye, const Vec
return from({right, realUp, backward}, eye);
}
template<class T> inline Matrix3x3<T> Matrix4<T>::rotation() const {
CORRADE_ASSERT(TypeTraits<T>::equals((*this)[0].xyz().dot(), (*this)[1].xyz().dot()) &&
TypeTraits<T>::equals((*this)[1].xyz().dot(), (*this)[2].xyz().dot()),
"Math::Matrix4::rotation(): the matrix doesn't have uniform scaling", {});
return {(*this)[0].xyz().normalized(),
(*this)[1].xyz().normalized(),
(*this)[2].xyz().normalized()};
template<class T> Matrix3x3<T> Matrix4<T>::rotation() const {
Matrix3x3<T> rotation{(*this)[0].xyz().normalized(),
(*this)[1].xyz().normalized(),
(*this)[2].xyz().normalized()};
CORRADE_ASSERT(rotation.isOrthogonal(),
"Math::Matrix4::rotation(): the normalized rotation part is not orthogonal", {});
return rotation;
}
template<class T> Matrix3x3<T> Matrix4<T>::rotationNormalized() const {
Matrix3x3<T> rotation{(*this)[0].xyz(),
(*this)[1].xyz(),
(*this)[2].xyz()};
CORRADE_ASSERT(rotation.isOrthogonal(),
"Math::Matrix4::rotationNormalized(): the rotation part is not orthogonal", {});
return rotation;
}
template<class T> T Matrix4<T>::uniformScalingSquared() const {

128
src/Magnum/Math/Test/Matrix3Test.cpp

@ -78,11 +78,17 @@ struct Matrix3Test: Corrade::TestSuite::Tester {
void shearingX();
void shearingY();
void projection();
void fromParts();
void rotationScalingPart();
void rotationNormalizedPart();
void rotationShearPart();
void rotationPart();
void rotationPartNotOrthogonal();
void rotationNormalizedPart();
void rotationNormalizedPartNotOrthogonal();
void scalingPart();
void uniformScalingPart();
void uniformScalingPartNotUniform();
void vectorParts();
void invertedRigid();
void transform();
@ -118,11 +124,17 @@ Matrix3Test::Matrix3Test() {
&Matrix3Test::shearingX,
&Matrix3Test::shearingY,
&Matrix3Test::projection,
&Matrix3Test::fromParts,
&Matrix3Test::rotationScalingPart,
&Matrix3Test::rotationNormalizedPart,
&Matrix3Test::rotationShearPart,
&Matrix3Test::rotationPart,
&Matrix3Test::rotationPartNotOrthogonal,
&Matrix3Test::rotationNormalizedPart,
&Matrix3Test::rotationNormalizedPartNotOrthogonal,
&Matrix3Test::scalingPart,
&Matrix3Test::uniformScalingPart,
&Matrix3Test::uniformScalingPartNotUniform,
&Matrix3Test::vectorParts,
&Matrix3Test::invertedRigid,
&Matrix3Test::transform,
@ -131,6 +143,8 @@ Matrix3Test::Matrix3Test() {
&Matrix3Test::configuration});
}
using namespace Literals;
void Matrix3Test::construct() {
constexpr Matrix3 a = {{3.0f, 5.0f, 8.0f},
{4.5f, 4.0f, 7.0f},
@ -372,28 +386,22 @@ void Matrix3Test::rotationScalingPart() {
Vector2(4.0f, 4.0f)));
}
void Matrix3Test::rotationNormalizedPart() {
std::ostringstream o;
Error redirectError{&o};
void Matrix3Test::rotationShearPart() {
Matrix3 rotation = Matrix3::rotation(15.0_degf);
Matrix3 rotationScalingTranslation = rotation*
Matrix3::scaling(Vector2::yScale(3.5f))*
Matrix3::translation({2.0f, -3.0f});
Matrix3 a({1.0f, 0.0f, 8.0f},
{1.0f, 0.1f, 7.0f},
{7.0f, -1.0f, 8.0f});
a.rotationNormalized();
CORRADE_COMPARE(o.str(), "Math::Matrix3::rotationNormalized(): the rotation part is not normalized\n");
Matrix3 b({ 0.965926f, 0.258819f, 1.0f},
{-0.258819f, 0.965926f, 3.0f},
{ 0.0f, 0.0f, 1.0f});
CORRADE_COMPARE(b.rotationNormalized(), Matrix2x2(Vector2( 0.965926f, 0.258819f),
Vector2(-0.258819f, 0.965926f)));
Matrix2x2 a = rotationScalingTranslation.rotationShear();
CORRADE_COMPARE(a, rotation.rotationScaling());
CORRADE_COMPARE(a, (Matrix2x2{Vector2{ 0.965926f, 0.258819f},
Vector2{-0.258819f, 0.965926f}}));
}
void Matrix3Test::rotationPart() {
Matrix3 rotation = Matrix3::rotation(Deg(15.0f));
Matrix2x2 expectedRotationPart(Vector2( 0.965926f, 0.258819f),
Vector2(-0.258819f, 0.965926f));
Matrix3 rotation = Matrix3::rotation(15.0_degf);
Matrix2x2 expectedRotationPart{Vector2{ 0.965926f, 0.258819f},
Vector2{-0.258819f, 0.965926f}};
/* For rotation and translation this is the same as rotationScaling() */
Matrix3 rotationTranslation = rotation*Matrix3::translation({2.0f, 5.0f});
@ -401,33 +409,79 @@ void Matrix3Test::rotationPart() {
CORRADE_COMPARE(rotationTranslationPart, rotationTranslation.rotationScaling());
CORRADE_COMPARE(rotationTranslationPart, expectedRotationPart);
/* Test uniform scaling */
Matrix3 rotationScaling = rotation*Matrix3::scaling(Vector2(3.0f));
Matrix2x2 rotationScalingPart = rotationScaling.rotation();
/* Uniform scaling */
Matrix3 rotationUniformScalingTranslation = rotation*
Matrix3::scaling(Vector2(3.0f))*
Matrix3::translation({2.0f, -3.0f});
Matrix2x2 rotationUniformScalingPart = rotationUniformScalingTranslation.rotation();
CORRADE_COMPARE(rotationUniformScalingPart.determinant(), 1.0f);
CORRADE_COMPARE(rotationUniformScalingPart*rotationUniformScalingPart.transposed(), Matrix2x2());
CORRADE_COMPARE(rotationUniformScalingPart, expectedRotationPart);
/* Non-uniform scaling */
Matrix3 rotationScalingTranslation = rotation*
Matrix3::scaling(Vector2::yScale(3.5f))*
Matrix3::translation({2.0f, -3.0f});
Matrix2x2 rotationScalingPart = rotationScalingTranslation.rotation();
CORRADE_COMPARE(rotationScalingPart.determinant(), 1.0f);
CORRADE_COMPARE(rotationScalingPart*rotationScalingPart.transposed(), Matrix2x2());
CORRADE_COMPARE(rotationScalingPart, expectedRotationPart);
}
/* Fails on non-uniform scaling */
std::ostringstream o;
Error redirectError{&o};
Matrix2x2 rotationScaling2 = (rotation*Matrix3::scaling(Vector2::yScale(3.5f))).rotation();
CORRADE_COMPARE(o.str(), "Math::Matrix3::rotation(): the matrix doesn't have uniform scaling\n");
CORRADE_COMPARE(rotationScaling2, Matrix2x2());
void Matrix3Test::rotationPartNotOrthogonal() {
std::ostringstream out;
Error redirectError{&out};
/* Test both non-orthogonality and "unnormalizable" scaling */
Matrix3::shearingX(1.5f).rotation();
Matrix3::scaling(Vector2::yScale(0.0f)).rotation();
CORRADE_COMPARE(out.str(),
"Math::Matrix3::rotation(): the normalized rotation part is not orthogonal\n"
"Math::Matrix3::rotation(): the normalized rotation part is not orthogonal\n");
}
void Matrix3Test::rotationNormalizedPart() {
Matrix3 a({ 0.965926f, 0.258819f, 1.0f},
{-0.258819f, 0.965926f, 3.0f},
{ 0.0f, 0.0f, 1.0f});
CORRADE_COMPARE(a.rotationNormalized(),
(Matrix2x2{Vector2{ 0.965926f, 0.258819f},
Vector2{-0.258819f, 0.965926f}}));
}
void Matrix3Test::rotationNormalizedPartNotOrthogonal() {
std::ostringstream out;
Error redirectError{&out};
Matrix3 a({1.0f, 0.0f, 8.0f},
{1.0f, 0.1f, 7.0f},
{7.0f, -1.0f, 8.0f});
a.rotationNormalized();
CORRADE_COMPARE(out.str(), "Math::Matrix3::rotationNormalized(): the rotation part is not orthogonal\n");
}
void Matrix3Test::scalingPart() {
Matrix3 translationRotationScaling =
Matrix3::translation({2.0f, -3.0f})*
Matrix3::rotation(15.0_degf)*
Matrix3::scaling({0.5f, 3.5f});
CORRADE_COMPARE(translationRotationScaling.scaling(), (Vector2{0.5f, 3.5f}));
}
void Matrix3Test::uniformScalingPart() {
const Matrix3 rotation = Matrix3::rotation(Deg(-74.0f));
const Matrix3 rotation = Matrix3::rotation(-74.0_degf);
/* Test uniform scaling */
CORRADE_COMPARE((rotation*Matrix3::scaling(Vector2(3.0f))).uniformScaling(), 3.0f);
}
/* Fails on non-uniform scaling */
std::ostringstream o;
Error redirectError{&o};
const Float nonUniformScaling = (rotation*Matrix3::scaling(Vector2::yScale(3.0f))).uniformScaling();
CORRADE_COMPARE(o.str(), "Math::Matrix3::uniformScaling(): the matrix doesn't have uniform scaling\n");
CORRADE_COMPARE(nonUniformScaling, 0.0f);
void Matrix3Test::uniformScalingPartNotUniform() {
std::ostringstream out;
Error redirectError{&out};
Matrix3::scaling(Vector2::yScale(3.0f)).uniformScaling();
CORRADE_COMPARE(out.str(), "Math::Matrix3::uniformScaling(): the matrix doesn't have uniform scaling\n");
}
void Matrix3Test::vectorParts() {

137
src/Magnum/Math/Test/Matrix4Test.cpp

@ -93,9 +93,14 @@ struct Matrix4Test: Corrade::TestSuite::Tester {
void fromParts();
void rotationScalingPart();
void rotationNormalizedPart();
void rotationShearPart();
void rotationPart();
void rotationPartNotOrthogonal();
void rotationNormalizedPart();
void rotationNormalizedPartNotOrthogonal();
void scalingPart();
void uniformScalingPart();
void uniformScalingPartNotUniform();
void vectorParts();
void invertedRigid();
void transform();
@ -146,9 +151,14 @@ Matrix4Test::Matrix4Test() {
&Matrix4Test::fromParts,
&Matrix4Test::rotationScalingPart,
&Matrix4Test::rotationNormalizedPart,
&Matrix4Test::rotationShearPart,
&Matrix4Test::rotationPart,
&Matrix4Test::rotationPartNotOrthogonal,
&Matrix4Test::rotationNormalizedPart,
&Matrix4Test::rotationNormalizedPartNotOrthogonal,
&Matrix4Test::scalingPart,
&Matrix4Test::uniformScalingPart,
&Matrix4Test::uniformScalingPartNotUniform,
&Matrix4Test::vectorParts,
&Matrix4Test::invertedRigid,
&Matrix4Test::transform,
@ -158,6 +168,8 @@ Matrix4Test::Matrix4Test() {
&Matrix4Test::configuration});
}
using namespace Literals;
void Matrix4Test::construct() {
constexpr Matrix4 a = {{3.0f, 5.0f, 8.0f, -3.0f},
{4.5f, 4.0f, 7.0f, 2.0f},
@ -516,65 +528,108 @@ void Matrix4Test::rotationScalingPart() {
Vector3(7.0f, -1.0f, 8.0f)));
}
void Matrix4Test::rotationNormalizedPart() {
std::ostringstream o;
Error redirectError{&o};
void Matrix4Test::rotationShearPart() {
Matrix4 rotation = Matrix4::rotation(-74.0_degf, Vector3{-1.0f, 2.0f, 2.0f}.normalized());
Matrix4 rotationScalingTranslation = rotation*
Matrix4::scaling(Vector3::yScale(3.5f))*
Matrix4::translation({2.0f, 5.0f, -3.0f});
Matrix4 a({0.0f, 0.0f, 1.0f, 4.0f},
{1.0f, 0.0f, 0.0f, 3.0f},
{0.0f, -1.0f, 0.1f, 0.0f},
{9.0f, 4.0f, 5.0f, 9.0f});
a.rotationNormalized();
CORRADE_COMPARE(o.str(), "Math::Matrix4::rotationNormalized(): the rotation part is not normalized\n");
Matrix4 b({ 0.35612214f, -0.80181062f, 0.47987163f, 1.0f},
{ 0.47987163f, 0.59757638f, 0.6423595f, 3.0f},
{-0.80181062f, 0.0015183985f, 0.59757638f, 4.0f},
{ 0.0f, 0.0f, 0.0f, 1.0f});
CORRADE_COMPARE(b.rotationNormalized(), Matrix3x3(Vector3( 0.35612214f, -0.80181062f, 0.47987163f),
Vector3( 0.47987163f, 0.59757638f, 0.6423595f),
Vector3(-0.80181062f, 0.0015183985f, 0.59757638f)));
Matrix3x3 a = rotationScalingTranslation.rotationShear();
CORRADE_COMPARE(a, rotation.rotationScaling());
CORRADE_COMPARE(a, (Matrix3x3{
Vector3{ 0.35612206f, -0.80181074f, 0.47987169f},
Vector3{ 0.47987163f, 0.59757626f, 0.64235962f},
Vector3{-0.80181062f, 0.00151846f, 0.59757626f}}));
}
void Matrix4Test::rotationPart() {
Matrix4 rotation = Matrix4::rotation(Deg(-74.0f), Vector3(-1.0f, 2.0f, 2.0f).normalized());
Matrix3x3 expectedRotationPart(Vector3( 0.35612206f, -0.80181074f, 0.47987169f),
Vector3( 0.47987163f, 0.59757626f, 0.64235962f),
Vector3(-0.80181062f, 0.00151846f, 0.59757626f));
Matrix4 rotation = Matrix4::rotation(-74.0_degf, Vector3{-1.0f, 2.0f, 2.0f}.normalized());
Matrix3x3 expectedRotationPart{
Vector3( 0.35612206f, -0.80181074f, 0.47987169f),
Vector3( 0.47987163f, 0.59757626f, 0.64235962f),
Vector3(-0.80181062f, 0.00151846f, 0.59757626f)};
/* For rotation and translation this is the same as rotationScaling() */
Matrix4 rotationTranslation = rotation*Matrix4::translation({2.0f, 5.0f, -3.0f});
Matrix4 rotationTranslation = rotation*
Matrix4::translation({2.0f, 5.0f, -3.0f});
Matrix3x3 rotationTranslationPart = rotationTranslation.rotation();
CORRADE_COMPARE(rotationTranslationPart, rotationTranslation.rotationScaling());
CORRADE_COMPARE(rotationTranslationPart, expectedRotationPart);
/* Test uniform scaling */
Matrix4 rotationScaling = rotation*Matrix4::scaling(Vector3(3.0f));
Matrix3x3 rotationScalingPart = rotationScaling.rotation();
/* Uniform scaling */
Matrix4 rotationUniformScalingTranslation = rotation*
Matrix4::scaling(Vector3(3.0f))*
Matrix4::translation({2.0f, 5.0f, -3.0f});
Matrix3x3 rotationUniformScalingPart = rotationUniformScalingTranslation.rotation();
CORRADE_COMPARE(rotationUniformScalingPart.determinant(), 1.0f);
CORRADE_COMPARE(rotationUniformScalingPart*rotationUniformScalingPart.transposed(), Matrix3x3());
CORRADE_COMPARE(rotationUniformScalingPart, expectedRotationPart);
/* Non-uniform scaling */
Matrix4 rotationScalingTranslation = rotation*
Matrix4::scaling(Vector3::yScale(3.5f))*
Matrix4::translation({2.0f, 5.0f, -3.0f});
Matrix3x3 rotationScalingPart = rotationScalingTranslation.rotation();
CORRADE_COMPARE(rotationScalingPart.determinant(), 1.0f);
CORRADE_COMPARE(rotationScalingPart*rotationScalingPart.transposed(), Matrix3x3());
CORRADE_COMPARE(rotationScalingPart, expectedRotationPart);
}
/* Fails on non-uniform scaling */
std::ostringstream o;
Error redirectError{&o};
Matrix3x3 rotationScaling2 = (rotation*Matrix4::scaling(Vector3::yScale(3.5f))).rotation();
CORRADE_COMPARE(o.str(), "Math::Matrix4::rotation(): the matrix doesn't have uniform scaling\n");
CORRADE_COMPARE(rotationScaling2, Matrix3x3());
void Matrix4Test::rotationPartNotOrthogonal() {
std::ostringstream out;
Error redirectError{&out};
/* Test both non-orthogonality and "unnormalizable" scaling */
Matrix4::shearingXY(1.5f, 0.0f).rotation();
Matrix4::scaling(Vector3::yScale(0.0f)).rotation();
CORRADE_COMPARE(out.str(),
"Math::Matrix4::rotation(): the normalized rotation part is not orthogonal\n"
"Math::Matrix4::rotation(): the normalized rotation part is not orthogonal\n");
}
void Matrix4Test::rotationNormalizedPart() {
Matrix4 a({ 0.35612214f, -0.80181062f, 0.47987163f, 1.0f},
{ 0.47987163f, 0.59757638f, 0.6423595f, 3.0f},
{-0.80181062f, 0.0015183985f, 0.59757638f, 4.0f},
{ 0.0f, 0.0f, 0.0f, 1.0f});
CORRADE_COMPARE(a.rotationNormalized(),
(Matrix3x3{Vector3{ 0.35612214f, -0.80181062f, 0.47987163f},
Vector3{ 0.47987163f, 0.59757638f, 0.6423595f},
Vector3{-0.80181062f, 0.0015183985f, 0.59757638f}}));
}
void Matrix4Test::rotationNormalizedPartNotOrthogonal() {
std::ostringstream out;
Error redirectError{&out};
Matrix4 a({0.0f, 0.0f, 1.0f, 4.0f},
{1.0f, 0.0f, 0.0f, 3.0f},
{0.0f, -1.0f, 0.1f, 0.0f},
{9.0f, 4.0f, 5.0f, 9.0f});
a.rotationNormalized();
CORRADE_COMPARE(out.str(), "Math::Matrix4::rotationNormalized(): the rotation part is not orthogonal\n");
}
void Matrix4Test::scalingPart() {
Matrix4 translationRotationScaling =
Matrix4::translation({2.0f, 5.0f, -3.0f})*
Matrix4::rotation(-74.0_degf, Vector3(-1.0f, 2.0f, 2.0f).normalized())*
Matrix4::scaling({0.5f, 3.5f, 1.2f});
CORRADE_COMPARE(translationRotationScaling.scaling(), (Vector3{0.5f, 3.5f, 1.2f}));
}
void Matrix4Test::uniformScalingPart() {
const Matrix4 rotation = Matrix4::rotation(Deg(-74.0f), Vector3(-1.0f, 2.0f, 2.0f).normalized());
const Matrix4 rotation = Matrix4::rotation(-74.0_degf, Vector3(-1.0f, 2.0f, 2.0f).normalized());
/* Test uniform scaling */
CORRADE_COMPARE((rotation*Matrix4::scaling(Vector3(3.0f))).uniformScaling(), 3.0f);
}
/* Fails on non-uniform scaling */
std::ostringstream o;
Error redirectError{&o};
const Float nonUniformScaling = (rotation*Matrix4::scaling(Vector3::yScale(3.0f))).uniformScaling();
CORRADE_COMPARE(o.str(), "Math::Matrix4::uniformScaling(): the matrix doesn't have uniform scaling\n");
CORRADE_COMPARE(nonUniformScaling, 0.0f);
void Matrix4Test::uniformScalingPartNotUniform() {
std::ostringstream out;
Error redirectError{&out}; Matrix4::scaling(Vector3::yScale(3.0f)).uniformScaling();
CORRADE_COMPARE(out.str(), "Math::Matrix4::uniformScaling(): the matrix doesn't have uniform scaling\n");
}
void Matrix4Test::vectorParts() {

Loading…
Cancel
Save