|
|
|
@ -1,11 +1,13 @@
|
|
|
|
|
#include "animation.hpp"
|
|
|
|
|
|
|
|
|
|
#include <iomanip>
|
|
|
|
|
#include <limits>
|
|
|
|
|
|
|
|
|
|
#include <osg/PositionAttitudeTransform>
|
|
|
|
|
#include <osg/TexGen>
|
|
|
|
|
#include <osg/TexEnvCombine>
|
|
|
|
|
#include <osg/ComputeBoundsVisitor>
|
|
|
|
|
#include <osg/MatrixTransform>
|
|
|
|
|
|
|
|
|
|
#include <components/nifosg/nifloader.hpp>
|
|
|
|
|
|
|
|
|
@ -13,6 +15,10 @@
|
|
|
|
|
#include <components/resource/scenemanager.hpp>
|
|
|
|
|
#include <components/resource/texturemanager.hpp>
|
|
|
|
|
|
|
|
|
|
#include <components/nifosg/nifloader.hpp> // KeyframeHolder
|
|
|
|
|
|
|
|
|
|
#include <components/vfs/manager.hpp>
|
|
|
|
|
|
|
|
|
|
#include <components/misc/resourcehelpers.hpp>
|
|
|
|
|
|
|
|
|
|
#include <components/sceneutil/statesetupdater.hpp>
|
|
|
|
@ -74,6 +80,89 @@ namespace
|
|
|
|
|
std::vector<osg::ref_ptr<osg::Texture2D> > mTextures;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class NodeMapVisitor : public osg::NodeVisitor
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
NodeMapVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {}
|
|
|
|
|
|
|
|
|
|
void apply(osg::MatrixTransform& trans)
|
|
|
|
|
{
|
|
|
|
|
mMap[Misc::StringUtils::lowerCase(trans.getName())] = &trans;
|
|
|
|
|
traverse(trans);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef std::map<std::string, osg::ref_ptr<osg::MatrixTransform> > NodeMap;
|
|
|
|
|
|
|
|
|
|
const NodeMap& getNodeMap() const
|
|
|
|
|
{
|
|
|
|
|
return mMap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
NodeMap mMap;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
NifOsg::TextKeyMap::const_iterator findGroupStart(const NifOsg::TextKeyMap &keys, const std::string &groupname)
|
|
|
|
|
{
|
|
|
|
|
NifOsg::TextKeyMap::const_iterator iter(keys.begin());
|
|
|
|
|
for(;iter != keys.end();++iter)
|
|
|
|
|
{
|
|
|
|
|
if(iter->second.compare(0, groupname.size(), groupname) == 0 &&
|
|
|
|
|
iter->second.compare(groupname.size(), 2, ": ") == 0)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return iter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float calcAnimVelocity(const std::multimap<float, std::string>& keys,
|
|
|
|
|
NifOsg::KeyframeController *nonaccumctrl, const osg::Vec3f& accum, const std::string &groupname)
|
|
|
|
|
{
|
|
|
|
|
const std::string start = groupname+": start";
|
|
|
|
|
const std::string loopstart = groupname+": loop start";
|
|
|
|
|
const std::string loopstop = groupname+": loop stop";
|
|
|
|
|
const std::string stop = groupname+": stop";
|
|
|
|
|
float starttime = std::numeric_limits<float>::max();
|
|
|
|
|
float stoptime = 0.0f;
|
|
|
|
|
|
|
|
|
|
// Pick the last Loop Stop key and the last Loop Start key.
|
|
|
|
|
// This is required because of broken text keys in AshVampire.nif.
|
|
|
|
|
// It has *two* WalkForward: Loop Stop keys at different times, the first one is used for stopping playback
|
|
|
|
|
// but the animation velocity calculation uses the second one.
|
|
|
|
|
// As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated,
|
|
|
|
|
// because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough.
|
|
|
|
|
NifOsg::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin());
|
|
|
|
|
while(keyiter != keys.rend())
|
|
|
|
|
{
|
|
|
|
|
if(keyiter->second == start || keyiter->second == loopstart)
|
|
|
|
|
{
|
|
|
|
|
starttime = keyiter->first;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
++keyiter;
|
|
|
|
|
}
|
|
|
|
|
keyiter = keys.rbegin();
|
|
|
|
|
while(keyiter != keys.rend())
|
|
|
|
|
{
|
|
|
|
|
if (keyiter->second == stop)
|
|
|
|
|
stoptime = keyiter->first;
|
|
|
|
|
else if (keyiter->second == loopstop)
|
|
|
|
|
{
|
|
|
|
|
stoptime = keyiter->first;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
++keyiter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(stoptime > starttime)
|
|
|
|
|
{
|
|
|
|
|
osg::Vec3f startpos = osg::componentMultiply(nonaccumctrl->getTranslation(starttime), accum);
|
|
|
|
|
osg::Vec3f endpos = osg::componentMultiply(nonaccumctrl->getTranslation(stoptime), accum);
|
|
|
|
|
|
|
|
|
|
return (startpos-endpos).length() / (stoptime - starttime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace MWRender
|
|
|
|
@ -84,7 +173,8 @@ namespace MWRender
|
|
|
|
|
, mInsert(parentNode)
|
|
|
|
|
, mResourceSystem(resourceSystem)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
for(size_t i = 0;i < sNumGroups;i++)
|
|
|
|
|
mAnimationTimePtr[i].reset(new AnimationTime(this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Animation::~Animation()
|
|
|
|
@ -98,16 +188,589 @@ namespace MWRender
|
|
|
|
|
mPtr = ptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Animation::setAccumulation(const osg::Vec3f& accum)
|
|
|
|
|
{
|
|
|
|
|
mAccumulate = accum;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t Animation::detectAnimGroup(osg::Node* node)
|
|
|
|
|
{
|
|
|
|
|
static const char sGroupRoots[sNumGroups][32] = {
|
|
|
|
|
"", /* Lower body / character root */
|
|
|
|
|
"Bip01 Spine1", /* Torso */
|
|
|
|
|
"Bip01 L Clavicle", /* Left arm */
|
|
|
|
|
"Bip01 R Clavicle", /* Right arm */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
while(node != mObjectRoot)
|
|
|
|
|
{
|
|
|
|
|
const std::string &name = node->getName();
|
|
|
|
|
for(size_t i = 1;i < sNumGroups;i++)
|
|
|
|
|
{
|
|
|
|
|
if(name == sGroupRoots[i])
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(node->getNumParents() > 0);
|
|
|
|
|
|
|
|
|
|
node = node->getParent(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::multimap<float, std::string> &Animation::AnimSource::getTextKeys()
|
|
|
|
|
{
|
|
|
|
|
return mKeyframes->mTextKeys;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Animation::addAnimSource(const std::string &model)
|
|
|
|
|
{
|
|
|
|
|
std::string kfname = model;
|
|
|
|
|
Misc::StringUtils::toLower(kfname);
|
|
|
|
|
|
|
|
|
|
if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0)
|
|
|
|
|
kfname.replace(kfname.size()-4, 4, ".kf");
|
|
|
|
|
|
|
|
|
|
if(!mResourceSystem->getVFS()->exists(kfname))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
boost::shared_ptr<AnimSource> animsrc;
|
|
|
|
|
animsrc.reset(new AnimSource);
|
|
|
|
|
animsrc->mKeyframes = mResourceSystem->getSceneManager()->getKeyframes(kfname);
|
|
|
|
|
|
|
|
|
|
if (animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (NifOsg::KeyframeHolder::KeyframeControllerMap::const_iterator it = animsrc->mKeyframes->mKeyframeControllers.begin();
|
|
|
|
|
it != animsrc->mKeyframes->mKeyframeControllers.end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
std::string bonename = Misc::StringUtils::lowerCase(it->first);
|
|
|
|
|
NodeMap::const_iterator found = mNodeMap.find(bonename);
|
|
|
|
|
if (found == mNodeMap.end())
|
|
|
|
|
throw std::runtime_error("addAnimSource: can't find bone " + bonename);
|
|
|
|
|
|
|
|
|
|
osg::Node* node = found->second;
|
|
|
|
|
|
|
|
|
|
size_t group = detectAnimGroup(node);
|
|
|
|
|
|
|
|
|
|
// clone the controller, because each Animation needs its own ControllerSource
|
|
|
|
|
osg::ref_ptr<NifOsg::KeyframeController> cloned = osg::clone(it->second.get(), osg::CopyOp::DEEP_COPY_ALL);
|
|
|
|
|
cloned->setSource(mAnimationTimePtr[group]);
|
|
|
|
|
|
|
|
|
|
animsrc->mControllerMap[group].insert(std::make_pair(bonename, cloned));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mAnimSources.push_back(animsrc);
|
|
|
|
|
|
|
|
|
|
SceneUtil::AssignControllerSourcesVisitor assignVisitor(mAnimationTimePtr[0]);
|
|
|
|
|
mObjectRoot->accept(assignVisitor);
|
|
|
|
|
|
|
|
|
|
if (!mAccumRoot)
|
|
|
|
|
{
|
|
|
|
|
NodeMap::const_iterator found = mNodeMap.find("root bone");
|
|
|
|
|
if (found == mNodeMap.end())
|
|
|
|
|
found = mNodeMap.find("bip01");
|
|
|
|
|
|
|
|
|
|
if (found != mNodeMap.end())
|
|
|
|
|
mAccumRoot = found->second;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Animation::hasAnimation(const std::string &anim)
|
|
|
|
|
{
|
|
|
|
|
AnimSourceList::const_iterator iter(mAnimSources.begin());
|
|
|
|
|
for(;iter != mAnimSources.end();++iter)
|
|
|
|
|
{
|
|
|
|
|
const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys();
|
|
|
|
|
if(findGroupStart(keys, anim) != keys.end())
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float Animation::getStartTime(const std::string &groupname) const
|
|
|
|
|
{
|
|
|
|
|
for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter)
|
|
|
|
|
{
|
|
|
|
|
const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys();
|
|
|
|
|
|
|
|
|
|
NifOsg::TextKeyMap::const_iterator found = findGroupStart(keys, groupname);
|
|
|
|
|
if(found != keys.end())
|
|
|
|
|
return found->first;
|
|
|
|
|
}
|
|
|
|
|
return -1.f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float Animation::getTextKeyTime(const std::string &textKey) const
|
|
|
|
|
{
|
|
|
|
|
for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter)
|
|
|
|
|
{
|
|
|
|
|
const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys();
|
|
|
|
|
|
|
|
|
|
for(NifOsg::TextKeyMap::const_iterator iterKey(keys.begin()); iterKey != keys.end(); ++iterKey)
|
|
|
|
|
{
|
|
|
|
|
if(iterKey->second.compare(0, textKey.size(), textKey) == 0)
|
|
|
|
|
return iterKey->first;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1.f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Animation::handleTextKey(AnimState &state, const std::string &groupname, const std::multimap<float, std::string>::const_iterator &key,
|
|
|
|
|
const std::multimap<float, std::string>& map)
|
|
|
|
|
{
|
|
|
|
|
// TODO: forward to listener?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Animation::play(const std::string &groupname, int priority, int groups, bool autodisable, float speedmult,
|
|
|
|
|
const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback)
|
|
|
|
|
{
|
|
|
|
|
if(!mObjectRoot || mAnimSources.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if(groupname.empty())
|
|
|
|
|
{
|
|
|
|
|
resetActiveGroups();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
priority = std::max(0, priority);
|
|
|
|
|
|
|
|
|
|
AnimStateMap::iterator stateiter = mStates.begin();
|
|
|
|
|
while(stateiter != mStates.end())
|
|
|
|
|
{
|
|
|
|
|
if(stateiter->second.mPriority == priority)
|
|
|
|
|
mStates.erase(stateiter++);
|
|
|
|
|
else
|
|
|
|
|
++stateiter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stateiter = mStates.find(groupname);
|
|
|
|
|
if(stateiter != mStates.end())
|
|
|
|
|
{
|
|
|
|
|
stateiter->second.mPriority = priority;
|
|
|
|
|
resetActiveGroups();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Look in reverse; last-inserted source has priority. */
|
|
|
|
|
AnimState state;
|
|
|
|
|
AnimSourceList::reverse_iterator iter(mAnimSources.rbegin());
|
|
|
|
|
for(;iter != mAnimSources.rend();++iter)
|
|
|
|
|
{
|
|
|
|
|
const NifOsg::TextKeyMap &textkeys = (*iter)->getTextKeys();
|
|
|
|
|
if(reset(state, textkeys, groupname, start, stop, startpoint, loopfallback))
|
|
|
|
|
{
|
|
|
|
|
state.mSource = *iter;
|
|
|
|
|
state.mSpeedMult = speedmult;
|
|
|
|
|
state.mLoopCount = loops;
|
|
|
|
|
state.mPlaying = (state.mTime < state.mStopTime);
|
|
|
|
|
state.mPriority = priority;
|
|
|
|
|
state.mGroups = groups;
|
|
|
|
|
state.mAutoDisable = autodisable;
|
|
|
|
|
mStates[groupname] = state;
|
|
|
|
|
|
|
|
|
|
NifOsg::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime));
|
|
|
|
|
if (state.mPlaying)
|
|
|
|
|
{
|
|
|
|
|
while(textkey != textkeys.end() && textkey->first <= state.mTime)
|
|
|
|
|
{
|
|
|
|
|
handleTextKey(state, groupname, textkey, textkeys);
|
|
|
|
|
++textkey;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0)
|
|
|
|
|
{
|
|
|
|
|
state.mLoopCount--;
|
|
|
|
|
state.mTime = state.mLoopStartTime;
|
|
|
|
|
state.mPlaying = true;
|
|
|
|
|
if(state.mTime >= state.mLoopStopTime)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
NifOsg::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime));
|
|
|
|
|
while(textkey != textkeys.end() && textkey->first <= state.mTime)
|
|
|
|
|
{
|
|
|
|
|
handleTextKey(state, groupname, textkey, textkeys);
|
|
|
|
|
++textkey;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(iter == mAnimSources.rend())
|
|
|
|
|
std::cerr<< "Failed to find animation "<<groupname<<" for "<<mPtr.getCellRef().getRefId() <<std::endl;
|
|
|
|
|
|
|
|
|
|
resetActiveGroups();
|
|
|
|
|
|
|
|
|
|
// This shouldn't be needed, same is already done in resetActiveGroups()
|
|
|
|
|
/*
|
|
|
|
|
if (!state.mPlaying && mNonAccumCtrl)
|
|
|
|
|
{
|
|
|
|
|
// If the animation state is not playing, we need to manually apply the accumulation
|
|
|
|
|
// (see updatePosition, which would be called if the animation was playing)
|
|
|
|
|
mAccumRoot->setPosition(-mNonAccumCtrl->getTranslation(state.mTime)*mAccumulate);
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Animation::reset(AnimState &state, const NifOsg::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback)
|
|
|
|
|
{
|
|
|
|
|
// Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two
|
|
|
|
|
// separate walkforward keys, and the last one is supposed to be used.
|
|
|
|
|
NifOsg::TextKeyMap::const_reverse_iterator groupend(keys.rbegin());
|
|
|
|
|
for(;groupend != keys.rend();++groupend)
|
|
|
|
|
{
|
|
|
|
|
if(groupend->second.compare(0, groupname.size(), groupname) == 0 &&
|
|
|
|
|
groupend->second.compare(groupname.size(), 2, ": ") == 0)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string starttag = groupname+": "+start;
|
|
|
|
|
NifOsg::TextKeyMap::const_reverse_iterator startkey(groupend);
|
|
|
|
|
while(startkey != keys.rend() && startkey->second != starttag)
|
|
|
|
|
++startkey;
|
|
|
|
|
if(startkey == keys.rend() && start == "loop start")
|
|
|
|
|
{
|
|
|
|
|
starttag = groupname+": start";
|
|
|
|
|
startkey = groupend;
|
|
|
|
|
while(startkey != keys.rend() && startkey->second != starttag)
|
|
|
|
|
++startkey;
|
|
|
|
|
}
|
|
|
|
|
if(startkey == keys.rend())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const std::string stoptag = groupname+": "+stop;
|
|
|
|
|
NifOsg::TextKeyMap::const_reverse_iterator stopkey(groupend);
|
|
|
|
|
while(stopkey != keys.rend()
|
|
|
|
|
// We have to ignore extra garbage at the end.
|
|
|
|
|
// The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop".
|
|
|
|
|
// Why, just why? :(
|
|
|
|
|
&& (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag))
|
|
|
|
|
++stopkey;
|
|
|
|
|
if(stopkey == keys.rend())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if(startkey->first > stopkey->first)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
state.mStartTime = startkey->first;
|
|
|
|
|
if (loopfallback)
|
|
|
|
|
{
|
|
|
|
|
state.mLoopStartTime = startkey->first;
|
|
|
|
|
state.mLoopStopTime = stopkey->first;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
state.mLoopStartTime = startkey->first;
|
|
|
|
|
state.mLoopStopTime = std::numeric_limits<float>::max();
|
|
|
|
|
}
|
|
|
|
|
state.mStopTime = stopkey->first;
|
|
|
|
|
|
|
|
|
|
state.mTime = state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint);
|
|
|
|
|
|
|
|
|
|
// mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation
|
|
|
|
|
// (see handleTextKey). But if startpoint is already past these keys, we need to assign them now.
|
|
|
|
|
if(state.mTime > state.mStartTime)
|
|
|
|
|
{
|
|
|
|
|
const std::string loopstarttag = groupname+": loop start";
|
|
|
|
|
const std::string loopstoptag = groupname+": loop stop";
|
|
|
|
|
|
|
|
|
|
NifOsg::TextKeyMap::const_reverse_iterator key(groupend);
|
|
|
|
|
for (; key != startkey && key != keys.rend(); ++key)
|
|
|
|
|
{
|
|
|
|
|
if (key->first > state.mTime)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (key->second == loopstarttag)
|
|
|
|
|
state.mLoopStartTime = key->first;
|
|
|
|
|
else if (key->second == loopstoptag)
|
|
|
|
|
state.mLoopStopTime = key->first;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Animation::resetActiveGroups()
|
|
|
|
|
{
|
|
|
|
|
// remove all previous external controllers from the scene graph
|
|
|
|
|
for (AnimSourceControllerMap::iterator it = mAnimSourceControllers.begin(); it != mAnimSourceControllers.end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
osg::Node* node = it->first;
|
|
|
|
|
node->removeUpdateCallback(it->second);
|
|
|
|
|
}
|
|
|
|
|
mAnimSourceControllers.clear();
|
|
|
|
|
|
|
|
|
|
mAccumCtrl = NULL;
|
|
|
|
|
|
|
|
|
|
for(size_t grp = 0;grp < sNumGroups;grp++)
|
|
|
|
|
{
|
|
|
|
|
AnimStateMap::const_iterator active = mStates.end();
|
|
|
|
|
|
|
|
|
|
AnimStateMap::const_iterator state = mStates.begin();
|
|
|
|
|
for(;state != mStates.end();++state)
|
|
|
|
|
{
|
|
|
|
|
if(!(state->second.mGroups&(1<<grp)))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if(active == mStates.end() || active->second.mPriority < state->second.mPriority)
|
|
|
|
|
active = state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mAnimationTimePtr[grp]->setAnimName((active == mStates.end()) ?
|
|
|
|
|
std::string() : active->first);
|
|
|
|
|
|
|
|
|
|
// add external controllers for the AnimSource active in this group
|
|
|
|
|
if (active != mStates.end())
|
|
|
|
|
{
|
|
|
|
|
boost::shared_ptr<AnimSource> animsrc = active->second.mSource;
|
|
|
|
|
|
|
|
|
|
for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[grp].begin(); it != animsrc->mControllerMap[grp].end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
osg::ref_ptr<osg::Node> node = mNodeMap.at(it->first); // this should not throw, we already checked for the node existing in addAnimSource
|
|
|
|
|
|
|
|
|
|
node->addUpdateCallback(it->second);
|
|
|
|
|
mAnimSourceControllers[node] = it->second;
|
|
|
|
|
|
|
|
|
|
if (grp == 0 && node == mAccumRoot)
|
|
|
|
|
mAccumCtrl = it->second;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//if(!mNonAccumRoot || mAccumulate == Ogre::Vector3(0.0f))
|
|
|
|
|
// return;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
AnimStateMap::const_iterator state = mStates.find(mAnimationTimePtr[0]->getAnimName());
|
|
|
|
|
if(state == mStates.end())
|
|
|
|
|
{
|
|
|
|
|
//if (mAccumRoot && mNonAccumRoot)
|
|
|
|
|
// mAccumRoot->setPosition(-mNonAccumRoot->getPosition()*mAccumulate);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
//if (mAccumRoot && mNonAccumCtrl)
|
|
|
|
|
// mAccumRoot->setPosition(-mNonAccumCtrl->getTranslation(state->second.mTime)*mAccumulate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Animation::changeGroups(const std::string &groupname, int groups)
|
|
|
|
|
{
|
|
|
|
|
AnimStateMap::iterator stateiter = mStates.find(groupname);
|
|
|
|
|
if(stateiter != mStates.end())
|
|
|
|
|
{
|
|
|
|
|
if(stateiter->second.mGroups != groups)
|
|
|
|
|
{
|
|
|
|
|
stateiter->second.mGroups = groups;
|
|
|
|
|
resetActiveGroups();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Animation::stopLooping(const std::string& groupname)
|
|
|
|
|
{
|
|
|
|
|
AnimStateMap::iterator stateiter = mStates.find(groupname);
|
|
|
|
|
if(stateiter != mStates.end())
|
|
|
|
|
{
|
|
|
|
|
stateiter->second.mLoopCount = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Animation::adjustSpeedMult(const std::string &groupname, float speedmult)
|
|
|
|
|
{
|
|
|
|
|
AnimStateMap::iterator state(mStates.find(groupname));
|
|
|
|
|
if(state != mStates.end())
|
|
|
|
|
state->second.mSpeedMult = speedmult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Animation::isPlaying(const std::string &groupname) const
|
|
|
|
|
{
|
|
|
|
|
AnimStateMap::const_iterator state(mStates.find(groupname));
|
|
|
|
|
if(state != mStates.end())
|
|
|
|
|
return state->second.mPlaying;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Animation::getInfo(const std::string &groupname, float *complete, float *speedmult) const
|
|
|
|
|
{
|
|
|
|
|
AnimStateMap::const_iterator iter = mStates.find(groupname);
|
|
|
|
|
if(iter == mStates.end())
|
|
|
|
|
{
|
|
|
|
|
if(complete) *complete = 0.0f;
|
|
|
|
|
if(speedmult) *speedmult = 0.0f;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(complete)
|
|
|
|
|
{
|
|
|
|
|
if(iter->second.mStopTime > iter->second.mStartTime)
|
|
|
|
|
*complete = (iter->second.mTime - iter->second.mStartTime) /
|
|
|
|
|
(iter->second.mStopTime - iter->second.mStartTime);
|
|
|
|
|
else
|
|
|
|
|
*complete = (iter->second.mPlaying ? 0.0f : 1.0f);
|
|
|
|
|
}
|
|
|
|
|
if(speedmult) *speedmult = iter->second.mSpeedMult;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float Animation::getCurrentTime(const std::string &groupname) const
|
|
|
|
|
{
|
|
|
|
|
AnimStateMap::const_iterator iter = mStates.find(groupname);
|
|
|
|
|
if(iter == mStates.end())
|
|
|
|
|
return -1.f;
|
|
|
|
|
|
|
|
|
|
return iter->second.mTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Animation::disable(const std::string &groupname)
|
|
|
|
|
{
|
|
|
|
|
AnimStateMap::iterator iter = mStates.find(groupname);
|
|
|
|
|
if(iter != mStates.end())
|
|
|
|
|
mStates.erase(iter);
|
|
|
|
|
resetActiveGroups();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float Animation::getVelocity(const std::string &groupname) const
|
|
|
|
|
{
|
|
|
|
|
return 0.f;
|
|
|
|
|
/*
|
|
|
|
|
// Look in reverse; last-inserted source has priority.
|
|
|
|
|
AnimSourceList::const_reverse_iterator animsrc(mAnimSources.rbegin());
|
|
|
|
|
for(;animsrc != mAnimSources.rend();++animsrc)
|
|
|
|
|
{
|
|
|
|
|
const NifOsg::TextKeyMap &keys = (*animsrc)->mTextKeys;
|
|
|
|
|
if(findGroupStart(keys, groupname) != keys.end())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if(animsrc == mAnimSources.rend())
|
|
|
|
|
return 0.0f;
|
|
|
|
|
|
|
|
|
|
float velocity = 0.0f;
|
|
|
|
|
const NifOsg::TextKeyMap &keys = (*animsrc)->mTextKeys;
|
|
|
|
|
const std::vector<Ogre::Controller<Ogre::Real> >&ctrls = (*animsrc)->mControllers[0];
|
|
|
|
|
for(size_t i = 0;i < ctrls.size();i++)
|
|
|
|
|
{
|
|
|
|
|
NifOsg::NodeTargetValue<Ogre::Real> *dstval;
|
|
|
|
|
dstval = static_cast<NifOgre::NodeTargetValue<Ogre::Real>*>(ctrls[i].getDestination().getPointer());
|
|
|
|
|
if(dstval->getNode() == mNonAccumRoot)
|
|
|
|
|
{
|
|
|
|
|
velocity = calcAnimVelocity(keys, dstval, mAccumulate, groupname);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there's no velocity, keep looking
|
|
|
|
|
if(!(velocity > 1.0f))
|
|
|
|
|
{
|
|
|
|
|
AnimSourceList::const_reverse_iterator animiter = mAnimSources.rbegin();
|
|
|
|
|
while(*animiter != *animsrc)
|
|
|
|
|
++animiter;
|
|
|
|
|
|
|
|
|
|
while(!(velocity > 1.0f) && ++animiter != mAnimSources.rend())
|
|
|
|
|
{
|
|
|
|
|
const NifOgre::TextKeyMap &keys = (*animiter)->mTextKeys;
|
|
|
|
|
const std::vector<Ogre::Controller<Ogre::Real> >&ctrls = (*animiter)->mControllers[0];
|
|
|
|
|
for(size_t i = 0;i < ctrls.size();i++)
|
|
|
|
|
{
|
|
|
|
|
NifOgre::NodeTargetValue<Ogre::Real> *dstval;
|
|
|
|
|
dstval = static_cast<NifOgre::NodeTargetValue<Ogre::Real>*>(ctrls[i].getDestination().getPointer());
|
|
|
|
|
if(dstval->getNode() == mNonAccumRoot)
|
|
|
|
|
{
|
|
|
|
|
velocity = calcAnimVelocity(keys, dstval, mAccumulate, groupname);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return velocity;
|
|
|
|
|
*/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
osg::Vec3f Animation::runAnimation(float duration)
|
|
|
|
|
{
|
|
|
|
|
osg::Vec3f movement(0.f, 0.f, 0.f);
|
|
|
|
|
AnimStateMap::iterator stateiter = mStates.begin();
|
|
|
|
|
while(stateiter != mStates.end())
|
|
|
|
|
{
|
|
|
|
|
AnimState &state = stateiter->second;
|
|
|
|
|
const NifOsg::TextKeyMap &textkeys = state.mSource->getTextKeys();
|
|
|
|
|
NifOsg::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.mTime));
|
|
|
|
|
|
|
|
|
|
float timepassed = duration * state.mSpeedMult;
|
|
|
|
|
while(state.mPlaying)
|
|
|
|
|
{
|
|
|
|
|
float targetTime;
|
|
|
|
|
|
|
|
|
|
if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0)
|
|
|
|
|
goto handle_loop;
|
|
|
|
|
|
|
|
|
|
targetTime = state.mTime + timepassed;
|
|
|
|
|
if(textkey == textkeys.end() || textkey->first > targetTime)
|
|
|
|
|
{
|
|
|
|
|
//if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName())
|
|
|
|
|
// updatePosition(state.mTime, targetTime, movement);
|
|
|
|
|
state.mTime = std::min(targetTime, state.mStopTime);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName())
|
|
|
|
|
// updatePosition(state.mTime, textkey->first, movement);
|
|
|
|
|
state.mTime = textkey->first;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.mPlaying = (state.mTime < state.mStopTime);
|
|
|
|
|
timepassed = targetTime - state.mTime;
|
|
|
|
|
|
|
|
|
|
while(textkey != textkeys.end() && textkey->first <= state.mTime)
|
|
|
|
|
{
|
|
|
|
|
handleTextKey(state, stateiter->first, textkey, textkeys);
|
|
|
|
|
++textkey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0)
|
|
|
|
|
{
|
|
|
|
|
handle_loop:
|
|
|
|
|
state.mLoopCount--;
|
|
|
|
|
state.mTime = state.mLoopStartTime;
|
|
|
|
|
state.mPlaying = true;
|
|
|
|
|
|
|
|
|
|
textkey = textkeys.lower_bound(state.mTime);
|
|
|
|
|
while(textkey != textkeys.end() && textkey->first <= state.mTime)
|
|
|
|
|
{
|
|
|
|
|
handleTextKey(state, stateiter->first, textkey, textkeys);
|
|
|
|
|
++textkey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(state.mTime >= state.mLoopStopTime)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(timepassed <= 0.0f)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!state.mPlaying && state.mAutoDisable)
|
|
|
|
|
{
|
|
|
|
|
mStates.erase(stateiter++);
|
|
|
|
|
|
|
|
|
|
resetActiveGroups();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
++stateiter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateEffects(duration);
|
|
|
|
|
|
|
|
|
|
return osg::Vec3f();
|
|
|
|
|
return movement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Animation::setObjectRoot(const std::string &model)
|
|
|
|
@ -117,7 +780,14 @@ namespace MWRender
|
|
|
|
|
mObjectRoot->getParent(0)->removeChild(mObjectRoot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mNodeMap.clear();
|
|
|
|
|
mAnimSourceControllers.clear();
|
|
|
|
|
|
|
|
|
|
mObjectRoot = mResourceSystem->getSceneManager()->createInstance(model, mInsert);
|
|
|
|
|
|
|
|
|
|
NodeMapVisitor visitor;
|
|
|
|
|
mObjectRoot->accept(visitor);
|
|
|
|
|
mNodeMap = visitor.getNodeMap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
osg::Group* Animation::getObjectRoot()
|
|
|
|
@ -318,6 +988,15 @@ namespace MWRender
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float Animation::AnimationTime::getValue(osg::NodeVisitor*)
|
|
|
|
|
{
|
|
|
|
|
// FIXME: hold a pointer instead of searching every frame
|
|
|
|
|
AnimStateMap::const_iterator iter = mAnimation->mStates.find(mAnimationName);
|
|
|
|
|
if(iter != mAnimation->mStates.end())
|
|
|
|
|
return iter->second.mTime;
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float EffectAnimationTime::getValue(osg::NodeVisitor*)
|
|
|
|
|
{
|
|
|
|
|
return mTime;
|
|
|
|
|