/* -*-c++-*- * Copyright (C) 2008 Cedric Pinson * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library 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 * OpenSceneGraph Public License for more details. */ #ifndef OSGANIMATION_TIMELINE_H #define OSGANIMATION_TIMELINE_H #include #include #include #include #include #include #include #include namespace osgAnimation { class Action : public osg::Object { public: class Callback : public osg::Object { public: Callback(){} Callback(const Callback&,const osg::CopyOp&) {} META_Object(osgAnimation,Callback); virtual void operator()(Action* /*action*/) {} void addNestedCallback(Callback* callback) { if (_nested.valid()) _nested->addNestedCallback(callback); else _nested = callback; } protected: osg::ref_ptr _nested; }; typedef std::map > FrameCallback; META_Object(osgAnimation, Action); Action() { _numberFrame = 25; _fps = 25; _speed = 1.0; _loop = 1; } Action(const Action&,const osg::CopyOp&) {} void setCallback(double when, Callback* callback) { setCallback(static_cast(floor(when*_fps)), callback); } void setCallback(unsigned int frame, Callback* callback) { if (_framesCallback[frame].valid()) _framesCallback[frame]->addNestedCallback(callback); else _framesCallback[frame] = callback; } Callback* getCallback(unsigned int frame) { if (_framesCallback.find(frame) == _framesCallback.end()) return 0; return _framesCallback[frame].get(); } void setNumFrames(unsigned int numFrames) { _numberFrame = numFrames;} void setDuration(double duration) { _numberFrame = static_cast(floor(duration * _fps)); } unsigned int getNumFrames() const { return _numberFrame;} double getDuration() const { return _numberFrame * 1.0 / _fps; } // 0 means infini else it's the number of loop virtual void setLoop(int nb) { _loop = nb; } virtual unsigned int getLoop() const { return _loop;} // get the number of loop, the frame relative to loop. // return true if in range, and false if out of range. bool evaluateFrame(unsigned int frame, unsigned int& resultframe, unsigned int& nbloop ) { nbloop = frame / getNumFrames(); resultframe = frame; if (frame > getNumFrames()-1) { if (!getLoop()) resultframe = frame % getNumFrames(); else { if (nbloop >= getLoop()) return false; else resultframe = frame % getNumFrames(); } } return true; } virtual void evaluate(unsigned int frame) { unsigned int frameInAction; unsigned int loopDone; if (!evaluateFrame(frame, frameInAction, loopDone)) osg::notify(osg::DEBUG_INFO) << getName() << " Action frame " << frameInAction << " finished" << std::endl; else osg::notify(osg::DEBUG_INFO) << getName() << " Action frame " << frame << " relative to loop " << frameInAction << " no loop " << loopDone<< std::endl; } virtual void evaluateCallback(unsigned int frame) { unsigned int frameInAction; unsigned int loopDone; if (!evaluateFrame(frame, frameInAction, loopDone)) return; frame = frameInAction; if (_framesCallback.find(frame) != _framesCallback.end()) { std::cout << getName() << " evaluate callback " << _framesCallback[frame]->getName() << " at " << frame << std::endl; (*_framesCallback[frame])(this); } } protected: FrameCallback _framesCallback; double _speed; unsigned int _fps; unsigned int _numberFrame; unsigned int _loop; enum Status { Play, Stop }; Status _state; }; class OSGANIMATION_EXPORT Timeline : public osg::Object { public: META_Object(osgAnimation, Timeline); Timeline(); Timeline(const Timeline& nc,const osg::CopyOp& op = osg::CopyOp::SHALLOW_COPY); enum Status { Play, Stop }; Status getStatus() const { return _state; } typedef std::pair > FrameAction; typedef std::vector ActionList; typedef std::map ActionLayers; const ActionList& getActionLayer(int i) { return _actions[i]; } unsigned int getCurrentFrame() const { return _currentFrame;} double getCurrentTime() const { return _currentFrame * 1.0 / _fps;} void play() { _state = Play; } void gotoFrame(unsigned int frame) { _currentFrame = frame; } void stop() { _state = Stop; } bool getEvaluating() const { return _evaluating;} bool isActive(Action* activeAction) { // update from high priority to low priority for( ActionLayers::iterator iterAnim = _actions.begin(); iterAnim != _actions.end(); ++iterAnim ) { // update all animation ActionList& list = iterAnim->second; for (unsigned int i = 0; i < list.size(); i++) { Action* action = list[i].second.get(); if (action == activeAction) { unsigned int firstFrame = list[i].first; // check if current frame of timeline hit an action interval if (_currentFrame >= firstFrame && _currentFrame < (firstFrame + action->getNumFrames()) ) return true; } } } return false; } void removeAction(Action* action) { if (getEvaluating()) _removeActionOperations.push_back(FrameAction(0, action)); else internalRemoveAction(action); } virtual void addActionAt(unsigned int frame, Action* action, int priority = 0) { if (getEvaluating()) _addActionOperations.push_back(Command(priority,FrameAction(frame, action))); else internalAddAction(priority, FrameAction(frame, action)); } virtual void addActionAt(double t, Action* action, int priority = 0) { unsigned int frame = static_cast(floor(t * _fps)); addActionAt(frame, action, priority); } virtual void evaluate(unsigned int frame) { setEvaluating(true); osg::notify(osg::DEBUG_INFO) << getName() << " evaluate frame " << _currentFrame << std::endl; // update from high priority to low priority for( ActionLayers::reverse_iterator iterAnim = _actions.rbegin(); iterAnim != _actions.rend(); ++iterAnim ) { // update all animation ActionList& list = iterAnim->second; for (unsigned int i = 0; i < list.size(); i++) { unsigned int firstFrame = list[i].first; Action* action = list[i].second.get(); // check if current frame of timeline hit an action interval if (frame >= firstFrame && frame < (firstFrame + action->getNumFrames()) ) action->evaluate(frame - firstFrame); } } setEvaluating(false); // evaluate callback after updating all animation evaluateCallback(frame); _previousFrameEvaluated = frame; } virtual void evaluateCallback(unsigned int frame) { // update from high priority to low priority for( ActionLayers::reverse_iterator iterAnim = _actions.rbegin(); iterAnim != _actions.rend(); ++iterAnim ) { // update all animation ActionList& list = iterAnim->second; for (unsigned int i = 0; i < list.size(); i++) { unsigned int firstFrame = list[i].first; Action* action = list[i].second.get(); // check if current frame of timeline hit an action interval if (frame >= firstFrame && frame < (firstFrame + action->getNumFrames()) ) action->evaluateCallback(frame - firstFrame); } } processPendingOperation(); } virtual void update(double simulationTime) { // first time we call update we generate one frame if (!_initFirstFrame) { _lastUpdate = simulationTime; _initFirstFrame = true; evaluate(_currentFrame); } // find the number of frame pass since the last update double delta = (simulationTime - _lastUpdate); double nbframes = delta * _fps * _speed; unsigned int nb = static_cast(floor(nbframes)); for (unsigned int i = 0; i < nb; i++) { if (_state == Play) _currentFrame++; evaluate(_currentFrame); } if (nb) { _lastUpdate += ((double)nb) / _fps; } } protected: ActionLayers _actions; double _lastUpdate; double _speed; unsigned int _currentFrame; unsigned int _fps; unsigned int _numberFrame; unsigned int _previousFrameEvaluated; bool _loop; bool _initFirstFrame; Status _state; // to manage pending operation bool _evaluating; struct Command { Command():_priority(0) {} Command(int priority, const FrameAction& action) : _priority(priority), _action(action) {} int _priority; FrameAction _action; }; typedef std::vector CommandList; CommandList _addActionOperations; ActionList _removeActionOperations; void setEvaluating(bool state) { _evaluating = state;} void processPendingOperation() { // process all pending add action operation while( !_addActionOperations.empty()) { internalAddAction(_addActionOperations.back()._priority, _addActionOperations.back()._action); _addActionOperations.pop_back(); } // process all pending remove action operation while( !_removeActionOperations.empty()) { internalRemoveAction(_removeActionOperations.back().second.get()); _removeActionOperations.pop_back(); } } void internalRemoveAction(Action* action) { for (ActionLayers::iterator it = _actions.begin(); it != _actions.end(); it++) { ActionList& fa = it->second; for (unsigned int i = 0; i < fa.size(); i++) if (fa[i].second.get() == action) { fa.erase(fa.begin() + i); return; } } } void internalAddAction(int priority, const FrameAction& ftl) { _actions[priority].push_back(ftl); } }; // blend in from 0 to weight in duration class BlendIn : public Action { double _weight; osg::ref_ptr _animation; public: BlendIn(Animation* animation, double duration, double weight) { _animation = animation; _weight = weight; float d = duration * _fps; setNumFrames(static_cast(floor(d)) + 1); setName("BlendIn"); } double getWeight() const { return _weight;} virtual void evaluate(unsigned int frame) { Action::evaluate(frame); // frame + 1 because the start is 0 and we want to start the blend in at the first // frame. double ratio = ( (frame+1) * 1.0 / (getNumFrames()) ); double w = _weight; if (frame < getNumFrames() -1 ) // the last frame we set the target weight the user asked w = _weight * ratio; _animation->setWeight(w); } }; // blend in from 0 to weight in duration class BlendOut : public Action { double _weight; osg::ref_ptr _animation; public: BlendOut(Animation* animation, double duration) { _animation = animation; float d = duration * _fps; setNumFrames(static_cast(floor(d) + 1)); _weight = 1.0; setName("BlendOut"); } double getWeight() const { return _weight;} virtual void evaluate(unsigned int frame) { Action::evaluate(frame); // frame + 1 because the start is 0 and we want to start the blend in at the first // frame. double ratio = ( (frame+1) * 1.0 / (getNumFrames()) ); double w = 0.0; if (frame < getNumFrames() -1 ) // the last frame we set the target weight the user asked w = _weight * (1.0-ratio); _animation->setWeight(w); } }; class ActionAnimation : public Action { public: ActionAnimation(Animation* animation) : _animation(animation) { setDuration(animation->getDuration()); setName(animation->getName()); } virtual void evaluate(unsigned int frame) { Action::evaluate(frame); _animation->update(frame * 1.0/_fps); } Animation* getAnimation() { return _animation.get(); } protected: osg::ref_ptr _animation; }; // encapsulate animation with blend in blend out for classic usage class StripAnimation : public Action { protected: typedef std::pair > FrameAction; public: StripAnimation(Animation* animation, double blendInDuration, double blendOutDuration, double blendInWeightTarget = 1.0 ) { _blendIn = new BlendIn(animation, blendInDuration, blendInWeightTarget); _animation = new ActionAnimation(animation); unsigned int start = static_cast(floor((_animation->getDuration() - blendOutDuration) * _fps)); _blendOut = FrameAction(start, new BlendOut(animation, blendOutDuration)); setName(animation->getName() + "_Strip"); _blendIn->setName(_animation->getName() + "_" + _blendIn->getName()); _blendOut.second->setName(_animation->getName() + "_" + _blendOut.second->getName()); setDuration(animation->getDuration()); } ActionAnimation* getActionAnimation() { return _animation.get(); } BlendIn* getBlendIn() { return _blendIn.get(); } BlendOut* getBlendOut() { return dynamic_cast(_blendOut.second.get()); } const ActionAnimation* getActionAnimation() const { return _animation.get(); } const BlendIn* getBlendIn() const { return _blendIn.get(); } const BlendOut* getBlendOut() const { return dynamic_cast(_blendOut.second.get()); } unsigned int getLoop() const { return _animation->getLoop(); } void setLoop(unsigned int loop) { _animation->setLoop(loop); if (!loop) setDuration(-1); else setDuration(loop * _animation->getDuration()); // duration changed re evaluate the blendout duration unsigned int start = static_cast(floor((getDuration() - _blendOut.second->getDuration()) * _fps)); _blendOut = FrameAction(start, _blendOut.second); } virtual void evaluate(unsigned int frame) { if (frame > getNumFrames() - 1) return; Action::evaluate(frame); if (frame < _blendIn->getNumFrames()) _blendIn->evaluate(frame); if (frame >= _blendOut.first) _blendOut.second->evaluate(frame - _blendOut.first); _animation->evaluate(frame); } protected: osg::ref_ptr _blendIn; FrameAction _blendOut; osg::ref_ptr _animation; }; class RunAction : public Action::Callback { protected: osg::ref_ptr _tm; osg::ref_ptr _action; public: RunAction(Timeline* tm, Action* a) : _tm(tm), _action(a) {} virtual void operator()(Action* /*action*/) { _tm->addActionAt(_tm->getCurrentFrame(), _action.get()); // warning we are trsversing the vector } }; } #endif