diff --git a/doc/changelog.dox b/doc/changelog.dox index 13ffe7190..1d350c4a6 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -76,6 +76,9 @@ See also: - Added @ref Math::Constants::piQuarter() - Added a convenience function @ref Math::select() as a constant interpolation counterpart to @ref Math::lerp() +- Added a convenience function @ref Math::planeEquation() to aid with passing + passing parameters to @ref Math::Intersection::planeLine(), + @ref Math::Distance::pointPlane() and others - Ability to convert @ref Math::BoolVector from and to external representation diff --git a/src/Magnum/Math/Distance.h b/src/Magnum/Math/Distance.h index b87fbe78e..53967dc73 100644 --- a/src/Magnum/Math/Distance.h +++ b/src/Magnum/Math/Distance.h @@ -169,8 +169,8 @@ 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. -@see @ref pointPlaneNormalized() - */ +@see @ref planeEquation(), @ref pointPlaneNormalized() +*/ template inline T pointPlaneScaled(const Vector3& point, const Vector4& plane) { return dot(plane.xyz(), point) + plane.w(); } @@ -187,6 +187,7 @@ The distance is negative if the point lies behind the plane. In cases where the planes normal is a unit vector, @ref pointPlaneNormalized() is more efficient. If merely the sign of the distance is of interest, @ref pointPlaneScaled() is more efficient. +@see @ref planeEquation() */ template inline T pointPlane(const Vector3& point, const Vector4& plane) { return pointPlaneScaled(point, plane)/plane.xyz().length(); @@ -205,6 +206,7 @@ The distance is negative if the point lies behind the plane. Expects that More efficient than @ref pointPlane() in cases where the plane's normal is normalized. Equivalent to @ref pointPlaneScaled() but with assertion added on top. +@see @ref planeEquation() */ template inline T pointPlaneNormalized(const Vector3& point, const Vector4& plane) { CORRADE_ASSERT(plane.xyz().isNormalized(), diff --git a/src/Magnum/Math/Test/Vector4Test.cpp b/src/Magnum/Math/Test/Vector4Test.cpp index 1fa37a7af..51a2d4057 100644 --- a/src/Magnum/Math/Test/Vector4Test.cpp +++ b/src/Magnum/Math/Test/Vector4Test.cpp @@ -68,6 +68,9 @@ struct Vector4Test: Corrade::TestSuite::Tester { void threeComponent(); void twoComponent(); + void planeEquationThreePoints(); + void planeEquationNormalPoint(); + void swizzleType(); void debug(); void configuration(); @@ -93,6 +96,9 @@ Vector4Test::Vector4Test() { &Vector4Test::threeComponent, &Vector4Test::twoComponent, + &Vector4Test::planeEquationThreePoints, + &Vector4Test::planeEquationNormalPoint, + &Vector4Test::swizzleType, &Vector4Test::debug, &Vector4Test::configuration}); @@ -259,6 +265,34 @@ void Vector4Test::twoComponent() { CORRADE_COMPARE(d, 1.0f); } +void Vector4Test::planeEquationThreePoints() { + const Vector3 a{1.0f, 0.5f, 3.0f}; + const Vector3 b{1.5f, 1.5f, 2.5f}; + const Vector3 c{2.0f, 1.5f, 1.0f}; + const Vector4 eq = Math::planeEquation(a, b, c); + + CORRADE_COMPARE(Math::dot(a, eq.xyz()) + eq.w(), 0.0f); + CORRADE_COMPARE(Math::dot(b, eq.xyz()) + eq.w(), 0.0f); + CORRADE_COMPARE(Math::dot(c, eq.xyz()) + eq.w(), 0.0f); + CORRADE_COMPARE(eq, (Vector4{-0.9045340f, 0.3015113f, -0.3015113f, 1.658312f})); + + /* Different winding order should only give a negated normal */ + CORRADE_COMPARE(Math::planeEquation(a, c, b), -eq); +} + +void Vector4Test::planeEquationNormalPoint() { + const Vector3 a{1.0f, 0.5f, 3.0f}; + const Vector3 normal{-0.9045340f, 0.3015113f, -0.3015113f}; + const Vector4 eq = Math::planeEquation(normal, a); + + const Vector3 b{1.5f, 1.5f, 2.5f}; + const Vector3 c{2.0f, 1.5f, 1.0f}; + CORRADE_COMPARE(Math::dot(a, eq.xyz()) + eq.w(), 0.0f); + CORRADE_COMPARE(Math::dot(b, eq.xyz()) + eq.w(), 0.0f); + CORRADE_COMPARE(Math::dot(c, eq.xyz()) + eq.w(), 0.0f); + CORRADE_COMPARE(eq, (Vector4{-0.9045340f, 0.3015113f, -0.3015113f, 1.658312f})); +} + void Vector4Test::swizzleType() { constexpr Vector4i orig; constexpr auto c = swizzle<'y', 'a', 'y', 'x'>(orig); diff --git a/src/Magnum/Math/Vector3.h b/src/Magnum/Math/Vector3.h index a02f6c2d4..4ae6468f6 100644 --- a/src/Magnum/Math/Vector3.h +++ b/src/Magnum/Math/Vector3.h @@ -48,7 +48,7 @@ Which is equivalent to the common one (source: https://twitter.com/sjb3d/status/563640846671953920): @f[ \boldsymbol a \times \boldsymbol b = \begin{pmatrix}a_yb_z - a_zb_y \\ a_zb_x - a_xb_z \\ a_xb_y - a_yb_x \end{pmatrix} @f] -@see @ref cross(const Vector2&, const Vector2&) +@see @ref cross(const Vector2&, const Vector2&), @ref planeEquation() */ template inline Vector3 cross(const Vector3& a, const Vector3& b) { return swizzle<'y', 'z', 'x'>(a*swizzle<'y', 'z', 'x'>(b) - diff --git a/src/Magnum/Math/Vector4.h b/src/Magnum/Math/Vector4.h index 129f3ff73..56d6751d9 100644 --- a/src/Magnum/Math/Vector4.h +++ b/src/Magnum/Math/Vector4.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Class @ref Magnum::Math::Vector4 + * @brief Class @ref Magnum::Math::Vector4, function @ref Magnum::Math::planeEquation() */ #include "Magnum/Math/Vector3.h" @@ -200,6 +200,54 @@ template class Vector4: public Vector<4, T> { MAGNUM_VECTOR_SUBCLASS_IMPLEMENTATION(4, Vector4) }; +/** @relatesalso Vector4 +@brief Create a plane equation from three points + +Assuming the three points form a triangle in a counter-clockwise winding, +creates a plane equation in the following form: @f[ + ax + by + cz + d = 0 +@f] + +The first three coefficients describe the *scaled* normal +@f$ \boldsymbol{n} = (a, b, c)^T @f$ and are calculated using a cross product. +The coefficient @f$ d @f$ is calculated using a dot product with the normal +@f$ \boldsymbol{n} @f$ using the first point in order to satisfy the equation +when assigning @f$ \boldsymbol{p_i} @f$ to @f$ x @f$, @f$ y @f$, @f$ z @f$. @f[ + \begin{array}{rcl} + \boldsymbol{n} & = & (\boldsymbol{p_1} - \boldsymbol{p_0}) \times (\boldsymbol{p_2} - \boldsymbol{p_0}) \\ + d & = & - \boldsymbol{n} \cdot \boldsymbol{p_0} + \end{array} +@f] +@see @ref planeEquation(const Vector3&, const Vector3&), @ref cross(), + @ref dot() +*/ +template Vector4 planeEquation(const Vector3& p0, const Vector3& p1, const Vector3& p2) { + const Vector3 normal = Math::cross(p1 - p0, p2 - p0).normalized(); + return {normal, -Math::dot(normal, p0)}; +} + + +/** @relatesalso Vector4 +@brief Create a plane equation from a normal and a point + +Creates a plane equation in the following form: @f[ + ax + by + cz + d = 0 +@f] + +The first three coefficients describe the *scaled* normal +@f$ \boldsymbol{n} = (a, b, c)^T @f$, the coefficient @f$ d @f$ is calculated +using a dot product with the normal @f$ \boldsymbol{n} @f$ using the point +@f$ \boldsymbol{p} @f$ in order to satisfy the equation when assigning +@f$ \boldsymbol{p} @f$ to @f$ x @f$, @f$ y @f$, @f$ z @f$. @f[ + d = - \boldsymbol{n} \cdot \boldsymbol{p} +@f] +@see @ref planeEquation(const Vector3&, const Vector3&, const Vector3&), + @ref dot() +*/ +template Vector4 planeEquation(const Vector3& normal, const Vector3& point) { + return {normal, -Math::dot(normal, point)}; +} + #ifndef DOXYGEN_GENERATING_OUTPUT MAGNUM_VECTORn_OPERATOR_IMPLEMENTATION(4, Vector4) #endif