diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e85aa9b83..082100b27 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -172,16 +172,19 @@ namespace MWMechanics MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if(!MWWorld::Class::get(ptr).getCreatureStats(ptr).isDead()) - mActors.insert(std::make_pair(ptr, CharacterController(ptr, anim, CharState_Idle))); + mActors.insert(std::make_pair(ptr, new CharacterController(ptr, anim, CharState_Idle))); else - mActors.insert(std::make_pair(ptr, CharacterController(ptr, anim, CharState_Death1))); + mActors.insert(std::make_pair(ptr, new CharacterController(ptr, anim, CharState_Death1))); } void Actors::removeActor (const MWWorld::Ptr& ptr) { PtrControllerMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) + { + delete iter->second; mActors.erase(iter); + } } void Actors::updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) @@ -189,10 +192,10 @@ namespace MWMechanics PtrControllerMap::iterator iter = mActors.find(old); if(iter != mActors.end()) { - CharacterController ctrl = iter->second; + CharacterController *ctrl = iter->second; mActors.erase(iter); - ctrl.updatePtr(ptr); + ctrl->updatePtr(ptr); mActors.insert(std::make_pair(ptr, ctrl)); } } @@ -203,7 +206,10 @@ namespace MWMechanics while(iter != mActors.end()) { if(iter->first.getCell()==cellStore) + { + delete iter->second; mActors.erase(iter++); + } else ++iter; } @@ -222,8 +228,8 @@ namespace MWMechanics { if(!MWWorld::Class::get(iter->first).getCreatureStats(iter->first).isDead()) { - if(iter->second.getState() >= CharState_Death1) - iter->second.setState(CharState_Idle); + if(iter->second->getState() >= CharState_Death1) + iter->second->setState(CharState_Idle); updateActor(iter->first, totalDuration); if(iter->first.getTypeName() == typeid(ESM::NPC).name()) @@ -250,10 +256,10 @@ namespace MWMechanics continue; } - if(iter->second.getState() >= CharState_Death1) + if(iter->second->getState() >= CharState_Death1) continue; - iter->second.setState(CharState_Death1); + iter->second->setState(CharState_Death1); ++mDeathCount[MWWorld::Class::get(iter->first).getId(iter->first)]; @@ -270,7 +276,7 @@ namespace MWMechanics for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) { Movement movement; - iter->second.update(duration, movement); + iter->second->update(duration, movement); mMovement.push_back(std::make_pair(iter->first, movement)); } MWBase::Environment::get().getWorld()->doPhysics(mMovement, duration); @@ -297,27 +303,27 @@ namespace MWMechanics { PtrControllerMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - iter->second.forceStateUpdate(); + iter->second->forceStateUpdate(); } void Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) { PtrControllerMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - iter->second.playGroup(groupName, mode, number); + iter->second->playGroup(groupName, mode, number); } void Actors::skipAnimation(const MWWorld::Ptr& ptr) { PtrControllerMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - iter->second.skipAnim(); + iter->second->skipAnim(); } bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) { PtrControllerMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - return iter->second.isAnimPlaying(groupName); + return iter->second->isAnimPlaying(groupName); return false; } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 386840e3a..1369d783c 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,7 +25,7 @@ namespace MWMechanics { class Actors { - typedef std::map<MWWorld::Ptr,CharacterController> PtrControllerMap; + typedef std::map<MWWorld::Ptr,CharacterController*> PtrControllerMap; PtrControllerMap mActors; MWWorld::PtrMovementList mMovement; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 853ffc375..f958b8286 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -45,6 +45,7 @@ void Animation::destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectL Animation::Animation(const MWWorld::Ptr &ptr) : mPtr(ptr) + , mCamera(NULL) , mInsert(NULL) , mSkelBase(NULL) , mAccumRoot(NULL) @@ -72,8 +73,9 @@ Animation::~Animation() void Animation::setObjectRoot(Ogre::SceneNode *node, const std::string &model, bool baseonly) { - OgreAssert(!mInsert, "Object already has a root!"); - mInsert = node->createChildSceneNode(); + OgreAssert(mAnimSources.size() == 0, "Setting object root while animation sources are set!"); + if(!mInsert) + mInsert = node->createChildSceneNode(); std::string mdlname = Misc::StringUtils::lowerCase(model); std::string::size_type p = mdlname.rfind('\\'); @@ -89,6 +91,9 @@ void Animation::setObjectRoot(Ogre::SceneNode *node, const std::string &model, b Misc::StringUtils::toLower(mdlname); } + mSkelBase = NULL; + destroyObjectList(mInsert->getCreator(), mObjectRoot); + mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) : NifOgre::Loader::createObjectBase(mInsert, mdlname)); if(mObjectRoot.mSkelBase) @@ -110,7 +115,23 @@ void Animation::setObjectRoot(Ogre::SceneNode *node, const std::string &model, b 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(); + for(size_t i = 0;i < mObjectRoot.mControllers.size();i++) { if(mObjectRoot.mControllers[i].getSource().isNull()) @@ -736,4 +757,24 @@ bool Animation::isPriorityActive(int priority) const return false; } +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); +} + } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 31be0fb2a..f87fade55 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -11,6 +11,7 @@ namespace MWRender { +class Camera; class Animation { @@ -79,7 +80,10 @@ protected: }; typedef std::map<std::string,AnimState> AnimStateMap; + typedef std::map<Ogre::MovableObject*,std::string> ObjectAttachMap; + MWWorld::Ptr mPtr; + Camera *mCamera; Ogre::SceneNode *mInsert; Ogre::Entity *mSkelBase; @@ -94,6 +98,8 @@ protected: Ogre::SharedPtr<AnimationValue> mAnimationValuePtr[sNumGroups]; + ObjectAttachMap mAttachedObjects; + float mAnimVelocity; float mAnimSpeedMult; @@ -131,7 +137,18 @@ protected: bool handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key); + /* Sets the root model of the object. If 'baseonly' is true, then any meshes or particle + * systems in the model are ignored (useful for NPCs, where only the skeleton is needed for + * the root). + * + * Note that you must make sure all animation sources are cleared before reseting the object + * root. All nodes previously retrieved with getNode will also become invalidated. + */ void setObjectRoot(Ogre::SceneNode *node, const std::string &model, bool baseonly); + + /* Adds the keyframe controllers in the specified model as a new animation source. Note that + * the filename portion of the provided model name will be prepended with 'x', and the .nif + * extension will be replaced with .kf. */ void addAnimSource(const std::string &model); static void destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects); @@ -199,7 +216,16 @@ public: virtual void showWeapons(bool showWeapon); + void setCamera(Camera *cam) + { mCamera = cam; } + Ogre::Node *getNode(const std::string &name); + + // Attaches the given object to a bone on this object's base skeleton. If the bone doesn't + // exist, the object isn't attached and NULL is returned. The returned TagPoint is only + // valid until the next setObjectRoot call. + Ogre::TagPoint *attachObjectToBone(const Ogre::String &bonename, Ogre::MovableObject *obj); + void detachObjectFromBone(Ogre::MovableObject *obj); }; } diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index e71e694f9..d226c577c 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -3,6 +3,7 @@ #include <OgreSceneNode.h> #include <OgreCamera.h> #include <OgreSceneManager.h> +#include <OgreTagPoint.h> #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -57,10 +58,10 @@ namespace MWRender Ogre::Quaternion xr(Ogre::Radian(getPitch() + Ogre::Math::HALF_PI), Ogre::Vector3::UNIT_X); if (!mVanity.enabled && !mPreviewMode) { - mCameraNode->setOrientation(xr); + mCamera->getParentNode()->setOrientation(xr); } else { Ogre::Quaternion zr(Ogre::Radian(getYaw()), Ogre::Vector3::NEGATIVE_UNIT_Z); - mCameraNode->setOrientation(zr * xr); + mCamera->getParentNode()->setOrientation(zr * xr); } } @@ -112,14 +113,12 @@ namespace MWRender void Camera::toggleViewMode() { mFirstPersonView = !mFirstPersonView; - mAnimation->setViewMode(isFirstPerson() ? NpcAnimation::VM_FirstPerson : - NpcAnimation::VM_Normal); + processViewChange(); + if (mFirstPersonView) { mCamera->setPosition(0.f, 0.f, 0.f); - setLowHeight(false); } else { mCamera->setPosition(0.f, 0.f, mCameraDistance); - setLowHeight(true); } } @@ -139,21 +138,16 @@ namespace MWRender return true; mVanity.enabled = enable; - mAnimation->setViewMode(isFirstPerson() ? NpcAnimation::VM_FirstPerson : - NpcAnimation::VM_Normal); + processViewChange(); float offset = mPreviewCam.offset; Ogre::Vector3 rot(0.f, 0.f, 0.f); if (mVanity.enabled) { rot.x = Ogre::Degree(-30.f).valueRadians(); mMainCam.offset = mCamera->getPosition().z; - - setLowHeight(true); } else { rot.x = getPitch(); offset = mMainCam.offset; - - setLowHeight(!mFirstPersonView); } rot.z = getYaw(); @@ -169,20 +163,15 @@ namespace MWRender return; mPreviewMode = enable; - mAnimation->setViewMode(isFirstPerson() ? NpcAnimation::VM_FirstPerson : - NpcAnimation::VM_Normal); + processViewChange(); float offset = mCamera->getPosition().z; if (mPreviewMode) { mMainCam.offset = offset; offset = mPreviewCam.offset; - - setLowHeight(true); } else { mPreviewCam.offset = offset; offset = mMainCam.offset; - - setLowHeight(!mFirstPersonView); } mCamera->setPosition(0.f, 0.f, offset); @@ -283,26 +272,48 @@ namespace MWRender // If we're switching to a new NpcAnimation, ensure the old one is // using a normal view mode if(mAnimation && mAnimation != anim) + { mAnimation->setViewMode(NpcAnimation::VM_Normal); + mAnimation->setCamera(NULL); + mAnimation->detachObjectFromBone(mCamera); + } mAnimation = anim; - mAnimation->setViewMode(isFirstPerson() ? NpcAnimation::VM_FirstPerson : - NpcAnimation::VM_Normal); + mAnimation->setCamera(this); + + processViewChange(); } - void Camera::setHeight(float height) + void Camera::processViewChange() { - mHeight = height; - mCameraNode->setPosition(0.f, 0.f, mHeight); + mAnimation->detachObjectFromBone(mCamera); + mCamera->detachFromParent(); + + if(isFirstPerson()) + { + mAnimation->setViewMode(NpcAnimation::VM_FirstPerson); + Ogre::TagPoint *tag = mAnimation->attachObjectToBone("Head", mCamera); + tag->setInheritOrientation(false); + } + else + { + mAnimation->setViewMode(NpcAnimation::VM_Normal); + mCameraNode->attachObject(mCamera); + } } float Camera::getHeight() { - return mHeight * mTrackingPtr.getRefData().getBaseNode()->getScale().z; + if(mCamera->isParentTagPoint()) + { + Ogre::TagPoint *tag = static_cast<Ogre::TagPoint*>(mCamera->getParentNode()); + return tag->_getFullLocalTransform().getTrans().z; + } + return mCamera->getParentNode()->getPosition().z; } bool Camera::getPosition(Ogre::Vector3 &player, Ogre::Vector3 &camera) { - mCamera->getParentSceneNode ()->needUpdate(true); + mCamera->getParentSceneNode()->needUpdate(true); camera = mCamera->getRealPosition(); player = mTrackingPtr.getRefData().getBaseNode()->getPosition(); @@ -325,15 +336,6 @@ namespace MWRender mFreeLook = enable; } - void Camera::setLowHeight(bool low) - { - if (low) { - mCameraNode->setPosition(0.f, 0.f, mHeight * 0.85); - } else { - mCameraNode->setPosition(0.f, 0.f, mHeight); - } - } - bool Camera::isVanityOrPreviewModeEnabled() { return mPreviewMode || mVanity.enabled; diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index ad5e35f93..3418efcc9 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -46,8 +46,6 @@ namespace MWRender /// Updates sound manager listener data void updateListener(); - void setLowHeight(bool low = true); - public: Camera(Ogre::Camera *camera); ~Camera(); @@ -80,6 +78,8 @@ namespace MWRender bool isFirstPerson() const { return !(mVanity.enabled || mPreviewMode || !mFirstPersonView); } + void processViewChange(); + void update(float duration); /// Set camera distance for current mode. Don't work on 1st person view. @@ -93,7 +93,6 @@ namespace MWRender void setAnimation(NpcAnimation *anim); - void setHeight(float height); float getHeight(); /// Stores player and camera world positions in passed arguments diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index e9ecedc55..c6e6e158e 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -44,9 +44,14 @@ namespace MWRender { mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); - /// \todo Read the fallback values from INIImporter (Inventory:Directional*) + // This is a dummy light to turn off shadows without having to use a separate set of shaders Ogre::Light* l = mSceneMgr->createLight(); l->setType (Ogre::Light::LT_DIRECTIONAL); + l->setDiffuseColour (Ogre::ColourValue(0,0,0)); + + /// \todo Read the fallback values from INIImporter (Inventory:Directional*) + l = mSceneMgr->createLight(); + l->setType (Ogre::Light::LT_DIRECTIONAL); l->setDirection (Ogre::Vector3(0.3, -0.7, 0.3)); l->setDiffuseColour (Ogre::ColourValue(1,1,1)); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b5f2ea031..f2df5ccd5 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -14,6 +14,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "renderconst.hpp" +#include "camera.hpp" namespace MWRender @@ -98,17 +99,22 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, MWWor Misc::StringUtils::toLower(mBodyPrefix); bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); + std::string smodel = (viewMode != VM_FirstPerson) ? + (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif") : + (!isBeast ? "meshes\\base_anim.1st.nif" : "meshes\\base_animkna.1st.nif") ; setObjectRoot(node, smodel, true); - addAnimSource(smodel); - if(mBodyPrefix.find("argonian") != std::string::npos) - addAnimSource("meshes\\argonian_swimkna.nif"); - else if(!mNpc->isMale() && !isBeast) - addAnimSource("meshes\\base_anim_female.nif"); - if(mNpc->mModel.length() > 0) - addAnimSource("meshes\\"+mNpc->mModel); - if(mViewMode == VM_FirstPerson) + if(mViewMode != VM_FirstPerson) + { + addAnimSource(smodel); + if(mBodyPrefix.find("argonian") != std::string::npos) + addAnimSource("meshes\\argonian_swimkna.nif"); + else if(!mNpc->isMale() && !isBeast) + addAnimSource("meshes\\base_anim_female.nif"); + if(mNpc->mModel.length() > 0) + addAnimSource("meshes\\"+mNpc->mModel); + } + else { /* A bit counter-intuitive, but unlike third-person anims, it seems * beast races get both base_anim.1st.nif and base_animkna.1st.nif. @@ -128,20 +134,28 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) assert(viewMode != VM_HeadOnly); mViewMode = viewMode; + clearAnimSources(); + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get<ESM::Race>().find(mNpc->mRace); - bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); - clearAnimSources(); - addAnimSource(smodel); - if(mBodyPrefix.find("argonian") != std::string::npos) - addAnimSource("meshes\\argonian_swimkna.nif"); - else if(!mNpc->isMale() && !isBeast) - addAnimSource("meshes\\base_anim_female.nif"); - if(mNpc->mModel.length() > 0) - addAnimSource("meshes\\"+mNpc->mModel); - if(mViewMode == VM_FirstPerson) + bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; + std::string smodel = (viewMode != VM_FirstPerson) ? + (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif") : + (!isBeast ? "meshes\\base_anim.1st.nif" : "meshes\\base_animkna.1st.nif") ; + setObjectRoot(mInsert->getParentSceneNode(), smodel, true); + + if(mViewMode != VM_FirstPerson) + { + addAnimSource(smodel); + if(mBodyPrefix.find("argonian") != std::string::npos) + addAnimSource("meshes\\argonian_swimkna.nif"); + else if(!mNpc->isMale() && !isBeast) + addAnimSource("meshes\\base_anim_female.nif"); + if(mNpc->mModel.length() > 0) + addAnimSource("meshes\\"+mNpc->mModel); + } + else { /* A bit counter-intuitive, but unlike third-person anims, it seems * beast races get both base_anim.1st.nif and base_animkna.1st.nif. @@ -197,19 +211,11 @@ void NpcAnimation::updateParts(bool forceupdate) if(!forceupdate) return; - /* FIXME: Remove this once we figure out how to show what in first-person */ - if(mViewMode == VM_FirstPerson) - { - for(size_t i = 0;i < slotlistsize;i++) - this->*slotlist[i].mPart = inv.getSlot(slotlist[i].mSlot); - return; - } - for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++) { - MWWorld::ContainerStoreIterator iter = inv.getSlot(slotlist[i].mSlot); + MWWorld::ContainerStoreIterator store = inv.getSlot(slotlist[i].mSlot); - this->*slotlist[i].mPart = iter; + this->*slotlist[i].mPart = store; removePartGroup(slotlist[i].mSlot); if(this->*slotlist[i].mPart == inv.end()) @@ -219,7 +225,6 @@ void NpcAnimation::updateParts(bool forceupdate) removeIndividualPart(ESM::PRT_Hair); int prio = 1; - MWWorld::ContainerStoreIterator &store = this->*slotlist[i].mPart; if(store->getTypeName() == typeid(ESM::Clothing).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 0; @@ -279,32 +284,34 @@ void NpcAnimation::updateParts(bool forceupdate) std::pair<std::string, int> thisCombination = std::make_pair(race, flags); if (sRaceMapping.find(thisCombination) == sRaceMapping.end()) { - static std::map<int, int> bodypartMap; - if(bodypartMap.size() == 0) + typedef std::multimap<ESM::BodyPart::MeshPart,ESM::PartReferenceType> BodyPartMapType; + static BodyPartMapType sBodyPartMap; + if(sBodyPartMap.size() == 0) { - bodypartMap[ESM::PRT_Neck] = ESM::BodyPart::MP_Neck; - bodypartMap[ESM::PRT_Cuirass] = ESM::BodyPart::MP_Chest; - bodypartMap[ESM::PRT_Groin] = ESM::BodyPart::MP_Groin; - bodypartMap[ESM::PRT_RHand] = ESM::BodyPart::MP_Hand; - bodypartMap[ESM::PRT_LHand] = ESM::BodyPart::MP_Hand; - bodypartMap[ESM::PRT_RWrist] = ESM::BodyPart::MP_Wrist; - bodypartMap[ESM::PRT_LWrist] = ESM::BodyPart::MP_Wrist; - bodypartMap[ESM::PRT_RForearm] = ESM::BodyPart::MP_Forearm; - bodypartMap[ESM::PRT_LForearm] = ESM::BodyPart::MP_Forearm; - bodypartMap[ESM::PRT_RUpperarm] = ESM::BodyPart::MP_Upperarm; - bodypartMap[ESM::PRT_LUpperarm] = ESM::BodyPart::MP_Upperarm; - bodypartMap[ESM::PRT_RFoot] = ESM::BodyPart::MP_Foot; - bodypartMap[ESM::PRT_LFoot] = ESM::BodyPart::MP_Foot; - bodypartMap[ESM::PRT_RAnkle] = ESM::BodyPart::MP_Ankle; - bodypartMap[ESM::PRT_LAnkle] = ESM::BodyPart::MP_Ankle; - bodypartMap[ESM::PRT_RKnee] = ESM::BodyPart::MP_Knee; - bodypartMap[ESM::PRT_LKnee] = ESM::BodyPart::MP_Knee; - bodypartMap[ESM::PRT_RLeg] = ESM::BodyPart::MP_Upperleg; - bodypartMap[ESM::PRT_LLeg] = ESM::BodyPart::MP_Upperleg; - bodypartMap[ESM::PRT_Tail] = ESM::BodyPart::MP_Tail; + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail)); } - sRaceMapping[thisCombination].resize(ESM::PRT_Count, NULL); + std::vector<const ESM::BodyPart*> &parts = sRaceMapping[thisCombination]; + parts.resize(ESM::PRT_Count, NULL); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>(); @@ -325,29 +332,55 @@ void NpcAnimation::updateParts(bool forceupdate) && bodypart.mId[bodypart.mId.size()-3] == '1' && bodypart.mId[bodypart.mId.size()-2] == 's' && bodypart.mId[bodypart.mId.size()-1] == 't'; - if (firstPerson != (mViewMode == VM_FirstPerson)) + if(firstPerson != (mViewMode == VM_FirstPerson)) + { + if(mViewMode == VM_FirstPerson && (bodypart.mData.mPart == ESM::BodyPart::MP_Hand || + bodypart.mData.mPart == ESM::BodyPart::MP_Wrist || + bodypart.mData.mPart == ESM::BodyPart::MP_Forearm || + bodypart.mData.mPart == ESM::BodyPart::MP_Upperarm)) + { + /* Allow 3rd person skins as a fallback for the arms if 1st person is missing. */ + BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + if(!parts[bIt->second]) + parts[bIt->second] = &*it; + bIt++; + } + } continue; - for (std::map<int, int>::iterator bIt = bodypartMap.begin(); bIt != bodypartMap.end(); ++bIt ) - if (bIt->second == bodypart.mData.mPart) - sRaceMapping[thisCombination][bIt->first] = &*it; + } + BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + parts[bIt->second] = &*it; + bIt++; + } } } - for (int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part) + const std::vector<const ESM::BodyPart*> &parts = sRaceMapping[thisCombination]; + for(int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part) { - const ESM::BodyPart* bodypart = sRaceMapping[thisCombination][part]; - if (mPartPriorities[part] < 1 && bodypart) - addOrReplaceIndividualPart(part, -1,1, "meshes\\"+bodypart->mModel); + if(mPartPriorities[part] < 1) + { + const ESM::BodyPart* bodypart = parts[part]; + if(bodypart) + addOrReplaceIndividualPart(part, -1,1, "meshes\\"+bodypart->mModel); + } } } NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename) { NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); - setRenderProperties(objects, mVisibilityFlags, RQG_Main, RQG_Alpha); + setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha); for(size_t i = 0;i < objects.mEntities.size();i++) - objects.mEntities[i]->getUserObjectBindings().setUserAny(Ogre::Any(group)); + { + Ogre::Entity *ent = objects.mEntities[i]; + ent->getUserObjectBindings().setUserAny(Ogre::Any(group)); + } for(size_t i = 0;i < objects.mParticles.size();i++) objects.mParticles[i]->getUserObjectBindings().setUserAny(Ogre::Any(group)); @@ -382,6 +415,13 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) Ogre::Vector3 ret = Animation::runAnimation(timepassed); Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); + if(mViewMode == VM_FirstPerson && mCamera) + { + float pitch = mCamera->getPitch(); + Ogre::Node *node = baseinst->getBone("Bip01 Neck"); + node->pitch(Ogre::Radian(pitch*0.75f), Ogre::Node::TS_WORLD); + } + for(size_t i = 0;i < sPartListSize;i++) { Ogre::Entity *ent = mObjectParts[i].mSkelBase; @@ -458,9 +498,31 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM:: { const ESM::BodyPart *bodypart = 0; if(!mNpc->isMale() && !part->mFemale.empty()) + { bodypart = partStore.search(part->mFemale+ext); + if(!bodypart && mViewMode == VM_FirstPerson) + { + bodypart = partStore.search(part->mFemale); + if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || + bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || + bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || + bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) + bodypart = NULL; + } + } if(!bodypart && !part->mMale.empty()) + { bodypart = partStore.search(part->mMale+ext); + if(!bodypart && mViewMode == VM_FirstPerson) + { + bodypart = partStore.search(part->mMale); + if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || + bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || + bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || + bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) + bodypart = NULL; + } + } if(bodypart) addOrReplaceIndividualPart(part->mPart, group, priority, "meshes\\"+bodypart->mModel); @@ -472,8 +534,7 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM:: void NpcAnimation::showWeapons(bool showWeapon) { mShowWeapons = showWeapon; - if(showWeapon && - mViewMode != VM_FirstPerson/* FIXME: Remove this once first-person bodies work */) + if(showWeapon) { MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); mWeapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); diff --git a/apps/openmw/mwrender/renderconst.hpp b/apps/openmw/mwrender/renderconst.hpp index 1d2cdf1ea..44599ebee 100644 --- a/apps/openmw/mwrender/renderconst.hpp +++ b/apps/openmw/mwrender/renderconst.hpp @@ -59,6 +59,9 @@ enum VisibilityFlags // overlays, we only want these on the main render target RV_Overlay = 1024, + // First person meshes do not cast shadows + RV_FirstPerson = 2048, + RV_Map = RV_Terrain + RV_Statics + RV_StaticsSmall + RV_Misc + RV_Water };