#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 { 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 > 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 > *grpctrls = mAnimSources.back().mControllers; NifOgre::NodeTargetValue *dstval; for(size_t i = 0;i < ctrls.size();i++) { dstval = static_cast*>(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 *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::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(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: "<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; state.mAutoDisable = autodisable; mStates[groupname] = state; break; } } if(iter == mAnimSources.rend()) std::cerr<< "Failed to find animation "<second.mGroups&(1<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 >&ctrls = animsrc->mControllers[0]; for(size_t i = 0;i < ctrls.size();i++) { NifOgre::NodeTargetValue *dstval; dstval = static_cast*>(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 >&ctrls = animsrc->mControllers[0]; for(size_t i = 0;i < ctrls.size();i++) { NifOgre::NodeTargetValue *dstval; dstval = static_cast*>(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; } bool Animation::disable(const std::string &groupname) { AnimStateMap::iterator iter = mStates.find(groupname); if(iter != mStates.end()) mStates.erase(iter); return resetActiveGroups(); } Ogre::Vector3 Animation::runAnimation(float duration) { Ogre::Vector3 movement(0.0f); duration *= mAnimSpeedMult; AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) { 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; } if(!state.mPlaying && state.mAutoDisable) mStates.erase(stateiter++); else stateiter++; } 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) { } }