diff --git a/doc/changelog.dox b/doc/changelog.dox index 0b8f5e2b4..4af346df2 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -123,6 +123,11 @@ See also: and @ref Math::Matrix4::from(const Matrix3x3&, const Vector3&) to create a transformation from a rotation and translation part (see [mosra/magnum#471](https://github.com/mosra/magnum/pull/471)) +- Added @ref Math::Matrix4::orthographicProjection(const Vector2&, const Vector2&, T, T) + and @ref Math::Matrix3::projection(const Vector2&, const Vector2&) to + match the classic `glOrtho()` and `gluOrtho2D()` APIs, complementing + @ref Math::Matrix4::perspectiveProjection(const Vector2&, const Vector2&, T, T) + that got added in 2019.10 - Added @ref Math::Intersection::rayRange() (see [mosra/magnum#484](https://github.com/mosra/magnum/pull/484)) - Added @ref Math::RectangularMatrix::RectangularMatrix(IdentityInitT, T) constructor as it might be useful to create non-square identity matrices as diff --git a/src/Magnum/Math/Matrix3.h b/src/Magnum/Math/Matrix3.h index e89c87a3a..8b2ccbdc3 100644 --- a/src/Magnum/Math/Matrix3.h +++ b/src/Magnum/Math/Matrix3.h @@ -218,16 +218,41 @@ template class Matrix3: public Matrix3x3 { * \end{pmatrix} * @f] * - * Similar to the classic @m_class{m-doc-external} [gluOrtho2D()](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluOrtho2D.xml) - * function, except that the projection is always centered. + * If you need an off-center projection (as with the classic + * @m_class{m-doc-external} [gluOrtho2D()](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluOrtho2D.xml) + * function, use @ref projection(const Vector2&, const Vector2&). * @see @ref Matrix4::orthographicProjection(), * @ref Matrix4::perspectiveProjection() - * @m_keywords{gluOrtho2D()} */ static Matrix3 projection(const Vector2& size) { return scaling(T(2.0)/size); } + /** + * @brief 2D off-center orthographic projection matrix + * @param bottomLeft Bottom left corner of the clipping plane + * @param topRight Top right corner of the clipping plane + * @m_since_latest + * + * @f[ + * \boldsymbol{A} = \begin{pmatrix} + * \frac{2}{r - l} & 0 & - \frac{r + l}{r - l} \\ + * 0 & \frac{2}{t - b} & - \frac{t + b}{t - b} \\ + * 0 & 0 & 1 + * \end{pmatrix} + * @f] + * + * Equivalent to the classic @m_class{m-doc-external} [gluOrtho2D()](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluOrtho2D.xml) + * function. If @p bottomLeft and @p topRight are a negation of each + * other, this function is equivalent to + * @ref projection(const Vector2&). + * + * @see @ref Matrix4::orthographicProjection(const Vector2&, const Vector2&, T, T), + * @ref Matrix4::perspectiveProjection(const Vector2&, const Vector2&, T, T) + * @m_keywords{gluOrtho2D()} + */ + static Matrix3 projection(const Vector2& bottomLeft, const Vector2& topRight); + /** * @brief Create matrix from rotation/scaling part and translation part * @param rotationScaling Rotation/scaling part (upper-left 2x2 @@ -726,6 +751,16 @@ template Matrix3 Matrix3::rotation(const Rad angle) { { T(0), T(0), T(1)}}; } +template Matrix3 Matrix3::projection(const Vector2& bottomLeft, const Vector2& topRight) { + const Vector2 difference = topRight - bottomLeft; + const Vector2 scale = T(2.0)/difference; + const Vector2 offset = (topRight + bottomLeft)/difference; + + return {{ scale.x(), T(0), T(0)}, + { T(0), scale.y(), T(0)}, + {-offset.x(), -offset.y(), T(1)}}; +} + template Matrix2x2 Matrix3::rotation() const { Matrix2x2 rotation{(*this)[0].xy().normalized(), (*this)[1].xy().normalized()}; diff --git a/src/Magnum/Math/Matrix4.h b/src/Magnum/Math/Matrix4.h index 348ad4151..490c1581d 100644 --- a/src/Magnum/Math/Matrix4.h +++ b/src/Magnum/Math/Matrix4.h @@ -333,13 +333,43 @@ template class Matrix4: public Matrix4x4 { * \end{pmatrix} * @f] * - * Similar to the classic @m_class{m-doc-external} [glOrtho()](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glOrtho.xml) - * function, except that the projection is always centered. + * If you need an off-center projection (as with the classic + * @m_class{m-doc-external} [glOrtho()](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glOrtho.xml) + * function), use @ref orthographicProjection(const Vector2&, const Vector2&, T, T). * @see @ref perspectiveProjection(), @ref Matrix3::projection() - * @m_keywords{glOrtho()} */ static Matrix4 orthographicProjection(const Vector2& size, T near, T far); + /** + * @brief 3D off-center orthographic projection matrix + * @param bottomLeft Bottom left corner of the clipping plane + * @param topRight Top right corner of the clipping plane + * @param near Distance to near clipping plane, positive is + * ahead + * @param far Distance to far clipping plane, positive is + * ahead + * @m_since_latest + * + * @f[ + * \boldsymbol{A} = \begin{pmatrix} + * \frac{2}{r - l} & 0 & 0 & - \frac{r + l}{r - l} \\ + * 0 & \frac{2}{t - b} & 0 & - \frac{t + b}{t - b} \\ + * 0 & 0 & \frac{2}{n - f} & \frac{n + f}{n - f} \\ + * 0 & 0 & 0 & 1 + * \end{pmatrix} + * @f] + * + * Equivalent to the classic @m_class{m-doc-external} [glOrtho()](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glOrtho.xml) + * function. If @p bottomLeft and @p topRight are a negation of each + * other, this function is equivalent to + * @ref orthographicProjection(const Vector2&, T, T). + * + * @see @ref perspectiveProjection(), + * @ref Matrix3::projection(const Vector2&, const Vector2&) + * @m_keywords{glOrtho()} + */ + static Matrix4 orthographicProjection(const Vector2& bottomLeft, const Vector2& topRight, T near, T far); + /** * @brief 3D perspective projection matrix * @param size Size of near clipping plane @@ -455,8 +485,8 @@ template class Matrix4: public Matrix4x4 { * @ref perspectiveProjection(const Vector2&, T, T). * * @see @ref perspectiveProjection(Rad fov, T, T, T), - * @ref orthographicProjection(), @ref Matrix3::projection(), - * @ref Constants::inf() + * @ref orthographicProjection(const Vector2&, const Vector2&, T, T), + * @ref Matrix3::projection(), @ref Constants::inf() * @m_keywords{glFrustum()} */ static Matrix4 perspectiveProjection(const Vector2& bottomLeft, const Vector2& topRight, T near, T far); @@ -1120,6 +1150,17 @@ template Matrix4 Matrix4::orthographicProjection(const Vector2 { T(0), T(0), near*zScale-T(1), T(1)}}; } +template Matrix4 Matrix4::orthographicProjection(const Vector2& bottomLeft, const Vector2& topRight, const T near, const T far) { + const Vector3 difference{topRight - bottomLeft, near - far}; + const Vector3 scale = T(2.0)/difference; + const Vector3 offset = Vector3{topRight + bottomLeft, near + far}/difference; + + return {{ scale.x(), T(0), T(0), T(0)}, + { T(0), scale.y(), T(0), T(0)}, + { T(0), T(0), scale.z(), T(0)}, + {-offset.x(), -offset.y(), offset.z(), T(1)}}; +} + template Matrix4 Matrix4::perspectiveProjection(const Vector2& size, const T near, const T far) { const Vector2 xyScale = 2*near/size; diff --git a/src/Magnum/Math/Test/Matrix3Test.cpp b/src/Magnum/Math/Test/Matrix3Test.cpp index c4e7b193e..4c18ded9f 100644 --- a/src/Magnum/Math/Test/Matrix3Test.cpp +++ b/src/Magnum/Math/Test/Matrix3Test.cpp @@ -82,6 +82,7 @@ struct Matrix3Test: Corrade::TestSuite::Tester { void shearingX(); void shearingY(); void projection(); + void projectionOffCenter(); void fromParts(); void rotationScalingPart(); @@ -135,6 +136,7 @@ Matrix3Test::Matrix3Test() { &Matrix3Test::shearingX, &Matrix3Test::shearingY, &Matrix3Test::projection, + &Matrix3Test::projectionOffCenter, &Matrix3Test::fromParts, &Matrix3Test::rotationScalingPart, @@ -399,11 +401,26 @@ void Matrix3Test::shearingY() { } void Matrix3Test::projection() { - Matrix3 expected({2.0f/4.0f, 0.0f, 0.0f}, - { 0.0f, 2.0f/3.0f, 0.0f}, + Matrix3 expected({2.0f/5.0f, 0.0f, 0.0f}, + { 0.0f, 2.0f/4.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}); - CORRADE_COMPARE(Matrix3::projection({4.0f, 3.0f}), expected); + CORRADE_COMPARE(Matrix3::projection({5.0f, 4.0f}), expected); +} + +void Matrix3Test::projectionOffCenter() { + Matrix3 expected({0.4f, 0.0f, 0.0f}, + {0.0f, 0.5f, 0.0f}, + {0.4f, 0.25f, 1.0f}); + /* Shifted by (-1, -0.5) compared to the projection() test */ + Matrix3 actual = Matrix3::projection({-3.5f, -2.5f}, {1.5f, 1.5f}); + CORRADE_COMPARE(actual, expected); + + /* NDC is left-handed, so point on the near plane top right corner should + be (1, 1), and a point on the far plane bottom left corner should be + (-1, -1) */ + CORRADE_COMPARE(actual.transformPoint({1.5f, 1.5f}), (Vector2{1.0f, 1.0f})); + CORRADE_COMPARE(actual.transformPoint({-3.5f, -2.5f}), (Vector2{-1.0f, -1.0f})); } void Matrix3Test::fromParts() { diff --git a/src/Magnum/Math/Test/Matrix4Test.cpp b/src/Magnum/Math/Test/Matrix4Test.cpp index 04aa9d193..85df462b8 100644 --- a/src/Magnum/Math/Test/Matrix4Test.cpp +++ b/src/Magnum/Math/Test/Matrix4Test.cpp @@ -90,6 +90,7 @@ struct Matrix4Test: Corrade::TestSuite::Tester { void shearingXZ(); void shearingYZ(); void orthographicProjection(); + void orthographicProjectionOffCenter(); void perspectiveProjection(); void perspectiveProjectionInfiniteFar(); void perspectiveProjectionFov(); @@ -160,6 +161,7 @@ Matrix4Test::Matrix4Test() { &Matrix4Test::shearingXZ, &Matrix4Test::shearingYZ, &Matrix4Test::orthographicProjection, + &Matrix4Test::orthographicProjectionOffCenter, &Matrix4Test::perspectiveProjection, &Matrix4Test::perspectiveProjectionInfiniteFar, &Matrix4Test::perspectiveProjectionFov, @@ -523,6 +525,22 @@ void Matrix4Test::orthographicProjection() { CORRADE_COMPARE(actual.transformPoint({0.0f, 0.0f, -9.0f}), Vector3(0.0f, 0.0f, +1.0f)); } +void Matrix4Test::orthographicProjectionOffCenter() { + Matrix4 expected({0.4f, 0.0f, 0.0f, 0.0f}, + {0.0f, 0.5f, 0.0f, 0.0f}, + {0.0f, 0.0f, -0.25f, 0.0f}, + {0.4f, 0.25f, -1.25f, 1.0f}); + /* Shifted by (-1, -0.5) compared to the orthographicProjection() test */ + Matrix4 actual = Matrix4::orthographicProjection({-3.5f, -2.5f}, {1.5f, 1.5f}, 1.0f, 9.0f); + CORRADE_COMPARE(actual, expected); + + /* NDC is left-handed, so point on the near plane top right corner should + be (1, 1, -1), and a point on the far plane bottom left corner should be + (-1, -1, 1) */ + CORRADE_COMPARE(actual.transformPoint({1.5f, 1.5f, -1.0f}), (Vector3{1.0f, 1.0f, -1.0f})); + CORRADE_COMPARE(actual.transformPoint({-3.5f, -2.5f, -9.0f}), (Vector3{-1.0f, -1.0f, +1.0f})); +} + void Matrix4Test::perspectiveProjection() { Matrix4 expected({4.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 7.111111f, 0.0f, 0.0f},