diff --git a/src/SceneGraph/Animable.cpp b/src/SceneGraph/Animable.cpp new file mode 100644 index 000000000..8af96cd96 --- /dev/null +++ b/src/SceneGraph/Animable.cpp @@ -0,0 +1,39 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include "Animable.hpp" + +namespace Magnum { namespace SceneGraph { + +#ifndef DOXYGEN_GENERATING_OUTPUT +template class MAGNUM_SCENEGRAPH_EXPORT Animable<2>; +template class MAGNUM_SCENEGRAPH_EXPORT Animable<3>; +template class MAGNUM_SCENEGRAPH_EXPORT AnimableGroup<2>; +template class MAGNUM_SCENEGRAPH_EXPORT AnimableGroup<3>; +#endif + +Debug operator<<(Debug debug, AnimationState value) { + switch(value) { + #define _c(value) case AnimationState::value: return debug << "SceneGraph::AnimationState::" #value; + _c(Stopped) + _c(Paused) + _c(Running) + #undef _c + } + + return debug << "SceneGraph::AnimationState::(invalid)"; +} + +}} diff --git a/src/SceneGraph/Animable.h b/src/SceneGraph/Animable.h new file mode 100644 index 000000000..9bb2b3ac8 --- /dev/null +++ b/src/SceneGraph/Animable.h @@ -0,0 +1,341 @@ +#ifndef Magnum_SceneGraph_Animable_h +#define Magnum_SceneGraph_Animable_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::Animable, enum Magnum::SceneGraph::AnimationState + */ + +#include "AbstractGroupedFeature.h" + +#include "magnumSceneGraphVisibility.h" + +namespace Magnum { namespace SceneGraph { + +/** +@brief Animation state + +@see Animatable::setState() +*/ +enum class AnimationState: std::uint8_t { + /** + * The animation is stopped. The animation will be started from the + * beginning when state is changed to @ref AnimationState "AnimationState::Running". + */ + Stopped, + + /** + * The animation is stopped. The animation will continue from paused + * position when state is changed to @ref AnimationState "AnimationState::Running". + */ + Paused, + + /** The animation is running. */ + Running +}; + +/** @debugoperator{Magnum::SceneGraph::Animable} */ +Debug MAGNUM_SCENEGRAPH_EXPORT operator<<(Debug debug, AnimationState value); + +/** +@brief %Animable + +Adds animation feature to object. Each %Animable is part of some AnimableGroup, +which takes care of running the animations. + +@section Animable-usage Usage + +First thing is add Animable feature to some object and implement +animationStep(). You can do it conveniently using multiple inheritance (see +@ref scenegraph-features for introduction). You need to pass animation +duration to constructor, animationStep() provides both absolute animation +time and time delta. Example: +@code +typedef SceneGraph::Object> Object3D; +typedef SceneGraph::Scene> Scene3D; + +class AnimableObject: public Object3D, SceneGraph::Animable3D<> { + public: + AnimableObject(Object* parent = nullptr, SceneGraph::DrawableGroup3D<>* group = nullptr): Object3D(parent), SceneGraph::Animable3D<>(this, 10.0f, group) { + // ... + } + + void animationStep(GLfloat time, GLfloat delta) override { + rotateX(deg(15.0f)*delta); // rotate at 15 degrees per second + } +} +@endcode + +Then add the object to your scene and some animation group. You can also use +AnimableGroup::add() and AnimableGroup::remove(). The animation is initially +in stopped state and without repeat, see setState(), setRepeated() and +setRepeatCount() for more information. +@code +Scene3D scene; +SceneGraph::AnimableGroup3D<> animables; + +(new AnimableObject(&scene, &animables)) + ->setState(SceneGraph::AnimationState::Running); +// ... +@endcode + +Animation step is performed by calling AnimableGroup::step() in your draw event +implementation. The function expects absolute time from relative to some fixed +point in the past and time delta (i.e. duration of the frame). You can use +Timeline for that, see its documentation for more information. +@code +Timeline timeline; +timeline.start(); + +void MyApplication::drawEvent() { + animables.step(timeline.lastFrameTime(), timeline.lastFrameDuration()); + + // ... + + timeline.nextFrame(); +} +@endcode + +@section Animable-performance Using animable groups to improve performance + +AnimableGroup is optimized for case when no animation is running - it just +puts itself to rest and waits until some animation changes its state to +running again. If you put animations which are not pernamently running to +separate group, they will not be traversed when calling AnimableGroup::step(), +saving precious frame time. + +@section Animable-explicit-specializations Explicit template specializations + +The following specialization are explicitly compiled into %SceneGraph library. +For other specializations you have to use Animable.hpp implementation file to +avoid linker errors. See also @ref compilation-speedup-hpp for more information. + + - @ref Animable "Animable<2, GLfloat>", @ref AnimableGroup "AnimableGroup<2, GLfloat>" + - @ref Animable "Animable<3, GLfloat>", @ref AnimableGroup "AnimableGroup<3, GLfloat>" + +@see @ref scenegraph, Animable2D, Animable3D, AnimableGroup2D, AnimableGroup3D +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +template +#else +template +#endif +class MAGNUM_SCENEGRAPH_EXPORT Animable: public AbstractGroupedFeature, T> { + friend class AnimableGroup; + + public: + /** + * @brief Constructor + * @param object %Object this animable belongs to + * @param duration Duration of the animation cycle in seconds. Set to + * 0 for infinite non-repeating animation. + * @param group Group this animable belongs to + * + * Creates stopped non-repeating animation with specified duration, + * adds the feature to the object and also to group, if specified. + * @see setRepeated(), AnimableGroup::add() + */ + Animable(AbstractObject* object, GLfloat duration, AnimableGroup* group = nullptr); + + /** @brief Animation duration */ + inline GLfloat duration() const { return _duration; } + + /** @brief Animation state */ + inline AnimationState state() const { return currentState; } + + /** + * @brief Set animation state + * @return Pointer to self (for method chaining) + * + * Note that changing state from @ref AnimationState "AnimationState::Stopped" + * to @ref AnimationState "AnimationState::Paused" is ignored and + * animation remains in @ref AnimationState "AnimationState::Stopped" + * state. See also animationStep() for more information. + * @see animationStarted(), animationPaused(), animationResumed(), + * animationStopped() + */ + Animable* setState(AnimationState state); + + /** + * @brief Whether the animation is repeated + * + * @see repeatCount() + */ + inline bool isRepeated() const { return _repeated; } + + /** + * @brief Enable/disable repeated animation + * @return Pointer to self (for method chaining) + * + * Default is `false`. + * @see setRepeatCount() + */ + inline Animable* setRepeated(bool repeated) { + _repeated = repeated; + return this; + } + + /** + * @brief Repeat count + * + * @see isRepeated() + */ + inline std::uint16_t repeatCount() const { return _repeatCount; } + + /** + * @brief Set repeat count + * @return Pointer to self (for method chaining) + * + * Has effect only if repeated animation is enabled. `0` means + * infinitely repeated animation. Default is `0`. + * @see setRepeated() + */ + inline Animable* setRepeatCount(std::uint16_t count) { + _repeatCount = count; + return this; + } + + AnimableGroup* group(); + const AnimableGroup* group() const; + + protected: + /** + * @brief Perform animation step + * @param time Time from start of the animation + * @param delta Time delta for current frame + * + * This function is periodically called from AnimableGroup::step() if + * the animation state is set to @ref AnimationState "AnimationState::Running". + * After animation duration is exceeded and repeat is not enabled or + * repeat count is exceeded, the animation state is set to + * @ref AnimationState "AnimationState::Stopped". + * + * If the animation is resumed from @ref AnimationState "AnimationState::Paused", + * this function is called with @p time continuing from the point + * when it was paused. If the animation is resumed from + * @ref AnimationState "AnimationState::Stopped", @p time starts with + * zero. + * + * @see state(), duration(), isRepeated(), repeatCount() + */ + virtual void animationStep(GLfloat time, GLfloat delta) = 0; + + /** + * @brief Action on animation start + * + * Called from AnimableGroup::step() when state is changed from + * @ref AnimationState "AnimationState::Stopped" to + * @ref AnimationState "AnimationState::Running" and before first + * animationStep() is called. + * + * Default implementation does nothing. + * + * @see setState() + */ + inline virtual void animationStarted() {} + + /** + * @brief Action on animation pause + * + * Called from AnimableGroup::step() when state changes from + * @ref AnimationState "AnimationState::Running" to + * @ref AnimationState "AnimationState::Paused" and after last + * animationStep() is called. + * + * Default implementation does nothing. + * + * @see setState() + */ + inline virtual void animationPaused() {} + + /** + * @brief Action on animation resume + * + * Called from AnimableGroup::step() when state changes from + * @ref AnimationState "AnimationState::Paused" to + * @ref AnimationState "AnimationState::Running" and before first + * animationStep() is called. + * + * Default implementation does nothing. + * + * @see setState() + */ + inline virtual void animationResumed() {} + + /** + * @brief Action on animation stop + * + * Called from AnimableGroup::step() when state changes from either + * @ref AnimationState "AnimationState::Running" or + * @ref AnimationState "AnimationState::Paused" to + * @ref AnimationState "AnimationState::Stopped" and after last + * animationStep() is called. + * + * You may want to use this function to properly finish the animation + * in case the framerate is not high enough to have animationStep() + * called enough times. Default implementation does nothing. + * + * @see setState() + */ + inline virtual void animationStopped() {} + + private: + const GLfloat _duration; + GLfloat startTime, pauseTime; + AnimationState previousState; + AnimationState currentState; + bool _repeated; + std::uint16_t _repeatCount; + std::uint16_t repeats; +}; + +/** +@brief Two-dimensional drawable + +Convenience alternative to %Animable<2, T>. See Animable for more +information. +@note Not available on GCC < 4.7. Use %Animable<2, T> instead. +@see Animable3D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef CORRADE_GCC46_COMPATIBILITY +template using Animable2D = Animable<2, T>; +#endif +#else +typedef Animable<2, T = GLfloat> Animable2D; +#endif + +/** +@brief Three-dimensional animable + +Convenience alternative to %Animable<3, T>. See Animable for more +information. +@note Not available on GCC < 4.7. Use %Animable<3, T> instead. +@see Animable2D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef CORRADE_GCC46_COMPATIBILITY +template using Animable3D = Animable<3, T>; +#endif +#else +typedef Animable<3, T = GLfloat> Animable3D; +#endif + +}} + +#endif diff --git a/src/SceneGraph/Animable.hpp b/src/SceneGraph/Animable.hpp new file mode 100644 index 000000000..dfa060a97 --- /dev/null +++ b/src/SceneGraph/Animable.hpp @@ -0,0 +1,126 @@ +#ifndef Magnum_SceneGraph_Animable_hpp +#define Magnum_SceneGraph_Animable_hpp +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief @ref compilation-speedup-hpp "Template implementation" for Animable.h and AnimableGroup.h + */ + +#include "AnimableGroup.h" +#include "Animable.h" + +#include "Timeline.h" + +namespace Magnum { namespace SceneGraph { + +template Animable::Animable(AbstractObject* object, GLfloat duration, AnimableGroup* group): AbstractGroupedFeature, T>(object, group), _duration(duration), startTime(std::numeric_limits::infinity()), pauseTime(-std::numeric_limits::infinity()), previousState(AnimationState::Stopped), currentState(AnimationState::Stopped), _repeated(false), _repeatCount(0), repeats(0) {} + +template Animable* Animable::setState(AnimationState state) { + if(currentState == state) return this; + + /* Not allowed (for sanity) */ + if(previousState == AnimationState::Stopped && state == AnimationState::Paused) + return this; + + /* Wake up the group in case no animations are running */ + group()->wakeUp = true; + currentState = state; + return this; +} + +template AnimableGroup* Animable::group() { + return static_cast*>(AbstractGroupedFeature, T>::group()); +} + +template const AnimableGroup* Animable::group() const { + return static_cast*>(AbstractGroupedFeature, T>::group()); +} + +template void AnimableGroup::step(const GLfloat time, const GLfloat delta) { + if(!_runningCount && !wakeUp) return; + wakeUp = false; + + for(std::size_t i = 0; i != this->size(); ++i) { + Animable* animable = (*this)[i]; + + /* The animation was stopped recently, just decrease count of running + animations if the animation was running before */ + if(animable->previousState != AnimationState::Stopped && animable->currentState == AnimationState::Stopped) { + if(animable->previousState == AnimationState::Running) + --_runningCount; + animable->previousState = AnimationState::Stopped; + animable->animationStopped(); + continue; + + /* The animation was paused recently, set pause time to previous frame time */ + } else if(animable->previousState == AnimationState::Running && animable->currentState == AnimationState::Paused) { + animable->previousState = AnimationState::Paused; + animable->pauseTime = time; + --_runningCount; + animable->animationPaused(); + continue; + + /* Skip the rest for not running animations */ + } else if(animable->currentState != AnimationState::Running) { + CORRADE_INTERNAL_ASSERT(animable->previousState == animable->currentState); + continue; + + /* The animation was started recently, set start time to previous frame time */ + } else if(animable->previousState == AnimationState::Stopped) { + animable->previousState = AnimationState::Running; + animable->startTime = time; + ++_runningCount; + animable->animationStarted(); + + /* The animation was resumed recently, add pause duration to start time */ + } else if(animable->previousState == AnimationState::Paused) { + animable->previousState = AnimationState::Running; + animable->startTime += time - animable->pauseTime; + ++_runningCount; + animable->animationResumed(); + } + + CORRADE_INTERNAL_ASSERT(animable->previousState == AnimationState::Running); + + /* Animation time exceeded duration */ + if(animable->_duration != 0.0f && time-animable->startTime > animable->_duration) { + /* Not repeated or repeat count exceeded, stop */ + if(!animable->_repeated || animable->repeats+1 == animable->_repeatCount) { + animable->previousState = AnimationState::Stopped; + animable->currentState = AnimationState::Stopped; + --_runningCount; + continue; + } + + /* Increase repeat count and add duration to startTime */ + ++animable->repeats; + animable->startTime += animable->_duration; + } + + /* Animation is still running, perform animation step */ + CORRADE_ASSERT(time-animable->startTime >= 0.0f, + "SceneGraph::AnimableGroup::step(): animation was started in future - probably wrong time passed", ); + CORRADE_ASSERT(delta >= 0.0f, + "SceneGraph::AnimableGroup::step(): negative delta passed", ); + animable->animationStep(time - animable->startTime, delta); + } + + CORRADE_INTERNAL_ASSERT(_runningCount <= this->size()); +} + +}} + +#endif diff --git a/src/SceneGraph/AnimableGroup.h b/src/SceneGraph/AnimableGroup.h new file mode 100644 index 000000000..243dfb5a7 --- /dev/null +++ b/src/SceneGraph/AnimableGroup.h @@ -0,0 +1,106 @@ +#ifndef Magnum_SceneGraph_AnimableGroup_h +#define Magnum_SceneGraph_AnimableGroup_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::AnimableGroup + */ + +#include "FeatureGroup.h" + +#include "magnumSceneGraphVisibility.h" + +namespace Magnum { namespace SceneGraph { + +/** +@brief Group of animables + +See Animable for more information. +@see @ref scenegraph, AnimableGroup2D, AnimableGroup3D +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +template +#else +template +#endif +class MAGNUM_SCENEGRAPH_EXPORT AnimableGroup: public FeatureGroup, T> { + friend class Animable; + + public: + /** + * @brief Constructor + */ + inline AnimableGroup(): _runningCount(0), wakeUp(false) {} + + /** + * @brief Count of running animations + * + * @see step() + */ + inline std::size_t runningCount() const { return _runningCount; } + + /** + * @brief Perform animation step + * @param time Absolute time (e.g. Timeline::previousFrameTime()) + * @param delta Time delta for current frame (e.g. Timeline::previousFrameDuration()) + * + * If there are no running animations the function does nothing. + * @see runningCount() + */ + void step(const GLfloat time, const GLfloat delta); + + private: + std::size_t _runningCount; + bool wakeUp; +}; + +/** +@brief Two-dimensional drawable + +Convenience alternative to %AnimableGroup<2, T>. See Animable for +more information. +@note Not available on GCC < 4.7. Use %AnimableGroup<2, T> instead. +@see AnimableGroup3D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef CORRADE_GCC46_COMPATIBILITY +template using AnimableGroup2D = AnimableGroup<2, T>; +#endif +#else +typedef AnimableGroup<2, T = GLfloat> AnimableGroup2D; +#endif + +/** +@brief Three-dimensional animable + +Convenience alternative to %AnimableGroup<3, T>. See Animable for +more information. +@note Not available on GCC < 4.7. Use %AnimableGroup<3, T> instead. +@see AnimableGroup2D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef CORRADE_GCC46_COMPATIBILITY +template using AnimableGroup3D = AnimableGroup<3, T>; +#endif +#else +typedef AnimableGroup<3, T = GLfloat> AnimableGroup3D; +#endif + +}} + +#endif diff --git a/src/SceneGraph/CMakeLists.txt b/src/SceneGraph/CMakeLists.txt index 1cd0780fd..5348905bf 100644 --- a/src/SceneGraph/CMakeLists.txt +++ b/src/SceneGraph/CMakeLists.txt @@ -1,4 +1,5 @@ set(MagnumSceneGraph_SRCS + Animable.cpp Camera.cpp) set(MagnumSceneGraph_HEADERS AbstractCamera.h @@ -11,6 +12,9 @@ set(MagnumSceneGraph_HEADERS AbstractTranslationRotation3D.h AbstractTranslationRotationScaling2D.h AbstractTranslationRotationScaling3D.h + Animable.h + Animable.hpp + AnimableGroup.h Camera2D.h Camera2D.hpp Camera3D.h diff --git a/src/SceneGraph/SceneGraph.h b/src/SceneGraph/SceneGraph.h index e8e7bd6ad..14fba11ca 100644 --- a/src/SceneGraph/SceneGraph.h +++ b/src/SceneGraph/SceneGraph.h @@ -66,6 +66,20 @@ template class AbstractTranslationRotation3D; template class AbstractTranslationRotationScaling2D; template class AbstractTranslationRotationScaling3D; +template class Animable; +#ifndef CORRADE_GCC46_COMPATIBILITY +template using Animable2D = Animable<2, T>; +template using Animable3D = Animable<3, T>; +#endif + +enum class AnimationState: std::uint8_t; + +template class AnimableGroup; +#ifndef CORRADE_GCC46_COMPATIBILITY +template using AnimableGroup2D = AnimableGroup<2, T>; +template using AnimableGroup3D = AnimableGroup<3, T>; +#endif + template class Camera2D; template class Camera3D; diff --git a/src/SceneGraph/Test/AnimableTest.cpp b/src/SceneGraph/Test/AnimableTest.cpp new file mode 100644 index 000000000..ad5c5ed5d --- /dev/null +++ b/src/SceneGraph/Test/AnimableTest.cpp @@ -0,0 +1,304 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include "AnimableTest.h" + +#include "SceneGraph/Animable.h" +#include "SceneGraph/AnimableGroup.h" +#include "SceneGraph/MatrixTransformation3D.h" + +CORRADE_TEST_MAIN(Magnum::SceneGraph::Test::AnimableTest) + +namespace Magnum { namespace SceneGraph { namespace Test { + +typedef SceneGraph::Object> Object3D; + +AnimableTest::AnimableTest() { + addTests(&AnimableTest::state, + &AnimableTest::step, + &AnimableTest::duration, + &AnimableTest::repeat, + &AnimableTest::stop, + &AnimableTest::pause, + &AnimableTest::debug); +} + +void AnimableTest::state() { + class StateTrackingAnimable: public SceneGraph::Animable<3> { + public: + StateTrackingAnimable(AbstractObject<3>* object, AnimableGroup<3>* group = nullptr): SceneGraph::Animable<3>(object, 1.0f, group) {} + + std::string trackedState; + + protected: + void animationStep(GLfloat, GLfloat) override {} + + void animationStarted() override { trackedState += "started"; } + void animationPaused() override { trackedState += "paused"; } + void animationResumed() override { trackedState += "resumed"; } + void animationStopped() override { trackedState += "stopped"; } + }; + + Object3D object; + AnimableGroup<3> group; + CORRADE_COMPARE(group.runningCount(), 0); + + /* Verify initial state */ + StateTrackingAnimable animable(&object, &group); + CORRADE_COMPARE(animable.state(), AnimationState::Stopped); + CORRADE_VERIFY(animable.trackedState.empty()); + group.step(1.0f, 1.0f); + CORRADE_VERIFY(animable.trackedState.empty()); + CORRADE_COMPARE(group.runningCount(), 0); + + /* Stopped -> paused is not supported */ + CORRADE_COMPARE(animable.state(), AnimationState::Stopped); + animable.setState(AnimationState::Paused); + CORRADE_COMPARE(animable.state(), AnimationState::Stopped); + + /* Stopped -> running */ + CORRADE_COMPARE(animable.state(), AnimationState::Stopped); + animable.trackedState.clear(); + animable.setState(AnimationState::Running); + CORRADE_VERIFY(animable.trackedState.empty()); + group.step(1.0f, 1.0f); + CORRADE_COMPARE(animable.trackedState, "started"); + CORRADE_COMPARE(group.runningCount(), 1); + + /* Running -> paused */ + CORRADE_COMPARE(animable.state(), AnimationState::Running); + animable.trackedState.clear(); + animable.setState(AnimationState::Paused); + CORRADE_VERIFY(animable.trackedState.empty()); + group.step(1.0f, 1.0f); + CORRADE_COMPARE(animable.trackedState, "paused"); + CORRADE_COMPARE(group.runningCount(), 0); + + /* Paused -> running */ + CORRADE_COMPARE(animable.state(), AnimationState::Paused); + animable.trackedState.clear(); + animable.setState(AnimationState::Running); + CORRADE_VERIFY(animable.trackedState.empty()); + group.step(1.0f, 1.0f); + CORRADE_COMPARE(animable.trackedState, "resumed"); + CORRADE_COMPARE(group.runningCount(), 1); + + /* Running -> stopped */ + CORRADE_COMPARE(animable.state(), AnimationState::Running); + animable.trackedState.clear(); + animable.setState(AnimationState::Stopped); + CORRADE_VERIFY(animable.trackedState.empty()); + group.step(1.0f, 1.0f); + CORRADE_COMPARE(animable.trackedState, "stopped"); + CORRADE_COMPARE(group.runningCount(), 0); + + animable.setState(AnimationState::Running); + group.step(1.0f, 1.0f); + animable.setState(AnimationState::Paused); + + /* Paused -> stopped */ + CORRADE_COMPARE(animable.state(), AnimationState::Paused); + animable.trackedState.clear(); + animable.setState(AnimationState::Stopped); + CORRADE_VERIFY(animable.trackedState.empty()); + group.step(1.0f, 1.0f); + CORRADE_COMPARE(animable.trackedState, "stopped"); + CORRADE_COMPARE(group.runningCount(), 0); + + /* Verify running count can go past 0/1 */ + group.add((new StateTrackingAnimable(&object, &group))->setState(AnimationState::Running)); + group.add((new StateTrackingAnimable(&object, &group))->setState(AnimationState::Running)); + group.step(1.0f, 1.0f); + CORRADE_COMPARE(group.runningCount(), 2); +} + +class OneShotAnimable: public SceneGraph::Animable<3> { + public: + OneShotAnimable(AbstractObject<3>* object, AnimableGroup<3>* group = nullptr): SceneGraph::Animable<3>(object, 10.0f, group), time(-1.0f) { + setState(AnimationState::Running); + } + + GLfloat time; + + protected: + void animationStep(GLfloat time, GLfloat) override { + this->time = time; + } +}; + +void AnimableTest::step() { + class InifiniteAnimable: public SceneGraph::Animable<3> { + public: + InifiniteAnimable(AbstractObject<3>* object, AnimableGroup<3>* group = nullptr): SceneGraph::Animable<3>(object, 0.0f, group), time(-1.0f), delta(0.0f) {} + + GLfloat time, delta; + + protected: + void animationStep(GLfloat time, GLfloat delta) override { + this->time = time; + this->delta = delta; + } + }; + + Object3D object; + AnimableGroup<3> group; + InifiniteAnimable animable(&object, &group); + + /* Calling step() if no object is running should do nothing */ + group.step(5.0f, 0.5f); + CORRADE_COMPARE(group.runningCount(), 0); + CORRADE_COMPARE(animable.time, -1.0f); + CORRADE_COMPARE(animable.delta, 0.0f); + + /* Calling step() with running animation should start it with zero + absolute time */ + animable.setState(AnimationState::Running); + group.step(5.0f, 0.5f); + CORRADE_COMPARE(group.runningCount(), 1); + CORRADE_COMPARE(animable.time, 0.0f); + CORRADE_COMPARE(animable.delta, 0.5f); + + /* Repeated call to step() will add to absolute animation time */ + group.step(8.0f, 0.75f); + CORRADE_COMPARE(animable.time, 3.0f); + CORRADE_COMPARE(animable.delta, 0.75f); +} + +void AnimableTest::duration() { + Object3D object; + AnimableGroup<3> group; + OneShotAnimable animable(&object, &group); + CORRADE_VERIFY(!animable.isRepeated()); + + /* First animation step is in duration, verify that animation is still + running and animationStep() is called */ + group.step(1.0f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Running); + CORRADE_COMPARE(animable.time, 0.0f); + + /* Next animation step is out of duration and repeat is not enabled, + animationStep() shouldn't be called and animation should be stopped */ + group.step(12.75f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Stopped); + CORRADE_COMPARE(animable.time, 0.0f); +} + +void AnimableTest::repeat() { + class RepeatingAnimable: public SceneGraph::Animable<3> { + public: + RepeatingAnimable(AbstractObject<3>* object, AnimableGroup<3>* group = nullptr): SceneGraph::Animable<3>(object, 10.0f, group), time(-1.0f) { + setState(AnimationState::Running); + setRepeated(true); + } + + GLfloat time; + + protected: + void animationStep(GLfloat time, GLfloat) override { + this->time = time; + } + }; + + Object3D object; + AnimableGroup<3> group; + RepeatingAnimable animable(&object, &group); + CORRADE_COMPARE(animable.repeatCount(), 0); + + /* First animation steps is in first loop iteration */ + group.step(1.0f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Running); + CORRADE_COMPARE(animable.time, 0.0f); + + /* Next animation step is in second loop iteration, animation should be + still running with time shifted by animation duration */ + group.step(11.5f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Running); + CORRADE_COMPARE(animable.time, 0.5f); + + /* Third loop iteration (just to be sure) */ + group.step(25.5f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Running); + CORRADE_COMPARE(animable.time, 4.5f); + + /* Cap repeat count to 3, the animation should be stopped now (and + animationStep() shouldn't be called)*/ + animable.setRepeatCount(3); + group.step(33.0f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Stopped); + CORRADE_COMPARE(animable.time, 4.5f); +} + +void AnimableTest::stop() { + Object3D object; + AnimableGroup<3> group; + OneShotAnimable animable(&object, &group); + CORRADE_COMPARE(animable.repeatCount(), 0); + + /* Eat up some absolute time */ + group.step(1.0f, 0.5f); + group.step(1.5f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Running); + CORRADE_COMPARE(animable.time, 0.5f); + + /* Stop the animable, nothing should be done */ + animable.setState(AnimationState::Stopped); + group.step(1.5f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Stopped); + CORRADE_COMPARE(animable.time, 0.5f); + + /* Restarting the animation should start with zero absolute time */ + animable.setState(AnimationState::Running); + group.step(2.5f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Running); + CORRADE_COMPARE(animable.time, 0.0f); +} + +void AnimableTest::pause() { + Object3D object; + AnimableGroup<3> group; + OneShotAnimable animable(&object, &group); + + /* First two steps, animation is running */ + group.step(1.0f, 0.5f); + group.step(2.5f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Running); + CORRADE_COMPARE(animable.time, 1.5f); + + /* Pausing the animation, first step should decrease count of running + animations and save paused time, next steps shouldn't affect anything */ + CORRADE_COMPARE(group.runningCount(), 1); + animable.setState(AnimationState::Paused); + CORRADE_COMPARE(group.runningCount(), 1); + group.step(3.0f, 0.5f); + CORRADE_COMPARE(group.runningCount(), 0); + group.step(4.5f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Paused); + CORRADE_COMPARE(animable.time, 1.5f); + + /* Unpausing, next step should continue from absolute time when pause + occured */ + animable.setState(AnimationState::Running); + group.step(5.0f, 0.5f); + CORRADE_COMPARE(animable.state(), AnimationState::Running); + CORRADE_COMPARE(animable.time, 2.0f); +} + +void AnimableTest::debug() { + std::ostringstream o; + Debug(&o) << AnimationState::Running; + CORRADE_COMPARE(o.str(), "SceneGraph::AnimationState::Running\n"); +} + +}}} diff --git a/src/SceneGraph/Test/AnimableTest.h b/src/SceneGraph/Test/AnimableTest.h new file mode 100644 index 000000000..1428db3ca --- /dev/null +++ b/src/SceneGraph/Test/AnimableTest.h @@ -0,0 +1,38 @@ +#ifndef Magnum_SceneGraph_Test_AnimableTest_h +#define Magnum_SceneGraph_Test_AnimableTest_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include + +namespace Magnum { namespace SceneGraph { namespace Test { + +class AnimableTest: public Corrade::TestSuite::Tester { + public: + AnimableTest(); + + void state(); + void step(); + void duration(); + void repeat(); + void stop(); + void pause(); + + void debug(); +}; + +}}} + +#endif diff --git a/src/SceneGraph/Test/CMakeLists.txt b/src/SceneGraph/Test/CMakeLists.txt index eef3c02cb..aa0efa333 100644 --- a/src/SceneGraph/Test/CMakeLists.txt +++ b/src/SceneGraph/Test/CMakeLists.txt @@ -1,3 +1,4 @@ +corrade_add_test(SceneGraphAnimableTest AnimableTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test(SceneGraphObjectTest ObjectTest.cpp LIBRARIES MagnumSceneGraphTestLib) corrade_add_test(SceneGraphCameraTest CameraTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test(SceneGraphSceneTest SceneTest.cpp LIBRARIES MagnumSceneGraph)