mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
721 lines
34 KiB
721 lines
34 KiB
#ifndef Magnum_Animation_Player_h |
|
#define Magnum_Animation_Player_h |
|
/* |
|
This file is part of Magnum. |
|
|
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 |
|
Vladimír Vondruš <mosra@centrum.cz> |
|
|
|
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::Animation::Player, enum @ref Magnum::Animation::State |
|
*/ |
|
|
|
#include <chrono> |
|
#include <vector> |
|
|
|
#include "Magnum/Animation/Track.h" |
|
#include "Magnum/Math/Range.h" |
|
|
|
namespace Magnum { namespace Animation { |
|
|
|
/** |
|
@brief Player state |
|
|
|
@see @ref Player |
|
@experimental |
|
*/ |
|
enum class State: UnsignedByte { |
|
/** |
|
* The animation clip is currently playing. Setting the state to |
|
* @ref State::Playing does nothing. |
|
*/ |
|
Playing, |
|
|
|
/** |
|
* The animation clip is currently paused. Setting the state to |
|
* @ref State::Playing starts playing from where it left, setting the state |
|
* to @ref State::Stopped stops the animation, setting the state to |
|
* @ref State::Paused does nothing. |
|
*/ |
|
Paused, |
|
|
|
/** |
|
* The animation clip is currently stopped. Setting the state to |
|
* @ref State::Playing starts playing from the beginning, attempting to set |
|
* the state to @ref State::Paused will retain the @ref State::Stopped |
|
* state, setting the state to @ref State::Stopped does nothing. |
|
*/ |
|
Stopped |
|
}; |
|
|
|
/** @debugoperatorenum{State} */ |
|
MAGNUM_EXPORT Debug& operator<<(Debug& debug, State value); |
|
|
|
namespace Implementation { |
|
template<class, class> struct DefaultScaler; |
|
} |
|
|
|
/** |
|
@brief Animation player |
|
@tparam T Time type |
|
@tparam K Key type |
|
|
|
Provides a generic way for querying interpolated results from multiple |
|
animation tracks of distinct types from a single place, together with managing |
|
their running state. |
|
|
|
@section Animation-Player-setup Setting up |
|
|
|
The @ref Player class is used by adding tracks to it and specifying what should |
|
be done with interpolation result values. The simplest option is specifying a |
|
destination location when adding the track using @ref add() --- that'll mean |
|
you get a fresh set of animated values at your disposal after every iteration: |
|
|
|
@snippet MagnumAnimation.cpp Player-usage |
|
|
|
The Player stores just @ref TrackView instances, for every @ref Track instance |
|
you have to ensure that it stays alive for the whole lifetime of the player |
|
instance. |
|
|
|
In case you need to apply the animated values using a setter, it's possible |
|
to fire a callback every iteration. Note that the @ref addWithCallback() |
|
function has also a typeless version taking just @cpp void* @ce user pointer |
|
instead of a reference to a concrete type. Below is an example of animating |
|
@ref SceneGraph object transformations using the |
|
@ref SceneGraph::TranslationRotationScalingTransformation3D transformation |
|
implementation: |
|
|
|
@snippet MagnumAnimation.cpp Player-usage-callback |
|
|
|
The @ref addWithCallbackOnChange() variant will fire the callback only if the |
|
interpolated value changes, which is useful for triggering other events. See |
|
@ref Animation-Player-higher-order "below" for an example. Lastly, there is |
|
@ref addRawCallback() that allows for greater control and further performance |
|
optimizations. See its documentation for a usage example code snippet. |
|
|
|
The animation is implicitly played only once, use @ref setPlayCount() to set a |
|
number of repeats or make it repeat indefinitely. By default, the |
|
@ref duration() of an animation is calculated implicitly from all added tracks. |
|
You can use @ref setDuration() to specify a custom duration: |
|
|
|
- If it extends beyond the keyframe values, values of begin/end keyframes |
|
will be extrapolated according to @ref Extrapolation specified for every |
|
track. |
|
- If it will be shorter, only a slice of the animation will be played. |
|
- If duration size is empty (min and and max se to the same value) and |
|
@ref setPlayCount() is set to inifite, then the animator will indefinitely |
|
give out value from a key that's at the start of the duration. If play |
|
count is finite, the animation will get stopped right away. |
|
|
|
@section Animation-Player-playback Animation playback |
|
|
|
The @ref Player class doesn't access any global timer functionality, but |
|
instead requires you to call its APIs with explicit time values. That allows |
|
for greater flexibility and control over animation playback, among other |
|
things. |
|
|
|
By default, the player is in a @ref State::Stopped state. Call @ref play() with |
|
a time value denoting the moment at which the animation should start. After |
|
that, the @ref advance() function is meant to be called every frame with |
|
a current time value. As long as the animation is playing, the @ref advance() |
|
function will update track result destination locations with interpolated |
|
values and/or fire user-defined callbacks described above. |
|
|
|
Once the animation playback is finished (exhausing the whole @ref duration() of |
|
all @ref playCount() iterations), the @ref advance() will update the |
|
destination locations and/or fire user-defined callbacks with values that |
|
correspond to @ref duration() end time. This is guaranteed to be always the |
|
case in order to correctly "park" the animations --- even if your app would |
|
freeze for a while and @ref advance() would get called later, the result values |
|
will never be calculated from a key value that's outside @ref duration(). |
|
|
|
Calling @ref stop() immediately transfers @ref state() to @ref State::Stopped |
|
and the next @ref advance() iteration will give out interpolated values |
|
corresponding to the begin time of @ref duration(), again to "park" the |
|
animation back to its initial state. After that, no more updates are done until |
|
the animation is started again. Compared to when the animation stops by itself, |
|
this will park it at the beginning, not at the end. |
|
|
|
Calling @ref pause() while the animation is running immediately transfers the |
|
animation state to @ref State::Paused and the next @ref advance() iteration |
|
will give out interpolated values corresponding to a time that was passed to |
|
the @ref pause() function. After that, no more updates are done until the |
|
animation is resumed again with @ref play() or stopped with @ref stop(). |
|
|
|
The callbacks are only ever fired from within the @ref advance() function, |
|
never from @ref pause(), @ref stop() or any other API. |
|
|
|
For managing global application you can use @ref Timeline, @ref std::chrono |
|
APIs or any other type that supports basic arithmetic. The time doesn't have to |
|
be monotonic or have constant speed, but note that non-continuous and backward |
|
time jumps may have worse performance than going monotonically forward. See |
|
@ref Animation-Player-time-type "below" for more information about using |
|
different time types. |
|
|
|
@snippet MagnumAnimation.cpp Player-usage-playback |
|
|
|
@section Animation-Player-time-type Using custom time/key types |
|
|
|
In long-running apps it's not desirable to use @ref Magnum::Float "Float" for |
|
global application time, since its precision will deteriorate over time. Even |
|
after one hour the precision loss might start to get noticeable. To overcome |
|
this problem, it's possible to specify a type for time values that's different |
|
from type used for animation track keys. In contrast, using |
|
@ref Magnum::Float "Float" for animation track key values is usually good |
|
enough, as the tracks are never too long for this to become a problem --- and |
|
if the tracks *are* long, you can always use a different key type for them as |
|
well. A good choice is @ref std::chrono::nanoseconds as a time type and keeping |
|
track key values as @ref Magnum::Float "Float" seconds: |
|
|
|
@snippet MagnumAnimation.cpp Player-usage-chrono |
|
|
|
While there's a builtin support for the above, you are free to use any other |
|
type combination --- for that you need to provide a *scaler* function that will |
|
take care of converting a time difference to play iteration index and key value |
|
inside given iteration. The types should be implicitly constructible, and have |
|
basic arithmetic and comparison operators. In order to reduce header size, the |
|
@ref Player implementation is in a separate @ref Player.hpp file that you need |
|
to include to get all needed template function definitions. See also |
|
@ref compilation-speedup-hpp for more information. |
|
|
|
@snippet MagnumAnimation-custom.cpp Player-usage-custom |
|
|
|
@section Animation-Player-higher-order Higher-order players, animating time |
|
|
|
Sometimes you might want to control multiple players at the same time or |
|
animate player state. That's doable by creating specialized tracks that control |
|
given player via a state change callback. By adding more tracks you can control |
|
multiple players from a central location. |
|
|
|
@snippet MagnumAnimation.cpp Player-higher-order |
|
|
|
Besides state, you can also animate @ref setDuration() and @ref setPlayCount(), |
|
but be aware that setting those while the animation is playing might cause |
|
unwanted jumps and abrupt stops. Time is also completely in your control and |
|
you can employ another @ref Player instance to speed it up or slow it down for |
|
a particular animation: |
|
|
|
@snippet MagnumAnimation.cpp Player-higher-order-animated-time |
|
|
|
@section Animation-Player-explicit-specializations Explicit template specializations |
|
|
|
The following specializations are explicitly compiled into the @ref Animation |
|
library. For other specializations (e.g. using an integer key type) you have to |
|
use the @ref Player.hpp implementation file to avoid linker errors. See also |
|
@ref compilation-speedup-hpp for more information. |
|
|
|
- @ref Player "Player<Float, Float>" |
|
- @ref Player "Player<std::chrono::nanoseconds, Float>" |
|
|
|
@experimental |
|
*/ |
|
template<class T, class K |
|
#ifdef DOXYGEN_GENERATING_OUTPUT |
|
= T |
|
#endif |
|
> class Player { |
|
public: |
|
/** @brief Time type */ |
|
typedef T TimeType; |
|
|
|
/** @brief Key type */ |
|
typedef K KeyType; |
|
|
|
/** |
|
* @brief Scaler function type |
|
* |
|
* The function gets time from when the animation started and combined |
|
* duration of all tracks; returns play iteration index and key value |
|
* inside given iteration. The combined duration is guaranteed to be |
|
* always non-zero, zero durations are handled by the player itself. |
|
*/ |
|
typedef std::pair<UnsignedInt, K>(*Scaler)(T, K); |
|
|
|
/** @brief Constructor */ |
|
explicit Player(); |
|
|
|
/** |
|
* @brief Construct with a custom scaler function |
|
* @param scaler Scaler function |
|
*/ |
|
explicit Player(Scaler scaler); |
|
|
|
/** @brief Copying is not allowed */ |
|
Player(const Player<T, K>&) = delete; |
|
|
|
/** @brief Move constructor */ |
|
/* The noexcept specifier was removed because it causes *extreme* |
|
issues on various Clang versions (iOS, Android, Emscripten) |
|
currently used on Travis CI. Not on my stock 6.0 or NDK r17 tho. */ |
|
Player(Player<T, K>&&); |
|
|
|
~Player(); |
|
|
|
/** @brief Copying is not allowed */ |
|
Player<T, K>& operator=(const Player<T, K>&) = delete; |
|
|
|
/** @brief Move assignment */ |
|
/* Deliberately not noexcept, see above */ |
|
Player<T, K>& operator=(Player<T, K>&&); |
|
|
|
/** @brief Time-to-key scaler */ |
|
Scaler scaler() const { return _scaler; } |
|
|
|
/** |
|
* @brief Duration |
|
* |
|
* If the duration was not set explicitly using @ref setDuration(), |
|
* returns value calculated implicitly from all added tracks. If no |
|
* tracks are added, returns default-constructed value. |
|
*/ |
|
Math::Range1D<K> duration() const { return _duration; } |
|
|
|
/** |
|
* @brief Set duration |
|
* |
|
* The duration is initially a default-constructed value, then |
|
* calculated implicitly from added tracks. Setting it explicitly will |
|
* overwrite the implicitly calculated value. Adding a track after the |
|
* duration was set explicitly will extend the duration to span all |
|
* track durations. |
|
* |
|
* Setting a duration that extends beyond the keyframe values will |
|
* cause values of begin/end keyframes to be extrapolated according to |
|
* @ref Extrapolation specified for given track. Setting a shorter |
|
* duration will cause only a slice of all tracks to be played. |
|
* |
|
* Modifying this value while @ref state() is @ref State::Playing may |
|
* cause the animation to jump or abruptly stop after next call to |
|
* @ref advance(). |
|
* @see @ref TrackView::duration() |
|
*/ |
|
Player<T, K>& setDuration(const Math::Range1D<K>& duration) { |
|
_duration = duration; |
|
return *this; |
|
} |
|
|
|
/** @brief Play count */ |
|
UnsignedInt playCount() const { return _playCount; } |
|
|
|
/** |
|
* @brief Set play count |
|
* |
|
* By default, play count is set to @cpp 1 @ce, meaning the animation |
|
* @ref duration() is played once. Value of @cpp 0 @ce means the |
|
* animation is repeated indefinitely. |
|
* |
|
* Modifying this value while @ref state() is @ref State::Playing may |
|
* cause the animation to jump or abruptly stop after next call to |
|
* @ref advance(). |
|
*/ |
|
Player<T, K>& setPlayCount(UnsignedInt count) { |
|
_playCount = count; |
|
return *this; |
|
} |
|
|
|
/** |
|
* @brief Whether the player is empty |
|
* |
|
* @see @ref size(), @ref add(), @ref addWithCallback(), |
|
* @ref addWithCallbackOnChange(), @ref addRawCallback() |
|
*/ |
|
bool isEmpty() const; |
|
|
|
/** |
|
* @brief Count of tracks managed by this player |
|
* |
|
* @see @ref isEmpty(), @ref add(), @ref addWithCallback(), |
|
* @ref addWithCallbackOnChange(), @ref addRawCallback() |
|
*/ |
|
std::size_t size() const; |
|
|
|
/** |
|
* @brief Track at given position |
|
* |
|
* Due to the type-erased nature of the player implementation, it's not |
|
* possible to know the exact track type. |
|
*/ |
|
const TrackViewStorage<K>& track(std::size_t i) const; |
|
|
|
/** |
|
* @brief Add a track with a result destination |
|
* |
|
* The @p destination is updated with new value after each call to |
|
* @ref advance() as long as the animation is playing. |
|
*/ |
|
template<class V, class R> Player<T, K>& add(const TrackView<K, V, R>& track, R& destination); |
|
|
|
/** @overload |
|
* |
|
* Note that track ownership is *not* transferred to the @ref Player |
|
* and you have to ensure that it's kept in scope for the whole |
|
* lifetime of the @ref Player instance. |
|
*/ |
|
template<class V, class R> Player<T, K>& add(const Track<K, V, R>& track, R& destination) { |
|
return add(TrackView<K, V, R>{track}, destination); |
|
} |
|
|
|
/** |
|
* @brief Add a track with a result callback |
|
* |
|
* The @p callback is called with current key value, interpolated |
|
* result value and the @p userData pointer after each call to |
|
* @ref advance() as long as the animation is playing. The key value is |
|
* guaranteed to never be outside of the @ref duration() ranage, with |
|
* the interpolated result always corresponding to that key value. |
|
* |
|
* See the overload below for a more convenient type-safe way to pass |
|
* user data. |
|
* @see @ref addRawCallback() |
|
*/ |
|
#ifdef DOXYGEN_GENERATING_OUTPUT |
|
template<class V, class R> Player<T, K>& addWithCallback(const TrackView<K, V, R>& track, void(*callback)(const K&, const R&, void*), void* userData = nullptr); |
|
#else |
|
/* Otherwise the user would be forced to use the + operator to convert |
|
a lambda to a function pointer and (besides being weird and |
|
annoying) it's also not portable because it doesn't work on MSVC |
|
2015 and older versions of MSVC 2017. OTOH, putting this in the docs |
|
wouldn say nothing about how the callback signature should look. */ |
|
template<class V, class R, class Callback> Player<T, K>& addWithCallback(const TrackView<K, V, R>& track, Callback callback, void* userData = nullptr); |
|
#endif |
|
|
|
/** @overload |
|
* |
|
* Note that the track ownership is *not* transferred to the |
|
* @ref Player and you have to ensure that it's kept in scope for the |
|
* whole lifetime of the @ref Player instance. |
|
*/ |
|
#ifdef DOXYGEN_GENERATING_OUTPUT |
|
template<class V, class R> Player<T, K>& addWithCallback(const Track<K, V, R>& track, void(*callback)(const K&, const R&, void*), void* userData = nullptr); |
|
#else /* See above why */ |
|
template<class V, class R, class Callback> Player<T, K>& addWithCallback(const Track<K, V, R>& track, Callback callback, void* userData = nullptr) { |
|
return addWithCallback(TrackView<K, V, R>{track}, callback, userData); |
|
} |
|
#endif |
|
|
|
/** |
|
* @brief Add a track with a result callback |
|
* |
|
* Equivalent to calling the above with a lambda wrapper that casts |
|
* @cpp void* @ce back to @cpp T* @ce and dereferences it in order to |
|
* pass it to @p callback. There is no additional overhead compared to |
|
* the overload taking the @cpp void* @ce pointer, however see |
|
* @ref addRawCallback() for optimization possibilities. |
|
*/ |
|
#ifdef DOXYGEN_GENERATING_OUTPUT |
|
template<class V, class R, class U> Player<T, K>& addWithCallback(const TrackView<K, V, R>& track, void(*callback)(const K&, const R&, U&), U& userData); |
|
#else /* See above why */ |
|
template<class V, class R, class U, class Callback> Player<T, K>& addWithCallback(const TrackView<K, V, R>& track, Callback callback, U& userData); |
|
#endif |
|
|
|
/** @overload |
|
* |
|
* Note that the track ownership is *not* transferred to the |
|
* @ref Player and you have to ensure that it's kept in scope for the |
|
* whole lifetime of the @ref Player instance. |
|
*/ |
|
#ifdef DOXYGEN_GENERATING_OUTPUT |
|
template<class V, class R, class U> Player<T, K>& addWithCallback(const Track<K, V, R>& track, void(*callback)(const K&, const R&, U&), U& userData); |
|
#else /* See above why */ |
|
template<class V, class R, class U, class Callback> Player<T, K>& addWithCallback(const Track<K, V, R>& track, Callback callback, U& userData) { |
|
return addWithCallback(TrackView<K, V, R>{track}, callback, userData); |
|
} |
|
#endif |
|
|
|
/** |
|
* @brief Add a track with a result callback that's called on change |
|
* |
|
* A combination of @ref add() and @ref addWithCallback() --- during |
|
* each call to @ref advance(), as long as the animation is playing, |
|
* the new value is compared to @p destination. If the new value is |
|
* different from the stored one, @p callback is called and |
|
* @p destination is updated. Note that in order to keep the memory |
|
* management inside the player class simple, the value can't be cached |
|
* inside and you are required to provide the @p destination location. |
|
* |
|
* See the overload below for a more convenient type-safe way to pass |
|
* user data. |
|
* @see @ref addRawCallback() |
|
*/ |
|
#ifdef DOXYGEN_GENERATING_OUTPUT |
|
template<class V, class R> Player<T, K>& addWithCallbackOnChange(const TrackView<K, V, R>& track, void(*callback)(const K&, const R&, void*), R& destination, void* userData = nullptr); |
|
#else /* See above why */ |
|
template<class V, class R, class Callback> Player<T, K>& addWithCallbackOnChange(const TrackView<K, V, R>& track, Callback callback, R& destination, void* userData = nullptr); |
|
#endif |
|
|
|
/** @overload |
|
* |
|
* Note that the track ownership is *not* transferred to the |
|
* @ref Player and you have to ensure that it's kept in scope for the |
|
* whole lifetime of the @ref Player instance. |
|
*/ |
|
#ifdef DOXYGEN_GENERATING_OUTPUT |
|
template<class V, class R> Player<T, K>& addWithCallbackOnChange(const Track<K, V, R>& track, void(*callback)(const K&, const R&, void*), R& destination, void* userData = nullptr); |
|
#else /* See above why */ |
|
template<class V, class R, class Callback> Player<T, K>& addWithCallbackOnChange(const Track<K, V, R>& track, Callback callback, R& destination, void* userData = nullptr) { |
|
return addWithCallbackOnChange(TrackView<K, V, R>{track}, callback, destination, userData); |
|
} |
|
#endif |
|
|
|
/** |
|
* @brief Add a track with a result callback that's called on change |
|
* |
|
* Equivalent to calling the above with a lambda wrapper that casts |
|
* @cpp void* @ce back to @cpp T* @ce and dereferences it in order to |
|
* pass it to @p callback. There is no additional overhead compared to |
|
* the overload taking the @cpp void* @ce pointer, however see |
|
* @ref addRawCallback() for optimization possibilities. |
|
*/ |
|
#ifdef DOXYGEN_GENERATING_OUTPUT |
|
template<class V, class R, class U> Player<T, K>& addWithCallbackOnChange(const TrackView<K, V, R>& track, void(*callback)(const K&, const R&, void*), R& destination, U& userData); |
|
#else /* See above why */ |
|
template<class V, class R, class U, class Callback> Player<T, K>& addWithCallbackOnChange(const TrackView<K, V, R>& track, Callback callback, R& destination, U& userData); |
|
#endif |
|
|
|
/** @overload |
|
* |
|
* Note that the track ownership is *not* transferred to the |
|
* @ref Player and you have to ensure that it's kept in scope for the |
|
* whole lifetime of the @ref Player instance. |
|
*/ |
|
#ifdef DOXYGEN_GENERATING_OUTPUT |
|
template<class V, class R, class U> Player<T, K>& addWithCallbackOnChange(const Track<K, V, R>& track, void(*callback)(const K&, const R&, void*), R& destination, U& userData); |
|
#else |
|
template<class V, class R, class U, class Callback> Player<T, K>& addWithCallbackOnChange(const Track<K, V, R>& track, Callback callback, R& destination, U& userData) { |
|
return addWithCallbackOnChange(TrackView<K, V, R>{track}, callback, destination, userData); |
|
} |
|
#endif |
|
|
|
/** |
|
* @brief Add a track with a raw callback |
|
* |
|
* This is a low-level function meant to be used if you want to avoid |
|
* the extra overhead of an additional callback in @ref addWithCallback() |
|
* or @ref addWithCallbackOnChange(), want more flexibility in the user |
|
* callback or want to control the track interpolation directly --- for |
|
* example taking advantage of @ref TrackView::atStrict() or passing an |
|
* inlineable interpolator function instead of using the saved |
|
* interpolator function pointer. |
|
* |
|
* The callback takes the raw @ref TrackViewStorage reference (which |
|
* you need to cast to a correct type), the interpolated key and hint |
|
* that's meant to be passed to @ref TrackView::at(), the destination |
|
* pointer (equivalent to the one passed to @ref add()), user callback |
|
* pointer (which again needs to be cast to a correct type) and user |
|
* data pointer. The following code snippet shows implementation of the |
|
* @ref addWithCallbackOnChange() API using this function, using a |
|
* custom callback to add a value to a vector if it changes: |
|
* |
|
* @snippet MagnumAnimation.cpp Player-addRawCallback |
|
*/ |
|
#ifdef DOXYGEN_GENERATING_OUTPUT |
|
template<class V, class R, class Callback> Player<T, K>& addRawCallback(const TrackView<K, V, R>& track, void(*callback)(const TrackViewStorage<K>&, K, std::size_t&, void*, void(*)(), void*), void* destination, void(*userCallback)(), void* userData); |
|
#else |
|
template<class V, class R, class Callback> Player<T, K>& addRawCallback(const TrackView<K, V, R>& track, Callback callback, void* destination, void(*userCallback)(), void* userData); |
|
#endif |
|
|
|
/** @overload |
|
* |
|
* Note that the track ownership is *not* transferred to the |
|
* @ref Player and you have to ensure that it's kept in scope for the |
|
* whole lifetime of the @ref Player instance. |
|
*/ |
|
#ifdef DOXYGEN_GENERATING_OUTPUT |
|
template<class V, class R> Player<T, K>& addRawCallback(const Track<K, V, R>& track, void(*callback)(const TrackViewStorage<K>&, K, std::size_t&, void*, void(*)(), void*), void* destination, void(*userCallback)(), void* userData); |
|
#else |
|
template<class V, class R, class Callback> Player<T, K>& addRawCallback(const Track<K, V, R>& track, Callback callback, void* destination, void(*userCallback)(), void* userData) { |
|
return addRawCallback(TrackView<K, V, R>{track}, callback, destination, userCallback, userData); |
|
} |
|
#endif |
|
|
|
/** |
|
* @brief State |
|
* |
|
* The player is @ref State::Stopped by default. |
|
* @see @ref play(), @ref pause(), @ref stop(), @ref setState() |
|
*/ |
|
State state() const { return _state; } |
|
|
|
/** |
|
* @brief Play |
|
* |
|
* Starts playing all tracks added to the player at given @p startTime. |
|
* If @ref state() is already @ref State::Playing, the animation is |
|
* restarted from the beginning at @p startTiime. If @ref state() is |
|
* @ref State::Paused, the animation continues from the time that was |
|
* passed to @ref pause(). |
|
* |
|
* If @p startTime is in the future (that is, time passed to the next |
|
* @ref advance() iteration will be less than @p startTime), |
|
* @ref advance() will do nothing until given point in the future. |
|
* Setting time to such a particular value can be used to synchronize |
|
* playback of multiple independent animation clips. |
|
* @see @ref setState() |
|
*/ |
|
Player<T, K>& play(T startTime); |
|
|
|
/** |
|
* @brief Pause |
|
* |
|
* Pauses the currently playing animation at given @p pauseTime. If |
|
* @ref state() is not @ref State::Playing, the function does nothing. |
|
* See @ref advance() for a detailed description of behavior when the |
|
* animation gets paused. |
|
* @see @ref setState() |
|
*/ |
|
Player<T, K>& pause(T pauseTime); |
|
|
|
/** |
|
* @brief Stop |
|
* |
|
* Stops the currently playing animation. If @ref state() is |
|
* @ref State::Paused, discard the pause information. If @ref state() |
|
* is already @ref State::Stopped, the function does nothing. See |
|
* @ref advance() for a detailed description of behavior when the |
|
* animation gets stopped. |
|
* @see @ref setState() |
|
*/ |
|
Player<T, K>& stop(); |
|
|
|
/** |
|
* @brief Set state |
|
* |
|
* Convenience function that calls @ref play(), @ref pause() or |
|
* @ref stop() based on @p state. See documentation of these functions |
|
* for detailed description. The @p time parameter is used only when |
|
* @p state is @ref State::Playing or @ref State::Paused, it's ignored |
|
* for @ref State::Stopped. |
|
*/ |
|
Player<T, K>& setState(State state, T time); |
|
|
|
/** |
|
* @brief Advance the animation |
|
* |
|
* As long as @ref state() is @ref State::Playing, goes through all |
|
* tracks added with @ref add(), @ref addWithCallback() or |
|
* @ref addWithCallbackOnChange() in order they were added and updates |
|
* the destination locations and/or fires the callbacks with |
|
* interpolation results. |
|
* |
|
* If @ref state() is @ref State::Paused or @ref State::Stopped, the |
|
* function does nothing. If @p time is less than time that was passed |
|
* to @ref play(), the function does nothing. If @p time is large |
|
* enough that @ref duration() times @ref playCount() got exhausted, |
|
* the function will update destination locations and/or fire |
|
* user-defined callback with key and result values corresponding to |
|
* the end time of @ref duration() in order to correctly "park" the |
|
* animation. The state then becomes @ref State::Stopped and no more |
|
* updates are done until the animation is started again. |
|
* |
|
* If @ref pause() was called right before a particular @ref advance() |
|
* iteration, the function will update destination locations and/or |
|
* fire user-defined callbacks with key and result values corresponding |
|
* to the time passed to the @ref pause() call before to correctly |
|
* "park" the animation. After that, no more updates are done until the |
|
* animation is started again. |
|
* |
|
* If @ref stop() was called right before a particular @ref advance() |
|
* iteration, the function will update destination locations and/or |
|
* fire user-defined callbacks with key and result values corresponding |
|
* to the begin time of @ref duration() to correctly "park" the |
|
* animation back to its initial state. After that, no more updates are |
|
* done until the animation is started again. |
|
*/ |
|
Player<T, K>& advance(T time); |
|
|
|
private: |
|
struct Track; |
|
|
|
Player<T, K>& addInternal(const TrackViewStorage<K>& track, void (*advancer)(const TrackViewStorage<K>&, K, std::size_t&, void*, void(*)(), void*), void* destination, void(*userCallback)(), void* userCallbackData); |
|
|
|
std::vector<Track> _tracks; |
|
Math::Range1D<K> _duration; |
|
UnsignedInt _playCount{1}; |
|
State _state{State::Stopped}; |
|
T _startTime{}, _pauseTime{}; |
|
Scaler _scaler; |
|
}; |
|
|
|
template<class T, class K> template<class V, class R> Player<T, K>& Player<T, K>::add(const TrackView<K, V, R>& track, R& destination) { |
|
return addInternal(track, |
|
[](const TrackViewStorage<K>& track, K key, std::size_t& hint, void* destination, void(*)(), void*) { |
|
*static_cast<R*>(destination) = static_cast<const TrackView<K, V, R>&>(track).at(key, hint); |
|
}, &destination, nullptr, nullptr); |
|
} |
|
|
|
#ifndef DOXYGEN_GENERATING_OUTPUT |
|
template<class T, class K> template<class V, class R, class Callback> Player<T, K>& Player<T, K>::addWithCallback(const TrackView<K, V, R>& track, Callback callback, void* userData) { |
|
auto callbackPtr = static_cast<void(*)(const K&, const R&, void*)>(callback); |
|
return addInternal(track, |
|
[](const TrackViewStorage<K>& track, K key, std::size_t& hint, void*, void(*callback)(), void* userData) { |
|
/** @todo try to use atStrict() if possible */ |
|
reinterpret_cast<void(*)(const K&, const R&, void*)>(callback)(key, static_cast<const TrackView<K, V, R>&>(track).at(key, hint), userData); |
|
}, nullptr, reinterpret_cast<void(*)()>(callbackPtr), userData); |
|
} |
|
|
|
template<class T, class K> template<class V, class R, class U, class Callback> Player<T, K>& Player<T, K>::addWithCallback(const TrackView<K, V, R>& track, Callback callback, U& userData) { |
|
auto callbackPtr = static_cast<void(*)(const K&, const R&, U&)>(callback); |
|
return addInternal(track, |
|
[](const TrackViewStorage<K>& track, K key, std::size_t& hint, void*, void(*callback)(), void* userData) { |
|
/** @todo try to use atStrict() if possible */ |
|
reinterpret_cast<void(*)(const K&, const R&, U&)>(callback)(key, static_cast<const TrackView<K, V, R>&>(track).at(key, hint), *static_cast<U*>(userData)); |
|
}, nullptr, reinterpret_cast<void(*)()>(callbackPtr), &userData); |
|
} |
|
|
|
template<class T, class K> template<class V, class R, class Callback> Player<T, K>& Player<T, K>::addWithCallbackOnChange(const TrackView<K, V, R>& track, Callback callback, R& destination, void* userData) { |
|
auto callbackPtr = static_cast<void(*)(const K&, const R&, void*)>(callback); |
|
return addInternal(track, |
|
[](const TrackViewStorage<K>& track, K key, std::size_t& hint, void* destination, void(*callback)(), void* userData) { |
|
/** @todo try to use atStrict() if possible */ |
|
R result = static_cast<const TrackView<K, V, R>&>(track).at(key, hint); |
|
if(result == *static_cast<R*>(destination)) return; |
|
reinterpret_cast<void(*)(const K&, const R&, void*)>(callback)(key, result, userData); |
|
*static_cast<R*>(destination) = result; |
|
}, &destination, reinterpret_cast<void(*)()>(callbackPtr), userData); |
|
} |
|
|
|
template<class T, class K> template<class V, class R, class U, class Callback> Player<T, K>& Player<T, K>::addWithCallbackOnChange(const TrackView<K, V, R>& track, Callback callback, R& destination, U& userData) { |
|
auto callbackPtr = static_cast<void(*)(const K&, const R&, U&)>(callback); |
|
return addInternal(track, |
|
[](const TrackViewStorage<K>& track, K key, std::size_t& hint, void* destination, void(*callback)(), void* userData) { |
|
/** @todo try to use atStrict() if possible */ |
|
R result = static_cast<const TrackView<K, V, R>&>(track).at(key, hint); |
|
if(result == *static_cast<R*>(destination)) return; |
|
reinterpret_cast<void(*)(const K&, const R&, U&)>(callback)(key, result, *static_cast<U*>(userData)); |
|
*static_cast<R*>(destination) = result; |
|
}, &destination, reinterpret_cast<void(*)()>(callbackPtr), &userData); |
|
} |
|
|
|
template<class T, class K> template<class V, class R, class Callback> Player<T, K>& Player<T, K>::addRawCallback(const TrackView<K, V, R>& track, Callback callback, void* destination, void(*userCallback)(), void* userData) { |
|
auto callbackPtr = static_cast<void(*)(const TrackViewStorage<K>&, K, std::size_t&, void*, void(*)(), void*)>(callback); |
|
return addInternal(track, callbackPtr, destination, userCallback, userData); |
|
} |
|
#endif |
|
|
|
#if defined(CORRADE_TARGET_WINDOWS) && !defined(__MINGW32__) |
|
extern template class MAGNUM_EXPORT Player<Float, Float>; |
|
extern template class MAGNUM_EXPORT Player<std::chrono::nanoseconds, Float>; |
|
#endif |
|
|
|
}} |
|
|
|
#endif
|
|
|