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/changelog.dox b/doc/changelog.dox index 346b59f5e..f9a8a8deb 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -47,6 +47,9 @@ See also: @subsubsection changelog-latest-new-math Math library +- 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(), @@ -66,6 +69,14 @@ 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::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 @@ -85,6 +96,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 @@ -297,6 +312,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/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/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/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/doc/types.dox b/doc/types.dox index 317d5aad5..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 | @@ -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/Animation/Interpolation.cpp b/src/Magnum/Animation/Interpolation.cpp index 10b2c1e67..0845816eb 100644 --- a/src/Magnum/Animation/Interpolation.cpp +++ b/src/Magnum/Animation/Interpolation.cpp @@ -25,6 +25,7 @@ #include "Interpolation.h" +#include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/DualQuaternion.h" namespace Magnum { namespace Animation { @@ -36,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 */ @@ -61,11 +63,24 @@ Debug& operator<<(Debug& debug, const Extrapolation value) { namespace Implementation { -template auto TypeTraits, Math::Quaternion>::interpolator(Interpolation interpolation) -> Interpolator { +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::Spline: + 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; + case Interpolation::Linear: return Math::slerpShortestPath; + + case Interpolation::Spline: case Interpolation::Custom: ; /* nope */ } @@ -75,7 +90,20 @@ 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 */ + } + + 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 */ } @@ -83,8 +111,14 @@ 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>; +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 aed2ce90a..d93195717 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 */ @@ -89,21 +97,30 @@ 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::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::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()" - -@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 transformations-interpolation, @ref Trade::animationInterpolatorFor() @experimental */ template> auto interpolatorFor(Interpolation interpolation) -> R(*)(const V&, const V&, Float); @@ -212,6 +229,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); @@ -222,6 +242,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 */ } @@ -239,6 +260,7 @@ template auto TypeTraitsBool::interpolator(Interpolation interpolati case Interpolation::Constant: case Interpolation::Linear: return Math::select; + case Interpolation::Spline: case Interpolation::Custom: ; /* nope */ } @@ -247,6 +269,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); @@ -259,6 +288,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 91bb92dfa..b9406ad15 100644 --- a/src/Magnum/Animation/Test/InterpolationTest.cpp +++ b/src/Magnum/Animation/Test/InterpolationTest.cpp @@ -27,6 +27,8 @@ #include #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" @@ -38,8 +40,13 @@ struct InterpolationTest: TestSuite::Tester { void interpolatorFor(); void interpolatorForBool(); void interpolatorForBoolVector(); + void interpolatorForComplex(); void interpolatorForQuaternion(); void interpolatorForDualQuaternion(); + void interpolatorForCubicHermiteScalar(); + void interpolatorForCubicHermiteVector(); + void interpolatorForCubicHermiteComplex(); + void interpolatorForCubicHermiteQuaternion(); void interpolate(); void interpolateStrict(); @@ -138,8 +145,13 @@ InterpolationTest::InterpolationTest() { addTests({&InterpolationTest::interpolatorFor, &InterpolationTest::interpolatorForBool, &InterpolationTest::interpolatorForBoolVector, + &InterpolationTest::interpolatorForComplex, &InterpolationTest::interpolatorForQuaternion, - &InterpolationTest::interpolatorForDualQuaternion}); + &InterpolationTest::interpolatorForDualQuaternion, + &InterpolationTest::interpolatorForCubicHermiteScalar, + &InterpolationTest::interpolatorForCubicHermiteVector, + &InterpolationTest::interpolatorForCubicHermiteComplex, + &InterpolationTest::interpolatorForCubicHermiteQuaternion}); addInstancedTests({&InterpolationTest::interpolate, &InterpolationTest::interpolateStrict}, @@ -175,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"); } @@ -215,6 +227,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()), @@ -227,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"); } @@ -255,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/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 diff --git a/src/Magnum/Animation/Track.h b/src/Magnum/Animation/Track.h index ad118938e..c5dc55308 100644 --- a/src/Magnum/Animation/Track.h +++ b/src/Magnum/Animation/Track.h @@ -55,28 +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 type | Value type | Result type | Interpolator -------------------- | ----------------- | ------------- | ------------ -Constant | any `V` | `V` | @ref 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::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion&, const Quaternion&, T) "Math::lerp()" -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()" - -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 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/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 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/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/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 f966aa2ef..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&), @@ -72,14 +72,20 @@ 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))); } /** @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,13 @@ template class Complex { return Implementation::isNormalizedSquared(dot()); } - /** @brief Real part */ - constexpr T real() const { return _real; } + /** @brief Real part (@f$ a_0 @f$) */ + T& real() { return _real; } + constexpr T real() const { return _real; } /**< @overload */ - /** @brief Imaginary part */ - constexpr T imaginary() const { return _imaginary; } + /** @brief Imaginary part (@f$ a_i @f$) */ + T& imaginary() { return _imaginary; } + constexpr T imaginary() const { return _imaginary; } /**< @overload */ /** * @brief Convert complex number to vector @@ -456,6 +464,57 @@ 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), + @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(), + "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[ + \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) + */ +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/CubicHermite.h b/src/Magnum/Math/CubicHermite.h new file mode 100644 index 000000000..6d4022fb0 --- /dev/null +++ b/src/Magnum/Math/CubicHermite.h @@ -0,0 +1,465 @@ +#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. + +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, + @ref CubicBezier +@experimental +*/ +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 + * + * 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/Dual.h b/src/Magnum/Math/Dual.h index 5cf8c04ee..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,17 @@ template class Dual { return !operator==(other); } - /** @brief Real part */ + /** @brief Real part (@f$ a_0 @f$) */ 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 */ + /** @brief Dual part (@f$ a_\epsilon @f$) */ 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/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 81c4a95ae..b268c9a26 100644 --- a/src/Magnum/Math/DualQuaternion.h +++ b/src/Magnum/Math/DualQuaternion.h @@ -48,33 +48,114 @@ 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 dotResult = dot(normalizedA.real().vector(), normalizedB.real().vector()); + 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) - TypeTraits::epsilon()) + 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(); - /* Avoid division by zero */ - const T cosHalfAngle = dotResult + normalizedA.real().scalar()*normalizedB.real().scalar(); - if(std::abs(cosHalfAngle) >= T(1)) - return {normalizedA.real(), {Implementation::lerp(normalizedA.dual().vector(), normalizedB.dual().vector(), t), T(0)}}; + /* 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) - 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 */ - 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(); @@ -98,8 +179,14 @@ template inline DualQuaternion sclerp(const DualQuaternion& norma @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/Functions.h b/src/Magnum/Math/Functions.h index 80af3f9dd..9909406b9 100644 --- a/src/Magnum/Math/Functions.h +++ b/src/Magnum/Math/Functions.h @@ -559,8 +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 lerpInverted(), @ref lerp(const Quaternion&, const Quaternion&, T) +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 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/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/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 9075b2798..325b68ce7 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&), @@ -87,8 +87,16 @@ 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() + +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), + @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(), @@ -96,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 @@ -103,31 +136,97 @@ 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 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", {}); 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); } +/** @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) - TypeTraits::epsilon()) + return 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 */ @@ -236,17 +335,21 @@ template class Quaternion { return Implementation::isNormalizedSquared(dot()); } - /** @brief Vector part */ - constexpr const Vector3 vector() const { return _vector; } + /** @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 */ - constexpr T scalar() const { return _scalar; } + /** @brief Scalar part (@f$ q_S @f$) */ + T& scalar() { return _scalar; } + constexpr T scalar() const { return _scalar; } /**< @overload */ /** * @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/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/CMakeLists.txt b/src/Magnum/Math/Test/CMakeLists.txt index 9563699da..14311a9a1 100644 --- a/src/Magnum/Math/Test/CMakeLists.txt +++ b/src/Magnum/Math/Test/CMakeLists.txt @@ -54,18 +54,22 @@ 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) 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 MathMatrix3Test MathMatrix4Test MathComplexTest + MathCubicHermiteTest MathDualComplexTest MathQuaternionTest MathDualQuaternionTest diff --git a/src/Magnum/Math/Test/ComplexTest.cpp b/src/Magnum/Math/Test/ComplexTest.cpp index c5540a1bc..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(); @@ -86,6 +88,10 @@ struct ComplexTest: Corrade::TestSuite::Tester { void angle(); void rotation(); void matrix(); + void lerp(); + void lerpNotNormalized(); + void slerp(); + void slerpNotNormalized(); void transformVector(); void debug(); @@ -102,6 +108,8 @@ ComplexTest::ComplexTest() { &ComplexTest::constructCopy, &ComplexTest::convert, + &ComplexTest::data, + &ComplexTest::compare, &ComplexTest::isNormalized, &ComplexTest::isNormalizedEpsilon, @@ -128,6 +136,10 @@ ComplexTest::ComplexTest() { &ComplexTest::angle, &ComplexTest::rotation, &ComplexTest::matrix, + &ComplexTest::lerp, + &ComplexTest::lerpNotNormalized, + &ComplexTest::slerp, + &ComplexTest::slerpNotNormalized, &ComplexTest::transformVector, &ComplexTest::debug, @@ -141,14 +153,13 @@ 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)); - - 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)); } @@ -245,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)); @@ -411,6 +435,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/CubicHermiteTest.cpp b/src/Magnum/Math/Test/CubicHermiteTest.cpp new file mode 100644 index 000000000..d27dc94cb --- /dev/null +++ b/src/Magnum/Math/Test/CubicHermiteTest.cpp @@ -0,0 +1,956 @@ +/* + 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/Bezier.h" +#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 constructFromBezier(); + + 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 splerpVectorFromBezier(); + 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::constructFromBezier, + + &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::splerpVectorFromBezier, + &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::CubicBezier2D CubicBezier2D; +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::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}; + + 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::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}}; + + 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/Test/DualQuaternionTest.cpp b/src/Magnum/Math/Test/DualQuaternionTest.cpp index 578342963..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}); @@ -479,34 +481,99 @@ 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(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.170316f, 0.424975f, 0.0f}, 0.889038f}, + {{10.689f, 7.47059f, 5.33428f}, -5.61881f}}; + DualQuaternion expected2{ + {{0.34568f, 0.282968f, 0.0f}, 0.89467f}, + {{12.8764f, 15.8357f, 5.03088f}, -9.98371f}}; + DualQuaternion expected3{ + {{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::sclerp(from, DualQuaternion(-from.real(), from.dual()), 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 */ - 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); + { + 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/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/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 } 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) diff --git a/src/Magnum/Math/Test/QuaternionTest.cpp b/src/Magnum/Math/Test/QuaternionTest.cpp index 98e3149b2..d29b2997d 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(); @@ -87,8 +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(); @@ -104,6 +114,8 @@ typedef Math::Quaternion Quaternion; typedef Math::Vector3 Vector3; typedef Math::Vector4 Vector4; +using namespace Math::Literals; + QuaternionTest::QuaternionTest() { addTests({&QuaternionTest::construct, &QuaternionTest::constructIdentity, @@ -114,6 +126,8 @@ QuaternionTest::QuaternionTest() { &QuaternionTest::constructCopy, &QuaternionTest::convert, + &QuaternionTest::data, + &QuaternionTest::compare, &QuaternionTest::isNormalized, &QuaternionTest::isNormalizedEpsilon, @@ -140,8 +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, @@ -152,11 +174,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)); } @@ -249,6 +268,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)); @@ -455,43 +487,121 @@ 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()); + Quaternion a = Quaternion::rotation(15.0_degf, Vector3(1.0f/Constants::sqrt3())); + Quaternion b = Quaternion::rotation(23.0_degf, Vector3::xAxis()); - std::ostringstream o; - Error redirectError{&o}; + 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_VERIFY(lerpShortestPath.isNormalized()); + CORRADE_COMPARE(lerp, expected); + CORRADE_COMPARE(lerpShortestPath, expected); +} - Math::lerp(a*3.0f, b, 0.35f); - CORRADE_COMPARE(o.str(), "Math::lerp(): quaternions must be normalized\n"); +void QuaternionTest::lerpShortestPath() { + Quaternion a = Quaternion::rotation(0.0_degf, Vector3::zAxis()); + Quaternion b = Quaternion::rotation(225.0_degf, Vector3::zAxis()); - o.str({}); - Math::lerp(a, b*-3.0f, 0.35f); - CORRADE_COMPARE(o.str(), "Math::lerp(): quaternions must be normalized\n"); + Quaternion slerp = Math::lerp(a, b, 0.25f); + Quaternion slerpShortestPath = Math::lerpShortestPath(a, b, 0.25f); - Quaternion lerp = Math::lerp(a, b, 0.35f); - CORRADE_COMPARE(lerp, Quaternion({0.119127f, 0.049134f, 0.049134f}, 0.990445f)); + 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::slerp() { - Quaternion a = Quaternion::rotation(Deg(15.0f), Vector3(1.0f/Constants::sqrt3())); - Quaternion b = Quaternion::rotation(Deg(23.0f), Vector3::xAxis()); +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); - std::ostringstream o; - Error redirectError{&o}; + 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})); +} - Math::slerp(a*3.0f, b, 0.35f); - CORRADE_COMPARE(o.str(), "Math::slerp(): quaternions must be normalized\n"); +void QuaternionTest::lerpNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; - 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_COMPARE(slerp, Quaternion({0.1191653f, 0.0491109f, 0.0491109f}, 0.9904423f)); + 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, 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() { + /* 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}; + + 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() { diff --git a/src/Magnum/Math/Vector.h b/src/Magnum/Math/Vector.h index 2ffe11c84..db43ecfbf 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() @@ -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&), 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&); diff --git a/src/Magnum/Trade/AnimationData.cpp b/src/Magnum/Trade/AnimationData.cpp index 5208ac45a..fd0b1ed11 100644 --- a/src/Magnum/Trade/AnimationData.cpp +++ b/src/Magnum/Trade/AnimationData.cpp @@ -97,8 +97,13 @@ 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); +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) { @@ -120,8 +125,14 @@ Debug& operator<<(Debug& debug, const AnimationTrackType value) { _c(Vector4) _c(Vector4ui) _c(Vector4i) + _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 d8196cd3d..2f8dab041 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -75,13 +75,46 @@ 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. */ 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} */ @@ -96,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 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, @@ -395,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 */ @@ -441,8 +497,15 @@ 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; } + + 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