From adaeb21a4daf3c62c88ee3b1c169a9331f172d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 28 Sep 2018 13:02:16 +0200 Subject: [PATCH] Animation: implemented Player:seekBy() / Player::seekTo(). --- src/Magnum/Animation/Player.h | 56 +++++- src/Magnum/Animation/Player.hpp | 38 ++++ src/Magnum/Animation/Test/PlayerTest.cpp | 240 +++++++++++++++++++++++ 3 files changed, 332 insertions(+), 2 deletions(-) diff --git a/src/Magnum/Animation/Player.h b/src/Magnum/Animation/Player.h index a91ef6b9a..cd1f892df 100644 --- a/src/Magnum/Animation/Player.h +++ b/src/Magnum/Animation/Player.h @@ -160,7 +160,14 @@ 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(). +animation is resumed again with @ref play(), stopped with @ref stop() or seeked +using @ref seekBy() / @ref seekTo(). + +Calling @ref seekBy() / @ref seekTo() while the animation is either playing or +pause will cause it to jump to specified time -- the next call to @ref advance() +will update the destination locations and/or fire user-defined callbacks with +new values, behaving as if the animation was played / paused with the seek +time. The callbacks are only ever fired from within the @ref advance() function, never from @ref pause(), @ref stop() or any other API. @@ -642,6 +649,42 @@ template& pause(T pauseTime); + /** + * @brief Seek by given time delta + * + * Causes the animation to jump forward (if @p timeDelta is positive) + * or backward (if @p timeDelta is negative). If @ref state() is + * @ref State::Paused, seeking too far backward will make the animation + * paused at the beginning, while seeking too far forward will cause + * it paused at the end (i.e., not stopped). If @ref state() is already + * @ref State::Stopped, the function does nothing. See @ref advance() + * for a detailed description of seeking behavior. + * + * @note This function doesn't clamp the seek in any way --- so for + * example seeking too far back will make the animation wait for + * being played from the beginning in the future. + */ + Player& seekBy(T timeDelta); + + /** + * @brief Seek to given absolute animation time + * + * Causes the animation to jump to @p animationTime at given + * @p seekTime. If @ref state() is @ref State::Playing, seeking too far + * backward will make the animation start from the beginning, while + * seeking too far forward will cause the animation to be stopped. If + * @ref state() is @ref State::Paused, seeking too far backward will + * make the animation paused at the beginning, while seeking too far + * forward will cause it paused at the end (i.e., not stopped). If + * @ref state() is @ref State::Stopped, the function does nothing. See + * @ref advance() for a detailed description of seeking behavior. + * + * @note This function doesn't clamp the seek in any way --- so for + * example seeking too far back will make the animation wait for + * being played from the beginning in the future. + */ + Player& seekTo(T seekTime, T animationTime); + /** * @brief Stop * @@ -689,7 +732,8 @@ template& advance(T time); diff --git a/src/Magnum/Animation/Player.hpp b/src/Magnum/Animation/Player.hpp index e333a92d6..8a487d01c 100644 --- a/src/Magnum/Animation/Player.hpp +++ b/src/Magnum/Animation/Player.hpp @@ -127,6 +127,44 @@ template Player& Player::pause(T pauseTime) { return *this; } +template Player& Player::seekBy(T timeDelta) { + /* Animation is stopped, nothing to do */ + if(_state == State::Stopped) return *this; + + /* If the animation is paused and parked already, trigger a "park" again in + order to have the values updated on the next call to advance(). The + value is simply the new elapsed animation time. */ + if(_state == State::Paused && _stopPauseTime == T{}) { + _stopPauseTime = _startTime + timeDelta; + _startTime = {}; + return *this; + } + + /* Otherwise, the animation is either playing or not yet parked, simply + patch the start time to make the seek */ + _startTime -= timeDelta; + return *this; +} + +template Player& Player::seekTo(T seekTime, T animationTime) { + /* Animation is stopped, nothing to do */ + if(_state == State::Stopped) return *this; + + /* If the animation is paused and parked already, trigger a "park" again in + order to have the values updated on the next call to advance(). The + value is simply the new elapsed animation time. */ + if(_state == State::Paused && _stopPauseTime == T{}) { + _stopPauseTime = animationTime; + _startTime = {}; + return *this; + } + + /* Otherwise, the animation is either playing or not yet parked, simply + patch the start time to make the seek */ + _startTime = seekTime - animationTime; + return *this; +} + template Player& Player::stop() { _state = State::Stopped; /* Anything, just not a default-constructed value */ diff --git a/src/Magnum/Animation/Test/PlayerTest.cpp b/src/Magnum/Animation/Test/PlayerTest.cpp index 9dadde05d..cc951f207 100644 --- a/src/Magnum/Animation/Test/PlayerTest.cpp +++ b/src/Magnum/Animation/Test/PlayerTest.cpp @@ -61,6 +61,16 @@ struct PlayerTest: TestSuite::Tester { void advanceZeroDurationInfinitePlayCount(); void advanceZeroDurationInfinitePlayCountChrono(); + void seekByStopped(); + void seekByPlaying(); + void seekByPaused(); + void seekByPausedParked(); + + void seekToStopped(); + void seekToPlaying(); + void seekToPaused(); + void seekToPausedParked(); + void setState(); void add(); @@ -132,6 +142,16 @@ PlayerTest::PlayerTest() { &PlayerTest::advanceZeroDurationInfinitePlayCount, &PlayerTest::advanceZeroDurationInfinitePlayCountChrono, + &PlayerTest::seekByStopped, + &PlayerTest::seekByPlaying, + &PlayerTest::seekByPaused, + &PlayerTest::seekByPausedParked, + + &PlayerTest::seekToStopped, + &PlayerTest::seekToPlaying, + &PlayerTest::seekToPaused, + &PlayerTest::seekToPausedParked, + &PlayerTest::setState, &PlayerTest::add, @@ -800,6 +820,226 @@ void PlayerTest::advanceZeroDurationInfinitePlayCountChrono() { CORRADE_COMPARE(value, 4.0f); } +void PlayerTest::seekByStopped() { + Float value = -1.0f; + Player player; + player.add(Track, value); + + CORRADE_COMPARE(player.state(), State::Stopped); + CORRADE_COMPARE(player.elapsed(0.0f), std::make_pair(0, 0.0f)); + CORRADE_COMPARE(value, -1.0f); + + player.seekBy(1.5f); + player.advance(1.75f); + + /* Nothing should change */ + CORRADE_COMPARE(player.state(), State::Stopped); + CORRADE_COMPARE(player.elapsed(0.0f), std::make_pair(0, 0.0f)); + CORRADE_COMPARE(value, -1.0f); +} + +void PlayerTest::seekByPlaying() { + Float value = -1.0f; + Player player; + player.add(Track, value) + .play(22.0f); + + /* 1.75 secs in */ + player.advance(23.75f); + CORRADE_COMPARE(player.state(), State::Playing); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 1.75f)); + CORRADE_COMPARE(value, 4.0f); + + /* Seek to 0.5 secs in, the value should not change after just a seek */ + value = -1.0f; + player.seekBy(-1.25f); + CORRADE_COMPARE(player.state(), State::Playing); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, -1.0f); + + /* Now it should be updated at 0.5 secs in */ + player.advance(23.75f); + CORRADE_COMPARE(player.state(), State::Playing); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, 2.0f); +} + +void PlayerTest::seekByPaused() { + Float value = -1.0f; + Player player; + player.add(Track, value) + .play(22.0f); + + /* Pause at 1.75 secs in, no advance() yet so not parked yet */ + player.pause(23.75f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 1.75f)); + CORRADE_COMPARE(value, -1.0f); + + /* Seek to 0.5 secs in, the value should not change after just a seek */ + player.seekBy(-1.25f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, -1.0f); + + /* Now it should be updated at 0.5 secs in */ + player.advance(23.75f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, 2.0f); + + /* Updating again should do nothing */ + value = -1.0f; + player.advance(25.0f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(25.0f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, -1.0f); +} + +void PlayerTest::seekByPausedParked() { + Float value = -1.0f; + Player player; + player.add(Track, value) + .play(22.0f); + + /* Pause at 1.75 secs in */ + player.pause(23.75f) + .advance(23.75f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 1.75f)); + CORRADE_COMPARE(value, 4.0f); + + /* Seek to 0.5 secs in, the value should not change after just a seek */ + value = -1.0f; + player.seekBy(-1.25f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, -1.0f); + + /* Now it should be updated (re-parked) at 0.5 secs in */ + player.advance(23.75f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, 2.0f); + + /* Updating again should do nothing */ + value = -1.0f; + player.advance(25.0f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(25.0f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, -1.0f); +} + +void PlayerTest::seekToStopped() { + Float value = -1.0f; + Player player; + player.add(Track, value); + + CORRADE_COMPARE(player.state(), State::Stopped); + CORRADE_COMPARE(player.elapsed(0.0f), std::make_pair(0, 0.0f)); + CORRADE_COMPARE(value, -1.0f); + + player.seekTo(1.75f, -0.5f); + player.advance(1.75f); + + /* Nothing should change */ + CORRADE_COMPARE(player.state(), State::Stopped); + CORRADE_COMPARE(player.elapsed(0.0f), std::make_pair(0, 0.0f)); + CORRADE_COMPARE(value, -1.0f); +} + +void PlayerTest::seekToPlaying() { + Float value = -1.0f; + Player player; + player.add(Track, value) + .play(22.0f); + + /* 1.75 secs in */ + player.advance(23.75f); + CORRADE_COMPARE(player.state(), State::Playing); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 1.75f)); + CORRADE_COMPARE(value, 4.0f); + + /* Seek to 0.5 secs in, the value should not change after just a seek */ + value = -1.0f; + player.seekTo(23.75f, 0.5f); + CORRADE_COMPARE(player.state(), State::Playing); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, -1.0f); + + /* Now it should be updated at 0.5 secs in */ + player.advance(23.75f); + CORRADE_COMPARE(player.state(), State::Playing); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, 2.0f); +} + +void PlayerTest::seekToPaused() { + Float value = -1.0f; + Player player; + player.add(Track, value) + .play(22.0f); + + /* Pause at 1.75 secs in, no advance() yet so not parked yet */ + player.pause(23.75f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 1.75f)); + CORRADE_COMPARE(value, -1.0f); + + /* Seek to 0.5 secs in, the value should not change after just a seek */ + player.seekTo(23.75f, 0.5f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, -1.0f); + + /* Now it should be updated at 0.5 secs in */ + player.advance(23.75f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, 2.0f); + + /* Updating again should do nothing */ + value = -1.0f; + player.advance(25.0f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(25.0f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, -1.0f); +} + +void PlayerTest::seekToPausedParked() { + Float value = -1.0f; + Player player; + player.add(Track, value) + .play(22.0f); + + /* Pause at 1.75 secs in */ + player.pause(23.75f) + .advance(23.75f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 1.75f)); + CORRADE_COMPARE(value, 4.0f); + + /* Seek to 0.5 secs in, the value should not change after just a seek */ + value = -1.0f; + player.seekTo(23.75f, 0.5f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, -1.0f); + + /* Now it should be updated (re-parked) at 0.5 secs in */ + player.advance(23.75f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(23.75f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, 2.0f); + + /* Updating again should do nothing */ + value = -1.0f; + player.advance(25.0f); + CORRADE_COMPARE(player.state(), State::Paused); + CORRADE_COMPARE(player.elapsed(25.0f), std::make_pair(0, 0.5f)); + CORRADE_COMPARE(value, -1.0f); +} + void PlayerTest::setState() { Player player; CORRADE_COMPARE(player.state(), State::Stopped);