#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, 2019, 2020, 2021, 2022, 2023 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::Interpolation. @ref Magnum::Animation::Extrapolation, function @ref Magnum::Animation::interpolatorFor(), @ref Magnum::Animation::interpolate(), @ref Magnum::Animation::interpolateStrict(), @ref Magnum::Animation::ease(), @ref Magnum::Animation::easeClamped() @ref Magnum::Animation::unpack(), @ref Magnum::Animation::unpackEase(), @ref Magnum::Animation::unpackEaseClamped() */ #include #include "Magnum/Magnum.h" #include "Magnum/Math/Functions.h" #include "Magnum/Animation/Animation.h" namespace Magnum { namespace Animation { /** @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, /** * Spline interpolation. * * @see @ref Math::splerp() */ Spline, /** * 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 Result of interpolating two `V` values (for example interpolating two @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 */ 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 potentially less correct results. @m_class{m-fullwidth} Interpolation | Value type | Result type | Interpolator ------------------- | ----------------- | ------------- | ------------ @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::BitVector | @ref Math::BitVector | @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); /** @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(), @ref Track::before(), @ref Track::after(), @ref TrackView::before(), @ref TrackView::after() @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 @tparam R Result 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. Used internally from @ref Track::at() / @ref TrackView::at(), see @ref Track documentation for more information. @see @ref interpolateStrict(), @ref Math::select(), @ref Math::lerp(), @ref Math::slerp(), @ref Math::sclerp() @experimental */ template> R interpolate(const Containers::StridedArrayView1D& keys, const Containers::StridedArrayView1D& values, Extrapolation before, Extrapolation after, R(*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. Used internally from @ref Track::atStrict() / @ref TrackView::atStrict(), see @ref Track documentation for more information. @see @ref Math::select(), @ref Math::lerp(), @ref Math::slerp(), @ref Math::sclerp() @experimental */ template> R interpolateStrict(const Containers::StridedArrayView1D& keys, const Containers::StridedArrayView1D& values, R(*interpolator)(const V&, const V&, Float), K frame, std::size_t& hint); /** @brief Combine easing function and an interpolator Useful to create a new function out of one of the interpolators from @ref transformations-interpolation and an easing function from @ref Easing. For example, the following two expressions give the same result: @snippet MagnumAnimation.cpp ease @see @ref unpack(), @ref unpackEase() */ template(*interpolator)(const V&, const V&, Float), Float(*easer)(Float)> constexpr auto ease() -> ResultOf(*)(const V&, const V&, Float) { return [](const V& a, const V& b, Float t) { return interpolator(a, b, easer(t)); }; } /** @brief Combine easing function and an interpolator In addition to @ref ease() clamps value coming to @p easer to range @f$ [0 ; 1] @f$. Useful when extrapolating using @ref Easing functions that have bad behavior outside of this range. */ template(*interpolator)(const V&, const V&, Float), Float(*easer)(Float)> constexpr auto easeClamped() -> ResultOf(*)(const V&, const V&, Float) { return [](const V& a, const V& b, Float t) { return interpolator(a, b, easer(Math::clamp(t, 0.0f, 1.0f))); }; } /** @brief Combine unpacking function and an interpolator Similar to @ref ease(), but for adding an unpacker function to interpolator inputs instead of modifying the interpolator phase. The following two expressions give the same result: @snippet MagnumAnimation.cpp unpack @see @ref unpackEase() */ template(*interpolator)(const V&, const V&, Float), V(*unpacker)(const T&)> constexpr auto unpack() -> ResultOf(*)(const V&, const V&, Float) { return [](const V& a, const V& b, Float t) { return interpolator(unpacker(a), unpacker(b), t); }; } /** @brief Combine unpacking and easing functions with an interpolator Combination of @ref ease() and @ref unpack(), creating a function that first unpack the interpolator inputs, then modifies the interpolator phase and finally passes that to the interpolator function. The following two expressions give the same result: @snippet MagnumAnimation.cpp unpackEase */ template(*interpolator)(const V&, const V&, Float), V(*unpacker)(const T&), Float(*easer)(Float)> constexpr auto unpackEase() -> ResultOf(*)(const V&, const V&, Float) { return [](const V& a, const V& b, Float t) { return interpolator(unpacker(a), unpacker(b), easer(t)); }; } /** @brief Combine easing function and an interpolator In addition to @ref unpackEase() clamps value coming to @p easer to range @f$ [0 ; 1] @f$. Useful when extrapolating with @ref Easing functions that have bad behavior outside of this range. */ template(*interpolator)(const V&, const V&, Float), V(*unpacker)(const T&), Float(*easer)(Float)> constexpr auto unpackEaseClamped() -> ResultOf(*)(const V&, const V&, Float) { return [](const V& a, const V& b, Float t) { return interpolator(unpacker(a), unpacker(b), easer(Math::clamp(t, 0.0f, 1.0f))); }; } namespace Implementation { /* Generic types where result type is the same as value type */ template struct ResultTraits { typedef typename std::remove_const::type Type; }; template struct ResultTraits> { typedef T Type; }; template struct ResultTraits> { typedef T 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::Spline: case Interpolation::Custom: ; /* nope */ } CORRADE_ASSERT_UNREACHABLE("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::Spline: case Interpolation::Custom: ; /* nope */ } CORRADE_ASSERT_UNREACHABLE("Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {}); } template<> struct TypeTraits: TypeTraitsBool {}; template struct TypeTraits, Math::BitVector>: TypeTraitsBool> {}; /* Complex, preferring slerp() as it is more precise */ template struct #ifndef CORRADE_TARGET_CLANG_CL /* Clang-CL complains that it's ignored if it's on the class, so putting it on the function instead. However MSVC doesn't like that, so doing this only for Clang-CL. */ MAGNUM_EXPORT #endif TypeTraits, Math::Complex> { typedef Math::Complex(*Interpolator)(const Math::Complex&, const Math::Complex&, Float); static #ifdef CORRADE_TARGET_CLANG_CL MAGNUM_EXPORT #endif Interpolator interpolator(Interpolation interpolation); }; /* Quaternions and dual quaternions, preferring slerp() as it is more precise */ template struct #ifndef CORRADE_TARGET_CLANG_CL /* Clang-CL complains that it's ignored if it's on the class, so putting it on the function instead. However MSVC doesn't like that, so doing this only for Clang-CL. */ MAGNUM_EXPORT #endif TypeTraits, Math::Quaternion> { typedef Math::Quaternion(*Interpolator)(const Math::Quaternion&, const Math::Quaternion&, Float); static #ifdef CORRADE_TARGET_CLANG_CL MAGNUM_EXPORT #endif Interpolator interpolator(Interpolation interpolation); }; template struct #ifndef CORRADE_TARGET_CLANG_CL /* Clang-CL complains that it's ignored if it's on the class, so putting it on the function instead. However MSVC doesn't like that, so doing this only for Clang-CL. */ MAGNUM_EXPORT #endif TypeTraits, Math::DualQuaternion> { typedef Math::DualQuaternion(*Interpolator)(const Math::DualQuaternion&, const Math::DualQuaternion&, Float); static #ifdef CORRADE_TARGET_CLANG_CL MAGNUM_EXPORT #endif Interpolator interpolator(Interpolation interpolation); }; /* Cubic Hermite spline point has a different result type */ template struct #ifndef CORRADE_TARGET_CLANG_CL /* Clang-CL complains that it's ignored if it's on the class, so putting it on the function instead. However MSVC doesn't like that, so doing this only for Clang-CL. */ MAGNUM_EXPORT #endif TypeTraits, T> { typedef T(*Interpolator)(const Math::CubicHermite&, const Math::CubicHermite&, Float); static #ifdef CORRADE_TARGET_CLANG_CL MAGNUM_EXPORT #endif 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::type, R>::interpolator(interpolation); } template R interpolate(const Containers::StridedArrayView1D& keys, const Containers::StridedArrayView1D& 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", {}); /* 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(Float(keys[hint]), Float(keys[hint + 1]), Float(frame))); } template R interpolateStrict(const Containers::StridedArrayView1D& keys, const Containers::StridedArrayView1D& values, R(*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(Float(keys[hint]), Float(keys[hint + 1]), Float(frame))); } }} #endif