#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) , mCurrentAnim(NULL) , mCurrentControllers(NULL) , mCurrentKeys(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 < 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); if(mSkelBase != objlist.mSkelBase) { Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); 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); } } Ogre::SkeletonPtr skel = Ogre::SkeletonManager::getSingleton().getByName(skelinst->getName()); 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(data)) continue; if(!mNonAccumRoot) { mAccumRoot = mInsert; mNonAccumRoot = mSkelBase->getSkeleton()->getBone(bone->getName()); } 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(groupdata); } break; } } 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; } void Animation::calcAnimVelocity() { 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); } } } 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; Ogre::TransformKeyFrame kf(0, mCurrentTime); Ogre::Animation::NodeTrackIterator trackiter = mCurrentAnim->getNodeTrackIterator(); while(trackiter.hasMoreElements()) { const Ogre::NodeAnimationTrack *track = trackiter.getNext(); if(track->getAssociatedNode()->getName() == mNonAccumRoot->getName()) { track->getInterpolatedKeyFrame(mCurrentAnim->_getTimeIndex(mCurrentTime), &kf); break; } } /* Get the non-accumulation root's difference from the last update. */ posdiff = (kf.getTranslate() - 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) { mStartKey = mCurrentKeys->begin(); while(mStartKey != mCurrentKeys->end() && mStartKey->second != start) mStartKey++; if(mStartKey != mCurrentKeys->end()) mCurrentTime = mStartKey->first; else { mStartKey = mCurrentKeys->begin(); mCurrentTime = mStartKey->first; } mNextKey = mStartKey; if(stop.length() > 0) { mStopKey = mStartKey; while(mStopKey != mCurrentKeys->end() && mStopKey->second != stop) mStopKey++; if(mStopKey == mCurrentKeys->end()) mStopKey--; } 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; } } } bool Animation::handleEvent(float time, const std::string &evt) { if(evt == "start" || evt == "loop start") { /* Do nothing */ return true; } 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 == "loop stop") { if(mLooping) { reset("loop start"); if(mCurrentTime >= time) return false; return true; } } else if(evt == "stop") { if(mLooping) { reset("loop start"); if(mCurrentTime >= time) return false; return true; } // fall-through } if(mController) mController->markerEvent(time, evt); return true; } void Animation::play(const std::string &groupname, const std::string &start, const std::string &stop, bool loop) { try { bool found = 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)) { Ogre::SkeletonInstance *skel = iter->mSkelBase->getSkeleton(); mCurrentAnim = skel->getAnimation(groupname); mCurrentKeys = &mTextKeys[groupname]; mCurrentGroup = groupname; mCurrentControllers = &iter->mControllers; mAnimVelocity = 0.0f; if(mNonAccumRoot) calcAnimVelocity(); found = true; break; } } if(!found) 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; } float time = mNextKey->first; const std::string &evt = mNextKey->second; mNextKey++; mCurrentTime = time; if(mNonAccumRoot) movement += updatePosition(); mPlaying = (mLooping || mStopKey->first > mCurrentTime); timepassed = targetTime - mCurrentTime; if(!handleEvent(time, evt)) 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; } }