From a77a46c926e046c59d4eede8afd43524f214b7c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 30 Aug 2018 12:47:57 +0200 Subject: [PATCH 01/26] Math: debug output for Half with proper amount of decimal places. The totally random places after were not exactly useful. This file was sitting here for a while, not sure why I didn't commit that earlier. --- src/Magnum/CMakeLists.txt | 1 + src/Magnum/Math/Half.cpp | 41 +++++++++++++++++++++++++++++++ src/Magnum/Math/Half.h | 14 ++++++++--- src/Magnum/Math/Test/HalfTest.cpp | 6 ++--- 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 src/Magnum/Math/Half.cpp diff --git a/src/Magnum/CMakeLists.txt b/src/Magnum/CMakeLists.txt index 944da0a00..1b05ac225 100644 --- a/src/Magnum/CMakeLists.txt +++ b/src/Magnum/CMakeLists.txt @@ -133,6 +133,7 @@ endif() # Files shared between main library and math unit test library set(MagnumMath_SRCS Math/Color.cpp + Math/Half.cpp Math/Functions.cpp Math/Packing.cpp Math/instantiation.cpp) diff --git a/src/Magnum/Math/Half.cpp b/src/Magnum/Math/Half.cpp new file mode 100644 index 000000000..1d62fb0ed --- /dev/null +++ b/src/Magnum/Math/Half.cpp @@ -0,0 +1,41 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + 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 "Magnum/Math/Half.h" + +#include +#include + +namespace Magnum { namespace Math { + +Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, Half value) { + std::ostringstream out; + /* Wikipedia says it's 3 or 4 decimal places: + https://en.wikipedia.org/wiki/Half-precision_floating-point_format */ + out << std::setprecision(4) << Float(value); + return debug << out.str(); +} + +}} diff --git a/src/Magnum/Math/Half.h b/src/Magnum/Math/Half.h index afb3cb70f..8f64edf2e 100644 --- a/src/Magnum/Math/Half.h +++ b/src/Magnum/Math/Half.h @@ -150,10 +150,16 @@ inline Half operator "" _h(long double value) { return Half(Float(value)); } } -/** @debugoperator{Half} */ -inline Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, Half value) { - return debug << Float(value); -} +/** +@debugoperator{Half} + +Prints the value with 4 significant digits. +@see @ref Corrade::Utility::Debug::operator<<(float), + @ref Corrade::Utility::Debug::operator<<(double), + @ref Corrade::Utility::Debug::operator<<(long double value) +@todoc remove `long double value` once doxygen can link to long double overloads properly +*/ +MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, Half value); }} diff --git a/src/Magnum/Math/Test/HalfTest.cpp b/src/Magnum/Math/Test/HalfTest.cpp index 951b2fc2c..e49f70304 100644 --- a/src/Magnum/Math/Test/HalfTest.cpp +++ b/src/Magnum/Math/Test/HalfTest.cpp @@ -609,12 +609,12 @@ void HalfTest::debug() { std::ostringstream out; - Debug{&out} << -3.64_h << Half{Constants::inf()} + Debug{&out} << -36.41_h << Half{Constants::inf()} << Math::Vector3{3.14159_h, -1.4142_h, 1.618_h}; #ifdef _MSC_VER - CORRADE_COMPARE(out.str(), "-3.64063 inf Vector(3.14063, -1.41406, 1.61816)\n"); + CORRADE_COMPARE(out.str(), "-36.41 inf Vector(3.141, -1.414, 1.618)\n"); #else - CORRADE_COMPARE(out.str(), "-3.64062 inf Vector(3.14062, -1.41406, 1.61816)\n"); + CORRADE_COMPARE(out.str(), "-36.41 inf Vector(3.141, -1.414, 1.618)\n"); #endif } From 5e9bce3823e461d2b8de9b25df2599f6e55a50d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 30 Aug 2018 15:25:55 +0200 Subject: [PATCH 02/26] DebugTools: doc++ --- src/Magnum/DebugTools/BufferData.h | 8 +++---- src/Magnum/DebugTools/ForceRenderer.h | 8 +++---- src/Magnum/DebugTools/ObjectRenderer.h | 8 +++---- src/Magnum/DebugTools/ResourceManager.h | 4 ++-- src/Magnum/DebugTools/ShapeRenderer.h | 10 ++++---- src/Magnum/DebugTools/TextureImage.h | 32 ++++++++++++------------- 6 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/Magnum/DebugTools/BufferData.h b/src/Magnum/DebugTools/BufferData.h index 5dbfa61d4..00a090e9d 100644 --- a/src/Magnum/DebugTools/BufferData.h +++ b/src/Magnum/DebugTools/BufferData.h @@ -50,8 +50,8 @@ Emulates @ref GL::Buffer::subData() call on platforms that don't support it (such as OpenGL ES) by using @ref GL::Buffer::map(). @note This function is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. @requires_gles30 Extension @gl_extension{EXT,map_buffer_range} in OpenGL ES 2.0. @@ -70,8 +70,8 @@ Emulates @ref GL::Buffer::data() call on platforms that don't support it (such as OpenGL ES) by using @ref GL::Buffer::map(). @note This function is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. @requires_gles30 Extension @gl_extension{EXT,map_buffer_range} in OpenGL ES 2.0. diff --git a/src/Magnum/DebugTools/ForceRenderer.h b/src/Magnum/DebugTools/ForceRenderer.h index e6519ba91..7cc9f9e5a 100644 --- a/src/Magnum/DebugTools/ForceRenderer.h +++ b/src/Magnum/DebugTools/ForceRenderer.h @@ -47,8 +47,8 @@ namespace Magnum { namespace DebugTools { See @ref ForceRenderer documentation for more information. @note This class is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" and `WITH_SCENEGRAPH` enabled (done by + default). See @ref building-features for more information. */ class ForceRendererOptions { public: @@ -109,8 +109,8 @@ new DebugTools::ForceRenderer2D(object, {0.3f, 1.5f, -0.7f}, &force, "my", debug @endcode @note This class is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" and `WITH_SCENEGRAPH` enabled (done by + default). See @ref building-features for more information. @see @ref ForceRenderer2D, @ref ForceRenderer3D, @ref ForceRendererOptions */ diff --git a/src/Magnum/DebugTools/ObjectRenderer.h b/src/Magnum/DebugTools/ObjectRenderer.h index 115dd4170..67d8ed84f 100644 --- a/src/Magnum/DebugTools/ObjectRenderer.h +++ b/src/Magnum/DebugTools/ObjectRenderer.h @@ -46,8 +46,8 @@ namespace Magnum { namespace DebugTools { See @ref ObjectRenderer documentation for more information. @note This class is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" and `WITH_SCENEGRAPH` enabled (done by + default). See @ref building-features for more information. */ class ObjectRendererOptions { public: @@ -91,8 +91,8 @@ new DebugTools::ObjectRenderer2D(object, "my", debugDrawables); @endcode @note This class is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" and `WITH_SCENEGRAPH` enabled (done by + default). See @ref building-features for more information. @see @ref ObjectRenderer2D, @ref ObjectRenderer3D, @ref ObjectRendererOptions */ diff --git a/src/Magnum/DebugTools/ResourceManager.h b/src/Magnum/DebugTools/ResourceManager.h index 765511104..e555ca7f7 100644 --- a/src/Magnum/DebugTools/ResourceManager.h +++ b/src/Magnum/DebugTools/ResourceManager.h @@ -71,8 +71,8 @@ Stores various data used by debug renderers. See @ref debug-tools for more information. @note This class is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. */ #ifdef MAGNUM_BUILD_DEPRECATED CORRADE_IGNORE_DEPRECATED_PUSH diff --git a/src/Magnum/DebugTools/ShapeRenderer.h b/src/Magnum/DebugTools/ShapeRenderer.h index dc348ecf0..8a8a2b852 100644 --- a/src/Magnum/DebugTools/ShapeRenderer.h +++ b/src/Magnum/DebugTools/ShapeRenderer.h @@ -82,8 +82,9 @@ namespace Implementation { See @ref ShapeRenderer documentation for more information. @note This class is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default) and + `WITH_SHAPES` enabled (disabled by default). See @ref building-features for + more information. */ class CORRADE_DEPRECATED("scheduled for removal, see the docs for alternatives") ShapeRendererOptions { public: @@ -177,8 +178,9 @@ new DebugTools::ShapeRenderer2D(shape, "red", debugDrawables); @endcode @note This class is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default) and + `WITH_SHAPES` enabled (disabled by default). See @ref building-features for + more information. @see @ref ShapeRenderer2D, @ref ShapeRenderer3D, @ref ShapeRendererOptions diff --git a/src/Magnum/DebugTools/TextureImage.h b/src/Magnum/DebugTools/TextureImage.h index 3ec10a5c6..191f83618 100644 --- a/src/Magnum/DebugTools/TextureImage.h +++ b/src/Magnum/DebugTools/TextureImage.h @@ -55,8 +55,8 @@ additional shader and the @glsl floatBitsToUint() @ce GLSL function and then reinterpreted back to @ref GL::PixelType::Float when read to client memory. @note This function is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. */ MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, Image2D& image); @@ -70,8 +70,8 @@ Image2D image = DebugTools::textureSubImage(texture, 0, rect, {PixelFormat::RGBA @endcode @note This function is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. */ MAGNUM_DEBUGTOOLS_EXPORT Image2D textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, Image2D&& image); @@ -87,8 +87,8 @@ marked as framebuffer readable are supported; their generic @ref Magnum::PixelFormat "PixelFormat" counterparts are supported as well. @note This function is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. */ MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, Image2D& image); @@ -102,8 +102,8 @@ Image2D image = DebugTools::textureSubImage(texture, CubeMapCoordinate::Positive @endcode @note This function is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. */ MAGNUM_DEBUGTOOLS_EXPORT Image2D textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, Image2D&& image); @@ -124,8 +124,8 @@ marked as framebuffer readable are supported; their generic @requires_webgl20 Pixel buffer objects are not available in WebGL 1.0. @note This function is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. */ MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, GL::BufferImage2D& image, GL::BufferUsage usage); @@ -139,8 +139,8 @@ BufferImage2D image = DebugTools::textureSubImage(texture, 0, rect, {PixelFormat @endcode @note This function is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. */ MAGNUM_DEBUGTOOLS_EXPORT GL::BufferImage2D textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, GL::BufferImage2D&& image, GL::BufferUsage usage); @@ -158,8 +158,8 @@ marked as framebuffer readable are supported; their generic @requires_webgl20 Pixel buffer objects are not available in WebGL 1.0. @note This function is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. */ MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, GL::BufferImage2D& image, GL::BufferUsage usage); @@ -173,8 +173,8 @@ BufferImage2D image = DebugTools::textureSubImage(texture, CubeMapCoordinate::Po @endcode @note This function is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. */ MAGNUM_DEBUGTOOLS_EXPORT GL::BufferImage2D textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, GL::BufferImage2D&& image, GL::BufferUsage usage); #endif From bb9b41057fd481c0b68f976bb441c327fbcc5eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 30 Aug 2018 17:39:42 +0200 Subject: [PATCH 03/26] Math: separate assertion tests into a dedicated test case. --- src/Magnum/Math/Test/QuaternionTest.cpp | 62 +++++++++++++++---------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/Magnum/Math/Test/QuaternionTest.cpp b/src/Magnum/Math/Test/QuaternionTest.cpp index 98e3149b2..f779d477d 100644 --- a/src/Magnum/Math/Test/QuaternionTest.cpp +++ b/src/Magnum/Math/Test/QuaternionTest.cpp @@ -88,7 +88,9 @@ struct QuaternionTest: Corrade::TestSuite::Tester { void angle(); void matrix(); void lerp(); + void lerpNotNormalized(); void slerp(); + void slerpNotNormalized(); void transformVector(); void transformVectorNormalized(); @@ -104,6 +106,8 @@ typedef Math::Quaternion Quaternion; typedef Math::Vector3 Vector3; typedef Math::Vector4 Vector4; +using namespace Math::Literals; + QuaternionTest::QuaternionTest() { addTests({&QuaternionTest::construct, &QuaternionTest::constructIdentity, @@ -141,7 +145,9 @@ QuaternionTest::QuaternionTest() { &QuaternionTest::angle, &QuaternionTest::matrix, &QuaternionTest::lerp, + &QuaternionTest::lerpNotNormalized, &QuaternionTest::slerp, + &QuaternionTest::slerpNotNormalized, &QuaternionTest::transformVector, &QuaternionTest::transformVectorNormalized, @@ -455,38 +461,32 @@ void QuaternionTest::matrix() { } void QuaternionTest::lerp() { - Quaternion a = Quaternion::rotation(Deg(15.0f), Vector3(1.0f/Constants::sqrt3())); - Quaternion b = Quaternion::rotation(Deg(23.0f), Vector3::xAxis()); - - std::ostringstream o; - Error redirectError{&o}; - - Math::lerp(a*3.0f, b, 0.35f); - CORRADE_COMPARE(o.str(), "Math::lerp(): quaternions must be normalized\n"); - - o.str({}); - Math::lerp(a, b*-3.0f, 0.35f); - CORRADE_COMPARE(o.str(), "Math::lerp(): quaternions must be normalized\n"); - + Quaternion a = Quaternion::rotation(15.0_degf, Vector3(1.0f/Constants::sqrt3())); + Quaternion b = Quaternion::rotation(23.0_degf, Vector3::xAxis()); Quaternion lerp = Math::lerp(a, b, 0.35f); + + CORRADE_VERIFY(lerp.isNormalized()); CORRADE_COMPARE(lerp, Quaternion({0.119127f, 0.049134f, 0.049134f}, 0.990445f)); } -void QuaternionTest::slerp() { - Quaternion a = Quaternion::rotation(Deg(15.0f), Vector3(1.0f/Constants::sqrt3())); - Quaternion b = Quaternion::rotation(Deg(23.0f), Vector3::xAxis()); +void QuaternionTest::lerpNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; - std::ostringstream o; - Error redirectError{&o}; - - Math::slerp(a*3.0f, b, 0.35f); - CORRADE_COMPARE(o.str(), "Math::slerp(): quaternions must be normalized\n"); - - o.str({}); - Math::slerp(a, b*-3.0f, 0.35f); - CORRADE_COMPARE(o.str(), "Math::slerp(): quaternions must be normalized\n"); + Quaternion a; + Math::lerp(a*3.0f, a, 0.35f); + Math::lerp(a, a*-3.0f, 0.35f); + CORRADE_COMPARE(out.str(), + "Math::lerp(): quaternions must be normalized\n" + "Math::lerp(): quaternions must be normalized\n"); +} +void QuaternionTest::slerp() { + Quaternion a = Quaternion::rotation(15.0_degf, Vector3(1.0f/Constants::sqrt3())); + Quaternion b = Quaternion::rotation(23.0_degf, Vector3::xAxis()); Quaternion slerp = Math::slerp(a, b, 0.35f); + + CORRADE_VERIFY(slerp.isNormalized()); CORRADE_COMPARE(slerp, Quaternion({0.1191653f, 0.0491109f, 0.0491109f}, 0.9904423f)); /* Avoid division by zero */ @@ -494,6 +494,18 @@ void QuaternionTest::slerp() { CORRADE_COMPARE(Math::slerp(a, -a, 0.42f), a); } +void QuaternionTest::slerpNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + Quaternion a; + Math::slerp(a*3.0f, a, 0.35f); + Math::slerp(a, a*-3.0f, 0.35f); + CORRADE_COMPARE(out.str(), + "Math::slerp(): quaternions must be normalized\n" + "Math::slerp(): quaternions must be normalized\n"); +} + void QuaternionTest::transformVector() { Quaternion a = Quaternion::rotation(Deg(23.0f), Vector3::xAxis()); Matrix4 m = Matrix4::rotationX(Deg(23.0f)); From 0f4b88870a1f14eb25d9933e376a5f0ce5dd477c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 30 Aug 2018 23:01:17 +0200 Subject: [PATCH 04/26] Math: simplify angle() implementation for Complex. Not sure why that wasn't done before. --- src/Magnum/Math/Complex.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magnum/Math/Complex.h b/src/Magnum/Math/Complex.h index f966aa2ef..cc9cb73b7 100644 --- a/src/Magnum/Math/Complex.h +++ b/src/Magnum/Math/Complex.h @@ -72,7 +72,7 @@ Expects that both complex numbers are normalized. @f[ template inline Rad angle(const Complex& normalizedA, const Complex& normalizedB) { CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), "Math::angle(): complex numbers must be normalized", {}); - return Rad(std::acos(normalizedA.real()*normalizedB.real() + normalizedA.imaginary()*normalizedB.imaginary())); + return Rad(std::acos(dot(normalizedA, normalizedB))); } /** From c2c6b9f22b1a03515ae30aec94fd6dd64cb02fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 30 Aug 2018 22:46:28 +0200 Subject: [PATCH 05/26] Math: added lerp() and slerp() for complex numbers. It's a straight copy of the code for quaternions -- it could probably be simplified a bit, but I don't have the necessary brain cells at the moment. I tried the following but failed: retun Complex::rotation(acos(cosAngle)*t)*normalizedA; --- doc/changelog.dox | 3 ++ src/Magnum/Math/Complex.h | 47 +++++++++++++++++++ src/Magnum/Math/Functions.h | 3 +- src/Magnum/Math/Quaternion.h | 12 +++-- src/Magnum/Math/Test/ComplexTest.cpp | 61 +++++++++++++++++++++++++ src/Magnum/Math/Test/QuaternionTest.cpp | 26 +++++++++++ 6 files changed, 146 insertions(+), 6 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 346b59f5e..dd81f0fca 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -66,6 +66,9 @@ See also: @ref Math::Color4::fromSrgb(UnsignedInt, T), @ref Math::Color3::toSrgbInt(), and @ref Math::Color4::toSrgbAlphaInt() for easier conversion of packed 24-/32-bit sRGB colors to and from @ref Math::Color3 / @ref Math::Color4 +- Added @ref Math::lerp(const Complex&, const Complex&, T) and + @ref Math::slerp(const Complex&, const Complex&, T) for feature + parity with @ref Math::Quaternion - Added @ref Math::Range2D::x(), @ref Math::Range3D::x(), @ref Math::Range2D::y(), @ref Math::Range3D::y(), @ref Math::Range3D::z() and @ref Math::Range3D::y() for slicing ranges into lower dimensions diff --git a/src/Magnum/Math/Complex.h b/src/Magnum/Math/Complex.h index cc9cb73b7..465d70b7b 100644 --- a/src/Magnum/Math/Complex.h +++ b/src/Magnum/Math/Complex.h @@ -456,6 +456,53 @@ template inline Complex operator/(T scalar, const Complex& comple return {scalar/complex.real(), scalar/complex.imaginary()}; } +/** @relatesalso Complex +@brief Linear interpolation of two complex numbers +@param normalizedA First complex number +@param normalizedB Second complex number +@param t Interpolation phase (from range @f$ [0; 1] @f$) + +Expects that both complex numbers are normalized. @f[ + c_{LERP} = \frac{(1 - t) c_A + t c_B}{|(1 - t) c_A + t c_B|} +@f] +@see @ref Complex::isNormalized(), @ref slerp(const Complex&, const Complex&, T), + @ref lerp(const Quaternion&, const Quaternion&, T), + @ref lerp(const T&, const T&, U) +*/ +template inline Complex lerp(const Complex& normalizedA, const Complex& normalizedB, T t) { + CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), + "Math::lerp(): complex numbers must be normalized", {}); + return ((T(1) - t)*normalizedA + t*normalizedB).normalized(); +} + +/** @relatesalso Complex +@brief Spherical linear interpolation of two complex numbers +@param normalizedA First complex number +@param normalizedB Second complex number +@param t Interpolation phase (from range @f$ [0; 1] @f$) + +Expects that both complex numbers are normalized. If the complex numbers are +the same, returns the first argument. @f[ + c_{SLERP} = \frac{sin((1 - t) \theta) c_A + sin(t \theta) c_B}{sin \theta} + ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ + \theta = acos \left( \frac{c_A \cdot c_B}{|c_A| \cdot |c_B|} \right) = acos(c_A \cdot c_B) +@f] +@see @ref Complex::isNormalized(), @ref lerp(const Complex&, const Complex&, T), + @ref slerp(const Quaternion&, const Quaternion&, T) + */ +template inline Complex slerp(const Complex& normalizedA, const Complex& normalizedB, T t) { + CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), + "Math::slerp(): complex numbers must be normalized", {}); + const T cosAngle = dot(normalizedA, normalizedB); + + /* Avoid division by zero */ + if(std::abs(cosAngle) >= T(1)) return Complex{normalizedA}; + + /** @todo couldn't this be done somewhat simpler? */ + const T a = std::acos(cosAngle); + return (std::sin((T(1) - t)*a)*normalizedA + std::sin(t*a)*normalizedB)/std::sin(a); +} + /** @debugoperator{Complex} */ template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, const Complex& value) { return debug << "Complex(" << Corrade::Utility::Debug::nospace diff --git a/src/Magnum/Math/Functions.h b/src/Magnum/Math/Functions.h index 80af3f9dd..0612b91c0 100644 --- a/src/Magnum/Math/Functions.h +++ b/src/Magnum/Math/Functions.h @@ -560,7 +560,8 @@ The interpolation for vectors is done as in following, similarly for scalars: @f @f] See @ref select() for constant interpolation using the same API. -@see @ref lerpInverted(), @ref lerp(const Quaternion&, const Quaternion&, T) +@see @ref lerpInverted(), @ref lerp(const Complex&, const Complex&, T), + @ref lerp(const Quaternion&, const Quaternion&, T) @m_keyword{mix(),GLSL mix(),} */ template inline diff --git a/src/Magnum/Math/Quaternion.h b/src/Magnum/Math/Quaternion.h index 9075b2798..7286c33ac 100644 --- a/src/Magnum/Math/Quaternion.h +++ b/src/Magnum/Math/Quaternion.h @@ -87,8 +87,10 @@ template inline Rad angle(const Quaternion& normalizedA, const Qu Expects that both quaternions are normalized. @f[ q_{LERP} = \frac{(1 - t) q_A + t q_B}{|(1 - t) q_A + t q_B|} @f] -@see @ref Quaternion::isNormalized(), @ref slerp(const Quaternion&, const Quaternion&, T), - @ref lerp(const T&, const T&, U), @ref sclerp() +@see @ref Quaternion::isNormalized(), + @ref slerp(const Quaternion&, const Quaternion&, T), @ref sclerp(), + @ref lerp(const T&, const T&, U), + @ref lerp(const Complex&, const Complex&, T) */ template inline Quaternion lerp(const Quaternion& normalizedA, const Quaternion& normalizedB, T t) { CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), @@ -105,12 +107,12 @@ template inline Quaternion lerp(const Quaternion& normalizedA, co Expects that both quaternions are normalized. If the quaternions are the same or one is a negation of the other, returns the first argument. @f[ q_{SLERP} = \frac{sin((1 - t) \theta) q_A + sin(t \theta) q_B}{sin \theta} - ~ ~ ~ ~ ~ ~ ~ + ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ \theta = acos \left( \frac{q_A \cdot q_B}{|q_A| \cdot |q_B|} \right) = acos(q_A \cdot q_B) @f] @see @ref Quaternion::isNormalized(), @ref lerp(const Quaternion&, const Quaternion&, T), - @ref sclerp() - */ + @ref slerp(const Complex&, const Complex&, T), @ref sclerp() +*/ template inline Quaternion slerp(const Quaternion& normalizedA, const Quaternion& normalizedB, T t) { CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), "Math::slerp(): quaternions must be normalized", {}); diff --git a/src/Magnum/Math/Test/ComplexTest.cpp b/src/Magnum/Math/Test/ComplexTest.cpp index c5540a1bc..c434f57ec 100644 --- a/src/Magnum/Math/Test/ComplexTest.cpp +++ b/src/Magnum/Math/Test/ComplexTest.cpp @@ -86,6 +86,10 @@ struct ComplexTest: Corrade::TestSuite::Tester { void angle(); void rotation(); void matrix(); + void lerp(); + void lerpNotNormalized(); + void slerp(); + void slerpNotNormalized(); void transformVector(); void debug(); @@ -128,6 +132,10 @@ ComplexTest::ComplexTest() { &ComplexTest::angle, &ComplexTest::rotation, &ComplexTest::matrix, + &ComplexTest::lerp, + &ComplexTest::lerpNotNormalized, + &ComplexTest::slerp, + &ComplexTest::slerpNotNormalized, &ComplexTest::transformVector, &ComplexTest::debug, @@ -141,6 +149,8 @@ typedef Math::Vector2 Vector2; typedef Math::Matrix3 Matrix3; typedef Math::Matrix2x2 Matrix2x2; +using namespace Math::Literals; + void ComplexTest::construct() { constexpr Complex a = {0.5f, -3.7f}; CORRADE_COMPARE(a, Complex(0.5f, -3.7f)); @@ -411,6 +421,57 @@ void ComplexTest::matrix() { CORRADE_COMPARE(b, a); } +void ComplexTest::lerp() { + /* Results should be consistent with QuaternionTest::lerp2D() (but not + equivalent, probably because quaternions double cover and complex + numbers not) */ + Complex a = Complex::rotation(15.0_degf); + Complex b = Complex::rotation(57.0_degf); + Complex lerp = Math::lerp(a, b, 0.35f); + + CORRADE_VERIFY(lerp.isNormalized()); + CORRADE_COMPARE(lerp.angle(), 29.4308_degf); /* almost but not quite 29.7 */ + CORRADE_COMPARE(lerp, (Complex{0.87095f, 0.491372f})); +} + +void ComplexTest::lerpNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + Complex a; + Math::lerp(a*3.0f, a, 0.35f); + Math::lerp(a, a*-3.0f, 0.35f); + CORRADE_COMPARE(out.str(), + "Math::lerp(): complex numbers must be normalized\n" + "Math::lerp(): complex numbers must be normalized\n"); +} + +void ComplexTest::slerp() { + /* Result angle should be equivalent to QuaternionTest::slerp2D() */ + Complex a = Complex::rotation(15.0_degf); + Complex b = Complex::rotation(57.0_degf); + Complex slerp = Math::slerp(a, b, 0.35f); + + CORRADE_VERIFY(slerp.isNormalized()); + CORRADE_COMPARE(slerp.angle(), 29.7_degf); /* 15 + (57-15)*0.35 */ + CORRADE_COMPARE(slerp, (Complex{0.868632f, 0.495459f})); + + /* Avoid division by zero */ + CORRADE_COMPARE(Math::slerp(a, a, 0.25f), a); +} + +void ComplexTest::slerpNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + Complex a; + Math::slerp(a*3.0f, a, 0.35f); + Math::slerp(a, a*-3.0f, 0.35f); + CORRADE_COMPARE(out.str(), + "Math::slerp(): complex numbers must be normalized\n" + "Math::slerp(): complex numbers must be normalized\n"); +} + void ComplexTest::transformVector() { Complex a = Complex::rotation(Deg(23.0f)); Matrix3 m = Matrix3::rotation(Deg(23.0f)); diff --git a/src/Magnum/Math/Test/QuaternionTest.cpp b/src/Magnum/Math/Test/QuaternionTest.cpp index f779d477d..662e0a7d4 100644 --- a/src/Magnum/Math/Test/QuaternionTest.cpp +++ b/src/Magnum/Math/Test/QuaternionTest.cpp @@ -88,8 +88,10 @@ struct QuaternionTest: Corrade::TestSuite::Tester { void angle(); void matrix(); void lerp(); + void lerp2D(); void lerpNotNormalized(); void slerp(); + void slerp2D(); void slerpNotNormalized(); void transformVector(); void transformVectorNormalized(); @@ -145,8 +147,10 @@ QuaternionTest::QuaternionTest() { &QuaternionTest::angle, &QuaternionTest::matrix, &QuaternionTest::lerp, + &QuaternionTest::lerp2D, &QuaternionTest::lerpNotNormalized, &QuaternionTest::slerp, + &QuaternionTest::slerp2D, &QuaternionTest::slerpNotNormalized, &QuaternionTest::transformVector, &QuaternionTest::transformVectorNormalized, @@ -469,6 +473,17 @@ void QuaternionTest::lerp() { CORRADE_COMPARE(lerp, Quaternion({0.119127f, 0.049134f, 0.049134f}, 0.990445f)); } +void QuaternionTest::lerp2D() { + /* Results should be consistent with ComplexTest::lerp() */ + Quaternion a = Quaternion::rotation(15.0_degf, Vector3::zAxis()); + Quaternion b = Quaternion::rotation(57.0_degf, Vector3::zAxis()); + Quaternion lerp = Math::lerp(a, b, 0.35f); + + CORRADE_VERIFY(lerp.isNormalized()); + CORRADE_COMPARE(lerp.angle(), 29.6351_degf); /* almost but not quite 29.7 */ + CORRADE_COMPARE(lerp, (Quaternion{{0.0f, 0.0f, 0.255742f}, 0.966745f})); +} + void QuaternionTest::lerpNotNormalized() { std::ostringstream out; Error redirectError{&out}; @@ -494,6 +509,17 @@ void QuaternionTest::slerp() { CORRADE_COMPARE(Math::slerp(a, -a, 0.42f), a); } +void QuaternionTest::slerp2D() { + /* Result angle should be equivalent to ComplexTest::slerp() */ + Quaternion a = Quaternion::rotation(15.0_degf, Vector3::zAxis()); + Quaternion b = Quaternion::rotation(57.0_degf, Vector3::zAxis()); + Quaternion slerp = Math::slerp(a, b, 0.35f); + + CORRADE_VERIFY(slerp.isNormalized()); + CORRADE_COMPARE(slerp.angle(), 29.7_degf); /* 15 + (57-15)*0.35 */ + CORRADE_COMPARE(slerp, (Quaternion{{0.0f, 0.0f, 0.256289f}, 0.9666f})); +} + void QuaternionTest::slerpNotNormalized() { std::ostringstream out; Error redirectError{&out}; From 8c7c74c22e50e0bacb22ad38204675173eb6f149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 31 Aug 2018 20:53:53 +0200 Subject: [PATCH 06/26] Math: mutable getters for Complex, Dual and Quaternion. Not sure why these were omitted. Everything else has them. --- doc/changelog.dox | 4 ++++ src/Magnum/Math/Complex.h | 6 ++++-- src/Magnum/Math/Dual.h | 8 ++++++-- src/Magnum/Math/Quaternion.h | 8 ++++++-- src/Magnum/Math/Test/ComplexTest.cpp | 24 +++++++++++++++++++----- src/Magnum/Math/Test/DualTest.cpp | 23 +++++++++++++++++++---- src/Magnum/Math/Test/QuaternionTest.cpp | 24 +++++++++++++++++++----- 7 files changed, 77 insertions(+), 20 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index dd81f0fca..f2ffda900 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -88,6 +88,10 @@ See also: @ref Math::Distance::pointPlane() and others - Ability to convert @ref Math::BoolVector from and to external representation +- Mutable overloads for @ref Math::Complex::real(), + @ref Math::Complex::imaginary(), @ref Math::Dual::real(), + @ref Math::Dual::dual(), @ref Math::Quaternion::vector() and + @ref Math::Quaternion::scalar() - Ability to use @ref Math::Complex, @ref Math::DualComplex, @ref Math::Quaternion, @ref Math::DualQuaternion with @ref Corrade::Utility::Configuration and @ref Corrade::Utility::Arguments diff --git a/src/Magnum/Math/Complex.h b/src/Magnum/Math/Complex.h index 465d70b7b..9299826df 100644 --- a/src/Magnum/Math/Complex.h +++ b/src/Magnum/Math/Complex.h @@ -192,10 +192,12 @@ template class Complex { } /** @brief Real part */ - constexpr T real() const { return _real; } + T& real() { return _real; } + constexpr T real() const { return _real; } /**< @overload */ /** @brief Imaginary part */ - constexpr T imaginary() const { return _imaginary; } + T& imaginary() { return _imaginary; } + constexpr T imaginary() const { return _imaginary; } /**< @overload */ /** * @brief Convert complex number to vector diff --git a/src/Magnum/Math/Dual.h b/src/Magnum/Math/Dual.h index 5cf8c04ee..151db9099 100644 --- a/src/Magnum/Math/Dual.h +++ b/src/Magnum/Math/Dual.h @@ -123,11 +123,15 @@ template class Dual { /** @brief Real part */ T& real() { return _real; } - constexpr T real() const { return _real; } /**< @overload */ + /* Returning const so it's possible to call constexpr functions on the + result. WTF, C++?! */ + constexpr const T real() const { return _real; } /**< @overload */ /** @brief Dual part */ T& dual() { return _dual; } - constexpr T dual() const { return _dual; } /**< @overload */ + /* Returning const so it's possible to call constexpr functions on the + result. WTF, C++?! */ + constexpr const T dual() const { return _dual; } /**< @overload */ /** * @brief Add and assign dual number diff --git a/src/Magnum/Math/Quaternion.h b/src/Magnum/Math/Quaternion.h index 7286c33ac..2256104a4 100644 --- a/src/Magnum/Math/Quaternion.h +++ b/src/Magnum/Math/Quaternion.h @@ -239,10 +239,14 @@ template class Quaternion { } /** @brief Vector part */ - constexpr const Vector3 vector() const { return _vector; } + Vector3& vector() { return _vector; } + /* Returning const so it's possible to call constexpr functions on the + result. WTF, C++?! */ + constexpr const Vector3 vector() const { return _vector; } /**< @overload */ /** @brief Scalar part */ - constexpr T scalar() const { return _scalar; } + T& scalar() { return _scalar; } + constexpr T scalar() const { return _scalar; } /**< @overload */ /** * @brief Rotation angle of unit quaternion diff --git a/src/Magnum/Math/Test/ComplexTest.cpp b/src/Magnum/Math/Test/ComplexTest.cpp index c434f57ec..fd9c100a0 100644 --- a/src/Magnum/Math/Test/ComplexTest.cpp +++ b/src/Magnum/Math/Test/ComplexTest.cpp @@ -64,6 +64,8 @@ struct ComplexTest: Corrade::TestSuite::Tester { void constructCopy(); void convert(); + void data(); + void compare(); void isNormalized(); template void isNormalizedEpsilon(); @@ -106,6 +108,8 @@ ComplexTest::ComplexTest() { &ComplexTest::constructCopy, &ComplexTest::convert, + &ComplexTest::data, + &ComplexTest::compare, &ComplexTest::isNormalized, &ComplexTest::isNormalizedEpsilon, @@ -154,11 +158,8 @@ using namespace Math::Literals; void ComplexTest::construct() { constexpr Complex a = {0.5f, -3.7f}; CORRADE_COMPARE(a, Complex(0.5f, -3.7f)); - - constexpr Float b = a.real(); - constexpr Float c = a.imaginary(); - CORRADE_COMPARE(b, 0.5f); - CORRADE_COMPARE(c, -3.7f); + CORRADE_COMPARE(a.real(), 0.5f); + CORRADE_COMPARE(a.imaginary(), -3.7f); CORRADE_VERIFY((std::is_nothrow_constructible::value)); } @@ -255,6 +256,19 @@ void ComplexTest::convert() { CORRADE_VERIFY(!(std::is_convertible::value)); } +void ComplexTest::data() { + constexpr Complex ca{1.5f, -3.5f}; + constexpr Float real = ca.real(); + constexpr Float imaginary = ca.imaginary(); + CORRADE_COMPARE(real, 1.5f); + CORRADE_COMPARE(imaginary, -3.5f); + + Complex a{1.5f, -3.5f}; + a.real() = 2.0f; + a.imaginary() = -3.5f; + CORRADE_COMPARE(a, (Complex{2.0f, -3.5f})); +} + void ComplexTest::compare() { CORRADE_VERIFY(Complex(3.7f, -1.0f+TypeTraits::epsilon()/2) == Complex(3.7f, -1.0f)); CORRADE_VERIFY(Complex(3.7f, -1.0f+TypeTraits::epsilon()*2) != Complex(3.7f, -1.0f)); diff --git a/src/Magnum/Math/Test/DualTest.cpp b/src/Magnum/Math/Test/DualTest.cpp index e44362de4..f53a42981 100644 --- a/src/Magnum/Math/Test/DualTest.cpp +++ b/src/Magnum/Math/Test/DualTest.cpp @@ -42,6 +42,8 @@ struct DualTest: Corrade::TestSuite::Tester { void constructConversion(); void constructCopy(); + void data(); + void compare(); void addSubtract(); @@ -79,6 +81,8 @@ DualTest::DualTest() { &DualTest::constructConversion, &DualTest::constructCopy, + &DualTest::data, + &DualTest::compare, &DualTest::addSubtract, @@ -101,10 +105,8 @@ DualTest::DualTest() { void DualTest::construct() { constexpr Dual a = {2.0f, -7.5f}; - constexpr Float b = a.real(); - constexpr Float c = a.dual(); - CORRADE_COMPARE(b, 2.0f); - CORRADE_COMPARE(c, -7.5f); + CORRADE_COMPARE(a.real(), 2.0f); + CORRADE_COMPARE(a.dual(), -7.5f); constexpr Dual d(3.0f); CORRADE_COMPARE(d.real(), 3.0f); @@ -176,6 +178,19 @@ void DualTest::constructCopy() { CORRADE_VERIFY(std::is_nothrow_copy_assignable::value); } +void DualTest::data() { + constexpr Dual ca{1.5f, -3.5f}; + constexpr Float real = ca.real(); + constexpr Float dual = ca.dual(); + CORRADE_COMPARE(real, 1.5f); + CORRADE_COMPARE(dual, -3.5f); + + Dual a{1.5f, -3.5f}; + a.real() = 2.0f; + a.dual() = -3.5f; + CORRADE_COMPARE(a, (Dual{2.0f, -3.5f})); +} + void DualTest::compare() { CORRADE_VERIFY(Dual(1.0f, 1.0f+TypeTraits::epsilon()/2) == Dual(1.0f, 1.0f)); CORRADE_VERIFY(Dual(1.0f, 1.0f+TypeTraits::epsilon()*2) != Dual(1.0f, 1.0f)); diff --git a/src/Magnum/Math/Test/QuaternionTest.cpp b/src/Magnum/Math/Test/QuaternionTest.cpp index 662e0a7d4..202697898 100644 --- a/src/Magnum/Math/Test/QuaternionTest.cpp +++ b/src/Magnum/Math/Test/QuaternionTest.cpp @@ -65,6 +65,8 @@ struct QuaternionTest: Corrade::TestSuite::Tester { void constructCopy(); void convert(); + void data(); + void compare(); void isNormalized(); template void isNormalizedEpsilon(); @@ -120,6 +122,8 @@ QuaternionTest::QuaternionTest() { &QuaternionTest::constructCopy, &QuaternionTest::convert, + &QuaternionTest::data, + &QuaternionTest::compare, &QuaternionTest::isNormalized, &QuaternionTest::isNormalizedEpsilon, @@ -162,11 +166,8 @@ QuaternionTest::QuaternionTest() { void QuaternionTest::construct() { constexpr Quaternion a = {{1.0f, 2.0f, 3.0f}, -4.0f}; CORRADE_COMPARE(a, Quaternion({1.0f, 2.0f, 3.0f}, -4.0f)); - - constexpr Vector3 b = a.vector(); - constexpr Float c = a.scalar(); - CORRADE_COMPARE(b, Vector3(1.0f, 2.0f, 3.0f)); - CORRADE_COMPARE(c, -4.0f); + CORRADE_COMPARE(a.vector(), Vector3(1.0f, 2.0f, 3.0f)); + CORRADE_COMPARE(a.scalar(), -4.0f); CORRADE_VERIFY((std::is_nothrow_constructible::value)); } @@ -259,6 +260,19 @@ void QuaternionTest::convert() { CORRADE_VERIFY(!(std::is_convertible::value)); } +void QuaternionTest::data() { + constexpr Quaternion ca{{1.0f, 2.0f, 3.0f}, -4.0f}; + constexpr Vector3 vector = ca.vector(); + constexpr Float scalar = ca.scalar(); + CORRADE_COMPARE(vector, (Vector3{1.0f, 2.0f, 3.0f})); + CORRADE_COMPARE(scalar, -4.0f); + + Quaternion a{{1.0f, 2.0f, 3.0f}, -4.0f}; + a.vector().y() = 4.3f; + a.scalar() = 1.1f; + CORRADE_COMPARE(a, (Quaternion{{1.0f, 4.3f, 3.0f}, 1.1f})); +} + void QuaternionTest::compare() { CORRADE_VERIFY(Quaternion({1.0f+TypeTraits::epsilon()/2, 2.0f, 3.0f}, -4.0f) == Quaternion({1.0f, 2.0f, 3.0f}, -4.0f)); CORRADE_VERIFY(Quaternion({1.0f+TypeTraits::epsilon()*2, 2.0f, 3.0f}, -4.0f) != Quaternion({1.0f, 2.0f, 3.0f}, -4.0f)); From ffe838a4040281725e763d07db6b737f83f37a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 31 Aug 2018 21:23:42 +0200 Subject: [PATCH 07/26] Animation: add an explanatory comment. --- src/Magnum/Animation/Test/PlayerTest.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Magnum/Animation/Test/PlayerTest.cpp b/src/Magnum/Animation/Test/PlayerTest.cpp index 61a9df872..a1520ea14 100644 --- a/src/Magnum/Animation/Test/PlayerTest.cpp +++ b/src/Magnum/Animation/Test/PlayerTest.cpp @@ -833,6 +833,10 @@ void PlayerTest::runFor100YearsFloat() { CORRADE_COMPARE(player.state(), State::Playing); { + /* Asm.js uses doubles for all floating-point calculations, so we don't + lose any precision and thus even the "run for 100 years" test passes + there. Unfortunately it's not possible to detect if this is asm.js + so the XFAIL is done like this. */ #ifndef CORRADE_TARGET_EMSCRIPTEN CORRADE_EXPECT_FAIL_IF(data.failsFuzzyFloat, "Imprecision larger than 2.5e-4f."); #else From ac11a8e81551b5d1f6001cb89d19fad4c667b903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 30 Aug 2018 18:09:15 +0200 Subject: [PATCH 08/26] Animation, Trade: support Complex for rotation animations. Since there's now lerp() and slerp() for it. --- src/Magnum/Animation/Interpolation.cpp | 13 +++++++++++ src/Magnum/Animation/Interpolation.h | 8 +++++++ .../Animation/Test/InterpolationTest.cpp | 23 +++++++++++++++++++ src/Magnum/Animation/Track.h | 2 ++ src/Magnum/Trade/AnimationData.cpp | 2 ++ src/Magnum/Trade/AnimationData.h | 9 +++++++- 6 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/Magnum/Animation/Interpolation.cpp b/src/Magnum/Animation/Interpolation.cpp index 10b2c1e67..fcb4e9c4e 100644 --- a/src/Magnum/Animation/Interpolation.cpp +++ b/src/Magnum/Animation/Interpolation.cpp @@ -25,6 +25,7 @@ #include "Interpolation.h" +#include "Magnum/Math/Complex.h" #include "Magnum/Math/DualQuaternion.h" namespace Magnum { namespace Animation { @@ -61,6 +62,17 @@ Debug& operator<<(Debug& debug, const Extrapolation value) { namespace Implementation { +template auto TypeTraits, Math::Complex>::interpolator(Interpolation interpolation) -> Interpolator { + switch(interpolation) { + case Interpolation::Constant: return Math::select; + case Interpolation::Linear: return Math::slerp; + + case Interpolation::Custom: ; /* nope */ + } + + CORRADE_ASSERT(false, "Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {}); +} + template auto TypeTraits, Math::Quaternion>::interpolator(Interpolation interpolation) -> Interpolator { switch(interpolation) { case Interpolation::Constant: return Math::select; @@ -83,6 +95,7 @@ template auto TypeTraits, Math::DualQuaternion< CORRADE_ASSERT(false, "Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {}); } +template struct MAGNUM_EXPORT TypeTraits, Math::Complex>; template struct MAGNUM_EXPORT TypeTraits, Math::Quaternion>; template struct MAGNUM_EXPORT TypeTraits, Math::DualQuaternion>; diff --git a/src/Magnum/Animation/Interpolation.h b/src/Magnum/Animation/Interpolation.h index aed2ce90a..cc946f326 100644 --- a/src/Magnum/Animation/Interpolation.h +++ b/src/Magnum/Animation/Interpolation.h @@ -100,6 +100,7 @@ Interpolation type | Value type | Result type | Interpolator @ref Interpolation::Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() @ref Interpolation::Linear | any scalar `V` | `V` | @ref Math::lerp() @ref Interpolation::Linear | any vector `V` | `V` | @ref Math::lerp() +@ref Interpolation::Linear | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex&, const Complex&, T) "Math::slerp()" @ref Interpolation::Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()" @ref Interpolation::Linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()" @@ -247,6 +248,13 @@ template auto TypeTraitsBool::interpolator(Interpolation interpolati template<> struct TypeTraits: TypeTraitsBool {}; template struct TypeTraits, Math::BoolVector>: TypeTraitsBool> {}; +/* Complex, preferring slerp() as it is more precise */ +template struct MAGNUM_EXPORT TypeTraits, Math::Complex> { + typedef Math::Complex(*Interpolator)(const Math::Complex&, const Math::Complex&, Float); + + static Interpolator interpolator(Interpolation interpolation); +}; + /* Quaternions and dual quaternions, preferring slerp() as it is more precise */ template struct MAGNUM_EXPORT TypeTraits, Math::Quaternion> { typedef Math::Quaternion(*Interpolator)(const Math::Quaternion&, const Math::Quaternion&, Float); diff --git a/src/Magnum/Animation/Test/InterpolationTest.cpp b/src/Magnum/Animation/Test/InterpolationTest.cpp index 91bb92dfa..2e5a4aae4 100644 --- a/src/Magnum/Animation/Test/InterpolationTest.cpp +++ b/src/Magnum/Animation/Test/InterpolationTest.cpp @@ -27,6 +27,7 @@ #include #include "Magnum/Animation/Interpolation.h" +#include "Magnum/Math/Complex.h" #include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/Half.h" @@ -38,6 +39,7 @@ struct InterpolationTest: TestSuite::Tester { void interpolatorFor(); void interpolatorForBool(); void interpolatorForBoolVector(); + void interpolatorForComplex(); void interpolatorForQuaternion(); void interpolatorForDualQuaternion(); @@ -138,6 +140,7 @@ InterpolationTest::InterpolationTest() { addTests({&InterpolationTest::interpolatorFor, &InterpolationTest::interpolatorForBool, &InterpolationTest::interpolatorForBoolVector, + &InterpolationTest::interpolatorForComplex, &InterpolationTest::interpolatorForQuaternion, &InterpolationTest::interpolatorForDualQuaternion}); @@ -215,6 +218,26 @@ void InterpolationTest::interpolatorForBoolVector() { "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); } +void InterpolationTest::interpolatorForComplex() { + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Constant)( + Complex::rotation(25.0_degf), + Complex::rotation(75.0_degf), 0.5f), + Complex::rotation(25.0_degf)); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Linear)( + Complex::rotation(25.0_degf), + Complex::rotation(75.0_degf), 0.5f), + Complex::rotation(50.0_degf)); + + std::ostringstream out; + Error redirectError{&out}; + Animation::interpolatorFor(Interpolation::Custom); + Animation::interpolatorFor(Interpolation(0xde)); + + CORRADE_COMPARE(out.str(), + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n" + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); +} + void InterpolationTest::interpolatorForQuaternion() { CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Constant)( Quaternion::rotation(25.0_degf, Vector3::xAxis()), diff --git a/src/Magnum/Animation/Track.h b/src/Magnum/Animation/Track.h index ad118938e..a7fa77b20 100644 --- a/src/Magnum/Animation/Track.h +++ b/src/Magnum/Animation/Track.h @@ -67,7 +67,9 @@ Linear | @cpp bool @ce | @cpp bool @ce | @ref Math::select() Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() Linear | any scalar `V` | `V` | @ref Math::lerp() Linear | any vector `V` | `V` | @ref Math::lerp() +Linear | @ref Math::Complex | @ref Math::Complex | @ref Math::lerp(const Complex&, const Complex&, T) "Math::lerp()" Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion&, const Quaternion&, T) "Math::lerp()" +Spherical linear | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex&, const Complex&, T) "Math::slerp()" Spherical linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()" Screw linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()" diff --git a/src/Magnum/Trade/AnimationData.cpp b/src/Magnum/Trade/AnimationData.cpp index 5208ac45a..c2f8f3278 100644 --- a/src/Magnum/Trade/AnimationData.cpp +++ b/src/Magnum/Trade/AnimationData.cpp @@ -97,6 +97,7 @@ template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Ani template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Animation::Interpolation) -> Vector4d(*)(const Vector4d&, const Vector4d&, Float); template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Animation::Interpolation) -> Vector4i(*)(const Vector4i&, const Vector4i&, Float); template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Animation::Interpolation) -> Vector4ui(*)(const Vector4ui&, const Vector4ui&, Float); +template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Animation::Interpolation) -> Complex(*)(const Complex&, const Complex&, Float); template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Animation::Interpolation) -> Quaternion(*)(const Quaternion&, const Quaternion&, Float); template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Animation::Interpolation) -> DualQuaternion(*)(const DualQuaternion&, const DualQuaternion&, Float); @@ -120,6 +121,7 @@ Debug& operator<<(Debug& debug, const AnimationTrackType value) { _c(Vector4) _c(Vector4ui) _c(Vector4i) + _c(Complex) _c(Quaternion) _c(DualQuaternion) #undef _c diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index d8196cd3d..5d3781957 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -75,6 +75,12 @@ enum class AnimationTrackType: UnsignedByte { Vector4ui, /**< @ref Magnum::Vector4ui "Vector4ui" */ Vector4i, /**< @ref Magnum::Vector4i "Vector4i" */ + /** + * @ref Magnum::Complex "Complex". Usually used for + * @ref AnimationTrackTarget::Rotation2D. + */ + Complex, + /** * @ref Magnum::Quaternion "Quaternion". Usually used for * @ref AnimationTrackTarget::Rotation3D. @@ -114,7 +120,7 @@ enum class AnimationTrackTarget: UnsignedByte { * Modifies 2D object rotation. Type is usually * @ref Magnum::Complex "Complex". * - * @see @ref ObjectData2D::rotation() + * @see @ref AnimationTrackType::Complex, @ref ObjectData2D::rotation() */ Rotation2D, @@ -441,6 +447,7 @@ namespace Implementation { template<> constexpr AnimationTrackType animationTypeFor>() { return AnimationTrackType::Vector3i; } template<> constexpr AnimationTrackType animationTypeFor>() { return AnimationTrackType::Vector4i; } + template<> constexpr AnimationTrackType animationTypeFor() { return AnimationTrackType::Complex; } template<> constexpr AnimationTrackType animationTypeFor() { return AnimationTrackType::Quaternion; } template<> constexpr AnimationTrackType animationTypeFor() { return AnimationTrackType::DualQuaternion; } /* LCOV_EXCL_STOP */ From e997344b8c03986e383c819b67444b624dcd5362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 31 Aug 2018 20:45:39 +0200 Subject: [PATCH 09/26] Math: new CubicHermite class. For spline interpolation. --- doc/changelog.dox | 1 + doc/types.dox | 4 + src/Magnum/Magnum.h | 30 + src/Magnum/Math/CMakeLists.txt | 1 + src/Magnum/Math/Complex.h | 5 +- src/Magnum/Math/CubicHermite.h | 422 ++++++++++ src/Magnum/Math/Functions.h | 8 +- src/Magnum/Math/Math.h | 23 +- src/Magnum/Math/Quaternion.h | 5 +- src/Magnum/Math/Test/CMakeLists.txt | 2 + src/Magnum/Math/Test/CubicHermiteTest.cpp | 915 ++++++++++++++++++++++ src/Magnum/Math/instantiation.cpp | 12 + 12 files changed, 1416 insertions(+), 12 deletions(-) create mode 100644 src/Magnum/Math/CubicHermite.h create mode 100644 src/Magnum/Math/Test/CubicHermiteTest.cpp diff --git a/doc/changelog.dox b/doc/changelog.dox index f2ffda900..1c22b3fce 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -47,6 +47,7 @@ See also: @subsubsection changelog-latest-new-math Math library +- New @ref Math::CubicHermite class for cubic Hermite spline interpolation - Added @ref Math::Intersection::rangeFrustum(), @ref Math::Intersection::aabbFrustum(), @ref Math::Intersection::sphereFrustum(), diff --git a/doc/types.dox b/doc/types.dox index 317d5aad5..164ba457a 100644 --- a/doc/types.dox +++ b/doc/types.dox @@ -170,10 +170,14 @@ Other types, which don't have their GLSL equivalent, are: or @ref QuadraticBezier3Dd - @ref CubicBezier2D or @ref CubicBezier2Dd, @ref CubicBezier3D or @ref CubicBezier3Dd +- @ref CubicHermite1D or @ref CubicHermite1Dd, @ref CubicHermite2D or + @ref CubicHermite2Dd, @ref CubicHermite3D or @ref CubicHermite3Dd - @ref Complex or @ref Complexd, @ref DualComplex or @ref DualComplexd - @ref Frustum or @ref Frustumd - @ref Quaternion or @ref Quaterniond, @ref DualQuaternion or @ref DualQuaterniond +- @ref CubicHermiteComplex or @ref CubicHermiteComplexd +- @ref CubicHermiteQuaternion or @ref CubicHermiteQuaterniond - @ref Range1D / @ref Range2D / @ref Range3D, @ref Range1Di / @ref Range2Di / @ref Range3Di or @ref Range1Dd / @ref Range2Dd / @ref Range3Dd diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index 159fa87ae..ce2cd1e4b 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -456,6 +456,21 @@ typedef Math::CubicBezier2D CubicBezier2D; /** @brief Float three-dimensional cubic Bézier curve */ typedef Math::CubicBezier3D CubicBezier3D; +/** @brief Float scalar cubic Hermite spline point */ +typedef Math::CubicHermite1D CubicHermite1D; + +/** @brief Float two-dimensional cubic Hermite spline point */ +typedef Math::CubicHermite2D CubicHermite2D; + +/** @brief Float three-dimensional cubic Hermite spline point */ +typedef Math::CubicHermite3D CubicHermite3D; + +/** @brief Float cubic Hermite spline complex number */ +typedef Math::CubicHermiteComplex CubicHermiteComplex; + +/** @brief Float cubic Hermite spline quaternion */ +typedef Math::CubicHermiteQuaternion CubicHermiteQuaternion; + /** @brief Float complex number */ typedef Math::Complex Complex; @@ -641,6 +656,21 @@ typedef Math::CubicBezier2D CubicBezier2Dd; /** @brief Double three-dimensional cubic Bézier curve */ typedef Math::CubicBezier3D CubicBezier3Dd; +/** @brief Double scalar cubic Hermite spline point */ +typedef Math::CubicHermite1D CubicHermite1Dd; + +/** @brief Double two-dimensional cubic Hermite spline point */ +typedef Math::CubicHermite2D CubicHermite2Dd; + +/** @brief Double three-dimensional cubic Hermite spline point */ +typedef Math::CubicHermite3D CubicHermite3Dd; + +/** @brief Double cubic Hermite spline complex number */ +typedef Math::CubicHermiteComplex CubicHermiteComplexd; + +/** @brief Double cubic Hermite spline quaternion */ +typedef Math::CubicHermiteQuaternion CubicHermiteQuaterniond; + /** @brief Double complex number */ typedef Math::Complex Complexd; diff --git a/src/Magnum/Math/CMakeLists.txt b/src/Magnum/Math/CMakeLists.txt index 744599e03..b5510e744 100644 --- a/src/Magnum/Math/CMakeLists.txt +++ b/src/Magnum/Math/CMakeLists.txt @@ -30,6 +30,7 @@ set(MagnumMath_HEADERS Color.h Complex.h Constants.h + CubicHermite.h Distance.h Dual.h DualComplex.h diff --git a/src/Magnum/Math/Complex.h b/src/Magnum/Math/Complex.h index 9299826df..be0dc5d81 100644 --- a/src/Magnum/Math/Complex.h +++ b/src/Magnum/Math/Complex.h @@ -469,7 +469,10 @@ Expects that both complex numbers are normalized. @f[ @f] @see @ref Complex::isNormalized(), @ref slerp(const Complex&, const Complex&, T), @ref lerp(const Quaternion&, const Quaternion&, T), - @ref lerp(const T&, const T&, U) + @ref lerp(const T&, const T&, U), + @ref lerp(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) */ template inline Complex lerp(const Complex& normalizedA, const Complex& normalizedB, T t) { CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), diff --git a/src/Magnum/Math/CubicHermite.h b/src/Magnum/Math/CubicHermite.h new file mode 100644 index 000000000..e14d909e1 --- /dev/null +++ b/src/Magnum/Math/CubicHermite.h @@ -0,0 +1,422 @@ +#ifndef Magnum_Math_CubicHermiteSpline_h +#define Magnum_Math_CubicHermiteSpline_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + 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::CubicHermite, alias @ref Magnum::Math::CubicHermite2D, @ref Magnum::Math::CubicHermite3D, function @ref Magnum::Math::select(), @ref Magnum::Math::lerp(), @ref Magnum::Math::splerp() + */ + +#include "Magnum/Math/Complex.h" +#include "Magnum/Math/Quaternion.h" + +namespace Magnum { namespace Math { + +/** +@brief Cubic Hermite spline point + +Represents a point on a [cubic Hermite spline](https://en.wikipedia.org/wiki/Cubic_Hermite_spline). + +Unlike @ref Bezier, which describes a curve segment, this structure describes +a spline point @f$ \boldsymbol{p} @f$, with in-tangent @f$ \boldsymbol{m} @f$ +and out-tangent @f$ \boldsymbol{n} @f$. This form is more suitable for +animation keyframe representation. The structure assumes the in/out tangents +to be in their final form, i.e. already normalized by length of their adjacent +segments. + +@see @ref CubicHermite2D, @ref CubicHermite3D, + @ref Magnum::CubicHermite2D, @ref Magnum::CubicHermite2Dd, + @ref Magnum::CubicHermite3D, @ref Magnum::CubicHermite3Dd, + @ref CubicBezier +@experimental +*/ +template class CubicHermite { + public: + typedef T Type; /**< @brief Underlying data type */ + + /** + * @brief Default constructor + * + * Equivalent to @ref CubicHermite(ZeroInitT) for vector types and to + * @ref CubicHermite(IdentityInitT) for complex and quaternion types. + */ + constexpr /*implicit*/ CubicHermite() noexcept: CubicHermite{typename std::conditional::value, IdentityInitT, ZeroInitT>::type{typename std::conditional::value, IdentityInitT, ZeroInitT>::type::Init{}}} {} + + /** + * @brief Default constructor + * + * Construct cubic Hermite spline point with all control points being + * zero. + */ + constexpr explicit CubicHermite(ZeroInitT) noexcept: CubicHermite{ZeroInit, typename std::conditional::value, ZeroInitT*, void*>::type{}} {} + + /** + * @brief Identity constructor + * + * The @ref point() is constructed as identity in order to have + * interpolation working correctly; @ref inTangent() and + * @ref outTangent() is constructed as zero. Enabled only for complex + * and quaternion types. + */ + template::value>::type> constexpr explicit CubicHermite(IdentityInitT) noexcept: _inTangent{ZeroInit}, _point{IdentityInit}, _outTangent{ZeroInit} {} + + /** @brief Construct cubic Hermite spline point without initializing its contents */ + explicit CubicHermite(NoInitT) noexcept: CubicHermite{NoInit, typename std::conditional::value, NoInitT*, void*>::type{}} {} + + /** + * @brief Construct cubic Hermite spline point with given control points + * @param inTangent In-tangent @f$ \boldsymbol{m} @f$ + * @param point Point @f$ \boldsymbol{p} @f$ + * @param outTangent Out-tangent @f$ \boldsymbol{n} @f$ + */ + constexpr /*implicit*/ CubicHermite(const T& inTangent, const T& point, const T& outTangent) noexcept: _inTangent{inTangent}, _point{point}, _outTangent{outTangent} {} + + /** + * @brief Construct subic Hermite spline point from another of different type + * + * Performs only default casting on the values, no rounding or + * anything else. + */ + template constexpr explicit CubicHermite(const CubicHermite& other) noexcept: _inTangent{T(other._inTangent)}, _point{T(other._point)}, _outTangent{T(other._outTangent)} {} + + /** @brief Equality comparison */ + bool operator==(const CubicHermite& other) const; + + /** @brief Non-equality comparison */ + bool operator!=(const CubicHermite& other) const { + return !operator==(other); + } + + /** @brief In-tangent @f$ \boldsymbol{m} @f$ */ + T& inTangent() { return _inTangent; } + /* returns const& so [] operations are also constexpr */ + constexpr const T& inTangent() const { return _inTangent; } /**< @overload */ + + /** @brief Point @f$ \boldsymbol{p} @f$ */ + T& point() { return _point; } + /* returns const& so [] operations are also constexpr */ + constexpr const T& point() const { return _point; } /**< @overload */ + + /** @brief Out-tangent @f$ \boldsymbol{n} @f$ */ + T& outTangent() { return _outTangent; } + /* returns const& so [] operations are also constexpr */ + constexpr const T& outTangent() const { return _outTangent; } /**< @overload */ + + private: + template friend class CubicHermite; + + /* Called from CubicHermite(ZeroInit), either using the ZeroInit + constructor (if available) or passing zero directly (for scalar + types) */ + constexpr explicit CubicHermite(ZeroInitT, ZeroInitT*) noexcept: _inTangent{ZeroInit}, _point{ZeroInit}, _outTangent{ZeroInit} {} + constexpr explicit CubicHermite(ZeroInitT, void*) noexcept: _inTangent{T(0)}, _point{T(0)}, _outTangent{T(0)} {} + + /* Called from CubicHermite(NoInit), either using the NoInit + constructor (if available) or not doing oanything */ + explicit CubicHermite(NoInitT, NoInitT*) noexcept: _inTangent{NoInit}, _point{NoInit}, _outTangent{NoInit} {} + explicit CubicHermite(NoInitT, void*) noexcept {} + + T _inTangent; + T _point; + T _outTangent; +}; + +/** +@brief Scalar cubic Hermite spline point + +Convenience alternative to @cpp CubicHermite @ce. See @ref CubicHermite for +more information. +@see @ref CubicHermite2D, @ref CubicHermite3D, @ref CubicHermiteComplex, + @ref CubicHermiteQuaternion, @ref Magnum::CubicHermite1D, + @ref Magnum::CubicHermite1Dd +*/ +#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ +template using CubicHermite1D = CubicHermite; +#endif + +/** +@brief Two-dimensional cubic Hermite spline point + +Convenience alternative to @cpp CubicHermite> @ce. See +@ref CubicHermite for more information. +@see @ref CubicHermite1D, @ref CubicHermite3D, @ref CubicHermiteComplex, + @ref CubicHermiteQuaternion, @ref Magnum::CubicHermite2D, + @ref Magnum::CubicHermite2Dd +*/ +#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ +template using CubicHermite2D = CubicHermite>; +#endif + +/** +@brief Three-dimensional cubic Hermite spline point + +Convenience alternative to @cpp CubicHermite> @ce. See +@ref CubicHermite for more information. +@see @ref CubicHermite1D, @ref CubicHermite2D, @ref CubicHermiteComplex, + @ref CubicHermiteQuaternion, @ref Magnum::CubicHermite3D, + @ref Magnum::CubicHermite3Dd +*/ +#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ +template using CubicHermite3D = CubicHermite>; +#endif + +/** +@brief Cubic Hermite spline complex number + +Convenience alternative to @cpp CubicHermite> @ce. See +@ref CubicHermite for more information. +@see @ref CubicHermite1D, @ref CubicHermite2D, @ref CubicHermite3D, + @ref CubicHermiteQuaternion, @ref Magnum::CubicHermiteComplex, + @ref Magnum::CubicHermiteComplexd +*/ +#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ +template using CubicHermiteComplex = CubicHermite>; +#endif + +/** +@brief Cubic Hermite spline quaternion + +Convenience alternative to @cpp CubicHermite> @ce. See +@ref CubicHermite for more information. +@see @ref CubicHermite1D, @ref CubicHermite2D, @ref CubicHermite3D, + @ref CubicHermiteComplex, @ref Magnum::CubicHermiteQuaternion, + @ref Magnum::CubicHermiteQuaterniond +*/ +#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ +template using CubicHermiteQuaternion = CubicHermite>; +#endif + +/** @debugoperator{CubicHermite} */ +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, const CubicHermite& value) { + return debug << "CubicHermite(" << Corrade::Utility::Debug::nospace + << value.inTangent() << Corrade::Utility::Debug::nospace << "," + << value.point() << Corrade::Utility::Debug::nospace << "," + << value.outTangent() << Corrade::Utility::Debug::nospace << ")"; +} + +/* Explicit instantiation for commonly used types */ +#ifndef DOXYGEN_GENERATING_OUTPUT +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +#endif + +/** @relatesalso CubicHermite +@brief Constant interpolation of two cubic Hermite spline points +@param a First value +@param b Second value +@param t Interpolation phase + +Given segment points @f$ \boldsymbol{p}_i @f$, in-tangents @f$ \boldsymbol{m}_i @f$ +and out-tangents @f$ \boldsymbol{n}_i @f$, the interpolated value @f$ \boldsymbol{p} @f$ +at phase @f$ t @f$ is: @f[ + \boldsymbol{p}(t) = \begin{cases} + \boldsymbol{p}_a, & t < 1 \\ + \boldsymbol{p}_b, & t \ge 1 + \end{cases} +@f] + +Equivalent to calling @ref select(const T&, const T&, U) on +@ref CubicHermite::point() extracted from both @p a and @p b. +@see @ref lerp(const CubicHermite&, const CubicHermite&, U), + @ref splerp(const CubicHermite&, const CubicHermite&, U) +*/ +template T select(const CubicHermite& a, const CubicHermite& b, U t) { + /* Not using select() from Functions.h to avoid the header dependency */ + return t < U(1) ? a.point() : b.point(); +} + +/** @relatesalso CubicHermite +@brief Linear interpolation of two cubic Hermite points +@param a First spline point +@param b Second spline point +@param t Interpolation phase + +Given segment points @f$ \boldsymbol{p}_i @f$, in-tangents @f$ \boldsymbol{m}_i @f$ +and out-tangents @f$ \boldsymbol{n}_i @f$, the interpolated value @f$ \boldsymbol{p} @f$ +at phase @f$ t @f$ is: @f[ + \boldsymbol{p}(t) = (1 - t) \boldsymbol{p}_a + t \boldsymbol{p}_b +@f] + +Equivalent to calling @ref lerp(const T&, const T&, U) on +@ref CubicHermite::point() extracted from both @p a and @p b. +@see @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref splerp(const CubicHermite&, const CubicHermite&, U), + @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) +*/ +template T lerp(const CubicHermite& a, const CubicHermite& b, U t) { + /* To avoid dependency on Functions.h */ + return Implementation::lerp(a.point(), b.point(), t); +} + +/** @relatesalso CubicHermite +@brief Linear interpolation of two cubic Hermite complex numbers + +Unlike @ref lerp(const CubicHermite&, const CubicHermite&, U) this adds +a normalization step after. Given segment points @f$ \boldsymbol{p}_i @f$, +in-tangents @f$ \boldsymbol{m}_i @f$ and out-tangents @f$ \boldsymbol{n}_i @f$, +the interpolated value @f$ \boldsymbol{p} @f$ at phase @f$ t @f$ is: @f[ + \boldsymbol{p}(t) = \frac{(1 - t) \boldsymbol{p}_a + t \boldsymbol{p}_b}{|(1 - t) \boldsymbol{p}_a + t \boldsymbol{p}_b|} +@f] + +Equivalent to calling @ref lerp(const Complex&, const Complex&, T) on +@ref CubicHermite::point() extracted from @p a and @p b. Expects that +@ref CubicHermite::point() is a normalized complex number in both @p a and @p b. +@see @ref Complex::isNormalized(), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) +*/ +template Complex lerp(const CubicHermiteComplex& a, const CubicHermiteComplex& b, T t) { + return lerp(a.point(), b.point(), t); +} + +/** @relatesalso CubicHermite +@brief Linear interpolation of two cubic Hermite quaternions + +Unlike @ref lerp(const CubicHermite&, const CubicHermite&, U) this adds a +normalization step after. Given segment points @f$ \boldsymbol{p}_i @f$, +in-tangents @f$ \boldsymbol{m}_i @f$ and out-tangents @f$ \boldsymbol{n}_i @f$, +the interpolated value @f$ \boldsymbol{p} @f$ at phase @f$ t @f$ is: @f[ + \boldsymbol{p}(t) = \frac{(1 - t) \boldsymbol{p}_a + t \boldsymbol{p}_b}{|(1 - t) \boldsymbol{p}_a + t \boldsymbol{p}_b|} +@f] + +Equivalent to calling @ref lerp(const Quaternion&, const Quaternion&, T) +on @ref CubicHermite::point() extracted from @p a and @p b. Expects that +@ref CubicHermite::point() is a normalized quaternion in both @p a and @p b. +@see @ref Quaternion::isNormalized(), + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) +*/ +template Quaternion lerp(const CubicHermiteQuaternion& a, const CubicHermiteQuaternion& b, T t) { + return lerp(a.point(), b.point(), t); +} + +/** @relatesalso CubicHermite +@brief Spline interpolation of two cubic Hermite points +@param a First spline point +@param b Second spline point +@param t Interpolation phase + +Given segment points @f$ \boldsymbol{p}_i @f$, in-tangents @f$ \boldsymbol{m}_i @f$ +and out-tangents @f$ \boldsymbol{n}_i @f$, the interpolated value @f$ \boldsymbol{p} @f$ +at phase @f$ t @f$ is: @f[ + \boldsymbol{p}(t) = (2 t^3 - 3 t^2 + 1) \boldsymbol{p}_a + (t^3 - 2 t^2 + t) \boldsymbol{n}_a + (-2 t^3 + 3 t^2) \boldsymbol{p}_b + (t^3 - t^2)\boldsymbol{m}_b +@f] + +@see @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermite&, const CubicHermite&, U), +*/ +template T splerp(const CubicHermite& a, const CubicHermite& b, U t) { + return (U(2)*t*t*t - U(3)*t*t + U(1))*a.point() + + (t*t*t - U(2)*t*t + t)*a.outTangent() + + (U(-2)*t*t*t + U(3)*t*t)*b.point() + + (t*t*t - t*t)*b.inTangent(); +} + +/** @relatesalso CubicHermite +@brief Spline interpolation of two cubic Hermite complex numbers + +Unlike @ref splerp(const CubicHermite&, const CubicHermite&, U) this adds +a normalization step after. Given segment points @f$ \boldsymbol{p}_i @f$, +in-tangents @f$ \boldsymbol{m}_i @f$ and out-tangents @f$ \boldsymbol{n}_i @f$, +the interpolated value @f$ \boldsymbol{p} @f$ at phase @f$ t @f$ is: @f[ + \begin{array}{rcl} + \boldsymbol{p'}(t) & = & (2 t^3 - 3 t^2 + 1) \boldsymbol{p}_a + (t^3 - 2 t^2 + t) \boldsymbol{n}_a + (-2 t^3 + 3 t^2) \boldsymbol{p}_b + (t^3 - t^2)\boldsymbol{m}_b \\ + \boldsymbol{p}(t) & = & \cfrac{\boldsymbol{p'}(t)}{|\boldsymbol{p'}(t)|} + \end{array} +@f] + +Expects that @ref CubicHermite::point() is a normalized complex number in both +@p a and @p b. +@see @ref Complex::isNormalized(), + @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) +*/ +template Complex splerp(const CubicHermiteComplex& a, const CubicHermiteComplex& b, T t) { + CORRADE_ASSERT(a.point().isNormalized() && b.point().isNormalized(), + "Math::splerp(): complex spline points must be normalized", {}); + return ((T(2)*t*t*t - T(3)*t*t + T(1))*a.point() + + (t*t*t - T(2)*t*t + t)*a.outTangent() + + (T(-2)*t*t*t + T(3)*t*t)*b.point() + + (t*t*t - t*t)*b.inTangent()).normalized(); +} + +/** @relatesalso CubicHermite +@brief Spline interpolation of two cubic Hermite quaternions + +Unlike @ref splerp(const CubicHermite&, const CubicHermite&, U) this adds +a normalization step after. Given segment points @f$ \boldsymbol{p}_i @f$, +in-tangents @f$ \boldsymbol{m}_i @f$ and out-tangents @f$ \boldsymbol{n}_i @f$, +the interpolated value @f$ \boldsymbol{p} @f$ at phase @f$ t @f$ is: @f[ + \begin{array}{rcl} + \boldsymbol{p'}(t) & = & (2 t^3 - 3 t^2 + 1) \boldsymbol{p}_a + (t^3 - 2 t^2 + t) \boldsymbol{n}_a + (-2 t^3 + 3 t^2) \boldsymbol{p}_b + (t^3 - t^2)\boldsymbol{m}_b \\ + \boldsymbol{p}(t) & = & \cfrac{\boldsymbol{p'}(t)}{|\boldsymbol{p'}(t)|} + \end{array} +@f] + +Expects that @ref CubicHermite::point() is a normalized quaternion in both @p a +and @p b. +@see @ref Quaternion::isNormalized(), + @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) +*/ +template Quaternion splerp(const CubicHermiteQuaternion& a, const CubicHermiteQuaternion& b, T t) { + CORRADE_ASSERT(a.point().isNormalized() && b.point().isNormalized(), + "Math::splerp(): quaternion spline points must be normalized", {}); + return ((T(2)*t*t*t - T(3)*t*t + T(1))*a.point() + + (t*t*t - T(2)*t*t + t)*a.outTangent() + + (T(-2)*t*t*t + T(3)*t*t)*b.point() + + (t*t*t - t*t)*b.inTangent()).normalized(); +} + +template inline bool CubicHermite::operator==(const CubicHermite& other) const { + /* For non-scalar types default implementation of TypeTraits would be used, + which is just operator== */ + return TypeTraits::equals(_inTangent, other._inTangent) && + TypeTraits::equals(_point, other._point) && + TypeTraits::equals(_outTangent, other._outTangent); +} + +}} + +#endif diff --git a/src/Magnum/Math/Functions.h b/src/Magnum/Math/Functions.h index 0612b91c0..9909406b9 100644 --- a/src/Magnum/Math/Functions.h +++ b/src/Magnum/Math/Functions.h @@ -559,9 +559,13 @@ The interpolation for vectors is done as in following, similarly for scalars: @f \boldsymbol{v_{LERP}} = (1 - t) \boldsymbol{v_A} + t \boldsymbol{v_B} @f] -See @ref select() for constant interpolation using the same API. +See @ref select() for constant interpolation using the same API and +@ref splerp() for spline interpolation. @see @ref lerpInverted(), @ref lerp(const Complex&, const Complex&, T), - @ref lerp(const Quaternion&, const Quaternion&, T) + @ref lerp(const Quaternion&, const Quaternion&, T), + @ref lerp(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) @m_keyword{mix(),GLSL mix(),} */ template inline diff --git a/src/Magnum/Math/Math.h b/src/Magnum/Math/Math.h index 6b432f932..e721e2c95 100644 --- a/src/Magnum/Math/Math.h +++ b/src/Magnum/Math/Math.h @@ -43,14 +43,6 @@ template class BoolVector; /* Class Constants used only statically */ -template class Bezier; -template using QuadraticBezier = Bezier<2, dimensions, T>; -template using CubicBezier = Bezier<3, dimensions, T>; -template using QuadraticBezier2D = QuadraticBezier<2, T>; -template using QuadraticBezier3D = QuadraticBezier<3, T>; -template using CubicBezier2D = CubicBezier<2, T>; -template using CubicBezier3D = CubicBezier<3, T>; - template class Complex; template class Dual; template class DualComplex; @@ -90,6 +82,21 @@ template class Vector4; template class Color3; template class Color4; +template class Bezier; +template using QuadraticBezier = Bezier<2, dimensions, T>; +template using CubicBezier = Bezier<3, dimensions, T>; +template using QuadraticBezier2D = QuadraticBezier<2, T>; +template using QuadraticBezier3D = QuadraticBezier<3, T>; +template using CubicBezier2D = CubicBezier<2, T>; +template using CubicBezier3D = CubicBezier<3, T>; + +template class CubicHermite; +template using CubicHermite1D = CubicHermite; +template using CubicHermite2D = CubicHermite>; +template using CubicHermite3D = CubicHermite>; +template using CubicHermiteComplex = CubicHermite>; +template using CubicHermiteQuaternion = CubicHermite>; + template class Range; template using Range1D = Range<1, T>; template class Range2D; diff --git a/src/Magnum/Math/Quaternion.h b/src/Magnum/Math/Quaternion.h index 2256104a4..8917a5549 100644 --- a/src/Magnum/Math/Quaternion.h +++ b/src/Magnum/Math/Quaternion.h @@ -90,7 +90,10 @@ Expects that both quaternions are normalized. @f[ @see @ref Quaternion::isNormalized(), @ref slerp(const Quaternion&, const Quaternion&, T), @ref sclerp(), @ref lerp(const T&, const T&, U), - @ref lerp(const Complex&, const Complex&, T) + @ref lerp(const Complex&, const Complex&, T), + @ref lerp(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) */ template inline Quaternion lerp(const Quaternion& normalizedA, const Quaternion& normalizedB, T t) { CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), diff --git a/src/Magnum/Math/Test/CMakeLists.txt b/src/Magnum/Math/Test/CMakeLists.txt index 9563699da..4b5a7d4c4 100644 --- a/src/Magnum/Math/Test/CMakeLists.txt +++ b/src/Magnum/Math/Test/CMakeLists.txt @@ -54,6 +54,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(MathCubicHermiteTest CubicHermiteTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathFrustumTest FrustumTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathDistanceTest DistanceTest.cpp LIBRARIES MagnumMathTestLib) @@ -66,6 +67,7 @@ set_property(TARGET MathMatrix3Test MathMatrix4Test MathComplexTest + MathCubicHermiteTest MathDualComplexTest MathQuaternionTest MathDualQuaternionTest diff --git a/src/Magnum/Math/Test/CubicHermiteTest.cpp b/src/Magnum/Math/Test/CubicHermiteTest.cpp new file mode 100644 index 000000000..ea3fd5213 --- /dev/null +++ b/src/Magnum/Math/Test/CubicHermiteTest.cpp @@ -0,0 +1,915 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + 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 "Magnum/Math/CubicHermite.h" +#include "Magnum/Math/Functions.h" +#include "Magnum/Math/Vector2.h" + +namespace Magnum { namespace Math { namespace Test { + +struct CubicHermiteTest: Corrade::TestSuite::Tester { + explicit CubicHermiteTest(); + + void constructScalar(); + void constructVector(); + void constructComplex(); + void constructQuaternion(); + + void constructDefaultScalar(); + void constructDefaultVector(); + void constructDefaultComplex(); + void constructDefaultQuaternion(); + + void constructZeroScalar(); + void constructZeroVector(); + void constructZeroComplex(); + void constructZeroQuaternion(); + + void constructIdentityScalar(); + void constructIdentityVector(); + void constructIdentityComplex(); + void constructIdentityQuaternion(); + + void constructNoInitScalar(); + void constructNoInitVector(); + void constructNoInitComplex(); + void constructNoInitQuaternion(); + + void constructConversionScalar(); + void constructConversionVector(); + void constructConversionComplex(); + void constructConversionQuaternion(); + + void constructCopyScalar(); + void constructCopyVector(); + void constructCopyComplex(); + void constructCopyQuaternion(); + + void dataScalar(); + void dataVector(); + void dataComplex(); + void dataQuaternion(); + + void compareScalar(); + void compareVector(); + void compareComplex(); + void compareQuaternion(); + + void selectScalar(); + void selectVector(); + void selectComplex(); + void selectQuaternion(); + + void lerpScalar(); + void lerpVector(); + void lerpComplex(); + void lerpComplexNotNormalized(); + void lerpQuaternion(); + void lerpQuaternionNotNormalized(); + + void splerpScalar(); + void splerpVector(); + void splerpComplex(); + void splerpComplexNotNormalized(); + void splerpQuaternion(); + void splerpQuaternionNotNormalized(); + + void debugScalar(); + void debugVector(); + void debugComplex(); + void debugQuaternion(); +}; + +CubicHermiteTest::CubicHermiteTest() { + addTests({&CubicHermiteTest::constructScalar, + &CubicHermiteTest::constructVector, + &CubicHermiteTest::constructComplex, + &CubicHermiteTest::constructQuaternion, + + &CubicHermiteTest::constructDefaultScalar, + &CubicHermiteTest::constructDefaultVector, + &CubicHermiteTest::constructDefaultComplex, + &CubicHermiteTest::constructDefaultQuaternion, + + &CubicHermiteTest::constructZeroScalar, + &CubicHermiteTest::constructZeroVector, + &CubicHermiteTest::constructZeroComplex, + &CubicHermiteTest::constructZeroQuaternion, + + &CubicHermiteTest::constructIdentityScalar, + &CubicHermiteTest::constructIdentityVector, + &CubicHermiteTest::constructIdentityComplex, + &CubicHermiteTest::constructIdentityQuaternion, + + &CubicHermiteTest::constructNoInitScalar, + &CubicHermiteTest::constructNoInitVector, + &CubicHermiteTest::constructNoInitComplex, + &CubicHermiteTest::constructNoInitQuaternion, + + &CubicHermiteTest::constructConversionScalar, + &CubicHermiteTest::constructConversionVector, + &CubicHermiteTest::constructConversionComplex, + &CubicHermiteTest::constructConversionQuaternion, + + &CubicHermiteTest::constructCopyScalar, + &CubicHermiteTest::constructCopyVector, + &CubicHermiteTest::constructCopyComplex, + &CubicHermiteTest::constructCopyQuaternion, + + &CubicHermiteTest::dataScalar, + &CubicHermiteTest::dataVector, + &CubicHermiteTest::dataComplex, + &CubicHermiteTest::dataQuaternion, + + &CubicHermiteTest::compareScalar, + &CubicHermiteTest::compareVector, + &CubicHermiteTest::compareComplex, + &CubicHermiteTest::compareQuaternion, + + &CubicHermiteTest::selectScalar, + &CubicHermiteTest::selectVector, + &CubicHermiteTest::selectComplex, + &CubicHermiteTest::selectQuaternion, + + &CubicHermiteTest::lerpScalar, + &CubicHermiteTest::lerpVector, + &CubicHermiteTest::lerpComplex, + &CubicHermiteTest::lerpComplexNotNormalized, + &CubicHermiteTest::lerpQuaternion, + &CubicHermiteTest::lerpQuaternionNotNormalized, + + &CubicHermiteTest::splerpScalar, + &CubicHermiteTest::splerpVector, + &CubicHermiteTest::splerpComplex, + &CubicHermiteTest::splerpComplexNotNormalized, + &CubicHermiteTest::splerpQuaternion, + &CubicHermiteTest::splerpQuaternionNotNormalized, + + &CubicHermiteTest::debugScalar, + &CubicHermiteTest::debugVector, + &CubicHermiteTest::debugComplex, + &CubicHermiteTest::debugQuaternion}); +} + +typedef Math::Vector2 Vector2; +typedef Math::Complex Complex; +typedef Math::Quaternion Quaternion; +typedef Math::CubicHermite1D CubicHermite1D; +typedef Math::CubicHermite2D CubicHermite2D; +typedef Math::CubicHermiteComplex CubicHermiteComplex; +typedef Math::CubicHermiteQuaternion CubicHermiteQuaternion; + +void CubicHermiteTest::constructScalar() { + constexpr CubicHermite1D a{2.0f, -2.0f, -0.5f}; + CubicHermite1D b{2.0f, -2.0f, -0.5f}; + + CORRADE_COMPARE(a.inTangent(), 2.0f); + CORRADE_COMPARE(b.inTangent(), 2.0f); + CORRADE_COMPARE(a.point(), -2.0f); + CORRADE_COMPARE(b.point(), -2.0f); + CORRADE_COMPARE(a.outTangent(), -0.5f); + CORRADE_COMPARE(b.outTangent(), -0.5f); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructVector() { + constexpr CubicHermite2D a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + CubicHermite2D b{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + + CORRADE_COMPARE(a.inTangent(), (Vector2{1.0f, 2.0f})); + CORRADE_COMPARE(b.inTangent(), (Vector2{1.0f, 2.0f})); + CORRADE_COMPARE(a.point(), (Vector2{1.5f, -2.0f})); + CORRADE_COMPARE(b.point(), (Vector2{1.5f, -2.0f})); + CORRADE_COMPARE(a.outTangent(), (Vector2{3.0f, -0.5f})); + CORRADE_COMPARE(b.outTangent(), (Vector2{3.0f, -0.5f})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructComplex() { + constexpr CubicHermiteComplex a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + CubicHermiteComplex b{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + + CORRADE_COMPARE(a.inTangent(), (Complex{1.0f, 2.0f})); + CORRADE_COMPARE(b.inTangent(), (Complex{1.0f, 2.0f})); + CORRADE_COMPARE(a.point(), (Complex{1.5f, -2.0f})); + CORRADE_COMPARE(b.point(), (Complex{1.5f, -2.0f})); + CORRADE_COMPARE(a.outTangent(), (Complex{3.0f, -0.5f})); + CORRADE_COMPARE(b.outTangent(), (Complex{3.0f, -0.5f})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructQuaternion() { + constexpr CubicHermiteQuaternion a{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + CubicHermiteQuaternion b{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + + CORRADE_COMPARE(a.inTangent(), (Quaternion{{1.0f, 2.0f, -1.0f}, 3.0f})); + CORRADE_COMPARE(b.inTangent(), (Quaternion{{1.0f, 2.0f, -1.0f}, 3.0f})); + CORRADE_COMPARE(a.point(), (Quaternion{{1.5f, -2.0f, 0.1f}, 1.1f})); + CORRADE_COMPARE(b.point(), (Quaternion{{1.5f, -2.0f, 0.1f}, 1.1f})); + CORRADE_COMPARE(a.outTangent(), (Quaternion{{3.0f, -0.5f, 1.2f}, 0.3f})); + CORRADE_COMPARE(b.outTangent(), (Quaternion{{3.0f, -0.5f, 1.2f}, 0.3f})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructDefaultScalar() { + constexpr CubicHermite1D a; + CubicHermite1D b; + + /* Equivalent to ZeroInit constructor */ + CORRADE_COMPARE(a, (CubicHermite1D{0.0f, 0.0f, 0.0f})); + CORRADE_COMPARE(b, (CubicHermite1D{0.0f, 0.0f, 0.0f})); + + CORRADE_VERIFY((std::is_nothrow_default_constructible::value)); +} + +void CubicHermiteTest::constructDefaultVector() { + constexpr CubicHermite2D a; + CubicHermite2D b; + + /* Equivalent to ZeroInit constructor */ + CORRADE_COMPARE(a, (CubicHermite2D{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermite2D{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_default_constructible::value)); +} + +void CubicHermiteTest::constructDefaultComplex() { + constexpr CubicHermiteComplex a; + CubicHermiteComplex b; + + /* Equivalent to IdentityInit constructor */ + CORRADE_COMPARE(a, (CubicHermiteComplex{{0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteComplex{{0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_default_constructible::value)); +} + +void CubicHermiteTest::constructDefaultQuaternion() { + constexpr CubicHermiteQuaternion a; + CubicHermiteQuaternion b; + + /* Equivalent to IdentityInit constructor */ + CORRADE_COMPARE(a, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 1.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 1.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_default_constructible::value)); +} + +void CubicHermiteTest::constructZeroScalar() { + constexpr CubicHermite1D a{ZeroInit}; + CubicHermite1D b{ZeroInit}; + + CORRADE_COMPARE(a, (CubicHermite1D{0.0f, 0.0f, 0.0f})); + CORRADE_COMPARE(b, (CubicHermite1D{0.0f, 0.0f, 0.0f})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructZeroVector() { + constexpr CubicHermite2D a{ZeroInit}; + CubicHermite2D b{ZeroInit}; + + CORRADE_COMPARE(a, (CubicHermite2D{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermite2D{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructZeroComplex() { + constexpr CubicHermiteComplex a{ZeroInit}; + CubicHermiteComplex b{ZeroInit}; + + CORRADE_COMPARE(a, (CubicHermiteComplex{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteComplex{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructZeroQuaternion() { + constexpr CubicHermiteQuaternion a{ZeroInit}; + CubicHermiteQuaternion b{ZeroInit}; + + CORRADE_COMPARE(a, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructIdentityScalar() { + CORRADE_VERIFY(!(std::is_constructible::value)); +} + +void CubicHermiteTest::constructIdentityVector() { + CORRADE_VERIFY(!(std::is_constructible::value)); +} + +void CubicHermiteTest::constructIdentityComplex() { + constexpr CubicHermiteComplex a{IdentityInit}; + CubicHermiteComplex b{IdentityInit}; + + CORRADE_COMPARE(a, (CubicHermiteComplex{{0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteComplex{{0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructIdentityQuaternion() { + constexpr CubicHermiteQuaternion a{IdentityInit}; + CubicHermiteQuaternion b{IdentityInit}; + + CORRADE_COMPARE(a, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 1.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 1.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructNoInitScalar() { + CubicHermite1D spline{2.0f, -2.0f, -0.5f}; + new(&spline) CubicHermite1D{NoInit}; + + CORRADE_COMPARE(spline, (CubicHermite1D{2.0f, -2.0f, -0.5f})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void CubicHermiteTest::constructNoInitVector() { + CubicHermite2D spline{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + new(&spline) CubicHermite2D{NoInit}; + + CORRADE_COMPARE(spline, (CubicHermite2D{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void CubicHermiteTest::constructNoInitComplex() { + CubicHermiteComplex spline{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + new(&spline) CubicHermiteComplex{NoInit}; + + CORRADE_COMPARE(spline, (CubicHermiteComplex{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void CubicHermiteTest::constructNoInitQuaternion() { + CubicHermiteQuaternion spline{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + new(&spline) CubicHermiteQuaternion{NoInit}; + + CORRADE_COMPARE(spline, (CubicHermiteQuaternion{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void CubicHermiteTest::constructConversionScalar() { + typedef Math::CubicHermite1D CubicHermite1Di; + + constexpr CubicHermite1D a{2.0f, -2.0f, -0.5f}; + constexpr CubicHermite1Di b{a}; + CubicHermite1Di c{a}; + CORRADE_COMPARE(b, (CubicHermite1Di{2, -2, 0})); + CORRADE_COMPARE(c, (CubicHermite1Di{2, -2, 0})); + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructConversionVector() { + typedef Math::CubicHermite2D CubicHermite2Di; + + constexpr CubicHermite2D a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr CubicHermite2Di b{a}; + CubicHermite2Di c{a}; + CORRADE_COMPARE(b, (CubicHermite2Di{{1, 2}, {1, -2}, {3, 0}})); + CORRADE_COMPARE(c, (CubicHermite2Di{{1, 2}, {1, -2}, {3, 0}})); + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructConversionComplex() { + typedef Math::CubicHermiteComplex CubicHermiteComplexi; + + constexpr CubicHermiteComplex a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr CubicHermiteComplexi b{a}; + CubicHermiteComplexi c{a}; + CORRADE_COMPARE(b, (CubicHermiteComplexi{{1, 2}, {1, -2}, {3, 0}})); + CORRADE_COMPARE(c, (CubicHermiteComplexi{{1, 2}, {1, -2}, {3, 0}})); + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructConversionQuaternion() { + typedef Math::CubicHermiteQuaternion CubicHermiteQuaternioni; + + constexpr CubicHermiteQuaternion a{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + constexpr CubicHermiteQuaternioni b{a}; + CubicHermiteQuaternioni c{a}; + CORRADE_COMPARE(b, (CubicHermiteQuaternioni{ + {{1, 2, -1}, 3}, + {{1, -2, 0}, 1}, + {{3, 0, 1}, 0}})); + CORRADE_COMPARE(c, (CubicHermiteQuaternioni{ + {{1, 2, -1}, 3}, + {{1, -2, 0}, 1}, + {{3, 0, 1}, 0}})); + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructCopyScalar() { + constexpr CubicHermite1D a{2.0f, -2.0f, -0.5f}; + constexpr CubicHermite1D b{a}; + + CORRADE_COMPARE(b, (CubicHermite1D{2.0f, -2.0f, -0.5f})); + + CORRADE_VERIFY(std::is_nothrow_copy_constructible::value); + CORRADE_VERIFY(std::is_nothrow_copy_assignable::value); +} + +void CubicHermiteTest::constructCopyVector() { + constexpr CubicHermite2D a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr CubicHermite2D b{a}; + + CORRADE_COMPARE(b, (CubicHermite2D{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}})); + + CORRADE_VERIFY(std::is_nothrow_copy_constructible::value); + CORRADE_VERIFY(std::is_nothrow_copy_assignable::value); +} + +void CubicHermiteTest::constructCopyComplex() { + constexpr CubicHermiteComplex a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr CubicHermiteComplex b{a}; + + CORRADE_COMPARE(b, (CubicHermiteComplex{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}})); + + CORRADE_VERIFY(std::is_nothrow_copy_constructible::value); + CORRADE_VERIFY(std::is_nothrow_copy_assignable::value); +} + +void CubicHermiteTest::constructCopyQuaternion() { + constexpr CubicHermiteQuaternion a{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + constexpr CubicHermiteQuaternion b{a}; + + CORRADE_COMPARE(b, (CubicHermiteQuaternion{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}})); + + CORRADE_VERIFY(std::is_nothrow_copy_constructible::value); + CORRADE_VERIFY(std::is_nothrow_copy_assignable::value); +} + +void CubicHermiteTest::dataScalar() { + constexpr CubicHermite1D ca{2.0f, -2.0f, -0.5f}; + constexpr Float inTangent = ca.inTangent(); + constexpr Float point = ca.point(); + constexpr Float outTangent = ca.outTangent(); + CORRADE_COMPARE(inTangent, 2.0f); + CORRADE_COMPARE(point, -2.0f); + CORRADE_COMPARE(outTangent, -0.5f); + + CubicHermite1D a{2.0f, -2.0f, -0.5f}; + a.inTangent() = 3.0f; + a.point() = 1.0f; + a.outTangent() = 2.0f; + CORRADE_COMPARE(a, (CubicHermite1D{3.0f, 1.0f, 2.0f})); +} + +void CubicHermiteTest::dataVector() { + constexpr CubicHermite2D ca{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr Vector2 inTangent = ca.inTangent(); + constexpr Vector2 point = ca.point(); + constexpr Vector2 outTangent = ca.outTangent(); + CORRADE_COMPARE(inTangent, (Vector2{1.0f, 2.0f})); + CORRADE_COMPARE(point, (Vector2{1.5f, -2.0f})); + CORRADE_COMPARE(outTangent, (Vector2{3.0f, -0.5f})); + + CubicHermite2D a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + a.inTangent().y() = 3.0f; + a.point().x() = 1.0f; + a.outTangent().y() = 2.0f; + CORRADE_COMPARE(a, (CubicHermite2D{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}})); +} + +void CubicHermiteTest::dataComplex() { + constexpr CubicHermiteComplex ca{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr Complex inTangent = ca.inTangent(); + constexpr Complex point = ca.point(); + constexpr Complex outTangent = ca.outTangent(); + CORRADE_COMPARE(inTangent, (Complex{1.0f, 2.0f})); + CORRADE_COMPARE(point, (Complex{1.5f, -2.0f})); + CORRADE_COMPARE(outTangent, (Complex{3.0f, -0.5f})); + + CubicHermiteComplex a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + a.inTangent().imaginary() = 3.0f; + a.point().real() = 1.0f; + a.outTangent().imaginary() = 2.0f; + CORRADE_COMPARE(a, (CubicHermiteComplex{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}})); +} + +void CubicHermiteTest::dataQuaternion() { + constexpr CubicHermiteQuaternion ca{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + constexpr Quaternion inTangent = ca.inTangent(); + constexpr Quaternion point = ca.point(); + constexpr Quaternion outTangent = ca.outTangent(); + + CORRADE_COMPARE(inTangent, (Quaternion{{1.0f, 2.0f, -1.0f}, 3.0f})); + CORRADE_COMPARE(point, (Quaternion{{1.5f, -2.0f, 0.1f}, 1.1f})); + CORRADE_COMPARE(outTangent, (Quaternion{{3.0f, -0.5f, 1.2f}, 0.3f})); + + CubicHermiteQuaternion a{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + a.inTangent().vector().y() = 3.0f; + a.point().scalar() = 1.0f; + a.outTangent().vector().z() = 2.0f; + CORRADE_COMPARE(a, (CubicHermiteQuaternion{ + {{1.0f, 3.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.0f}, + {{3.0f, -0.5f, 2.0f}, 0.3f}})); +} + +void CubicHermiteTest::compareScalar() { + CORRADE_VERIFY((CubicHermite1D{3.0f, 1.0f, 2.0f} == CubicHermite1D{3.0f, 1.0f + TypeTraits::epsilon()/2, 2.0f})); + CORRADE_VERIFY((CubicHermite1D{3.0f, 1.0f, 2.0f} != CubicHermite1D{3.0f + TypeTraits::epsilon()*6, 1.0f, 2.0f})); +} + +void CubicHermiteTest::compareVector() { + CORRADE_VERIFY((CubicHermite2D{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}}) == (CubicHermite2D{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f + TypeTraits::epsilon()/2}})); + CORRADE_VERIFY((CubicHermite2D{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}}) != (CubicHermite2D{{1.0f + TypeTraits::epsilon()*2, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}})); +} + +void CubicHermiteTest::compareComplex() { + CORRADE_VERIFY((CubicHermiteComplex{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}}) == (CubicHermiteComplex{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f + TypeTraits::epsilon()/2}})); + CORRADE_VERIFY((CubicHermiteComplex{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}}) != (CubicHermiteComplex{{1.0f + TypeTraits::epsilon()*2, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}})); +} + +void CubicHermiteTest::compareQuaternion() { + CORRADE_VERIFY((CubicHermiteQuaternion{ + {{1.0f, 3.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.0f}, + {{3.0f, -0.5f, 2.0f}, 0.3f}}) == (CubicHermiteQuaternion{ + {{1.0f, 3.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.0f + TypeTraits::epsilon()/2}, + {{3.0f, -0.5f, 2.0f}, 0.3f}})); + CORRADE_VERIFY((CubicHermiteQuaternion{ + {{1.0f, 3.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.0f}, + {{3.0f, -0.5f, 2.0f}, 0.3f}}) != (CubicHermiteQuaternion{ + {{1.0f + TypeTraits::epsilon()*2, 3.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.0f}, + {{3.0f, -0.5f, 2.0f}, 0.3f}})); +} + +void CubicHermiteTest::selectScalar() { + CubicHermite1D a{2.0f, 3.0f, -1.0f}; + CubicHermite1D b{5.0f, -2.0f, 1.5f}; + + CORRADE_COMPARE(Math::select(a, b, 0.0f), 3.0f); + CORRADE_COMPARE(Math::select(a, b, 0.8f), 3.0f); + CORRADE_COMPARE(Math::select(a, b, 1.0f), -2.0f); +} + +void CubicHermiteTest::selectVector() { + CubicHermite2D a{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CubicHermite2D b{{5.0f, 0.3f}, {-2.0f, 1.1f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::select(a, b, 0.0f), (Vector2{3.0f, 0.1f})); + CORRADE_COMPARE(Math::select(a, b, 0.8f), (Vector2{3.0f, 0.1f})); + CORRADE_COMPARE(Math::select(a, b, 1.0f), (Vector2{-2.0f, 1.1f})); +} + +void CubicHermiteTest::selectComplex() { + CubicHermiteComplex a{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CubicHermiteComplex b{{5.0f, 0.3f}, {-2.0f, 1.1f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::select(a, b, 0.0f), (Complex{3.0f, 0.1f})); + CORRADE_COMPARE(Math::select(a, b, 0.8f), (Complex{3.0f, 0.1f})); + CORRADE_COMPARE(Math::select(a, b, 1.0f), (Complex{-2.0f, 1.1f})); +} + +void CubicHermiteTest::selectQuaternion() { + CubicHermiteQuaternion a{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + {{3.0f, 0.1f, 2.3f}, 0.7f}, + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CubicHermiteQuaternion b{ + {{5.0f, 0.3f, 1.1f}, 0.5f}, + {{-2.0f, 1.1f, 1.0f}, 1.3f}, + {{1.5f, 0.3f, 17.0f}, -7.0f}}; + + CORRADE_COMPARE(Math::select(a, b, 0.0f), (Quaternion{{3.0f, 0.1f, 2.3f}, 0.7f})); + CORRADE_COMPARE(Math::select(a, b, 0.8f), (Quaternion{{3.0f, 0.1f, 2.3f}, 0.7f})); + CORRADE_COMPARE(Math::select(a, b, 1.0f), (Quaternion{{-2.0f, 1.1f, 1.0f}, 1.3f})); +} + +void CubicHermiteTest::lerpScalar() { + CubicHermite1D a{2.0f, 3.0f, -1.0f}; + CubicHermite1D b{5.0f, -2.0f, 1.5f}; + + CORRADE_COMPARE(Math::lerp(a, b, 0.0f), 3.0f); + CORRADE_COMPARE(Math::lerp(a, b, 1.0f), -2.0f); + + CORRADE_COMPARE(Math::lerp(a, b, 0.35f), 1.25f); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.35f), 1.25f); + + CORRADE_COMPARE(Math::lerp(a, b, 0.8f), -1.0f); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.8f), -1.0f); +} + +void CubicHermiteTest::lerpVector() { + CubicHermite2D a{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CubicHermite2D b{{5.0f, 0.3f}, {-2.0f, 1.1f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::lerp(a, b, 0.0f), (Vector2{3.0f, 0.1f})); + CORRADE_COMPARE(Math::lerp(a, b, 1.0f), (Vector2{-2.0f, 1.1f})); + + CORRADE_COMPARE(Math::lerp(a, b, 0.35f), (Vector2{1.25f, 0.45f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.35f), (Vector2{1.25f, 0.45f})); + + CORRADE_COMPARE(Math::lerp(a, b, 0.8f), (Vector2{-1.0f, 0.9f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.8f), (Vector2{-1.0f, 0.9f})); +} + +void CubicHermiteTest::lerpComplex() { + CubicHermiteComplex a{{2.0f, 1.5f}, {0.999445f, 0.0333148f}, {-1.0f, 0.0f}}; + CubicHermiteComplex b{{5.0f, 0.3f}, {-0.876216f, 0.481919f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::lerp(a, b, 0.0f), (Complex{0.999445f, 0.0333148f})); + CORRADE_COMPARE(Math::lerp(a, b, 1.0f), (Complex{-0.876216f, 0.481919f})); + + CORRADE_COMPARE(Math::lerp(a, b, 0.35f), (Complex{0.874384f, 0.485235f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.35f), (Complex{0.874384f, 0.485235f})); + CORRADE_VERIFY(Math::lerp(a, b, 0.35f).isNormalized()); + + CORRADE_COMPARE(Math::lerp(a, b, 0.8f), (Complex{-0.78747f, 0.616353f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.8f), (Complex{-0.78747f, 0.616353f})); + CORRADE_VERIFY(Math::lerp(a, b, 0.8f).isNormalized()); +} + +void CubicHermiteTest::lerpComplexNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + CORRADE_COMPARE(Math::lerp(CubicHermiteComplex{}, {}, 0.3f), Complex{}); + + /* These will, tho */ + CubicHermiteComplex a{{}, Complex{}*2.0f, {}}; + Math::lerp({}, a, 0.3f); + Math::lerp(a, {}, 0.3f); + CORRADE_COMPARE(out.str(), + "Math::lerp(): complex numbers must be normalized\n" + "Math::lerp(): complex numbers must be normalized\n"); +} + +void CubicHermiteTest::lerpQuaternion() { + CubicHermiteQuaternion a{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + {{0.780076f, 0.0260025f, 0.598059f}, 0.182018f}, + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CubicHermiteQuaternion b{ + {{5.0f, 0.3f, 1.1f}, 0.5f}, + {{-0.711568f, 0.391362f, 0.355784f}, 0.462519f}, + {{1.5f, 0.3f, 17.0f}, -7.0f}}; + + CORRADE_COMPARE(Math::lerp(a, b, 0.0f), (Quaternion{{0.780076f, 0.0260025f, 0.598059f}, 0.182018f})); + CORRADE_COMPARE(Math::lerp(a, b, 1.0f), (Quaternion{{-0.711568f, 0.391362f, 0.355784f}, 0.462519f})); + + CORRADE_COMPARE(Math::lerp(a, b, 0.35f), (Quaternion{{0.392449f, 0.234067f, 0.780733f}, 0.426207f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.35f), (Quaternion{{0.392449f, 0.234067f, 0.780733f}, 0.426207f})); + CORRADE_VERIFY(Math::lerp(a, b, 0.35f).isNormalized()); + + CORRADE_COMPARE(Math::lerp(a, b, 0.8f), (Quaternion{{-0.533196f, 0.410685f, 0.521583f}, 0.524396f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.8f), (Quaternion{{-0.533196f, 0.410685f, 0.521583f}, 0.524396f})); + CORRADE_VERIFY(Math::lerp(a, b, 0.8f).isNormalized()); +} + +void CubicHermiteTest::lerpQuaternionNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + Math::lerp(CubicHermiteQuaternion{}, {}, 0.3f); + + /* These will, tho */ + CubicHermiteQuaternion a{{}, Quaternion{}*2.0f, {}}; + Math::lerp({}, a, 0.3f); + Math::lerp(a, {}, 0.3f); + CORRADE_COMPARE(out.str(), + "Math::lerp(): quaternions must be normalized\n" + "Math::lerp(): quaternions must be normalized\n"); +} + +void CubicHermiteTest::splerpScalar() { + CubicHermite1D a{2.0f, 3.0f, -1.0f}; + CubicHermite1D b{5.0f, -2.0f, 1.5f}; + + CORRADE_COMPARE(Math::splerp(a, b, 0.0f), 3.0f); + CORRADE_COMPARE(Math::splerp(a, b, 1.0f), -2.0f); + + CORRADE_COMPARE(Math::splerp(a, b, 0.35f), 1.04525f); + CORRADE_COMPARE(Math::splerp(a, b, 0.8f), -2.152f); +} + +void CubicHermiteTest::splerpVector() { + CubicHermite2D a{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CubicHermite2D b{{5.0f, 0.3f}, {-2.0f, 1.1f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::splerp(a, b, 0.0f), (Vector2{3.0f, 0.1f})); + CORRADE_COMPARE(Math::splerp(a, b, 1.0f), (Vector2{-2.0f, 1.1f})); + + CORRADE_COMPARE(Math::splerp(a, b, 0.35f), (Vector2{1.04525f, 0.357862f})); + CORRADE_COMPARE(Math::splerp(a, b, 0.8f), (Vector2{-2.152f, 0.9576f})); +} + +void CubicHermiteTest::splerpComplex() { + CubicHermiteComplex a{{2.0f, 1.5f}, {0.999445f, 0.0333148f}, {-1.0f, 0.0f}}; + CubicHermiteComplex b{{5.0f, 0.3f}, {-0.876216f, 0.481919f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::splerp(a, b, 0.0f), (Complex{0.999445f, 0.0333148f})); + CORRADE_COMPARE(Math::splerp(a, b, 1.0f), (Complex{-0.876216f, 0.481919f})); + + CORRADE_COMPARE(Math::splerp(a, b, 0.35f), (Complex{-0.483504f, 0.875342f})); + CORRADE_VERIFY(Math::splerp(a, b, 0.35f).isNormalized()); + + CORRADE_COMPARE(Math::splerp(a, b, 0.8f), (Complex{-0.95958f, 0.281435f})); + CORRADE_VERIFY(Math::splerp(a, b, 0.8f).isNormalized()); +} + +void CubicHermiteTest::splerpComplexNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + CORRADE_COMPARE(Math::splerp(CubicHermiteComplex{}, {}, 0.3f), Complex{}); + + /* These will, tho */ + CubicHermiteComplex a{{}, Complex{}*2.0f, {}}; + Math::splerp({}, a, 0.3f); + Math::splerp(a, {}, 0.3f); + CORRADE_COMPARE(out.str(), + "Math::splerp(): complex spline points must be normalized\n" + "Math::splerp(): complex spline points must be normalized\n"); +} + +void CubicHermiteTest::splerpQuaternion() { + CubicHermiteQuaternion a{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + {{0.780076f, 0.0260025f, 0.598059f}, 0.182018f}, + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CubicHermiteQuaternion b{ + {{5.0f, 0.3f, 1.1f}, 0.5f}, + {{-0.711568f, 0.391362f, 0.355784f}, 0.462519f}, + {{1.5f, 0.3f, 17.0f}, -7.0f}}; + + CORRADE_COMPARE(Math::splerp(a, b, 0.0f), (Quaternion{{0.780076f, 0.0260025f, 0.598059f}, 0.182018f})); + CORRADE_COMPARE(Math::splerp(a, b, 1.0f), (Quaternion{{-0.711568f, 0.391362f, 0.355784f}, 0.462519f})); + + CORRADE_COMPARE(Math::splerp(a, b, 0.35f), (Quaternion{{-0.309862f, 0.174831f, 0.809747f}, 0.466615f})); + CORRADE_VERIFY(Math::splerp(a, b, 0.35f).isNormalized()); + + CORRADE_COMPARE(Math::splerp(a, b, 0.8f), (Quaternion{{-0.911408f, 0.23368f, 0.185318f}, 0.283524f})); + CORRADE_VERIFY(Math::splerp(a, b, 0.8f).isNormalized()); +} + +void CubicHermiteTest::splerpQuaternionNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + Math::splerp(CubicHermiteQuaternion{}, {}, 0.3f); + + /* These will, tho */ + CubicHermiteQuaternion a{{}, Quaternion{}*2.0f, {}}; + Math::splerp({}, a, 0.3f); + Math::splerp(a, {}, 0.3f); + CORRADE_COMPARE(out.str(), + "Math::splerp(): quaternion spline points must be normalized\n" + "Math::splerp(): quaternion spline points must be normalized\n"); +} + +void CubicHermiteTest::debugScalar() { + std::ostringstream out; + Debug{&out} << CubicHermite1D{2.0f, 3.0f, -1.0f}; + CORRADE_COMPARE(out.str(), "CubicHermite(2, 3, -1)\n"); +} + +void CubicHermiteTest::debugVector() { + std::ostringstream out; + Debug{&out} << CubicHermite2D{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CORRADE_COMPARE(out.str(), "CubicHermite(Vector(2, 1.5), Vector(3, 0.1), Vector(-1, 0))\n"); +} + +void CubicHermiteTest::debugComplex() { + std::ostringstream out; + Debug{&out} << CubicHermiteComplex{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CORRADE_COMPARE(out.str(), "CubicHermite(Complex(2, 1.5), Complex(3, 0.1), Complex(-1, 0))\n"); +} + +void CubicHermiteTest::debugQuaternion() { + std::ostringstream out; + Debug{&out} << CubicHermiteQuaternion{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + {{3.0f, 0.1f, 2.3f}, 0.7f}, + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CORRADE_COMPARE(out.str(), "CubicHermite(Quaternion({2, 1.5, 0.3}, 1.1), Quaternion({3, 0.1, 2.3}, 0.7), Quaternion({-1, 0, 0.3}, 0.4))\n"); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Math::Test::CubicHermiteTest) diff --git a/src/Magnum/Math/instantiation.cpp b/src/Magnum/Math/instantiation.cpp index f6cf11909..baa5220fb 100644 --- a/src/Magnum/Math/instantiation.cpp +++ b/src/Magnum/Math/instantiation.cpp @@ -24,6 +24,7 @@ */ #include "Magnum/Math/Bezier.h" +#include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/DualComplex.h" #include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/Frustum.h" @@ -101,6 +102,17 @@ template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Bez template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Bezier<3, 2, Double>&); template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Bezier<3, 3, Double>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); + template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Complex&); template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Complex&); From b1b663fa65e58360069fabc72c79ed55ac9cd010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 31 Aug 2018 21:23:27 +0200 Subject: [PATCH 10/26] Animation: support for spline interpolation. --- src/Magnum/Animation/Interpolation.cpp | 23 ++++- src/Magnum/Animation/Interpolation.h | 31 ++++++- .../Animation/Test/InterpolationTest.cpp | 93 ++++++++++++++++++- src/Magnum/Animation/Track.h | 7 ++ 4 files changed, 146 insertions(+), 8 deletions(-) diff --git a/src/Magnum/Animation/Interpolation.cpp b/src/Magnum/Animation/Interpolation.cpp index fcb4e9c4e..1e38c107f 100644 --- a/src/Magnum/Animation/Interpolation.cpp +++ b/src/Magnum/Animation/Interpolation.cpp @@ -25,7 +25,7 @@ #include "Interpolation.h" -#include "Magnum/Math/Complex.h" +#include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/DualQuaternion.h" namespace Magnum { namespace Animation { @@ -37,6 +37,7 @@ Debug& operator<<(Debug& debug, const Interpolation value) { #define _c(value) case Interpolation::value: return debug << "Animation::Interpolation::" #value; _c(Constant) _c(Linear) + _c(Spline) _c(Custom) #undef _c /* LCOV_EXCL_STOP */ @@ -67,6 +68,7 @@ template auto TypeTraits, Math::Complex>::interpola case Interpolation::Constant: return Math::select; case Interpolation::Linear: return Math::slerp; + case Interpolation::Spline: case Interpolation::Custom: ; /* nope */ } @@ -78,6 +80,7 @@ template auto TypeTraits, Math::Quaternion>::int case Interpolation::Constant: return Math::select; case Interpolation::Linear: return Math::slerp; + case Interpolation::Spline: case Interpolation::Custom: ; /* nope */ } @@ -89,6 +92,19 @@ template auto TypeTraits, Math::DualQuaternion< case Interpolation::Constant: return Math::select; case Interpolation::Linear: return Math::sclerp; + case Interpolation::Spline: + case Interpolation::Custom: ; /* nope */ + } + + CORRADE_ASSERT(false, "Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {}); +} + +template auto TypeTraits, T>::interpolator(Interpolation interpolation) -> Interpolator { + switch(interpolation) { + case Interpolation::Constant: return Math::select; + case Interpolation::Linear: return Math::lerp; + case Interpolation::Spline: return Math::splerp; + case Interpolation::Custom: ; /* nope */ } @@ -98,6 +114,11 @@ template auto TypeTraits, Math::DualQuaternion< template struct MAGNUM_EXPORT TypeTraits, Math::Complex>; template struct MAGNUM_EXPORT TypeTraits, Math::Quaternion>; template struct MAGNUM_EXPORT TypeTraits, Math::DualQuaternion>; +template struct MAGNUM_EXPORT TypeTraits, Float>; +template struct MAGNUM_EXPORT TypeTraits>, Math::Vector2>; +template struct MAGNUM_EXPORT TypeTraits>, Math::Vector3>; +template struct MAGNUM_EXPORT TypeTraits>, Math::Complex>; +template struct MAGNUM_EXPORT TypeTraits>, Math::Quaternion>; } diff --git a/src/Magnum/Animation/Interpolation.h b/src/Magnum/Animation/Interpolation.h index cc946f326..2e71d96f7 100644 --- a/src/Magnum/Animation/Interpolation.h +++ b/src/Magnum/Animation/Interpolation.h @@ -60,6 +60,13 @@ enum class Interpolation: UnsignedByte { */ Linear, + /** + * Spline interpolation. + * + * @see @ref Math::splerp() + */ + Spline, + /** * Custom interpolation. An user-supplied interpolation function should be * used. @@ -74,8 +81,9 @@ MAGNUM_EXPORT Debug& operator<<(Debug& debug, Interpolation value); @brief Animation result type for given value type Result of interpolating two `V` values (for example interpolating two -@ref Color3 values gives back a @ref Color3 again, but interpolating a spline -does not result in a spline). +@ref Color3 values gives back a @ref Color3 again, but interpolating a +@ref Magnum::CubicHermite2D "CubicHermite2D" spline results in +@ref Magnum::Vector2 "Vector2"). @experimental */ #ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ @@ -96,6 +104,7 @@ faster but less precise results. Interpolation type | Value type | Result type | Interpolator ------------------- | ----------------- | ------------- | ------------ @ref Interpolation::Constant | any `V` | `V` | @ref Math::select() +@ref Interpolation::Constant | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::select(const CubicHermite&, const CubicHermite&, U) "Math::select()" @ref Interpolation::Linear | @cpp bool @ce | @cpp bool @ce | @ref Math::select() @ref Interpolation::Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() @ref Interpolation::Linear | any scalar `V` | `V` | @ref Math::lerp() @@ -103,6 +112,12 @@ Interpolation type | Value type | Result type | Interpolator @ref Interpolation::Linear | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex&, const Complex&, T) "Math::slerp()" @ref Interpolation::Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()" @ref Interpolation::Linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()" +@ref Interpolation::Linear | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::lerp(const CubicHermite&, const CubicHermite&, U) "Math::lerp()" +@ref Interpolation::Linear | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::lerp()" +@ref Interpolation::Linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerp()" +@ref Interpolation::Spline | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::splerp(const CubicHermite&, const CubicHermite&, U) "Math::splerp()" +@ref Interpolation::Spline | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::splerp()" +@ref Interpolation::Spline | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::splerp()" @see @ref interpolate(), @ref interpolateStrict() @experimental @@ -213,6 +228,9 @@ namespace Implementation { template struct ResultTraits { typedef V Type; }; +template struct ResultTraits> { + typedef T Type; +}; template struct TypeTraits { typedef V(*Interpolator)(const V&, const V&, Float); @@ -223,6 +241,7 @@ template auto TypeTraits::interpolator(Interpolation interpolatio case Interpolation::Constant: return Math::select; case Interpolation::Linear: return Math::lerp; + case Interpolation::Spline: case Interpolation::Custom: ; /* nope */ } @@ -240,6 +259,7 @@ template auto TypeTraitsBool::interpolator(Interpolation interpolati case Interpolation::Constant: case Interpolation::Linear: return Math::select; + case Interpolation::Spline: case Interpolation::Custom: ; /* nope */ } @@ -267,6 +287,13 @@ template struct MAGNUM_EXPORT TypeTraits, Math: static Interpolator interpolator(Interpolation interpolation); }; +/* Cubic Hermite spline point has a different result type */ +template struct MAGNUM_EXPORT TypeTraits, T> { + typedef T(*Interpolator)(const Math::CubicHermite&, const Math::CubicHermite&, Float); + + static Interpolator interpolator(Interpolation interpolation); +}; + } /* Needs to be defined later so it can pick up the TypeTraits definitions */ diff --git a/src/Magnum/Animation/Test/InterpolationTest.cpp b/src/Magnum/Animation/Test/InterpolationTest.cpp index 2e5a4aae4..b9406ad15 100644 --- a/src/Magnum/Animation/Test/InterpolationTest.cpp +++ b/src/Magnum/Animation/Test/InterpolationTest.cpp @@ -28,6 +28,7 @@ #include "Magnum/Animation/Interpolation.h" #include "Magnum/Math/Complex.h" +#include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/Half.h" @@ -42,6 +43,10 @@ struct InterpolationTest: TestSuite::Tester { void interpolatorForComplex(); void interpolatorForQuaternion(); void interpolatorForDualQuaternion(); + void interpolatorForCubicHermiteScalar(); + void interpolatorForCubicHermiteVector(); + void interpolatorForCubicHermiteComplex(); + void interpolatorForCubicHermiteQuaternion(); void interpolate(); void interpolateStrict(); @@ -142,7 +147,11 @@ InterpolationTest::InterpolationTest() { &InterpolationTest::interpolatorForBoolVector, &InterpolationTest::interpolatorForComplex, &InterpolationTest::interpolatorForQuaternion, - &InterpolationTest::interpolatorForDualQuaternion}); + &InterpolationTest::interpolatorForDualQuaternion, + &InterpolationTest::interpolatorForCubicHermiteScalar, + &InterpolationTest::interpolatorForCubicHermiteVector, + &InterpolationTest::interpolatorForCubicHermiteComplex, + &InterpolationTest::interpolatorForCubicHermiteQuaternion}); addInstancedTests({&InterpolationTest::interpolate, &InterpolationTest::interpolateStrict}, @@ -178,11 +187,11 @@ void InterpolationTest::interpolatorFor() { std::ostringstream out; Error redirectError{&out}; - Animation::interpolatorFor(Interpolation::Custom); + Animation::interpolatorFor(Interpolation::Spline); Animation::interpolatorFor(Interpolation(0xde)); CORRADE_COMPARE(out.str(), - "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n" + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Spline\n" "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); } @@ -250,11 +259,11 @@ void InterpolationTest::interpolatorForQuaternion() { std::ostringstream out; Error redirectError{&out}; - Animation::interpolatorFor(Interpolation::Custom); + Animation::interpolatorFor(Interpolation::Spline); Animation::interpolatorFor(Interpolation(0xde)); CORRADE_COMPARE(out.str(), - "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n" + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Spline\n" "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); } @@ -278,6 +287,80 @@ void InterpolationTest::interpolatorForDualQuaternion() { "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); } +void InterpolationTest::interpolatorForCubicHermiteScalar() { + CubicHermite1D a{2.0f, 3.0f, -1.0f}; + CubicHermite1D b{5.0f, -2.0f, 1.5f}; + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Constant)(a, b, 0.8f), 3.0f); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Linear)(a, b, 0.8f), -1.0f); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Spline)(a, b, 0.8f), -2.152f); + + std::ostringstream out; + Error redirectError{&out}; + Animation::interpolatorFor(Interpolation::Custom); + Animation::interpolatorFor(Interpolation(0xde)); + + CORRADE_COMPARE(out.str(), + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n" + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); +} + +void InterpolationTest::interpolatorForCubicHermiteVector() { + CubicHermite2D a{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CubicHermite2D b{{5.0f, 0.3f}, {-2.0f, 1.1f}, {1.5f, 0.3f}}; + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Constant)(a, b, 0.8f), (Vector2{3.0f, 0.1f})); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Linear)(a, b, 0.8f), (Vector2{-1.0f, 0.9f})); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Spline)(a, b, 0.8f), (Vector2{-2.152f, 0.9576f})); + + std::ostringstream out; + Error redirectError{&out}; + Animation::interpolatorFor(Interpolation::Custom); + Animation::interpolatorFor(Interpolation(0xde)); + + CORRADE_COMPARE(out.str(), + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n" + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); +} + +void InterpolationTest::interpolatorForCubicHermiteComplex() { + CubicHermiteComplex a{{2.0f, 1.5f}, {0.999445f, 0.0333148f}, {-1.0f, 0.0f}}; + CubicHermiteComplex b{{5.0f, 0.3f}, {-0.876216f, 0.481919f}, {1.5f, 0.3f}}; + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Constant)(a, b, 0.8f), (Complex{0.999445f, 0.0333148f})); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Linear)(a, b, 0.8f), (Complex{-0.78747f, 0.616353f})); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Spline)(a, b, 0.8f), (Complex{-0.95958f, 0.281435f})); + + std::ostringstream out; + Error redirectError{&out}; + Animation::interpolatorFor(Interpolation::Custom); + Animation::interpolatorFor(Interpolation(0xde)); + + CORRADE_COMPARE(out.str(), + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n" + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); +} + +void InterpolationTest::interpolatorForCubicHermiteQuaternion() { + CubicHermiteQuaternion a{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + {{0.780076f, 0.0260025f, 0.598059f}, 0.182018f}, + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CubicHermiteQuaternion b{ + {{5.0f, 0.3f, 1.1f}, 0.5f}, + {{-0.711568f, 0.391362f, 0.355784f}, 0.462519f}, + {{1.5f, 0.3f, 17.0f}, -7.0f}}; + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Constant)(a, b, 0.8f), (Quaternion{{0.780076f, 0.0260025f, 0.598059f}, 0.182018f})); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Linear)(a, b, 0.8f), (Quaternion{{-0.533196f, 0.410685f, 0.521583f}, 0.524396f})); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Spline)(a, b, 0.8f), (Quaternion{{-0.911408f, 0.23368f, 0.185318f}, 0.283524f})); + + std::ostringstream out; + Error redirectError{&out}; + Animation::interpolatorFor(Interpolation::Custom); + Animation::interpolatorFor(Interpolation(0xde)); + + CORRADE_COMPARE(out.str(), + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n" + "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); +} + namespace { constexpr Float Keys[]{0.0f, 2.0f, 4.0f, 5.0f}; constexpr Float Values[]{3.0f, 1.0f, 2.5f, 0.5f}; diff --git a/src/Magnum/Animation/Track.h b/src/Magnum/Animation/Track.h index a7fa77b20..e271b71cd 100644 --- a/src/Magnum/Animation/Track.h +++ b/src/Magnum/Animation/Track.h @@ -63,15 +63,22 @@ are common combinations: Interpolation type | Value type | Result type | Interpolator ------------------- | ----------------- | ------------- | ------------ Constant | any `V` | `V` | @ref Math::select() +Constant | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::select(const CubicHermite&, const CubicHermite&, U) "Math::select()" Linear | @cpp bool @ce | @cpp bool @ce | @ref Math::select() Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() Linear | any scalar `V` | `V` | @ref Math::lerp() Linear | any vector `V` | `V` | @ref Math::lerp() Linear | @ref Math::Complex | @ref Math::Complex | @ref Math::lerp(const Complex&, const Complex&, T) "Math::lerp()" Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion&, const Quaternion&, T) "Math::lerp()" +Linear | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::lerp(const CubicHermite&, const CubicHermite&, U) "Math::lerp()" +Linear | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::lerp()" +Linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerp()" Spherical linear | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex&, const Complex&, T) "Math::slerp()" Spherical linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()" Screw linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()" +Spline | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::splerp(const CubicHermite&, const CubicHermite&, U) "Math::splerp()" +Spline | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::splerp()" +Spline | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::splerp()" It's also possible to supply a generic interpolation behavior by passing the @ref Interpolation enum to the constructor. In case the interpolator function From c3d093bee9c9d35f6ff912975bfb0d65ef6134b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 31 Aug 2018 20:47:26 +0200 Subject: [PATCH 11/26] Trade: support for spline interpolation in AnimationData. --- src/Magnum/Trade/AnimationData.cpp | 9 ++++ src/Magnum/Trade/AnimationData.h | 83 +++++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/src/Magnum/Trade/AnimationData.cpp b/src/Magnum/Trade/AnimationData.cpp index c2f8f3278..fd0b1ed11 100644 --- a/src/Magnum/Trade/AnimationData.cpp +++ b/src/Magnum/Trade/AnimationData.cpp @@ -100,6 +100,10 @@ template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Animation::Interpolation) -> Complex(*)(const Complex&, const Complex&, Float); template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Animation::Interpolation) -> Quaternion(*)(const Quaternion&, const Quaternion&, Float); template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Animation::Interpolation) -> DualQuaternion(*)(const DualQuaternion&, const DualQuaternion&, Float); +template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor>(Animation::Interpolation) -> Math::Vector2(*)(const CubicHermite2D&, const CubicHermite2D&, Float); +template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor>(Animation::Interpolation) -> Math::Vector3(*)(const CubicHermite3D&, const CubicHermite3D&, Float); +template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Animation::Interpolation) -> Complex(*)(const CubicHermiteComplex&, const CubicHermiteComplex&, Float); +template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor(Animation::Interpolation) -> Quaternion(*)(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, Float); Debug& operator<<(Debug& debug, const AnimationTrackType value) { switch(value) { @@ -124,6 +128,11 @@ Debug& operator<<(Debug& debug, const AnimationTrackType value) { _c(Complex) _c(Quaternion) _c(DualQuaternion) + _c(CubicHermite1D) + _c(CubicHermite2D) + _c(CubicHermite3D) + _c(CubicHermiteComplex) + _c(CubicHermiteQuaternion) #undef _c /* LCOV_EXCL_STOP */ } diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index 5d3781957..98ff4cf13 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -87,7 +87,34 @@ enum class AnimationTrackType: UnsignedByte { */ Quaternion, - DualQuaternion /**< @ref Magnum::DualQuaternion "DualQuaternion" */ + DualQuaternion, /**< @ref Magnum::DualQuaternion "DualQuaternion" */ + CubicHermite1D, /**< @ref Magnum::CubicHermite1D "CubicHermite1D" */ + + /** + * @ref Magnum::CubicHermite2D "CubicHermite2D". Usually used for + * spline-interpolated @ref AnimationTrackTarget::Translation2D and + * @ref AnimationTrackTarget::Scaling2D. + */ + CubicHermite2D, + + /** + * @ref Magnum::CubicHermite3D "CubicHermite3D". Usually used for + * spline-interpolated @ref AnimationTrackTarget::Translation3D and + * @ref AnimationTrackTarget::Scaling3D. + */ + CubicHermite3D, + + /** + * @ref Magnum::CubicHermiteComplex "CubicHermiteComplex". Usually used for + * spline-interpolated @ref AnimationTrackTarget::Rotation2D. + */ + CubicHermiteComplex, + + /** + * @ref Magnum::CubicHermiteQuaternion "CubicHermiteQuaternion". Usually + * used for spline-interpolated @ref AnimationTrackTarget::Rotation3D. + */ + CubicHermiteQuaternion }; /** @debugoperatorenum{AnimationTrackType} */ @@ -102,49 +129,73 @@ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, AnimationTrackType value); enum class AnimationTrackTarget: UnsignedByte { /** * Modifies 2D object translation. Type is usually - * @ref Magnum::Vector2 "Vector2". + * @ref Magnum::Vector2 "Vector2" or + * @ref Magnum::CubicHermite2D "CubicHermite2D" for spline-interpolated + * data. * - * @see @ref AnimationTrackType::Vector2, @ref ObjectData2D::translation() + * @see @ref AnimationTrackType::Vector2, + * @ref AnimationTrackType::CubicHermite2D, + * @ref ObjectData2D::translation() */ Translation2D, /** * Modifies 3D object translation. Type is usually - * @ref Magnum::Vector3 "Vector3". + * @ref Magnum::Vector3 "Vector3" or + * @ref Magnum::CubicHermite3D "CubicHermite3D" for spline-interpolated + * data. * - * @see @ref AnimationTrackType::Vector3, @ref ObjectData3D::translation() + * @see @ref AnimationTrackType::Vector3, + * @ref AnimationTrackType::CubicHermite3D, + * @ref ObjectData3D::translation() */ Translation3D, /** * Modifies 2D object rotation. Type is usually - * @ref Magnum::Complex "Complex". + * @ref Magnum::Complex "Complex" or + * @ref Magnum::CubicHermiteComplex "CubicHermiteComplex" for + * spline-interpolated data. * - * @see @ref AnimationTrackType::Complex, @ref ObjectData2D::rotation() + * @see @ref AnimationTrackType::Complex, + * @ref AnimationTrackType::CubicHermiteComplex, + * @ref ObjectData2D::rotation() */ Rotation2D, /** * Modifies 3D object rotation. Type is usually - * @ref Magnum::Quaternion "Quaternion". + * @ref Magnum::Quaternion "Quaternion" or + * @ref Magnum::CubicHermiteQuaternion "CubicHermiteQuaternion" for + * spline-interpolated data. * - * @see @ref AnimationTrackType::Quaternion, @ref ObjectData3D::rotation() + * @see @ref AnimationTrackType::Quaternion, + * @ref AnimationTrackType::CubicHermiteQuaternion, + * @ref ObjectData3D::rotation() */ Rotation3D, /** * Modifies 2D object scaling. Type is usually - * @ref Magnum::Vector2 "Vector2". + * @ref Magnum::Vector2 "Vector2" or + * @ref Magnum::CubicHermite2D "CubicHermite2D" for spline-interpolated + * data. * - * @see @ref AnimationTrackType::Vector2, @ref ObjectData2D::scaling() + * @see @ref AnimationTrackType::Vector2, + * @ref AnimationTrackType::CubicHermite2D, + * @ref ObjectData2D::scaling() */ Scaling2D, /** * Modifies 3D object scaling. Type is usually - * @ref Magnum::Vector3 "Vector3". + * @ref Magnum::Vector3 "Vector3" or + * @ref Magnum::CubicHermite3D "CubicHermite3D" for spline-interpolated + * data. * - * @see @ref AnimationTrackType::Vector3, @ref ObjectData3D::scaling() + * @see @ref AnimationTrackType::Vector3, + * @ref AnimationTrackType::CubicHermite3D, + * @ref ObjectData3D::scaling() */ Scaling3D, @@ -450,6 +501,12 @@ namespace Implementation { template<> constexpr AnimationTrackType animationTypeFor() { return AnimationTrackType::Complex; } template<> constexpr AnimationTrackType animationTypeFor() { return AnimationTrackType::Quaternion; } template<> constexpr AnimationTrackType animationTypeFor() { return AnimationTrackType::DualQuaternion; } + + template<> constexpr AnimationTrackType animationTypeFor() { return AnimationTrackType::CubicHermite1D; } + template<> constexpr AnimationTrackType animationTypeFor() { return AnimationTrackType::CubicHermite2D; } + template<> constexpr AnimationTrackType animationTypeFor() { return AnimationTrackType::CubicHermite3D; } + template<> constexpr AnimationTrackType animationTypeFor() { return AnimationTrackType::CubicHermiteComplex; } + template<> constexpr AnimationTrackType animationTypeFor() { return AnimationTrackType::CubicHermiteQuaternion; } /* LCOV_EXCL_STOP */ } #endif From 6c8a2a46b8d69e7afd88bf4baefb6ea3aded1830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 31 Aug 2018 20:47:02 +0200 Subject: [PATCH 12/26] Math: implement cubic Bezier <-> cubic Hermite conversion. --- doc/changelog.dox | 4 ++- doc/snippets/MagnumMath.cpp | 14 ++++++++ src/Magnum/Math/Bezier.h | 39 ++++++++++++++++++-- src/Magnum/Math/CubicHermite.h | 43 +++++++++++++++++++++++ src/Magnum/Math/Test/BezierTest.cpp | 15 ++++++++ src/Magnum/Math/Test/CubicHermiteTest.cpp | 41 +++++++++++++++++++++ 6 files changed, 153 insertions(+), 3 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 1c22b3fce..410c51cfe 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -47,7 +47,9 @@ See also: @subsubsection changelog-latest-new-math Math library -- New @ref Math::CubicHermite class for cubic Hermite spline interpolation +- New @ref Math::CubicHermite class for cubic Hermite spline interpolation, + convertible to and from cubic Bézier curves using + @ref Math::Bezier::fromCubicHermite() and @ref Math::CubicHermite::fromBezier() - Added @ref Math::Intersection::rangeFrustum(), @ref Math::Intersection::aabbFrustum(), @ref Math::Intersection::sphereFrustum(), diff --git a/doc/snippets/MagnumMath.cpp b/doc/snippets/MagnumMath.cpp index 2b9f3ca11..39f0584cb 100644 --- a/doc/snippets/MagnumMath.cpp +++ b/doc/snippets/MagnumMath.cpp @@ -25,6 +25,8 @@ #include "Magnum/Magnum.h" #include "Magnum/Math/Color.h" +#include "Magnum/Math/Bezier.h" +#include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/DualComplex.h" #include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/Half.h" @@ -839,6 +841,18 @@ Color4 a = 0x33b27fcc_srgbaf; // {0.0331048f, 0.445201f, 0.212231f, 0.8f} static_cast(a); } +{ +/* [CubicHermite-fromBezier] */ +CubicBezier2D segment; +auto startPoint = CubicHermite2D::fromBezier( + {Vector2{}, Vector2{}, Vector2{}, segment[3]}, segment); +auto endPoint = CubicHermite2D::fromBezier(segment, + {segment[0], Vector2{}, Vector2{}, Vector2{}}); +/* [CubicHermite-fromBezier] */ +static_cast(startPoint); +static_cast(endPoint); +} + { /* [Half-usage] */ using namespace Math::Literals; diff --git a/src/Magnum/Math/Bezier.h b/src/Magnum/Math/Bezier.h index 60668a626..3909332c3 100644 --- a/src/Magnum/Math/Bezier.h +++ b/src/Magnum/Math/Bezier.h @@ -46,8 +46,12 @@ namespace Implementation { @tparam dimensions Dimensions of control points @tparam T Underlying data type -Implementation of M-order N-dimensional -[Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve). +Represents a M-order N-dimensional +[Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) segment. + +Cubic Bézier curves are fully interchangeable with cubic Hermite splines, use +@ref fromCubicHermite() and @ref CubicHermite::fromBezier() for the conversion. + @see @ref QuadraticBezier, @ref CubicBezier, @ref QuadraticBezier2D, @ref QuadraticBezier3D, @ref CubicBezier2D, @ref CubicBezier3D */ @@ -64,6 +68,37 @@ template class Bezier { Dimensions = dimensions /**< Dimensions of control points */ }; + /** + * @brief Create cubic Hermite spline point from adjacent Bézier curve segments + * + * Given two cubic Hermite spline points defined by points + * @f$ \boldsymbol{p}_i @f$, in-tangents @f$ \boldsymbol{m}_i @f$ and + * out-tangents @f$ \boldsymbol{n}_i @f$, the corresponding cubic + * Bezier curve segment with points @f$ \boldsymbol{c}_0 @f$, + * @f$ \boldsymbol{c}_1 @f$, @f$ \boldsymbol{c}_2 @f$ and + * @f$ \boldsymbol{c}_3 @f$ is defined as: @f[ + * \begin{array}{rcl} + * \boldsymbol{c}_0 & = & \boldsymbol{p}_a \\ + * \boldsymbol{c}_1 & = & \frac{1}{3} \boldsymbol{n}_a - \boldsymbol{p}_a \\ + * \boldsymbol{c}_2 & = & \boldsymbol{p}_b - \frac{1}{3} \boldsymbol{m}_b \\ + * \boldsymbol{c}_3 & = & \boldsymbol{p}_b + * \end{array} + * @f] + * + * Enabled only on @ref CubicBezier for @ref CubicHermite with vector + * underlying types. See @ref CubicHermite::fromBezier() for the + * inverse operation. + */ + template static + #ifndef DOXYGEN_GENERATING_OUTPUT + typename std::enable_if, VectorType>::value && order == 3, Bezier>::type + #else + Bezier + #endif + fromCubicHermite(const CubicHermite& a, const CubicHermite& b) { + return {a.point(), a.outTangent()/T(3) - a.point(), b.point() - b.inTangent()/T(3), b.point()}; + } + /** * @brief Default constructor * diff --git a/src/Magnum/Math/CubicHermite.h b/src/Magnum/Math/CubicHermite.h index e14d909e1..6d4022fb0 100644 --- a/src/Magnum/Math/CubicHermite.h +++ b/src/Magnum/Math/CubicHermite.h @@ -46,6 +46,9 @@ animation keyframe representation. The structure assumes the in/out tangents to be in their final form, i.e. already normalized by length of their adjacent segments. +Cubic Hermite splines are fully interchangeable with cubic Bézier curves, use +@ref fromBezier() and @ref Bezier::fromCubicHermite() for the conversion. + @see @ref CubicHermite2D, @ref CubicHermite3D, @ref Magnum::CubicHermite2D, @ref Magnum::CubicHermite2Dd, @ref Magnum::CubicHermite3D, @ref Magnum::CubicHermite3Dd, @@ -56,6 +59,46 @@ template class CubicHermite { public: typedef T Type; /**< @brief Underlying data type */ + /** + * @brief Create cubic Hermite spline point from adjacent Bézier curve segments + * + * Given two adjacent cubic Bézier curve segments defined by points + * @f$ \boldsymbol{a}_i @f$ and @f$ \boldsymbol{b}_i @f$, + * @f$ i \in \{ 0, 1, 2, 3 \} @f$, the corresponding cubic Hermite + * spline point @f$ \boldsymbol{p} @f$, in-tangent @f$ \boldsymbol{m} @f$ + * and out-tangent @f$ \boldsymbol{n} @f$ is defined as: @f[ + * \begin{array}{rcl} + * \boldsymbol{m} & = & 3 (\boldsymbol{a}_3 - \boldsymbol{a}_2) + * = 3 (\boldsymbol{b}_0 - \boldsymbol{a}_2) \\ + * \boldsymbol{p} & = & \boldsymbol{a}_3 = \boldsymbol{b}_0 \\ + * \boldsymbol{n} & = & 3 (\boldsymbol{b}_1 - \boldsymbol{a}_3) + * = 3 (\boldsymbol{b}_1 - \boldsymbol{b}_0) + * \end{array} + * @f] + * + * Expects that the two segments are adjacent (i.e., the endpoint of + * first segment is the start point of the second). If you need to + * create a cubic Hermite spline point that's at the beginning or at + * the end of a curve, simply pass a dummy Bézier segment that + * satisfies this constraint as the other parameter: + * + * @snippet MagnumMath.cpp CubicHermite-fromBezier + * + * Enabled only on vector underlying types. See + * @ref Bezier::fromCubicHermite() for the inverse operation. + */ + template static + #ifdef DOXYGEN_GENERATING_OUTPUT + CubicHermite + #else + typename std::enable_if, T>::value, CubicHermite>::type + #endif + fromBezier(const CubicBezier& a, const CubicBezier& b) { + return CORRADE_CONSTEXPR_ASSERT(a[3] == b[0], + "Math::CubicHermite::fromBezier(): segments are not adjacent"), + CubicHermite{3*(a[3] - a[2]), a[3], 3*(b[1] - a[3])}; + } + /** * @brief Default constructor * diff --git a/src/Magnum/Math/Test/BezierTest.cpp b/src/Magnum/Math/Test/BezierTest.cpp index 3080afea2..6bc198d08 100644 --- a/src/Magnum/Math/Test/BezierTest.cpp +++ b/src/Magnum/Math/Test/BezierTest.cpp @@ -29,6 +29,7 @@ #include #include "Magnum/Math/Bezier.h" +#include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/Vector2.h" #include "Magnum/Math/Functions.h" @@ -60,6 +61,7 @@ typedef Math::Bezier<1, 2, Float> LinearBezier2D; typedef Math::QuadraticBezier2D QuadraticBezier2D; typedef Math::QuadraticBezier2D QuadraticBezier2Dd; typedef Math::CubicBezier2D CubicBezier2D; +typedef Math::CubicHermite2D CubicHermite2D; struct BezierTest: Corrade::TestSuite::Tester { explicit BezierTest(); @@ -68,6 +70,7 @@ struct BezierTest: Corrade::TestSuite::Tester { void constructDefault(); void constructNoInit(); void constructConversion(); + void constructFromCubicHermite(); void constructCopy(); void convert(); @@ -91,6 +94,7 @@ BezierTest::BezierTest() { &BezierTest::constructDefault, &BezierTest::constructNoInit, &BezierTest::constructConversion, + &BezierTest::constructFromCubicHermite, &BezierTest::constructCopy, &BezierTest::convert, @@ -160,6 +164,16 @@ void BezierTest::constructConversion() { CORRADE_VERIFY((std::is_nothrow_constructible::value)); } +void BezierTest::constructFromCubicHermite() { + /* See CubicHermiterTest::constructFromBezier() for the inverse. Expected + value the same as in valueCubic() to test also interpolation with it. */ + CubicHermite2D a{{}, Vector2{0.0f, 0.0f}, Vector2{30.0f, 45.0f}}; + CubicHermite2D b{Vector2{-45, -72}, Vector2{5.0f, -20.0f}, {}}; + auto bezier = CubicBezier2D::fromCubicHermite(a, b); + + CORRADE_COMPARE(bezier, (CubicBezier2D{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}})); +} + void BezierTest::constructCopy() { constexpr QuadraticBezier2D a{Vector2{0.5f, 1.0f}, Vector2{1.1f, 0.3f}, Vector2{0.1f, 1.2f}}; constexpr QuadraticBezier2D b{a}; @@ -237,6 +251,7 @@ void BezierTest::valueQuadratic() { void BezierTest::valueCubic() { CubicBezier2D bezier{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}}; + /* Values should be exactly the same as in CubicHermiterTest::splerpVectorFromBezier() */ CORRADE_COMPARE(bezier.value(0.0f), (Vector2{0.0f, 0.0f})); CORRADE_COMPARE(bezier.value(0.2f), (Vector2{5.8f, 5.984f})); CORRADE_COMPARE(bezier.value(0.5f), (Vector2{11.875f, 4.625f})); diff --git a/src/Magnum/Math/Test/CubicHermiteTest.cpp b/src/Magnum/Math/Test/CubicHermiteTest.cpp index ea3fd5213..d27dc94cb 100644 --- a/src/Magnum/Math/Test/CubicHermiteTest.cpp +++ b/src/Magnum/Math/Test/CubicHermiteTest.cpp @@ -26,6 +26,7 @@ #include #include +#include "Magnum/Math/Bezier.h" #include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/Functions.h" #include "Magnum/Math/Vector2.h" @@ -65,6 +66,8 @@ struct CubicHermiteTest: Corrade::TestSuite::Tester { void constructConversionComplex(); void constructConversionQuaternion(); + void constructFromBezier(); + void constructCopyScalar(); void constructCopyVector(); void constructCopyComplex(); @@ -94,6 +97,7 @@ struct CubicHermiteTest: Corrade::TestSuite::Tester { void splerpScalar(); void splerpVector(); + void splerpVectorFromBezier(); void splerpComplex(); void splerpComplexNotNormalized(); void splerpQuaternion(); @@ -136,6 +140,8 @@ CubicHermiteTest::CubicHermiteTest() { &CubicHermiteTest::constructConversionComplex, &CubicHermiteTest::constructConversionQuaternion, + &CubicHermiteTest::constructFromBezier, + &CubicHermiteTest::constructCopyScalar, &CubicHermiteTest::constructCopyVector, &CubicHermiteTest::constructCopyComplex, @@ -165,6 +171,7 @@ CubicHermiteTest::CubicHermiteTest() { &CubicHermiteTest::splerpScalar, &CubicHermiteTest::splerpVector, + &CubicHermiteTest::splerpVectorFromBezier, &CubicHermiteTest::splerpComplex, &CubicHermiteTest::splerpComplexNotNormalized, &CubicHermiteTest::splerpQuaternion, @@ -179,6 +186,7 @@ CubicHermiteTest::CubicHermiteTest() { typedef Math::Vector2 Vector2; typedef Math::Complex Complex; typedef Math::Quaternion Quaternion; +typedef Math::CubicBezier2D CubicBezier2D; typedef Math::CubicHermite1D CubicHermite1D; typedef Math::CubicHermite2D CubicHermite2D; typedef Math::CubicHermiteComplex CubicHermiteComplex; @@ -499,6 +507,20 @@ void CubicHermiteTest::constructConversionQuaternion() { CORRADE_VERIFY((std::is_nothrow_constructible::value)); } +void CubicHermiteTest::constructFromBezier() { + /* Taken from BezierTest::valueCubic() -- we're testing the same values + also in splerpVectorFromBezier(). See + BezierTest::constructFromCubicHermite() for the inverse. */ + CubicBezier2D bezier{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}}; + auto a = CubicHermite2D::fromBezier({Vector2{}, Vector2{}, Vector2{}, bezier[0]}, bezier); + auto b = CubicHermite2D::fromBezier(bezier, {bezier[3], Vector2{}, Vector2{}, Vector2{}}); + + CORRADE_COMPARE(a.point(), bezier[0]); + CORRADE_COMPARE(a.outTangent(), (Vector2{30.0f, 45.0f})); + CORRADE_COMPARE(b.inTangent(), (Vector2{-45, -72})); + CORRADE_COMPARE(b.point(), bezier[3]); +} + void CubicHermiteTest::constructCopyScalar() { constexpr CubicHermite1D a{2.0f, -2.0f, -0.5f}; constexpr CubicHermite1D b{a}; @@ -815,6 +837,25 @@ void CubicHermiteTest::splerpVector() { CORRADE_COMPARE(Math::splerp(a, b, 0.8f), (Vector2{-2.152f, 0.9576f})); } +void CubicHermiteTest::splerpVectorFromBezier() { + /* Taken from BezierTest::valueCubic() */ + CubicBezier2D bezier{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}}; + auto a = CubicHermite2D::fromBezier({Vector2{}, Vector2{}, Vector2{}, bezier[0]}, bezier); + auto b = CubicHermite2D::fromBezier(bezier, {bezier[3], Vector2{}, Vector2{}, Vector2{}}); + + CORRADE_COMPARE(bezier.value(0.0f), (Vector2{0.0f, 0.0f})); + CORRADE_COMPARE(Math::splerp(a, b, 0.0f), (Vector2{0.0f, 0.0f})); + + CORRADE_COMPARE(bezier.value(0.2f), (Vector2{5.8f, 5.984f})); + CORRADE_COMPARE(Math::splerp(a, b, 0.2f), (Vector2{5.8f, 5.984f})); + + CORRADE_COMPARE(bezier.value(0.5f), (Vector2{11.875f, 4.625f})); + CORRADE_COMPARE(Math::splerp(a, b, 0.5f), (Vector2{11.875f, 4.625f})); + + CORRADE_COMPARE(bezier.value(1.0f), (Vector2{5.0f, -20.0f})); + CORRADE_COMPARE(Math::splerp(a, b, 1.0f), (Vector2{5.0f, -20.0f})); +} + void CubicHermiteTest::splerpComplex() { CubicHermiteComplex a{{2.0f, 1.5f}, {0.999445f, 0.0333148f}, {-1.0f, 0.0f}}; CubicHermiteComplex b{{5.0f, 0.3f}, {-0.876216f, 0.481919f}, {1.5f, 0.3f}}; From 68f34b3283ac04c5d6ec5c8ce318f182b8ba9275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 1 Sep 2018 02:22:37 +0200 Subject: [PATCH 13/26] Math: STUPID EFFING DOXYGEN GODDAMIT I HATE YA WITH PASSION --- src/Magnum/Math/Vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magnum/Math/Vector.h b/src/Magnum/Math/Vector.h index 2ffe11c84..0fc74ba3d 100644 --- a/src/Magnum/Math/Vector.h +++ b/src/Magnum/Math/Vector.h @@ -72,7 +72,7 @@ namespace Implementation { Returns `0` when two vectors are perpendicular, `> 0` when two vectors are in the same general direction, `1` when two *normalized* vectors are parallel, `< 0` when two vectors are in opposite general direction and `-1` when two -*normalized* vectors are antiparallel. @f[ +* *normalized* vectors are antiparallel. @f[ \boldsymbol a \cdot \boldsymbol b = \sum_{i=0}^{n-1} \boldsymbol a_i \boldsymbol b_i @f] @see @ref Vector::dot() const, @ref Vector::operator-(), @ref Vector2::perpendicular() From f583d1c853089c23e49b58c5c72ec8141f522a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 13:25:48 +0200 Subject: [PATCH 14/26] Math: cleaned up the sclerp() test. No need to have the precision so crazy. --- src/Magnum/Math/Test/DualQuaternionTest.cpp | 32 ++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Magnum/Math/Test/DualQuaternionTest.cpp b/src/Magnum/Math/Test/DualQuaternionTest.cpp index 578342963..17b062651 100644 --- a/src/Magnum/Math/Test/DualQuaternionTest.cpp +++ b/src/Magnum/Math/Test/DualQuaternionTest.cpp @@ -479,15 +479,20 @@ void DualQuaternionTest::transformPointNormalized() { } void DualQuaternionTest::sclerp() { - const DualQuaternion from = DualQuaternion::translation(Vector3{20.0f, .0f, .0f})*DualQuaternion::rotation(180.0_degf, Vector3{.0f, 1.0f, .0f}); - const DualQuaternion to = DualQuaternion::translation(Vector3{42.0f, 42.0f, 42.0f})*DualQuaternion::rotation(75.0_degf, Vector3{1.0f, .0f, .0f}); - - constexpr DualQuaternion expected1{Quaternion{{.23296291314453416f, .9238795325112867f, .0f}, .303603179340959f}, - Quaternion{{2.235619101917766f, 2.8169719855488395f, 10.722240915237789f}, -10.287636336847847f}}; - constexpr DualQuaternion expected2{Quaternion{{.4437679833315842f, .6845471059286887f, .0f}, .5783296955322937f}, - Quaternion{{5.764394870292371f, 11.161306653193549f, 9.671267015501789f}, -17.634394590712066f}}; - constexpr DualQuaternion expected3{Quaternion{{.5979785904506439f, .18738131458572468f, .0f}, .7793008714910992f}, - Quaternion{{13.409627907069353f, 25.452124456683414f, 5.681581047706807f}, -16.409481115504978f}}; + auto from = DualQuaternion::translation({20.0f, 0.0f, 0.0f})* + DualQuaternion::rotation(180.0_degf, Vector3::yAxis()); + auto to = DualQuaternion::translation({42.0f, 42.0f, 42.0f})* + DualQuaternion::rotation(75.0_degf, Vector3::xAxis()); + + DualQuaternion expected1{ + {{0.23296291f, 0.92387953f, 0.0f}, 0.30360317f}, + {{2.23561910f, 2.81697198f, 10.72224091f}, -10.28763633f}}; + DualQuaternion expected2{ + {{0.44376798f, 0.68454710f, 0.0f}, 0.57832969f}, + {{5.76439487f, 11.16130665f, 9.67126701f}, -17.63439459f}}; + DualQuaternion expected3{ + {{0.59797859f, 0.18738131f, 0.0f}, 0.77930087f}, + {{13.40962790f, 25.45212445f, 5.68158104f}, -16.40948111f}}; const DualQuaternion interp1 = Math::sclerp(from, to, 0.25f); const DualQuaternion interp2 = Math::sclerp(from, to, 0.52f); @@ -501,12 +506,13 @@ void DualQuaternionTest::sclerp() { /* Dual quaternions with identical rotation */ CORRADE_COMPARE(Math::sclerp(from, from, 0.42f), from); - CORRADE_COMPARE(Math::sclerp(from, DualQuaternion(-from.real(), from.dual()), 0.42f), from); + CORRADE_COMPARE(Math::sclerp(from, {-from.real(), from.dual()}, 0.42f), from); /* No difference in rotation, but in translation */ - const auto rotation = DualQuaternion::rotation(35.0_degf, Vector3{0.3f, 0.2f, 0.1f}); - CORRADE_COMPARE(Math::sclerp(DualQuaternion::translation(Vector3{1.0f, 2.0f, 4.0f})*rotation, DualQuaternion::translation(Vector3{5, -6, 2})*rotation, 0.25f), - DualQuaternion::translation(Vector3{2.0f, 0.0f, 3.5f})*rotation); + const auto rotation = DualQuaternion::rotation(35.0_degf, {0.3f, 0.2f, 0.1f}); + CORRADE_COMPARE(Math::sclerp( + DualQuaternion::translation({1.0f, 2.0f, 4.0f})*rotation, DualQuaternion::translation({5.0f, -6.0f, 2.0f})*rotation, 0.25f), + DualQuaternion::translation({2.0f, 0.0f, 3.5f})*rotation); } void DualQuaternionTest::debug() { From 425d31fb5cfc757835bfeded0f943bb06e42505e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 14:51:02 +0200 Subject: [PATCH 15/26] Math: no, this is testing it wrong. And causes an assertion message. Obviously the test doesn't pass anymore after this fix. --- src/Magnum/Math/Test/DualQuaternionTest.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Magnum/Math/Test/DualQuaternionTest.cpp b/src/Magnum/Math/Test/DualQuaternionTest.cpp index 17b062651..9b241b99d 100644 --- a/src/Magnum/Math/Test/DualQuaternionTest.cpp +++ b/src/Magnum/Math/Test/DualQuaternionTest.cpp @@ -506,13 +506,14 @@ void DualQuaternionTest::sclerp() { /* Dual quaternions with identical rotation */ CORRADE_COMPARE(Math::sclerp(from, from, 0.42f), from); - CORRADE_COMPARE(Math::sclerp(from, {-from.real(), from.dual()}, 0.42f), from); + CORRADE_COMPARE(Math::sclerp(from, -from, 0.42f), from); /* No difference in rotation, but in translation */ - const auto rotation = DualQuaternion::rotation(35.0_degf, {0.3f, 0.2f, 0.1f}); - CORRADE_COMPARE(Math::sclerp( - DualQuaternion::translation({1.0f, 2.0f, 4.0f})*rotation, DualQuaternion::translation({5.0f, -6.0f, 2.0f})*rotation, 0.25f), - DualQuaternion::translation({2.0f, 0.0f, 3.5f})*rotation); + auto rotation = DualQuaternion::rotation(35.0_degf, Vector3{0.3f, 0.2f, 0.1f}.normalized()); + auto interpolateTranslation = Math::sclerp( + DualQuaternion::translation({1.0f, 2.0f, 4.0f})*rotation, DualQuaternion::translation({5.0f, -6.0f, 2.0f})*rotation, 0.25f); + CORRADE_VERIFY(interpolateTranslation.isNormalized()); + CORRADE_COMPARE(interpolateTranslation, DualQuaternion::translation({2.0f, 0.0f, 3.5f})*rotation); } void DualQuaternionTest::debug() { From d156a60943c60373d53216d8f1a24a845568e39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 14:52:39 +0200 Subject: [PATCH 16/26] Math: fix sclerp() behavior with dual quaternions having the same angle. The fix done with https://github.com/mosra/magnum/pull/122 (0e05c7289e21b21596161e8f3c30cc3da6f624ec) was not tested properly (see previous commit) and thus this code path never worked. This properly lerps the translation part and recombines it with the rotation instead of interpolating just a part of it. Also I'm no longer having any "dotResult" that's done only on the vector part of the rotation, but instead using the full rotation quaternion dot product. I have no idea why it was done this way. This branch was also never properly tested -- it'll be with the introduction of "shortest path" variants in the next commit. --- doc/changelog.dox | 2 ++ src/Magnum/Math/DualQuaternion.h | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 410c51cfe..e67f6c82f 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -307,6 +307,8 @@ See also: - @ref Platform::GlfwApplication::exec() now asserts instead of crashing if the constructor fails to create a window (see [mosra/magnum#192](https://github.com/mosra/magnum/issues/192), [mosra/magnum#272](https://github.com/mosra/magnum/pull/272)) +- @ref Math::sclerp() was not properly interpolating the translation if + rotation was the same on both sides @subsection changelog-latest-docs Documentation diff --git a/src/Magnum/Math/DualQuaternion.h b/src/Magnum/Math/DualQuaternion.h index 81c4a95ae..d8f45269b 100644 --- a/src/Magnum/Math/DualQuaternion.h +++ b/src/Magnum/Math/DualQuaternion.h @@ -66,15 +66,15 @@ Expects that both dual quaternions are normalized. @f[ template inline DualQuaternion sclerp(const DualQuaternion& normalizedA, const DualQuaternion& normalizedB, const T t) { CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), "Math::sclerp(): dual quaternions must be normalized", {}); - const T dotResult = dot(normalizedA.real().vector(), normalizedB.real().vector()); + const T cosHalfAngle = dot(normalizedA.real(), normalizedB.real()); - /* Avoid division by zero */ - const T cosHalfAngle = dotResult + normalizedA.real().scalar()*normalizedB.real().scalar(); + /* Avoid division by zero: interpolate just the translation part */ + /** @todo could this be optimized somehow? */ if(std::abs(cosHalfAngle) >= T(1)) - return {normalizedA.real(), {Implementation::lerp(normalizedA.dual().vector(), normalizedB.dual().vector(), t), T(0)}}; + return DualQuaternion::translation(Implementation::lerp(normalizedA.translation(), normalizedB.translation(), t))*DualQuaternion{normalizedA.real()}; /* l + εm = q_A^**q_B, multiplying with -1 ensures shortest path when dot < 0 */ - const DualQuaternion diff = normalizedA.quaternionConjugated()*(dotResult < T(0) ? -normalizedB : normalizedB); + const DualQuaternion diff = normalizedA.quaternionConjugated()*(cosHalfAngle < T(0) ? -normalizedB : normalizedB); const Quaternion& l = diff.real(); const Quaternion& m = diff.dual(); From 737104f2c7401a05d873accc4c6dd4188736f221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 15:25:35 +0200 Subject: [PATCH 17/26] Math: added "shortest path" alternatives to lerp(), slerp() and sclerp(). Before neither of the lerp(), slerp() had the shortest path check, while sclerp() had it. Now, to be consistent, none of them has it and there are lerpShortestPath(), slerpShortestPath() and sclerpShortestPath() functions that have the shortest path check. This is different from other engines, where there's usually only the shortest path interpolation by default and either an optional "non-shortest-path" interpolation or no alternative at all. I like to give the users a choice, so there's both versions and the non-shortest-path version is the default, because -- at least in case of lerp() -- this results in a quite significant perf difference (15% faster), so why not have it. Preprocess your data instead ;) --- doc/changelog.dox | 5 + src/Magnum/Math/DualQuaternion.h | 107 +++++++++++++++++--- src/Magnum/Math/Quaternion.h | 102 ++++++++++++++++++- src/Magnum/Math/Test/DualQuaternionTest.cpp | 84 ++++++++++++--- src/Magnum/Math/Test/QuaternionTest.cpp | 62 +++++++++++- 5 files changed, 328 insertions(+), 32 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index e67f6c82f..f9a8a8deb 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -72,6 +72,11 @@ See also: - Added @ref Math::lerp(const Complex&, const Complex&, T) and @ref Math::slerp(const Complex&, const Complex&, T) for feature parity with @ref Math::Quaternion +- Added @ref Math::lerpShortestPath(const Quaternion&, const Quaternion&, T), + @ref Math::slerpShortestPath(const Quaternion&, const Quaternion&, T) + and @ref Math::sclerpShortestPath(const DualQuaternion&, const DualQuaternion&, T) + variants; clarified that the original versions are explicitly *not* + shortest-path - Added @ref Math::Range2D::x(), @ref Math::Range3D::x(), @ref Math::Range2D::y(), @ref Math::Range3D::y(), @ref Math::Range3D::z() and @ref Math::Range3D::y() for slicing ranges into lower dimensions diff --git a/src/Magnum/Math/DualQuaternion.h b/src/Magnum/Math/DualQuaternion.h index d8f45269b..5191b563d 100644 --- a/src/Magnum/Math/DualQuaternion.h +++ b/src/Magnum/Math/DualQuaternion.h @@ -48,26 +48,107 @@ namespace Implementation { @param normalizedB Second dual quaternion @param t Interpolation phase (from range @f$ [0; 1] @f$) -Expects that both dual quaternions are normalized. @f[ -\begin{array}{rcl} - l + \epsilon m & = & \hat q_A^* \hat q_B \\ - \frac{\hat a} 2 & = & acos \left( l_S \right) - \epsilon m_S \frac 1 {|l_V|} \\ - \hat {\boldsymbol n} & = & \boldsymbol n_0 + \epsilon \boldsymbol n_\epsilon - ~~~~~~~~ \boldsymbol n_0 = l_V \frac 1 {|l_V|} - ~~~~~~~~ \boldsymbol n_\epsilon = \left( m_V - {\boldsymbol n}_0 \frac {a_\epsilon} 2 l_S \right)\frac 1 {|l_V|} \\ - {\hat q}_{ScLERP} & = & \hat q_A (\hat q_A^* \hat q_B)^t = - \hat q_A \left[ \hat {\boldsymbol n} sin \left( t \frac {\hat a} 2 \right), cos \left( t \frac {\hat a} 2 \right) \right] \\ -\end{array} +Expects that both dual quaternions are normalized. If the real parts are the +same or one is a negation of the other, returns the @ref DualQuaternion::rotation() +(real) part combined with interpolated @ref DualQuaternion::translation(): @f[ + \begin{array}{rcl} + d & = & q_{A_0} \cdot q_{B_0} \\[5pt] + {\hat q}_{ScLERP} & = & 2 \left[(1 - t)(q_{A_\epsilon} q_{A_0}^*)_V + t (q_{B_\epsilon} q_{B_0}^*)_V \right] q_A, ~ {\color{m-primary} \text{if} ~ d \ge 1} + \end{array} @f] -@see @ref DualQuaternion::isNormalized(), - @ref slerp(const Quaternion&, const Quaternion&, T), - @ref lerp(const T&, const T&, U) + +@m_class{m-noindent} + +otherwise, the interpolation is performed as: @f[ + \begin{array}{rcl} + l + \epsilon m & = & \hat q_A^* \hat q_B \\[5pt] + \frac{\hat a} 2 & = & \arccos \left( l_S \right) - \epsilon m_S \frac 1 {|\boldsymbol{l}_V|} \\[5pt] + \hat {\boldsymbol n} & = & \boldsymbol n_0 + \epsilon \boldsymbol n_\epsilon, + ~~~~~~~~ \boldsymbol n_0 = \boldsymbol{l}_V \frac 1 {|\boldsymbol{l}_V|}, + ~~~~~~~~ \boldsymbol n_\epsilon = \left(\boldsymbol{m}_V - {\boldsymbol n}_0 \frac {a_\epsilon} 2 l_S \right)\frac 1 {|\boldsymbol{l}_V|} \\[5pt] + {\hat q}_{ScLERP} & = & \hat q_A (\hat q_A^* \hat q_B)^t = + \hat q_A \left[ \hat {\boldsymbol n} \sin \left( t \frac {\hat a} 2 \right), \cos \left( t \frac {\hat a} 2 \right) \right] + \end{array} +@f] + +Note that this function does not check for shortest path interpolation, see +@ref sclerpShortestPath() for an alternative. +@see @ref DualQuaternion::isNormalized(), @ref DualQuaternion::quaternionConjugated(), + @ref lerp(const Quaternion&, const Quaternion&, T), + @ref slerp(const Quaternion&, const Quaternion&, T) */ template inline DualQuaternion sclerp(const DualQuaternion& normalizedA, const DualQuaternion& normalizedB, const T t) { CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), "Math::sclerp(): dual quaternions must be normalized", {}); const T cosHalfAngle = dot(normalizedA.real(), normalizedB.real()); + /* Avoid division by zero: interpolate just the translation part */ + /** @todo could this be optimized somehow? */ + if(std::abs(cosHalfAngle) >= T(1)) + return DualQuaternion::translation(Implementation::lerp(normalizedA.translation(), normalizedB.translation(), t))*DualQuaternion{normalizedA.real()}; + + /* l + εm = q_A^**q_B */ + const DualQuaternion diff = normalizedA.quaternionConjugated()*normalizedB; + const Quaternion& l = diff.real(); + const Quaternion& m = diff.dual(); + + /* a/2 = acos(l_S) - εm_S/|l_V| */ + const T invr = l.vector().lengthInverted(); + const Dual aHalf{std::acos(l.scalar()), -m.scalar()*invr}; + + /* direction = n_0 = l_V/|l_V| + moment = n_ε = (m_V - n_0*(a_ε/2)*l_S)/|l_V| */ + const Vector3 direction = l.vector()*invr; + const Vector3 moment = (m.vector() - direction*(aHalf.dual()*l.scalar()))*invr; + const Dual> n{direction, moment}; + + /* q_ScLERP = q_A*(cos(t*a/2) + n*sin(t*a/2)) */ + Dual sin, cos; + std::tie(sin, cos) = Math::sincos(t*Dual>(aHalf)); + return normalizedA*DualQuaternion{n*sin, cos}; +} + +/** @relatesalso DualQuaternion +@brief Screw linear shortest-path interpolation of two dual quaternions +@param normalizedA First dual quaternion +@param normalizedB Second dual quaternion +@param t Interpolation phase (from range @f$ [0; 1] @f$) + +Unlike @ref sclerp(const DualQuaternion&, const DualQuaternion&, T) this +function interpolates on the shortest path. Expects that both dual quaternions +are normalized. If the real parts are the same or one is a negation of the +other, returns the @ref DualQuaternion::rotation() (real) part combined with +interpolated @ref DualQuaternion::translation(): @f[ + \begin{array}{rcl} + d & = & q_{A_0} \cdot q_{B_0} \\[5pt] + {\hat q}_{ScLERP} & = & 2 \left((1 - t)(q_{A_\epsilon} q_{A_0}^*)_V + t (q_{B_\epsilon} q_{B_0}^*)_V \right) (q_{A_0} + \epsilon [\boldsymbol{0}, 0]), ~ {\color{m-primary} \text{if} ~ d \ge 1} + \end{array} +@f] + +@m_class{m-noindent} + +otherwise, the interpolation is performed as: @f[ + \begin{array}{rcl} + l + \epsilon m & = & \begin{cases} + \phantom{-}\hat q_A^* \hat q_B, & d \ge 0 \\ + -\hat q_A^* \hat q_B, & d < 0 \\ + \end{cases} \\[15pt] + \frac{\hat a} 2 & = & \arccos \left( l_S \right) - \epsilon m_S \frac 1 {|\boldsymbol{l}_V|} \\[5pt] + \hat {\boldsymbol n} & = & \boldsymbol n_0 + \epsilon \boldsymbol n_\epsilon, + ~~~~~~~~ \boldsymbol n_0 = \boldsymbol{l}_V \frac 1 {|\boldsymbol{l}_V|}, + ~~~~~~~~ \boldsymbol n_\epsilon = \left(\boldsymbol{m}_V - {\boldsymbol n}_0 \frac {a_\epsilon} 2 l_S \right)\frac 1 {|\boldsymbol{l}_V|} \\[5pt] + {\hat q}_{ScLERP} & = & \hat q_A (\hat q_A^* \hat q_B)^t = + \hat q_A \left[ \hat {\boldsymbol n} \sin \left( t \frac {\hat a} 2 \right), \cos \left( t \frac {\hat a} 2 \right) \right] + \end{array} +@f] +@see @ref DualQuaternion::isNormalized(), @ref lerpShortestPath(), + @ref slerpShortestPath() +*/ +template inline DualQuaternion sclerpShortestPath(const DualQuaternion& normalizedA, const DualQuaternion& normalizedB, const T t) { + CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), + "Math::sclerp(): dual quaternions must be normalized", {}); + const T cosHalfAngle = dot(normalizedA.real(), normalizedB.real()); + /* Avoid division by zero: interpolate just the translation part */ /** @todo could this be optimized somehow? */ if(std::abs(cosHalfAngle) >= T(1)) diff --git a/src/Magnum/Math/Quaternion.h b/src/Magnum/Math/Quaternion.h index 8917a5549..528bbbbea 100644 --- a/src/Magnum/Math/Quaternion.h +++ b/src/Magnum/Math/Quaternion.h @@ -87,6 +87,9 @@ template inline Rad angle(const Quaternion& normalizedA, const Qu Expects that both quaternions are normalized. @f[ q_{LERP} = \frac{(1 - t) q_A + t q_B}{|(1 - t) q_A + t q_B|} @f] + +Note that this function does not check for shortest path interpolation, see +@ref lerpShortestPath() for an alternative. @see @ref Quaternion::isNormalized(), @ref slerp(const Quaternion&, const Quaternion&, T), @ref sclerp(), @ref lerp(const T&, const T&, U), @@ -101,6 +104,31 @@ template inline Quaternion lerp(const Quaternion& normalizedA, co return ((T(1) - t)*normalizedA + t*normalizedB).normalized(); } +/** @relatesalso Quaternion +@brief Linear shortest-path interpolation of two quaternions +@param normalizedA First quaternion +@param normalizedB Second quaternion +@param t Interpolation phase (from range @f$ [0; 1] @f$) + +Unlike @ref lerp(const Quaternion&, const Quaternion&, T), this +interpolates on the shortest path at some performance expense. Expects that +both quaternions are normalized. @f[ + \begin{array}{rcl} + d & = & q_A \cdot q_B \\[5pt] + q'_A & = & \begin{cases} + \phantom{-}q_A, & d \ge 0 \\ + -q_A, & d < 0 + \end{cases} \\[15pt] + q_{LERP} & = & \cfrac{(1 - t) q'_A + t q_B}{|(1 - t) q'_A + t q_B|} + \end{array} +@f] +@see @ref Quaternion::isNormalized(), @ref slerpShortestPath(), + @ref sclerpShortestPath() +*/ +template inline Quaternion lerpShortestPath(const Quaternion& normalizedA, const Quaternion& normalizedB, T t) { + return lerp(dot(normalizedA, normalizedB) < T(0) ? -normalizedA : normalizedA, normalizedB, t); +} + /** @relatesalso Quaternion @brief Spherical linear interpolation of two quaternions @param normalizedA First quaternion @@ -108,11 +136,24 @@ template inline Quaternion lerp(const Quaternion& normalizedA, co @param t Interpolation phase (from range @f$ [0; 1] @f$) Expects that both quaternions are normalized. If the quaternions are the same -or one is a negation of the other, returns the first argument. @f[ - q_{SLERP} = \frac{sin((1 - t) \theta) q_A + sin(t \theta) q_B}{sin \theta} - ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ - \theta = acos \left( \frac{q_A \cdot q_B}{|q_A| \cdot |q_B|} \right) = acos(q_A \cdot q_B) +or one is a negation of the other, it just returns the first argument: @f[ + \begin{array}{rcl} + d & = & q_A \cdot q_B \\[5pt] + q_{SLERP} & = & q_A, ~ {\color{m-primary} \text{if} ~ d \ge 1} + \end{array} @f] + +@m_class{m-noindent} + +otherwise, the interpolation is performed as: @f[ + \begin{array}{rcl} + \theta & = & \arccos \left( \frac{q_A \cdot q_B}{|q_A| |q_B|} \right) = \arccos(q_A \cdot q_B) = \arccos(d) \\[5pt] + q_{SLERP} & = & \cfrac{\sin((1 - t) \theta) q_A + \sin(t \theta) q_B}{\sin(\theta)} + \end{array} +@f] + +Note that this function does not check for shortest path interpolation, see +@ref slerpShortestPath() for an alternative. @see @ref Quaternion::isNormalized(), @ref lerp(const Quaternion&, const Quaternion&, T), @ref slerp(const Complex&, const Complex&, T), @ref sclerp() */ @@ -128,11 +169,62 @@ template inline Quaternion slerp(const Quaternion& normalizedA, c return (std::sin((T(1) - t)*a)*normalizedA + std::sin(t*a)*normalizedB)/std::sin(a); } +/** @relatesalso Quaternion +@brief Spherical linear shortest-path interpolation of two quaternions +@param normalizedA First quaternion +@param normalizedB Second quaternion +@param t Interpolation phase (from range @f$ [0; 1] @f$) + +Unlike @ref slerp(const Quaternion&, const Quaternion&, T) this function +interpolates on the shortest path. Expects that both quaternions are +normalized. If the quaternions are the same or one is a negation of the other, +it just returns the first argument: @f[ + \begin{array}{rcl} + d & = & q_A \cdot q_B \\ + q_{SLERP} & = & q_A, ~ {\color{m-primary} \text{if} ~ d \ge 1} + \end{array} +@f] + +@m_class{m-noindent} + +otherwise, the interpolation is performed as: @f[ + \begin{array}{rcl} + q'_A & = & \begin{cases} + \phantom{-}q_A, & d \ge 0 \\ + -q_A, & d < 0 + \end{cases} \\[15pt] + \theta & = & \arccos \left( \frac{|q'_A \cdot q_B|}{|q'_A| |q_B|} \right) = \arccos(|q'_A \cdot q_B|) = \arccos(|d|) \\[5pt] + q_{SLERP} & = & \cfrac{\sin((1 - t) \theta) q'_A + \sin(t \theta) q_B}{\sin(\theta)} + \end{array} +@f] +@see @ref Quaternion::isNormalized(), @ref lerpShortestPath(), + @ref sclerpShortestPath() +*/ +template inline Quaternion slerpShortestPath(const Quaternion& normalizedA, const Quaternion& normalizedB, T t) { + CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), + "Math::slerp(): quaternions must be normalized", {}); + const T cosHalfAngle = dot(normalizedA, normalizedB); + + /* Avoid division by zero */ + if(std::abs(cosHalfAngle) >= T(1)) return Quaternion{normalizedA}; + + const Quaternion shortestNormalizedA = cosHalfAngle < 0 ? -normalizedA : normalizedA; + + const T a = std::acos(std::abs(cosHalfAngle)); + return (std::sin((T(1) - t)*a)*shortestNormalizedA + std::sin(t*a)*normalizedB)/std::sin(a); +} + /** @brief Quaternion @tparam T Underlying data type -Represents 3D rotation. See @ref transformations for brief introduction. +Represents 3D rotation. Usually denoted as the following in equations, with +@f$ \boldsymbol{q}_V @f$ being the @ref vector() part and @f$ q_S @f$ being the +@ref scalar() part: @f[ + q = [\boldsymbol{q}_V, q_S] +@f] + +See @ref transformations for a brief introduction. @see @ref Magnum::Quaternion, @ref Magnum::Quaterniond, @ref DualQuaternion, @ref Matrix4 */ diff --git a/src/Magnum/Math/Test/DualQuaternionTest.cpp b/src/Magnum/Math/Test/DualQuaternionTest.cpp index 9b241b99d..23875683d 100644 --- a/src/Magnum/Math/Test/DualQuaternionTest.cpp +++ b/src/Magnum/Math/Test/DualQuaternionTest.cpp @@ -90,6 +90,7 @@ struct DualQuaternionTest: Corrade::TestSuite::Tester { void transformPointNormalized(); void sclerp(); + void sclerpShortestPath(); void debug(); void configuration(); @@ -144,6 +145,7 @@ DualQuaternionTest::DualQuaternionTest() { &DualQuaternionTest::transformPointNormalized, &DualQuaternionTest::sclerp, + &DualQuaternionTest::sclerpShortestPath, &DualQuaternionTest::debug, &DualQuaternionTest::configuration}); @@ -480,40 +482,98 @@ void DualQuaternionTest::transformPointNormalized() { void DualQuaternionTest::sclerp() { auto from = DualQuaternion::translation({20.0f, 0.0f, 0.0f})* - DualQuaternion::rotation(180.0_degf, Vector3::yAxis()); + DualQuaternion::rotation(65.0_degf, Vector3::yAxis()); auto to = DualQuaternion::translation({42.0f, 42.0f, 42.0f})* DualQuaternion::rotation(75.0_degf, Vector3::xAxis()); + const DualQuaternion begin = Math::sclerp(from, to, 0.0f); + const DualQuaternion beginShortestPath = Math::sclerpShortestPath(from, to, 0.0f); + const DualQuaternion end = Math::sclerp(from, to, 1.0f); + const DualQuaternion endShortestPath = Math::sclerpShortestPath(from, to, 1.0f); + CORRADE_COMPARE(begin, from); + CORRADE_COMPARE(beginShortestPath, from); + CORRADE_COMPARE(end, to); + CORRADE_COMPARE(endShortestPath, to); + DualQuaternion expected1{ - {{0.23296291f, 0.92387953f, 0.0f}, 0.30360317f}, - {{2.23561910f, 2.81697198f, 10.72224091f}, -10.28763633f}}; + {{0.170316f, 0.424975f, 0.0f}, 0.889038f}, + {{10.689f, 7.47059f, 5.33428f}, -5.61881f}}; DualQuaternion expected2{ - {{0.44376798f, 0.68454710f, 0.0f}, 0.57832969f}, - {{5.76439487f, 11.16130665f, 9.67126701f}, -17.63439459f}}; + {{0.34568f, 0.282968f, 0.0f}, 0.89467f}, + {{12.8764f, 15.8357f, 5.03088f}, -9.98371f}}; DualQuaternion expected3{ - {{0.59797859f, 0.18738131f, 0.0f}, 0.77930087f}, - {{13.40962790f, 25.45212445f, 5.68158104f}, -16.40948111f}}; + {{0.550678f, 0.072563f, 0.0f}, 0.831558f}, + {{15.6916f, 26.3477f, 4.23219f}, -12.6905f}}; const DualQuaternion interp1 = Math::sclerp(from, to, 0.25f); + const DualQuaternion interp1ShortestPath = Math::sclerpShortestPath(from, to, 0.25f); const DualQuaternion interp2 = Math::sclerp(from, to, 0.52f); + const DualQuaternion interp2ShortestPath = Math::sclerpShortestPath(from, to, 0.52f); const DualQuaternion interp3 = Math::sclerp(from, to, 0.88f); + const DualQuaternion interp3ShortestPath = Math::sclerpShortestPath(from, to, 0.88f); CORRADE_COMPARE(interp1, expected1); + CORRADE_COMPARE(interp1ShortestPath, expected1); CORRADE_COMPARE(interp2, expected2); + CORRADE_COMPARE(interp2ShortestPath, expected2); CORRADE_COMPARE(interp3, expected3); + CORRADE_COMPARE(interp3ShortestPath, expected3); /* Edge cases: */ /* Dual quaternions with identical rotation */ CORRADE_COMPARE(Math::sclerp(from, from, 0.42f), from); + CORRADE_COMPARE(Math::sclerpShortestPath(from, from, 0.42f), from); CORRADE_COMPARE(Math::sclerp(from, -from, 0.42f), from); + CORRADE_COMPARE(Math::sclerpShortestPath(from, -from, 0.42f), from); /* No difference in rotation, but in translation */ - auto rotation = DualQuaternion::rotation(35.0_degf, Vector3{0.3f, 0.2f, 0.1f}.normalized()); - auto interpolateTranslation = Math::sclerp( - DualQuaternion::translation({1.0f, 2.0f, 4.0f})*rotation, DualQuaternion::translation({5.0f, -6.0f, 2.0f})*rotation, 0.25f); - CORRADE_VERIFY(interpolateTranslation.isNormalized()); - CORRADE_COMPARE(interpolateTranslation, DualQuaternion::translation({2.0f, 0.0f, 3.5f})*rotation); + { + auto rotation = DualQuaternion::rotation(35.0_degf, Vector3{0.3f, 0.2f, 0.1f}.normalized()); + auto a = DualQuaternion::translation({1.0f, 2.0f, 4.0f})*rotation; + auto b = DualQuaternion::translation({5.0f, -6.0f, 2.0f})*rotation; + auto expected = DualQuaternion::translation({2.0f, 0.0f, 3.5f})*rotation; + + auto interpolateTranslation = Math::sclerp(a, b, 0.25f); + auto interpolateTranslationShortestPath = Math::sclerpShortestPath(a, b, 0.25f); + CORRADE_VERIFY(interpolateTranslation.isNormalized()); + CORRADE_VERIFY(interpolateTranslationShortestPath.isNormalized()); + CORRADE_COMPARE(interpolateTranslation, expected); + CORRADE_COMPARE(interpolateTranslationShortestPath, expected); + } +} + +void DualQuaternionTest::sclerpShortestPath() { + DualQuaternion a = DualQuaternion::translation({1.5f, 0.3f, 0.0f})* + DualQuaternion::rotation(0.0_degf, Vector3::zAxis()); + DualQuaternion b = DualQuaternion::translation({3.5f, 0.3f, 1.0f})* + DualQuaternion::rotation(225.0_degf, Vector3::zAxis()); + + DualQuaternion sclerp = Math::sclerp(a, b, 0.25f); + DualQuaternion sclerpShortestPath = Math::sclerpShortestPath(a, b, 0.25f); + + CORRADE_VERIFY(sclerp.isNormalized()); + CORRADE_VERIFY(sclerpShortestPath.isNormalized()); + CORRADE_COMPARE(sclerp.rotation().axis(), Vector3::zAxis()); + /** @todo why is this inverted compared to QuaternionTest::slerpShortestPath()? */ + CORRADE_COMPARE(sclerpShortestPath.rotation().axis(), -Vector3::zAxis()); + CORRADE_COMPARE(sclerp.rotation().angle(), 56.25_degf); + /* Because the axis is inverted, this is also inverted compared to + QuaternionTest::slerpShortestPath() */ + CORRADE_COMPARE(sclerpShortestPath.rotation().angle(), 360.0_degf - 326.25_degf); + + CORRADE_COMPARE(sclerp, (DualQuaternion{ + {{0.0f, 0.0f, 0.471397f}, 0.881921f}, + {{0.536892f, -0.692656f, 0.11024f}, -0.0589246f}})); + /* Also inverted compared to QuaternionTest::slerpShortestPath() */ + CORRADE_COMPARE(sclerpShortestPath, (DualQuaternion{ + {{0.0f, 0.0f, -0.290285f}, 0.95694f}, + {{0.794402f, 0.651539f, 0.119618f}, 0.0362856f}})); + + /* Translation along Z should be the same in both, in 25% of the way. + Translation in the XY plane is along a screw, so that's different. */ + CORRADE_COMPARE(sclerpShortestPath.translation().z(), 0.25f); + CORRADE_COMPARE(sclerpShortestPath.translation().z(), 0.25f); } void DualQuaternionTest::debug() { diff --git a/src/Magnum/Math/Test/QuaternionTest.cpp b/src/Magnum/Math/Test/QuaternionTest.cpp index 202697898..d29b2997d 100644 --- a/src/Magnum/Math/Test/QuaternionTest.cpp +++ b/src/Magnum/Math/Test/QuaternionTest.cpp @@ -89,12 +89,16 @@ struct QuaternionTest: Corrade::TestSuite::Tester { void rotation(); void angle(); void matrix(); + void lerp(); + void lerpShortestPath(); void lerp2D(); void lerpNotNormalized(); void slerp(); + void slerpShortestPath(); void slerp2D(); void slerpNotNormalized(); + void transformVector(); void transformVectorNormalized(); @@ -150,12 +154,16 @@ QuaternionTest::QuaternionTest() { &QuaternionTest::rotation, &QuaternionTest::angle, &QuaternionTest::matrix, + &QuaternionTest::lerp, + &QuaternionTest::lerpShortestPath, &QuaternionTest::lerp2D, &QuaternionTest::lerpNotNormalized, &QuaternionTest::slerp, + &QuaternionTest::slerpShortestPath, &QuaternionTest::slerp2D, &QuaternionTest::slerpNotNormalized, + &QuaternionTest::transformVector, &QuaternionTest::transformVectorNormalized, @@ -481,10 +489,34 @@ void QuaternionTest::matrix() { void QuaternionTest::lerp() { Quaternion a = Quaternion::rotation(15.0_degf, Vector3(1.0f/Constants::sqrt3())); Quaternion b = Quaternion::rotation(23.0_degf, Vector3::xAxis()); + Quaternion lerp = Math::lerp(a, b, 0.35f); + Quaternion lerpShortestPath = Math::lerpShortestPath(a, b, 0.35f); + Quaternion expected{{0.119127f, 0.049134f, 0.049134f}, 0.990445f}; + /* Both should give the same result */ CORRADE_VERIFY(lerp.isNormalized()); - CORRADE_COMPARE(lerp, Quaternion({0.119127f, 0.049134f, 0.049134f}, 0.990445f)); + CORRADE_VERIFY(lerpShortestPath.isNormalized()); + CORRADE_COMPARE(lerp, expected); + CORRADE_COMPARE(lerpShortestPath, expected); +} + +void QuaternionTest::lerpShortestPath() { + Quaternion a = Quaternion::rotation(0.0_degf, Vector3::zAxis()); + Quaternion b = Quaternion::rotation(225.0_degf, Vector3::zAxis()); + + Quaternion slerp = Math::lerp(a, b, 0.25f); + Quaternion slerpShortestPath = Math::lerpShortestPath(a, b, 0.25f); + + CORRADE_VERIFY(slerp.isNormalized()); + CORRADE_VERIFY(slerpShortestPath.isNormalized()); + CORRADE_COMPARE(slerp.axis(), Vector3::zAxis()); + CORRADE_COMPARE(slerpShortestPath.axis(), Vector3::zAxis()); + CORRADE_COMPARE(slerp.angle(), 38.8848_degf); + CORRADE_COMPARE(slerpShortestPath.angle(), 329.448_degf); + + CORRADE_COMPARE(slerp, (Quaternion{{0.0f, 0.0f, 0.332859f}, 0.942977f})); + CORRADE_COMPARE(slerpShortestPath, (Quaternion{{0.0f, 0.0f, 0.26347f}, -0.964667f})); } void QuaternionTest::lerp2D() { @@ -513,14 +545,40 @@ void QuaternionTest::lerpNotNormalized() { void QuaternionTest::slerp() { Quaternion a = Quaternion::rotation(15.0_degf, Vector3(1.0f/Constants::sqrt3())); Quaternion b = Quaternion::rotation(23.0_degf, Vector3::xAxis()); + Quaternion slerp = Math::slerp(a, b, 0.35f); + Quaternion slerpShortestPath = Math::slerpShortestPath(a, b, 0.35f); + Quaternion expected{{0.1191653f, 0.0491109f, 0.0491109f}, 0.9904423f}; + /* Both should give the same result */ CORRADE_VERIFY(slerp.isNormalized()); - CORRADE_COMPARE(slerp, Quaternion({0.1191653f, 0.0491109f, 0.0491109f}, 0.9904423f)); + CORRADE_COMPARE(slerp, expected); + CORRADE_VERIFY(slerpShortestPath.isNormalized()); + CORRADE_COMPARE(slerpShortestPath, expected); /* Avoid division by zero */ CORRADE_COMPARE(Math::slerp(a, a, 0.25f), a); CORRADE_COMPARE(Math::slerp(a, -a, 0.42f), a); + CORRADE_COMPARE(Math::slerpShortestPath(a, a, 0.25f), a); + CORRADE_COMPARE(Math::slerpShortestPath(a, -a, 0.25f), a); +} + +void QuaternionTest::slerpShortestPath() { + Quaternion a = Quaternion::rotation(0.0_degf, Vector3::zAxis()); + Quaternion b = Quaternion::rotation(225.0_degf, Vector3::zAxis()); + + Quaternion slerp = Math::slerp(a, b, 0.25f); + Quaternion slerpShortestPath = Math::slerpShortestPath(a, b, 0.25f); + + CORRADE_VERIFY(slerp.isNormalized()); + CORRADE_VERIFY(slerpShortestPath.isNormalized()); + CORRADE_COMPARE(slerp.axis(), Vector3::zAxis()); + CORRADE_COMPARE(slerpShortestPath.axis(), Vector3::zAxis()); + CORRADE_COMPARE(slerp.angle(), 56.25_degf); + CORRADE_COMPARE(slerpShortestPath.angle(), 326.25_degf); + + CORRADE_COMPARE(slerp, (Quaternion{{0.0f, 0.0f, 0.471397f}, 0.881921f})); + CORRADE_COMPARE(slerpShortestPath, (Quaternion{{0.0f, 0.0f, 0.290285f}, -0.95694f})); } void QuaternionTest::slerp2D() { From 1f507d81d4d11e7495a1430056ef2fc42eca3bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 15:34:54 +0200 Subject: [PATCH 18/26] Math: make docs of slerp() for Complex consistent with Quaternion. --- src/Magnum/Math/Complex.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Magnum/Math/Complex.h b/src/Magnum/Math/Complex.h index be0dc5d81..e701311d7 100644 --- a/src/Magnum/Math/Complex.h +++ b/src/Magnum/Math/Complex.h @@ -488,9 +488,10 @@ template inline Complex lerp(const Complex& normalizedA, const Co Expects that both complex numbers are normalized. If the complex numbers are the same, returns the first argument. @f[ - c_{SLERP} = \frac{sin((1 - t) \theta) c_A + sin(t \theta) c_B}{sin \theta} - ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ - \theta = acos \left( \frac{c_A \cdot c_B}{|c_A| \cdot |c_B|} \right) = acos(c_A \cdot c_B) + \begin{array}{rcl} + \theta & = & \arccos \left( \frac{c_A \cdot c_B}{|c_A| |c_B|} \right) = \arccos(c_A \cdot c_B) \\[6pt] + c_{SLERP} & = & \cfrac{\sin((1 - t) \theta) c_A + \sin(t \theta) c_B}{\sin(\theta)} + \end{array} @f] @see @ref Complex::isNormalized(), @ref lerp(const Complex&, const Complex&, T), @ref slerp(const Quaternion&, const Quaternion&, T) From ec340090255877cb1b36b9a55cc1a3c86d8a79c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 15:37:55 +0200 Subject: [PATCH 19/26] Math: document the usual notation for complex/dual/quat numbers. --- src/Magnum/Math/Complex.h | 12 +++++++++--- src/Magnum/Math/Dual.h | 9 +++++++-- src/Magnum/Math/DualComplex.h | 10 ++++++++-- src/Magnum/Math/DualQuaternion.h | 10 ++++++++-- src/Magnum/Math/Quaternion.h | 4 ++-- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/Magnum/Math/Complex.h b/src/Magnum/Math/Complex.h index e701311d7..695409ee3 100644 --- a/src/Magnum/Math/Complex.h +++ b/src/Magnum/Math/Complex.h @@ -79,7 +79,13 @@ template inline Rad angle(const Complex& normalizedA, const Compl @brief Complex number @tparam T Data type -Represents 2D rotation. See @ref transformations for brief introduction. +Represents 2D rotation. Usually denoted as the following in equations, with +@f$ a_0 @f$ being the @ref real() part and @f$ a_i @f$ the @ref imaginary() +part: @f[ + c = a_0 + i a_i +@f] + +See @ref transformations for brief introduction. @see @ref Magnum::Complex, @ref Magnum::Complexd, @ref Matrix3 */ template class Complex { @@ -191,11 +197,11 @@ template class Complex { return Implementation::isNormalizedSquared(dot()); } - /** @brief Real part */ + /** @brief Real part (@f$ a_0 @f$) */ T& real() { return _real; } constexpr T real() const { return _real; } /**< @overload */ - /** @brief Imaginary part */ + /** @brief Imaginary part (@f$ a_i @f$) */ T& imaginary() { return _imaginary; } constexpr T imaginary() const { return _imaginary; } /**< @overload */ diff --git a/src/Magnum/Math/Dual.h b/src/Magnum/Math/Dual.h index 151db9099..2e74a3230 100644 --- a/src/Magnum/Math/Dual.h +++ b/src/Magnum/Math/Dual.h @@ -45,6 +45,11 @@ namespace Implementation { /** @brief Dual number @tparam T Underlying data type + +Usually denoted as the following in equations, with @f$ a_0 @f$ being the +@ref real() part and @f$ a_\epsilon @f$ the @ref dual() part: @f[ + \hat a = a_0 + \epsilon a_\epsilon +@f] */ template class Dual { template friend class Dual; @@ -121,13 +126,13 @@ template class Dual { return !operator==(other); } - /** @brief Real part */ + /** @brief Real part (@f$ a_0 @f$) */ T& real() { return _real; } /* Returning const so it's possible to call constexpr functions on the result. WTF, C++?! */ constexpr const T real() const { return _real; } /**< @overload */ - /** @brief Dual part */ + /** @brief Dual part (@f$ a_\epsilon @f$) */ T& dual() { return _dual; } /* Returning const so it's possible to call constexpr functions on the result. WTF, C++?! */ diff --git a/src/Magnum/Math/DualComplex.h b/src/Magnum/Math/DualComplex.h index 59a7da4b7..58571fa6d 100644 --- a/src/Magnum/Math/DualComplex.h +++ b/src/Magnum/Math/DualComplex.h @@ -43,8 +43,14 @@ namespace Implementation { @brief Dual complex number @tparam T Underlying data type -Represents 2D rotation and translation. See @ref transformations for brief -introduction. +Represents 2D rotation and translation. Usually denoted as the following in +equations, with @f$ q_0 @f$ being the @ref real() part and @f$ q_\epsilon @f$ +the @ref dual() part: @f[ + \hat q = q_0 + \epsilon q_\epsilon +@f] + +See @ref Dual and @ref Complex for further notation description and +@ref transformations for brief introduction. @see @ref Magnum::DualComplex, @ref Magnum::DualComplexd, @ref Dual, @ref Complex, @ref Matrix3 @todo Can this be done similarly as in dual quaternions? It sort of works, but diff --git a/src/Magnum/Math/DualQuaternion.h b/src/Magnum/Math/DualQuaternion.h index 5191b563d..ab806d3a6 100644 --- a/src/Magnum/Math/DualQuaternion.h +++ b/src/Magnum/Math/DualQuaternion.h @@ -179,8 +179,14 @@ template inline DualQuaternion sclerpShortestPath(const DualQuaterni @brief Dual quaternion @tparam T Underlying data type -Represents 3D rotation and translation. See @ref transformations for brief -introduction. +Represents 3D rotation and translation. Usually denoted as the following in +equations, with @f$ q_0 @f$ being the @ref real() part and @f$ q_\epsilon @f$ +the @ref dual() part: @f[ + \hat q = q_0 + \epsilon q_\epsilon +@f] + +See @ref Dual and @ref Quaternion for further notation description and +@ref transformations for a brief introduction. @see @ref Magnum::DualQuaternion, @ref Magnum::DualQuaterniond, @ref Dual, @ref Quaternion, @ref Matrix4 */ diff --git a/src/Magnum/Math/Quaternion.h b/src/Magnum/Math/Quaternion.h index 528bbbbea..4196e61fe 100644 --- a/src/Magnum/Math/Quaternion.h +++ b/src/Magnum/Math/Quaternion.h @@ -333,13 +333,13 @@ template class Quaternion { return Implementation::isNormalizedSquared(dot()); } - /** @brief Vector part */ + /** @brief Vector part (@f$ \boldsymbol{q}_V @f$) */ Vector3& vector() { return _vector; } /* Returning const so it's possible to call constexpr functions on the result. WTF, C++?! */ constexpr const Vector3 vector() const { return _vector; } /**< @overload */ - /** @brief Scalar part */ + /** @brief Scalar part (@f$ q_S @f$) */ T& scalar() { return _scalar; } constexpr T scalar() const { return _scalar; } /**< @overload */ From 780e5258cce1b543745187fac2000a9ede7ab97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 15:39:39 +0200 Subject: [PATCH 20/26] Math: consistently use arccos() in math equations. --- src/Magnum/Math/Complex.h | 2 +- src/Magnum/Math/Quaternion.h | 4 ++-- src/Magnum/Math/Vector.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Magnum/Math/Complex.h b/src/Magnum/Math/Complex.h index 695409ee3..0fd31f984 100644 --- a/src/Magnum/Math/Complex.h +++ b/src/Magnum/Math/Complex.h @@ -63,7 +63,7 @@ template inline T dot(const Complex& a, const Complex& b) { @brief Angle between normalized complex numbers Expects that both complex numbers are normalized. @f[ - \theta = acos \left( \frac{Re(c_0 \cdot c_1))}{|c_0| |c_1|} \right) = acos (a_0 a_1 + b_0 b_1) + \theta = \arccos \left( \frac{Re(c_0 \cdot c_1))}{|c_0| |c_1|} \right) = \arccos (a_0 a_1 + b_0 b_1) @f] @see @ref Complex::isNormalized(), @ref angle(const Quaternion&, const Quaternion&), diff --git a/src/Magnum/Math/Quaternion.h b/src/Magnum/Math/Quaternion.h index 4196e61fe..4f1b600a1 100644 --- a/src/Magnum/Math/Quaternion.h +++ b/src/Magnum/Math/Quaternion.h @@ -66,7 +66,7 @@ namespace Implementation { @brief Angle between normalized quaternions Expects that both quaternions are normalized. @f[ - \theta = acos \left( \frac{p \cdot q}{|p| |q|} \right) = acos(p \cdot q) + \theta = \arccos \left( \frac{p \cdot q}{|p| |q|} \right) = \arccos(p \cdot q) @f] @see @ref Quaternion::isNormalized(), @ref angle(const Complex&, const Complex&), @@ -347,7 +347,7 @@ template class Quaternion { * @brief Rotation angle of unit quaternion * * Expects that the quaternion is normalized. @f[ - * \theta = 2 \cdot acos q_S + * \theta = 2 \cdot \arccos(q_S) * @f] * @see @ref isNormalized(), @ref axis(), @ref rotation() */ diff --git a/src/Magnum/Math/Vector.h b/src/Magnum/Math/Vector.h index 0fc74ba3d..db43ecfbf 100644 --- a/src/Magnum/Math/Vector.h +++ b/src/Magnum/Math/Vector.h @@ -86,7 +86,7 @@ template inline T dot(const Vector& a, const Expects that both vectors are normalized. Enabled only for floating-point types. @f[ - \theta = acos \left( \frac{\boldsymbol a \cdot \boldsymbol b}{|\boldsymbol a| |\boldsymbol b|} \right) = acos (\boldsymbol a \cdot \boldsymbol b) + \theta = \arccos \left( \frac{\boldsymbol a \cdot \boldsymbol b}{|\boldsymbol a| |\boldsymbol b|} \right) = \arccos (\boldsymbol a \cdot \boldsymbol b) @f] @see @ref Vector::isNormalized(), @ref angle(const Complex&, const Complex&), From fde07c6c584814b0402f1d0f5cf48a403b4b8c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 15:40:07 +0200 Subject: [PATCH 21/26] Doc++ --- doc/types.dox | 12 ++++++------ src/Magnum/Trade/AnimationData.h | 7 +++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/doc/types.dox b/doc/types.dox index 164ba457a..468265ff0 100644 --- a/doc/types.dox +++ b/doc/types.dox @@ -42,14 +42,14 @@ consistency and reduce confusion (e.g. @ref std::int32_t, @cpp int @ce and | Magnum type | Size | Equivalent GLSL type | | ------------------ | -------------- | ----------------------- | -| @ref UnsignedByte | 8bit unsigned | | -| @ref Byte | 8bit signed | | -| @ref UnsignedShort | 16bit unsigned | | -| @ref Short | 16bit signed | | +| @ref UnsignedByte | 8bit unsigned | (*none*) | +| @ref Byte | 8bit signed | (*none*) | +| @ref UnsignedShort | 16bit unsigned | (*none*) | +| @ref Short | 16bit signed | (*none*) | | @ref UnsignedInt | 32bit unsigned | @glsl uint @ce | | @ref Int | 32bit signed | @glsl int @ce | -| @ref UnsignedLong | 64bit unsigned | | -| @ref Long | 64bit signed | | +| @ref UnsignedLong | 64bit unsigned | (*none*) | +| @ref Long | 64bit signed | (*none*) | | @ref Half | 16bit | (*none*) | | @ref Float | 32bit | @glsl float @ce | | @ref Double | 64bit | @glsl double @ce | diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index 98ff4cf13..2f8dab041 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -452,10 +452,9 @@ class MAGNUM_TRADE_EXPORT AnimationData { /** @relatesalso AnimationData @brief Animation interpolator function for given interpolation behavior -To be used from importer plugins --- unlike @ref Animation::interpolatorFor() -guarantees that the returned function pointer is not instantiated inside plugin -binary to avoid dangling function pointers on plugin unload. See -@ref Animation::interpolatorFor() for more information. +To be used from importer plugins --- wrapper around @ref Animation::interpolatorFor(), +guaranteeing that the returned function pointer is not instantiated inside the +plugin binary to avoid dangling function pointers on plugin unload. @see @ref AnimationData @experimental */ From 9f8da6d47a911ad4d878b64b28372f2722f474a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 15:41:18 +0200 Subject: [PATCH 22/26] Animation: prefer "shortest path" interpolators in interpolatorFor(). The function has an explicit name but making it the default makes for a less surprising behavior. --- src/Magnum/Animation/Interpolation.cpp | 4 +-- src/Magnum/Animation/Interpolation.h | 40 ++++++++++++++------------ 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/Magnum/Animation/Interpolation.cpp b/src/Magnum/Animation/Interpolation.cpp index 1e38c107f..0845816eb 100644 --- a/src/Magnum/Animation/Interpolation.cpp +++ b/src/Magnum/Animation/Interpolation.cpp @@ -78,7 +78,7 @@ template auto TypeTraits, Math::Complex>::interpola template auto TypeTraits, Math::Quaternion>::interpolator(Interpolation interpolation) -> Interpolator { switch(interpolation) { case Interpolation::Constant: return Math::select; - case Interpolation::Linear: return Math::slerp; + case Interpolation::Linear: return Math::slerpShortestPath; case Interpolation::Spline: case Interpolation::Custom: ; /* nope */ @@ -90,7 +90,7 @@ template auto TypeTraits, Math::Quaternion>::int template auto TypeTraits, Math::DualQuaternion>::interpolator(Interpolation interpolation) -> Interpolator { switch(interpolation) { case Interpolation::Constant: return Math::select; - case Interpolation::Linear: return Math::sclerp; + case Interpolation::Linear: return Math::sclerpShortestPath; case Interpolation::Spline: case Interpolation::Custom: ; /* nope */ diff --git a/src/Magnum/Animation/Interpolation.h b/src/Magnum/Animation/Interpolation.h index 2e71d96f7..3261bf3bf 100644 --- a/src/Magnum/Animation/Interpolation.h +++ b/src/Magnum/Animation/Interpolation.h @@ -97,29 +97,31 @@ template using ResultOf = typename Implementation::ResultTraits::Typ Expects that @p interpolation is not @ref Interpolation::Custom. Favors output correctness over performance, supply custom interpolator functions for -faster but less precise results. +faster but potentially less correct results. @m_class{m-fullwidth} -Interpolation type | Value type | Result type | Interpolator +Interpolation | Value type | Result type | Interpolator ------------------- | ----------------- | ------------- | ------------ -@ref Interpolation::Constant | any `V` | `V` | @ref Math::select() -@ref Interpolation::Constant | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::select(const CubicHermite&, const CubicHermite&, U) "Math::select()" -@ref Interpolation::Linear | @cpp bool @ce | @cpp bool @ce | @ref Math::select() -@ref Interpolation::Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() -@ref Interpolation::Linear | any scalar `V` | `V` | @ref Math::lerp() -@ref Interpolation::Linear | any vector `V` | `V` | @ref Math::lerp() -@ref Interpolation::Linear | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex&, const Complex&, T) "Math::slerp()" -@ref Interpolation::Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()" -@ref Interpolation::Linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()" -@ref Interpolation::Linear | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::lerp(const CubicHermite&, const CubicHermite&, U) "Math::lerp()" -@ref Interpolation::Linear | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::lerp()" -@ref Interpolation::Linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerp()" -@ref Interpolation::Spline | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::splerp(const CubicHermite&, const CubicHermite&, U) "Math::splerp()" -@ref Interpolation::Spline | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::splerp()" -@ref Interpolation::Spline | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::splerp()" - -@see @ref interpolate(), @ref interpolateStrict() +@ref Interpolation::Constant "Constant" | any `V` | `V` | @ref Math::select() +@ref Interpolation::Constant "Constant" | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::select(const CubicHermite&, const CubicHermite&, U) "Math::select()" +@ref Interpolation::Linear "Linear" | @cpp bool @ce | @cpp bool @ce | @ref Math::select() +@ref Interpolation::Linear "Linear" | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() +@ref Interpolation::Linear "Linear" | any scalar `V` | `V` | @ref Math::lerp() +@ref Interpolation::Linear "Linear" | any vector `V` | `V` | @ref Math::lerp() +@ref Interpolation::Linear "Linear" | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex&, const Complex&, T) "Math::slerp()" +@ref Interpolation::Linear "Linear" | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerpShortestPath(const Quaternion&, const Quaternion&, T) "Math::slerpShortestPath()" +@ref Interpolation::Linear "Linear" | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerpShortestPath(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerpShortestPath()" +@ref Interpolation::Linear "Linear" | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::lerp(const CubicHermite&, const CubicHermite&, U) "Math::lerp()" +@ref Interpolation::Linear "Linear" | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::lerp()" +@ref Interpolation::Linear "Linear" | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerp()" +@ref Interpolation::Spline "Spline" | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::splerp(const CubicHermite&, const CubicHermite&, U) "Math::splerp()" +@ref Interpolation::Spline "Spline" | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::splerp()" +@ref Interpolation::Spline "Spline" | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::splerp()" + +@see @ref interpolate(), @ref interpolateStrict(), + @ref Animation-Track-interpolators "Track types and interpolators", + @ref Trade::animationInterpolatorFor() @experimental */ template> auto interpolatorFor(Interpolation interpolation) -> R(*)(const V&, const V&, Float); From 880bf7a1b6addc7f48ea23a499027e1a0d0d8e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 15:42:19 +0200 Subject: [PATCH 23/26] Animation: list the new interpolators. --- src/Magnum/Animation/Track.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Magnum/Animation/Track.h b/src/Magnum/Animation/Track.h index e271b71cd..3d0866dea 100644 --- a/src/Magnum/Animation/Track.h +++ b/src/Magnum/Animation/Track.h @@ -60,7 +60,7 @@ are common combinations: @m_class{m-fullwidth} -Interpolation type | Value type | Result type | Interpolator +Interpolation | Value type | Result type | Interpolator ------------------- | ----------------- | ------------- | ------------ Constant | any `V` | `V` | @ref Math::select() Constant | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::select(const CubicHermite&, const CubicHermite&, U) "Math::select()" @@ -69,13 +69,13 @@ Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() Linear | any scalar `V` | `V` | @ref Math::lerp() Linear | any vector `V` | `V` | @ref Math::lerp() Linear | @ref Math::Complex | @ref Math::Complex | @ref Math::lerp(const Complex&, const Complex&, T) "Math::lerp()" -Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion&, const Quaternion&, T) "Math::lerp()" +Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion&, const Quaternion&, T) "Math::lerp()", \n @ref Math::lerpShortestPath(const Quaternion&, const Quaternion&, T) "Math::lerpShortestPath()" Linear | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::lerp(const CubicHermite&, const CubicHermite&, U) "Math::lerp()" Linear | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::lerp()" Linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerp()" Spherical linear | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex&, const Complex&, T) "Math::slerp()" -Spherical linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()" -Screw linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()" +Spherical linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()", \n @ref Math::slerpShortestPath(const Quaternion&, const Quaternion&, T) "Math::slerpShortestPath()" +Screw linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()", \n @ref Math::sclerpShortestPath(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerpShortestPath()" Spline | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::splerp(const CubicHermite&, const CubicHermite&, U) "Math::splerp()" Spline | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::splerp()" Spline | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::splerp()" From eadf8986f6dd7498b9f32078dbb71a26355b528e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 22:18:12 +0200 Subject: [PATCH 24/26] Math: fuzzy compare for division-by-zero in quaternion s(c)lerp. Also remove a totally unneeded conversion. --- src/Magnum/Math/DualQuaternion.h | 4 ++-- src/Magnum/Math/Quaternion.h | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Magnum/Math/DualQuaternion.h b/src/Magnum/Math/DualQuaternion.h index ab806d3a6..b268c9a26 100644 --- a/src/Magnum/Math/DualQuaternion.h +++ b/src/Magnum/Math/DualQuaternion.h @@ -84,7 +84,7 @@ template inline DualQuaternion sclerp(const DualQuaternion& norma /* Avoid division by zero: interpolate just the translation part */ /** @todo could this be optimized somehow? */ - if(std::abs(cosHalfAngle) >= T(1)) + if(std::abs(cosHalfAngle) >= T(1) - TypeTraits::epsilon()) return DualQuaternion::translation(Implementation::lerp(normalizedA.translation(), normalizedB.translation(), t))*DualQuaternion{normalizedA.real()}; /* l + εm = q_A^**q_B */ @@ -151,7 +151,7 @@ template inline DualQuaternion sclerpShortestPath(const DualQuaterni /* Avoid division by zero: interpolate just the translation part */ /** @todo could this be optimized somehow? */ - if(std::abs(cosHalfAngle) >= T(1)) + if(std::abs(cosHalfAngle) >= T(1) - TypeTraits::epsilon()) return DualQuaternion::translation(Implementation::lerp(normalizedA.translation(), normalizedB.translation(), t))*DualQuaternion{normalizedA.real()}; /* l + εm = q_A^**q_B, multiplying with -1 ensures shortest path when dot < 0 */ diff --git a/src/Magnum/Math/Quaternion.h b/src/Magnum/Math/Quaternion.h index 4f1b600a1..325b68ce7 100644 --- a/src/Magnum/Math/Quaternion.h +++ b/src/Magnum/Math/Quaternion.h @@ -163,7 +163,8 @@ template inline Quaternion slerp(const Quaternion& normalizedA, c const T cosHalfAngle = dot(normalizedA, normalizedB); /* Avoid division by zero */ - if(std::abs(cosHalfAngle) >= T(1)) return Quaternion{normalizedA}; + if(std::abs(cosHalfAngle) >= T(1) - TypeTraits::epsilon()) + return normalizedA; const T a = std::acos(cosHalfAngle); return (std::sin((T(1) - t)*a)*normalizedA + std::sin(t*a)*normalizedB)/std::sin(a); @@ -206,7 +207,8 @@ template inline Quaternion slerpShortestPath(const Quaternion& no const T cosHalfAngle = dot(normalizedA, normalizedB); /* Avoid division by zero */ - if(std::abs(cosHalfAngle) >= T(1)) return Quaternion{normalizedA}; + if(std::abs(cosHalfAngle) >= T(1) - TypeTraits::epsilon()) + return normalizedA; const Quaternion shortestNormalizedA = cosHalfAngle < 0 ? -normalizedA : normalizedA; From cbc35c43535482cfbd1f7e09430bac7dbf63bce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 23:17:37 +0200 Subject: [PATCH 25/26] Stub documentation for transformation interpolation and animation. --- doc/animation.dox | 48 ++++++++++++++++++++++++++++ doc/features.dox | 1 + doc/namespaces.dox | 4 +-- doc/transformations.dox | 41 ++++++++++++++++++++++-- src/Magnum/Animation/Interpolation.h | 3 +- src/Magnum/Animation/Track.h | 43 +++++++------------------ 6 files changed, 103 insertions(+), 37 deletions(-) create mode 100644 doc/animation.dox diff --git a/doc/animation.dox b/doc/animation.dox new file mode 100644 index 000000000..e8562c635 --- /dev/null +++ b/doc/animation.dox @@ -0,0 +1,48 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + 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. +*/ + +namespace Magnum { + +/** @page animation Keyframe-based animation +@brief Layered framework for authoring, loading, optimizing and playing back animations + +@tableofcontents +@m_footernavigation + +Sorry, this page is yet to be written. Please see the @ref Animation namespace, +especially documentation of the lowest-level @ref Animation::interpolate() +function, the higher-level @ref Animation::Track class and the top-level +@ref Animation::Player API. The @ref transformations-interpolation +documentation lists all available builtin interpolation methods. + +@experimental + +@todoc bottom-up description (interpolating values, keyframes, storing, at(), player, callbacks) +@todoc perf optimizations (shortest path, preprocessing..., extrapolation, ...), batchplayer +@todoc mention animation import + +*/ + +} diff --git a/doc/features.dox b/doc/features.dox index 6832b64e2..22177f8df 100644 --- a/doc/features.dox +++ b/doc/features.dox @@ -35,6 +35,7 @@ necessary to read through everything, pick only what you need. - @subpage types --- @copybrief types - @subpage matrix-vector --- @copybrief matrix-vector - @subpage transformations --- @copybrief transformations +- @subpage animation --- @copybrief animation - @subpage plugins --- @copybrief plugins - @subpage opengl-wrapping --- @copybrief opengl-wrapping - @subpage shaders --- @copybrief shaders diff --git a/doc/namespaces.dox b/doc/namespaces.dox index 0787f4ca6..9445476d1 100644 --- a/doc/namespaces.dox +++ b/doc/namespaces.dox @@ -186,8 +186,8 @@ See @ref building and @ref cmake for more information. @brief Keyframe-based animation This library is built as part of Magnum by default. To use it, you need to -find `Magnum` package and link to `Magnum::Magnum` target. See @ref building -and @ref cmake for more information. +find `Magnum` package and link to `Magnum::Magnum` target. See @ref building, +@ref cmake and @ref animation for more information. @experimental */ diff --git a/doc/transformations.dox b/doc/transformations.dox index 6bbb253b2..ebf276b2d 100644 --- a/doc/transformations.dox +++ b/doc/transformations.dox @@ -243,8 +243,45 @@ and translation matrices using @ref DualComplex::fromMatrix() and @section transformations-interpolation Transformation interpolation -@todoc Write this when interpolation is done also for (dual) complex numbers and - dual quaternions +Magnum provides various tools for interpolation, from basic constant/linear +interpolation of scalars and vectors to spline-based interpolation of +quaternions or dual quaternions. The table below is a complete list of all +builtin interpolation methods: + +@m_class{m-fullwidth} + +Interpolation | Value type | Result type | Interpolator +------------------- | ----------------- | ------------- | ------------ +Constant | any `V` | `V` | @ref Math::select() +Constant | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::select(const CubicHermite&, const CubicHermite&, U) "Math::select()" +Linear | @cpp bool @ce | @cpp bool @ce | @ref Math::select() +Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() +Linear | any scalar `V` | `V` | @ref Math::lerp() +Linear | any vector `V` | `V` | @ref Math::lerp() +Linear | @ref Math::Complex | @ref Math::Complex | @ref Math::lerp(const Complex&, const Complex&, T) "Math::lerp()" +Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion&, const Quaternion&, T) "Math::lerp()", \n @ref Math::lerpShortestPath(const Quaternion&, const Quaternion&, T) "Math::lerpShortestPath()" +Linear | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::lerp(const CubicHermite&, const CubicHermite&, U) "Math::lerp()" +Linear | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::lerp()" +Linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerp()" +Spherical linear | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex&, const Complex&, T) "Math::slerp()" +Spherical linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()", \n @ref Math::slerpShortestPath(const Quaternion&, const Quaternion&, T) "Math::slerpShortestPath()" +Screw linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()", \n @ref Math::sclerpShortestPath(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerpShortestPath()" +Spline | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::splerp(const CubicHermite&, const CubicHermite&, U) "Math::splerp()" +Spline | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::splerp()" +Spline | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::splerp()" + +The @ref Math::CubicHermite class is a generic implementation of cubic Hermite +splines to which other curve types such as Bézier are convertible. See its +documentation for more information. + +@attention Direct interpolation of transformation matrices or Euler angles is + not supported due to their well known performance / correctness issues and + limitations such as gimbal lock --- you have to convert them to one of the + supported representations such as @ref Complex numbers or @ref Quaternion + and interpolate these instead. + +Interpolation is most commonly used in animations --- see @ref animation for +more information. @section transformations-normalization Normalizing transformations diff --git a/src/Magnum/Animation/Interpolation.h b/src/Magnum/Animation/Interpolation.h index 3261bf3bf..d93195717 100644 --- a/src/Magnum/Animation/Interpolation.h +++ b/src/Magnum/Animation/Interpolation.h @@ -120,8 +120,7 @@ Interpolation | Value type | Result type | Interpolator @ref Interpolation::Spline "Spline" | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::splerp()" @see @ref interpolate(), @ref interpolateStrict(), - @ref Animation-Track-interpolators "Track types and interpolators", - @ref Trade::animationInterpolatorFor() + @ref transformations-interpolation, @ref Trade::animationInterpolatorFor() @experimental */ template> auto interpolatorFor(Interpolation interpolation) -> R(*)(const V&, const V&, Float); diff --git a/src/Magnum/Animation/Track.h b/src/Magnum/Animation/Track.h index 3d0866dea..c5dc55308 100644 --- a/src/Magnum/Animation/Track.h +++ b/src/Magnum/Animation/Track.h @@ -55,37 +55,18 @@ interpolator function and extrapolation behavior. @section Animation-Track-interpolators Types and interpolators -The track supports arbitrary types for keys, values and interpolators. These -are common combinations: - -@m_class{m-fullwidth} - -Interpolation | Value type | Result type | Interpolator -------------------- | ----------------- | ------------- | ------------ -Constant | any `V` | `V` | @ref Math::select() -Constant | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::select(const CubicHermite&, const CubicHermite&, U) "Math::select()" -Linear | @cpp bool @ce | @cpp bool @ce | @ref Math::select() -Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() -Linear | any scalar `V` | `V` | @ref Math::lerp() -Linear | any vector `V` | `V` | @ref Math::lerp() -Linear | @ref Math::Complex | @ref Math::Complex | @ref Math::lerp(const Complex&, const Complex&, T) "Math::lerp()" -Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion&, const Quaternion&, T) "Math::lerp()", \n @ref Math::lerpShortestPath(const Quaternion&, const Quaternion&, T) "Math::lerpShortestPath()" -Linear | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::lerp(const CubicHermite&, const CubicHermite&, U) "Math::lerp()" -Linear | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::lerp()" -Linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerp()" -Spherical linear | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex&, const Complex&, T) "Math::slerp()" -Spherical linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()", \n @ref Math::slerpShortestPath(const Quaternion&, const Quaternion&, T) "Math::slerpShortestPath()" -Screw linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()", \n @ref Math::sclerpShortestPath(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerpShortestPath()" -Spline | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::splerp(const CubicHermite&, const CubicHermite&, U) "Math::splerp()" -Spline | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::splerp()" -Spline | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::splerp()" - -It's also possible to supply a generic interpolation behavior by passing the -@ref Interpolation enum to the constructor. In case the interpolator function -is not passed in as well, it's autodetected using @ref interpolatorFor(). See -its documentation for more information. The @ref Interpolation enum is then -stored in @ref interpolation() and acts as a hint for desired interpolation -behavior for users who might want to use their own interpolator. +The track supports arbitrary types for keys, values and interpolators. See +@ref transformations-interpolation for an overview of builtin interpolation +functions. + +Besides directly specifying an interpolator function as shown in the above +snippet, it's also possible to supply a generic interpolation behavior by +passing the @ref Interpolation enum to the constructor. In case the +interpolator function is not passed in as well, it's autodetected using +@ref interpolatorFor(). See its documentation for more information. The +@ref Interpolation enum is then stored in @ref interpolation() and acts as a +hint for desired interpolation behavior for users who might want to use their +own interpolator. @section Animation-Track-performance Performance tuning From 13a6ba8a23d28c308aed655cc6f757b29f9fc374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 3 Sep 2018 23:24:49 +0200 Subject: [PATCH 26/26] Math: benchmark quaternion interpolation methods. --- src/Magnum/Math/Test/CMakeLists.txt | 2 + .../Math/Test/InterpolationBenchmark.cpp | 152 ++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/Magnum/Math/Test/InterpolationBenchmark.cpp diff --git a/src/Magnum/Math/Test/CMakeLists.txt b/src/Magnum/Math/Test/CMakeLists.txt index 4b5a7d4c4..14311a9a1 100644 --- a/src/Magnum/Math/Test/CMakeLists.txt +++ b/src/Magnum/Math/Test/CMakeLists.txt @@ -61,6 +61,8 @@ corrade_add_test(MathDistanceTest DistanceTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathIntersectionTest IntersectionTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathIntersectionBenchmark IntersectionBenchmark.cpp LIBRARIES MagnumMathTestLib) +corrade_add_test(MathInterpolationBenchmark InterpolationBenchmark.cpp LIBRARIES MagnumMathTestLib) + set_property(TARGET MathVectorTest MathMatrixTest diff --git a/src/Magnum/Math/Test/InterpolationBenchmark.cpp b/src/Magnum/Math/Test/InterpolationBenchmark.cpp new file mode 100644 index 000000000..c023b35ed --- /dev/null +++ b/src/Magnum/Math/Test/InterpolationBenchmark.cpp @@ -0,0 +1,152 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + 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 + +#define CORRADE_NO_ASSERT +#include "Magnum/Math/DualQuaternion.h" + +namespace Magnum { namespace Math { namespace Test { + +struct InterpolationBenchmark: Corrade::TestSuite::Tester { + explicit InterpolationBenchmark(); + + void baseline(); + void quaternionLerp(); + void quaternionLerpShortestPath(); + void quaternionSlerp(); + void quaternionSlerpShortestPath(); + void dualQuaternionSclerp(); + void dualQuaternionSclerpShortestPath(); +}; + +using namespace Math::Literals; + +typedef Math::Quaternion Quaternion; +typedef Math::DualQuaternion DualQuaternion; +typedef Math::Vector3 Vector3; + +InterpolationBenchmark::InterpolationBenchmark() { + addBenchmarks({&InterpolationBenchmark::baseline, + &InterpolationBenchmark::quaternionLerp, + &InterpolationBenchmark::quaternionLerpShortestPath, + &InterpolationBenchmark::quaternionSlerp, + &InterpolationBenchmark::quaternionSlerpShortestPath, + &InterpolationBenchmark::dualQuaternionSclerp, + &InterpolationBenchmark::dualQuaternionSclerpShortestPath}, 100); +} + +void InterpolationBenchmark::baseline() { + Quaternion c; + Float t = 0.0f; + CORRADE_BENCHMARK(10000) { + c += Quaternion{}; + t += 0.0002f; + } + + CORRADE_VERIFY(!c.isNormalized()); +} + +void InterpolationBenchmark::quaternionLerp() { + auto a = Quaternion::rotation(225.0_degf, Vector3::zAxis()); + auto b = Quaternion::rotation(0.0_degf, Vector3::zAxis()); + Quaternion c; + Float t = 0.0f; + CORRADE_BENCHMARK(10000) { + c += lerp(a, b, t); + t += 0.0002f; + } + + CORRADE_VERIFY(!c.isNormalized()); +} + +void InterpolationBenchmark::quaternionLerpShortestPath() { + auto a = Quaternion::rotation(225.0_degf, Vector3::zAxis()); + auto b = Quaternion::rotation(0.0_degf, Vector3::zAxis()); + Quaternion c; + Float t = 0.0f; + CORRADE_BENCHMARK(10000) { + c += lerpShortestPath(a, b, t); + t += 0.0002f; + } + + CORRADE_VERIFY(!c.isNormalized()); +} + +void InterpolationBenchmark::quaternionSlerp() { + auto a = Quaternion::rotation(225.0_degf, Vector3::zAxis()); + auto b = Quaternion::rotation(0.0_degf, Vector3::zAxis()); + Quaternion c; + Float t = 0.0f; + CORRADE_BENCHMARK(10000) { + c += slerp(a, b, t); + t += 0.0002f; + } + + CORRADE_VERIFY(!c.isNormalized()); +} + +void InterpolationBenchmark::quaternionSlerpShortestPath() { + auto a = Quaternion::rotation(225.0_degf, Vector3::zAxis()); + auto b = Quaternion::rotation(0.0_degf, Vector3::zAxis()); + Quaternion c; + Float t = 0.0f; + CORRADE_BENCHMARK(10000) { + c += slerpShortestPath(a, b, t); + t += 0.0002f; + } + + CORRADE_VERIFY(!c.isNormalized()); +} + +void InterpolationBenchmark::dualQuaternionSclerp() { + auto a = DualQuaternion::rotation(225.0_degf, Vector3::zAxis()); + auto b = DualQuaternion::rotation(0.0_degf, Vector3::zAxis()); + DualQuaternion c; + Float t = 0.0f; + CORRADE_BENCHMARK(1000) { + c += sclerp(a, b, t); + t += 0.001f; + } + + CORRADE_VERIFY(!c.isNormalized()); +} + +void InterpolationBenchmark::dualQuaternionSclerpShortestPath() { + auto a = DualQuaternion::rotation(225.0_degf, Vector3::zAxis()); + auto b = DualQuaternion::rotation(0.0_degf, Vector3::zAxis()); + DualQuaternion c; + Float t = 0.0f; + CORRADE_BENCHMARK(1000) { + c += sclerpShortestPath(a, b, t); + t += 0.001f; + } + + CORRADE_VERIFY(!c.isNormalized()); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Math::Test::InterpolationBenchmark)