diff --git a/doc/changelog.dox b/doc/changelog.dox index e4884b2ca..72489ec1d 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -133,6 +133,8 @@ See also: - @ref Math::Frustum::begin() / @ref Math::Frustum::end() accessors for easy range-for access to @ref Math::Frustum planes +- New @ref Math::Matrix4::perspectiveProjection(const Vector2&, const Vector2&, T, T) + overload for calculating off-center projection matrices - @ref Color3ub and @ref Color4ub can be printed with @ref Corrade::Utility::Debug as actual colors using the @ref Corrade::Utility::Debug::color modifier diff --git a/src/Magnum/Math/Matrix4.h b/src/Magnum/Math/Matrix4.h index f716ff9a9..bb1f10a64 100644 --- a/src/Magnum/Math/Matrix4.h +++ b/src/Magnum/Math/Matrix4.h @@ -298,10 +298,14 @@ template class Matrix4: public Matrix4x4 { * 0 & 0 & -1 & 0 * \end{pmatrix} * @f] + * + * If you need an off-center projection, use + * @ref perspectiveProjection(const Vector2&, const Vector2&, T, T) + * instead. + * * @see @ref perspectiveProjection(Rad fov, T, T, T), * @ref orthographicProjection(), @ref Matrix3::projection(), * @ref Constants::inf() - * @m_keywords{gluPerspective()} */ static Matrix4 perspectiveProjection(const Vector2& size, T near, T far); @@ -340,6 +344,12 @@ template class Matrix4: public Matrix4x4 { * \end{pmatrix} * @f] * + * This function is similar to the classic @cpp gluPerspective() @ce, + * with the difference that @p fov is *horizontal* instead of vertical. + * If you need an off-center projection (as with the classic + * @cpp glFrustum() @ce function), use + * @ref perspectiveProjection(const Vector2&, const Vector2&, T, T). + * * @see @ref orthographicProjection(), @ref Matrix3::projection(), * @ref Constants::inf() * @m_keywords{gluPerspective()} @@ -348,6 +358,44 @@ template class Matrix4: public Matrix4x4 { return perspectiveProjection(T(2)*near*std::tan(T(fov)*T(0.5))*Vector2::yScale(T(1)/aspectRatio), near, far); } + /** + * @brief 3D off-center perspective projection matrix + * @param bottomLeft Bottom left corner of the near clipping plane + * @param topRight Top right corner of the near clipping plane + * @param near Distance to near clipping plane, positive is + * ahead + * @param far Distance to far clipping plane, positive is + * ahead + * + * If @p far is finite, the result is: @f[ + * \boldsymbol{A} = \begin{pmatrix} + * \frac{2n}{r - l} & 0 & \frac{r + l}{r - l} & 0 \\ + * 0 & \frac{2n}{t - b} & \frac{t + b}{t - b} & 0 \\ + * 0 & 0 & \frac{n + f}{n - f} & \frac{2nf}{n - f} \\ + * 0 & 0 & -1 & 0 + * \end{pmatrix} + * @f] + * + * For infinite @p far, the result is: @f[ + * \boldsymbol{A} = \begin{pmatrix} + * \frac{2n}{r - l} & 0 & \frac{r + l}{r - l} & 0 \\ + * 0 & \frac{2n}{t - b} & \frac{t + b}{t - b} & 0 \\ + * 0 & 0 & -1 & -2n \\ + * 0 & 0 & -1 & 0 + * \end{pmatrix} + * @f] + * + * Equivalent to the classic @cpp glFrustum() @ce function. If + * @p bottomLeft and @p topRight are a negation of each other, this + * function is equivalent to @ref perspectiveProjection(const Vector2&, T, T). + * + * @see @ref perspectiveProjection(Rad fov, T, T, T), + * @ref orthographicProjection(), @ref Matrix3::projection(), + * @ref Constants::inf() + * @m_keywords{glFrustum()} + */ + static Matrix4 perspectiveProjection(const Vector2& bottomLeft, const Vector2& topRight, T near, T far); + /** * @brief Matrix oriented towards a specific point * @param eye Location to place the matrix @@ -971,6 +1019,27 @@ template Matrix4 Matrix4::perspectiveProjection(const Vector2& { T(0), T(0), m32, T(0)}}; } +template Matrix4 Matrix4::perspectiveProjection(const Vector2& bottomLeft, const Vector2& topRight, const T near, const T far) { + const Vector2 xyDifference = topRight - bottomLeft; + const Vector2 xyScale = 2*near/xyDifference; + const Vector2 xyOffset = (topRight + bottomLeft)/xyDifference; + + T m22, m32; + if(far == Constants::inf()) { + m22 = T(-1); + m32 = T(-2)*near; + } else { + const T zScale = T(1.0)/(near-far); + m22 = (far+near)*zScale; + m32 = T(2)*far*near*zScale; + } + + return {{ xyScale.x(), T(0), T(0), T(0)}, + { T(0), xyScale.y(), T(0), T(0)}, + {xyOffset.x(), xyOffset.y(), m22, T(-1)}, + { T(0), T(0), m32, T(0)}}; +} + template Matrix4 Matrix4::lookAt(const Vector3& eye, const Vector3& target, const Vector3& up) { const Vector3 backward = (eye - target).normalized(); const Vector3 right = cross(up, backward).normalized(); diff --git a/src/Magnum/Math/Test/Matrix4Test.cpp b/src/Magnum/Math/Test/Matrix4Test.cpp index c80b956e7..0e729d871 100644 --- a/src/Magnum/Math/Test/Matrix4Test.cpp +++ b/src/Magnum/Math/Test/Matrix4Test.cpp @@ -93,6 +93,8 @@ struct Matrix4Test: Corrade::TestSuite::Tester { void perspectiveProjectionInfiniteFar(); void perspectiveProjectionFov(); void perspectiveProjectionFovInfiniteFar(); + void perspectiveProjectionOffCenter(); + void perspectiveProjectionOffCenterInfiniteFar(); void lookAt(); void fromParts(); @@ -158,6 +160,8 @@ Matrix4Test::Matrix4Test() { &Matrix4Test::perspectiveProjectionInfiniteFar, &Matrix4Test::perspectiveProjectionFov, &Matrix4Test::perspectiveProjectionFovInfiniteFar, + &Matrix4Test::perspectiveProjectionOffCenter, + &Matrix4Test::perspectiveProjectionOffCenterInfiniteFar, &Matrix4Test::lookAt, &Matrix4Test::fromParts, @@ -501,6 +505,10 @@ void Matrix4Test::perspectiveProjection() { /* NDC is left-handed, so point on near plane should be -1, far +1 */ CORRADE_COMPARE(actual.transformPoint({0.0f, 0.0f, -32.0f}), Vector3(0.0f, 0.0f, -1.0f)); CORRADE_COMPARE(actual.transformPoint({0.0f, 0.0f, -100.0f}), Vector3(0.0f, 0.0f, +1.0f)); + + /* The version with bottom/left/top/right should give the same result if + it's centered */ + CORRADE_COMPARE(Matrix4::perspectiveProjection({-8.0f, -4.5f}, {8.0f, 4.5f}, 32.0f, 100.0f), expected); } void Matrix4Test::perspectiveProjectionInfiniteFar() { @@ -515,6 +523,10 @@ void Matrix4Test::perspectiveProjectionInfiniteFar() { in direction of far plane +1 */ CORRADE_COMPARE(actual.transformPoint({0.0f, 0.0f, -32.0f}), Vector3(0.0f, 0.0f, -1.0f)); CORRADE_COMPARE(actual.transformVector({0.0f, 0.0f, -1.0f}), Vector3(0.0f, 0.0f, +1.0f)); + + /* The version with bottom/left/top/right should give the same result if + it's centered */ + CORRADE_COMPARE(Matrix4::perspectiveProjection({-8.0f, -4.5f}, {8.0f, 4.5f}, 32.0f, Constants::inf()), expected); } void Matrix4Test::perspectiveProjectionFov() { @@ -533,6 +545,38 @@ void Matrix4Test::perspectiveProjectionFovInfiniteFar() { CORRADE_COMPARE(Matrix4::perspectiveProjection(Deg(27.0f), 2.35f, 32.0f, Constants::inf()), expected); } +void Matrix4Test::perspectiveProjectionOffCenter() { + Matrix4 expected({ 4.0f, 0.0f, 0.0f, 0.0f}, + { 0.0f, 7.111111f, 0.0f, 0.0f}, + {-0.125f, -0.1111111f, -1.9411764f, -1.0f}, + { 0.0f, 0.0f, -94.1176452f, 0.0f}); + /* Shifted by (-1, -0.5) compared to the perspectiveProjection() test */ + Matrix4 actual = Matrix4::perspectiveProjection({-9.0f, -5.0f}, {7.0f, 4.0f}, 32.0f, 100.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 in the center on the far plane roughly (0, 0, + +1) due to the "off-centerness" */ + CORRADE_COMPARE(actual.transformPoint({7.0f, 4.0f, -32.0f}), Vector3(1.0f, 1.0f, -1.0f)); + CORRADE_COMPARE(actual.transformPoint({0.0f, 0.0f, -100.0f}), Vector3(0.125f, 0.1111111f, +1.0f)); +} + +void Matrix4Test::perspectiveProjectionOffCenterInfiniteFar() { + Matrix4 expected({ 4.0f, 0.0f, 0.0f, 0.0f}, + { 0.0f, 7.111111f, 0.0f, 0.0f}, + {-0.125f, -0.1111111f, -1.0f, -1.0f}, + { 0.0f, 0.0f, -64.0f, 0.0f}); + /* Shifted by (-1, -0.5) compared to perspectiveProjectionInfiniteFar() */ + Matrix4 actual = Matrix4::perspectiveProjection({-9.0f, -5.0f}, {7.0f, 4.0f}, 32.0f, Constants::inf()); + CORRADE_COMPARE(actual, expected); + + /* NDC is left-handed, so point on the near plane bottom left corner should + be (1, 1, -1) and a *vector* in the direction of the far plane roughly + (0, 0, +1) due to the "off-centerness" */ + CORRADE_COMPARE(actual.transformPoint({-9.0f, -5.0f, -32.0f}), Vector3(-1.0f, -1.0f, -1.0f)); + CORRADE_COMPARE(actual.transformVector({0.0f, 0.0f, -1.0f}), Vector3(0.125f, 0.1111111f, +1.0f)); +} + void Matrix4Test::lookAt() { Vector3 translation{5.3f, -8.9f, -10.0f}; Vector3 target{19.0f, 29.3f, 0.0f};