diff --git a/doc/changelog.dox b/doc/changelog.dox index 63e6736ba..72a140e97 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -40,6 +40,11 @@ See also: @subsection changelog-latest-new New features +@subsubsection changelog-latest-new-animation Animation library + +- New experimental @ref Animation library for keyframe-based animation + playback + @subsubsection changelog-latest-new-math Math library - Added @ref Math::Intersection::rangeFrustum(), diff --git a/doc/namespaces.dox b/doc/namespaces.dox index 37a5efb53..bf8056801 100644 --- a/doc/namespaces.dox +++ b/doc/namespaces.dox @@ -178,6 +178,18 @@ target_link_libraries(your-app Magnum::Magnum) See @ref building and @ref cmake for more information. */ +/** @dir Magnum/Animation + * @brief Namespace @ref Magnum::Animation + */ +/** @namespace Magnum::Animation +@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. +@experimental +*/ + /** @dir Magnum/Audio * @brief Namespace @ref Magnum::Audio, @ref Magnum::Audio::Extensions */ diff --git a/src/Magnum/Animation/Animation.h b/src/Magnum/Animation/Animation.h new file mode 100644 index 000000000..cb4978be9 --- /dev/null +++ b/src/Magnum/Animation/Animation.h @@ -0,0 +1,46 @@ +#ifndef Magnum_Animation_Animation_h +#define Magnum_Animation_Animation_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 Forward declarations for @ref Magnum::Animation namespace + */ + +#include "Magnum/Types.h" + +namespace Magnum { namespace Animation { + +namespace Implementation { + template struct TypeTraits; +} + +template using ResultOf = typename Implementation::TypeTraits::ResultType; + +enum class Extrapolation: UnsignedByte; + +}} + +#endif diff --git a/src/Magnum/Animation/CMakeLists.txt b/src/Magnum/Animation/CMakeLists.txt new file mode 100644 index 000000000..364dbaa21 --- /dev/null +++ b/src/Magnum/Animation/CMakeLists.txt @@ -0,0 +1,38 @@ +# +# 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. +# + +set(MagnumAnimation_HEADERS + Animation.h + Interpolation.h) + +# Force IDEs to display all header files in project view +add_custom_target(MagnumAnimation SOURCES ${MagnumAnimation_HEADERS}) +set_target_properties(MagnumAnimation PROPERTIES FOLDER "Magnum/Animation") + +install(FILES ${MagnumAnimation_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Animation) + +if(BUILD_TESTS) + add_subdirectory(Test) +endif() diff --git a/src/Magnum/Animation/Interpolation.cpp b/src/Magnum/Animation/Interpolation.cpp new file mode 100644 index 000000000..44e7873df --- /dev/null +++ b/src/Magnum/Animation/Interpolation.cpp @@ -0,0 +1,46 @@ +/* + 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 "Interpolation.h" + +namespace Magnum { namespace Animation { + +#ifndef DOXYGEN_GENERATING_OUTPUT +Debug& operator<<(Debug& debug, const Extrapolation value) { + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case Extrapolation::value: return debug << "Animation::Extrapolation::" #value; + _c(DefaultConstructed) + _c(Constant) + _c(Extrapolated) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "Animation::Extrapolation(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} +#endif + +}} diff --git a/src/Magnum/Animation/Interpolation.h b/src/Magnum/Animation/Interpolation.h new file mode 100644 index 000000000..7341f7cb2 --- /dev/null +++ b/src/Magnum/Animation/Interpolation.h @@ -0,0 +1,201 @@ +#ifndef Magnum_Animation_Interpolation_h +#define Magnum_Animation_Interpolation_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 Alias @ref Magnum::Animation::ResultOf, enum @ref Magnum::Animation::Extrapolation, function @ref Magnum::Animation::interpolate(), @ref Magnum::Animation::interpolateStrict() + */ + +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Math/Functions.h" +#ifdef CORRADE_MSVC2015_COMPATIBILITY +#include "Magnum/Animation/Animation.h" /* ResultOf alias on MSVC 2015 */ +#endif + +namespace Magnum { namespace Animation { + +namespace Implementation { + template struct TypeTraits { + typedef T ResultType; + }; +} + +/** +@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). +@experimental +*/ +#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ +template using ResultOf = typename Implementation::TypeTraits::ResultType; +#endif + +/** +@brief Animation extrapolation behavior + +Describes what value is returned for frames outside of keyframe range for given +track (frame lower than first keyframe or frame larger or equal to last +keyframe). +@see @ref interpolate() +@experimental +*/ +enum class Extrapolation: UnsignedByte { + /** + * Values of first two / last two keyframes are extrapolated. In case + * there is only one keyframe, it's passed to both inputs of the + * interpolator. Implicit behavior in @ref interpolateStrict(). + */ + Extrapolated, + + /** + * Value of first/last keyframe is used. In other words, for the first + * keyframe the interpolator is called with first two keyframes and + * interpolation factor set to `0.0f`; for the last keyframe the + * interpolator is called with last two keyframes and interpolation factor + * set to `1.0f`. In case there is only one keyframe, it's passed to both + * inputs of the interpolator. + */ + Constant, + + /** Default-constructed value is returned. */ + DefaultConstructed, + + /** @todo repeat? that would duplicate the play count feature though */ +}; + +/** @debugoperatorenum{Extrapolation} */ +MAGNUM_EXPORT Debug& operator<<(Debug& debug, Extrapolation value); + +/** +@brief Interpolate animation value +@tparam K Key type +@tparam V Value type +@param keys Keys +@param values Values +@param before Extrapolation mode before first keyframe +@param after Extrapolation mode after last keyframe +@param interpolator Interpolator function +@param frame Frame at which to interpolate +@param hint Hint for keyframe search + +Does a linear search over the keyframes until it finds last keyframe which is +not larger than @p frame. Once the keyframe is found, reference to it and the immediately following keyframe is passed to @p interpolator along with +calculated interpolation factor, returning the interpolated value. + +- In case the first keyframe is already larger than @p frame or @p frame is + larger or equal to the last keyframe, either the first two or last two + keyframes are used and value is extrapolated according to @p before / + @p after. +- In case only one keyframe is present, its value is used for both sides of + the interpolator. +- In case no keyframes are present, default-constructed value is returned. + +The @p hint parameter hints where to start the linear search and is updated +with keyframe index matching @p frame. If @p frame is earlier than @p hint, the +search is restarted from the beginning. +@see @ref interpolateStrict(), @ref Math::select(), @ref Math::lerp(), + @ref Math::slerp(), @ref Math::sclerp() +@experimental +*/ +template ResultOf interpolate(const Containers::StridedArrayView& keys, const Containers::StridedArrayView& values, Extrapolation before, Extrapolation after, ResultOf(*interpolator)(const V&, const V&, Float), K frame, std::size_t& hint); + +/** +@brief Interpolate animation value with strict constraints + +Does a linear search over the keyframes until it finds last keyframe which is +not larger than @p frame. Once the keyframe is found, reference to it and the immediately following keyframe is passed to @p interpolator along with +calculated interpolation factor, returning the interpolated value. The @p hint +parameter hints where to start the linear search and is updated with keyframe +index matching @p frame. If @p frame is earlier than @p hint, the search is +restarted from the beginning. + +This is a stricter but more performant version of @ref interpolate() with +implicit @ref Extrapolation::Extrapolated behavior. Expects that there are +always at least two keyframes. +@see @ref Math::select(), @ref Math::lerp(), @ref Math::slerp(), + @ref Math::sclerp() +@experimental +*/ +template ResultOf interpolateStrict(const Containers::StridedArrayView& keys, const Containers::StridedArrayView& values, ResultOf(*interpolator)(const V&, const V&, Float), K frame, std::size_t& hint); + +template ResultOf interpolate(const Containers::StridedArrayView& keys, const Containers::StridedArrayView& values, const Extrapolation before, const Extrapolation after, ResultOf(*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", {}); + + /* No data, return default-constructed value */ + if(!keys.size()) return {}; + + /* Only one frame, return it verbatim (or default-constructed, if desired) */ + if(keys.size() == 1) { + if((frame < keys[0] && before == Extrapolation::DefaultConstructed) || + (frame > keys[0] && after == Extrapolation::DefaultConstructed)) + return {}; + + return interpolator(values[0], values[0], 0.0f); + } + + /* Rewind from the beginning if hint is too late */ + if(hint >= keys.size() || frame < keys[hint]) hint = 0; + + /* Go through the keys until we find a pair that is around given time */ + while(hint + 2 < keys.size() && frame >= keys[hint + 1]) + ++hint; + + /* Special extrapolation outside of range. Usual extrapolation is handled + below. */ + if(frame < keys[hint]) { + if(before == Extrapolation::DefaultConstructed) return {}; + if(before == Extrapolation::Constant) frame = keys[hint]; + } else if(frame >= keys[hint + 1]) { + if(after == Extrapolation::DefaultConstructed) return {}; + if(after == Extrapolation::Constant) frame = keys[hint + 1]; + } + + return interpolator(values[hint], values[hint + 1], + Math::lerpInverted(keys[hint], keys[hint + 1], frame)); +} + +template ResultOf interpolateStrict(const Containers::StridedArrayView& keys, const Containers::StridedArrayView& values, ResultOf(*const interpolator)(const V&, const V&, Float), const K frame, std::size_t& hint) { + CORRADE_ASSERT(keys.size() >= 2, "Animation::interpolateStrict(): at least two keyframes required", {}); + CORRADE_ASSERT(keys.size() == values.size(), "Animation::interpolateStrict(): keys and values don't have the same size", {}); + + /* Rewind from the beginning if hint is too late */ + if(hint >= keys.size() || frame < keys[hint]) hint = 0; + + /* Go through the keys until we find a pair that is around given time */ + while(hint + 2 < keys.size() && frame >= keys[hint + 1]) + ++hint; + + return interpolator(values[hint], values[hint + 1], + Math::lerpInverted(keys[hint], keys[hint + 1], frame)); +} + +}} + +#endif diff --git a/src/Magnum/Animation/Test/CMakeLists.txt b/src/Magnum/Animation/Test/CMakeLists.txt new file mode 100644 index 000000000..22d5235a8 --- /dev/null +++ b/src/Magnum/Animation/Test/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# 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. +# + +corrade_add_test(AnimationInterpolationTest InterpolationTest.cpp LIBRARIES Magnum) + +set_property(TARGET + AnimationInterpolationTest + APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") + +set_target_properties( + AnimationInterpolationTest + PROPERTIES FOLDER "Magnum/Animation/Test") diff --git a/src/Magnum/Animation/Test/InterpolationTest.cpp b/src/Magnum/Animation/Test/InterpolationTest.cpp new file mode 100644 index 000000000..fb100ba89 --- /dev/null +++ b/src/Magnum/Animation/Test/InterpolationTest.cpp @@ -0,0 +1,252 @@ +/* + 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/Animation/Interpolation.h" + +namespace Magnum { namespace Animation { namespace Test { + +struct InterpolationTest: TestSuite::Tester { + explicit InterpolationTest(); + + void interpolate(); + void interpolateStrict(); + void interpolateSingleKeyframe(); + void interpolateNoKeyframe(); + + void interpolateHint(); + void interpolateStrictHint(); + + void interpolateError(); + void interpolateStrictError(); + + void debugExtrapolation(); +}; + +namespace { + +const struct { + const char* name; + Extrapolation extrapolationBefore; + Extrapolation extrapolationAfter; + Float time; + Float expectedValue, expectedValueStrict; + std::size_t expectedHint; +} Data[] { + {"before default-constructed", + Extrapolation::DefaultConstructed, Extrapolation::Extrapolated, + -1.0f, 0.0f, 4.0f, 0}, + {"before constant", + Extrapolation::Constant, Extrapolation::Extrapolated, + -1.0f, 3.0f, 4.0f, 0}, + {"before extrapolated", + Extrapolation::Extrapolated, Extrapolation::DefaultConstructed, + -1.0f, 4.0f, 4.0f, 0}, + {"during first", + Extrapolation::DefaultConstructed, Extrapolation::DefaultConstructed, + 1.5f, 1.5f, 1.5f, 0}, + {"during second", + Extrapolation::DefaultConstructed, Extrapolation::DefaultConstructed, + 4.75f, 1.0f, 1.0f, 2}, + {"after default-constructed", + Extrapolation::Extrapolated, Extrapolation::DefaultConstructed, + 6.0f, 0.0f, -1.5f, 2}, + {"after constant", + Extrapolation::Extrapolated, Extrapolation::Constant, + 6.0f, 0.5f, -1.5f, 2}, + {"after extrapolated", + Extrapolation::DefaultConstructed, Extrapolation::Extrapolated, + 6.0f, -1.5f, -1.5f, 2} +}; + +const struct { + const char* name; + Extrapolation extrapolation; + Float time; + Float expectedValue; +} SingleKeyframeData[] { + {"before default-constructed", + Extrapolation::DefaultConstructed, -1.0f, 0.0f}, + {"before constant", + Extrapolation::Constant, -1.0f, 3.0f}, + {"before extrapolated", + Extrapolation::Extrapolated, -1.0f, 3.0f}, + {"at", + Extrapolation::DefaultConstructed, 0.0f, 3.0f}, + {"after default-constructed", + Extrapolation::DefaultConstructed, 1.0f, 0.0f}, + {"after constant", + Extrapolation::Constant, 1.0f, 3.0f}, + {"after extrapolated", + Extrapolation::Extrapolated, 1.0f, 3.0f} +}; + +const struct { + const char* name; + std::size_t hint; +} HintData[] { + {"before", 1}, + {"at", 2}, + {"after", 3}, + {"out of bounds", 405780454} +}; + +} + +InterpolationTest::InterpolationTest() { + addInstancedTests({&InterpolationTest::interpolate, + &InterpolationTest::interpolateStrict}, + Containers::arraySize(Data)); + + addInstancedTests({&InterpolationTest::interpolateSingleKeyframe}, + Containers::arraySize(SingleKeyframeData)); + + addTests({&InterpolationTest::interpolateNoKeyframe}); + + addInstancedTests({&InterpolationTest::interpolateHint, + &InterpolationTest::interpolateStrictHint}, + Containers::arraySize(HintData)); + + addTests({&InterpolationTest::interpolateError, + &InterpolationTest::interpolateStrictError, + + &InterpolationTest::debugExtrapolation}); +} + +namespace { + constexpr Float Keys[]{0.0f, 2.0f, 4.0f, 5.0f}; + constexpr Float Values[]{3.0f, 1.0f, 2.5f, 0.5f}; +} + +void InterpolationTest::interpolate() { + const auto& data = Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + std::size_t hint{}; + CORRADE_COMPARE((Animation::interpolate( + Keys, Values, data.extrapolationBefore, data.extrapolationAfter, + Math::lerp, data.time, hint)), data.expectedValue); + CORRADE_COMPARE(hint, data.expectedHint); +} + +void InterpolationTest::interpolateStrict() { + const auto& data = Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + std::size_t hint{}; + CORRADE_COMPARE((Animation::interpolateStrict( + Keys, Values, Math::lerp, data.time, hint)), data.expectedValueStrict); + CORRADE_COMPARE(hint, data.expectedHint); +} + +void InterpolationTest::interpolateSingleKeyframe() { + const auto& data = SingleKeyframeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + std::size_t hint{}; + CORRADE_COMPARE((Animation::interpolate( + Containers::arrayView(Keys).prefix(1), + Containers::arrayView(Values).prefix(1), + data.extrapolation, data.extrapolation, + Math::lerp, data.time, hint)), data.expectedValue); + CORRADE_COMPARE(hint, 0); +} + +void InterpolationTest::interpolateNoKeyframe() { + std::size_t hint{}; + CORRADE_COMPARE((Animation::interpolate( + nullptr, nullptr, Extrapolation::Extrapolated, + Extrapolation::Extrapolated, Math::lerp, 3.5f, hint)), Float{}); + CORRADE_COMPARE(hint, 0); +} + +void InterpolationTest::interpolateHint() { + const auto& data = HintData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + std::size_t hint = data.hint; + CORRADE_COMPARE((Animation::interpolate( + Keys, Values, Extrapolation::Extrapolated, Extrapolation::Extrapolated, + Math::lerp, 4.75f, hint)), 1.0f); + CORRADE_COMPARE(hint, 2); +} + +void InterpolationTest::interpolateStrictHint() { + const auto& data = HintData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + std::size_t hint = data.hint; + CORRADE_COMPARE((Animation::interpolateStrict( + Keys, Values, Math::lerp, 4.75f, hint)), 1.0f); + CORRADE_COMPARE(hint, 2); +} + +void InterpolationTest::interpolateError() { + std::ostringstream out; + Error redirectError{&out}; + + { + std::size_t hint{}; + Animation::interpolate(Keys, nullptr, Extrapolation::Extrapolated, Extrapolation::Extrapolated, Math::lerp, 0.0f, hint); + } + + CORRADE_COMPARE(out.str(), + "Animation::interpolate(): keys and values don't have the same size\n"); +} + +void InterpolationTest::interpolateStrictError() { + std::ostringstream out; + Error redirectError{&out}; + + { + std::size_t hint{}; + Animation::interpolateStrict( + Containers::arrayView(Keys).prefix(1), + Containers::arrayView(Values).prefix(1), + Math::lerp, 0.0f, hint); + } { + std::size_t hint{}; + Animation::interpolateStrict( + Containers::arrayView(Keys).prefix(3), Values, + Math::lerp, 0.0f, hint); + } + + CORRADE_COMPARE(out.str(), + "Animation::interpolateStrict(): at least two keyframes required\n" + "Animation::interpolateStrict(): keys and values don't have the same size\n"); +} + +void InterpolationTest::debugExtrapolation() { + std::ostringstream out; + + Debug{&out} << Extrapolation::DefaultConstructed << Extrapolation(0xde); + CORRADE_COMPARE(out.str(), "Animation::Extrapolation::DefaultConstructed Animation::Extrapolation(0xde)\n"); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Animation::Test::InterpolationTest) diff --git a/src/Magnum/CMakeLists.txt b/src/Magnum/CMakeLists.txt index dfa75b48c..9ed777f4a 100644 --- a/src/Magnum/CMakeLists.txt +++ b/src/Magnum/CMakeLists.txt @@ -32,7 +32,9 @@ set(Magnum_SRCS PixelStorage.cpp Resource.cpp Sampler.cpp - Timeline.cpp) + Timeline.cpp + + Animation/Interpolation.cpp) set(Magnum_GracefulAssert_SRCS Image.cpp @@ -199,6 +201,7 @@ install(TARGETS Magnum install(FILES ${Magnum_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/configure.h DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}) +add_subdirectory(Animation) add_subdirectory(Math) add_subdirectory(Platform)