From 1f995d1ab2e44dd2cbfd5f5361cd7e031bd6c836 Mon Sep 17 00:00:00 2001 From: Squareys Date: Tue, 22 Nov 2016 21:46:31 +0100 Subject: [PATCH] Math: Add Distance::pointPlane* and two frustum intersection functions `Intersection::pointFrustum` and `Intersection::boxFrustum` Signed-off-by: Squareys --- src/Magnum/Math/Geometry/Distance.h | 56 +++++++++++++ src/Magnum/Math/Geometry/Intersection.h | 78 +++++++++++++++++++ src/Magnum/Math/Geometry/Test/CMakeLists.txt | 4 + .../Math/Geometry/Test/DistanceTest.cpp | 37 ++++++++- .../Math/Geometry/Test/IntersectionTest.cpp | 43 +++++++++- 5 files changed, 216 insertions(+), 2 deletions(-) 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)