diff --git a/src/Magnum/Animation/Animation.h b/src/Magnum/Animation/Animation.h index 57852e7c3..bb9d7e60b 100644 --- a/src/Magnum/Animation/Animation.h +++ b/src/Magnum/Animation/Animation.h @@ -34,11 +34,13 @@ namespace Magnum { namespace Animation { namespace Implementation { - template struct TypeTraits; + template struct ResultTraits; + template struct TypeTraits; } -template using ResultOf = typename Implementation::TypeTraits::ResultType; +template using ResultOf = typename Implementation::ResultTraits::Type; +enum class Interpolation: UnsignedByte; enum class Extrapolation: UnsignedByte; template> class Track; diff --git a/src/Magnum/Animation/Interpolation.cpp b/src/Magnum/Animation/Interpolation.cpp index 44e7873df..10b2c1e67 100644 --- a/src/Magnum/Animation/Interpolation.cpp +++ b/src/Magnum/Animation/Interpolation.cpp @@ -25,9 +25,25 @@ #include "Interpolation.h" +#include "Magnum/Math/DualQuaternion.h" + namespace Magnum { namespace Animation { #ifndef DOXYGEN_GENERATING_OUTPUT +Debug& operator<<(Debug& debug, const Interpolation value) { + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case Interpolation::value: return debug << "Animation::Interpolation::" #value; + _c(Constant) + _c(Linear) + _c(Custom) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "Animation::Interpolation(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + Debug& operator<<(Debug& debug, const Extrapolation value) { switch(value) { /* LCOV_EXCL_START */ @@ -43,4 +59,33 @@ Debug& operator<<(Debug& debug, const Extrapolation value) { } #endif +namespace Implementation { + +template auto TypeTraits, Math::Quaternion>::interpolator(Interpolation interpolation) -> Interpolator { + switch(interpolation) { + case Interpolation::Constant: return Math::select; + case Interpolation::Linear: return Math::slerp; + + case Interpolation::Custom: ; /* nope */ + } + + CORRADE_ASSERT(false, "Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {}); +} + +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::Custom: ; /* nope */ + } + + CORRADE_ASSERT(false, "Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {}); +} + +template struct MAGNUM_EXPORT TypeTraits, Math::Quaternion>; +template struct MAGNUM_EXPORT TypeTraits, Math::DualQuaternion>; + +} + }} diff --git a/src/Magnum/Animation/Interpolation.h b/src/Magnum/Animation/Interpolation.h index a7606f6a9..aed2ce90a 100644 --- a/src/Magnum/Animation/Interpolation.h +++ b/src/Magnum/Animation/Interpolation.h @@ -33,17 +33,42 @@ #include "Magnum/Magnum.h" #include "Magnum/Math/Functions.h" -#ifdef CORRADE_MSVC2015_COMPATIBILITY -#include "Magnum/Animation/Animation.h" /* ResultOf alias on MSVC 2015 */ -#endif +#include "Magnum/Animation/Animation.h" namespace Magnum { namespace Animation { -namespace Implementation { - template struct TypeTraits { - typedef T ResultType; - }; -} +/** +@brief Animation interpolation + +Describes the general desired way to interpolate animation keyframes. The +concrete choice of interpolator function is in user's hands. +@see @ref interpolatorFor() +@experimental +*/ +enum class Interpolation: UnsignedByte { + /** + * Constant interpolation. + * + * @see @ref Math::select() + */ + Constant, + + /** + * Linear interpolation. + * + * @see @ref Math::lerp(), @ref Math::slerp(), @ref Math::sclerp() + */ + Linear, + + /** + * Custom interpolation. An user-supplied interpolation function should be + * used. + */ + Custom +}; + +/** @debugoperatorenum{Interpolation} */ +MAGNUM_EXPORT Debug& operator<<(Debug& debug, Interpolation value); /** @brief Animation result type for given value type @@ -54,9 +79,35 @@ does not result in a spline). @experimental */ #ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ -template using ResultOf = typename Implementation::TypeTraits::ResultType; +template using ResultOf = typename Implementation::ResultTraits::Type; #endif +/** +@brief Interpolator function for given type +@tparam V Value type +@tparam R Result type + +Expects that @p interpolation is not @ref Interpolation::Custom. Favors +output correctness over performance, supply custom interpolator functions for +faster but less precise results. + +@m_class{m-fullwidth} + +Interpolation type | 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() +@experimental +*/ +template> auto interpolatorFor(Interpolation interpolation) -> R(*)(const V&, const V&, Float); + /** @brief Animation extrapolation behavior @@ -155,6 +206,66 @@ Used internally from @ref Track::atStrict() / @ref TrackView::atStrict(), see */ template> R interpolateStrict(const Containers::StridedArrayView& keys, const Containers::StridedArrayView& values, R(*interpolator)(const V&, const V&, Float), K frame, std::size_t& hint); +namespace Implementation { + +/* Generic types where result type is the same as value type */ +template struct ResultTraits { + typedef V Type; +}; +template struct TypeTraits { + typedef V(*Interpolator)(const V&, const V&, Float); + + static Interpolator interpolator(Interpolation interpolation); +}; +template auto TypeTraits::interpolator(Interpolation interpolation) -> Interpolator { + switch(interpolation) { + case Interpolation::Constant: return Math::select; + case Interpolation::Linear: return Math::lerp; + + case Interpolation::Custom: ; /* nope */ + } + + CORRADE_ASSERT(false, "Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {}); +} + +/* Specialization for booleans (no linear interpolation) */ +template struct TypeTraitsBool { + typedef T(*Interpolator)(const T&, const T&, Float); + + static Interpolator interpolator(Interpolation interpolation); +}; +template auto TypeTraitsBool::interpolator(Interpolation interpolation) -> Interpolator { + switch(interpolation) { + case Interpolation::Constant: + case Interpolation::Linear: return Math::select; + + case Interpolation::Custom: ; /* nope */ + } + + CORRADE_ASSERT(false, "Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {}); +} +template<> struct TypeTraits: TypeTraitsBool {}; +template struct TypeTraits, Math::BoolVector>: TypeTraitsBool> {}; + +/* 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); + + static Interpolator interpolator(Interpolation interpolation); +}; +template struct MAGNUM_EXPORT TypeTraits, Math::DualQuaternion> { + typedef Math::DualQuaternion(*Interpolator)(const Math::DualQuaternion&, const Math::DualQuaternion&, Float); + + static Interpolator interpolator(Interpolation interpolation); +}; + +} + +/* Needs to be defined later so it can pick up the TypeTraits definitions */ +template auto interpolatorFor(Interpolation interpolation) -> R(*)(const V&, const V&, Float) { + return Implementation::TypeTraits::interpolator(interpolation); +} + template R interpolate(const Containers::StridedArrayView& keys, const Containers::StridedArrayView& values, const Extrapolation before, const Extrapolation after, R(*const interpolator)(const V&, const V&, Float), K frame, std::size_t& hint) { CORRADE_ASSERT(keys.size() == values.size(), "Animation::interpolate(): keys and values don't have the same size", {}); diff --git a/src/Magnum/Animation/Test/CMakeLists.txt b/src/Magnum/Animation/Test/CMakeLists.txt index 7c5cc8dfe..636338ab2 100644 --- a/src/Magnum/Animation/Test/CMakeLists.txt +++ b/src/Magnum/Animation/Test/CMakeLists.txt @@ -23,7 +23,7 @@ # DEALINGS IN THE SOFTWARE. # -corrade_add_test(AnimationInterpolationTest InterpolationTest.cpp LIBRARIES Magnum) +corrade_add_test(AnimationInterpolationTest InterpolationTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(AnimationTrackTest TrackTest.cpp LIBRARIES Magnum) corrade_add_test(AnimationTrackViewTest TrackViewTest.cpp LIBRARIES Magnum) diff --git a/src/Magnum/Animation/Test/InterpolationTest.cpp b/src/Magnum/Animation/Test/InterpolationTest.cpp index 9daca7ef2..91bb92dfa 100644 --- a/src/Magnum/Animation/Test/InterpolationTest.cpp +++ b/src/Magnum/Animation/Test/InterpolationTest.cpp @@ -27,6 +27,7 @@ #include #include "Magnum/Animation/Interpolation.h" +#include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/Half.h" namespace Magnum { namespace Animation { namespace Test { @@ -34,6 +35,12 @@ namespace Magnum { namespace Animation { namespace Test { struct InterpolationTest: TestSuite::Tester { explicit InterpolationTest(); + void interpolatorFor(); + void interpolatorForBool(); + void interpolatorForBoolVector(); + void interpolatorForQuaternion(); + void interpolatorForDualQuaternion(); + void interpolate(); void interpolateStrict(); void interpolateSingleKeyframe(); @@ -51,11 +58,14 @@ struct InterpolationTest: TestSuite::Tester { void interpolateIntegerKey(); void interpolateStrictIntegerKey(); + void debugInterpolation(); void debugExtrapolation(); }; namespace { +using namespace Math::Literals; + const struct { const char* name; Extrapolation extrapolationBefore; @@ -125,6 +135,12 @@ const struct { } InterpolationTest::InterpolationTest() { + addTests({&InterpolationTest::interpolatorFor, + &InterpolationTest::interpolatorForBool, + &InterpolationTest::interpolatorForBoolVector, + &InterpolationTest::interpolatorForQuaternion, + &InterpolationTest::interpolatorForDualQuaternion}); + addInstancedTests({&InterpolationTest::interpolate, &InterpolationTest::interpolateStrict}, Containers::arraySize(Data)); @@ -147,9 +163,98 @@ InterpolationTest::InterpolationTest() { &InterpolationTest::interpolateIntegerKey, &InterpolationTest::interpolateStrictIntegerKey, + &InterpolationTest::debugInterpolation, &InterpolationTest::debugExtrapolation}); } +void InterpolationTest::interpolatorFor() { + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Constant)( + Vector2{0.3f, 0.5f}, Vector2{-0.3f, -1.5f}, 0.5f), (Vector2{0.3f, 0.5f})); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Linear)( + Vector2{0.3f, 0.5f}, Vector2{-0.3f, -1.5f}, 0.5f), (Vector2{0.0f, -0.5f})); + + 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::interpolatorForBool() { + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Constant)( + true, false, 0.5f), true); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Linear)( + true, false, 0.5f), true); + + 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::interpolatorForBoolVector() { + CORRADE_COMPARE(Animation::interpolatorFor>(Interpolation::Constant)( + Math::BoolVector<4>{0xa}, Math::BoolVector<4>{0x5}, 0.5f), (Math::BoolVector<4>{0xa})); + CORRADE_COMPARE(Animation::interpolatorFor>(Interpolation::Linear)( + Math::BoolVector<4>{0xa}, Math::BoolVector<4>{0x5}, 0.5f), (Math::BoolVector<4>{0xa})); + + 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()), + Quaternion::rotation(75.0_degf, Vector3::xAxis()), 0.5f), + Quaternion::rotation(25.0_degf, Vector3::xAxis())); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Linear)( + Quaternion::rotation(25.0_degf, Vector3::xAxis()), + Quaternion::rotation(75.0_degf, Vector3::xAxis()), 0.5f), + Quaternion::rotation(50.0_degf, Vector3::xAxis())); + + 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::interpolatorForDualQuaternion() { + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Constant)( + DualQuaternion::translation(Vector3::xAxis(2.5f)), + DualQuaternion::translation(Vector3::xAxis(7.5f)), 0.5f), + DualQuaternion::translation(Vector3::xAxis(2.5f))); + CORRADE_COMPARE(Animation::interpolatorFor(Interpolation::Linear)( + DualQuaternion::translation(Vector3::xAxis(2.5f)), + DualQuaternion::translation(Vector3::xAxis(7.5f)), 0.5f), + DualQuaternion::translation(Vector3::xAxis(5.0f))); + + 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}; @@ -295,6 +400,13 @@ void InterpolationTest::interpolateStrictError() { "Animation::interpolateStrict(): keys and values don't have the same size\n"); } +void InterpolationTest::debugInterpolation() { + std::ostringstream out; + + Debug{&out} << Interpolation::Custom << Interpolation(0xde); + CORRADE_COMPARE(out.str(), "Animation::Interpolation::Custom Animation::Interpolation(0xde)\n"); +} + void InterpolationTest::debugExtrapolation() { std::ostringstream out; diff --git a/src/Magnum/CMakeLists.txt b/src/Magnum/CMakeLists.txt index 9ed777f4a..10e23f106 100644 --- a/src/Magnum/CMakeLists.txt +++ b/src/Magnum/CMakeLists.txt @@ -32,14 +32,14 @@ set(Magnum_SRCS PixelStorage.cpp Resource.cpp Sampler.cpp - Timeline.cpp - - Animation/Interpolation.cpp) + Timeline.cpp) set(Magnum_GracefulAssert_SRCS Image.cpp ImageView.cpp - PixelFormat.cpp) + PixelFormat.cpp + + Animation/Interpolation.cpp) set(Magnum_HEADERS AbstractResourceLoader.h