From 1ef813fdade9f67a60970d166096c34b25e6dd87 Mon Sep 17 00:00:00 2001 From: Nghia Truong Date: Wed, 5 Feb 2020 21:24:11 -0700 Subject: [PATCH] Math: add reflect() and refract() functions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Vladimír Vondruš --- src/Magnum/Math/Functions.h | 52 +++++++++++++++++++++++ src/Magnum/Math/Matrix3.h | 3 +- src/Magnum/Math/Matrix4.h | 3 +- src/Magnum/Math/Test/CMakeLists.txt | 1 + src/Magnum/Math/Test/FunctionsTest.cpp | 58 ++++++++++++++++++++++++++ 5 files changed, 115 insertions(+), 2 deletions(-) diff --git a/src/Magnum/Math/Functions.h b/src/Magnum/Math/Functions.h index d6d699c6d..9af15bbbe 100644 --- a/src/Magnum/Math/Functions.h +++ b/src/Magnum/Math/Functions.h @@ -654,6 +654,58 @@ template inline Vector sqrtInverted(const Ve return Vector(T(1))/Math::sqrt(a); } +/** +@brief Reflect a vector +@m_since_latest + +Reflects the vector off a surface given the surface outward normal. Expects +that the normal vector is normalized. For a vector @f$ \boldsymbol{v} @f$ and a +normal @f$ \boldsymbol{n} @f$, the reflection vector @f$ \boldsymbol{r} @f$ is +calculated as: @f[ + \boldsymbol{r} = \boldsymbol{v} - 2 (\boldsymbol{n} \cdot \boldsymbol{v}) \boldsymbol{n} +@f] +@see @ref dot(const Vector&, const Vector&), @ref refract(), + @ref Vector::isNormalized(), @ref Matrix3::reflection(), + @ref Matrix4::reflection() +*/ +template inline Vector reflect(const Vector& vector, const Vector& normal) { + CORRADE_ASSERT(normal.isNormalized(), + "Math::reflect(): normal" << normal << "is not normalized", {}); + return vector - T(2.0)*dot(vector, normal)*normal; +} + +/** +@brief Refract a vector +@m_since_latest + +Refracts a vector through a medium given the surface outward normal and ratio +of indices of refraction eta. Expects that both @p vector and @p normal is +normalized. For a vector @f$ \boldsymbol{v} @f$, normal @f$ \boldsymbol{n} @f$ +and a ratio of indices of refraction @f$ \eta @f$, the refraction vector +@f$ \boldsymbol{r} @f$ is calculated as: @f[ + \begin{array}{rcl} + \eta & = & \cfrac{\text{IOR}_\text{source}}{\text{IOR}_\text{destination}} \\[10pt] + k & = & 1 - \eta^2 (1 - (\boldsymbol{n} \cdot \boldsymbol{v})^2) \\ + \boldsymbol{r} & = & \begin{cases} + \boldsymbol{0}, & \text{if} ~ k < 0 \\ + \eta \boldsymbol{v} - (\eta (\boldsymbol{n} \cdot \boldsymbol{v}) + \sqrt{k}) \boldsymbol{n}, & \text{if} ~ k \ge 0 + \end{cases} + \end{array} +@f] + +Wikipedia has a [List of refractive indices](https://en.wikipedia.org/wiki/List_of_refractive_indices). +@see @ref dot(const Vector&, const Vector&), @ref reflect(), + @ref Vector::isNormalized() +*/ +template inline Vector refract(const Vector& vector, const Vector& normal, T eta) { + CORRADE_ASSERT(vector.isNormalized() && normal.isNormalized(), + "Math::refract(): vectors" << vector << "and" << normal << "are not normalized", {}); + const T dot = Math::dot(vector, normal); + const T k = T(1.0) - eta*eta*(T(1.0) - dot*dot); + if(k < T(0.0)) return {}; + return eta*vector - (eta*dot + std::sqrt(k))*normal; +} + /*@}*/ }} diff --git a/src/Magnum/Math/Matrix3.h b/src/Magnum/Math/Matrix3.h index d436a48db..9beb0204c 100644 --- a/src/Magnum/Math/Matrix3.h +++ b/src/Magnum/Math/Matrix3.h @@ -113,7 +113,8 @@ template class Matrix3: public Matrix3x3 { * @cpp Matrix3::scaling(Vector2::yScale(-1.0f)) @ce. @f[ * \boldsymbol{A} = \boldsymbol{I} - 2 \boldsymbol{NN}^T ~~~~~ \boldsymbol{N} = \begin{pmatrix} n_x \\ n_y \end{pmatrix} * @f] - * @see @ref Matrix4::reflection(), @ref Vector::isNormalized() + * @see @ref Matrix4::reflection(), @ref Vector::isNormalized(), + * @ref reflect() */ static Matrix3 reflection(const Vector2& normal) { CORRADE_ASSERT(normal.isNormalized(), diff --git a/src/Magnum/Math/Matrix4.h b/src/Magnum/Math/Matrix4.h index b539b9457..856d320ea 100644 --- a/src/Magnum/Math/Matrix4.h +++ b/src/Magnum/Math/Matrix4.h @@ -183,7 +183,8 @@ template class Matrix4: public Matrix4x4 { * @cpp Matrix4::scaling(Vector3::yScale(-1.0f)) @ce. @f[ * \boldsymbol{A} = \boldsymbol{I} - 2 \boldsymbol{NN}^T ~~~~~ \boldsymbol{N} = \begin{pmatrix} n_x \\ n_y \\ n_z \end{pmatrix} * @f] - * @see @ref Matrix3::reflection(), @ref Vector::isNormalized() + * @see @ref Matrix3::reflection(), @ref Vector::isNormalized(), + * @ref reflect() */ static Matrix4 reflection(const Vector3& normal); diff --git a/src/Magnum/Math/Test/CMakeLists.txt b/src/Magnum/Math/Test/CMakeLists.txt index bd35181af..f5c4e68e8 100644 --- a/src/Magnum/Math/Test/CMakeLists.txt +++ b/src/Magnum/Math/Test/CMakeLists.txt @@ -78,6 +78,7 @@ set_property(TARGET MathCubicHermiteTest MathDualComplexTest MathFrustumTest + MathFunctionsTest MathQuaternionTest MathDualQuaternionTest diff --git a/src/Magnum/Math/Test/FunctionsTest.cpp b/src/Magnum/Math/Test/FunctionsTest.cpp index cf2b20cc7..5803d6f4b 100644 --- a/src/Magnum/Math/Test/FunctionsTest.cpp +++ b/src/Magnum/Math/Test/FunctionsTest.cpp @@ -23,8 +23,10 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include +#include #include "Magnum/Math/Functions.h" #include "Magnum/Math/Vector4.h" @@ -69,6 +71,11 @@ struct FunctionsTest: Corrade::TestSuite::Tester { void isNan(); void isNanfVector(); + void reflect(); + void reflectNotNormalized(); + void refract(); + void refractNotNormalized(); + void trigonometric(); void trigonometricWithBase(); template void sincos(); @@ -125,6 +132,11 @@ FunctionsTest::FunctionsTest() { &FunctionsTest::isNan, &FunctionsTest::isNanfVector, + &FunctionsTest::reflect, + &FunctionsTest::reflectNotNormalized, + &FunctionsTest::refract, + &FunctionsTest::refractNotNormalized, + &FunctionsTest::trigonometric, &FunctionsTest::trigonometricWithBase, &FunctionsTest::sincos, @@ -440,6 +452,52 @@ void FunctionsTest::isNanfVector() { CORRADE_COMPARE(Math::isNan(Vector3{0.3f, -Constants::inf(), 1.0f}), Math::BoolVector<3>{0x00}); } +void FunctionsTest::reflect() { + /* Reflection along Y will simply flip the Y component */ + CORRADE_COMPARE(Math::reflect( + Vector3{1.0f, 2.0f, 3.0f}, + Vector3::yAxis()), + (Vector3{1.0f, -2.0f, 3.0f})); + + CORRADE_COMPARE(Math::reflect( + Vector3{2.0f, 1.0f, 1.0f}, + Vector3{1.0f, -1.0f, 1.0f}.normalized()), + (Vector3{0.666667f, 2.33333f, -0.333333f})); +} + +void FunctionsTest::reflectNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + Math::reflect(Vector3{}, Vector3{1.0f}); + CORRADE_COMPARE(out.str(), + "Math::reflect(): normal Vector(1, 1, 1) is not normalized\n"); +} + +void FunctionsTest::refract() { + CORRADE_COMPARE(Math::refract( + Vector3{1.0f, 0.0f, 1.0f}.normalized(), + Vector3{0.0f, 0.0f, -1.0f}, 1.0f/1.5f), + (Vector3{0.471405f, 0.0f, 0.881917f})); + CORRADE_COMPARE(Math::refract( + Vector3{4.0f, 1.0f, 1.0f}.normalized(), + Vector3{0.0f, -2.0f, -1.0f}.normalized(), 1.0f/1.5f), + (Vector3{0.628539f, 0.661393f, 0.409264f})); + + /* Total absorption */ + CORRADE_COMPARE(Math::refract( + Vector3{1.0f, 0.1f, 0.0f}.normalized(), + Vector3::yAxis(), 1.5f), + Vector3{0.0f}); +} + +void FunctionsTest::refractNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + Math::refract(Vector3{}, Vector3{1.0f}, 0.0f); + CORRADE_COMPARE(out.str(), + "Math::refract(): vectors Vector(0, 0, 0) and Vector(1, 1, 1) are not normalized\n"); +} + void FunctionsTest::trigonometric() { CORRADE_COMPARE(Math::sin(Deg(30.0f)), 0.5f); CORRADE_COMPARE(Math::sin(Rad(Constants::pi()/6)), 0.5f);