diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 403f270d8..b23981f81 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -603,7 +603,7 @@ namespace MWRender void Animation::resetActiveGroups() { // remove all previous external controllers from the scene graph - for (AnimSourceControllerMap::iterator it = mAnimSourceControllers.begin(); it != mAnimSourceControllers.end(); ++it) + for (ControllerMap::iterator it = mActiveControllers.begin(); it != mActiveControllers.end(); ++it) { osg::Node* node = it->first; node->removeUpdateCallback(it->second); @@ -611,13 +611,8 @@ namespace MWRender // Should be no longer needed with OSG 3.4 it->second->setNestedCallback(NULL); } - if (mResetAccumRootCallback && mAccumRoot) - { - mAccumRoot->removeUpdateCallback(mResetAccumRootCallback); - // Should be no longer needed with OSG 3.4 - mResetAccumRootCallback->setNestedCallback(NULL); - } - mAnimSourceControllers.clear(); + + mActiveControllers.clear(); mAccumCtrl = NULL; @@ -647,7 +642,7 @@ namespace MWRender osg::ref_ptr node = mNodeMap.at(it->first); // this should not throw, we already checked for the node existing in addAnimSource node->addUpdateCallback(it->second); - mAnimSourceControllers[node] = it->second; + mActiveControllers.insert(std::make_pair(node, it->second)); if (grp == 0 && node == mAccumRoot) { @@ -660,10 +655,12 @@ namespace MWRender mResetAccumRootCallback->setAccumulate(mAccumulate); } mAccumRoot->addUpdateCallback(mResetAccumRootCallback); + mActiveControllers.insert(std::make_pair(mAccumRoot, mResetAccumRootCallback)); } } } } + addControllers(); } void Animation::changeGroups(const std::string &groupname, int groups) @@ -893,7 +890,7 @@ namespace MWRender mObjectRoot = NULL; mNodeMap.clear(); - mAnimSourceControllers.clear(); + mActiveControllers.clear(); mAccumRoot = NULL; mAccumCtrl = NULL; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 86dd21df4..e30f2b082 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -182,10 +182,10 @@ protected: // Used to reset the position of the accumulation root every frame - the movement should be applied to the physics system osg::ref_ptr mResetAccumRootCallback; - // Keep track of keyframe controllers from external files that we added to our scene graph. + // Keep track of controllers that we added to our scene graph. // We may need to rebuild these controllers when the active animation groups / sources change. - typedef std::map, osg::ref_ptr > AnimSourceControllerMap; - AnimSourceControllerMap mAnimSourceControllers; + typedef std::multimap, osg::ref_ptr > ControllerMap; + ControllerMap mActiveControllers; boost::shared_ptr mAnimationTimePtr[sNumGroups]; @@ -256,6 +256,12 @@ protected: void clearAnimSources(); + /** + * Provided to allow derived classes adding their own controllers. Note, the controllers must be added to mActiveControllers + * so they get cleaned up properly on the next controller rebuild. A controller rebuild may be necessary to ensure correct ordering. + */ + virtual void addControllers() {} + osg::Vec4f getEnchantmentColor(MWWorld::Ptr item); void addGlow(osg::ref_ptr node, osg::Vec4f glowColor); diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 392f8978a..bb4724555 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -250,7 +250,7 @@ namespace MWRender void Camera::setSneakOffset(float offset) { - // TODO: implement + mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset)); } float Camera::getYaw() diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e9e3b7aef..598e2fba9 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -1,9 +1,7 @@ #include "npcanimation.hpp" #include - - -#include // XXX +#include #include @@ -74,6 +72,100 @@ std::string getVampireHead(const std::string& race, bool female) namespace MWRender { +/// @note Assumes that the node being rotated has its "original" orientation set every frame by a different controller. +/// The pitch is then applied on top of that orientation. +/// @note Must be set on a MatrixTransform. +class RotateController : public osg::NodeCallback +{ +public: + RotateController(osg::Node* relativeTo, osg::Vec3f axis) + : mRotate(0.f) + , mAxis(axis) + , mRelativeTo(relativeTo) + { + + } + + void setRotate(float rotate) + { + mRotate = rotate; + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (mRotate == 0.f) + return; + osg::MatrixTransform* transform = static_cast(node); + osg::Matrix matrix = transform->getMatrix(); + osg::Quat worldOrient = getWorldOrientation(node); + + osg::Quat rotate (mRotate, mAxis); + osg::Quat orient = worldOrient * rotate * worldOrient.inverse() * matrix.getRotate(); + matrix.setRotate(orient); + + transform->setMatrix(matrix); + + traverse(node,nv); + } + + osg::Quat getWorldOrientation(osg::Node* node) + { + // this could be optimized later, we just need the world orientation, not the full matrix + osg::MatrixList worldMats = node->getWorldMatrices(mRelativeTo); + osg::Quat worldOrient; + if (worldMats.size()) + { + osg::Matrixf worldMat = worldMats[0]; + worldOrient = worldMat.getRotate(); + } + return worldOrient; + } + +protected: + float mRotate; + osg::Vec3f mAxis; + osg::ref_ptr mRelativeTo; +}; + +/// Subclass RotateController to add a Z-offset for sneaking in first person mode. +/// @note We use inheritance instead of adding another controller, so that we do not have to compute the worldOrient twice. +/// @note Must be set on a MatrixTransform. +class NeckController : public RotateController +{ +public: + NeckController(osg::Node* relativeTo) + : RotateController(relativeTo, osg::Vec3f(-1,0,0)) + { + } + + void setOffset(osg::Vec3f offset) + { + mOffset = offset; + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osg::MatrixTransform* transform = static_cast(node); + osg::Matrix matrix = transform->getMatrix(); + + osg::Quat worldOrient = getWorldOrientation(node); + osg::Quat rotate (mRotate, mAxis); + osg::Quat orient = worldOrient * rotate * worldOrient.inverse() * matrix.getRotate(); + + matrix.setRotate(orient); + matrix.setTrans(matrix.getTrans() + worldOrient.inverse() * mOffset); + + transform->setMatrix(matrix); + + traverse(node,nv); + } + +private: + osg::Vec3f mOffset; +}; + +// -------------------------------------------------------------------------------------------------------------- + HeadAnimationTime::HeadAnimationTime(MWWorld::Ptr reference) : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mEnabled(true), mValue(0) { @@ -150,6 +242,8 @@ void HeadAnimationTime::setBlinkStop(float value) mBlinkStop = value; } +// ---------------------------------------------------- + static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; @@ -196,7 +290,6 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr par mViewMode(viewMode), mShowWeapons(false), mShowCarriedLeft(true), - //mFirstPersonOffset(0.f, 0.f, 0.f), mNpcType(Type_Normal), mVisibilityFlags(visibilityFlags), mAlpha(1.f), @@ -583,11 +676,6 @@ void NpcAnimation::updateParts() if (wasArrowAttached) attachArrow(); } -/* -void NpcAnimation::addFirstPersonOffset(const Ogre::Vector3 &offset) -{ - mFirstPersonOffset += offset; -}*/ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor) { @@ -605,21 +693,17 @@ osg::Vec3f NpcAnimation::runAnimation(float timepassed) mHeadAnimationTime->update(timepassed); + if (mFirstPersonNeckController) + { + mFirstPersonNeckController->setRotate(mPtr.getRefData().getPosition().rot[0]); + mFirstPersonNeckController->setOffset(mFirstPersonOffset); + } + /* if (mSkelBase) { Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); - if(mViewMode == VM_FirstPerson) - { - float pitch = mPtr.getRefData().getPosition().rot[0]; - Ogre::Node *node = baseinst->getBone("Bip01 Neck"); - node->pitch(Ogre::Radian(-pitch), Ogre::Node::TS_WORLD); - - // This has to be done before this function ends; - // updateSkeletonInstance, below, touches the hands. - node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD); - } - else + if(mViewMode != VM_FirstPerson) { // In third person mode we may still need pitch for ranged weapon targeting pitchSkeleton(mPtr.getRefData().getPosition().rot[0], baseinst); @@ -629,7 +713,6 @@ osg::Vec3f NpcAnimation::runAnimation(float timepassed) node->rotate(Ogre::Quaternion(mHeadYaw, Ogre::Vector3::UNIT_Z) * Ogre::Quaternion(mHeadPitch, Ogre::Vector3::UNIT_X), Ogre::Node::TS_WORLD); } } - mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. */ return ret; @@ -792,6 +875,22 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector(found->second.get())) + { + osg::Node* node = found->second; + mFirstPersonNeckController = new NeckController(mObjectRoot.get()); + node->addUpdateCallback(mFirstPersonNeckController); + mActiveControllers.insert(std::make_pair(node, mFirstPersonNeckController)); + } + } +} + void NpcAnimation::showWeapons(bool showWeapon) { mShowWeapons = showWeapon; @@ -947,6 +1046,11 @@ void NpcAnimation::setVampire(bool vampire) } } +void NpcAnimation::setFirstPersonOffset(const osg::Vec3f &offset) +{ + mFirstPersonOffset = offset; +} + void NpcAnimation::updatePtr(const MWWorld::Ptr &updated) { Animation::updatePtr(updated); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 0f90bb2d7..28ae43144 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -48,6 +48,8 @@ public: virtual float getValue(osg::NodeVisitor* nv); }; +class NeckController; + class NpcAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: @@ -92,7 +94,7 @@ private: int mPartslots[ESM::PRT_Count]; //Each part slot is taken by clothing, armor, or is empty int mPartPriorities[ESM::PRT_Count]; - //Ogre::Vector3 mFirstPersonOffset; + osg::Vec3f mFirstPersonOffset; boost::shared_ptr mHeadAnimationTime; boost::shared_ptr mWeaponAnimationTime; @@ -119,6 +121,11 @@ private: //void applyAlpha(float alpha, Ogre::Entity* ent, NifOgre::ObjectScenePtr scene); + osg::ref_ptr mFirstPersonNeckController; + +protected: + virtual void addControllers(); + public: /** * @param ptr @@ -186,6 +193,9 @@ public: virtual void setVampire(bool vampire); + /// Set a translation offset (in object root space) to apply to meshes when in first person mode. + void setFirstPersonOffset(const osg::Vec3f& offset); + virtual void updatePtr(const MWWorld::Ptr& updated); };