mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 04:49:54 +00:00
3e9b0a333c
Animations that move a character may do so either visually or physically. An axis' accumuluation value specifies whether the movement is visual (0) or physical (1). Idle animations, for instance, typically don't physically move a character, while death animations may physically move them along the X and Y planes, but not along Z (the vertical movement is purely visual).
236 lines
6.9 KiB
C++
236 lines
6.9 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)
|
|
, mStartPosition(0.0f)
|
|
, mLastPosition(0.0f)
|
|
, mCurrentKeys(NULL)
|
|
, mAnimState(NULL)
|
|
{
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
void Animation::createEntityList(Ogre::SceneNode *node, const std::string &model)
|
|
{
|
|
mInsert = node;
|
|
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);
|
|
}
|
|
|
|
Ogre::SkeletonInstance *skelinst = mEntityList.mSkelBase->getSkeleton();
|
|
// Would be nice if Ogre::SkeletonInstance allowed access to the 'master' Ogre::SkeletonPtr.
|
|
Ogre::SkeletonManager &skelMgr = Ogre::SkeletonManager::getSingleton();
|
|
Ogre::SkeletonPtr skel = skelMgr.getByName(skelinst->getName());
|
|
Ogre::Skeleton::BoneIterator boneiter = skel->getBoneIterator();
|
|
while(boneiter.hasMoreElements())
|
|
{
|
|
Ogre::Bone *bone = boneiter.peekNext();
|
|
const Ogre::Any &data = bone->getUserObjectBindings().getUserAny(NifOgre::sTextKeyExtraDataID);
|
|
if(!data.isEmpty())
|
|
{
|
|
mTextKeys["all"] = Ogre::any_cast<NifOgre::TextKeyMap>(data);
|
|
|
|
mAccumRoot = skelinst->getRootBone();
|
|
mAccumRoot->setManuallyControlled(true);
|
|
mNonAccumRoot = skelinst->getBone(bone->getHandle());
|
|
|
|
mStartPosition = mNonAccumRoot->getPosition();
|
|
mLastPosition = mStartPosition;
|
|
break;
|
|
}
|
|
boneiter.moveNext();
|
|
}
|
|
|
|
if(boneiter.hasMoreElements())
|
|
{
|
|
asiter = aset->getAnimationStateIterator();
|
|
while(asiter.hasMoreElements())
|
|
{
|
|
Ogre::AnimationState *state = asiter.getNext();
|
|
Ogre::UserObjectBindings &bindings = boneiter.peekNext()->getUserObjectBindings();
|
|
const Ogre::Any &data = bindings.getUserAny(std::string(NifOgre::sTextKeyExtraDataID)+"@"+state->getAnimationName());
|
|
if(!data.isEmpty())
|
|
mTextKeys[state->getAnimationName()] = Ogre::any_cast<NifOgre::TextKeyMap>(data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
std::vector<std::string> Animation::getAnimationNames()
|
|
{
|
|
std::vector<std::string> anims;
|
|
if(mEntityList.mSkelBase)
|
|
{
|
|
Ogre::AnimationStateSet *as = mEntityList.mSkelBase->getAllAnimationStates();
|
|
Ogre::AnimationStateIterator ai = as->getAnimationStateIterator();
|
|
while(ai.hasMoreElements())
|
|
anims.push_back(ai.getNext()->getAnimationName());
|
|
}
|
|
return anims;
|
|
}
|
|
|
|
|
|
void Animation::setController(MWMechanics::CharacterController *controller)
|
|
{
|
|
mController = controller;
|
|
}
|
|
|
|
|
|
void Animation::setAccumulation(const Ogre::Vector3 &accum)
|
|
{
|
|
mAccumulate = accum;
|
|
}
|
|
|
|
|
|
void Animation::updatePosition(float time)
|
|
{
|
|
mAnimState->setTimePosition(time);
|
|
|
|
if(mNonAccumRoot)
|
|
{
|
|
/* Update the animation and get the non-accumulation root's difference from the
|
|
* last update. */
|
|
mEntityList.mSkelBase->getSkeleton()->setAnimationState(*mAnimState->getParent());
|
|
Ogre::Vector3 posdiff = (mNonAccumRoot->getPosition() - mLastPosition) * mAccumulate;
|
|
|
|
/* Translate the accumulation root back to compensate for the move. */
|
|
mAccumRoot->translate(-posdiff);
|
|
mLastPosition += posdiff;
|
|
|
|
if(mPtr.isInCell())
|
|
{
|
|
/* Finally, move the object based on how much the non-accumulation root moved. */
|
|
Ogre::Vector3 newpos(mPtr.getRefData().getPosition().pos);
|
|
newpos += mInsert->getOrientation() * posdiff;
|
|
|
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
world->moveObject(mPtr, newpos.x, newpos.y, newpos.z);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Animation::resetPosition(float time)
|
|
{
|
|
mAnimState->setTimePosition(time);
|
|
|
|
mNextKey = mCurrentKeys->begin();
|
|
while(mNextKey != mCurrentKeys->end() && mNextKey->first < time)
|
|
mNextKey++;
|
|
|
|
if(mNonAccumRoot)
|
|
{
|
|
mEntityList.mSkelBase->getSkeleton()->setAnimationState(*mAnimState->getParent());
|
|
mLastPosition = mNonAccumRoot->getPosition();
|
|
mAccumRoot->setPosition(mStartPosition - mLastPosition);
|
|
}
|
|
}
|
|
|
|
|
|
float Animation::findStart(const std::string &groupname, const std::string &start)
|
|
{
|
|
mNextKey = mCurrentKeys->end();
|
|
if(mCurrentKeys->size() == 0)
|
|
return 0.0f;
|
|
|
|
if(groupname == "all")
|
|
{
|
|
mNextKey = mCurrentKeys->begin();
|
|
return 0.0f;
|
|
}
|
|
|
|
std::string startmarker = groupname+": "+start;
|
|
NifOgre::TextKeyMap::const_iterator iter;
|
|
for(iter = mCurrentKeys->begin();iter != mCurrentKeys->end();iter++)
|
|
{
|
|
if(iter->second == startmarker)
|
|
return iter->first;
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
|
|
void Animation::play(const std::string &groupname, const std::string &start)
|
|
{
|
|
try {
|
|
if(!mEntityList.mSkelBase)
|
|
throw std::runtime_error("Attempting to animate an inanimate object");
|
|
|
|
Ogre::AnimationState *newstate = mEntityList.mSkelBase->getAnimationState(groupname);
|
|
if(mAnimState)
|
|
mAnimState->setEnabled(false);
|
|
mAnimState = newstate;
|
|
mAnimState->setEnabled(true);
|
|
mCurrentKeys = &mTextKeys[groupname];
|
|
|
|
resetPosition(findStart(groupname, start));
|
|
}
|
|
catch(std::exception &e) {
|
|
std::cerr<< e.what() <<std::endl;
|
|
}
|
|
}
|
|
|
|
void Animation::runAnimation(float timepassed)
|
|
{
|
|
while(mAnimState && timepassed > 0.0f)
|
|
{
|
|
float targetTime = mAnimState->getTimePosition() + timepassed;
|
|
if(mNextKey == mCurrentKeys->end() || mNextKey->first > targetTime)
|
|
{
|
|
updatePosition(targetTime);
|
|
break;
|
|
}
|
|
|
|
const std::string &evt = mNextKey->second;
|
|
updatePosition(mNextKey->first);
|
|
timepassed = targetTime - mNextKey->first;
|
|
mNextKey++;
|
|
|
|
if(mController)
|
|
mController->markerEvent(evt);
|
|
}
|
|
}
|
|
|
|
}
|