#include "animation.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/character.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.hpp" #include "../mwworld/fallback.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "renderconst.hpp" namespace MWRender { Ogre::Real Animation::AnimationTime::getValue() const { AnimStateMap::const_iterator iter = mAnimation->mStates.find(mAnimationName); if(iter != mAnimation->mStates.end()) return iter->second.mTime; return 0.0f; } void Animation::AnimationTime::setValue(Ogre::Real) { } Ogre::Real Animation::EffectAnimationTime::getValue() const { return mTime; } void Animation::EffectAnimationTime::setValue(Ogre::Real) { } Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) : mPtr(ptr) , mInsert(node) , mSkelBase(NULL) , mAccumRoot(NULL) , mNonAccumRoot(NULL) , mNonAccumCtrl(NULL) , mAccumulate(0.0f) , mNullAnimationTimePtr(OGRE_NEW NullAnimationTime) , mGlowLight(NULL) { for(size_t i = 0;i < sNumGroups;i++) mAnimationTimePtr[i].bind(OGRE_NEW AnimationTime(this)); } Animation::~Animation() { setLightEffect(0); mEffects.clear(); mAnimSources.clear(); } void Animation::setObjectRoot(const std::string &model, bool baseonly) { OgreAssert(mAnimSources.empty(), "Setting object root while animation sources are set!"); mSkelBase = NULL; mObjectRoot.setNull(); if(model.empty()) return; 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)); // Fast forward auto-play particles, which will have been set up as Emitting by the loader. for (unsigned int i=0; imParticles.size(); ++i) mObjectRoot->mParticles[i]->fastForward(1, 0.1); 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); // Reattach any objects that have been attached to this one ObjectAttachMap::iterator iter = mAttachedObjects.begin(); while(iter != mAttachedObjects.end()) { if(!skelinst->hasBone(iter->second)) mAttachedObjects.erase(iter++); else { mSkelBase->attachObjectToBone(iter->second, iter->first); ++iter; } } } else mAttachedObjects.clear(); } struct AddGlow { Ogre::Vector3* mColor; NifOgre::MaterialControllerManager* mMaterialControllerMgr; AddGlow(Ogre::Vector3* col, NifOgre::MaterialControllerManager* materialControllerMgr) : mColor(col) , mMaterialControllerMgr(materialControllerMgr) {} void operator()(Ogre::Entity* entity) const { if (!entity->getNumSubEntities()) return; Ogre::MaterialPtr writableMaterial = mMaterialControllerMgr->getWritableMaterial(entity); sh::MaterialInstance* instance = sh::Factory::getInstance().getMaterialInstance(writableMaterial->getName()); instance->setProperty("env_map", sh::makeProperty(new sh::BooleanValue(true))); instance->setProperty("env_map_color", sh::makeProperty(new sh::Vector3(mColor->x, mColor->y, mColor->z))); } }; class VisQueueSet { Ogre::uint32 mVisFlags; Ogre::uint8 mSolidQueue, mTransQueue; Ogre::Real mDist; public: VisQueueSet(Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist) : mVisFlags(visflags), mSolidQueue(solidqueue), mTransQueue(transqueue), mDist(dist) { } void operator()(Ogre::Entity *entity) const { if(mVisFlags != 0) entity->setVisibilityFlags(mVisFlags); entity->setRenderingDistance(mDist); unsigned int numsubs = entity->getNumSubEntities(); for(unsigned int i = 0;i < numsubs;++i) { Ogre::SubEntity* subEnt = entity->getSubEntity(i); sh::Factory::getInstance()._ensureMaterial(subEnt->getMaterial()->getName(), "Default"); subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? mTransQueue : mSolidQueue); } } void operator()(Ogre::ParticleSystem *psys) const { if(mVisFlags != 0) psys->setVisibilityFlags(mVisFlags); psys->setRenderingDistance(mDist); // TODO: Check particle material for actual transparency psys->setRenderQueueGroup(mTransQueue); } }; void Animation::setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist, bool enchantedGlow, Ogre::Vector3* glowColor) { std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); std::for_each(objlist->mParticles.begin(), objlist->mParticles.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); if (enchantedGlow) std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(), AddGlow(glowColor, &objlist->mMaterialControllerMgr)); } size_t Animation::detectAnimGroup(const Ogre::Node *node) { static const char sGroupRoots[sNumGroups][32] = { "", /* Lower body / character root */ "Bip01 Spine1", /* Torso */ "Bip01 L Clavicle", /* Left arm */ "Bip01 R Clavicle", /* Right 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; Ogre::SharedPtr animsrc(OGRE_NEW AnimSource); NifOgre::Loader::createKfControllers(mSkelBase, kfname, animsrc->mTextKeys, ctrls); if(animsrc->mTextKeys.empty() || ctrls.empty()) return; mAnimSources.push_back(animsrc); std::vector > *grpctrls = animsrc->mControllers; for(size_t i = 0;i < ctrls.size();i++) { NifOgre::NodeTargetValue *dstval; dstval = static_cast*>(ctrls[i].getDestination().getPointer()); size_t grp = detectAnimGroup(dstval->getNode()); if(!mAccumRoot && grp == 0) { mNonAccumRoot = dstval->getNode(); mAccumRoot = mNonAccumRoot->getParent(); if(!mAccumRoot) { std::cerr<< "Non-Accum root for "<getNode()->getName() == "Bip01") { mNonAccumRoot = dstval->getNode(); mAccumRoot = mNonAccumRoot->getParent(); if(!mAccumRoot) { std::cerr<< "Non-Accum root for "<mControllers.size(); ++i) { if (mObjectRoot->mControllers[i].getSource().isNull()) mObjectRoot->mControllers[i].setSource(mAnimationTimePtr[0]); } } void Animation::clearAnimSources() { mStates.clear(); for(size_t i = 0;i < sNumGroups;i++) mAnimationTimePtr[i]->setAnimName(std::string()); mNonAccumCtrl = NULL; mAccumRoot = NULL; mNonAccumRoot = NULL; mAnimSources.clear(); } void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light) { const MWWorld::Fallback *fallback = MWBase::Environment::get().getWorld()->getFallback(); const int clr = light->mData.mColor; Ogre::ColourValue color(((clr >> 0) & 0xFF) / 255.0f, ((clr >> 8) & 0xFF) / 255.0f, ((clr >> 16) & 0xFF) / 255.0f); const float radius = float(light->mData.mRadius); if((light->mData.mFlags&ESM::Light::Negative)) color *= -1; objlist->mLights.push_back(sceneMgr->createLight()); Ogre::Light *olight = objlist->mLights.back(); olight->setDiffuseColour(color); Ogre::ControllerValueRealPtr src(Ogre::ControllerManager::getSingleton().getFrameTimeSource()); Ogre::ControllerValueRealPtr dest(OGRE_NEW OEngine::Render::LightValue(olight, color)); Ogre::ControllerFunctionRealPtr func(OGRE_NEW OEngine::Render::LightFunction( (light->mData.mFlags&ESM::Light::Flicker) ? OEngine::Render::LT_Flicker : (light->mData.mFlags&ESM::Light::FlickerSlow) ? OEngine::Render::LT_FlickerSlow : (light->mData.mFlags&ESM::Light::Pulse) ? OEngine::Render::LT_Pulse : (light->mData.mFlags&ESM::Light::PulseSlow) ? OEngine::Render::LT_PulseSlow : OEngine::Render::LT_Normal )); objlist->mControllers.push_back(Ogre::Controller(src, dest, func)); bool interior = !(mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior()); static bool outQuadInLin = fallback->getFallbackBool("LightAttenuation_OutQuadInLin"); static bool useQuadratic = fallback->getFallbackBool("LightAttenuation_UseQuadratic"); static float quadraticValue = fallback->getFallbackFloat("LightAttenuation_QuadraticValue"); static float quadraticRadiusMult = fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); static bool useLinear = fallback->getFallbackBool("LightAttenuation_UseLinear"); static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); bool quadratic = useQuadratic && (!outQuadInLin || !interior); // with the standard 1 / (c + d*l + d*d*q) equation the attenuation factor never becomes zero, // so we ignore lights if their attenuation falls below this factor. const float threshold = 0.03; float quadraticAttenuation = 0; float linearAttenuation = 0; float activationRange = 0; if (quadratic) { float r = radius * quadraticRadiusMult; quadraticAttenuation = quadraticValue / std::pow(r, 2); activationRange = std::sqrt(1.0f / (threshold * quadraticAttenuation)); } if (useLinear) { float r = radius * linearRadiusMult; linearAttenuation = linearValue / r; activationRange = std::max(activationRange, 1.0f / (threshold * linearAttenuation)); } olight->setAttenuation(activationRange, 0, linearAttenuation, quadraticAttenuation); // If there's an AttachLight bone, attach the light to that, otherwise put it in the center, if(objlist->mSkelBase && objlist->mSkelBase->getSkeleton()->hasBone("AttachLight")) objlist->mSkelBase->attachObjectToBone("AttachLight", olight); else { Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; for(size_t i = 0;i < objlist->mEntities.size();i++) { Ogre::Entity *ent = objlist->mEntities[i]; bounds.merge(ent->getBoundingBox()); } Ogre::SceneNode *node = bounds.isFinite() ? mInsert->createChildSceneNode(bounds.getCenter()) : mInsert->createChildSceneNode(); node->attachObject(olight); } } 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::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; // Pick the last Loop Stop key and the last Loop Start key. // This is required because of broken text keys in AshVampire.nif. // It has *two* WalkForward: Loop Stop keys at different times, the first one is used for stopping playback // but the animation velocity calculation uses the second one. // As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated, // because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough. NifOgre::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin()); while(keyiter != keys.rend()) { if(keyiter->second == start || keyiter->second == loopstart) { starttime = keyiter->first; break; } ++keyiter; } keyiter = keys.rbegin(); while(keyiter != keys.rend()) { if (keyiter->second == stop) stoptime = keyiter->first; else if (keyiter->second == loopstop) { 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; } float Animation::getVelocity(const std::string &groupname) const { /* Look in reverse; last-inserted source has priority. */ AnimSourceList::const_reverse_iterator animsrc(mAnimSources.rbegin()); for(;animsrc != mAnimSources.rend();animsrc++) { const NifOgre::TextKeyMap &keys = (*animsrc)->mTextKeys; if(findGroupStart(keys, groupname) != keys.end()) break; } if(animsrc == mAnimSources.rend()) return 0.0f; float velocity = 0.0f; 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) { velocity = calcAnimVelocity(keys, dstval, mAccumulate, groupname); break; } } // If there's no velocity, keep looking if(!(velocity > 1.0f)) { AnimSourceList::const_reverse_iterator animiter = mAnimSources.rbegin(); while(*animiter != *animsrc) ++animiter; while(!(velocity > 1.0f) && ++animiter != mAnimSources.rend()) { const NifOgre::TextKeyMap &keys = (*animiter)->mTextKeys; const std::vector >&ctrls = (*animiter)->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) { velocity = calcAnimVelocity(keys, dstval, mAccumulate, groupname); break; } } } } return velocity; } 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); } } 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) { // Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two // separate walkforward keys, and the last one is supposed to be used. NifOgre::TextKeyMap::const_reverse_iterator groupend(keys.rbegin()); for(;groupend != keys.rend();++groupend) { if(groupend->second.compare(0, groupname.size(), groupname) == 0 && groupend->second.compare(groupname.size(), 2, ": ") == 0) break; } std::string starttag = groupname+": "+start; NifOgre::TextKeyMap::const_reverse_iterator startkey(groupend); while(startkey != keys.rend() && startkey->second != starttag) ++startkey; if(startkey == keys.rend() && start == "loop start") { starttag = groupname+": start"; startkey = groupend; while(startkey != keys.rend() && startkey->second != starttag) ++startkey; } if(startkey == keys.rend()) return false; const std::string stoptag = groupname+": "+stop; NifOgre::TextKeyMap::const_reverse_iterator stopkey(groupend); while(stopkey != keys.rend() // We have to ignore extra garbage at the end. // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop". // Why, just why? :( && (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag)) ++stopkey; if(stopkey == keys.rend()) return false; if(startkey->first > stopkey->first) return false; state.mStartTime = startkey->first; state.mLoopStartTime = startkey->first; state.mLoopStopTime = stopkey->first; state.mStopTime = stopkey->first; state.mTime = state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint); // mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation // (see handleTextKey). But if startpoint is already past these keys, we need to assign them now. if(state.mTime > state.mStartTime) { const std::string loopstarttag = groupname+": loop start"; const std::string loopstoptag = groupname+": loop stop"; NifOgre::TextKeyMap::const_reverse_iterator key(groupend); for (; key != startkey && key != keys.rend(); ++key) { if (key->first > state.mTime) continue; if (key->second == loopstarttag) state.mLoopStartTime = key->first; else if (key->second == loopstoptag) state.mLoopStopTime = key->first; } } return true; } void split(const std::string &s, char delim, std::vector &elems) { std::stringstream ss(s); std::string item; while (std::getline(ss, item, delim)) { elems.push_back(item); } } void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key, const NifOgre::TextKeyMap& textkeys) { //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; } if(evt.compare(0, 10, "soundgen: ") == 0) { std::string soundgen = evt.substr(10); // The event can optionally contain volume and pitch modifiers float volume=1.f, pitch=1.f; if (soundgen.find(" ") != std::string::npos) { std::vector tokens; split(soundgen, ' ', tokens); soundgen = tokens[0]; if (tokens.size() >= 2) volume = Ogre::StringConverter::parseReal(tokens[1]); if (tokens.size() >= 3) pitch = Ogre::StringConverter::parseReal(tokens[2]); } std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); if(!sound.empty()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager::PlayType type = MWBase::SoundManager::Play_TypeSfx; if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0) type = MWBase::SoundManager::Play_TypeFoot; sndMgr->playSound3D(mPtr, sound, volume, pitch, type); } return; } if(evt.compare(0, groupname.size(), groupname) != 0 || evt.compare(groupname.size(), 2, ": ") != 0) { // Not ours, skip it return; } size_t off = groupname.size()+2; size_t len = evt.size() - off; if(evt.compare(off, len, "loop start") == 0) state.mLoopStartTime = key->first; else if(evt.compare(off, len, "loop stop") == 0) state.mLoopStopTime = key->first; else if(evt.compare(off, len, "equip attach") == 0) showWeapons(true); else if(evt.compare(off, len, "unequip detach") == 0) showWeapons(false); else if(evt.compare(off, len, "chop hit") == 0) mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop); else if(evt.compare(off, len, "slash hit") == 0) mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash); else if(evt.compare(off, len, "thrust hit") == 0) mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust); else if(evt.compare(off, len, "hit") == 0) { if (groupname == "attack1") mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop); else if (groupname == "attack2") mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash); else if (groupname == "attack3") mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust); else mPtr.getClass().hit(mPtr); } else if (!groupname.empty() && groupname.compare(0, groupname.size()-1, "attack") == 0 && evt.compare(off, len, "start") == 0) { NifOgre::TextKeyMap::const_iterator hitKey = key; // Not all animations have a hit key defined. If there is none, the hit happens with the start key. bool hasHitKey = false; while (hitKey != textkeys.end()) { if (hitKey->second == groupname + ": hit") { hasHitKey = true; break; } if (hitKey->second == groupname + ": stop") break; ++hitKey; } if (!hasHitKey) { if (groupname == "attack1") mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop); else if (groupname == "attack2") mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash); else if (groupname == "attack3") mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust); } } else if (evt.compare(off, len, "shoot attach") == 0) attachArrow(); else if (evt.compare(off, len, "shoot release") == 0) releaseArrow(); else if (evt.compare(off, len, "shoot follow attach") == 0) attachArrow(); else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release") MWBase::Environment::get().getWorld()->castSpell(mPtr); else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) mPtr.getClass().block(mPtr); } void Animation::changeGroups(const std::string &groupname, int groups) { AnimStateMap::iterator stateiter = mStates.begin(); stateiter = mStates.find(groupname); if(stateiter != mStates.end()) { if(stateiter->second.mGroups != groups) { stateiter->second.mGroups = groups; resetActiveGroups(); } return; } } void Animation::play(const std::string &groupname, int priority, int groups, bool autodisable, float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops) { if(!mSkelBase || mAnimSources.empty()) return; if(groupname.empty()) { resetActiveGroups(); return; } priority = std::max(0, priority); AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) { if(stateiter->second.mPriority == priority) mStates.erase(stateiter++); else ++stateiter; } stateiter = mStates.find(groupname); if(stateiter != mStates.end()) { stateiter->second.mPriority = priority; resetActiveGroups(); return; } /* Look in reverse; last-inserted source has priority. */ AnimState state; AnimSourceList::reverse_iterator iter(mAnimSources.rbegin()); for(;iter != mAnimSources.rend();++iter) { const NifOgre::TextKeyMap &textkeys = (*iter)->mTextKeys; if(reset(state, textkeys, groupname, start, stop, startpoint)) { state.mSource = *iter; state.mSpeedMult = speedmult; state.mLoopCount = loops; state.mPlaying = (state.mTime < state.mStopTime); state.mPriority = priority; state.mGroups = groups; state.mAutoDisable = autodisable; mStates[groupname] = state; NifOgre::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime)); if (state.mPlaying) { while(textkey != textkeys.end() && textkey->first <= state.mTime) { handleTextKey(state, groupname, textkey, textkeys); ++textkey; } } if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) { state.mLoopCount--; state.mTime = state.mLoopStartTime; state.mPlaying = true; if(state.mTime >= state.mLoopStopTime) break; NifOgre::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime)); while(textkey != textkeys.end() && textkey->first <= state.mTime) { handleTextKey(state, groupname, textkey, textkeys); ++textkey; } } break; } } if(iter == mAnimSources.rend()) std::cerr<< "Failed to find animation "<setPosition(-mNonAccumCtrl->getTranslation(state.mTime)*mAccumulate); } } bool Animation::isPlaying(const std::string &groupname) const { AnimStateMap::const_iterator state(mStates.find(groupname)); if(state != mStates.end()) return state->second.mPlaying; return false; } void Animation::resetActiveGroups() { for(size_t grp = 0;grp < sNumGroups;grp++) { AnimStateMap::const_iterator active = mStates.end(); AnimStateMap::const_iterator state = mStates.begin(); for(;state != mStates.end();++state) { if(!(state->second.mGroups&(1<second.mPriority < state->second.mPriority) active = state; } mAnimationTimePtr[grp]->setAnimName((active == mStates.end()) ? std::string() : active->first); } mNonAccumCtrl = NULL; if(!mNonAccumRoot || mAccumulate == Ogre::Vector3(0.0f)) return; AnimStateMap::const_iterator state = mStates.find(mAnimationTimePtr[0]->getAnimName()); if(state == mStates.end()) return; const Ogre::SharedPtr &animsrc = state->second.mSource; 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) { mNonAccumCtrl = dstval; break; } } } bool Animation::getInfo(const std::string &groupname, float *complete, float *speedmult) const { AnimStateMap::const_iterator iter = mStates.find(groupname); if(iter == mStates.end()) { if(complete) *complete = 0.0f; if(speedmult) *speedmult = 0.0f; return false; } if(complete) { if(iter->second.mStopTime > iter->second.mStartTime) *complete = (iter->second.mTime - iter->second.mStartTime) / (iter->second.mStopTime - iter->second.mStartTime); else *complete = (iter->second.mPlaying ? 0.0f : 1.0f); } if(speedmult) *speedmult = iter->second.mSpeedMult; return true; } float Animation::getStartTime(const std::string &groupname) const { for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) { const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); if(found != keys.end()) return found->first; } return -1.f; } float Animation::getTextKeyTime(const std::string &textKey) const { for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) { const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; for(NifOgre::TextKeyMap::const_iterator iterKey(keys.begin()); iterKey != keys.end(); ++iterKey) { if(iterKey->second.compare(0, textKey.size(), textKey) == 0) return iterKey->first; } } return -1.f; } float Animation::getCurrentTime(const std::string &groupname) const { AnimStateMap::const_iterator iter = mStates.find(groupname); if(iter == mStates.end()) return -1.f; return iter->second.mTime; } void Animation::disable(const std::string &groupname) { AnimStateMap::iterator iter = mStates.find(groupname); if(iter != mStates.end()) mStates.erase(iter); resetActiveGroups(); } Ogre::Vector3 Animation::runAnimation(float duration) { Ogre::Vector3 movement(0.0f); AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) { AnimState &state = stateiter->second; const NifOgre::TextKeyMap &textkeys = state.mSource->mTextKeys; NifOgre::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.mTime)); float timepassed = duration * state.mSpeedMult; while(state.mPlaying) { float targetTime; if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) goto handle_loop; targetTime = state.mTime + timepassed; if(textkey == textkeys.end() || textkey->first > targetTime) { if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName()) updatePosition(state.mTime, targetTime, movement); state.mTime = std::min(targetTime, state.mStopTime); } else { if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName()) updatePosition(state.mTime, textkey->first, movement); state.mTime = textkey->first; } state.mPlaying = (state.mTime < state.mStopTime); timepassed = targetTime - state.mTime; while(textkey != textkeys.end() && textkey->first <= state.mTime) { handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) { handle_loop: state.mLoopCount--; state.mTime = state.mLoopStartTime; state.mPlaying = true; textkey = textkeys.lower_bound(state.mTime); while(textkey != textkeys.end() && textkey->first <= state.mTime) { handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } if(state.mTime >= state.mLoopStopTime) break; } if(timepassed <= 0.0f) break; } if(!state.mPlaying && state.mAutoDisable) { if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName()) mAccumRoot->setPosition(0.f,0.f,0.f); mStates.erase(stateiter++); resetActiveGroups(); } else ++stateiter; } for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) { if(!mObjectRoot->mControllers[i].getSource().isNull()) mObjectRoot->mControllers[i].update(); } // Apply group controllers for(size_t grp = 0;grp < sNumGroups;grp++) { const std::string &name = mAnimationTimePtr[grp]->getAnimName(); if(!name.empty() && (stateiter=mStates.find(name)) != mStates.end()) { const Ogre::SharedPtr &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(); } updateEffects(duration); return movement; } void Animation::showWeapons(bool showWeapon) { } class ToggleLight { bool mEnable; public: ToggleLight(bool enable) : mEnable(enable) { } void operator()(Ogre::Light *light) const { light->setVisible(mEnable); } }; void Animation::enableLights(bool enable) { std::for_each(mObjectRoot->mLights.begin(), mObjectRoot->mLights.end(), ToggleLight(enable)); } class MergeBounds { Ogre::AxisAlignedBox *mBounds; public: MergeBounds(Ogre::AxisAlignedBox *bounds) : mBounds(bounds) { } void operator()(Ogre::MovableObject *obj) { mBounds->merge(obj->getWorldBoundingBox(true)); } }; Ogre::AxisAlignedBox Animation::getWorldBounds() { Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; std::for_each(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(), MergeBounds(&bounds)); return bounds; } Ogre::TagPoint *Animation::attachObjectToBone(const Ogre::String &bonename, Ogre::MovableObject *obj) { Ogre::TagPoint *tag = NULL; Ogre::SkeletonInstance *skel = (mSkelBase ? mSkelBase->getSkeleton() : NULL); if(skel && skel->hasBone(bonename)) { tag = mSkelBase->attachObjectToBone(bonename, obj); mAttachedObjects[obj] = bonename; } return tag; } void Animation::detachObjectFromBone(Ogre::MovableObject *obj) { ObjectAttachMap::iterator iter = mAttachedObjects.find(obj); if(iter != mAttachedObjects.end()) mAttachedObjects.erase(iter); mSkelBase->detachObjectFromBone(obj); } bool Animation::upperBodyReady() const { for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) { if((stateiter->second.mPriority > MWMechanics::Priority_Movement && stateiter->second.mPriority < MWMechanics::Priority_Torch) || stateiter->second.mPriority == MWMechanics::Priority_Death) return false; } return true; } void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename, std::string texture) { // Early out if we already have this effect for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) return; std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture); EffectParams params; params.mModelName = model; if (bonename.empty()) params.mObjects = NifOgre::Loader::createObjects(mInsert, model); else params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); // TODO: turn off shadow casting setRenderProperties(params.mObjects, RV_Misc, RQG_Main, RQG_Alpha, 0.f, false, NULL); params.mLoop = loop; params.mEffectId = effectId; params.mBoneName = bonename; for(size_t i = 0;i < params.mObjects->mControllers.size();i++) { if(params.mObjects->mControllers[i].getSource().isNull()) params.mObjects->mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationTime())); } // Do some manual adjustments on the created entities/particle systems // It looks like vanilla MW totally ignores lighting settings for effects attached to characters. // If we don't do this, some effects will look way too dark depending on the environment // (e.g. magic_cast_dst.nif). They were clearly meant to use emissive lighting. // We used to have this hack in the NIF material loader, but for effects not attached to characters // (e.g. ash storms) the lighting settings do seem to be in use. Is there maybe a flag we have missed? Ogre::ColourValue ambient = Ogre::ColourValue(0.f, 0.f, 0.f); Ogre::ColourValue diffuse = Ogre::ColourValue(0.f, 0.f, 0.f); Ogre::ColourValue specular = Ogre::ColourValue(0.f, 0.f, 0.f); Ogre::ColourValue emissive = Ogre::ColourValue(1.f, 1.f, 1.f); for(size_t i = 0;i < params.mObjects->mParticles.size(); ++i) { Ogre::ParticleSystem* partSys = params.mObjects->mParticles[i]; Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(partSys); for (int t=0; tgetNumTechniques(); ++t) { Ogre::Technique* tech = mat->getTechnique(t); for (int p=0; pgetNumPasses(); ++p) { Ogre::Pass* pass = tech->getPass(p); pass->setAmbient(ambient); pass->setDiffuse(diffuse); pass->setSpecular(specular); pass->setEmissive(emissive); if (!texture.empty()) { for (int tex=0; texgetNumTextureUnitStates(); ++tex) { Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); tus->setTextureName(correctedTexture); } } } } } for(size_t i = 0;i < params.mObjects->mEntities.size(); ++i) { Ogre::Entity* ent = params.mObjects->mEntities[i]; if (ent == params.mObjects->mSkelBase) continue; Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(ent); for (int t=0; tgetNumTechniques(); ++t) { Ogre::Technique* tech = mat->getTechnique(t); for (int p=0; pgetNumPasses(); ++p) { Ogre::Pass* pass = tech->getPass(p); pass->setAmbient(ambient); pass->setDiffuse(diffuse); pass->setSpecular(specular); pass->setEmissive(emissive); if (!texture.empty()) { for (int tex=0; texgetNumTextureUnitStates(); ++tex) { Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); tus->setTextureName(correctedTexture); } } } } } mEffects.push_back(params); } void Animation::removeEffect(int effectId) { for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) { if (it->mEffectId == effectId) { mEffects.erase(it); return; } } } void Animation::getLoopingEffects(std::vector &out) { for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) { if (it->mLoop) out.push_back(it->mEffectId); } } void Animation::updateEffects(float duration) { for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) { NifOgre::ObjectScenePtr objects = it->mObjects; for(size_t i = 0; i < objects->mControllers.size() ;i++) { EffectAnimationTime* value = dynamic_cast(objects->mControllers[i].getSource().get()); if (value) value->addTime(duration); objects->mControllers[i].update(); } if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength) { if (it->mLoop) { // Start from the beginning again; carry over the remainder float remainder = objects->mControllers[0].getSource()->getValue() - objects->mMaxControllerLength; for(size_t i = 0; i < objects->mControllers.size() ;i++) { EffectAnimationTime* value = dynamic_cast(objects->mControllers[i].getSource().get()); if (value) value->resetTime(remainder); } } else { it = mEffects.erase(it); continue; } } ++it; } } void Animation::preRender(Ogre::Camera *camera) { for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) { NifOgre::ObjectScenePtr objects = it->mObjects; objects->rotateBillboardNodes(camera); } mObjectRoot->rotateBillboardNodes(camera); } // TODO: Should not be here Ogre::Vector3 Animation::getEnchantmentColor(MWWorld::Ptr item) { Ogre::Vector3 result(1,1,1); std::string enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) return result; const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); assert (enchantment->mEffects.mList.size()); const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( enchantment->mEffects.mList.front().mEffectID); result.x = magicEffect->mData.mRed / 255.f; result.y = magicEffect->mData.mGreen / 255.f; result.z = magicEffect->mData.mBlue / 255.f; return result; } void Animation::setLightEffect(float effect) { if (effect == 0) { if (mGlowLight) { mInsert->getCreator()->destroySceneNode(mGlowLight->getParentSceneNode()); mInsert->getCreator()->destroyLight(mGlowLight); mGlowLight = NULL; } } else { if (!mGlowLight) { mGlowLight = mInsert->getCreator()->createLight(); Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; for(size_t i = 0;i < mObjectRoot->mEntities.size();i++) { Ogre::Entity *ent = mObjectRoot->mEntities[i]; bounds.merge(ent->getBoundingBox()); } mInsert->createChildSceneNode(bounds.getCenter())->attachObject(mGlowLight); } mGlowLight->setType(Ogre::Light::LT_POINT); effect += 3; mGlowLight->setAttenuation(1.0f / (0.03 * (0.5/effect)), 0, 0.5/effect, 0); } } ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model) : Animation(ptr, ptr.getRefData().getBaseNode()) { if (!model.empty()) { setObjectRoot(model, false); Ogre::Vector3 extents = getWorldBounds().getSize(); float size = std::max(std::max(extents.x, extents.y), extents.z); bool small = (size < Settings::Manager::getInt("small object size", "Viewing distance")) && Settings::Manager::getBool("limit small object distance", "Viewing distance"); // do not fade out doors. that will cause holes and look stupid if(ptr.getTypeName().find("Door") != std::string::npos) small = false; float dist = small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0.0f; Ogre::Vector3 col = getEnchantmentColor(ptr); setRenderProperties(mObjectRoot, (mPtr.getTypeName() == typeid(ESM::Static).name()) ? (small ? RV_StaticsSmall : RV_Statics) : RV_Misc, RQG_Main, RQG_Alpha, dist, !ptr.getClass().getEnchantment(ptr).empty(), &col); } else { // No model given. Create an object root anyway, so that lights can be added to it if needed. mObjectRoot = NifOgre::ObjectScenePtr (new NifOgre::ObjectScene(mInsert->getCreator())); } } void ObjectAnimation::addLight(const ESM::Light *light) { addExtraLight(mInsert->getCreator(), mObjectRoot, light); } void ObjectAnimation::removeParticles() { for (unsigned int i=0; imParticles.size(); ++i) { // Don't destroyParticleSystem, the ParticleSystemController is still holding a pointer to it. // Don't setVisible, this could conflict with a VisController. // The following will remove all spawned particles, then set the speed factor to zero so that no new ones will be spawned. mObjectRoot->mParticles[i]->setSpeedFactor(0.f); mObjectRoot->mParticles[i]->clear(); } } class FindEntityTransparency { public: bool operator()(Ogre::Entity *ent) const { unsigned int numsubs = ent->getNumSubEntities(); for(unsigned int i = 0;i < numsubs;++i) { sh::Factory::getInstance()._ensureMaterial(ent->getSubEntity(i)->getMaterial()->getName(), "Default"); if(ent->getSubEntity(i)->getMaterial()->isTransparent()) return true; } return false; } }; bool ObjectAnimation::canBatch() const { if(!mObjectRoot->mParticles.empty() || !mObjectRoot->mLights.empty() || !mObjectRoot->mControllers.empty()) return false; if (!mObjectRoot->mBillboardNodes.empty()) return false; return std::find_if(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(), FindEntityTransparency()) == mObjectRoot->mEntities.end(); } void ObjectAnimation::fillBatch(Ogre::StaticGeometry *sg) { std::vector::reverse_iterator iter = mObjectRoot->mEntities.rbegin(); for(;iter != mObjectRoot->mEntities.rend();++iter) { Ogre::Node *node = (*iter)->getParentNode(); if ((*iter)->isVisible()) sg->addEntity(*iter, node->_getDerivedPosition(), node->_getDerivedOrientation(), node->_getDerivedScale()); } } }