diff --git a/doc/changelog.dox b/doc/changelog.dox index 243264762..cd386e7f7 100644 --- a/doc/changelog.dox +++ b/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 diff --git a/src/Magnum/Math/Matrix3.h b/src/Magnum/Math/Matrix3.h index 05f0df34d..c540b59d1 100644 --- a/src/Magnum/Math/Matrix3.h +++ b/src/Magnum/Math/Matrix3.h @@ -77,8 +77,7 @@ template class Matrix3: public Matrix3x3 { * 0 & 0 & 1 * \end{pmatrix} * @f] - * @see @ref rotationScaling(), - * @ref Matrix4::scaling(const Vector3&), + * @see @ref scaling() const, @ref Matrix4::scaling(const Vector3&), * @ref Vector2::xScale(), @ref Vector2::yScale() */ constexpr static Matrix3 scaling(const Vector2& vector) { @@ -259,7 +258,7 @@ template class Matrix3: public Matrix3x3 { /** * @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 Matrix3: public Matrix3x3 { * \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&, const Vector2&), - * @ref rotation() const, @ref rotationNormalized(), - * @ref uniformScaling(), @ref rotation(Rad), - * @ref Matrix4::rotationScaling() + * @ref rotation(Rad), @ref Matrix4::rotationScaling() */ constexpr Matrix2x2 rotationScaling() const { return {(*this)[0].xy(), @@ -278,10 +283,11 @@ template class Matrix3: public Matrix3x3 { } /** - * @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 Matrix3: public Matrix3x3 { * \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&, const Vector2&), + * @ref rotation(Rad), @ref Matrix4::rotationShear() const */ - Matrix2x2 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 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), - * @ref Matrix4::rotation() const + * @see @ref rotationNormalized(), @ref scaling() const, + * @ref rotation(Rad), @ref Matrix4::rotation() const */ - Matrix2x2 rotation() const { - CORRADE_ASSERT(TypeTraits::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 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 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 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 scaling() const { + return {(*this)[0].xy().length(), + (*this)[1].xy().length()}; } /** @@ -341,7 +470,9 @@ template class Matrix3: public Matrix3x3 { * 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 Matrix3: public Matrix3x3 { * \end{pmatrix} * @f] * - * @see @ref rotationScaling(), @ref rotation() const, - * @ref rotationNormalized(), @ref scaling(const Vector2&), - * @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&), + * @ref Matrix4::uniformScalingSquared() */ - T uniformScalingSquared() const { - const T scalingSquared = (*this)[0].xy().dot(); - CORRADE_ASSERT(TypeTraits::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 Matrix3: public Matrix3x3 { * \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&), - * @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&), @ref Matrix4::uniformScaling() */ T uniformScaling() const { return std::sqrt(uniformScalingSquared()); } @@ -499,6 +652,29 @@ template Matrix3 Matrix3::rotation(const Rad angle) { { T(0), T(0), T(1)}}; } +template Matrix2x2 Matrix3::rotation() const { + Matrix2x2 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 Matrix2x2 Matrix3::rotationNormalized() const { + Matrix2x2 rotation{(*this)[0].xy(), + (*this)[1].xy()}; + CORRADE_ASSERT(rotation.isOrthogonal(), + "Math::Matrix3::rotationNormalized(): the rotation part is not orthogonal", {}); + return rotation; +} + +template T Matrix3::uniformScalingSquared() const { + const T scalingSquared = (*this)[0].xy().dot(); + CORRADE_ASSERT(TypeTraits::equals((*this)[1].xy().dot(), scalingSquared), + "Math::Matrix3::uniformScaling(): the matrix doesn't have uniform scaling", {}); + return scalingSquared; +} + template inline Matrix3 Matrix3::invertedRigid() const { CORRADE_ASSERT(isRigidTransformation(), "Math::Matrix3::invertedRigid(): the matrix doesn't represent rigid transformation", {}); diff --git a/src/Magnum/Math/Matrix4.h b/src/Magnum/Math/Matrix4.h index 7a2ad4e43..f1b30eaa4 100644 --- a/src/Magnum/Math/Matrix4.h +++ b/src/Magnum/Math/Matrix4.h @@ -62,7 +62,7 @@ template class Matrix4: public Matrix4x4 { * 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&), * @ref Vector3::xAxis(), @ref Vector3::yAxis(), * @ref Vector3::zAxis() @@ -86,8 +86,7 @@ template class Matrix4: public Matrix4x4 { * 0 & 0 & 0 & 1 * \end{pmatrix} * @f] - * @see @ref rotationScaling(), - * @ref Matrix3::scaling(const Vector2&), + * @see @ref scaling() const, @ref Matrix3::scaling(const Vector2&), * @ref Vector3::xScale(), @ref Vector3::yScale(), * @ref Vector3::zScale() */ @@ -433,7 +432,7 @@ template class Matrix4: public Matrix4x4 { /** * @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 Matrix4: public Matrix4x4 { * \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&, const Vector3&), - * @ref rotation() const, @ref rotationNormalized(), - * @ref uniformScaling(), @ref rotation(Rad, const Vector3&), + * @ref rotation(Rad, const Vector3&), * @ref Matrix3::rotationScaling() const */ constexpr Matrix3x3 rotationScaling() const { @@ -454,68 +460,216 @@ template class Matrix4: public Matrix4x4 { } /** - * @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&, const Vector3&), + * @ref rotation(Rad, const Vector3&), + * @ref Matrix3::rotationShear() const */ - Matrix3x3 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 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&), + * @see @ref rotationNormalized(), @ref scaling() const, + * @ref rotation(Rad, const Vector3&), * @ref Matrix3::rotation() const */ Matrix3x3 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 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 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 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 Matrix4: public Matrix4x4 { * \end{pmatrix} * @f] * - * @see @ref rotationScaling(), @ref rotation() const, - * @ref rotationNormalized(), @ref scaling(const Vector3&), - * @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&), + * @ref Matrix3::uniformScalingSquared() */ T uniformScalingSquared() const; @@ -534,8 +707,11 @@ template class Matrix4: public Matrix4x4 { * @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 Matrix4: public Matrix4x4 { * \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&), + * 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&), * @ref Matrix3::uniformScaling() */ T uniformScaling() const { return std::sqrt(uniformScalingSquared()); } @@ -787,13 +970,22 @@ template Matrix4 Matrix4::lookAt(const Vector3& eye, const Vec return from({right, realUp, backward}, eye); } -template inline Matrix3x3 Matrix4::rotation() const { - CORRADE_ASSERT(TypeTraits::equals((*this)[0].xyz().dot(), (*this)[1].xyz().dot()) && - TypeTraits::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 Matrix3x3 Matrix4::rotation() const { + Matrix3x3 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 Matrix3x3 Matrix4::rotationNormalized() const { + Matrix3x3 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 T Matrix4::uniformScalingSquared() const { diff --git a/src/Magnum/Math/Test/Matrix3Test.cpp b/src/Magnum/Math/Test/Matrix3Test.cpp index 85ae84d26..bad040124 100644 --- a/src/Magnum/Math/Test/Matrix3Test.cpp +++ b/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() { diff --git a/src/Magnum/Math/Test/Matrix4Test.cpp b/src/Magnum/Math/Test/Matrix4Test.cpp index 14364edc6..c792316c7 100644 --- a/src/Magnum/Math/Test/Matrix4Test.cpp +++ b/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() {