openmw-tes3coop/apps/openmw/mwrender/animation.cpp
Chris Robinson 6605aa7dec Create three bone groups
This still won't work as expected. Currently there is only one priority level,
and only one animation state per priority level can be active at a time.

It's close, though.
2013-05-10 20:45:09 -07:00

683 lines
20 KiB
C++

#include "animation.hpp"
#include <OgreSkeletonManager.h>
#include <OgreSkeletonInstance.h>
#include <OgreEntity.h>
#include <OgreSubEntity.h>
#include <OgreParticleSystem.h>
#include <OgreBone.h>
#include <OgreSubMesh.h>
#include <OgreSceneManager.h>
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwmechanics/character.hpp"
namespace MWRender
{
Ogre::Real Animation::AnimationValue::getValue() const
{
AnimStateMap::const_iterator iter = mAnimation->mStates.find(mAnimationName);
if(iter != mAnimation->mStates.end())
return iter->second.mTime;
return 0.0f;
}
void Animation::AnimationValue::setValue(Ogre::Real)
{
}
void Animation::destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects)
{
for(size_t i = 0;i < objects.mParticles.size();i++)
sceneMgr->destroyParticleSystem(objects.mParticles[i]);
for(size_t i = 0;i < objects.mEntities.size();i++)
sceneMgr->destroyEntity(objects.mEntities[i]);
objects.mControllers.clear();
objects.mParticles.clear();
objects.mEntities.clear();
objects.mSkelBase = NULL;
}
Animation::Animation(const MWWorld::Ptr &ptr)
: mPtr(ptr)
, mInsert(NULL)
, mSkelBase(NULL)
, mAccumRoot(NULL)
, mNonAccumRoot(NULL)
, mNonAccumCtrl(NULL)
, mAccumulate(0.0f)
, mAnimVelocity(0.0f)
, mAnimSpeedMult(1.0f)
{
for(size_t i = 0;i < sNumGroups;i++)
mAnimationValuePtr[i].bind(OGRE_NEW AnimationValue(this));
}
Animation::~Animation()
{
if(mInsert)
{
mAnimSources.clear();
Ogre::SceneManager *sceneMgr = mInsert->getCreator();
destroyObjectList(sceneMgr, mObjectRoot);
}
}
void Animation::setObjectRoot(Ogre::SceneNode *node, const std::string &model, bool baseonly)
{
OgreAssert(!mInsert, "Object already has a root!");
mInsert = node->createChildSceneNode();
std::string mdlname = Misc::StringUtils::lowerCase(model);
std::string::size_type p = mdlname.rfind('\\');
if(p == std::string::npos)
p = mdlname.rfind('/');
if(p != std::string::npos)
mdlname.insert(mdlname.begin()+p+1, 'x');
else
mdlname.insert(mdlname.begin(), 'x');
if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(mdlname))
{
mdlname = model;
Misc::StringUtils::toLower(mdlname);
}
mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) :
NifOgre::Loader::createObjectBase(mInsert, mdlname));
if(mObjectRoot.mSkelBase)
{
mSkelBase = mObjectRoot.mSkelBase;
Ogre::AnimationStateSet *aset = mObjectRoot.mSkelBase->getAllAnimationStates();
Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator();
while(asiter.hasMoreElements())
{
Ogre::AnimationState *state = asiter.getNext();
state->setEnabled(false);
state->setLoop(false);
}
// Set the bones as manually controlled since we're applying the
// transformations manually
Ogre::SkeletonInstance *skelinst = mObjectRoot.mSkelBase->getSkeleton();
Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator();
while(boneiter.hasMoreElements())
boneiter.getNext()->setManuallyControlled(true);
}
for(size_t i = 0;i < mObjectRoot.mControllers.size();i++)
{
if(mObjectRoot.mControllers[i].getSource().isNull())
mObjectRoot.mControllers[i].setSource(mAnimationValuePtr[0]);
}
}
void Animation::setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue)
{
for(size_t i = 0;i < objlist.mEntities.size();i++)
{
Ogre::Entity *ent = objlist.mEntities[i];
if(visflags != 0)
ent->setVisibilityFlags(visflags);
for(unsigned int j = 0;j < ent->getNumSubEntities();++j)
{
Ogre::SubEntity* subEnt = ent->getSubEntity(j);
subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? transqueue : solidqueue);
}
}
for(size_t i = 0;i < objlist.mParticles.size();i++)
{
Ogre::ParticleSystem *part = objlist.mParticles[i];
if(visflags != 0)
part->setVisibilityFlags(visflags);
// TODO: Check particle material for actual transparency
part->setRenderQueueGroup(transqueue);
}
}
size_t Animation::detectAnimGroup(const Ogre::Node *node)
{
static const char sGroupRoots[sNumGroups][32] = {
"", /* Lower body / character root */
"Bip01 Spine1", /* Upper body */
"Bip01 L Clavicle", /* Left arm */
};
while(node)
{
const Ogre::String &name = node->getName();
for(size_t i = 1;i < sNumGroups;i++)
{
if(name == sGroupRoots[i])
return i;
}
node = node->getParent();
}
return 0;
}
void Animation::addAnimSource(const std::string &model)
{
OgreAssert(mInsert, "Object is missing a root!");
if(!mSkelBase)
return;
std::string kfname = Misc::StringUtils::lowerCase(model);
std::string::size_type p = kfname.rfind('\\');
if(p == std::string::npos)
p = kfname.rfind('/');
if(p != std::string::npos)
kfname.insert(kfname.begin()+p+1, 'x');
else
kfname.insert(kfname.begin(), 'x');
if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0)
kfname.replace(kfname.size()-4, 4, ".kf");
if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(kfname))
return;
std::vector<Ogre::Controller<Ogre::Real> > ctrls;
mAnimSources.push_back(AnimSource());
NifOgre::Loader::createKfControllers(mSkelBase, kfname, mAnimSources.back().mTextKeys, ctrls);
if(mAnimSources.back().mTextKeys.size() == 0 || ctrls.size() == 0)
{
mAnimSources.pop_back();
return;
}
std::vector<Ogre::Controller<Ogre::Real> > *grpctrls = mAnimSources.back().mControllers;
NifOgre::NodeTargetValue<Ogre::Real> *dstval;
for(size_t i = 0;i < ctrls.size();i++)
{
dstval = static_cast<NifOgre::NodeTargetValue<Ogre::Real>*>(ctrls[i].getDestination().getPointer());
size_t grp = detectAnimGroup(dstval->getNode());
if(!mAccumRoot && grp == 0)
{
mAccumRoot = mInsert;
mNonAccumRoot = dstval->getNode();
}
ctrls[i].setSource(mAnimationValuePtr[grp]);
grpctrls[grp].push_back(ctrls[i]);
}
}
void Animation::clearAnimSources()
{
mStates.clear();
for(size_t i = 0;i < sNumGroups;i++)
mAnimationValuePtr[i]->setAnimName(std::string());
mNonAccumCtrl = NULL;
mAnimVelocity = 0.0f;
mAccumRoot = NULL;
mNonAccumRoot = NULL;
mAnimSources.clear();
}
Ogre::Node *Animation::getNode(const std::string &name)
{
if(mSkelBase)
{
Ogre::SkeletonInstance *skel = mSkelBase->getSkeleton();
if(skel->hasBone(name))
return skel->getBone(name);
}
return NULL;
}
NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::TextKeyMap &keys, const std::string &groupname)
{
NifOgre::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;
}
bool Animation::hasAnimation(const std::string &anim)
{
AnimSourceList::const_iterator iter(mAnimSources.begin());
for(;iter != mAnimSources.end();iter++)
{
const NifOgre::TextKeyMap &keys = iter->mTextKeys;
if(findGroupStart(keys, anim) != keys.end())
return true;
}
return false;
}
void Animation::setAccumulation(const Ogre::Vector3 &accum)
{
mAccumulate = accum;
}
void Animation::setSpeed(float speed)
{
mAnimSpeedMult = 1.0f;
if(mAnimVelocity > 1.0f && speed > 0.0f)
mAnimSpeedMult = speed / mAnimVelocity;
}
void Animation::updatePtr(const MWWorld::Ptr &ptr)
{
mPtr = ptr;
}
float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::NodeTargetValue<Ogre::Real> *nonaccumctrl, const Ogre::Vector3 &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;
NifOgre::TextKeyMap::const_iterator keyiter(keys.begin());
while(keyiter != keys.end())
{
if(keyiter->second == start || keyiter->second == loopstart)
starttime = keyiter->first;
else if(keyiter->second == loopstop || keyiter->second == stop)
{
stoptime = keyiter->first;
break;
}
keyiter++;
}
if(stoptime > starttime)
{
Ogre::Vector3 startpos = nonaccumctrl->getTranslation(starttime) * accum;
Ogre::Vector3 endpos = nonaccumctrl->getTranslation(stoptime) * accum;
return startpos.distance(endpos) / (stoptime-starttime);
}
return 0.0f;
}
static void updateBoneTree(const Ogre::SkeletonInstance *skelsrc, Ogre::Bone *bone)
{
if(skelsrc->hasBone(bone->getName()))
{
Ogre::Bone *srcbone = skelsrc->getBone(bone->getName());
if(!srcbone->getParent() || !bone->getParent())
{
bone->setOrientation(srcbone->getOrientation());
bone->setPosition(srcbone->getPosition());
bone->setScale(srcbone->getScale());
}
else
{
bone->_setDerivedOrientation(srcbone->_getDerivedOrientation());
bone->_setDerivedPosition(srcbone->_getDerivedPosition());
bone->setScale(Ogre::Vector3::UNIT_SCALE);
}
}
else
{
// No matching bone in the source. Make sure it stays properly offset
// from its parent.
bone->resetToInitialState();
}
Ogre::Node::ChildNodeIterator boneiter = bone->getChildIterator();
while(boneiter.hasMoreElements())
updateBoneTree(skelsrc, static_cast<Ogre::Bone*>(boneiter.getNext()));
}
void Animation::updateSkeletonInstance(const Ogre::SkeletonInstance *skelsrc, Ogre::SkeletonInstance *skel)
{
Ogre::Skeleton::BoneIterator boneiter = skel->getRootBoneIterator();
while(boneiter.hasMoreElements())
updateBoneTree(skelsrc, boneiter.getNext());
}
void Animation::updatePosition(float oldtime, float newtime, Ogre::Vector3 &position)
{
/* Get the non-accumulation root's difference from the last update, and move the position
* accordingly.
*/
Ogre::Vector3 off = mNonAccumCtrl->getTranslation(newtime)*mAccumulate;
position += off - mNonAccumCtrl->getTranslation(oldtime)*mAccumulate;
/* Translate the accumulation root back to compensate for the move. */
mAccumRoot->setPosition(-off);
}
bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint)
{
std::string tag = groupname+": "+start;
NifOgre::TextKeyMap::const_iterator startkey(keys.begin());
while(startkey != keys.end() && startkey->second != tag)
startkey++;
if(startkey == keys.end() && start == "loop start")
{
tag = groupname+": start";
startkey = keys.begin();
while(startkey != keys.end() && startkey->second != tag)
startkey++;
}
if(startkey == keys.end())
return false;
tag = groupname+": "+stop;
NifOgre::TextKeyMap::const_iterator stopkey(startkey);
while(stopkey != keys.end() && stopkey->second != tag)
stopkey++;
if(stopkey == keys.end())
return false;
if(startkey == stopkey)
return false;
state.mStartKey = startkey;
state.mLoopStartKey = startkey;
state.mStopKey = stopkey;
state.mNextKey = startkey;
state.mTime = state.mStartKey->first + ((state.mStopKey->first - state.mStartKey->first) * startpoint);
tag = groupname+": loop start";
while(state.mNextKey->first <= state.mTime && state.mNextKey != state.mStopKey)
{
if(state.mNextKey->second == tag)
state.mLoopStartKey = state.mNextKey;
state.mNextKey++;
}
return true;
}
bool Animation::doLoop(AnimState &state)
{
if(state.mLoopCount == 0)
return false;
state.mLoopCount--;
state.mTime = state.mLoopStartKey->first;
state.mNextKey = state.mLoopStartKey;
state.mNextKey++;
state.mPlaying = true;
return true;
}
bool Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key)
{
float time = key->first;
const std::string &evt = key->second;
if(evt.compare(0, 7, "sound: ") == 0)
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f);
return true;
}
if(evt.compare(0, 10, "soundgen: ") == 0)
{
// FIXME: Lookup the SoundGen (SNDG) for the specified sound that corresponds
// to this actor type
return true;
}
if(evt.compare(0, groupname.size(), groupname) != 0 ||
evt.compare(groupname.size(), 2, ": ") != 0)
{
// Not ours, skip it
return true;
}
size_t off = groupname.size()+2;
size_t len = evt.size() - off;
if(evt.compare(off, len, "start") == 0 || evt.compare(off, len, "loop start") == 0)
{
state.mLoopStartKey = key;
return true;
}
if(evt.compare(off, len, "loop stop") == 0 || evt.compare(off, len, "stop") == 0)
{
if(doLoop(state))
{
if(state.mTime >= time)
return false;
}
return true;
}
std::cerr<< "Unhandled animation textkey: "<<evt <<std::endl;
return true;
}
bool Animation::play(const std::string &groupname, Priority priority, int groups, const std::string &start, const std::string &stop, float startpoint, size_t loops)
{
if(!mSkelBase)
return false;
if(groupname.empty())
return resetActiveGroups();
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;
return resetActiveGroups();
}
/* Look in reverse; last-inserted source has priority. */
AnimSourceList::reverse_iterator iter(mAnimSources.rbegin());
for(;iter != mAnimSources.rend();iter++)
{
AnimState state;
if(reset(state, iter->mTextKeys, groupname, start, stop, startpoint))
{
state.mSource = &*iter;
state.mLoopCount = loops;
state.mPlaying = true;
state.mPriority = priority;
state.mGroups = groups;
mStates[groupname] = state;
break;
}
}
if(iter == mAnimSources.rend())
std::cerr<< "Failed to find animation "<<groupname <<std::endl;
return resetActiveGroups();
}
bool Animation::resetActiveGroups()
{
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;
}
mAnimationValuePtr[grp]->setAnimName((active == mStates.end()) ?
std::string() : active->first);
}
mNonAccumCtrl = NULL;
mAnimVelocity = 0.0f;
if(!mNonAccumRoot || mAccumulate == Ogre::Vector3(0.0f))
return false;
AnimStateMap::const_iterator state = mStates.find(mAnimationValuePtr[0]->getAnimName());
if(state == mStates.end())
return false;
bool ismoving = false;
const AnimSource *animsrc = state->second.mSource;
const NifOgre::TextKeyMap &keys = animsrc->mTextKeys;
const std::vector<Ogre::Controller<Ogre::Real> >&ctrls = animsrc->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)
{
mAnimVelocity = calcAnimVelocity(keys, dstval, mAccumulate, state->first);
ismoving = (mAnimVelocity > 1.0f);
mNonAccumCtrl = dstval;
break;
}
}
// If there's no velocity, keep looking
while(!(mAnimVelocity > 1.0f) && animsrc-- != &mAnimSources[0])
{
const NifOgre::TextKeyMap &keys = animsrc->mTextKeys;
const std::vector<Ogre::Controller<Ogre::Real> >&ctrls = animsrc->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)
{
mAnimVelocity = calcAnimVelocity(keys, dstval, mAccumulate, state->first);
break;
}
}
}
return ismoving;
}
bool Animation::getInfo(const std::string &groupname, float *complete, std::string *start, std::string *stop) const
{
AnimStateMap::const_iterator iter = mStates.find(groupname);
if(iter == mStates.end())
{
if(complete) *complete = 0.0f;
if(start) *start = "";
if(stop) *stop = "";
return false;
}
if(complete) *complete = (iter->second.mTime - iter->second.mStartKey->first) /
(iter->second.mStopKey->first - iter->second.mStartKey->first);
if(start) *start = iter->second.mStartKey->second.substr(groupname.size()+2);
if(stop) *stop = iter->second.mStopKey->second.substr(groupname.size()+2);
return true;
}
Ogre::Vector3 Animation::runAnimation(float duration)
{
Ogre::Vector3 movement(0.0f);
duration *= mAnimSpeedMult;
AnimStateMap::iterator stateiter = mStates.begin();
for(;stateiter != mStates.end();stateiter++)
{
AnimState &state = stateiter->second;
float timepassed = duration;
while(state.mPlaying)
{
float targetTime = state.mTime + timepassed;
if(state.mNextKey->first > targetTime)
{
if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName())
updatePosition(state.mTime, targetTime, movement);
state.mTime = targetTime;
break;
}
NifOgre::TextKeyMap::const_iterator key(state.mNextKey++);
if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName())
updatePosition(state.mTime, key->first, movement);
state.mTime = key->first;
state.mPlaying = (key != state.mStopKey);
timepassed = targetTime - state.mTime;
if(!handleTextKey(state, stateiter->first, key))
break;
}
}
for(size_t i = 0;i < mObjectRoot.mControllers.size();i++)
mObjectRoot.mControllers[i].update();
// Apply group controllers
for(size_t grp = 0;grp < sNumGroups;grp++)
{
const std::string &name = mAnimationValuePtr[grp]->getAnimName();
if(!name.empty() && (stateiter=mStates.find(name)) != mStates.end())
{
AnimSource *src = stateiter->second.mSource;
for(size_t i = 0;i < src->mControllers[grp].size();i++)
src->mControllers[grp][i].update();
}
}
if(mSkelBase)
{
// HACK: Dirty the animation state set so that Ogre will apply the
// transformations to entities this skeleton instance is shared with.
mSkelBase->getAllAnimationStates()->_notifyDirty();
}
return movement;
}
void Animation::showWeapons(bool showWeapon)
{
}
}