#include "animation.hpp" #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/character.hpp" namespace MWRender { 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.mCameras.clear(); objects.mParticles.clear(); objects.mEntities.clear(); objects.mSkelBase = NULL; } Animation::Animation(const MWWorld::Ptr &ptr) : mAnimationBaseValuePtr(OGRE_NEW AnimationValue(this)) , mPtr(ptr) , mController(NULL) , mInsert(NULL) , mSkelBase(NULL) , mAccumRoot(NULL) , mNonAccumRoot(NULL) , mAccumulate(0.0f) , mLastPosition(0.0f) , mCurrentControllers(NULL) , mCurrentKeys(NULL) , mCurrentTime(0.0f) , mPlaying(false) , mLooping(false) , mNonAccumCtrl(NULL) , mAnimVelocity(0.0f) , mAnimSpeedMult(1.0f) { } Animation::~Animation() { if(mInsert) { Ogre::SceneManager *sceneMgr = mInsert->getCreator(); for(size_t i = 0;i < mObjectLists.size();i++) destroyObjectList(sceneMgr, mObjectLists[i]); mObjectLists.clear(); } } void Animation::addObjectList(Ogre::SceneNode *node, const std::string &model, bool baseonly) { if(!mInsert) { mInsert = node->createChildSceneNode(); assert(mInsert); } mObjectLists.push_back(!baseonly ? NifOgre::Loader::createObjects(mInsert, model) : NifOgre::Loader::createObjectBase(mInsert, model)); NifOgre::ObjectList &objlist = mObjectLists.back(); if(objlist.mSkelBase) { if(!mSkelBase) mSkelBase = objlist.mSkelBase; Ogre::AnimationStateSet *aset = objlist.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 = objlist.mSkelBase->getSkeleton(); Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); while(boneiter.hasMoreElements()) boneiter.getNext()->setManuallyControlled(true); Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); if(mSkelBase == objlist.mSkelBase) { if(objlist.mTextKeys.size() > 0) { mAccumRoot = mInsert; mNonAccumRoot = baseinst->getBone(objlist.mTextKeys.begin()->first); } } else { for(size_t i = 0;i < objlist.mControllers.size();i++) { NifOgre::NodeTargetValue *dstval; dstval = dynamic_cast*>(objlist.mControllers[i].getDestination().getPointer()); if(!dstval) continue; const Ogre::String &trgtname = dstval->getNode()->getName(); if(!baseinst->hasBone(trgtname)) continue; Ogre::Bone *bone = baseinst->getBone(trgtname); dstval->setNode(bone); } } } for(size_t i = 0;i < objlist.mControllers.size();i++) { if(objlist.mControllers[i].getSource().isNull()) objlist.mControllers[i].setSource(mAnimationBaseValuePtr); } if(!mCurrentControllers || (*mCurrentControllers).size() == 0) mCurrentControllers = &objlist.mControllers; } 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); } } 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; } bool Animation::hasAnimation(const std::string &anim) { for(std::vector::const_iterator iter(mObjectLists.begin());iter != mObjectLists.end();iter++) { if(iter->mSkelBase && iter->mSkelBase->hasAnimationState(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::updatePtr(const MWWorld::Ptr &ptr) { mPtr = ptr; } float Animation::calcAnimVelocity(NifOgre::NodeTargetValue *nonaccumctrl, const std::string &groupname, const NifOgre::TextKeyMap *keys) { const std::string loopstart = groupname+": loop start"; const std::string loopstop = groupname+": loop stop"; float loopstarttime = 0.0f; float loopstoptime = std::numeric_limits::max(); NifOgre::TextKeyMap::const_iterator keyiter = keys->begin(); while(keyiter != keys->end()) { if(keyiter->second == loopstart) loopstarttime = keyiter->first; else if(keyiter->second == loopstop) { loopstoptime = keyiter->first; break; } keyiter++; } if(loopstoptime > loopstarttime) { Ogre::Vector3 startpos = nonaccumctrl->getTranslation(loopstarttime); Ogre::Vector3 endpos = nonaccumctrl->getTranslation(loopstarttime); return startpos.distance(endpos) / (loopstoptime-loopstarttime); } 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(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() { Ogre::Vector3 posdiff; /* Get the non-accumulation root's difference from the last update. */ posdiff = (mNonAccumCtrl->getTranslation(mCurrentTime) - 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 &start, const std::string &stop) { std::string tag = mCurrentGroup+": "+start; mStartKey = mCurrentKeys->begin(); while(mStartKey != mCurrentKeys->end() && mStartKey->second != tag) mStartKey++; if(mStartKey == mCurrentKeys->end() && tag == "loop start") { tag = mCurrentGroup+": start"; mStartKey = mCurrentKeys->begin(); while(mStartKey != mCurrentKeys->end() && mStartKey->second != tag) mStartKey++; } if(mStartKey == mCurrentKeys->end()) mStartKey = mCurrentKeys->begin(); tag = mCurrentGroup+": "+stop; mStopKey = mStartKey; while(mStopKey != mCurrentKeys->end() && mStopKey->second != tag) mStopKey++; if(mStopKey == mCurrentKeys->end()) mStopKey--; mCurrentTime = mStartKey->first; mLoopStartKey = mStartKey; mNextKey = mStartKey; ++mNextKey; if(mNonAccumCtrl) mLastPosition = mNonAccumCtrl->getTranslation(mCurrentTime) * mAccumulate; } void Animation::doLoop() { mCurrentTime = mLoopStartKey->first; mNextKey = mLoopStartKey; ++mNextKey; if(mNonAccumCtrl) mLastPosition = mNonAccumCtrl->getTranslation(mCurrentTime) * mAccumulate; } bool Animation::handleTextKey(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, mCurrentGroup.size(), mCurrentGroup) != 0 || evt.compare(mCurrentGroup.size(), 2, ": ") != 0) { // Not ours return true; } size_t off = mCurrentGroup.size()+2; size_t len = evt.size() - off; if(evt.compare(off, len, "start") == 0 || evt.compare(off, len, "loop start") == 0) { mLoopStartKey = key; return true; } if(evt.compare(off, len, "loop stop") == 0 || evt.compare(off, len, "stop") == 0) { if(mLooping) { doLoop(); if(mCurrentTime >= time) return false; return true; } // fall-through } if(mController) mController->markerEvent(time, evt.substr(off)); return true; } void Animation::play(const std::string &groupname, const std::string &start, const std::string &stop, bool loop) { try { bool foundanim = false; /* Look in reverse; last-inserted source has priority. */ for(std::vector::reverse_iterator iter(mObjectLists.rbegin());iter != mObjectLists.rend();iter++) { if(iter->mSkelBase && iter->mSkelBase->hasAnimationState(groupname)) { const NifOgre::TextKeyMap *keys = &iter->mTextKeys.begin()->second; NifOgre::NodeTargetValue *nonaccumctrl = NULL; for(size_t i = 0;i < iter->mControllers.size();i++) { NifOgre::NodeTargetValue *dstval; dstval = dynamic_cast*>(iter->mControllers[i].getDestination().getPointer()); if(dstval && dstval->getNode() == mNonAccumRoot) { nonaccumctrl = dstval; break; } } if(!foundanim) { mCurrentKeys = keys; mCurrentGroup = groupname; mCurrentControllers = &iter->mControllers; mNonAccumCtrl = nonaccumctrl; mAnimVelocity = 0.0f; foundanim = true; } if(!mNonAccumRoot) break; mAnimVelocity = calcAnimVelocity(nonaccumctrl, groupname, keys); if(mAnimVelocity > 0.0f) break; } } if(!foundanim) throw std::runtime_error("Failed to find animation "+groupname); reset(start, stop); setLooping(loop); mPlaying = true; } catch(std::exception &e) { std::cerr<< e.what() <first > targetTime) { mCurrentTime = targetTime; if(mNonAccumRoot) movement += updatePosition(); break; } NifOgre::TextKeyMap::const_iterator key(mNextKey++); mCurrentTime = key->first; if(mNonAccumRoot) movement += updatePosition(); mPlaying = (mLooping || mStopKey->first > mCurrentTime); timepassed = targetTime - mCurrentTime; if(!handleTextKey(key)) break; } for(size_t i = 0;i < (*mCurrentControllers).size();i++) (*mCurrentControllers)[i].update(); if(mSkelBase) { const Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); for(std::vector::iterator iter(mObjectLists.begin());iter != mObjectLists.end();iter++) { Ogre::Entity *ent = iter->mSkelBase; if(!ent) continue; Ogre::SkeletonInstance *inst = ent->getSkeleton(); if(baseinst != inst) updateSkeletonInstance(baseinst, inst); // HACK: Dirty the animation state set so that Ogre will apply the // transformations to entities this skeleton instance is shared with. ent->getAllAnimationStates()->_notifyDirty(); } } return movement; } }