forked from teamnwah/openmw-tes3coop
617158afcd
Objects attached to actors (shirts, robes, etc) do not require the same node hierarchy as the character root. So to ensure proper placement, we need to set the bone target's derived transformation using the source bone's derived transformation (which in turn means we need to work up from the root, to ensure the bone's parents are properly placed).
426 lines
14 KiB
C++
426 lines
14 KiB
C++
#include "animation.hpp"
|
|
|
|
#include <OgreSkeletonManager.h>
|
|
#include <OgreSkeletonInstance.h>
|
|
#include <OgreEntity.h>
|
|
#include <OgreBone.h>
|
|
#include <OgreSubMesh.h>
|
|
#include <OgreSceneManager.h>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
|
|
#include "../mwmechanics/character.hpp"
|
|
|
|
namespace MWRender
|
|
{
|
|
|
|
Animation::Animation(const MWWorld::Ptr &ptr)
|
|
: mPtr(ptr)
|
|
, mController(NULL)
|
|
, mInsert(NULL)
|
|
, mAccumRoot(NULL)
|
|
, mNonAccumRoot(NULL)
|
|
, mAccumulate(Ogre::Vector3::ZERO)
|
|
, mLastPosition(0.0f)
|
|
, mCurrentKeys(NULL)
|
|
, mCurrentAnim(NULL)
|
|
, mCurrentTime(0.0f)
|
|
, mPlaying(false)
|
|
, mLooping(false)
|
|
, mAnimVelocity(0.0f)
|
|
, mAnimSpeedMult(1.0f)
|
|
{
|
|
}
|
|
|
|
Animation::~Animation()
|
|
{
|
|
if(mInsert)
|
|
{
|
|
Ogre::SceneManager *sceneMgr = mInsert->getCreator();
|
|
for(size_t i = 0;i < mEntityList.mEntities.size();i++)
|
|
sceneMgr->destroyEntity(mEntityList.mEntities[i]);
|
|
}
|
|
mEntityList.mEntities.clear();
|
|
mEntityList.mSkelBase = NULL;
|
|
}
|
|
|
|
|
|
Ogre::Bone *Animation::insertSkeletonSource(const std::string &name)
|
|
{
|
|
Ogre::SkeletonManager &skelMgr = Ogre::SkeletonManager::getSingleton();
|
|
Ogre::SkeletonPtr skel = skelMgr.getByName(name);
|
|
if(skel.isNull())
|
|
{
|
|
NifOgre::Loader::createSkeleton(name);
|
|
skel = skelMgr.getByName(name);
|
|
if(skel.isNull())
|
|
{
|
|
std::cerr<< "Failed to get skeleton source "<<name <<std::endl;
|
|
return NULL;
|
|
}
|
|
}
|
|
skel->touch();
|
|
mSkeletonSources.push_back(skel);
|
|
|
|
Ogre::Skeleton::BoneIterator boneiter = skel->getBoneIterator();
|
|
while(boneiter.hasMoreElements())
|
|
{
|
|
Ogre::Bone *bone = boneiter.getNext();
|
|
Ogre::UserObjectBindings &bindings = bone->getUserObjectBindings();
|
|
const Ogre::Any &data = bindings.getUserAny(NifOgre::sTextKeyExtraDataID);
|
|
if(data.isEmpty() || !Ogre::any_cast<bool>(data))
|
|
continue;
|
|
|
|
for(int i = 0;i < skel->getNumAnimations();i++)
|
|
{
|
|
Ogre::Animation *anim = skel->getAnimation(i);
|
|
const Ogre::Any &groupdata = bindings.getUserAny(std::string(NifOgre::sTextKeyExtraDataID)+
|
|
"@"+anim->getName());
|
|
if(!groupdata.isEmpty())
|
|
mTextKeys[anim->getName()] = Ogre::any_cast<NifOgre::TextKeyMap>(groupdata);
|
|
}
|
|
|
|
return bone;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void Animation::createEntityList(Ogre::SceneNode *node, const std::string &model)
|
|
{
|
|
mInsert = node->createChildSceneNode();
|
|
assert(mInsert);
|
|
|
|
mEntityList = NifOgre::Loader::createEntities(mInsert, model);
|
|
if(mEntityList.mSkelBase)
|
|
{
|
|
Ogre::AnimationStateSet *aset = mEntityList.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 (needed if we want to apply an animation
|
|
// from one skeleton onto another).
|
|
Ogre::SkeletonInstance *skelinst = mEntityList.mSkelBase->getSkeleton();
|
|
Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator();
|
|
while(boneiter.hasMoreElements())
|
|
boneiter.getNext()->setManuallyControlled(true);
|
|
|
|
Ogre::Bone *bone = insertSkeletonSource(skelinst->getName());
|
|
if(!bone)
|
|
{
|
|
for(std::vector<Ogre::SkeletonPtr>::const_iterator iter(mSkeletonSources.begin());
|
|
!bone && iter != mSkeletonSources.end();iter++)
|
|
{
|
|
Ogre::Skeleton::BoneIterator boneiter = (*iter)->getBoneIterator();
|
|
while(boneiter.hasMoreElements())
|
|
{
|
|
bone = boneiter.getNext();
|
|
Ogre::UserObjectBindings &bindings = bone->getUserObjectBindings();
|
|
const Ogre::Any &data = bindings.getUserAny(NifOgre::sTextKeyExtraDataID);
|
|
if(!data.isEmpty() && Ogre::any_cast<bool>(data))
|
|
break;
|
|
|
|
bone = NULL;
|
|
}
|
|
}
|
|
}
|
|
if(bone)
|
|
{
|
|
mAccumRoot = mInsert;
|
|
mNonAccumRoot = skelinst->getBone(bone->getName());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool Animation::hasAnimation(const std::string &anim)
|
|
{
|
|
for(std::vector<Ogre::SkeletonPtr>::const_iterator iter(mSkeletonSources.begin());iter != mSkeletonSources.end();iter++)
|
|
{
|
|
if((*iter)->hasAnimation(anim))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void Animation::setController(MWMechanics::CharacterController *controller)
|
|
{
|
|
mController = controller;
|
|
}
|
|
|
|
|
|
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::setLooping(bool loop)
|
|
{
|
|
mLooping = loop;
|
|
}
|
|
|
|
|
|
void Animation::applyAnimation(const Ogre::Animation *anim, float time, Ogre::SkeletonInstance *skel)
|
|
{
|
|
Ogre::TimeIndex timeindex = anim->_getTimeIndex(time);
|
|
Ogre::Animation::NodeTrackIterator tracks = anim->getNodeTrackIterator();
|
|
while(tracks.hasMoreElements())
|
|
{
|
|
Ogre::NodeAnimationTrack *track = tracks.getNext();
|
|
const Ogre::String &targetname = track->getAssociatedNode()->getName();
|
|
if(!skel->hasBone(targetname))
|
|
continue;
|
|
Ogre::Bone *bone = skel->getBone(targetname);
|
|
bone->setOrientation(Ogre::Quaternion::IDENTITY);
|
|
bone->setPosition(Ogre::Vector3::ZERO);
|
|
bone->setScale(Ogre::Vector3::UNIT_SCALE);
|
|
track->applyToNode(bone, timeindex);
|
|
}
|
|
|
|
// HACK: Dirty the animation state set so that Ogre will apply the
|
|
// transformations to entities this skeleton instance is shared with.
|
|
mEntityList.mSkelBase->getAllAnimationStates()->_notifyDirty();
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
|
|
Ogre::Vector3 Animation::updatePosition(float time)
|
|
{
|
|
if(mLooping)
|
|
mCurrentTime = std::fmod(std::max(time, 0.0f), mCurrentAnim->getLength());
|
|
else
|
|
mCurrentTime = std::min(mCurrentAnim->getLength(), std::max(time, 0.0f));
|
|
applyAnimation(mCurrentAnim, mCurrentTime, mEntityList.mSkelBase->getSkeleton());
|
|
|
|
Ogre::Vector3 posdiff = Ogre::Vector3::ZERO;
|
|
if(mNonAccumRoot)
|
|
{
|
|
/* Get the non-accumulation root's difference from the last update. */
|
|
posdiff = (mNonAccumRoot->getPosition() - mLastPosition) * mAccumulate;
|
|
|
|
/* Translate the accumulation root back to compensate for the move. */
|
|
mLastPosition += posdiff;
|
|
mAccumRoot->setPosition(-mLastPosition);
|
|
}
|
|
return posdiff;
|
|
}
|
|
|
|
void Animation::reset(const std::string &marker)
|
|
{
|
|
mNextKey = mCurrentKeys->begin();
|
|
while(mNextKey != mCurrentKeys->end() && mNextKey->second != marker)
|
|
mNextKey++;
|
|
|
|
if(mNextKey != mCurrentKeys->end())
|
|
mCurrentTime = mNextKey->first;
|
|
else
|
|
{
|
|
mNextKey = mCurrentKeys->begin();
|
|
mCurrentTime = 0.0f;
|
|
}
|
|
|
|
if(mNonAccumRoot)
|
|
{
|
|
const Ogre::NodeAnimationTrack *track = 0;
|
|
Ogre::Animation::NodeTrackIterator trackiter = mCurrentAnim->getNodeTrackIterator();
|
|
while(!track && trackiter.hasMoreElements())
|
|
{
|
|
const Ogre::NodeAnimationTrack *cur = trackiter.getNext();
|
|
if(cur->getAssociatedNode()->getName() == mNonAccumRoot->getName())
|
|
track = cur;
|
|
}
|
|
|
|
if(track)
|
|
{
|
|
Ogre::TransformKeyFrame kf(0, mCurrentTime);
|
|
track->getInterpolatedKeyFrame(mCurrentAnim->_getTimeIndex(mCurrentTime), &kf);
|
|
mLastPosition = kf.getTranslate() * mAccumulate;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Animation::play(const std::string &groupname, const std::string &start, bool loop)
|
|
{
|
|
try {
|
|
bool found = false;
|
|
/* Look in reverse; last-inserted source has priority. */
|
|
for(std::vector<Ogre::SkeletonPtr>::const_reverse_iterator iter(mSkeletonSources.rbegin());iter != mSkeletonSources.rend();iter++)
|
|
{
|
|
if((*iter)->hasAnimation(groupname))
|
|
{
|
|
mCurrentAnim = (*iter)->getAnimation(groupname);
|
|
mCurrentKeys = &mTextKeys[groupname];
|
|
mAnimVelocity = 0.0f;
|
|
|
|
if(mNonAccumRoot)
|
|
{
|
|
const Ogre::NodeAnimationTrack *track = 0;
|
|
|
|
Ogre::Animation::NodeTrackIterator trackiter = mCurrentAnim->getNodeTrackIterator();
|
|
while(!track && trackiter.hasMoreElements())
|
|
{
|
|
const Ogre::NodeAnimationTrack *cur = trackiter.getNext();
|
|
if(cur->getAssociatedNode()->getName() == mNonAccumRoot->getName())
|
|
track = cur;
|
|
}
|
|
|
|
if(track && track->getNumKeyFrames() > 1)
|
|
{
|
|
float loopstarttime = 0.0f;
|
|
float loopstoptime = mCurrentAnim->getLength();
|
|
NifOgre::TextKeyMap::const_iterator keyiter = mCurrentKeys->begin();
|
|
while(keyiter != mCurrentKeys->end())
|
|
{
|
|
if(keyiter->second == "loop start")
|
|
loopstarttime = keyiter->first;
|
|
else if(keyiter->second == "loop stop")
|
|
{
|
|
loopstoptime = keyiter->first;
|
|
break;
|
|
}
|
|
keyiter++;
|
|
}
|
|
|
|
if(loopstoptime > loopstarttime)
|
|
{
|
|
Ogre::TransformKeyFrame startkf(0, loopstarttime);
|
|
Ogre::TransformKeyFrame endkf(0, loopstoptime);
|
|
|
|
track->getInterpolatedKeyFrame(mCurrentAnim->_getTimeIndex(loopstarttime), &startkf);
|
|
track->getInterpolatedKeyFrame(mCurrentAnim->_getTimeIndex(loopstoptime), &endkf);
|
|
|
|
mAnimVelocity = startkf.getTranslate().distance(endkf.getTranslate()) /
|
|
(loopstoptime-loopstarttime);
|
|
}
|
|
}
|
|
}
|
|
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found)
|
|
throw std::runtime_error("Failed to find animation "+groupname);
|
|
|
|
reset(start);
|
|
mPlaying = true;
|
|
mLooping = loop;
|
|
}
|
|
catch(std::exception &e) {
|
|
std::cerr<< e.what() <<std::endl;
|
|
}
|
|
}
|
|
|
|
Ogre::Vector3 Animation::runAnimation(float timepassed)
|
|
{
|
|
Ogre::Vector3 movement = Ogre::Vector3::ZERO;
|
|
|
|
timepassed *= mAnimSpeedMult;
|
|
while(mCurrentAnim && mPlaying)
|
|
{
|
|
float targetTime = mCurrentTime + timepassed;
|
|
if(mNextKey == mCurrentKeys->end() || mNextKey->first > targetTime)
|
|
{
|
|
movement += updatePosition(targetTime);
|
|
mPlaying = (mLooping || mCurrentAnim->getLength() >= targetTime);
|
|
break;
|
|
}
|
|
|
|
float time = mNextKey->first;
|
|
const std::string &evt = mNextKey->second;
|
|
mNextKey++;
|
|
|
|
movement += updatePosition(time);
|
|
timepassed = targetTime - time;
|
|
|
|
if(evt == "start" || evt == "loop start")
|
|
{
|
|
/* Do nothing */
|
|
continue;
|
|
}
|
|
if(evt == "loop stop")
|
|
{
|
|
if(mLooping)
|
|
{
|
|
reset("loop start");
|
|
if(mCurrentTime >= time)
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if(evt == "stop")
|
|
{
|
|
if(mLooping)
|
|
{
|
|
reset("loop start");
|
|
if(mCurrentTime >= time)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
mPlaying = false;
|
|
if(mController)
|
|
mController->markerEvent(time, evt);
|
|
}
|
|
continue;
|
|
}
|
|
if(mController)
|
|
mController->markerEvent(time, evt);
|
|
}
|
|
|
|
return movement;
|
|
}
|
|
|
|
}
|