diff --git a/src/Magnum/Math/CMakeLists.txt b/src/Magnum/Math/CMakeLists.txt index d6783821f..baeaa4dfb 100644 --- a/src/Magnum/Math/CMakeLists.txt +++ b/src/Magnum/Math/CMakeLists.txt @@ -33,6 +33,7 @@ set(MagnumMath_HEADERS Dual.h DualComplex.h DualQuaternion.h + Frustum.h Functions.h Math.h TypeTraits.h diff --git a/src/Magnum/Math/Frustum.h b/src/Magnum/Math/Frustum.h new file mode 100644 index 000000000..6396d51a0 --- /dev/null +++ b/src/Magnum/Math/Frustum.h @@ -0,0 +1,101 @@ +#ifndef Magnum_Math_Frustum_h +#define Magnum_Math_Frustum_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016 + Vladimír Vondruš + Copyright © 2016 Jonathan Hale + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Math::Frustum + */ + +#include +#include +#include + +#include "Magnum/Math/Matrix4.h" +#include "Magnum/Math/Vector4.h" + +namespace Magnum { namespace Math { + +/** +@brief Camera frustum + +*/ +template class Frustum { + public: + + /** + * @brief Create a frustum from projection matrix + */ + static Frustum fromMatrix(const Matrix4& m) { + return Frustum{ + m.row(3) + m.row(0), + m.row(3) - m.row(0), + m.row(3) + m.row(1), + m.row(3) - m.row(1), + m.row(3) + m.row(2), + m.row(3) - m.row(2) + }; + } + + /** + * @brief Construct frustum from frustum planes + */ + constexpr /*implicit*/ Frustum(const Vector4& left, const Vector4& right, const Vector4& bottom, const Vector4& top, const Vector4& near, const Vector4& far): _data{left, right, bottom, top, near, far} {} + + /** + * @brief Raw data + * @return One-dimensional array of length `24`. + */ + T* data() { return _data[0].data(); } + + /** @overload */ + constexpr const T* data() const { return _data[0].data(); } + + /** + * @brief The frustum planes + * + * In order left (index `0`), right (index `1`), bottom (index `1`), + * top (index `3`), near (index `4`), far (index `5`). + */ + constexpr Corrade::Containers::StaticArrayView<6, const Vector4> planes() const { + return Corrade::Containers::StaticArrayView<6, const Vector4>{_data}; + } + + /** + * @brief Plane at given index + * + * In order left (index `0`), right (index `1`), bottom (index `1`), + * top (index `3`), near (index `4`), far (index `5`). + */ + constexpr Vector4 operator[](std::size_t i) const { return _data[i]; } + + private: + Vector4 _data[6]; +}; + +}} + +#endif diff --git a/src/Magnum/Math/Geometry/Distance.h b/src/Magnum/Math/Geometry/Distance.h index 4d8a91f2b..351639b25 100644 --- a/src/Magnum/Math/Geometry/Distance.h +++ b/src/Magnum/Math/Geometry/Distance.h @@ -31,6 +31,7 @@ #include "Magnum/Math/Functions.h" #include "Magnum/Math/Vector3.h" +#include "Magnum/Math/Vector4.h" namespace Magnum { namespace Math { namespace Geometry { @@ -162,6 +163,61 @@ class Distance { * the square root. */ template static T lineSegmentPointSquared(const Vector3& a, const Vector3& b, const Vector3& point); + + /** + * @brief Distance of point from plane + * + * The distance **d** is computed from point **p** and plane with normal + * **n** and **w** using: @f[ + * d = \frac{\sum_i^3 (p \cdot n) + w}{\left| n \right|} + * @f] + * The distance is negative if the point lies behind the plane. + * + * In cases where the planes normal is a unit vector, @ref pointPlaneUnnormalized() + * is more efficient. + * + * If merely the sign of the distance is of interest, @ref pointPlaneScaled() + * is more efficient. + */ + template static T pointPlane(const Vector3& point, const Vector4& plane) { + return pointPlaneScaled(point, plane)/plane.xyz().length(); + } + + /** + * @brief Distance of point from plane, scaled by the length of the planes normal + * + * The distance **d** is computed from point **p** and plane with normal + * **n** and **w** using: @f[ + * d = \sum_i^3 (p \cdot n) + w + * @f] + * The distance is negative if the point lies behind the plane. + * + * More efficient than @ref pointPlane() when merely the sign of the distance is + * of interest, for example when testing on which half space of the plane the + * point lies. + */ + template static T pointPlaneScaled(const Vector3& point, const Vector4& plane) { + return (plane.xyz()*point).sum() + plane.w(); + } + + /** + * @brief Distance of point from plane with normalized normal + * + * The distance **d** is computed from point **p** and plane with normal + * **n** and **w** using: @f[ + * d = \sum_i^3 (p \cdot n) + w + * @f] + * The distance is negative if the point lies behind the plane. + * + * More efficient than @ref pointPlane() in cases where the planes normal is + * normalized. + */ + template static T pointPlaneNormalized(const Vector3& point, const Vector4& plane) { + CORRADE_ASSERT(plane.xyz().isNormalized(), + "Math::Geometry::Distance::pointPlaneNormalized(): the planes normal is not a unit vector", {}); + return pointPlaneScaled(point, plane); + } + }; template T Distance::lineSegmentPoint(const Vector2& a, const Vector2& b, const Vector2& point) { diff --git a/src/Magnum/Math/Geometry/Intersection.h b/src/Magnum/Math/Geometry/Intersection.h index 8777b4869..510a84a9c 100644 --- a/src/Magnum/Math/Geometry/Intersection.h +++ b/src/Magnum/Math/Geometry/Intersection.h @@ -29,6 +29,9 @@ * @brief Class @ref Magnum::Math::Geometry::Intersection */ +#include "Magnum/Math/Frustum.h" +#include "Magnum/Math/Geometry/Distance.h" +#include "Magnum/Math/Range.h" #include "Magnum/Math/Vector3.h" namespace Magnum { namespace Math { namespace Geometry { @@ -123,8 +126,83 @@ class Intersection { const T f = dot(planePosition, planeNormal); return (f-dot(planeNormal, p))/dot(planeNormal, r); } + + /** + * @brief Intersection of a point and a camera frustum + * @param point Point + * @param frustum Frustum planes with normals pointing outwards + * @return `true` if the point is on or inside the frustum. + * + * Checks for each plane of the frustum whether the point is behind the plane + * (the points distance from the plane is negative) using + * @ref Distance::pointPlaneScaled(). + */ + template static bool pointFrustum(const Vector3& point, const Frustum& frustum); + + /** + * @brief Intersection of a range and a camera frustum + * @return `true` if the box intersects with the camera frustum. + * + * Counts for each plane of the frustum how many points of the box lie in + * front of the plane (outside of the frustum). If none, the box must lie + * entirely outside of the frustum and there is no intersection. + * Else, the box is considered as intersecting, even if it is merely corners + * of the box overlapping with corners of the frustum, since checking the + * corners is less efficient. + */ + template static bool boxFrustum(const Range3D& box, const Frustum& frustum); + }; +template bool Intersection::pointFrustum(const Vector3& point, const Frustum& frustum) { + for(const Vector4& f : frustum.planes()) { + if(Distance::pointPlaneScaled(point, f) < T(0)) { + /* the point is in front of one of the frustum planes (normals point outwards) */ + return false; + } + } + + return true; +} + +template bool Intersection::boxFrustum(const Range3D& box, const Frustum& frustum) { + /* + * Create the 8 vertices of the box from the 2 given vertices min and max + * Check for each corner of an octant whether it is inside the frustum. + * If only some of the corners are inside, the octant requires further checks. + */ + int planes = 0; + + for(const Vector4& plane : frustum.planes()) { + int corners = 0; + + for(UnsignedByte c = 0; c < 8; ++c) { + const Vector3 corner = Math::lerp(box.min(), box.max(), Math::BoolVector<3>{c}); + + if(Distance::pointPlaneScaled(corner, plane) >= T(0)) { + ++corners; + } + } + + if(corners == 0) { + /* all corners are outside this plane */ + return false; + } + + if(corners == 8) { + ++planes; + } + } + + if(planes == 6) { + return true; + } + + // potentially check corners here to avoid false positives! + + return true; +} + }}} #endif diff --git a/src/Magnum/Math/Geometry/Test/CMakeLists.txt b/src/Magnum/Math/Geometry/Test/CMakeLists.txt index b5aca0649..f9c82e41e 100644 --- a/src/Magnum/Math/Geometry/Test/CMakeLists.txt +++ b/src/Magnum/Math/Geometry/Test/CMakeLists.txt @@ -25,3 +25,7 @@ corrade_add_test(MathGeometryDistanceTest DistanceTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathGeometryIntersectionTest IntersectionTest.cpp LIBRARIES MagnumMathTestLib) + +set_property(TARGET + MathGeometryDistanceTest + APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") diff --git a/src/Magnum/Math/Geometry/Test/DistanceTest.cpp b/src/Magnum/Math/Geometry/Test/DistanceTest.cpp index 787936aab..c881e4777 100644 --- a/src/Magnum/Math/Geometry/Test/DistanceTest.cpp +++ b/src/Magnum/Math/Geometry/Test/DistanceTest.cpp @@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include "Magnum/Math/Constants.h" @@ -37,17 +38,24 @@ struct DistanceTest: Corrade::TestSuite::Tester { void linePoint3D(); void lineSegmentPoint2D(); void lineSegmentPoint3D(); + void pointPlane(); + void pointPlaneScaled(); + void pointPlaneNormalized(); }; typedef Math::Vector2 Vector2; typedef Math::Vector3 Vector3; +typedef Math::Vector4 Vector4; typedef Math::Constants Constants; DistanceTest::DistanceTest() { addTests({&DistanceTest::linePoint2D, &DistanceTest::linePoint3D, &DistanceTest::lineSegmentPoint2D, - &DistanceTest::lineSegmentPoint3D}); + &DistanceTest::lineSegmentPoint3D, + &DistanceTest::pointPlane, + &DistanceTest::pointPlaneScaled, + &DistanceTest::pointPlaneNormalized}); } void DistanceTest::linePoint2D() { @@ -156,6 +164,33 @@ void DistanceTest::lineSegmentPoint3D() { Constants::sqrt2()); } +void DistanceTest::pointPlane() { + Vector3 point{0.0f, 0.0f, 0.0f}; + Vector4 plane{3.0f, 0.0f, 4.0f, 5.0f}; + + CORRADE_COMPARE(Distance::pointPlane(point, plane), 1.0f); +} + +void DistanceTest::pointPlaneScaled() { + Vector3 point{1.0f, 1.0f, 1.0f}; + Vector4 plane{2.0f, 2.0f, 2.0f, 0.0f}; + + CORRADE_COMPARE(Distance::pointPlaneScaled(point, plane), 6.0f); +} + +void DistanceTest::pointPlaneNormalized() { + Vector3 point{1.0f, 2.0f, 3.0f}; + Vector4 invalidPlane{2.0f, 2.0f, 2.0f, 0.0f}; + + const Vector4 plane{0.0f, 1.0f, 0.0f, 1.0f}; + CORRADE_COMPARE(Distance::pointPlaneNormalized(point, plane), 3.0f); + + std::ostringstream o; + Error redirectError{&o}; + Distance::pointPlaneNormalized(point, invalidPlane); + CORRADE_COMPARE(o.str(), "Math::Geometry::Distance::pointPlaneNormalized(): the planes normal is not a unit vector\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Math::Geometry::Test::DistanceTest) diff --git a/src/Magnum/Math/Geometry/Test/IntersectionTest.cpp b/src/Magnum/Math/Geometry/Test/IntersectionTest.cpp index f5df68d1c..f4882ba90 100644 --- a/src/Magnum/Math/Geometry/Test/IntersectionTest.cpp +++ b/src/Magnum/Math/Geometry/Test/IntersectionTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016 Vladimír Vondruš + Copyright © 2016 Jonathan Hale Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -34,15 +35,22 @@ struct IntersectionTest: Corrade::TestSuite::Tester { void planeLine(); void lineLine(); + void pointFrustum(); + void boxFrustum(); }; typedef Math::Vector2 Vector2; typedef Math::Vector3 Vector3; +typedef Math::Vector4 Vector4; +typedef Math::Frustum Frustum; typedef Math::Constants Constants; +typedef Math::Range3D Range3D; IntersectionTest::IntersectionTest() { addTests({&IntersectionTest::planeLine, - &IntersectionTest::lineLine}); + &IntersectionTest::lineLine, + &IntersectionTest::pointFrustum, + &IntersectionTest::boxFrustum}); } void IntersectionTest::planeLine() { @@ -97,6 +105,39 @@ void IntersectionTest::lineLine() { {0.0f, 0.0f}, {1.0f, 2.0f}), Constants::inf()); } +void IntersectionTest::pointFrustum() { + const Frustum frustum{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {-1.0f, 0.0f, 0.0f, 10.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, -1.0f, 0.0f, 10.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, -1.0f, 10.0f}}; + + /* Point on edge */ + CORRADE_VERIFY(Intersection::pointFrustum(Vector3{}, frustum)); + /* Point inside */ + CORRADE_VERIFY(Intersection::pointFrustum(Vector3{5.0f, 5.0f, 5.0f}, frustum)); + /* Point outside */ + CORRADE_VERIFY(!Intersection::pointFrustum(Vector3{0.0f, 0.0f, 100.0f}, frustum)); +} + +void IntersectionTest::boxFrustum() { + const Frustum frustum{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {-1.0f, 0.0f, 0.0f, 10.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, -1.0f, 0.0f, 10.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, -1.0f, 10.0f}}; + + CORRADE_VERIFY(Intersection::boxFrustum(Range3D{Vector3{1.0f}, Vector3{2.0f}}, frustum)); + /* Bigger than frustum, but still intersects */ + CORRADE_VERIFY(Intersection::boxFrustum(Range3D{Vector3{-100.0f}, Vector3{100.0f}}, frustum)); + /* Outside of frustum */ + CORRADE_VERIFY(!Intersection::boxFrustum(Range3D{Vector3{-10.0f}, Vector3{-5.0f}}, frustum)); +} + }}}} CORRADE_TEST_MAIN(Magnum::Math::Geometry::Test::IntersectionTest) diff --git a/src/Magnum/Math/Test/CMakeLists.txt b/src/Magnum/Math/Test/CMakeLists.txt index 9f93ec1e1..7a89fe901 100644 --- a/src/Magnum/Math/Test/CMakeLists.txt +++ b/src/Magnum/Math/Test/CMakeLists.txt @@ -52,6 +52,7 @@ corrade_add_test(MathQuaternionTest QuaternionTest.cpp LIBRARIES MagnumMathTestL corrade_add_test(MathDualQuaternionTest DualQuaternionTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathBezierTest BezierTest.cpp LIBRARIES MagnumMathTestLib) +corrade_add_test(MathFrustumTest FrustumTest.cpp LIBRARIES MagnumMathTestLib) set_property(TARGET MathVectorTest diff --git a/src/Magnum/Math/Test/FrustumTest.cpp b/src/Magnum/Math/Test/FrustumTest.cpp new file mode 100644 index 000000000..be75700b5 --- /dev/null +++ b/src/Magnum/Math/Test/FrustumTest.cpp @@ -0,0 +1,89 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016 + Vladimír Vondruš + Copyright © 2016 Jonathan Hale + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include "Magnum/Math/Frustum.h" + +using namespace Corrade; + +namespace Magnum { namespace Math { namespace Test { + +struct FrustumTest: TestSuite::Tester { + explicit FrustumTest(); + + void construct(); + void constructFromMatrix(); +}; + +typedef Vector4 Vector4; +typedef Matrix4 Matrix4; +typedef Frustum Frustum; +typedef Deg Degf; + +FrustumTest::FrustumTest() { + addTests({&FrustumTest::construct, + &FrustumTest::constructFromMatrix}); +} + +void FrustumTest::construct() { + Vector4 planes[6]{ + {-1.0f, 0.0f, 0.0f, 1.0f}, + { 1.0f, 0.0f, 0.0f, 1.0f}, + { 0.0f,-1.0f, 0.0f, 1.0f}, + { 0.0f, 1.0f, 0.0f, 1.0f}, + { 0.0f, 0.0f,-1.0f, 1.0f}, + { 0.0f, 0.0f, 1.0f, 1.0f}}; + + Frustum frustum{ + planes[0], planes[1], + planes[2], planes[3], + planes[4], planes[5], + }; + + CORRADE_COMPARE_AS(frustum.planes(), Containers::ArrayView(planes), TestSuite::Compare::Container); +} + +void FrustumTest::constructFromMatrix() { + Vector4 planes[6]{ + { 1.0f, 0.0f,-1.0f, 0.0f}, + {-1.0f, 0.0f,-1.0f, 0.0f}, + { 0.0f, 1.0f,-1.0f, 0.0f}, + { 0.0f,-1.0f,-1.0f, 0.0f}, + { 0.0f, 0.0f,-2.22222f,-2.22222f}, + { 0.0f, 0.0f, 0.22222f, 2.22222f}}; + + const Frustum frustum = Frustum::fromMatrix( + Matrix4::perspectiveProjection(Degf(90.0f), 1.0f, 1.0f, 10.0f)); + + CORRADE_COMPARE_AS(frustum.planes(), Containers::ArrayView(planes), TestSuite::Compare::Container); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Math::Test::FrustumTest)