mirror of https://github.com/mosra/magnum.git
14 changed files with 2013 additions and 4 deletions
@ -0,0 +1,47 @@
|
||||
/*
|
||||
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. |
||||
*/ |
||||
|
||||
#include "Magnum/Magnum.h" |
||||
|
||||
using namespace Magnum; |
||||
|
||||
#ifndef CORRADE_TARGET_EMSCRIPTEN |
||||
/* [Player-usage-custom] */ |
||||
#include "Magnum/Animation/Player.hpp" |
||||
|
||||
// …
|
||||
|
||||
/* 64-bit integer global time (microseconds), 16-bit frame counter with 24 FPS */ |
||||
Animation::Player<UnsignedLong, UnsignedShort> player{ |
||||
[](UnsignedLong time, UnsignedShort duration) { |
||||
/* One frame is 1/24 second */ |
||||
const UnsignedLong durationNs = UnsignedLong(duration)*1000000/24; |
||||
const UnsignedInt playCount = time/durationNs; |
||||
const UnsignedShort factor = (time - playCount*durationNs)*24/1000000; |
||||
return std::make_pair(playCount, factor); |
||||
}}; |
||||
/* [Player-usage-custom] */ |
||||
/* WARNING: Keep the above in sync with PlayerCustomTest */ |
||||
#endif |
||||
@ -0,0 +1,57 @@
|
||||
/*
|
||||
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. |
||||
*/ |
||||
|
||||
#include "Player.hpp" |
||||
|
||||
namespace Magnum { namespace Animation { |
||||
|
||||
Debug& operator<<(Debug& debug, const State value) { |
||||
switch(value) { |
||||
/* LCOV_EXCL_START */ |
||||
#define _c(value) case State::value: return debug << "Animation::State::" #value; |
||||
_c(Stopped) |
||||
_c(Playing) |
||||
_c(Paused) |
||||
#undef _c |
||||
/* LCOV_EXCL_STOP */ |
||||
} |
||||
|
||||
return debug << "Animation::State(" << Debug::nospace << reinterpret_cast<void*>(UnsignedByte(value)) << Debug::nospace << ")"; |
||||
} |
||||
|
||||
/* On non-MinGW Windows the instantiations are already marked with extern
|
||||
template */ |
||||
#if !defined(CORRADE_TARGET_WINDOWS) || defined(__MINGW32__) |
||||
#define MAGNUM_EXPORT_HPP MAGNUM_EXPORT |
||||
#else |
||||
#define MAGNUM_EXPORT_HPP |
||||
#endif |
||||
|
||||
#ifndef DOXYGEN_GENERATING_OUTPUT |
||||
template class MAGNUM_EXPORT_HPP Player<Float, Float>; |
||||
template class MAGNUM_EXPORT_HPP Player<std::chrono::nanoseconds, Float>; |
||||
#endif |
||||
|
||||
}} |
||||
@ -0,0 +1,653 @@
|
||||
#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. |
||||
|
||||
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. The |
||||
animation is implicitly played only once, use @ref setPlayCount() to set a |
||||
number of repeats or make it repeat indefinitely. |
||||
|
||||
@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. |
||||
*/ |
||||
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 */ |
||||
Player(Player<T, K>&&) noexcept; |
||||
|
||||
~Player(); |
||||
|
||||
/** @brief Copying is not allowed */ |
||||
Player<T, K>& operator=(const Player<T, K>&) = delete; |
||||
|
||||
/** @brief Move assignment */ |
||||
Player<T, K>& operator=(Player<T, K>&&) noexcept; |
||||
|
||||
/** @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() |
||||
*/ |
||||
bool isEmpty() const; |
||||
|
||||
/**
|
||||
* @brief Count of tracks managed by this player |
||||
* |
||||
* @see @ref isEmpty(), @ref add(), @ref addWithCallback(), |
||||
* @ref addWithCallbackOnChange() |
||||
*/ |
||||
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. |
||||
*/ |
||||
#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. |
||||
*/ |
||||
#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. |
||||
*/ |
||||
#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. |
||||
*/ |
||||
#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 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) { |
||||
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) { |
||||
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) { |
||||
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) { |
||||
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); |
||||
} |
||||
#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 |
||||
@ -0,0 +1,188 @@
|
||||
#ifndef Magnum_Animation_Player_hpp |
||||
#define Magnum_Animation_Player_hpp |
||||
/*
|
||||
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 @ref compilation-speedup-hpp "Template implementation" for @ref Animable.h and @ref AnimableGroup.h |
||||
*/ |
||||
|
||||
#include "Player.h" |
||||
|
||||
namespace Magnum { namespace Animation { |
||||
|
||||
namespace Implementation { |
||||
template<class T, class K> struct DefaultScaler { |
||||
static std::pair<UnsignedInt, K> scale(T time, K duration) { |
||||
const UnsignedInt playCount = time/duration; |
||||
const K factor = std::fmod(Double(time), Double(duration)); |
||||
return {playCount, factor}; |
||||
} |
||||
}; |
||||
|
||||
template<> struct DefaultScaler<std::chrono::nanoseconds, Float> { |
||||
static std::pair<UnsignedInt, Float> scale(std::chrono::nanoseconds time, Float duration) { |
||||
/* I hope I'm not having any catastrophic cancellation here :/ */ |
||||
const std::chrono::nanoseconds durationNs{std::chrono::nanoseconds::rep(Double(duration)*1000000000.0)}; |
||||
const UnsignedInt playCount = time/durationNs; |
||||
const Float factor = Float((time - playCount*durationNs).count()/1000000000.0); |
||||
return {playCount, factor}; |
||||
} |
||||
}; |
||||
} |
||||
|
||||
#ifndef DOXYGEN_GENERATING_OUTPUT |
||||
template<class T, class K> struct Player<T, K>::Track { |
||||
/* Not sure why is this still needed for emplace_back(). It's 2018,
|
||||
COME ON ¯\_(ツ)_/¯ */ |
||||
/*implicit*/ Track(const TrackViewStorage<K>& track, void (*advancer)(const TrackViewStorage<K>&, K, std::size_t&, void*, void(*)(), void*), void* destination, void(*userCallback)(), void* userCallbackData, std::size_t hint) noexcept: track{track}, advancer{advancer}, destination{destination}, userCallback{userCallback}, userCallbackData{userCallbackData}, hint{hint} {} |
||||
|
||||
TrackViewStorage<K> track; |
||||
void (*advancer)(const TrackViewStorage<K>&, K, std::size_t&, void*, void(*)(), void*); |
||||
void* destination; |
||||
void(*userCallback)(); |
||||
void* userCallbackData; |
||||
std::size_t hint; |
||||
}; |
||||
#endif |
||||
|
||||
template<class T, class K> Player<T, K>::Player(Player<T, K>&&) noexcept = default; |
||||
|
||||
template<class T, class K> Player<T, K>& Player<T, K>::operator=(Player<T, K>&&) noexcept = default; |
||||
|
||||
template<class T, class K> Player<T, K>::Player(): Player<T, K>{Implementation::DefaultScaler<T, K>::scale} {} |
||||
|
||||
template<class T, class K> Player<T, K>::Player(Scaler scaler): _scaler{scaler} {} |
||||
|
||||
template<class T, class K> Player<T, K>::~Player() = default; |
||||
|
||||
template<class T, class K> bool Player<T, K>::isEmpty() const { |
||||
return _tracks.empty(); |
||||
} |
||||
|
||||
template<class T, class K> std::size_t Player<T, K>::size() const { |
||||
return _tracks.size(); |
||||
} |
||||
|
||||
template<class T, class K> const TrackViewStorage<K>& Player<T, K>::track(std::size_t i) const { |
||||
CORRADE_ASSERT(i < _tracks.size(), |
||||
"Animation::Player::track(): index out of range", _tracks[i].track); |
||||
return _tracks[i].track; |
||||
} |
||||
|
||||
template<class T, class K> Player<T, K>& Player<T, K>::addInternal(const TrackViewStorage<K>& track, void(*const advancer)(const TrackViewStorage<K>&, K, std::size_t&, void*, void(*)(), void*), void* const destination, void(*const userCallback)(), void* const userCallbackData) { |
||||
if(_tracks.empty() && _duration == Math::Range1D<K>{}) |
||||
_duration = track.duration(); |
||||
else |
||||
_duration = Math::join(track.duration(), _duration); |
||||
_tracks.emplace_back(track, advancer, destination, userCallback, userCallbackData, 0); |
||||
return *this; |
||||
} |
||||
|
||||
template<class T, class K> Player<T, K>& Player<T, K>::play(T startTime) { |
||||
/* In case we were paused, move start time backwards by the duration that
|
||||
was already played back */ |
||||
if(_state == State::Paused) { |
||||
_startTime = startTime - _startTime; |
||||
_state = State::Playing; |
||||
return *this; |
||||
} |
||||
|
||||
_state = State::Playing; |
||||
_startTime = startTime; |
||||
return *this; |
||||
} |
||||
|
||||
template<class T, class K> Player<T, K>& Player<T, K>::pause(T pauseTime) { |
||||
/* Avoid breaking the pause state when not playing */ |
||||
if(_state != State::Playing) return *this; |
||||
|
||||
_state = State::Paused; |
||||
_pauseTime = pauseTime; |
||||
return *this; |
||||
} |
||||
|
||||
template<class T, class K> Player<T, K>& Player<T, K>::stop() { |
||||
_state = State::Stopped; |
||||
return *this; |
||||
} |
||||
|
||||
template<class T, class K> Player<T, K>& Player<T, K>::setState(State state, T time) { |
||||
switch(state) { |
||||
case State::Playing: return play(time); |
||||
case State::Paused: return pause(time); |
||||
case State::Stopped: return stop(); |
||||
} |
||||
|
||||
CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
||||
} |
||||
|
||||
template<class T, class K> Player<T, K>& Player<T, K>::advance(T time) { |
||||
/* Time to use for advancing the animation */ |
||||
T timeToUse = time; |
||||
|
||||
/* The animation was paused right before this iteration, "park" the
|
||||
animation to the pause time. This time will be used by play() to offset |
||||
the playback when the animation is resumed. |
||||
|
||||
std::chrono::duration doesn't have operator bool, so I need to compare |
||||
to default-constructed value. Ugh. */ |
||||
if(_state == State::Paused && (_pauseTime != T{})) { |
||||
timeToUse = _pauseTime; |
||||
_startTime = _pauseTime - _startTime; |
||||
_pauseTime = {}; |
||||
|
||||
/* The animation was stopped by the user right before this iteration,
|
||||
"park" the animation to the initial time */ |
||||
} else if(_state == State::Stopped && (_startTime != T{})) { |
||||
_startTime = {}; |
||||
timeToUse = {}; |
||||
|
||||
/* Otherwise, if the player is not playing or scheduled to start playing in
|
||||
the future, do nothing */ |
||||
} else if(_state != State::Playing || time < _startTime) return *this; |
||||
|
||||
/* Calculate current play iteration and key value in that iteration. If we
|
||||
exceeded play count, stop the animation and give out value at duration |
||||
end. */ |
||||
UnsignedInt playCount; |
||||
K key; |
||||
std::tie(playCount, key) = _scaler(timeToUse - _startTime, _duration.size()[0]); |
||||
if(_playCount && playCount >= _playCount) { |
||||
_state = State::Stopped; |
||||
_startTime = {}; |
||||
key = _duration.size()[0]; |
||||
} |
||||
|
||||
/* Advance all tracks. Properly handle durations that don't start at 0. */ |
||||
for(Track& t: _tracks) |
||||
t.advancer(t.track, _duration.min()[0] + key, t.hint, t.destination, t.userCallback, t.userCallbackData); |
||||
|
||||
return *this; |
||||
} |
||||
|
||||
}} |
||||
|
||||
#endif |
||||
@ -0,0 +1,80 @@
|
||||
/*
|
||||
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. |
||||
*/ |
||||
|
||||
#include <Corrade/TestSuite/Tester.h> |
||||
|
||||
#include "Magnum/Animation/Player.hpp" |
||||
|
||||
namespace Magnum { namespace Animation { namespace Test { |
||||
|
||||
struct PlayerCustomTest: TestSuite::Tester { |
||||
explicit PlayerCustomTest(); |
||||
|
||||
void test(); |
||||
}; |
||||
|
||||
PlayerCustomTest::PlayerCustomTest() { |
||||
addTests({&PlayerCustomTest::test}); |
||||
} |
||||
|
||||
#ifdef CORRADE_TARGET_EMSCRIPTEN |
||||
typedef std::uint64_t UnsignedLong; /** @todo what about this? */ |
||||
#endif |
||||
|
||||
namespace { |
||||
const Animation::Track<UnsignedShort, Float> Track{{ |
||||
{24, 1.5f}, /* 1.0 sec */ |
||||
{60, 3.0f}, /* 2.5 sec */ |
||||
{72, 5.0f}, /* 3.0 sec */ |
||||
{96, 2.0f} /* 4.0 sec */ |
||||
}, Math::lerp}; |
||||
} |
||||
|
||||
void PlayerCustomTest::test() { |
||||
Player<UnsignedLong, UnsignedShort> player{ |
||||
/* Keep this in sync with PlayerCustomTest */ |
||||
[](UnsignedLong time, UnsignedShort duration) { |
||||
/* One frame is 1/24 second */ |
||||
const UnsignedLong durationNs = UnsignedLong(duration)*1000000ull/24; |
||||
const UnsignedInt playCount = time/durationNs; |
||||
const UnsignedShort factor = (time - playCount*durationNs)*24/1000000ull; |
||||
return std::make_pair(playCount, factor); |
||||
}}; |
||||
|
||||
Float value = -1.0f; |
||||
player.add(Track, value) |
||||
.play(2000000ull); |
||||
|
||||
CORRADE_COMPARE(player.duration().size()[0], 24*3); |
||||
|
||||
/* 1.75 secs in */ |
||||
player.advance(3750000ull); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
} |
||||
|
||||
}}} |
||||
|
||||
CORRADE_TEST_MAIN(Magnum::Animation::Test::PlayerCustomTest) |
||||
@ -0,0 +1,774 @@
|
||||
/*
|
||||
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. |
||||
*/ |
||||
|
||||
#include <sstream> |
||||
#include <Corrade/TestSuite/Tester.h> |
||||
#include <Corrade/TestSuite/Compare/Numeric.h> |
||||
|
||||
#include "Magnum/Animation/Player.h" |
||||
|
||||
namespace Magnum { namespace Animation { namespace Test { |
||||
|
||||
struct PlayerTest: TestSuite::Tester { |
||||
explicit PlayerTest(); |
||||
|
||||
void constructEmpty(); |
||||
void construct(); |
||||
void constructChrono(); |
||||
void constructCopy(); |
||||
void constructMove(); |
||||
|
||||
void setDurationExtend(); |
||||
void setDurationReplace(); |
||||
|
||||
void trackInvalidIndex(); |
||||
|
||||
void advanceNotRunning(); |
||||
void advancePlaying(); |
||||
void advanceRestart(); |
||||
void advanceStop(); |
||||
void advancePauseResume(); |
||||
void advancePauseStop(); |
||||
void advancePlayCount(); |
||||
void advancePlayCountInfinite(); |
||||
void advanceChrono(); |
||||
|
||||
void setState(); |
||||
|
||||
void add(); |
||||
void addWithCallback(); |
||||
void addWithCallbackTemplate(); |
||||
void addWithCallbackOnChange(); |
||||
void addWithCallbackOnChangeTemplate(); |
||||
|
||||
void runFor100YearsFloat(); |
||||
void runFor100YearsChrono(); |
||||
|
||||
void debugState(); |
||||
}; |
||||
|
||||
namespace { |
||||
const struct { |
||||
const char* name; |
||||
Float offsetFloat; |
||||
std::chrono::nanoseconds offsetChrono; |
||||
bool failsFloat, failsFuzzyFloat; |
||||
} RunFor100YearsData[]{ |
||||
{"0", {}, {}, false, false}, |
||||
{"1 minute", 60.0f, std::chrono::minutes{1}, false, false}, |
||||
{"5 minutes", 5.0f*60.0f, std::chrono::minutes{5}, true, false}, |
||||
{"30 minutes", 30.0f*60.0f, std::chrono::minutes{30}, true, false}, |
||||
{"1 hour", 60.0f*60.0f, std::chrono::hours{1}, true, false}, |
||||
{"1 day", 24.0f*60.0f*60.0f, std::chrono::hours{24}, true, true}, |
||||
{"100 days", 100.0f*24.0f*60.0f*60.0f, std::chrono::hours{100*24}, true, true}, |
||||
{"100 years", 100.0f*365.0f*24.0f*60.0f*60.0f, std::chrono::hours{100*365*24}, true, true}, |
||||
}; |
||||
} |
||||
|
||||
PlayerTest::PlayerTest() { |
||||
addTests({&PlayerTest::constructEmpty, |
||||
&PlayerTest::construct, |
||||
&PlayerTest::constructCopy, |
||||
&PlayerTest::constructMove, |
||||
|
||||
&PlayerTest::setDurationExtend, |
||||
&PlayerTest::setDurationReplace, |
||||
|
||||
&PlayerTest::trackInvalidIndex, |
||||
|
||||
&PlayerTest::advanceNotRunning, |
||||
&PlayerTest::advancePlaying, |
||||
&PlayerTest::advanceRestart, |
||||
&PlayerTest::advanceStop, |
||||
&PlayerTest::advancePauseResume, |
||||
&PlayerTest::advancePauseStop, |
||||
&PlayerTest::advancePlayCount, |
||||
&PlayerTest::advancePlayCountInfinite, |
||||
&PlayerTest::advanceChrono, |
||||
|
||||
&PlayerTest::setState, |
||||
|
||||
&PlayerTest::add, |
||||
&PlayerTest::addWithCallback, |
||||
&PlayerTest::addWithCallbackTemplate, |
||||
&PlayerTest::addWithCallbackOnChange, |
||||
&PlayerTest::addWithCallbackOnChangeTemplate}); |
||||
|
||||
addInstancedTests({ |
||||
&PlayerTest::runFor100YearsFloat, |
||||
&PlayerTest::runFor100YearsChrono}, |
||||
Containers::arraySize(RunFor100YearsData)); |
||||
|
||||
addTests({&PlayerTest::debugState}); |
||||
} |
||||
|
||||
void PlayerTest::constructEmpty() { |
||||
Player<Float> player; |
||||
CORRADE_VERIFY(player.scaler()); |
||||
CORRADE_COMPARE(player.duration(), Range1D{}); |
||||
CORRADE_COMPARE(player.playCount(), 1); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_VERIFY(player.isEmpty()); |
||||
CORRADE_COMPARE(player.size(), 0); |
||||
} |
||||
|
||||
namespace { |
||||
const Animation::Track<Float, Float> Track{{ |
||||
{1.0f, 1.5f}, |
||||
{2.5f, 3.0f}, |
||||
{3.0f, 5.0f}, |
||||
{4.0f, 2.0f} |
||||
}, Math::lerp}; |
||||
} |
||||
|
||||
void PlayerTest::construct() { |
||||
Animation::Track<Float, Int> track2{{ |
||||
{0.5f, 42}, |
||||
{3.0f, 1337}, |
||||
{3.5f, -17} |
||||
}, Math::select}; |
||||
|
||||
Float value = -1.0f; |
||||
Int value2 = -1; |
||||
Player<Float> player; |
||||
player.add(Track, value) |
||||
.add(track2, value2) |
||||
.setPlayCount(37); |
||||
CORRADE_VERIFY(player.scaler()); |
||||
CORRADE_COMPARE(player.duration(), (Range1D{0.5f, 4.0f})); |
||||
CORRADE_COMPARE(player.playCount(), 37); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_VERIFY(!player.isEmpty()); |
||||
CORRADE_COMPARE(player.size(), 2); |
||||
CORRADE_COMPARE(player.track(0).keys().data(), Track.keys().data()); |
||||
CORRADE_COMPARE(player.track(1).keys().data(), track2.keys().data()); |
||||
} |
||||
|
||||
void PlayerTest::constructChrono() { |
||||
Animation::Track<Float, Int> track2{{ |
||||
{0.5f, 42}, |
||||
{3.0f, 1337}, |
||||
{3.5f, -17} |
||||
}, Math::select}; |
||||
|
||||
Float value = -1.0f; |
||||
Int value2 = -1; |
||||
Player<std::chrono::nanoseconds, Float> player; |
||||
player.add(Track, value) |
||||
.add(track2, value2) |
||||
.setPlayCount(37); |
||||
CORRADE_VERIFY(player.scaler()); |
||||
CORRADE_COMPARE(player.duration(), (Range1D{0.5f, 4.0f})); |
||||
CORRADE_COMPARE(player.playCount(), 37); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_VERIFY(!player.isEmpty()); |
||||
CORRADE_COMPARE(player.size(), 2); |
||||
CORRADE_COMPARE(player.track(0).keys().data(), Track.keys().data()); |
||||
CORRADE_COMPARE(player.track(1).keys().data(), track2.keys().data()); |
||||
} |
||||
|
||||
void PlayerTest::constructCopy() { |
||||
CORRADE_VERIFY(!(std::is_constructible<Player<Float>, const Player<Float>&>{})); |
||||
CORRADE_VERIFY(!(std::is_assignable<Player<Float>, const Player<Float>&>{})); |
||||
} |
||||
|
||||
void PlayerTest::constructMove() { |
||||
Animation::Track<Float, Int> track2{{ |
||||
{0.5f, 42}, |
||||
{3.0f, 1337}, |
||||
{3.5f, -17} |
||||
}, Math::select}; |
||||
|
||||
Float value = -1.0f; |
||||
Int value2 = -1; |
||||
Player<Float> a; |
||||
a.add(Track, value) |
||||
.add(track2, value2) |
||||
.setPlayCount(37) |
||||
.play({}); |
||||
CORRADE_COMPARE(a.duration(), (Range1D{0.5f, 4.0f})); |
||||
CORRADE_COMPARE(a.playCount(), 37); |
||||
CORRADE_COMPARE(a.state(), State::Playing); |
||||
CORRADE_VERIFY(!a.isEmpty()); |
||||
CORRADE_COMPARE(a.size(), 2); |
||||
CORRADE_COMPARE(a.track(0).keys().data(), Track.keys().data()); |
||||
CORRADE_COMPARE(a.track(1).keys().data(), track2.keys().data()); |
||||
|
||||
Player<Float> b{std::move(a)}; |
||||
CORRADE_COMPARE(b.duration(), (Range1D{0.5f, 4.0f})); |
||||
CORRADE_COMPARE(b.playCount(), 37); |
||||
CORRADE_COMPARE(b.state(), State::Playing); |
||||
CORRADE_VERIFY(!b.isEmpty()); |
||||
CORRADE_COMPARE(b.size(), 2); |
||||
CORRADE_COMPARE(b.track(0).keys().data(), Track.keys().data()); |
||||
CORRADE_COMPARE(b.track(1).keys().data(), track2.keys().data()); |
||||
|
||||
Player<Float> c; |
||||
c.setDuration({1.2f, 1.3f}); |
||||
c = std::move(b); |
||||
CORRADE_COMPARE(c.duration(), (Range1D{0.5f, 4.0f})); |
||||
CORRADE_COMPARE(c.playCount(), 37); |
||||
CORRADE_COMPARE(c.state(), State::Playing); |
||||
CORRADE_VERIFY(!c.isEmpty()); |
||||
CORRADE_COMPARE(c.size(), 2); |
||||
CORRADE_COMPARE(c.track(0).keys().data(), Track.keys().data()); |
||||
CORRADE_COMPARE(c.track(1).keys().data(), track2.keys().data()); |
||||
} |
||||
|
||||
void PlayerTest::setDurationExtend() { |
||||
Float value; |
||||
Player<Float> player; |
||||
player.setDuration({-1.0f, 2.0f}); |
||||
CORRADE_COMPARE(player.duration(), (Range1D{-1.0f, 2.0f})); |
||||
|
||||
player.add(Track, value); |
||||
CORRADE_COMPARE(player.duration(), (Range1D{-1.0f, 4.0f})); |
||||
} |
||||
|
||||
void PlayerTest::setDurationReplace() { |
||||
Float value; |
||||
Player<Float> player; |
||||
player.add(Track, value); |
||||
CORRADE_COMPARE(player.duration(), (Range1D{1.0f, 4.0f})); |
||||
|
||||
player.setDuration({-1.0f, 2.0f}); |
||||
CORRADE_COMPARE(player.duration(), (Range1D{-1.0f, 2.0f})); |
||||
} |
||||
|
||||
void PlayerTest::trackInvalidIndex() { |
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
|
||||
Player<Float> player; |
||||
player.track(0); |
||||
|
||||
CORRADE_COMPARE(out.str(), "Animation::Player::track(): index out of range\n"); |
||||
} |
||||
|
||||
void PlayerTest::advanceNotRunning() { |
||||
Float value = -1.0f; |
||||
Player<Float> player; |
||||
player.add(Track, value); |
||||
|
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
player.advance(1.75f); |
||||
|
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
} |
||||
|
||||
void PlayerTest::advancePlaying() { |
||||
Float value = -1.0f; |
||||
Player<Float> player; |
||||
player.add(Track, value) |
||||
.play(2.0f); |
||||
|
||||
CORRADE_COMPARE(player.duration().size(), 3.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* Still before starting time, nothing is done */ |
||||
player.advance(1.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* 1.75 secs in */ |
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
|
||||
/* 2.67 secs in */ |
||||
player.advance(4.6666667f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 3.0f); |
||||
|
||||
/* When the player gets stopped, the value at the stop time is written */ |
||||
player.advance(5.5f); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, 2.0f); |
||||
|
||||
/* But further advancing will not write anything */ |
||||
value = -1.0f; |
||||
player.advance(100.0f); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
} |
||||
|
||||
void PlayerTest::advanceRestart() { |
||||
Float value = -1.0f; |
||||
Player<Float> player; |
||||
player.add(Track, value) |
||||
.play(2.0f); |
||||
|
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* Still before starting time, nothing is done */ |
||||
player.advance(1.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* 1.75 secs in */ |
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
|
||||
/* Call play again, will restart from the beginning... */ |
||||
value = -1.0f; |
||||
player.play(4.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* ... but only after calling advance() again. Now at 1 sec in. */ |
||||
player.advance(5.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 2.5f); |
||||
} |
||||
|
||||
void PlayerTest::advanceStop() { |
||||
Float value = -1.0f; |
||||
Player<Float> player; |
||||
player.add(Track, value) |
||||
.play(2.0f); |
||||
|
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
|
||||
/* Stop, should not update anything */ |
||||
value = -1.0; |
||||
player.stop(); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* Advancing will update with a value from beginning of the duration */ |
||||
player.advance(5.0f); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, 1.5f); |
||||
|
||||
/* But further advancing will not write anything */ |
||||
value = -1.0f; |
||||
player.advance(100.0f); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
} |
||||
|
||||
void PlayerTest::advancePauseResume() { |
||||
Float value = -1.0f; |
||||
Player<Float> player; |
||||
player.add(Track, value) |
||||
.play(2.0f); |
||||
|
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
|
||||
/* Pausing should not update anything */ |
||||
value = -1.0f; |
||||
player.pause(4.0f); |
||||
CORRADE_COMPARE(player.state(), State::Paused); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* Pausing again should be a no-op */ |
||||
player.pause(4.1f); |
||||
CORRADE_COMPARE(player.state(), State::Paused); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* But advance() after should. No matter what time is passed to it, it
|
||||
should update with time of pause. */ |
||||
player.advance(4.5f); |
||||
CORRADE_COMPARE(player.state(), State::Paused); |
||||
CORRADE_COMPARE(value, 5.0f); /* value at 2.0f, not 2.5f */ |
||||
|
||||
/* Advancing further should do nothing */ |
||||
value = -1.0f; |
||||
player.advance(50.0f); |
||||
CORRADE_COMPARE(player.state(), State::Paused); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* Resuming the animation, again should not update anything */ |
||||
player.play(100.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* Advancing the animation should update again. It was paused after two
|
||||
seconds, so continuing at 2.5 seconds now. */ |
||||
player.advance(100.5f); |
||||
CORRADE_COMPARE(value, 3.5f); |
||||
} |
||||
|
||||
void PlayerTest::advancePauseStop() { |
||||
Float value = -1.0f; |
||||
Player<Float> player; |
||||
player.add(Track, value) |
||||
.play(2.0f); |
||||
|
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
|
||||
/* Pause, get value from the pause time */ |
||||
player.pause(4.0f); |
||||
player.advance(4.5f); |
||||
CORRADE_COMPARE(player.state(), State::Paused); |
||||
CORRADE_COMPARE(value, 5.0f); |
||||
|
||||
/* Stop, should not update anything */ |
||||
value = -1.0; |
||||
player.stop(); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* Advancing will update with a value from beginning of the duration */ |
||||
player.advance(5.0f); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, 1.5f); |
||||
|
||||
/* But further advancing will not write anything */ |
||||
value = -1.0f; |
||||
player.advance(100.0f); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* Pause while stopped is a no-op */ |
||||
player.pause(101.0f); |
||||
player.advance(101.0f); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
} |
||||
|
||||
void PlayerTest::advancePlayCount() { |
||||
Float value = -1.0f; |
||||
Player<Float> player; |
||||
player.add(Track, value) |
||||
.setPlayCount(3) |
||||
.play(2.0f); |
||||
|
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* Still before starting time, nothing is done */ |
||||
player.advance(1.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* 1.75 secs in */ |
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
|
||||
/* 2 secs in, second round */ |
||||
player.advance(7.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 5.0f); |
||||
|
||||
/* 1.75 secs in, third round */ |
||||
player.advance(9.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
|
||||
/* When the player gets stopped, the value at the stop time is written */ |
||||
player.advance(11.5f); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, 2.0f); |
||||
|
||||
/* But further advancing will not write anything */ |
||||
value = -1.0f; |
||||
player.advance(100.0f); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
} |
||||
|
||||
void PlayerTest::advancePlayCountInfinite() { |
||||
Float value = -1.0f; |
||||
Player<Float> player; |
||||
player.add(Track, value) |
||||
.setPlayCount(0) |
||||
.play(2.0f); |
||||
|
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* Still before starting time, nothing is done */ |
||||
player.advance(1.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* 1.75 secs in */ |
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
|
||||
/* 2 secs in, second round */ |
||||
player.advance(7.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 5.0f); |
||||
|
||||
/* 1.75 secs in, 10th round */ |
||||
player.advance(33.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
} |
||||
|
||||
void PlayerTest::advanceChrono() { |
||||
Float value = -1.0f; |
||||
Player<std::chrono::nanoseconds, Float> player; |
||||
player.add(Track, value) |
||||
.play(std::chrono::seconds{2}); |
||||
|
||||
CORRADE_COMPARE(player.duration().size(), 3.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* Still before starting time, nothing is done */ |
||||
player.advance(std::chrono::milliseconds{1750}); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* 1.75 secs in */ |
||||
player.advance(std::chrono::milliseconds{3750}); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
} |
||||
|
||||
void PlayerTest::setState() { |
||||
Player<Float> player; |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
|
||||
player.setState(State::Playing, {}); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
|
||||
player.setState(State::Paused, {}); |
||||
CORRADE_COMPARE(player.state(), State::Paused); |
||||
|
||||
player.setState(State::Stopped, {}); |
||||
CORRADE_COMPARE(player.state(), State::Stopped); |
||||
} |
||||
|
||||
void PlayerTest::add() { |
||||
Float value = -1.0f; |
||||
Player<Float> player; |
||||
player.add(Track, value) |
||||
.play(2.0f); |
||||
|
||||
CORRADE_COMPARE(player.duration().size(), 3.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* 1.75 secs in */ |
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 4.0f); |
||||
} |
||||
|
||||
void PlayerTest::addWithCallback() { |
||||
struct Data { |
||||
Float value = -1.0f; |
||||
Int called = 0; |
||||
} data; |
||||
Player<Float> player; |
||||
player.addWithCallback(Track, [](const Float&, const Float& value, void* userData) { |
||||
static_cast<Data*>(userData)->value = value; |
||||
++static_cast<Data*>(userData)->called; |
||||
}, &data) |
||||
.play(2.0f); |
||||
|
||||
CORRADE_COMPARE(player.duration().size(), 3.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(data.value, -1.0f); |
||||
CORRADE_COMPARE(data.called, 0); |
||||
|
||||
/* 1.75 secs in */ |
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(data.value, 4.0f); |
||||
CORRADE_COMPARE(data.called, 1); |
||||
} |
||||
|
||||
void PlayerTest::addWithCallbackTemplate() { |
||||
struct Data { |
||||
Float value = -1.0f; |
||||
Int called = 0; |
||||
} data; |
||||
Player<Float> player; |
||||
player.addWithCallback(Track, [](const Float&, const Float& value, Data& userData) { |
||||
userData.value = value; |
||||
++userData.called; |
||||
}, data) |
||||
.play(2.0f); |
||||
|
||||
CORRADE_COMPARE(player.duration().size(), 3.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(data.value, -1.0f); |
||||
CORRADE_COMPARE(data.called, 0); |
||||
|
||||
/* 1.75 secs in */ |
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(data.value, 4.0f); |
||||
CORRADE_COMPARE(data.called, 1); |
||||
} |
||||
|
||||
void PlayerTest::addWithCallbackOnChange() { |
||||
struct Data { |
||||
Float value = -1.0f; |
||||
Int called = 0; |
||||
} data; |
||||
Player<Float> player; |
||||
player.addWithCallbackOnChange(Track, [](const Float&, const Float& value, void* userData) { |
||||
static_cast<Data*>(userData)->value = value; |
||||
++static_cast<Data*>(userData)->called; |
||||
}, data.value, &data) |
||||
.play(2.0f); |
||||
|
||||
CORRADE_COMPARE(player.duration().size(), 3.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(data.value, -1.0f); |
||||
CORRADE_COMPARE(data.called, 0); |
||||
|
||||
/* 1.75 secs in */ |
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(data.value, 4.0f); |
||||
CORRADE_COMPARE(data.called, 1); |
||||
|
||||
/* At the same time, same value, should not be called again */ |
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(data.value, 4.0f); |
||||
CORRADE_COMPARE(data.called, 1); |
||||
|
||||
/* Different time, different value, called again */ |
||||
player.advance(4.0f); |
||||
CORRADE_COMPARE(data.value, 5.0f); |
||||
CORRADE_COMPARE(data.called, 2); |
||||
} |
||||
|
||||
void PlayerTest::addWithCallbackOnChangeTemplate() { |
||||
struct Data { |
||||
Float value = -1.0f; |
||||
Int called = 0; |
||||
} data; |
||||
Player<Float> player; |
||||
player.addWithCallbackOnChange(Track, [](const Float&, const Float& value, Data& userData) { |
||||
userData.value = value; |
||||
++userData.called; |
||||
}, data.value, data) |
||||
.play(2.0f); |
||||
|
||||
CORRADE_COMPARE(player.duration().size(), 3.0f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(data.value, -1.0f); |
||||
CORRADE_COMPARE(data.called, 0); |
||||
|
||||
/* 1.75 secs in */ |
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(data.value, 4.0f); |
||||
CORRADE_COMPARE(data.called, 1); |
||||
|
||||
/* At the same time, same value, should not be called again */ |
||||
player.advance(3.75f); |
||||
CORRADE_COMPARE(data.value, 4.0f); |
||||
CORRADE_COMPARE(data.called, 1); |
||||
|
||||
/* Different time, different value, called again */ |
||||
player.advance(4.0f); |
||||
CORRADE_COMPARE(data.value, 5.0f); |
||||
CORRADE_COMPARE(data.called, 2); |
||||
} |
||||
|
||||
void PlayerTest::runFor100YearsFloat() { |
||||
auto&& data = RunFor100YearsData[testCaseInstanceId()]; |
||||
setTestCaseDescription(data.name); |
||||
|
||||
Float value = -1.0f; |
||||
Player<Float> player; |
||||
player.add(Track, value) |
||||
.setPlayCount(0) |
||||
.play({}); |
||||
|
||||
/* The track must fit an integer number of times into the day for this test
|
||||
to work (3 seconds do fit) */ |
||||
CORRADE_COMPARE(player.duration().size()[0], 3.0f); |
||||
|
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* 2.67 secs in given iteration. Comparing with a slightly larger epsilon,
|
||||
because it fails right after five minutes otherwise. */ |
||||
player.advance(data.offsetFloat + 2.6666666666667f); |
||||
|
||||
if(data.failsFloat || data.failsFuzzyFloat) |
||||
Debug{} << "Calculated value:" << value; |
||||
|
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
{ |
||||
CORRADE_EXPECT_FAIL_IF(data.failsFuzzyFloat, "Imprecision larger than 2.5e-4f."); |
||||
CORRADE_COMPARE_WITH(value, 3.0f, TestSuite::Compare::around(0.00025f)); |
||||
} |
||||
if(!data.failsFuzzyFloat) { |
||||
CORRADE_EXPECT_FAIL_IF(data.failsFloat, "Imprecision larger than 1e-6f."); |
||||
CORRADE_COMPARE(value, 3.0f); |
||||
} |
||||
} |
||||
|
||||
void PlayerTest::runFor100YearsChrono() { |
||||
auto&& data = RunFor100YearsData[testCaseInstanceId()]; |
||||
setTestCaseDescription(data.name); |
||||
|
||||
Float value = -1.0f; |
||||
Player<std::chrono::nanoseconds, Float> player; |
||||
player.add(Track, value) |
||||
.setPlayCount(0) |
||||
.play({}); |
||||
|
||||
/* The track must fit an integer number of times into the day for this test
|
||||
to work (3 seconds do fit) */ |
||||
CORRADE_COMPARE(player.duration().size()[0], 3.0f); |
||||
|
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, -1.0f); |
||||
|
||||
/* 2.67 secs in */ |
||||
player.advance(data.offsetChrono + std::chrono::nanoseconds{2666666667ull}); |
||||
CORRADE_COMPARE(player.state(), State::Playing); |
||||
CORRADE_COMPARE(value, 3.0f); |
||||
} |
||||
|
||||
void PlayerTest::debugState() { |
||||
std::ostringstream out; |
||||
|
||||
Debug{&out} << State::Playing << State(0xde); |
||||
CORRADE_COMPARE(out.str(), "Animation::State::Playing Animation::State(0xde)\n"); |
||||
} |
||||
|
||||
}}} |
||||
|
||||
CORRADE_TEST_MAIN(Magnum::Animation::Test::PlayerTest) |
||||
Loading…
Reference in new issue