From 8444ee9981226e6f739b4df8ced94ef7bd9f7abe Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Fri, 13 Jul 2018 21:48:59 -0500 Subject: [PATCH 01/18] Start rendering npc's --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/actor.cpp | 196 +++++++++++++++++++++++++++++ apps/opencs/view/render/actor.hpp | 51 ++++++++ apps/opencs/view/render/object.cpp | 25 +++- apps/openmw/mwrender/animation.cpp | 34 +---- components/CMakeLists.txt | 2 +- components/esm/mappings.cpp | 134 ++++++++++++++++++++ components/esm/mappings.hpp | 16 +++ components/sceneutil/visitor.cpp | 19 ++- components/sceneutil/visitor.hpp | 30 +++++ 10 files changed, 471 insertions(+), 38 deletions(-) create mode 100644 apps/opencs/view/render/actor.cpp create mode 100644 apps/opencs/view/render/actor.hpp create mode 100644 components/esm/mappings.cpp create mode 100644 components/esm/mappings.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f2821f184a..26713f9256 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -93,7 +93,7 @@ opencs_units (view/render opencs_units_noqt (view/render lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase - cellarrow cellmarker cellborder pathgrid + cellarrow cellmarker cellborder pathgrid actor ) opencs_hdrs_noqt (view/render diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp new file mode 100644 index 0000000000..1273b94c64 --- /dev/null +++ b/apps/opencs/view/render/actor.cpp @@ -0,0 +1,196 @@ +#include "actor.hpp" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../../model/world/data.hpp" + +namespace CSVRender +{ + Actor::Actor(const std::string& id, int type, CSMWorld::Data& data) + : mId(id) + , mType(type) + , mData(data) + , mSkeleton(nullptr) + , mBaseNode(new osg::Group()) + { + } + + osg::Group* Actor::getBaseNode() + { + return mBaseNode; + } + + void Actor::update() + { + const std::string MeshPrefix = "meshes\\"; + const unsigned int FemaleFlag = ESM::BodyPart::BPF_Female; + + auto& bodyParts = mData.getBodyParts(); + auto& races = mData.getRaces(); + auto& referenceables = mData.getReferenceables(); + auto sceneMgr = mData.getResourceSystem()->getSceneManager(); + + + // Remove children + mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); + + // Npcs and creatures are handled differently + if (mType == CSMWorld::UniversalId::Type_Npc) + { + auto& npc = dynamic_cast& >(referenceables.getRecord(mId)).get(); + + auto isBeast = [&](std::string race) -> bool { + int index = races.searchId(race); + if (index != -1 && !races.getRecord(index).isDeleted()) + return races.getRecord(index).get().mData.mFlags & ESM::Race::Beast; + else + return false; + }; + + // Load skeleton + std::string skeletonResource; + if (isBeast(npc.mRace)) { + std::cout << "is beast\n"; + skeletonResource = "base_animkna.nif"; + } + else if (npc.isMale()) { + std::cout << "is male\n"; + skeletonResource = "base_anim.nif"; + } + else { + std::cout << "is female\n"; + skeletonResource = "base_anim_female.nif"; + } + + std::string skeletonModel = MeshPrefix + skeletonResource; + skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); + { + osg::ref_ptr temp = sceneMgr->getInstance(skeletonModel); + mSkeleton = dynamic_cast(temp.get()); + if (!mSkeleton) + { + mSkeleton = new SceneUtil::Skeleton(); + mSkeleton->addChild(temp); + } + mBaseNode->addChild(mSkeleton); + } + + // Map bone names to bones + SceneUtil::NodeMapVisitor::NodeMap nodeMap; + SceneUtil::NodeMapVisitor nmVisitor(nodeMap); + mSkeleton->accept(nmVisitor); + + if (!npc.isMale()) { + for (auto it : nodeMap) { + std::cout << it.first << "\n"; + } + } + + + // Female mesh has some drawables attached, get rid of them + SceneUtil::HideDrawablesVisitor hdVisitor; + mSkeleton->accept(hdVisitor); + + // Convenience method to retrieve the mesh name of a body part + auto getBodyPartMesh = [&](std::string bpName) -> std::string { + int index = bodyParts.searchId(bpName); + if (index != -1 && !bodyParts.getRecord(index).isDeleted()) + return MeshPrefix + bodyParts.getRecord(index).get().mModel; + else + return ""; + }; + + using BPRaceKey = std::tuple; + using RaceToBPMap = std::map; + // Convenience method to generate a map from body part + race to mesh name + auto genRaceToBodyPartMap = [&](RaceToBPMap& bpMap) { + int size = bodyParts.getSize(); + for (int i = 0; i < size; ++i) + { + auto& record = bodyParts.getRecord(i); + if (!record.isDeleted()) + { + auto& bodyPart = record.get(); + bpMap.emplace( + BPRaceKey(bodyPart.mData.mPart, bodyPart.mData.mFlags & FemaleFlag ? 1 : 0, bodyPart.mRace), + MeshPrefix + bodyPart.mModel); + } + } + }; + + // Generate mapping + RaceToBPMap r2bpMap; + genRaceToBodyPartMap(r2bpMap); + + // Convenience method to add a body part + auto addBodyPart = [&](ESM::PartReferenceType type, std::string mesh) { + // Retrieve mesh name if necessary + if (mesh.empty()) + { + auto meshResult = r2bpMap.find(BPRaceKey(ESM::getMeshPart(type), npc.isMale() ? 0 : 1, npc.mRace)); + if (meshResult != r2bpMap.end()) + { + mesh = meshResult->second; + } + } + + // Attach to skeleton + std::string boneName = ESM::getBoneName(type); + auto node = nodeMap.find(boneName); + if (!mesh.empty() && node != nodeMap.end()) + { + auto instance = sceneMgr->getInstance(mesh); + if (!npc.isMale() && type == ESM::PRT_LHand) { + SceneUtil::NodeMapVisitor::NodeMap handNodeMap; + SceneUtil::NodeMapVisitor nmVisitor(handNodeMap); + instance->accept(nmVisitor); + + std::cout << "Left hand\n"; + for (auto it : handNodeMap) { + std::cout << it.first << std::endl; + } + } + SceneUtil::attach(instance, mSkeleton, boneName, node->second); + } + }; + + // Add body parts + for (unsigned int i = 0; i < ESM::PRT_Count; ++i) + { + auto part = static_cast(i); + switch (part) + { + case ESM::PRT_Head: + addBodyPart(part, getBodyPartMesh(npc.mHead)); + break; + case ESM::PRT_Hair: + addBodyPart(part, getBodyPartMesh(npc.mHair)); + break; + case ESM::PRT_Skirt: + case ESM::PRT_Shield: + case ESM::PRT_RPauldron: + case ESM::PRT_LPauldron: + case ESM::PRT_Weapon: + // No body part mesh associated + break; + default: + addBodyPart(part, ""); + } + } + // Post setup + mSkeleton->markDirty(); + mSkeleton->setActive(SceneUtil::Skeleton::Active); + } + } +} diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp new file mode 100644 index 0000000000..10f4de558d --- /dev/null +++ b/apps/opencs/view/render/actor.hpp @@ -0,0 +1,51 @@ +#ifndef OPENCS_VIEW_RENDER_ACTOR_H +#define OPENCS_VIEW_RENDER_ACTOR_H + +#include + +#include + +namespace osg +{ + class Group; +} + +namespace CSMWorld +{ + class Data; +} + +namespace SceneUtil +{ + class Skeleton; +} + +namespace CSVRender +{ + /// Handles loading an npc or creature + class Actor + { + public: + /// Creates an actor. + /// \param id The referenceable id + /// \param type The record type + /// \param data The data store + Actor(const std::string& id, int type, CSMWorld::Data& data); + + /// Retrieves the base node that meshes are attached to + osg::Group* getBaseNode(); + + /// (Re)creates the npc or creature renderable + void update(); + + private: + std::string mId; + int mType; + CSMWorld::Data& mData; + + SceneUtil::Skeleton* mSkeleton; + osg::ref_ptr mBaseNode; + }; +} + +#endif diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 2b1e3addee..b8e1716504 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -1,6 +1,8 @@ #include "object.hpp" #include +#include +#include #include #include @@ -11,6 +13,7 @@ #include #include #include +#include #include @@ -28,7 +31,9 @@ #include #include #include +#include +#include "actor.hpp" #include "mask.hpp" @@ -84,6 +89,7 @@ void CSVRender::Object::update() const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); int index = referenceables.searchId (mReferenceableId); + int recordType = -1; const ESM::Light* light = NULL; if (index==-1) @@ -96,7 +102,7 @@ void CSVRender::Object::update() referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model)). toString().toUtf8().constData(); - int recordType = + recordType = referenceables.getData (index, referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType)).toInt(); if (recordType == CSMWorld::UniversalId::Type_Light) @@ -112,7 +118,7 @@ void CSVRender::Object::update() model = "marker_creature.nif"; } - if (model.empty()) + if (recordType != CSMWorld::UniversalId::Type_Npc && model.empty()) error = 2; } @@ -126,9 +132,18 @@ void CSVRender::Object::update() { try { - std::string path = "meshes\\" + model; - - mResourceSystem->getSceneManager()->getInstance(path, mBaseNode); + if (recordType == CSMWorld::UniversalId::Type_Npc) + { + std::cout << "recordType: Npc\n"; + Actor actor(mReferenceableId, recordType, mData); + actor.update(); + mBaseNode->addChild(actor.getBaseNode()); + } + else + { + std::string path = "meshes\\" + model; + mResourceSystem->getSceneManager()->getInstance(path, mBaseNode); + } } catch (std::exception& e) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 7410dbf037..e4a7c94b3c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -91,32 +91,6 @@ namespace std::vector > mToRemove; }; - class NodeMapVisitor : public osg::NodeVisitor - { - public: - typedef std::map > NodeMap; - - NodeMapVisitor(NodeMap& map) - : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) - , mMap(map) - {} - - void apply(osg::MatrixTransform& trans) - { - // Take transformation for first found node in file - const std::string nodeName = Misc::StringUtils::lowerCase(trans.getName()); - if (mMap.find(nodeName) == mMap.end()) - { - mMap[nodeName] = &trans; - } - - traverse(trans); - } - - private: - NodeMap& mMap; - }; - NifOsg::TextKeyMap::const_iterator findGroupStart(const NifOsg::TextKeyMap &keys, const std::string &groupname) { NifOsg::TextKeyMap::const_iterator iter(keys.begin()); @@ -468,7 +442,7 @@ namespace MWRender } if (mDone) return; - + // Set the starting time to measure glow duration from if this is a temporary glow if ((mDuration >= 0) && mStartingTime == 0) mStartingTime = nv->getFrameStamp()->getSimulationTime(); @@ -1042,7 +1016,7 @@ namespace MWRender { if (!mNodeMapCreated && mObjectRoot) { - NodeMapVisitor visitor(mNodeMap); + SceneUtil::NodeMapVisitor visitor(mNodeMap); mObjectRoot->accept(visitor); mNodeMapCreated = true; } @@ -1313,7 +1287,7 @@ namespace MWRender if(state.getTime() >= state.mLoopStopTime) break; - } + } if(timepassed <= 0.0f) break; @@ -1528,7 +1502,7 @@ namespace MWRender osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem); mGlowUpdater = glowUpdater; node->addUpdateCallback(glowUpdater); - + // set a texture now so that the ShaderVisitor can find it osg::ref_ptr writableStateSet = NULL; if (!node->getStateSet()) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 2da7c80bfa..e7e0ea0daa 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -77,7 +77,7 @@ add_component_dir (esm loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile - aisequence magiceffects util custommarkerstate stolenitems transport animationstate controlsstate + aisequence magiceffects util custommarkerstate stolenitems transport animationstate controlsstate mappings ) add_component_dir (esmterrain diff --git a/components/esm/mappings.cpp b/components/esm/mappings.cpp new file mode 100644 index 0000000000..440e735739 --- /dev/null +++ b/components/esm/mappings.cpp @@ -0,0 +1,134 @@ +#include "mappings.hpp" + +#include + +namespace ESM +{ + ESM::BodyPart::MeshPart getMeshPart(ESM::PartReferenceType type) + { + switch(type) + { + case ESM::PRT_Head: + return ESM::BodyPart::MP_Head; + case ESM::PRT_Hair: + return ESM::BodyPart::MP_Hair; + case ESM::PRT_Neck: + return ESM::BodyPart::MP_Neck; + case ESM::PRT_Cuirass: + return ESM::BodyPart::MP_Chest; + case ESM::PRT_Groin: + return ESM::BodyPart::MP_Groin; + case ESM::PRT_RHand: + return ESM::BodyPart::MP_Hand; + case ESM::PRT_LHand: + return ESM::BodyPart::MP_Hand; + case ESM::PRT_RWrist: + return ESM::BodyPart::MP_Wrist; + case ESM::PRT_LWrist: + return ESM::BodyPart::MP_Wrist; + case ESM::PRT_RForearm: + return ESM::BodyPart::MP_Forearm; + case ESM::PRT_LForearm: + return ESM::BodyPart::MP_Forearm; + case ESM::PRT_RUpperarm: + return ESM::BodyPart::MP_Upperarm; + case ESM::PRT_LUpperarm: + return ESM::BodyPart::MP_Upperarm; + case ESM::PRT_RFoot: + return ESM::BodyPart::MP_Foot; + case ESM::PRT_LFoot: + return ESM::BodyPart::MP_Foot; + case ESM::PRT_RAnkle: + return ESM::BodyPart::MP_Ankle; + case ESM::PRT_LAnkle: + return ESM::BodyPart::MP_Ankle; + case ESM::PRT_RKnee: + return ESM::BodyPart::MP_Knee; + case ESM::PRT_LKnee: + return ESM::BodyPart::MP_Knee; + case ESM::PRT_RLeg: + return ESM::BodyPart::MP_Upperleg; + case ESM::PRT_LLeg: + return ESM::BodyPart::MP_Upperleg; + case ESM::PRT_Tail: + return ESM::BodyPart::MP_Tail; + default: + throw std::runtime_error("PartReferenceType " + + std::to_string(type) + " not associated with a mesh part"); + } + } + + std::string getBoneName(ESM::PartReferenceType type) + { + switch(type) + { + case ESM::PRT_Head: + return "head"; + case ESM::PRT_Hair: + return "head"; // This is purposeful. + case ESM::PRT_Neck: + return "neck"; + case ESM::PRT_Cuirass: + return "chest"; + case ESM::PRT_Groin: + return "groin"; + case ESM::PRT_Skirt: + return "groin"; + case ESM::PRT_RHand: + return "right hand"; + case ESM::PRT_LHand: + return "left hand"; + case ESM::PRT_RWrist: + return "right wrist"; + case ESM::PRT_LWrist: + return "left wrist"; + case ESM::PRT_Shield: + return "shield bone"; + case ESM::PRT_RForearm: + return "right forearm"; + case ESM::PRT_LForearm: + return "left forearm"; + case ESM::PRT_RUpperarm: + return "right upper arm"; + case ESM::PRT_LUpperarm: + return "left upper arm"; + case ESM::PRT_RFoot: + return "right foot"; + case ESM::PRT_LFoot: + return "left foot"; + case ESM::PRT_RAnkle: + return "right ankle"; + case ESM::PRT_LAnkle: + return "left ankle"; + case ESM::PRT_RKnee: + return "right knee"; + case ESM::PRT_LKnee: + return "left knee"; + case ESM::PRT_RLeg: + return "right upper leg"; + case ESM::PRT_LLeg: + return "left upper leg"; + case ESM::PRT_RPauldron: + return "right clavicle"; + case ESM::PRT_LPauldron: + return "left clavicle"; + case ESM::PRT_Weapon: + return "weapon bone"; + case ESM::PRT_Tail: + return "tail"; + default: + throw std::runtime_error("unknown PartReferenceType"); + } + } + + std::string getMeshFilter(ESM::PartReferenceType type) + { + switch(type) + { + case ESM::PRT_Hair: + return "hair"; + default: + return getBoneName(type); + } + } +} diff --git a/components/esm/mappings.hpp b/components/esm/mappings.hpp new file mode 100644 index 0000000000..f930fef152 --- /dev/null +++ b/components/esm/mappings.hpp @@ -0,0 +1,16 @@ +#ifndef OPENMW_ESM_MAPPINGS_H +#define OPENMW_ESM_MAPPINGS_H + +#include + +#include +#include + +namespace ESM +{ + ESM::BodyPart::MeshPart getMeshPart(ESM::PartReferenceType type); + std::string getBoneName(ESM::PartReferenceType type); + std::string getMeshFilter(ESM::PartReferenceType type); +} + +#endif diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 2f6123e34d..5aaeb459ee 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -1,5 +1,6 @@ #include "visitor.hpp" +#include #include #include @@ -23,7 +24,7 @@ namespace SceneUtil { if (Misc::StringUtils::ciEqual(node.className(), mNameToFind)) mFoundNodes.push_back(&node); - + traverse(node); } @@ -54,4 +55,20 @@ namespace SceneUtil partsys->setFreezeOnCull(false); } + void NodeMapVisitor::apply(osg::MatrixTransform& trans) + { + // Take transformation for first found node in file + const std::string nodeName = Misc::StringUtils::lowerCase(trans.getName()); + if (mMap.find(nodeName) == mMap.end()) + { + mMap[nodeName] = &trans; + } + + traverse(trans); + } + + void HideDrawablesVisitor::apply(osg::Drawable& drawable) + { + drawable.setNodeMask(0); + } } diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index 265fd6d02a..bd3945296d 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H #define OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H +#include #include // Commonly used scene graph visitors @@ -58,6 +59,35 @@ namespace SceneUtil virtual void apply(osg::Drawable& drw); }; + /// Maps names to nodes + class NodeMapVisitor : public osg::NodeVisitor + { + public: + typedef std::map > NodeMap; + + NodeMapVisitor(NodeMap& map) + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + , mMap(map) + {} + + void apply(osg::MatrixTransform& trans); + + private: + NodeMap& mMap; + }; + + /// Hides all attached drawables + class HideDrawablesVisitor : public osg::NodeVisitor + { + public: + HideDrawablesVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + void apply(osg::Drawable& drawable) override; + }; + } #endif From e2ac392a4076c09aeb42d4a1f9213c83cb6d46ad Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Tue, 17 Jul 2018 21:28:05 -0500 Subject: [PATCH 02/18] Move common stuff to scene util, fix errors with 1st person meshes --- apps/opencs/view/render/actor.cpp | 247 ++++++++++++-------------- apps/openmw/mwrender/animation.cpp | 4 +- apps/openmw/mwrender/npcanimation.cpp | 33 +--- components/CMakeLists.txt | 1 + components/sceneutil/actorutil.cpp | 30 ++++ components/sceneutil/actorutil.hpp | 11 ++ components/sceneutil/visitor.cpp | 89 +++++++++- components/sceneutil/visitor.hpp | 40 ++++- 8 files changed, 290 insertions(+), 165 deletions(-) create mode 100644 components/sceneutil/actorutil.cpp create mode 100644 components/sceneutil/actorutil.hpp diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index 1273b94c64..93fff270a0 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -45,152 +46,140 @@ namespace CSVRender // Remove children mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); - // Npcs and creatures are handled differently - if (mType == CSMWorld::UniversalId::Type_Npc) + try { - auto& npc = dynamic_cast& >(referenceables.getRecord(mId)).get(); - - auto isBeast = [&](std::string race) -> bool { - int index = races.searchId(race); - if (index != -1 && !races.getRecord(index).isDeleted()) - return races.getRecord(index).get().mData.mFlags & ESM::Race::Beast; - else - return false; - }; - - // Load skeleton - std::string skeletonResource; - if (isBeast(npc.mRace)) { - std::cout << "is beast\n"; - skeletonResource = "base_animkna.nif"; - } - else if (npc.isMale()) { - std::cout << "is male\n"; - skeletonResource = "base_anim.nif"; - } - else { - std::cout << "is female\n"; - skeletonResource = "base_anim_female.nif"; - } - - std::string skeletonModel = MeshPrefix + skeletonResource; - skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); + // Npcs and creatures are handled differently + if (mType == CSMWorld::UniversalId::Type_Npc) { - osg::ref_ptr temp = sceneMgr->getInstance(skeletonModel); - mSkeleton = dynamic_cast(temp.get()); - if (!mSkeleton) + auto& npc = dynamic_cast& >(referenceables.getRecord(mId)).get(); + auto& race = dynamic_cast& >(races.getRecord(npc.mRace)).get(); + + bool is1stPerson = false; + bool isFemale = !npc.isMale(); + bool isBeast = race.mData.mFlags & ESM::Race::Beast; + bool isWerewolf = false; + + // Load skeleton + std::string skeletonModel = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); + skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); { - mSkeleton = new SceneUtil::Skeleton(); - mSkeleton->addChild(temp); - } - mBaseNode->addChild(mSkeleton); - } - - // Map bone names to bones - SceneUtil::NodeMapVisitor::NodeMap nodeMap; - SceneUtil::NodeMapVisitor nmVisitor(nodeMap); - mSkeleton->accept(nmVisitor); - - if (!npc.isMale()) { - for (auto it : nodeMap) { - std::cout << it.first << "\n"; - } - } - - - // Female mesh has some drawables attached, get rid of them - SceneUtil::HideDrawablesVisitor hdVisitor; - mSkeleton->accept(hdVisitor); - - // Convenience method to retrieve the mesh name of a body part - auto getBodyPartMesh = [&](std::string bpName) -> std::string { - int index = bodyParts.searchId(bpName); - if (index != -1 && !bodyParts.getRecord(index).isDeleted()) - return MeshPrefix + bodyParts.getRecord(index).get().mModel; - else - return ""; - }; - - using BPRaceKey = std::tuple; - using RaceToBPMap = std::map; - // Convenience method to generate a map from body part + race to mesh name - auto genRaceToBodyPartMap = [&](RaceToBPMap& bpMap) { - int size = bodyParts.getSize(); - for (int i = 0; i < size; ++i) - { - auto& record = bodyParts.getRecord(i); - if (!record.isDeleted()) + osg::ref_ptr temp = sceneMgr->getInstance(skeletonModel); + mSkeleton = dynamic_cast(temp.get()); + if (!mSkeleton) { - auto& bodyPart = record.get(); - bpMap.emplace( - BPRaceKey(bodyPart.mData.mPart, bodyPart.mData.mFlags & FemaleFlag ? 1 : 0, bodyPart.mRace), - MeshPrefix + bodyPart.mModel); + mSkeleton = new SceneUtil::Skeleton(); + mSkeleton->addChild(temp); } + mBaseNode->addChild(mSkeleton); } - }; - // Generate mapping - RaceToBPMap r2bpMap; - genRaceToBodyPartMap(r2bpMap); + // Map bone names to bones + SceneUtil::NodeMapVisitor::NodeMap nodeMap; + SceneUtil::NodeMapVisitor nmVisitor(nodeMap); + mSkeleton->accept(nmVisitor); - // Convenience method to add a body part - auto addBodyPart = [&](ESM::PartReferenceType type, std::string mesh) { - // Retrieve mesh name if necessary - if (mesh.empty()) - { - auto meshResult = r2bpMap.find(BPRaceKey(ESM::getMeshPart(type), npc.isMale() ? 0 : 1, npc.mRace)); - if (meshResult != r2bpMap.end()) + // Female mesh has some drawables attached, get rid of them + SceneUtil::CleanObjectRootVisitor cleanVisitor; + mSkeleton->accept(cleanVisitor); + cleanVisitor.remove(); + + // Convenience method to retrieve the mesh name of a body part + auto getBodyPartMesh = [&](std::string bpName) -> std::string { + int index = bodyParts.searchId(bpName); + if (index != -1 && !bodyParts.getRecord(index).isDeleted()) + return MeshPrefix + bodyParts.getRecord(index).get().mModel; + else + return ""; + }; + + using BPRaceKey = std::tuple; + using RaceToBPMap = std::map; + // Convenience method to generate a map from body part + race to mesh name + auto genRaceToBodyPartMap = [&](RaceToBPMap& bpMap) { + int size = bodyParts.getSize(); + for (int i = 0; i < size; ++i) { - mesh = meshResult->second; - } - } + auto& record = bodyParts.getRecord(i); + if (!record.isDeleted()) + { + // Method to check if 1st person part or not + auto is1stPersonPart = [](std::string name) { + return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; + }; - // Attach to skeleton - std::string boneName = ESM::getBoneName(type); - auto node = nodeMap.find(boneName); - if (!mesh.empty() && node != nodeMap.end()) - { - auto instance = sceneMgr->getInstance(mesh); - if (!npc.isMale() && type == ESM::PRT_LHand) { - SceneUtil::NodeMapVisitor::NodeMap handNodeMap; - SceneUtil::NodeMapVisitor nmVisitor(handNodeMap); - instance->accept(nmVisitor); + auto& bodyPart = record.get(); + if (bodyPart.mData.mType != ESM::BodyPart::MT_Skin || is1stPersonPart(bodyPart.mId)) + continue; - std::cout << "Left hand\n"; - for (auto it : handNodeMap) { - std::cout << it.first << std::endl; + bpMap.emplace( + BPRaceKey(bodyPart.mData.mPart, bodyPart.mData.mFlags & FemaleFlag ? 1 : 0, bodyPart.mRace), + MeshPrefix + bodyPart.mModel); } } - SceneUtil::attach(instance, mSkeleton, boneName, node->second); - } - }; + }; - // Add body parts - for (unsigned int i = 0; i < ESM::PRT_Count; ++i) - { - auto part = static_cast(i); - switch (part) + // Generate mapping + RaceToBPMap r2bpMap; + genRaceToBodyPartMap(r2bpMap); + + // Convenience method to add a body part + auto addBodyPart = [&](ESM::PartReferenceType type, std::string mesh) { + // Retrieve mesh name if necessary + if (mesh.empty()) + { + auto meshResult = r2bpMap.find(BPRaceKey(ESM::getMeshPart(type), isFemale ? 1 : 0, npc.mRace)); + if (meshResult != r2bpMap.end()) + { + mesh = meshResult->second; + } + else if (isFemale){ + meshResult = r2bpMap.find(BPRaceKey(ESM::getMeshPart(type), 0, npc.mRace)); + if (meshResult != r2bpMap.end()) + mesh = meshResult->second; + } + } + + // Attach to skeleton + std::string boneName = ESM::getBoneName(type); + auto node = nodeMap.find(boneName); + if (!mesh.empty() && node != nodeMap.end()) + { + auto instance = sceneMgr->getInstance(mesh); + SceneUtil::attach(instance, mSkeleton, boneName, node->second); + } + }; + + // Add body parts + for (unsigned int i = 0; i < ESM::PRT_Count; ++i) { - case ESM::PRT_Head: - addBodyPart(part, getBodyPartMesh(npc.mHead)); - break; - case ESM::PRT_Hair: - addBodyPart(part, getBodyPartMesh(npc.mHair)); - break; - case ESM::PRT_Skirt: - case ESM::PRT_Shield: - case ESM::PRT_RPauldron: - case ESM::PRT_LPauldron: - case ESM::PRT_Weapon: - // No body part mesh associated - break; - default: - addBodyPart(part, ""); + auto part = static_cast(i); + switch (part) + { + case ESM::PRT_Head: + addBodyPart(part, getBodyPartMesh(npc.mHead)); + break; + case ESM::PRT_Hair: + addBodyPart(part, getBodyPartMesh(npc.mHair)); + break; + case ESM::PRT_Skirt: + case ESM::PRT_Shield: + case ESM::PRT_RPauldron: + case ESM::PRT_LPauldron: + case ESM::PRT_Weapon: + // No body part mesh associated + break; + default: + addBodyPart(part, ""); + } } + // Post setup + mSkeleton->markDirty(); + mSkeleton->setActive(SceneUtil::Skeleton::Active); } - // Post setup - mSkeleton->markDirty(); - mSkeleton->setActive(SceneUtil::Skeleton::Active); + } + catch (std::exception& e) + { + std::cout << "Caught exception: " << e.what() << std::endl; } } } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e4a7c94b3c..484f90ab5c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1339,7 +1339,7 @@ namespace MWRender { osg::ref_ptr created = sceneMgr->getInstance(model); - CleanObjectRootVisitor removeDrawableVisitor; + SceneUtil::CleanObjectRootVisitor removeDrawableVisitor; created->accept(removeDrawableVisitor); removeDrawableVisitor.remove(); @@ -1408,7 +1408,7 @@ namespace MWRender if (isCreature) { - RemoveTriBipVisitor removeTriBipVisitor; + SceneUtil::RemoveTriBipVisitor removeTriBipVisitor; mObjectRoot->accept(removeTriBipVisitor); removeTriBipVisitor.remove(); } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 98f8ce892d..2716ea19ba 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -304,7 +305,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr par void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) { assert(viewMode != VM_HeadOnly); - if(mViewMode == viewMode) + if(mViewMode == viewMode) return; mViewMode = viewMode; @@ -451,37 +452,15 @@ void NpcAnimation::updateNpcBase() } } + bool is1stPerson = mViewMode == VM_FirstPerson; bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - std::string smodel; - if (mViewMode != VM_FirstPerson) - { - if (isWerewolf) - smodel = "meshes\\wolf\\skin.nif"; - else if (isBeast) - smodel = "meshes\\base_animkna.nif"; - else if (isFemale) - smodel = "meshes\\base_anim_female.nif"; - else - smodel = "meshes\\base_anim.nif"; - } - else - { - if (isWerewolf) - smodel = "meshes\\wolf\\skin.1st.nif"; - else if (isBeast) - smodel = "meshes\\base_animkna.1st.nif"; - else if (isFemale) - smodel = "meshes\\base_anim_female.1st.nif"; - else - smodel = "meshes\\base_anim.1st.nif"; - } - + std::string smodel = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); smodel = Misc::ResourceHelpers::correctActorModelPath(smodel, mResourceSystem->getVFS()); setObjectRoot(smodel, true, true, false); - if(mViewMode != VM_FirstPerson) + if(!is1stPerson) { const std::string base = "meshes\\xbase_anim.nif"; if (smodel != base) @@ -675,7 +654,7 @@ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const st } osg::Vec3f NpcAnimation::runAnimation(float timepassed) -{ +{ osg::Vec3f ret = Animation::runAnimation(timepassed); mHeadAnimationTime->update(timepassed); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e7e0ea0daa..7af76137c5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -51,6 +51,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer + actorutil ) add_component_dir (nif diff --git a/components/sceneutil/actorutil.cpp b/components/sceneutil/actorutil.cpp new file mode 100644 index 0000000000..988a61f60e --- /dev/null +++ b/components/sceneutil/actorutil.cpp @@ -0,0 +1,30 @@ +#include "actorutil.hpp" + +namespace SceneUtil +{ + std::string getActorSkeleton(bool firstPerson, bool isFemale, bool isBeast, bool isWerewolf) + { + if (!firstPerson) + { + if (isWerewolf) + return "meshes\\wolf\\skin.nif"; + else if (isBeast) + return "meshes\\base_animkna.nif"; + else if (isFemale) + return "meshes\\base_anim_female.nif"; + else + return "meshes\\base_anim.nif"; + } + else + { + if (isWerewolf) + return "meshes\\wolf\\skin.1st.nif"; + else if (isBeast) + return "meshes\\base_animkna.1st.nif"; + else if (isFemale) + return "meshes\\base_anim_female.1st.nif"; + else + return "meshes\\base_anim.1st.nif"; + } + } +} diff --git a/components/sceneutil/actorutil.hpp b/components/sceneutil/actorutil.hpp new file mode 100644 index 0000000000..7bdbbaa922 --- /dev/null +++ b/components/sceneutil/actorutil.hpp @@ -0,0 +1,11 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_ACTORUTIL_HPP +#define OPENMW_COMPONENTS_SCENEUTIL_ACTORUTIL_HPP + +#include + +namespace SceneUtil +{ + std::string getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf); +} + +#endif diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 5aaeb459ee..07be1608e6 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -1,5 +1,7 @@ #include "visitor.hpp" +#include + #include #include @@ -67,8 +69,91 @@ namespace SceneUtil traverse(trans); } - void HideDrawablesVisitor::apply(osg::Drawable& drawable) + void RemoveVisitor::remove() { - drawable.setNodeMask(0); + for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) + { + if (!it->second->removeChild(it->first)) + std::cerr << "error removing " << it->first->getName() << std::endl; + } + } + + void CleanObjectRootVisitor::apply(osg::Drawable& drw) + { + applyDrawable(drw); + } + + void CleanObjectRootVisitor::apply(osg::Group& node) + { + applyNode(node); + } + + void CleanObjectRootVisitor::apply(osg::MatrixTransform& node) + { + applyNode(node); + } + + void CleanObjectRootVisitor::apply(osg::Node& node) + { + applyNode(node); + } + + void CleanObjectRootVisitor::applyNode(osg::Node& node) + { + if (node.getStateSet()) + node.setStateSet(NULL); + + if (node.getNodeMask() == 0x1 && node.getNumParents() == 1) + mToRemove.push_back(std::make_pair(&node, node.getParent(0))); + else + traverse(node); + } + + void CleanObjectRootVisitor::applyDrawable(osg::Node& node) + { + osg::NodePath::iterator parent = getNodePath().end()-2; + // We know that the parent is a Group because only Groups can have children. + osg::Group* parentGroup = static_cast(*parent); + + // Try to prune nodes that would be empty after the removal + if (parent != getNodePath().begin()) + { + // This could be extended to remove the parent's parent, and so on if they are empty as well. + // But for NIF files, there won't be a benefit since only TriShapes can be set to STATIC dataVariance. + osg::Group* parentParent = static_cast(*(parent - 1)); + if (parentGroup->getNumChildren() == 1 && parentGroup->getDataVariance() == osg::Object::STATIC) + { + mToRemove.push_back(std::make_pair(parentGroup, parentParent)); + return; + } + } + + mToRemove.push_back(std::make_pair(&node, parentGroup)); + } + + void RemoveTriBipVisitor::apply(osg::Drawable& drw) + { + applyImpl(drw); + } + + void RemoveTriBipVisitor::apply(osg::Group& node) + { + traverse(node); + } + + void RemoveTriBipVisitor::apply(osg::MatrixTransform& node) + { + traverse(node); + } + + void RemoveTriBipVisitor::applyImpl(osg::Node& node) + { + const std::string toFind = "tri bip"; + if (Misc::StringUtils::ciCompareLen(node.getName(), toFind, toFind.size()) == 0) + { + osg::Group* parent = static_cast(*(getNodePath().end()-2)); + // Not safe to remove in apply(), since the visitor is still iterating the child list + mToRemove.push_back(std::make_pair(&node, parent)); + } } } diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index bd3945296d..3e9a565c03 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -68,7 +68,8 @@ namespace SceneUtil NodeMapVisitor(NodeMap& map) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) , mMap(map) - {} + { + } void apply(osg::MatrixTransform& trans); @@ -76,18 +77,47 @@ namespace SceneUtil NodeMap& mMap; }; - /// Hides all attached drawables - class HideDrawablesVisitor : public osg::NodeVisitor + /// @brief Base class for visitors that remove nodes from a scene graph. + /// Subclasses need to fill the mToRemove vector. + /// To use, node->accept(removeVisitor); removeVisitor.remove(); + class RemoveVisitor : public osg::NodeVisitor { public: - HideDrawablesVisitor() + RemoveVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } - void apply(osg::Drawable& drawable) override; + void remove(); + + protected: + // + typedef std::vector > RemoveVec; + std::vector > mToRemove; }; + // Removes all drawables from a graph. + class CleanObjectRootVisitor : public RemoveVisitor + { + public: + virtual void apply(osg::Drawable& drw); + virtual void apply(osg::Group& node); + virtual void apply(osg::MatrixTransform& node); + virtual void apply(osg::Node& node); + + void applyNode(osg::Node& node); + void applyDrawable(osg::Node& node); + }; + + class RemoveTriBipVisitor : public RemoveVisitor + { + public: + virtual void apply(osg::Drawable& drw); + virtual void apply(osg::Group& node); + virtual void apply(osg::MatrixTransform& node); + + void applyImpl(osg::Node& node); + }; } #endif From 6b42f37918271cdd41000e141e8f597b69b82f5b Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Sun, 22 Jul 2018 15:38:30 -0500 Subject: [PATCH 03/18] Handle creatures too --- apps/opencs/view/render/actor.cpp | 284 ++++++++++++++++------------- apps/opencs/view/render/actor.hpp | 8 + apps/opencs/view/render/object.cpp | 5 +- 3 files changed, 167 insertions(+), 130 deletions(-) diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index 93fff270a0..3691085e3c 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -18,6 +18,8 @@ namespace CSVRender { + const std::string Actor::MeshPrefix = "meshes\\"; + Actor::Actor(const std::string& id, int type, CSMWorld::Data& data) : mId(id) , mType(type) @@ -34,7 +36,42 @@ namespace CSVRender void Actor::update() { - const std::string MeshPrefix = "meshes\\"; + try + { + mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); + + if (mType == CSMWorld::UniversalId::Type_Npc) + updateNpc(); + else if (mType == CSMWorld::UniversalId::Type_Creature) + updateCreature(); + } + catch (std::exception& e) + { + std::cout << "Caught exception: " << e.what() << std::endl; + } + } + + void Actor::updateCreature() + { + auto& referenceables = mData.getReferenceables(); + + auto& creature = dynamic_cast& >(referenceables.getRecord(mId)).get(); + + std::string skeletonModel = MeshPrefix + creature.mModel; + skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); + loadSkeleton(skeletonModel); + + SceneUtil::RemoveTriBipVisitor removeTriBipVisitor; + mSkeleton->accept(removeTriBipVisitor); + removeTriBipVisitor.remove(); + + // Post setup + mSkeleton->markDirty(); + mSkeleton->setActive(SceneUtil::Skeleton::Active); + } + + void Actor::updateNpc() + { const unsigned int FemaleFlag = ESM::BodyPart::BPF_Female; auto& bodyParts = mData.getBodyParts(); @@ -42,144 +79,137 @@ namespace CSVRender auto& referenceables = mData.getReferenceables(); auto sceneMgr = mData.getResourceSystem()->getSceneManager(); + auto& npc = dynamic_cast& >(referenceables.getRecord(mId)).get(); + auto& race = dynamic_cast& >(races.getRecord(npc.mRace)).get(); - // Remove children - mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); + bool is1stPerson = false; + bool isFemale = !npc.isMale(); + bool isBeast = race.mData.mFlags & ESM::Race::Beast; + bool isWerewolf = false; - try - { - // Npcs and creatures are handled differently - if (mType == CSMWorld::UniversalId::Type_Npc) + // Load skeleton + std::string skeletonModel = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); + skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); + loadSkeleton(skeletonModel); + + // Get rid of the extra attachments + SceneUtil::CleanObjectRootVisitor cleanVisitor; + mSkeleton->accept(cleanVisitor); + cleanVisitor.remove(); + + // Map bone names to bones + SceneUtil::NodeMapVisitor::NodeMap nodeMap; + SceneUtil::NodeMapVisitor nmVisitor(nodeMap); + mSkeleton->accept(nmVisitor); + + using BPRaceKey = std::tuple; + using RaceToBPMap = std::map; + // Convenience method to generate a map from body part + race to mesh name + auto genRaceToBodyPartMap = [&](RaceToBPMap& bpMap) { + int size = bodyParts.getSize(); + for (int i = 0; i < size; ++i) { - auto& npc = dynamic_cast& >(referenceables.getRecord(mId)).get(); - auto& race = dynamic_cast& >(races.getRecord(npc.mRace)).get(); - - bool is1stPerson = false; - bool isFemale = !npc.isMale(); - bool isBeast = race.mData.mFlags & ESM::Race::Beast; - bool isWerewolf = false; - - // Load skeleton - std::string skeletonModel = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); - skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); + auto& record = bodyParts.getRecord(i); + if (!record.isDeleted()) { - osg::ref_ptr temp = sceneMgr->getInstance(skeletonModel); - mSkeleton = dynamic_cast(temp.get()); - if (!mSkeleton) - { - mSkeleton = new SceneUtil::Skeleton(); - mSkeleton->addChild(temp); - } - mBaseNode->addChild(mSkeleton); + // Method to check if 1st person part or not + auto is1stPersonPart = [](std::string name) { + return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; + }; + + auto& bodyPart = record.get(); + if (bodyPart.mData.mType != ESM::BodyPart::MT_Skin || is1stPersonPart(bodyPart.mId)) + continue; + + bpMap.emplace( + BPRaceKey(bodyPart.mData.mPart, bodyPart.mData.mFlags & FemaleFlag ? 1 : 0, bodyPart.mRace), + MeshPrefix + bodyPart.mModel); } + } + }; - // Map bone names to bones - SceneUtil::NodeMapVisitor::NodeMap nodeMap; - SceneUtil::NodeMapVisitor nmVisitor(nodeMap); - mSkeleton->accept(nmVisitor); + // Generate mapping + RaceToBPMap r2bpMap; + genRaceToBodyPartMap(r2bpMap); - // Female mesh has some drawables attached, get rid of them - SceneUtil::CleanObjectRootVisitor cleanVisitor; - mSkeleton->accept(cleanVisitor); - cleanVisitor.remove(); - - // Convenience method to retrieve the mesh name of a body part - auto getBodyPartMesh = [&](std::string bpName) -> std::string { - int index = bodyParts.searchId(bpName); - if (index != -1 && !bodyParts.getRecord(index).isDeleted()) - return MeshPrefix + bodyParts.getRecord(index).get().mModel; - else - return ""; - }; - - using BPRaceKey = std::tuple; - using RaceToBPMap = std::map; - // Convenience method to generate a map from body part + race to mesh name - auto genRaceToBodyPartMap = [&](RaceToBPMap& bpMap) { - int size = bodyParts.getSize(); - for (int i = 0; i < size; ++i) - { - auto& record = bodyParts.getRecord(i); - if (!record.isDeleted()) - { - // Method to check if 1st person part or not - auto is1stPersonPart = [](std::string name) { - return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; - }; - - auto& bodyPart = record.get(); - if (bodyPart.mData.mType != ESM::BodyPart::MT_Skin || is1stPersonPart(bodyPart.mId)) - continue; - - bpMap.emplace( - BPRaceKey(bodyPart.mData.mPart, bodyPart.mData.mFlags & FemaleFlag ? 1 : 0, bodyPart.mRace), - MeshPrefix + bodyPart.mModel); - } - } - }; - - // Generate mapping - RaceToBPMap r2bpMap; - genRaceToBodyPartMap(r2bpMap); - - // Convenience method to add a body part - auto addBodyPart = [&](ESM::PartReferenceType type, std::string mesh) { - // Retrieve mesh name if necessary - if (mesh.empty()) - { - auto meshResult = r2bpMap.find(BPRaceKey(ESM::getMeshPart(type), isFemale ? 1 : 0, npc.mRace)); - if (meshResult != r2bpMap.end()) - { - mesh = meshResult->second; - } - else if (isFemale){ - meshResult = r2bpMap.find(BPRaceKey(ESM::getMeshPart(type), 0, npc.mRace)); - if (meshResult != r2bpMap.end()) - mesh = meshResult->second; - } - } - - // Attach to skeleton - std::string boneName = ESM::getBoneName(type); - auto node = nodeMap.find(boneName); - if (!mesh.empty() && node != nodeMap.end()) - { - auto instance = sceneMgr->getInstance(mesh); - SceneUtil::attach(instance, mSkeleton, boneName, node->second); - } - }; - - // Add body parts - for (unsigned int i = 0; i < ESM::PRT_Count; ++i) + // Convenience method to add a body part + auto addBodyPart = [&](ESM::PartReferenceType type, std::string mesh) { + // Retrieve mesh name if necessary + if (mesh.empty()) + { + auto meshResult = r2bpMap.find(BPRaceKey(ESM::getMeshPart(type), isFemale ? 1 : 0, npc.mRace)); + if (meshResult != r2bpMap.end()) { - auto part = static_cast(i); - switch (part) - { - case ESM::PRT_Head: - addBodyPart(part, getBodyPartMesh(npc.mHead)); - break; - case ESM::PRT_Hair: - addBodyPart(part, getBodyPartMesh(npc.mHair)); - break; - case ESM::PRT_Skirt: - case ESM::PRT_Shield: - case ESM::PRT_RPauldron: - case ESM::PRT_LPauldron: - case ESM::PRT_Weapon: - // No body part mesh associated - break; - default: - addBodyPart(part, ""); - } + mesh = meshResult->second; } - // Post setup - mSkeleton->markDirty(); - mSkeleton->setActive(SceneUtil::Skeleton::Active); + else if (isFemale){ + meshResult = r2bpMap.find(BPRaceKey(ESM::getMeshPart(type), 0, npc.mRace)); + if (meshResult != r2bpMap.end()) + mesh = meshResult->second; + } + } + + // Attach to skeleton + std::string boneName = ESM::getBoneName(type); + auto node = nodeMap.find(boneName); + if (!mesh.empty() && node != nodeMap.end()) + { + auto instance = sceneMgr->getInstance(mesh); + SceneUtil::attach(instance, mSkeleton, boneName, node->second); + } + }; + + // Add body parts + for (unsigned int i = 0; i < ESM::PRT_Count; ++i) + { + auto part = static_cast(i); + switch (part) + { + case ESM::PRT_Head: + addBodyPart(part, getBodyPartMesh(npc.mHead)); + break; + case ESM::PRT_Hair: + addBodyPart(part, getBodyPartMesh(npc.mHair)); + break; + case ESM::PRT_Skirt: + case ESM::PRT_Shield: + case ESM::PRT_RPauldron: + case ESM::PRT_LPauldron: + case ESM::PRT_Weapon: + // No body part mesh associated + break; + default: + addBodyPart(part, ""); } } - catch (std::exception& e) + + // Post setup + mSkeleton->markDirty(); + mSkeleton->setActive(SceneUtil::Skeleton::Active); + } + + void Actor::loadSkeleton(const std::string& model) + { + auto sceneMgr = mData.getResourceSystem()->getSceneManager(); + + osg::ref_ptr temp = sceneMgr->getInstance(model); + mSkeleton = dynamic_cast(temp.get()); + if (!mSkeleton) { - std::cout << "Caught exception: " << e.what() << std::endl; + mSkeleton = new SceneUtil::Skeleton(); + mSkeleton->addChild(temp); } + mBaseNode->addChild(mSkeleton); + } + + std::string Actor::getBodyPartMesh(const std::string& bodyPartId) + { + const auto& bodyParts = mData.getBodyParts(); + + int index = bodyParts.searchId(bodyPartId); + if (index != -1 && !bodyParts.getRecord(index).isDeleted()) + return MeshPrefix + bodyParts.getRecord(index).get().mModel; + else + return ""; } } diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index 10f4de558d..b4e65ff2b6 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -39,6 +39,14 @@ namespace CSVRender void update(); private: + void loadSkeleton(const std::string& model); + void updateCreature(); + void updateNpc(); + + std::string getBodyPartMesh(const std::string& bodyPartId); + + static const std::string MeshPrefix; + std::string mId; int mType; CSMWorld::Data& mData; diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index b8e1716504..dbfd595b6a 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -132,9 +132,8 @@ void CSVRender::Object::update() { try { - if (recordType == CSMWorld::UniversalId::Type_Npc) + if (recordType == CSMWorld::UniversalId::Type_Npc || recordType == CSMWorld::UniversalId::Type_Creature) { - std::cout << "recordType: Npc\n"; Actor actor(mReferenceableId, recordType, mData); actor.update(); mBaseNode->addChild(actor.getBaseNode()); @@ -147,8 +146,8 @@ void CSVRender::Object::update() } catch (std::exception& e) { - // TODO: use error marker mesh Log(Debug::Error) << e.what(); + mBaseNode->addChild(createErrorCube()); } } From 97ac0a92dd430c6a7b17a2b812fde52ecbc5b010 Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Sat, 28 Jul 2018 12:23:43 -0500 Subject: [PATCH 04/18] Move data handling out of rendering code, equip armor/clothes --- apps/opencs/CMakeLists.txt | 1 + apps/opencs/model/world/actoradapter.cpp | 242 +++++++++++++++++++++++ apps/opencs/model/world/actoradapter.hpp | 80 ++++++++ apps/opencs/model/world/data.cpp | 12 ++ apps/opencs/model/world/data.hpp | 7 + apps/opencs/view/render/actor.cpp | 129 ++++-------- apps/opencs/view/render/actor.hpp | 11 +- 7 files changed, 389 insertions(+), 93 deletions(-) create mode 100644 apps/opencs/model/world/actoradapter.cpp create mode 100644 apps/opencs/model/world/actoradapter.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 26713f9256..999324c51c 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -19,6 +19,7 @@ opencs_hdrs_noqt (model/doc opencs_units (model/world idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel landtexturetableproxymodel + actoradapter ) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp new file mode 100644 index 0000000000..249908bb09 --- /dev/null +++ b/apps/opencs/model/world/actoradapter.cpp @@ -0,0 +1,242 @@ +#include "actoradapter.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "data.hpp" + +namespace CSMWorld +{ + ActorAdapter::ActorAdapter(CSMWorld::Data& data) + : mReferenceables(data.getReferenceables()) + , mRaces(data.getRaces()) + , mBodyParts(data.getBodyParts()) + { + connect(data.getTableModel(UniversalId::Type_Referenceable), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + this, SLOT(handleReferenceableChanged(const QModelIndex&, const QModelIndex&))); + connect(data.getTableModel(UniversalId::Type_Race), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + this, SLOT(handleRaceChanged(const QModelIndex&, const QModelIndex&))); + connect(data.getTableModel(UniversalId::Type_BodyPart), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + this, SLOT(handleBodyPartChanged(const QModelIndex&, const QModelIndex&))); + } + + const ActorAdapter::ActorPartMap* ActorAdapter::getActorPartMap(const std::string& refId) + { + auto it = mActorPartMaps.find(refId); + if (it != mActorPartMaps.end()) + { + return &it->second; + } + else + { + updateActor(refId); + it = mActorPartMaps.find(refId); + if (it != mActorPartMaps.end()) + return &it->second; + } + + return nullptr; + } + + void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight) + { + // TODO + } + + void ActorAdapter::handleRaceChanged(const QModelIndex& topLeft, const QModelIndex& botRight) + { + // TODO + } + + void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight) + { + // TODO + } + + ActorAdapter::RacePartMap& ActorAdapter::getOrCreateRacePartMap(const std::string& raceId, bool isFemale) + { + auto key = std::make_pair(raceId, isFemale); + auto it = mRacePartMaps.find(key); + if (it != mRacePartMaps.end()) + { + return it->second; + } + else + { + // Create and find result + updateRaceParts(raceId); + return mRacePartMaps.find(key)->second; + } + } + + void ActorAdapter::updateRaceParts(const std::string& raceId) + { + // Convenience function to determine if part is for 1st person view + auto is1stPersonPart = [](std::string name) { + return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; + }; + + RacePartMap maleMap, femaleMap; + for (int i = 0; i < mBodyParts.getSize(); ++i) + { + auto& record = mBodyParts.getRecord(i); + if (!record.isDeleted() && record.get().mRace == raceId && record.get().mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(record.get().mId)) + { + auto& part = record.get(); + auto type = (ESM::BodyPart::MeshPart) part.mData.mPart; + // Note: Prefer the first part encountered for duplicates. emplace() does not overwrite + if (part.mData.mFlags & ESM::BodyPart::BPF_Female) + femaleMap.emplace(type, part.mId); + else + maleMap.emplace(type, part.mId); + } + } + + mRacePartMaps[std::make_pair(raceId, true)] = femaleMap; + mRacePartMaps[std::make_pair(raceId, false)] = maleMap; + } + + void ActorAdapter::updateActor(const std::string& refId) + { + int index = mReferenceables.searchId(refId); + if (index != -1) + { + int typeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); + int recordType = mReferenceables.getData(index, typeColumn).toInt(); + if (recordType == CSMWorld::UniversalId::Type_Creature) + updateCreature(refId); + else if (recordType == CSMWorld::UniversalId::Type_Npc) + updateNpc(refId); + } + } + + void ActorAdapter::updateNpc(const std::string& refId) + { + auto& record = mReferenceables.getRecord(refId); + if (record.isDeleted()) + { + mActorPartMaps.erase(refId); + return; + } + + auto& npc = dynamic_cast&>(record).get(); + auto& femaleRacePartMap = getOrCreateRacePartMap(npc.mRace, true); + auto& maleRacePartMap = getOrCreateRacePartMap(npc.mRace, false); + + ActorPartMap npcMap; + + // Look at the npc's inventory first + for (auto& item : npc.mInventory.mList) + { + if (item.mCount > 0) + { + std::string itemId = item.mItem.toString(); + // Handle armor, weapons, and clothing + int index = mReferenceables.searchId(itemId); + if (index != -1 && !mReferenceables.getRecord(index).isDeleted()) + { + auto& itemRecord = mReferenceables.getRecord(index); + + int typeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); + int recordType = mReferenceables.getData(index, typeColumn).toInt(); + if (recordType == CSMWorld::UniversalId::Type_Armor) + { + auto& armor = dynamic_cast&>(itemRecord).get(); + for (auto& part : armor.mParts.mParts) + { + std::string bodyPartId; + if (!npc.isMale()) + bodyPartId = part.mFemale; + if (bodyPartId.empty()) + bodyPartId = part.mMale; + + if (!bodyPartId.empty()) + npcMap.emplace(static_cast(part.mPart), bodyPartId); + } + } + else if (recordType == CSMWorld::UniversalId::Type_Clothing) + { + auto& clothing = dynamic_cast&>(itemRecord).get(); + for (auto& part : clothing.mParts.mParts) + { + std::string bodyPartId; + if (!npc.isMale()) + bodyPartId = part.mFemale; + if (bodyPartId.empty()) + bodyPartId = part.mMale; + + if (!bodyPartId.empty()) + npcMap.emplace(static_cast(part.mPart), bodyPartId); + } + } + else if (recordType == CSMWorld::UniversalId::Type_Weapon) + { + // TODO + } + } + } + } + + // Fill in the rest with body parts + for (int i = 0; i < ESM::PRT_Count; ++i) + { + auto type = static_cast(i); + if (npcMap.find(type) == npcMap.end()) + { + switch (type) + { + case ESM::PRT_Head: + npcMap.emplace(type, npc.mHead); + break; + case ESM::PRT_Hair: + npcMap.emplace(type, npc.mHair); + break; + case ESM::PRT_Skirt: + case ESM::PRT_Shield: + case ESM::PRT_RPauldron: + case ESM::PRT_LPauldron: + case ESM::PRT_Weapon: + // No body part associated + break; + default: + { + std::string bodyPartId; + // Check female map if applicable + if (!npc.isMale()) + { + auto partIt = femaleRacePartMap.find(ESM::getMeshPart(type)); + if (partIt != femaleRacePartMap.end()) + bodyPartId = partIt->second; + } + + // Check male map next + if (bodyPartId.empty() || npc.isMale()) + { + auto partIt = maleRacePartMap.find(ESM::getMeshPart(type)); + if (partIt != maleRacePartMap.end()) + bodyPartId = partIt->second; + } + + // Add to map + if (!bodyPartId.empty()) + { + npcMap.emplace(type, bodyPartId); + } + } + } + } + } + + mActorPartMaps[refId] = npcMap; + } + + void ActorAdapter::updateCreature(const std::string& refId) + { + // TODO + } +} diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp new file mode 100644 index 0000000000..2c94f9c2ca --- /dev/null +++ b/apps/opencs/model/world/actoradapter.hpp @@ -0,0 +1,80 @@ +#ifndef CSM_WOLRD_ACTORADAPTER_H +#define CSM_WOLRD_ACTORADAPTER_H + +#include +#include +#include + +#include + +#include +#include + +#include "refidcollection.hpp" +#include "idcollection.hpp" + +namespace ESM +{ + struct Race; + enum PartReferenceType; +} + +namespace CSMWorld +{ + class Data; + + /// Quick and dirty hashing functor. + struct StringBoolPairHash + { + size_t operator()(const std::pair& value) const noexcept + { + auto stringHash = std::hash(); + return stringHash(value.first) + value.second; + } + }; + + class ActorAdapter : public QObject + { + Q_OBJECT + public: + + // Maps body part type to 'body part' id + using ActorPartMap = std::unordered_map; + // Maps mesh part type to 'body part' id + using RacePartMap = std::unordered_map; + + ActorAdapter(CSMWorld::Data& data); + + const ActorPartMap* getActorPartMap(const std::string& refId); + + signals: + + void actorChanged(const std::string& refId); + + public slots: + + void handleReferenceableChanged(const QModelIndex&, const QModelIndex&); + void handleRaceChanged(const QModelIndex&, const QModelIndex&); + void handleBodyPartChanged(const QModelIndex&, const QModelIndex&); + + private: + + RacePartMap& getOrCreateRacePartMap(const std::string& raceId, bool isFemale); + + void updateRaceParts(const std::string& raceId); + void updateActor(const std::string& refId); + void updateNpc(const std::string& refId); + void updateCreature(const std::string& refId); + + RefIdCollection& mReferenceables; + IdCollection& mRaces; + IdCollection& mBodyParts; + + // Key: referenceable id + std::unordered_map mActorPartMaps; + // Key: race id, is female + std::unordered_map, RacePartMap, StringBoolPairHash> mRacePartMaps; + }; +} + +#endif diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 053754943e..f3f897a292 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -572,6 +572,8 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat UniversalId::Type_Video); addModel (new IdTable (&mMetaData), UniversalId::Type_MetaData); + mActorAdapter.reset(new ActorAdapter(*this)); + mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files } @@ -912,6 +914,16 @@ QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& return iter->second; } +const CSMWorld::ActorAdapter* CSMWorld::Data::getActorAdapter() const +{ + return mActorAdapter.get(); +} + +CSMWorld::ActorAdapter* CSMWorld::Data::getActorAdapter() +{ + return mActorAdapter.get(); +} + void CSMWorld::Data::merge() { mGlobals.merge(); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 1b975f4308..7c4d8885ad 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -36,6 +36,7 @@ #include "../doc/stage.hpp" +#include "actoradapter.hpp" #include "idcollection.hpp" #include "nestedidcollection.hpp" #include "universalid.hpp" @@ -73,6 +74,7 @@ namespace ESM namespace CSMWorld { + class ActorAdapter; class ResourcesManager; class Resources; @@ -110,6 +112,7 @@ namespace CSMWorld RefCollection mRefs; IdCollection mFilters; Collection mMetaData; + std::unique_ptr mActorAdapter; const Fallback::Map* mFallbackMap; std::vector mModels; std::map mModelIndex; @@ -287,6 +290,10 @@ namespace CSMWorld /// \note The returned table may either be the model for the ID itself or the model that /// contains the record specified by the ID. + const ActorAdapter* getActorAdapter() const; + + ActorAdapter* getActorAdapter(); + void merge(); ///< Merge modified into base. diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index 3691085e3c..b816c15040 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include "../../model/world/data.hpp" @@ -24,8 +23,8 @@ namespace CSVRender : mId(id) , mType(type) , mData(data) - , mSkeleton(nullptr) , mBaseNode(new osg::Group()) + , mSkeleton(nullptr) { } @@ -57,6 +56,7 @@ namespace CSVRender auto& creature = dynamic_cast& >(referenceables.getRecord(mId)).get(); + // Load skeleton with meshes std::string skeletonModel = MeshPrefix + creature.mModel; skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); loadSkeleton(skeletonModel); @@ -65,6 +65,9 @@ namespace CSVRender mSkeleton->accept(removeTriBipVisitor); removeTriBipVisitor.remove(); + // Attach weapons + loadBodyParts(creature.mId); + // Post setup mSkeleton->markDirty(); mSkeleton->setActive(SceneUtil::Skeleton::Active); @@ -72,12 +75,8 @@ namespace CSVRender void Actor::updateNpc() { - const unsigned int FemaleFlag = ESM::BodyPart::BPF_Female; - - auto& bodyParts = mData.getBodyParts(); auto& races = mData.getRaces(); auto& referenceables = mData.getReferenceables(); - auto sceneMgr = mData.getResourceSystem()->getSceneManager(); auto& npc = dynamic_cast& >(referenceables.getRecord(mId)).get(); auto& race = dynamic_cast& >(races.getRecord(npc.mRace)).get(); @@ -97,91 +96,8 @@ namespace CSVRender mSkeleton->accept(cleanVisitor); cleanVisitor.remove(); - // Map bone names to bones - SceneUtil::NodeMapVisitor::NodeMap nodeMap; - SceneUtil::NodeMapVisitor nmVisitor(nodeMap); - mSkeleton->accept(nmVisitor); - - using BPRaceKey = std::tuple; - using RaceToBPMap = std::map; - // Convenience method to generate a map from body part + race to mesh name - auto genRaceToBodyPartMap = [&](RaceToBPMap& bpMap) { - int size = bodyParts.getSize(); - for (int i = 0; i < size; ++i) - { - auto& record = bodyParts.getRecord(i); - if (!record.isDeleted()) - { - // Method to check if 1st person part or not - auto is1stPersonPart = [](std::string name) { - return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; - }; - - auto& bodyPart = record.get(); - if (bodyPart.mData.mType != ESM::BodyPart::MT_Skin || is1stPersonPart(bodyPart.mId)) - continue; - - bpMap.emplace( - BPRaceKey(bodyPart.mData.mPart, bodyPart.mData.mFlags & FemaleFlag ? 1 : 0, bodyPart.mRace), - MeshPrefix + bodyPart.mModel); - } - } - }; - - // Generate mapping - RaceToBPMap r2bpMap; - genRaceToBodyPartMap(r2bpMap); - - // Convenience method to add a body part - auto addBodyPart = [&](ESM::PartReferenceType type, std::string mesh) { - // Retrieve mesh name if necessary - if (mesh.empty()) - { - auto meshResult = r2bpMap.find(BPRaceKey(ESM::getMeshPart(type), isFemale ? 1 : 0, npc.mRace)); - if (meshResult != r2bpMap.end()) - { - mesh = meshResult->second; - } - else if (isFemale){ - meshResult = r2bpMap.find(BPRaceKey(ESM::getMeshPart(type), 0, npc.mRace)); - if (meshResult != r2bpMap.end()) - mesh = meshResult->second; - } - } - - // Attach to skeleton - std::string boneName = ESM::getBoneName(type); - auto node = nodeMap.find(boneName); - if (!mesh.empty() && node != nodeMap.end()) - { - auto instance = sceneMgr->getInstance(mesh); - SceneUtil::attach(instance, mSkeleton, boneName, node->second); - } - }; - - // Add body parts - for (unsigned int i = 0; i < ESM::PRT_Count; ++i) - { - auto part = static_cast(i); - switch (part) - { - case ESM::PRT_Head: - addBodyPart(part, getBodyPartMesh(npc.mHead)); - break; - case ESM::PRT_Hair: - addBodyPart(part, getBodyPartMesh(npc.mHair)); - break; - case ESM::PRT_Skirt: - case ESM::PRT_Shield: - case ESM::PRT_RPauldron: - case ESM::PRT_LPauldron: - case ESM::PRT_Weapon: - // No body part mesh associated - break; - default: - addBodyPart(part, ""); - } - } + // Attach parts to skeleton + loadBodyParts(npc.mId); // Post setup mSkeleton->markDirty(); @@ -200,6 +116,37 @@ namespace CSVRender mSkeleton->addChild(temp); } mBaseNode->addChild(mSkeleton); + + // Map bone names to bones + mNodeMap.clear(); + SceneUtil::NodeMapVisitor nmVisitor(mNodeMap); + mSkeleton->accept(nmVisitor); + + } + + void Actor::loadBodyParts(const std::string& actorId) + { + auto actorAdapter = mData.getActorAdapter(); + auto partMap = actorAdapter->getActorPartMap(actorId); + if (partMap) + { + for (auto& pair : *partMap) + attachBodyPart(pair.first, getBodyPartMesh(pair.second)); + } + } + + void Actor::attachBodyPart(ESM::PartReferenceType type, const std::string& mesh) + { + auto sceneMgr = mData.getResourceSystem()->getSceneManager(); + + // Attach to skeleton + std::string boneName = ESM::getBoneName(type); + auto node = mNodeMap.find(boneName); + if (!mesh.empty() && node != mNodeMap.end()) + { + auto instance = sceneMgr->getInstance(mesh); + SceneUtil::attach(instance, mSkeleton, boneName, node->second); + } } std::string Actor::getBodyPartMesh(const std::string& bodyPartId) diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index b4e65ff2b6..4f809c1720 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -5,6 +5,9 @@ #include +#include +#include + namespace osg { class Group; @@ -39,10 +42,13 @@ namespace CSVRender void update(); private: - void loadSkeleton(const std::string& model); void updateCreature(); void updateNpc(); + void loadSkeleton(const std::string& model); + void loadBodyParts(const std::string& actorId); + void attachBodyPart(ESM::PartReferenceType, const std::string& mesh); + std::string getBodyPartMesh(const std::string& bodyPartId); static const std::string MeshPrefix; @@ -51,8 +57,9 @@ namespace CSVRender int mType; CSMWorld::Data& mData; - SceneUtil::Skeleton* mSkeleton; osg::ref_ptr mBaseNode; + SceneUtil::Skeleton* mSkeleton; + SceneUtil::NodeMapVisitor::NodeMap mNodeMap; }; } From 6bece13a32ff40b69ed4d9acf380c210d00a70e8 Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Tue, 21 Aug 2018 15:41:05 -0400 Subject: [PATCH 05/18] Use new Log class for error message --- apps/opencs/view/render/actor.cpp | 5 ++--- components/sceneutil/visitor.cpp | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index b816c15040..a61d31de67 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -1,10 +1,9 @@ #include "actor.hpp" -#include - #include #include +#include #include #include #include @@ -46,7 +45,7 @@ namespace CSVRender } catch (std::exception& e) { - std::cout << "Caught exception: " << e.what() << std::endl; + Log(Debug::Error) << "Exception in Actor::update(): " << e.what(); } } diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 07be1608e6..536331132d 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -1,12 +1,12 @@ #include "visitor.hpp" -#include - #include #include #include +#include + #include namespace SceneUtil @@ -74,7 +74,7 @@ namespace SceneUtil for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) { if (!it->second->removeChild(it->first)) - std::cerr << "error removing " << it->first->getName() << std::endl; + Log(Debug::Error) << "error removing " << it->first->getName(); } } From b2115b60e6710c07b6209e241036fc2e8e006748 Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Tue, 21 Aug 2018 15:54:21 -0400 Subject: [PATCH 06/18] Fix qt4 build --- apps/opencs/model/world/actoradapter.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 2c94f9c2ca..09bd2682ba 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include From 1276e0fa9b2e2f7ac5e1a67d288c79479e536fc8 Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Thu, 23 Aug 2018 22:40:43 -0400 Subject: [PATCH 07/18] Handle changes to race record when rendering actors --- apps/opencs/CMakeLists.txt | 4 +- apps/opencs/model/world/actoradapter.cpp | 66 ++++++++++++++++++++---- apps/opencs/model/world/actoradapter.hpp | 7 +++ apps/opencs/view/render/actor.cpp | 18 ++++++- apps/opencs/view/render/actor.hpp | 9 +++- apps/opencs/view/render/object.cpp | 7 +-- apps/opencs/view/render/object.hpp | 3 ++ 7 files changed, 98 insertions(+), 16 deletions(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 999324c51c..b0bd95eb95 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -89,12 +89,12 @@ opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget previewwidget editmode instancemode instanceselectionmode instancemovemode orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller - cellwater terraintexturemode + cellwater terraintexturemode actor ) opencs_units_noqt (view/render lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase - cellarrow cellmarker cellborder pathgrid actor + cellarrow cellmarker cellborder pathgrid ) opencs_hdrs_noqt (view/render diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 249908bb09..9c397879e9 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -1,7 +1,6 @@ #include "actoradapter.hpp" -#include - +#include #include #include #include @@ -45,17 +44,56 @@ namespace CSMWorld void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { - // TODO + // Setup + const int TypeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); + int rowStart = getHighestIndex(topLeft).row(); + int rowEnd = getHighestIndex(botRight).row(); + + // Handle each record + for (int row = rowStart; row <= rowEnd; ++row) + { + int type = mReferenceables.getData(row, TypeColumn).toInt(); + if (type == CSMWorld::UniversalId::Type_Creature || type == CSMWorld::UniversalId::Type_Npc) + { + // Update the cached npc or creature + std::string refId = mReferenceables.getId(row); + if (mActorPartMaps.find(refId) != mActorPartMaps.end()) + updateActor(refId); + } + else if (type == CSMWorld::UniversalId::Type_Armor) + { + // TODO update everything? + // store all items referenced when creating map and check against that here + } + else if (type == CSMWorld::UniversalId::Type_Clothing) + { + // TODO update everything? + } + } } void ActorAdapter::handleRaceChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { - // TODO + int rowStart = getHighestIndex(topLeft).row(); + int rowEnd = getHighestIndex(botRight).row(); + for (int row = rowStart; row <= rowEnd; ++row) + { + std::string raceId = mRaces.getId(row); + updateNpcsWithRace(raceId); + } } void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { // TODO + Log(Debug::Info) << "Body Part Changed (" << topLeft.row() << ", " << topLeft.column() << ") (" << botRight.row() << ", " << botRight.column() << ")"; + } + + QModelIndex ActorAdapter::getHighestIndex(QModelIndex index) const + { + while (index.parent().isValid()) + index = index.parent(); + return index; } ActorAdapter::RacePartMap& ActorAdapter::getOrCreateRacePartMap(const std::string& raceId, bool isFemale) @@ -174,10 +212,6 @@ namespace CSMWorld npcMap.emplace(static_cast(part.mPart), bodyPartId); } } - else if (recordType == CSMWorld::UniversalId::Type_Weapon) - { - // TODO - } } } } @@ -233,10 +267,24 @@ namespace CSMWorld } mActorPartMaps[refId] = npcMap; + emit actorChanged(refId); } void ActorAdapter::updateCreature(const std::string& refId) { - // TODO + emit actorChanged(refId); + } + + void ActorAdapter::updateNpcsWithRace(const std::string& raceId) + { + for (auto it : mActorPartMaps) + { + auto& refId = it.first; + auto& npc = dynamic_cast&>(mReferenceables.getRecord(refId)).get(); + if (npc.mRace == raceId) + { + updateNpc(refId); + } + } } } diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 09bd2682ba..ad308fc022 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -60,6 +60,11 @@ namespace CSMWorld private: + ActorAdapter(const ActorAdapter&) = delete; + ActorAdapter& operator=(const ActorAdapter&) = delete; + + QModelIndex getHighestIndex(QModelIndex) const; + RacePartMap& getOrCreateRacePartMap(const std::string& raceId, bool isFemale); void updateRaceParts(const std::string& raceId); @@ -67,6 +72,8 @@ namespace CSMWorld void updateNpc(const std::string& refId); void updateCreature(const std::string& refId); + void updateNpcsWithRace(const std::string& raceId); + RefIdCollection& mReferenceables; IdCollection& mRaces; IdCollection& mBodyParts; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index a61d31de67..6d663ad5e6 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -20,6 +20,7 @@ namespace CSVRender Actor::Actor(const std::string& id, int type, CSMWorld::Data& data) : mId(id) + , mInitialized(false) , mType(type) , mData(data) , mBaseNode(new osg::Group()) @@ -45,7 +46,22 @@ namespace CSVRender } catch (std::exception& e) { - Log(Debug::Error) << "Exception in Actor::update(): " << e.what(); + Log(Debug::Info) << "Exception in Actor::update(): " << e.what(); + } + + if (!mInitialized) + { + mInitialized = true; + connect(mData.getActorAdapter(), SIGNAL(actorChanged(const std::string&)), this, SLOT(handleActorChanged(const std::string&))); + } + } + + void Actor::handleActorChanged(const std::string& refId) + { + if (mId == refId) + { + Log(Debug::Info) << "Actor::actorChanged " << mId; + update(); } } diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index 4f809c1720..42a6019ed7 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -5,6 +5,8 @@ #include +#include + #include #include @@ -26,8 +28,9 @@ namespace SceneUtil namespace CSVRender { /// Handles loading an npc or creature - class Actor + class Actor : public QObject { + Q_OBJECT public: /// Creates an actor. /// \param id The referenceable id @@ -41,6 +44,9 @@ namespace CSVRender /// (Re)creates the npc or creature renderable void update(); + private slots: + void handleActorChanged(const std::string& refId); + private: void updateCreature(); void updateNpc(); @@ -54,6 +60,7 @@ namespace CSVRender static const std::string MeshPrefix; std::string mId; + bool mInitialized; int mType; CSMWorld::Data& mData; diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index dbfd595b6a..63f1bc8a82 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -134,9 +134,10 @@ void CSVRender::Object::update() { if (recordType == CSMWorld::UniversalId::Type_Npc || recordType == CSMWorld::UniversalId::Type_Creature) { - Actor actor(mReferenceableId, recordType, mData); - actor.update(); - mBaseNode->addChild(actor.getBaseNode()); + if (!mActor) + mActor.reset(new Actor(mReferenceableId, recordType, mData)); + mActor->update(); + mBaseNode->addChild(mActor->getBaseNode()); } else { diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 3e54093d3e..10a46fc103 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -1,6 +1,7 @@ #ifndef OPENCS_VIEW_OBJECT_H #define OPENCS_VIEW_OBJECT_H +#include #include #include @@ -41,6 +42,7 @@ namespace CSMWorld namespace CSVRender { + class Actor; class Object; // An object to attach as user data to the osg::Node, allows us to get an Object back from a Node when we are doing a ray query @@ -98,6 +100,7 @@ namespace CSVRender osg::ref_ptr mMarker[3]; int mSubMode; float mMarkerTransparency; + std::unique_ptr mActor; /// Not implemented Object (const Object&); From 2a9ebac57225407d3ff991d81964d9e7ceeb0941 Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Fri, 24 Aug 2018 02:44:02 -0400 Subject: [PATCH 08/18] Simplify update logic, update when race parts are changed. --- apps/opencs/model/world/actoradapter.cpp | 207 +++++++++++++++-------- apps/opencs/model/world/actoradapter.hpp | 35 +++- apps/opencs/view/render/actor.cpp | 6 +- 3 files changed, 168 insertions(+), 80 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 9c397879e9..4626d16424 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -24,22 +24,22 @@ namespace CSMWorld this, SLOT(handleBodyPartChanged(const QModelIndex&, const QModelIndex&))); } - const ActorAdapter::ActorPartMap* ActorAdapter::getActorPartMap(const std::string& refId) + const ActorAdapter::ActorPartMap* ActorAdapter::getActorParts(const std::string& refId, bool create) { - auto it = mActorPartMaps.find(refId); - if (it != mActorPartMaps.end()) + auto it = mCachedActors.find(refId); + if (it != mCachedActors.end()) { - return &it->second; + return &it->second.parts; + } + else if (create) + { + updateActor(refId); + return getActorParts(refId, false); } else { - updateActor(refId); - it = mActorPartMaps.find(refId); - if (it != mActorPartMaps.end()) - return &it->second; + return nullptr; } - - return nullptr; } void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight) @@ -57,17 +57,13 @@ namespace CSMWorld { // Update the cached npc or creature std::string refId = mReferenceables.getId(row); - if (mActorPartMaps.find(refId) != mActorPartMaps.end()) + if (mCachedActors.find(refId) != mCachedActors.end()) updateActor(refId); } - else if (type == CSMWorld::UniversalId::Type_Armor) + else if (type == CSMWorld::UniversalId::Type_Armor || type == CSMWorld::UniversalId::Type_Clothing) { - // TODO update everything? - // store all items referenced when creating map and check against that here - } - else if (type == CSMWorld::UniversalId::Type_Clothing) - { - // TODO update everything? + std::string refId = mReferenceables.getId(row); + updateActorsWithDependency(refId); } } } @@ -79,14 +75,28 @@ namespace CSMWorld for (int row = rowStart; row <= rowEnd; ++row) { std::string raceId = mRaces.getId(row); - updateNpcsWithRace(raceId); + updateActorsWithDependency(raceId); } } void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { - // TODO - Log(Debug::Info) << "Body Part Changed (" << topLeft.row() << ", " << topLeft.column() << ") (" << botRight.row() << ", " << botRight.column() << ")"; + int rowStart = getHighestIndex(topLeft).row(); + int rowEnd = getHighestIndex(botRight).row(); + for (int row = rowStart; row <= rowEnd; ++row) + { + // Manually update race specified by part + auto& record = mBodyParts.getRecord(row); + if (!record.isDeleted()) + { + updateRace(record.get().mRace); + } + + // Update entries with a tracked dependency + std::string partId = mBodyParts.getId(row); + updateRacesWithDependency(partId); + updateActorsWithDependency(partId); + } } QModelIndex ActorAdapter::getHighestIndex(QModelIndex index) const @@ -96,47 +106,66 @@ namespace CSMWorld return index; } - ActorAdapter::RacePartMap& ActorAdapter::getOrCreateRacePartMap(const std::string& raceId, bool isFemale) + bool ActorAdapter::is1stPersonPart(const std::string& name) const { - auto key = std::make_pair(raceId, isFemale); - auto it = mRacePartMaps.find(key); - if (it != mRacePartMaps.end()) + return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; + } + + ActorAdapter::RaceData& ActorAdapter::getRaceData(const std::string& raceId) + { + auto it = mCachedRaces.find(raceId); + if (it != mCachedRaces.end()) { return it->second; } else { // Create and find result - updateRaceParts(raceId); - return mRacePartMaps.find(key)->second; + updateRace(raceId); + return mCachedRaces.find(raceId)->second; } } - void ActorAdapter::updateRaceParts(const std::string& raceId) + void ActorAdapter::updateRace(const std::string& raceId) { - // Convenience function to determine if part is for 1st person view - auto is1stPersonPart = [](std::string name) { - return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; - }; + // Retrieve or create cache entry + auto raceDataIt = mCachedRaces.find(raceId); + if (raceDataIt == mCachedRaces.end()) + { + auto result = mCachedRaces.emplace(raceId, RaceData()); + raceDataIt = result.first; + } - RacePartMap maleMap, femaleMap; + auto& raceData = raceDataIt->second; + raceData.femaleParts.clear(); + raceData.maleParts.clear(); + raceData.dependencies.clear(); + + // Construct entry for (int i = 0; i < mBodyParts.getSize(); ++i) { auto& record = mBodyParts.getRecord(i); - if (!record.isDeleted() && record.get().mRace == raceId && record.get().mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(record.get().mId)) + if (!record.isDeleted() && record.get().mRace == raceId) { auto& part = record.get(); - auto type = (ESM::BodyPart::MeshPart) part.mData.mPart; - // Note: Prefer the first part encountered for duplicates. emplace() does not overwrite - if (part.mData.mFlags & ESM::BodyPart::BPF_Female) - femaleMap.emplace(type, part.mId); - else - maleMap.emplace(type, part.mId); + + // Part could affect race data + raceData.dependencies.emplace(part.mId, true); + + // Add base types + if (part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId)) + { + auto type = (ESM::BodyPart::MeshPart) part.mData.mPart; + // Note: Prefer the first part encountered for duplicates. emplace() does not overwrite + if (part.mData.mFlags & ESM::BodyPart::BPF_Female) + raceData.femaleParts.emplace(type, part.mId); + else + raceData.maleParts.emplace(type, part.mId); + } } } - mRacePartMaps[std::make_pair(raceId, true)] = femaleMap; - mRacePartMaps[std::make_pair(raceId, false)] = maleMap; + updateActorsWithDependency(raceId); } void ActorAdapter::updateActor(const std::string& refId) @@ -156,17 +185,28 @@ namespace CSMWorld void ActorAdapter::updateNpc(const std::string& refId) { auto& record = mReferenceables.getRecord(refId); + + // Retrieve record if possible if (record.isDeleted()) { - mActorPartMaps.erase(refId); + mCachedActors.erase(refId); + emit actorChanged(refId); return; } - auto& npc = dynamic_cast&>(record).get(); - auto& femaleRacePartMap = getOrCreateRacePartMap(npc.mRace, true); - auto& maleRacePartMap = getOrCreateRacePartMap(npc.mRace, false); - ActorPartMap npcMap; + // Create holder for cached data + auto actorIt = mCachedActors.find(refId); + if (actorIt == mCachedActors.end()) + { + auto result = mCachedActors.emplace(refId, ActorData()); + actorIt = result.first; + } + auto& actorData = actorIt->second; + + // Reset old data + actorData.parts.clear(); + actorData.dependencies.clear(); // Look at the npc's inventory first for (auto& item : npc.mInventory.mList) @@ -174,7 +214,7 @@ namespace CSMWorld if (item.mCount > 0) { std::string itemId = item.mItem.toString(); - // Handle armor, weapons, and clothing + // Handle armor and clothing int index = mReferenceables.searchId(itemId); if (index != -1 && !mReferenceables.getRecord(index).isDeleted()) { @@ -184,6 +224,10 @@ namespace CSMWorld int recordType = mReferenceables.getData(index, typeColumn).toInt(); if (recordType == CSMWorld::UniversalId::Type_Armor) { + // Changes here could affect the actor + actorData.dependencies.emplace(itemId, true); + + // Add any parts if there is room auto& armor = dynamic_cast&>(itemRecord).get(); for (auto& part : armor.mParts.mParts) { @@ -194,11 +238,18 @@ namespace CSMWorld bodyPartId = part.mMale; if (!bodyPartId.empty()) - npcMap.emplace(static_cast(part.mPart), bodyPartId); + { + actorData.parts.emplace(static_cast(part.mPart), bodyPartId); + actorData.dependencies.emplace(bodyPartId, true); + } } } else if (recordType == CSMWorld::UniversalId::Type_Clothing) { + // Changes here could affect the actor + actorData.dependencies.emplace(itemId, true); + + // Add any parts if there is room auto& clothing = dynamic_cast&>(itemRecord).get(); for (auto& part : clothing.mParts.mParts) { @@ -209,26 +260,37 @@ namespace CSMWorld bodyPartId = part.mMale; if (!bodyPartId.empty()) - npcMap.emplace(static_cast(part.mPart), bodyPartId); + { + actorData.parts.emplace(static_cast(part.mPart), bodyPartId); + actorData.dependencies.emplace(bodyPartId, true); + } } } } } } - // Fill in the rest with body parts + // Lookup cached race parts + auto& raceData = getRaceData(npc.mRace); + + // Changes to race could affect the actor + actorData.dependencies.emplace(npc.mRace, true); + + // Fill in the rest with race specific body parts for (int i = 0; i < ESM::PRT_Count; ++i) { auto type = static_cast(i); - if (npcMap.find(type) == npcMap.end()) + if (actorData.parts.find(type) == actorData.parts.end()) { switch (type) { case ESM::PRT_Head: - npcMap.emplace(type, npc.mHead); + actorData.parts.emplace(type, npc.mHead); + actorData.dependencies.emplace(npc.mHead, true); break; case ESM::PRT_Hair: - npcMap.emplace(type, npc.mHair); + actorData.parts.emplace(type, npc.mHair); + actorData.dependencies.emplace(npc.mHair, true); break; case ESM::PRT_Skirt: case ESM::PRT_Shield: @@ -243,48 +305,57 @@ namespace CSMWorld // Check female map if applicable if (!npc.isMale()) { - auto partIt = femaleRacePartMap.find(ESM::getMeshPart(type)); - if (partIt != femaleRacePartMap.end()) + auto partIt = raceData.femaleParts.find(ESM::getMeshPart(type)); + if (partIt != raceData.femaleParts.end()) bodyPartId = partIt->second; } // Check male map next if (bodyPartId.empty() || npc.isMale()) { - auto partIt = maleRacePartMap.find(ESM::getMeshPart(type)); - if (partIt != maleRacePartMap.end()) + auto partIt = raceData.maleParts.find(ESM::getMeshPart(type)); + if (partIt != raceData.maleParts.end()) bodyPartId = partIt->second; } // Add to map if (!bodyPartId.empty()) { - npcMap.emplace(type, bodyPartId); + actorData.parts.emplace(type, bodyPartId); + actorData.dependencies.emplace(bodyPartId, true); } } } } } - mActorPartMaps[refId] = npcMap; + // Signal change to actor emit actorChanged(refId); } void ActorAdapter::updateCreature(const std::string& refId) { + // Signal change to actor emit actorChanged(refId); } - void ActorAdapter::updateNpcsWithRace(const std::string& raceId) + void ActorAdapter::updateActorsWithDependency(const std::string& id) { - for (auto it : mActorPartMaps) + for (auto it : mCachedActors) { - auto& refId = it.first; - auto& npc = dynamic_cast&>(mReferenceables.getRecord(refId)).get(); - if (npc.mRace == raceId) - { - updateNpc(refId); - } + auto& deps = it.second.dependencies; + if (deps.find(id) != deps.end()) + updateActor(it.first); + } + } + + void ActorAdapter::updateRacesWithDependency(const std::string& id) + { + for (auto it : mCachedRaces) + { + auto& deps = it.second.dependencies; + if (deps.find(id) != deps.end()) + updateRace(it.first); } } } diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index ad308fc022..f1148199c0 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -41,12 +41,10 @@ namespace CSMWorld // Maps body part type to 'body part' id using ActorPartMap = std::unordered_map; - // Maps mesh part type to 'body part' id - using RacePartMap = std::unordered_map; ActorAdapter(CSMWorld::Data& data); - const ActorPartMap* getActorPartMap(const std::string& refId); + const ActorPartMap* getActorParts(const std::string& refId, bool create=true); signals: @@ -59,29 +57,48 @@ namespace CSMWorld void handleBodyPartChanged(const QModelIndex&, const QModelIndex&); private: + // Maps mesh part type to 'body part' id + using RacePartMap = std::unordered_map; + // Stores ids that are referenced by the actor. Data part is meaningless. + using DependencyMap = std::unordered_map; + + struct ActorData + { + ActorPartMap parts; + DependencyMap dependencies; + }; + + struct RaceData + { + RacePartMap femaleParts; + RacePartMap maleParts; + DependencyMap dependencies; + }; ActorAdapter(const ActorAdapter&) = delete; ActorAdapter& operator=(const ActorAdapter&) = delete; QModelIndex getHighestIndex(QModelIndex) const; + bool is1stPersonPart(const std::string& id) const; - RacePartMap& getOrCreateRacePartMap(const std::string& raceId, bool isFemale); + RaceData& getRaceData(const std::string& raceId); - void updateRaceParts(const std::string& raceId); + void updateRace(const std::string& raceId); void updateActor(const std::string& refId); void updateNpc(const std::string& refId); void updateCreature(const std::string& refId); - void updateNpcsWithRace(const std::string& raceId); + void updateActorsWithDependency(const std::string& id); + void updateRacesWithDependency(const std::string& id); RefIdCollection& mReferenceables; IdCollection& mRaces; IdCollection& mBodyParts; // Key: referenceable id - std::unordered_map mActorPartMaps; - // Key: race id, is female - std::unordered_map, RacePartMap, StringBoolPairHash> mRacePartMaps; + std::unordered_map mCachedActors; + // Key: race id + std::unordered_map mCachedRaces; }; } diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index 6d663ad5e6..b9979ae2c6 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -142,10 +142,10 @@ namespace CSVRender void Actor::loadBodyParts(const std::string& actorId) { auto actorAdapter = mData.getActorAdapter(); - auto partMap = actorAdapter->getActorPartMap(actorId); - if (partMap) + auto parts = actorAdapter->getActorParts(actorId); + if (parts) { - for (auto& pair : *partMap) + for (auto& pair : *parts) attachBodyPart(pair.first, getBodyPartMesh(pair.second)); } } From 031502b2ab80216d9dc4ef475a1ad6cb9ad2092e Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Sun, 26 Aug 2018 01:13:50 -0400 Subject: [PATCH 09/18] Reorganize ActorAdapter data, use weak cache for sharing --- apps/opencs/model/world/actoradapter.cpp | 715 +++++++++++++++-------- apps/opencs/model/world/actoradapter.hpp | 187 ++++-- apps/opencs/view/render/actor.cpp | 14 +- apps/opencs/view/render/actor.hpp | 3 + components/CMakeLists.txt | 4 + components/cache/weakcache.hpp | 116 ++++ 6 files changed, 714 insertions(+), 325 deletions(-) create mode 100644 components/cache/weakcache.hpp diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 4626d16424..b3a1216f75 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -11,72 +11,290 @@ namespace CSMWorld { - ActorAdapter::ActorAdapter(CSMWorld::Data& data) + const std::string& ActorAdapter::RaceData::getId() const + { + return mId; + } + + bool ActorAdapter::RaceData::handlesPart(ESM::PartReferenceType type) const + { + switch (type) + { + case ESM::PRT_Skirt: + case ESM::PRT_Shield: + case ESM::PRT_RPauldron: + case ESM::PRT_LPauldron: + case ESM::PRT_Weapon: + return false; + default: + return true; + } + } + + const std::string& ActorAdapter::RaceData::getFemalePart(ESM::PartReferenceType index) const + { + return mFemaleParts[ESM::getMeshPart(index)]; + } + + const std::string& ActorAdapter::RaceData::getMalePart(ESM::PartReferenceType index) const + { + return mMaleParts[ESM::getMeshPart(index)]; + } + + bool ActorAdapter::RaceData::hasDependency(const std::string& id) const + { + return mDependencies.find(id) != mDependencies.end(); + } + + void ActorAdapter::RaceData::setFemalePart(ESM::BodyPart::MeshPart index, const std::string& partId) + { + mFemaleParts[index] = partId; + addOtherDependency(partId); + } + + void ActorAdapter::RaceData::setMalePart(ESM::BodyPart::MeshPart index, const std::string& partId) + { + mMaleParts[index] = partId; + addOtherDependency(partId); + } + + void ActorAdapter::RaceData::addOtherDependency(const std::string& id) + { + if (!id.empty()) mDependencies.emplace(id); + } + + void ActorAdapter::RaceData::reset(const std::string& id) + { + mId = id; + for (auto& str : mFemaleParts) + str.clear(); + for (auto& str : mMaleParts) + str.clear(); + mDependencies.clear(); + + // Mark self as a dependency + addOtherDependency(id); + } + + + const std::string& ActorAdapter::ActorData::getId() const + { + return mId; + } + + bool ActorAdapter::ActorData::isFemale() const + { + return mFemale; + } + + const std::string& ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const + { + if (mParts[index].empty() && mRaceData && mRaceData->handlesPart(index)) + { + return mFemale ? mRaceData->getFemalePart(index) : mRaceData->getMalePart(index); + } + return mParts[index]; + } + + bool ActorAdapter::ActorData::hasDependency(const std::string& id) const + { + return mDependencies.find(id) != mDependencies.end(); + } + + void ActorAdapter::ActorData::setPart(ESM::PartReferenceType index, const std::string& partId) + { + mParts[index] = partId; + addOtherDependency(partId); + } + + void ActorAdapter::ActorData::addOtherDependency(const std::string& id) + { + if (!id.empty()) mDependencies.emplace(id); + } + + void ActorAdapter::ActorData::reset(const std::string& id, bool isFemale, RaceDataPtr raceData) + { + mId = id; + mFemale = isFemale; + mRaceData = raceData; + for (auto& str : mParts) + str.clear(); + mDependencies.clear(); + + // Mark self and race as a dependency + addOtherDependency(id); + if (raceData) addOtherDependency(raceData->getId()); + } + + + ActorAdapter::ActorAdapter(Data& data) : mReferenceables(data.getReferenceables()) , mRaces(data.getRaces()) , mBodyParts(data.getBodyParts()) { - connect(data.getTableModel(UniversalId::Type_Referenceable), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + // Setup qt slots and signals + QAbstractItemModel* refModel = data.getTableModel(UniversalId::Type_Referenceable); + connect(refModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), + this, SLOT(handleReferenceablesInserted(const QModelIndex&, int, int))); + connect(refModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(handleReferenceableChanged(const QModelIndex&, const QModelIndex&))); - connect(data.getTableModel(UniversalId::Type_Race), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + connect(refModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), + this, SLOT(handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int))); + + QAbstractItemModel* raceModel = data.getTableModel(UniversalId::Type_Race); + connect(raceModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), + this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int))); + connect(raceModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(handleRaceChanged(const QModelIndex&, const QModelIndex&))); - connect(data.getTableModel(UniversalId::Type_BodyPart), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + connect(raceModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), + this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int))); + + QAbstractItemModel* partModel = data.getTableModel(UniversalId::Type_BodyPart); + connect(partModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), + this, SLOT(handleBodyPartsInserted(const QModelIndex&, int, int))); + connect(partModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(handleBodyPartChanged(const QModelIndex&, const QModelIndex&))); + connect(partModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), + this, SLOT(handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int))); } - const ActorAdapter::ActorPartMap* ActorAdapter::getActorParts(const std::string& refId, bool create) + ActorAdapter::ActorDataPtr ActorAdapter::getActorData(const std::string& id) { - auto it = mCachedActors.find(refId); - if (it != mCachedActors.end()) + // Return cached actor data if it exists + ActorDataPtr data = mCachedActors.get(id); + if (data) { - return &it->second.parts; + return data; } - else if (create) + + // Create the actor data + data.reset(new ActorData()); + setupActor(id, data); + mCachedActors.insert(id, data); + return data; + } + + void ActorAdapter::handleReferenceablesInserted(const QModelIndex& parent, int start, int end) + { + // Only rows added at the top level are pertinent. Others are caught by dataChanged handler. + if (!parent.isValid()) { - updateActor(refId); - return getActorParts(refId, false); - } - else - { - return nullptr; + for (int row = start; row <= end; ++row) + { + std::string refId = mReferenceables.getId(row); + markDirtyDependency(refId); + } } + + // Update affected + updateDirty(); } void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { - // Setup - const int TypeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); - int rowStart = getHighestIndex(topLeft).row(); - int rowEnd = getHighestIndex(botRight).row(); + int start = getHighestIndex(topLeft).row(); + int end = getHighestIndex(botRight).row(); // Handle each record - for (int row = rowStart; row <= rowEnd; ++row) + for (int row = start; row <= end; ++row) { - int type = mReferenceables.getData(row, TypeColumn).toInt(); - if (type == CSMWorld::UniversalId::Type_Creature || type == CSMWorld::UniversalId::Type_Npc) - { - // Update the cached npc or creature - std::string refId = mReferenceables.getId(row); - if (mCachedActors.find(refId) != mCachedActors.end()) - updateActor(refId); - } - else if (type == CSMWorld::UniversalId::Type_Armor || type == CSMWorld::UniversalId::Type_Clothing) + std::string refId = mReferenceables.getId(row); + markDirtyDependency(refId); + } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleReferenceablesAboutToBeRemoved(const QModelIndex& parent, int start, int end) + { + // Only rows at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) { std::string refId = mReferenceables.getId(row); - updateActorsWithDependency(refId); + markDirtyDependency(refId); } } } + void ActorAdapter::handleReferenceablesRemoved(const QModelIndex& parent, int start, int end) + { + // Changes specified in handleReferenceablesAboutToBeRemoved + updateDirty(); + } + + void ActorAdapter::handleRacesInserted(const QModelIndex& parent, int start, int end) + { + // Only rows added at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + std::string raceId = mReferenceables.getId(row); + markDirtyDependency(raceId); + } + } + + // Update affected + updateDirty(); + } + void ActorAdapter::handleRaceChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { - int rowStart = getHighestIndex(topLeft).row(); - int rowEnd = getHighestIndex(botRight).row(); - for (int row = rowStart; row <= rowEnd; ++row) + int start = getHighestIndex(topLeft).row(); + int end = getHighestIndex(botRight).row(); + for (int row = start; row <= end; ++row) { std::string raceId = mRaces.getId(row); - updateActorsWithDependency(raceId); + markDirtyDependency(raceId); } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleRacesAboutToBeRemoved(const QModelIndex& parent, int start, int end) + { + // Only changes at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + std::string raceId = mRaces.getId(row); + markDirtyDependency(raceId); + } + } + } + + void ActorAdapter::handleRacesRemoved(const QModelIndex& parent, int start, int end) + { + // Changes specified in handleRacesAboutToBeRemoved + updateDirty(); + } + + void ActorAdapter::handleBodyPartsInserted(const QModelIndex& parent, int start, int end) + { + // Only rows added at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + // Race specified by part may need update + auto& record = mBodyParts.getRecord(row); + if (!record.isDeleted()) + { + markDirtyDependency(record.get().mRace); + } + + std::string partId = mBodyParts.getId(row); + markDirtyDependency(partId); + } + } + + // Update affected + updateDirty(); } void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight) @@ -85,18 +303,39 @@ namespace CSMWorld int rowEnd = getHighestIndex(botRight).row(); for (int row = rowStart; row <= rowEnd; ++row) { - // Manually update race specified by part + // Race specified by part may need update auto& record = mBodyParts.getRecord(row); if (!record.isDeleted()) { - updateRace(record.get().mRace); + markDirtyDependency(record.get().mRace); } // Update entries with a tracked dependency std::string partId = mBodyParts.getId(row); - updateRacesWithDependency(partId); - updateActorsWithDependency(partId); + markDirtyDependency(partId); } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleBodyPartsAboutToBeRemoved(const QModelIndex& parent, int start, int end) + { + // Only changes at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + std::string partId = mBodyParts.getId(row); + markDirtyDependency(partId); + } + } + } + + void ActorAdapter::handleBodyPartsRemoved(const QModelIndex& parent, int start, int end) + { + // Changes specified in handleBodyPartsAboutToBeRemoved + updateDirty(); } QModelIndex ActorAdapter::getHighestIndex(QModelIndex index) const @@ -111,251 +350,223 @@ namespace CSMWorld return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; } - ActorAdapter::RaceData& ActorAdapter::getRaceData(const std::string& raceId) + ActorAdapter::RaceDataPtr ActorAdapter::getRaceData(const std::string& id) { - auto it = mCachedRaces.find(raceId); - if (it != mCachedRaces.end()) + // Return cached race data if it exists + RaceDataPtr data = mCachedRaces.get(id); + if (data) return data; + + // Create the race data + data.reset(new RaceData()); + setupRace(id, data); + mCachedRaces.insert(id, data); + return data; + } + + void ActorAdapter::setupActor(const std::string& id, ActorDataPtr data) + { + int index = mReferenceables.searchId(id); + if (index == -1) { - return it->second; + // Record does not exist + data->reset(id); + emit actorChanged(id); + return; + } + + auto& record = mReferenceables.getRecord(index); + if (record.isDeleted()) + { + // Record is deleted and therefore not accessible + data->reset(id); + emit actorChanged(id); + return; + } + + const int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType); + int type = mReferenceables.getData(index, TypeColumn).toInt(); + if (type == UniversalId::Type_Creature) + { + // Valid creature record + setupCreature(id, data); + emit actorChanged(id); + } + else if (type == UniversalId::Type_Npc) + { + // Valid npc record + setupNpc(id, data); + emit actorChanged(id); } else { - // Create and find result - updateRace(raceId); - return mCachedRaces.find(raceId)->second; + // Wrong record type + data->reset(id); + emit actorChanged(id); } } - void ActorAdapter::updateRace(const std::string& raceId) + void ActorAdapter::setupRace(const std::string& id, RaceDataPtr data) { - // Retrieve or create cache entry - auto raceDataIt = mCachedRaces.find(raceId); - if (raceDataIt == mCachedRaces.end()) + // Common setup + data->reset(id); + + int index = mRaces.searchId(id); + if (index == -1) { - auto result = mCachedRaces.emplace(raceId, RaceData()); - raceDataIt = result.first; - } - - auto& raceData = raceDataIt->second; - raceData.femaleParts.clear(); - raceData.maleParts.clear(); - raceData.dependencies.clear(); - - // Construct entry - for (int i = 0; i < mBodyParts.getSize(); ++i) - { - auto& record = mBodyParts.getRecord(i); - if (!record.isDeleted() && record.get().mRace == raceId) - { - auto& part = record.get(); - - // Part could affect race data - raceData.dependencies.emplace(part.mId, true); - - // Add base types - if (part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId)) - { - auto type = (ESM::BodyPart::MeshPart) part.mData.mPart; - // Note: Prefer the first part encountered for duplicates. emplace() does not overwrite - if (part.mData.mFlags & ESM::BodyPart::BPF_Female) - raceData.femaleParts.emplace(type, part.mId); - else - raceData.maleParts.emplace(type, part.mId); - } - } - } - - updateActorsWithDependency(raceId); - } - - void ActorAdapter::updateActor(const std::string& refId) - { - int index = mReferenceables.searchId(refId); - if (index != -1) - { - int typeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); - int recordType = mReferenceables.getData(index, typeColumn).toInt(); - if (recordType == CSMWorld::UniversalId::Type_Creature) - updateCreature(refId); - else if (recordType == CSMWorld::UniversalId::Type_Npc) - updateNpc(refId); - } - } - - void ActorAdapter::updateNpc(const std::string& refId) - { - auto& record = mReferenceables.getRecord(refId); - - // Retrieve record if possible - if (record.isDeleted()) - { - mCachedActors.erase(refId); - emit actorChanged(refId); + // Record does not exist return; } - auto& npc = dynamic_cast&>(record).get(); - // Create holder for cached data - auto actorIt = mCachedActors.find(refId); - if (actorIt == mCachedActors.end()) + auto& raceRecord = mRaces.getRecord(index); + if (raceRecord.isDeleted()) { - auto result = mCachedActors.emplace(refId, ActorData()); - actorIt = result.first; + // Record is deleted, so not accessible + return; } - auto& actorData = actorIt->second; - // Reset old data - actorData.parts.clear(); - actorData.dependencies.clear(); + // TODO move stuff in actor related to race here - // Look at the npc's inventory first + // Setup body parts + for (int i = 0; i < mBodyParts.getSize(); ++i) + { + std::string partId = mBodyParts.getId(i); + auto& partRecord = mBodyParts.getRecord(i); + + if (partRecord.isDeleted()) + { + // Record is deleted, so not accessible. + continue; + } + + auto& part = partRecord.get(); + if (part.mRace == id && part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId)) + { + auto type = (ESM::BodyPart::MeshPart) part.mData.mPart; + bool female = part.mData.mFlags & ESM::BodyPart::BPF_Female; + if (female) data->setFemalePart(type, part.mId); + else data->setMalePart(type, part.mId); + } + } + } + + void ActorAdapter::setupNpc(const std::string& id, ActorDataPtr data) + { + // Common setup, record is known to exist and is not deleted + int index = mReferenceables.searchId(id); + auto& npc = dynamic_cast&>(mReferenceables.getRecord(index)).get(); + + RaceDataPtr raceData = getRaceData(npc.mRace); + data->reset(id, !npc.isMale(), raceData); + + // Add inventory items for (auto& item : npc.mInventory.mList) { - if (item.mCount > 0) + if (item.mCount <= 0) continue; + std::string itemId = item.mItem.toString(); + addNpcItem(itemId, data); + } + + // Add head and hair + data->setPart(ESM::PRT_Head, npc.mHead); + data->setPart(ESM::PRT_Hair, npc.mHair); + } + + void ActorAdapter::addNpcItem(const std::string& itemId, ActorDataPtr data) + { + int index = mReferenceables.searchId(itemId); + if (index == -1) + { + // Item does not exist yet + data->addOtherDependency(itemId); + return; + } + + auto& record = mReferenceables.getRecord(index); + if (record.isDeleted()) + { + // Item cannot be accessed yet + data->addOtherDependency(itemId); + return; + } + + // Convenience function to add a parts list to actor data + auto addParts = [&](const ESM::PartReferenceList& list) { + for (auto& part : list.mParts) { - std::string itemId = item.mItem.toString(); - // Handle armor and clothing - int index = mReferenceables.searchId(itemId); - if (index != -1 && !mReferenceables.getRecord(index).isDeleted()) - { - auto& itemRecord = mReferenceables.getRecord(index); + std::string partId; + auto partType = (ESM::PartReferenceType) part.mPart; - int typeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); - int recordType = mReferenceables.getData(index, typeColumn).toInt(); - if (recordType == CSMWorld::UniversalId::Type_Armor) - { - // Changes here could affect the actor - actorData.dependencies.emplace(itemId, true); + if (data->isFemale()) + partId = part.mFemale; + if (partId.empty()) + partId = part.mMale; - // Add any parts if there is room - auto& armor = dynamic_cast&>(itemRecord).get(); - for (auto& part : armor.mParts.mParts) - { - std::string bodyPartId; - if (!npc.isMale()) - bodyPartId = part.mFemale; - if (bodyPartId.empty()) - bodyPartId = part.mMale; + if (!partId.empty()) data->setPart(partType, partId); + } + }; - if (!bodyPartId.empty()) - { - actorData.parts.emplace(static_cast(part.mPart), bodyPartId); - actorData.dependencies.emplace(bodyPartId, true); - } - } - } - else if (recordType == CSMWorld::UniversalId::Type_Clothing) - { - // Changes here could affect the actor - actorData.dependencies.emplace(itemId, true); + int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType); + int type = mReferenceables.getData(index, TypeColumn).toInt(); + if (type == UniversalId::Type_Armor) + { + auto& armor = dynamic_cast&>(record).get(); + addParts(armor.mParts); - // Add any parts if there is room - auto& clothing = dynamic_cast&>(itemRecord).get(); - for (auto& part : clothing.mParts.mParts) - { - std::string bodyPartId; - if (!npc.isMale()) - bodyPartId = part.mFemale; - if (bodyPartId.empty()) - bodyPartId = part.mMale; + // Changing parts could affect what is picked for rendering + data->addOtherDependency(itemId); + } + else if (type == UniversalId::Type_Clothing) + { + auto& clothing = dynamic_cast&>(record).get(); + addParts(clothing.mParts); - if (!bodyPartId.empty()) - { - actorData.parts.emplace(static_cast(part.mPart), bodyPartId); - actorData.dependencies.emplace(bodyPartId, true); - } - } - } - } + // Changing parts could affect what is picked for rendering + data->addOtherDependency(itemId); + } + } + + void ActorAdapter::setupCreature(const std::string& id, ActorDataPtr data) + { + data->reset(id); + + // TODO move stuff from Actor here + } + + void ActorAdapter::markDirtyDependency(const std::string& dep) + { + for (auto raceIt : mCachedRaces) + { + if (raceIt->hasDependency(dep)) + mDirtyRaces.emplace(raceIt->getId()); + } + for (auto actorIt : mCachedActors) + { + if (actorIt->hasDependency(dep)) + mDirtyActors.emplace(actorIt->getId()); + } + } + + void ActorAdapter::updateDirty() + { + // Handle races before actors, since actors are dependent on race + for (auto& race : mDirtyRaces) + { + RaceDataPtr data = mCachedRaces.get(race); + if (data) + { + setupRace(race, data); } } + mDirtyRaces.clear(); - // Lookup cached race parts - auto& raceData = getRaceData(npc.mRace); - - // Changes to race could affect the actor - actorData.dependencies.emplace(npc.mRace, true); - - // Fill in the rest with race specific body parts - for (int i = 0; i < ESM::PRT_Count; ++i) + for (auto& actor : mDirtyActors) { - auto type = static_cast(i); - if (actorData.parts.find(type) == actorData.parts.end()) + ActorDataPtr data = mCachedActors.get(actor); + if (data) { - switch (type) - { - case ESM::PRT_Head: - actorData.parts.emplace(type, npc.mHead); - actorData.dependencies.emplace(npc.mHead, true); - break; - case ESM::PRT_Hair: - actorData.parts.emplace(type, npc.mHair); - actorData.dependencies.emplace(npc.mHair, true); - break; - case ESM::PRT_Skirt: - case ESM::PRT_Shield: - case ESM::PRT_RPauldron: - case ESM::PRT_LPauldron: - case ESM::PRT_Weapon: - // No body part associated - break; - default: - { - std::string bodyPartId; - // Check female map if applicable - if (!npc.isMale()) - { - auto partIt = raceData.femaleParts.find(ESM::getMeshPart(type)); - if (partIt != raceData.femaleParts.end()) - bodyPartId = partIt->second; - } - - // Check male map next - if (bodyPartId.empty() || npc.isMale()) - { - auto partIt = raceData.maleParts.find(ESM::getMeshPart(type)); - if (partIt != raceData.maleParts.end()) - bodyPartId = partIt->second; - } - - // Add to map - if (!bodyPartId.empty()) - { - actorData.parts.emplace(type, bodyPartId); - actorData.dependencies.emplace(bodyPartId, true); - } - } - } + setupActor(actor, data); } } - - // Signal change to actor - emit actorChanged(refId); - } - - void ActorAdapter::updateCreature(const std::string& refId) - { - // Signal change to actor - emit actorChanged(refId); - } - - void ActorAdapter::updateActorsWithDependency(const std::string& id) - { - for (auto it : mCachedActors) - { - auto& deps = it.second.dependencies; - if (deps.find(id) != deps.end()) - updateActor(it.first); - } - } - - void ActorAdapter::updateRacesWithDependency(const std::string& id) - { - for (auto it : mCachedRaces) - { - auto& deps = it.second.dependencies; - if (deps.find(id) != deps.end()) - updateRace(it.first); - } + mDirtyActors.clear(); } } diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index f1148199c0..0e8b0c9fed 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -1,13 +1,13 @@ #ifndef CSM_WOLRD_ACTORADAPTER_H #define CSM_WOLRD_ACTORADAPTER_H -#include -#include -#include +#include +#include #include #include +#include #include #include @@ -17,88 +17,147 @@ namespace ESM { struct Race; - enum PartReferenceType; } namespace CSMWorld { class Data; - /// Quick and dirty hashing functor. - struct StringBoolPairHash - { - size_t operator()(const std::pair& value) const noexcept - { - auto stringHash = std::hash(); - return stringHash(value.first) + value.second; - } - }; - + /// Adapts multiple collections to provide the data needed to render + /// an npc or creature. class ActorAdapter : public QObject { - Q_OBJECT + Q_OBJECT + public: + + /// A list indexed by ESM::PartReferenceType + using ActorPartList = std::array; + /// A list indexed by ESM::BodyPart::MeshPart + using RacePartList = std::array; + /// Tracks unique strings + using StringSet = std::unordered_set; + + + /// Contains base race data shared between actors + class RaceData + { public: + /// Retrieves the id of the race represented + const std::string& getId() const; + /// Checks if a part could exist for the given type + bool handlesPart(ESM::PartReferenceType type) const; + /// Retrieves the associated body part + const std::string& getFemalePart(ESM::PartReferenceType index) const; + /// Retrieves the associated body part + const std::string& getMalePart(ESM::PartReferenceType index) const; + /// Checks if the race has a data dependency + bool hasDependency(const std::string& id) const; - // Maps body part type to 'body part' id - using ActorPartMap = std::unordered_map; - - ActorAdapter(CSMWorld::Data& data); - - const ActorPartMap* getActorParts(const std::string& refId, bool create=true); - - signals: - - void actorChanged(const std::string& refId); - - public slots: - - void handleReferenceableChanged(const QModelIndex&, const QModelIndex&); - void handleRaceChanged(const QModelIndex&, const QModelIndex&); - void handleBodyPartChanged(const QModelIndex&, const QModelIndex&); + /// Sets the associated part if it's empty and marks a dependency + void setFemalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId); + /// Sets the associated part if it's empty and marks a dependency + void setMalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId); + /// Marks an additional dependency + void addOtherDependency(const std::string& id); + /// Clears parts and dependencies + void reset(const std::string& raceId); private: - // Maps mesh part type to 'body part' id - using RacePartMap = std::unordered_map; - // Stores ids that are referenced by the actor. Data part is meaningless. - using DependencyMap = std::unordered_map; + bool handles(ESM::PartReferenceType type) const; + std::string mId; + RacePartList mFemaleParts; + RacePartList mMaleParts; + StringSet mDependencies; + }; + using RaceDataPtr = std::shared_ptr; - struct ActorData - { - ActorPartMap parts; - DependencyMap dependencies; - }; + /// Contains all the data needed to render an actor. Tracks dependencies + /// so that pertinent data changes can be checked. + class ActorData + { + public: + /// Retrieves the id of the actor represented + const std::string& getId() const; + /// Checks if the actor is female + bool isFemale() const; + /// Retrieves the associated actor part + const std::string& getPart(ESM::PartReferenceType index) const; + /// Checks if the actor has a data dependency + bool hasDependency(const std::string& id) const; - struct RaceData - { - RacePartMap femaleParts; - RacePartMap maleParts; - DependencyMap dependencies; - }; + /// Sets the actor part used and marks a dependency + void setPart(ESM::PartReferenceType partIndex, const std::string& partId); + /// Marks an additional dependency for the actor + void addOtherDependency(const std::string& id); + /// Clears race, parts, and dependencies + void reset(const std::string& actorId, bool female=true, RaceDataPtr raceData=nullptr); - ActorAdapter(const ActorAdapter&) = delete; - ActorAdapter& operator=(const ActorAdapter&) = delete; + private: + std::string mId; + bool mFemale; + RaceDataPtr mRaceData; + ActorPartList mParts; + StringSet mDependencies; + }; + using ActorDataPtr = std::shared_ptr; - QModelIndex getHighestIndex(QModelIndex) const; - bool is1stPersonPart(const std::string& id) const; - RaceData& getRaceData(const std::string& raceId); + ActorAdapter(Data& data); - void updateRace(const std::string& raceId); - void updateActor(const std::string& refId); - void updateNpc(const std::string& refId); - void updateCreature(const std::string& refId); + /// Obtains the shared data for a given actor + ActorDataPtr getActorData(const std::string& refId); - void updateActorsWithDependency(const std::string& id); - void updateRacesWithDependency(const std::string& id); + signals: - RefIdCollection& mReferenceables; - IdCollection& mRaces; - IdCollection& mBodyParts; + void actorChanged(const std::string& refId); - // Key: referenceable id - std::unordered_map mCachedActors; - // Key: race id - std::unordered_map mCachedRaces; + public slots: + + void handleReferenceablesInserted(const QModelIndex&, int, int); + void handleReferenceableChanged(const QModelIndex&, const QModelIndex&); + void handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int); + void handleReferenceablesRemoved(const QModelIndex&, int, int); + + void handleRacesInserted(const QModelIndex&, int, int); + void handleRaceChanged(const QModelIndex&, const QModelIndex&); + void handleRacesAboutToBeRemoved(const QModelIndex&, int, int); + void handleRacesRemoved(const QModelIndex&, int, int); + + void handleBodyPartsInserted(const QModelIndex&, int, int); + void handleBodyPartChanged(const QModelIndex&, const QModelIndex&); + void handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int); + void handleBodyPartsRemoved(const QModelIndex&, int, int); + + private: + + ActorAdapter(const ActorAdapter&) = delete; + ActorAdapter& operator=(const ActorAdapter&) = delete; + + QModelIndex getHighestIndex(QModelIndex) const; + bool is1stPersonPart(const std::string& id) const; + + RaceDataPtr getRaceData(const std::string& raceId); + + void setupActor(const std::string& id, ActorDataPtr data); + void setupRace(const std::string& id, RaceDataPtr data); + + void setupNpc(const std::string& id, ActorDataPtr data); + void addNpcItem(const std::string& itemId, ActorDataPtr data); + + void setupCreature(const std::string& id, ActorDataPtr data); + + void markDirtyDependency(const std::string& dependency); + void updateDirty(); + + RefIdCollection& mReferenceables; + IdCollection& mRaces; + IdCollection& mBodyParts; + + cache::WeakCache mCachedActors; // Key: referenceable id + cache::WeakCache mCachedRaces; // Key: race id + + StringSet mDirtyActors; // Actors that need updating + StringSet mDirtyRaces; // Races that need updating }; } diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index b9979ae2c6..0a2519d0dd 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -26,6 +26,7 @@ namespace CSVRender , mBaseNode(new osg::Group()) , mSkeleton(nullptr) { + mActorData = mData.getActorAdapter()->getActorData(mId); } osg::Group* Actor::getBaseNode() @@ -60,7 +61,6 @@ namespace CSVRender { if (mId == refId) { - Log(Debug::Info) << "Actor::actorChanged " << mId; update(); } } @@ -80,9 +80,6 @@ namespace CSVRender mSkeleton->accept(removeTriBipVisitor); removeTriBipVisitor.remove(); - // Attach weapons - loadBodyParts(creature.mId); - // Post setup mSkeleton->markDirty(); mSkeleton->setActive(SceneUtil::Skeleton::Active); @@ -141,12 +138,11 @@ namespace CSVRender void Actor::loadBodyParts(const std::string& actorId) { - auto actorAdapter = mData.getActorAdapter(); - auto parts = actorAdapter->getActorParts(actorId); - if (parts) + for (int i = 0; i < ESM::PRT_Count; ++i) { - for (auto& pair : *parts) - attachBodyPart(pair.first, getBodyPartMesh(pair.second)); + auto type = (ESM::PartReferenceType) i; + std::string partId = mActorData->getPart(type); + attachBodyPart(type, getBodyPartMesh(partId)); } } diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index 42a6019ed7..d7cbb30672 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -10,6 +10,8 @@ #include #include +#include "../../model/world/actoradapter.hpp" + namespace osg { class Group; @@ -63,6 +65,7 @@ namespace CSVRender bool mInitialized; int mType; CSMWorld::Data& mData; + CSMWorld::ActorAdapter::ActorDataPtr mActorData; osg::ref_ptr mBaseNode; SceneUtil::Skeleton* mSkeleton; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7af76137c5..494e1c5ce8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -150,6 +150,10 @@ add_component_dir (fallback fallback validate ) +add_component_dir(cache + weakcache + ) + if(NOT WIN32 AND NOT ANDROID) add_component_dir (crashcatcher crashcatcher diff --git a/components/cache/weakcache.hpp b/components/cache/weakcache.hpp new file mode 100644 index 0000000000..9d940cd746 --- /dev/null +++ b/components/cache/weakcache.hpp @@ -0,0 +1,116 @@ +#ifndef OPENMW_COMPONENTS_WEAKCACHE_HPP +#define OPENMW_COMPONENTS_WEAKCACHE_HPP + +#include +#include + +namespace cache +{ + /// \class WeakCache + /// Provides a container to weakly store pointers to shared data. + template + class WeakCache + { + public: + using WeakPtr = std::weak_ptr; + using StrongPtr = std::shared_ptr; + using Map = std::unordered_map; + + class iterator + { + public: + iterator(typename Map::iterator current, typename Map::iterator end); + iterator& operator++(); + bool operator==(const iterator& other); + bool operator!=(const iterator& other); + StrongPtr operator*(); + private: + typename Map::iterator mCurrent, mEnd; + StrongPtr mPtr; + }; + + /// Stores a weak pointer to the item. + void insert(Key key, StrongPtr value); + + /// Retrieves the item associated with the key. + /// \return An item or null. + StrongPtr get(Key key); + + iterator begin(); + iterator end(); + + private: + Map mData; + }; + + + template + WeakCache::iterator::iterator(typename Map::iterator current, typename Map::iterator end) + : mCurrent(current) + , mEnd(end) + { + // Move to 1st available valid item + for ( ; mCurrent != mEnd; ++mCurrent) + { + mPtr = mCurrent->second.lock(); + if (mPtr) break; + } + } + + template + typename WeakCache::iterator& WeakCache::iterator::operator++() + { + auto next = mCurrent; + ++next; + return *this = iterator(next, mEnd); + } + + template + bool WeakCache::iterator::operator==(const iterator& other) + { + return mCurrent == other.mCurrent; + } + + template + bool WeakCache::iterator::operator!=(const iterator& other) + { + return !(*this == other); + } + + template + typename WeakCache::StrongPtr WeakCache::iterator::operator*() + { + return mPtr; + } + + + template + void WeakCache::insert(Key key, StrongPtr value) + { + mData[key] = WeakPtr(value); + } + + template + typename WeakCache::StrongPtr WeakCache::get(Key key) + { + auto searchIt = mData.find(key); + if (searchIt != mData.end()) + return searchIt->second.lock(); + else + return StrongPtr(); + } + + template + typename WeakCache::iterator WeakCache::begin() + { + return iterator(mData.begin(), mData.end()); + } + + template + typename WeakCache::iterator WeakCache::end() + { + return iterator(mData.end(), mData.end()); + } +} + +#endif From 1518d630caa7068e2cf405120a65680bd08ac3e5 Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Fri, 7 Sep 2018 22:00:02 -0400 Subject: [PATCH 10/18] Fix issue with body part events not propogating to actors --- apps/opencs/model/world/actoradapter.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index b3a1216f75..6f564bac53 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -555,6 +555,14 @@ namespace CSMWorld if (data) { setupRace(race, data); + // Race was changed. Need to mark actor dependencies as dirty. + // Cannot use markDirtyDependency because that would invalidate + // the current iterator. + for (auto actorIt : mCachedActors) + { + if (actorIt->hasDependency(race)) + mDirtyActors.emplace(actorIt->getId()); + } } } mDirtyRaces.clear(); From c1ec926f4327c46dc98003ff90ecaf9205c665bf Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Fri, 7 Sep 2018 23:38:11 -0400 Subject: [PATCH 11/18] Workaround inconsistencies with record status changes --- apps/opencs/model/world/actoradapter.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 6f564bac53..d081f8a3ef 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -195,6 +195,10 @@ namespace CSMWorld int start = getHighestIndex(topLeft).row(); int end = getHighestIndex(botRight).row(); + // A change to record status (ex. Deleted) returns an invalid botRight + if (end == -1) + end = start; + // Handle each record for (int row = start; row <= end; ++row) { @@ -245,6 +249,11 @@ namespace CSMWorld { int start = getHighestIndex(topLeft).row(); int end = getHighestIndex(botRight).row(); + + // A change to record status (ex. Deleted) returns an invalid botRight + if (end == -1) + end = start; + for (int row = start; row <= end; ++row) { std::string raceId = mRaces.getId(row); @@ -299,9 +308,14 @@ namespace CSMWorld void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { - int rowStart = getHighestIndex(topLeft).row(); - int rowEnd = getHighestIndex(botRight).row(); - for (int row = rowStart; row <= rowEnd; ++row) + int start = getHighestIndex(topLeft).row(); + int end = getHighestIndex(botRight).row(); + + // A change to record status (ex. Deleted) returns an invalid botRight + if (end == -1) + end = start; + + for (int row = start; row <= end; ++row) { // Race specified by part may need update auto& record = mBodyParts.getRecord(row); From f43b70d77be6a21015c93db5076edcc118594c53 Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Sat, 8 Sep 2018 02:06:48 -0400 Subject: [PATCH 12/18] Centralize actor data, simplify logic --- apps/opencs/model/world/actoradapter.cpp | 52 ++++++++++--- apps/opencs/model/world/actoradapter.hpp | 13 +++- apps/opencs/view/render/actor.cpp | 97 +++++++----------------- apps/opencs/view/render/actor.hpp | 9 +-- apps/opencs/view/render/object.cpp | 88 +++++++++------------ 5 files changed, 116 insertions(+), 143 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index d081f8a3ef..47bc5f4da4 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "data.hpp" @@ -16,6 +17,11 @@ namespace CSMWorld return mId; } + bool ActorAdapter::RaceData::isBeast() const + { + return mIsBeast; + } + bool ActorAdapter::RaceData::handlesPart(ESM::PartReferenceType type) const { switch (type) @@ -63,9 +69,10 @@ namespace CSMWorld if (!id.empty()) mDependencies.emplace(id); } - void ActorAdapter::RaceData::reset(const std::string& id) + void ActorAdapter::RaceData::reset_data(const std::string& id, bool isBeast) { mId = id; + mIsBeast = isBeast; for (auto& str : mFemaleParts) str.clear(); for (auto& str : mMaleParts) @@ -82,11 +89,28 @@ namespace CSMWorld return mId; } + bool ActorAdapter::ActorData::isCreature() const + { + return mCreature; + } + bool ActorAdapter::ActorData::isFemale() const { return mFemale; } + std::string ActorAdapter::ActorData::getSkeleton() const + { + if (mCreature || !mSkeletonOverride.empty()) + return "meshes\\" + mSkeletonOverride; + + bool firstPerson = false; + bool beast = mRaceData ? mRaceData->isBeast() : false; + bool werewolf = false; + + return SceneUtil::getActorSkeleton(firstPerson, mFemale, beast, werewolf); + } + const std::string& ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const { if (mParts[index].empty() && mRaceData && mRaceData->handlesPart(index)) @@ -112,10 +136,12 @@ namespace CSMWorld if (!id.empty()) mDependencies.emplace(id); } - void ActorAdapter::ActorData::reset(const std::string& id, bool isFemale, RaceDataPtr raceData) + void ActorAdapter::ActorData::reset_data(const std::string& id, const std::string& skeleton, bool isCreature, bool isFemale, RaceDataPtr raceData) { mId = id; + mCreature = isCreature; mFemale = isFemale; + mSkeletonOverride = skeleton; mRaceData = raceData; for (auto& str : mParts) str.clear(); @@ -383,7 +409,7 @@ namespace CSMWorld if (index == -1) { // Record does not exist - data->reset(id); + data->reset_data(id); emit actorChanged(id); return; } @@ -392,7 +418,7 @@ namespace CSMWorld if (record.isDeleted()) { // Record is deleted and therefore not accessible - data->reset(id); + data->reset_data(id); emit actorChanged(id); return; } @@ -414,20 +440,18 @@ namespace CSMWorld else { // Wrong record type - data->reset(id); + data->reset_data(id); emit actorChanged(id); } } void ActorAdapter::setupRace(const std::string& id, RaceDataPtr data) { - // Common setup - data->reset(id); - int index = mRaces.searchId(id); if (index == -1) { // Record does not exist + data->reset_data(id); return; } @@ -435,10 +459,12 @@ namespace CSMWorld if (raceRecord.isDeleted()) { // Record is deleted, so not accessible + data->reset_data(id); return; } - // TODO move stuff in actor related to race here + auto& race = raceRecord.get(); + data->reset_data(id, race.mData.mFlags & ESM::Race::Beast); // Setup body parts for (int i = 0; i < mBodyParts.getSize(); ++i) @@ -470,7 +496,7 @@ namespace CSMWorld auto& npc = dynamic_cast&>(mReferenceables.getRecord(index)).get(); RaceDataPtr raceData = getRaceData(npc.mRace); - data->reset(id, !npc.isMale(), raceData); + data->reset_data(id, "", false, !npc.isMale(), raceData); // Add inventory items for (auto& item : npc.mInventory.mList) @@ -541,9 +567,11 @@ namespace CSMWorld void ActorAdapter::setupCreature(const std::string& id, ActorDataPtr data) { - data->reset(id); + // Record is known to exist and is not deleted + int index = mReferenceables.searchId(id); + auto& creature = dynamic_cast&>(mReferenceables.getRecord(index)).get(); - // TODO move stuff from Actor here + data->reset_data(id, creature.mModel, true); } void ActorAdapter::markDirtyDependency(const std::string& dep) diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 0e8b0c9fed..ba714e8402 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -44,6 +44,8 @@ namespace CSMWorld public: /// Retrieves the id of the race represented const std::string& getId() const; + /// Checks if it's a beast race + bool isBeast() const; /// Checks if a part could exist for the given type bool handlesPart(ESM::PartReferenceType type) const; /// Retrieves the associated body part @@ -60,11 +62,12 @@ namespace CSMWorld /// Marks an additional dependency void addOtherDependency(const std::string& id); /// Clears parts and dependencies - void reset(const std::string& raceId); + void reset_data(const std::string& raceId, bool isBeast=false); private: bool handles(ESM::PartReferenceType type) const; std::string mId; + bool mIsBeast; RacePartList mFemaleParts; RacePartList mMaleParts; StringSet mDependencies; @@ -78,8 +81,12 @@ namespace CSMWorld public: /// Retrieves the id of the actor represented const std::string& getId() const; + /// Checks if the actor is a creature + bool isCreature() const; /// Checks if the actor is female bool isFemale() const; + /// Returns the skeleton the actor should use for attaching parts to + std::string getSkeleton() const; /// Retrieves the associated actor part const std::string& getPart(ESM::PartReferenceType index) const; /// Checks if the actor has a data dependency @@ -90,11 +97,13 @@ namespace CSMWorld /// Marks an additional dependency for the actor void addOtherDependency(const std::string& id); /// Clears race, parts, and dependencies - void reset(const std::string& actorId, bool female=true, RaceDataPtr raceData=nullptr); + void reset_data(const std::string& actorId, const std::string& skeleton="", bool isCreature=false, bool female=true, RaceDataPtr raceData=nullptr); private: std::string mId; + bool mCreature; bool mFemale; + std::string mSkeletonOverride; RaceDataPtr mRaceData; ActorPartList mParts; StringSet mDependencies; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index 0a2519d0dd..7238166fc1 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -18,15 +18,15 @@ namespace CSVRender { const std::string Actor::MeshPrefix = "meshes\\"; - Actor::Actor(const std::string& id, int type, CSMWorld::Data& data) + Actor::Actor(const std::string& id, CSMWorld::Data& data) : mId(id) - , mInitialized(false) - , mType(type) , mData(data) , mBaseNode(new osg::Group()) , mSkeleton(nullptr) { mActorData = mData.getActorAdapter()->getActorData(mId); + connect(mData.getActorAdapter(), SIGNAL(actorChanged(const std::string&)), + this, SLOT(handleActorChanged(const std::string&))); } osg::Group* Actor::getBaseNode() @@ -36,25 +36,33 @@ namespace CSVRender void Actor::update() { - try - { - mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); + mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); - if (mType == CSMWorld::UniversalId::Type_Npc) - updateNpc(); - else if (mType == CSMWorld::UniversalId::Type_Creature) - updateCreature(); - } - catch (std::exception& e) + // Load skeleton + std::string skeletonModel = mActorData->getSkeleton(); + skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); + loadSkeleton(skeletonModel); + + if (!mActorData->isCreature()) { - Log(Debug::Info) << "Exception in Actor::update(): " << e.what(); + // Get rid of the extra attachments + SceneUtil::CleanObjectRootVisitor cleanVisitor; + mSkeleton->accept(cleanVisitor); + cleanVisitor.remove(); + + // Attach parts to skeleton + loadBodyParts(); + } + else + { + SceneUtil::RemoveTriBipVisitor removeTriBipVisitor; + mSkeleton->accept(removeTriBipVisitor); + removeTriBipVisitor.remove(); } - if (!mInitialized) - { - mInitialized = true; - connect(mData.getActorAdapter(), SIGNAL(actorChanged(const std::string&)), this, SLOT(handleActorChanged(const std::string&))); - } + // Post setup + mSkeleton->markDirty(); + mSkeleton->setActive(SceneUtil::Skeleton::Active); } void Actor::handleActorChanged(const std::string& refId) @@ -65,57 +73,6 @@ namespace CSVRender } } - void Actor::updateCreature() - { - auto& referenceables = mData.getReferenceables(); - - auto& creature = dynamic_cast& >(referenceables.getRecord(mId)).get(); - - // Load skeleton with meshes - std::string skeletonModel = MeshPrefix + creature.mModel; - skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); - loadSkeleton(skeletonModel); - - SceneUtil::RemoveTriBipVisitor removeTriBipVisitor; - mSkeleton->accept(removeTriBipVisitor); - removeTriBipVisitor.remove(); - - // Post setup - mSkeleton->markDirty(); - mSkeleton->setActive(SceneUtil::Skeleton::Active); - } - - void Actor::updateNpc() - { - auto& races = mData.getRaces(); - auto& referenceables = mData.getReferenceables(); - - auto& npc = dynamic_cast& >(referenceables.getRecord(mId)).get(); - auto& race = dynamic_cast& >(races.getRecord(npc.mRace)).get(); - - bool is1stPerson = false; - bool isFemale = !npc.isMale(); - bool isBeast = race.mData.mFlags & ESM::Race::Beast; - bool isWerewolf = false; - - // Load skeleton - std::string skeletonModel = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); - skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); - loadSkeleton(skeletonModel); - - // Get rid of the extra attachments - SceneUtil::CleanObjectRootVisitor cleanVisitor; - mSkeleton->accept(cleanVisitor); - cleanVisitor.remove(); - - // Attach parts to skeleton - loadBodyParts(npc.mId); - - // Post setup - mSkeleton->markDirty(); - mSkeleton->setActive(SceneUtil::Skeleton::Active); - } - void Actor::loadSkeleton(const std::string& model) { auto sceneMgr = mData.getResourceSystem()->getSceneManager(); @@ -136,7 +93,7 @@ namespace CSVRender } - void Actor::loadBodyParts(const std::string& actorId) + void Actor::loadBodyParts() { for (int i = 0; i < ESM::PRT_Count; ++i) { diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index d7cbb30672..2f19454f78 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -38,7 +38,7 @@ namespace CSVRender /// \param id The referenceable id /// \param type The record type /// \param data The data store - Actor(const std::string& id, int type, CSMWorld::Data& data); + Actor(const std::string& id, CSMWorld::Data& data); /// Retrieves the base node that meshes are attached to osg::Group* getBaseNode(); @@ -50,11 +50,8 @@ namespace CSVRender void handleActorChanged(const std::string& refId); private: - void updateCreature(); - void updateNpc(); - void loadSkeleton(const std::string& model); - void loadBodyParts(const std::string& actorId); + void loadBodyParts(); void attachBodyPart(ESM::PartReferenceType, const std::string& mesh); std::string getBodyPartMesh(const std::string& bodyPartId); @@ -62,8 +59,6 @@ namespace CSVRender static const std::string MeshPrefix; std::string mId; - bool mInitialized; - int mType; CSMWorld::Data& mData; CSMWorld::ActorAdapter::ActorDataPtr mActorData; diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 63f1bc8a82..6f0fb66068 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -83,74 +83,58 @@ void CSVRender::Object::update() { clear(); - std::string model; - int error = 0; // 1 referenceable does not exist, 2 referenceable does not specify a mesh - const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); + const int TypeIndex = referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); + const int ModelIndex = referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model); int index = referenceables.searchId (mReferenceableId); - int recordType = -1; const ESM::Light* light = NULL; - if (index==-1) - error = 1; - else - { - /// \todo check for Deleted state (error 1) - - model = referenceables.getData (index, - referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model)). - toString().toUtf8().constData(); - - recordType = - referenceables.getData (index, - referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType)).toInt(); - if (recordType == CSMWorld::UniversalId::Type_Light) - { - light = &dynamic_cast& >(referenceables.getRecord(index)).get(); - if (model.empty()) - model = "marker_light.nif"; - } - - if (recordType == CSMWorld::UniversalId::Type_CreatureLevelledList) - { - if (model.empty()) - model = "marker_creature.nif"; - } - - if (recordType != CSMWorld::UniversalId::Type_Npc && model.empty()) - error = 2; - } - mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); - if (error) + if (index == -1) { mBaseNode->addChild(createErrorCube()); + return; } - else + + /// \todo check for Deleted state (error 1) + + int recordType = referenceables.getData(index, TypeIndex).toInt(); + std::string model = referenceables.getData(index, ModelIndex).toString().toUtf8().constData(); + + if (recordType == CSMWorld::UniversalId::Type_Light) { - try + light = &dynamic_cast& >(referenceables.getRecord(index)).get(); + if (model.empty()) + model = "marker_light.nif"; + } + + if (recordType == CSMWorld::UniversalId::Type_CreatureLevelledList) + { + if (model.empty()) + model = "marker_creature.nif"; + } + + try + { + if (recordType == CSMWorld::UniversalId::Type_Npc || recordType == CSMWorld::UniversalId::Type_Creature) { - if (recordType == CSMWorld::UniversalId::Type_Npc || recordType == CSMWorld::UniversalId::Type_Creature) - { - if (!mActor) - mActor.reset(new Actor(mReferenceableId, recordType, mData)); - mActor->update(); - mBaseNode->addChild(mActor->getBaseNode()); - } - else - { - std::string path = "meshes\\" + model; - mResourceSystem->getSceneManager()->getInstance(path, mBaseNode); - } + if (!mActor) mActor.reset(new Actor(mReferenceableId, mData)); + mActor->update(); + mBaseNode->addChild(mActor->getBaseNode()); } - catch (std::exception& e) + else { - Log(Debug::Error) << e.what(); - mBaseNode->addChild(createErrorCube()); + std::string path = "meshes\\" + model; + mResourceSystem->getSceneManager()->getInstance(path, mBaseNode); } } + catch (std::exception& e) + { + // TODO: use error marker mesh + Log(Debug::Error) << e.what(); + } if (light) { From 676fc488557453f1b49c2d0441221ac573de2efb Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Sat, 8 Sep 2018 02:24:33 -0400 Subject: [PATCH 13/18] Re-add logic for empty model --- apps/opencs/view/render/object.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 6f0fb66068..8b3b5ca255 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -124,11 +124,15 @@ void CSVRender::Object::update() mActor->update(); mBaseNode->addChild(mActor->getBaseNode()); } - else + else if (!model.empty()) { std::string path = "meshes\\" + model; mResourceSystem->getSceneManager()->getInstance(path, mBaseNode); } + else + { + throw std::runtime_error(mReferenceableId + " has no model"); + } } catch (std::exception& e) { From 0096951f2598df434e436000c994c1a3320179d7 Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Sat, 8 Sep 2018 02:32:47 -0400 Subject: [PATCH 14/18] cleanup --- apps/opencs/model/world/actoradapter.cpp | 1 - apps/opencs/model/world/data.hpp | 1 - apps/opencs/view/render/actor.cpp | 2 -- apps/opencs/view/render/object.cpp | 2 -- 4 files changed, 6 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 47bc5f4da4..8c9e5bd1f0 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -1,6 +1,5 @@ #include "actoradapter.hpp" -#include #include #include #include diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 7c4d8885ad..e50780f507 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -74,7 +74,6 @@ namespace ESM namespace CSMWorld { - class ActorAdapter; class ResourcesManager; class Resources; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index 7238166fc1..d6077a65a5 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -3,12 +3,10 @@ #include #include -#include #include #include #include #include -#include #include #include diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 8b3b5ca255..36d80fa4b7 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include @@ -31,7 +30,6 @@ #include #include #include -#include #include "actor.hpp" #include "mask.hpp" From 7eb1b14b21ba0122b1aac63c1b5a2be38249c6f7 Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Sat, 8 Sep 2018 03:13:03 -0400 Subject: [PATCH 15/18] Periodically prune empty elements in weak cache --- components/cache/weakcache.hpp | 38 +++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/components/cache/weakcache.hpp b/components/cache/weakcache.hpp index 9d940cd746..976818cc21 100644 --- a/components/cache/weakcache.hpp +++ b/components/cache/weakcache.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace cache { @@ -19,18 +20,19 @@ namespace cache class iterator { public: - iterator(typename Map::iterator current, typename Map::iterator end); + iterator(WeakCache* cache, typename Map::iterator current, typename Map::iterator end); iterator& operator++(); bool operator==(const iterator& other); bool operator!=(const iterator& other); StrongPtr operator*(); private: + WeakCache* mCache; typename Map::iterator mCurrent, mEnd; StrongPtr mPtr; }; /// Stores a weak pointer to the item. - void insert(Key key, StrongPtr value); + void insert(Key key, StrongPtr value, bool prune=true); /// Retrieves the item associated with the key. /// \return An item or null. @@ -39,14 +41,19 @@ namespace cache iterator begin(); iterator end(); + /// Removes known invalid entries + void prune(); + private: Map mData; + std::vector mDirty; }; template - WeakCache::iterator::iterator(typename Map::iterator current, typename Map::iterator end) - : mCurrent(current) + WeakCache::iterator::iterator(WeakCache* cache, typename Map::iterator current, typename Map::iterator end) + : mCache(cache) + , mCurrent(current) , mEnd(end) { // Move to 1st available valid item @@ -54,6 +61,7 @@ namespace cache { mPtr = mCurrent->second.lock(); if (mPtr) break; + else mCache->mDirty.push_back(mCurrent->first); } } @@ -62,7 +70,7 @@ namespace cache { auto next = mCurrent; ++next; - return *this = iterator(next, mEnd); + return *this = iterator(mCache, next, mEnd); } template @@ -85,9 +93,10 @@ namespace cache template - void WeakCache::insert(Key key, StrongPtr value) + void WeakCache::insert(Key key, StrongPtr value, bool shouldPrune) { mData[key] = WeakPtr(value); + if (shouldPrune) prune(); } template @@ -103,13 +112,26 @@ namespace cache template typename WeakCache::iterator WeakCache::begin() { - return iterator(mData.begin(), mData.end()); + return iterator(this, mData.begin(), mData.end()); } template typename WeakCache::iterator WeakCache::end() { - return iterator(mData.end(), mData.end()); + return iterator(this, mData.end(), mData.end()); + } + + template + void WeakCache::prune() + { + // Remove empty entries + for (auto& key : mDirty) + { + auto it = mData.find(key); + if (it != mData.end() && it->second.use_count() == 0) + mData.erase(it); + } + mDirty.clear(); } } From ac848b09028315850916bf644a22c965bd98581a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 27 Sep 2018 13:14:34 +0400 Subject: [PATCH 16/18] Use male bodyparts as fallback for females in the editor --- apps/opencs/model/world/actoradapter.cpp | 12 ++++++++++-- apps/opencs/model/world/actoradapter.hpp | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 8c9e5bd1f0..425c4c8044 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -110,11 +110,19 @@ namespace CSMWorld return SceneUtil::getActorSkeleton(firstPerson, mFemale, beast, werewolf); } - const std::string& ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const + const std::string ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const { if (mParts[index].empty() && mRaceData && mRaceData->handlesPart(index)) { - return mFemale ? mRaceData->getFemalePart(index) : mRaceData->getMalePart(index); + if (mFemale) + { + // Note: we should use male parts for females as fallback + const std::string femalePart = mRaceData->getFemalePart(index); + if (!femalePart.empty()) + return femalePart; + } + + return mRaceData->getMalePart(index); } return mParts[index]; } diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index ba714e8402..d842bca354 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -88,7 +88,7 @@ namespace CSMWorld /// Returns the skeleton the actor should use for attaching parts to std::string getSkeleton() const; /// Retrieves the associated actor part - const std::string& getPart(ESM::PartReferenceType index) const; + const std::string getPart(ESM::PartReferenceType index) const; /// Checks if the actor has a data dependency bool hasDependency(const std::string& id) const; From 35abf7367cf85220c0117c270906661f73e8af76 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 27 Sep 2018 18:22:17 +0400 Subject: [PATCH 17/18] Implement wearing priority for editor --- CHANGELOG.md | 1 + apps/opencs/model/world/actoradapter.cpp | 83 ++++++++++++++++++++---- apps/opencs/model/world/actoradapter.hpp | 5 +- 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04547e2496..c72ac7b9e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -132,6 +132,7 @@ Bug #4653: Length of non-ASCII strings is handled incorrectly in ESM reader Bug #4654: Editor: UpdateVisitor does not initialize skeletons for animated objects Feature #912: Editor: Add missing icons to UniversalId tables + Feature #1221: Editor: Creature/NPC rendering Feature #1617: Editor: Enchantment effect record verifier Feature #1645: Casting effects from objects Feature #2606: Editor: Implemented (optional) case sensitive global search diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 425c4c8044..ea40a1f8dd 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -112,7 +112,8 @@ namespace CSMWorld const std::string ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const { - if (mParts[index].empty() && mRaceData && mRaceData->handlesPart(index)) + auto it = mParts.find(index); + if (it == mParts.end() && mRaceData && mRaceData->handlesPart(index)) { if (mFemale) { @@ -124,7 +125,9 @@ namespace CSMWorld return mRaceData->getMalePart(index); } - return mParts[index]; + + const std::string& partName = it->second.first; + return partName; } bool ActorAdapter::ActorData::hasDependency(const std::string& id) const @@ -132,9 +135,16 @@ namespace CSMWorld return mDependencies.find(id) != mDependencies.end(); } - void ActorAdapter::ActorData::setPart(ESM::PartReferenceType index, const std::string& partId) + void ActorAdapter::ActorData::setPart(ESM::PartReferenceType index, const std::string& partId, int priority) { - mParts[index] = partId; + auto it = mParts.find(index); + if (it != mParts.end()) + { + if (it->second.second >= priority) + return; + } + + mParts[index] = std::make_pair(partId, priority); addOtherDependency(partId); } @@ -150,8 +160,7 @@ namespace CSMWorld mFemale = isFemale; mSkeletonOverride = skeleton; mRaceData = raceData; - for (auto& str : mParts) - str.clear(); + mParts.clear(); mDependencies.clear(); // Mark self and race as a dependency @@ -514,8 +523,8 @@ namespace CSMWorld } // Add head and hair - data->setPart(ESM::PRT_Head, npc.mHead); - data->setPart(ESM::PRT_Hair, npc.mHair); + data->setPart(ESM::PRT_Head, npc.mHead, 0); + data->setPart(ESM::PRT_Hair, npc.mHair, 0); } void ActorAdapter::addNpcItem(const std::string& itemId, ActorDataPtr data) @@ -537,8 +546,8 @@ namespace CSMWorld } // Convenience function to add a parts list to actor data - auto addParts = [&](const ESM::PartReferenceList& list) { - for (auto& part : list.mParts) + auto addParts = [&](const std::vector& list, int priority) { + for (auto& part : list) { std::string partId; auto partType = (ESM::PartReferenceType) part.mPart; @@ -548,7 +557,7 @@ namespace CSMWorld if (partId.empty()) partId = part.mMale; - if (!partId.empty()) data->setPart(partType, partId); + data->setPart(partType, partId, priority); } }; @@ -557,15 +566,63 @@ namespace CSMWorld if (type == UniversalId::Type_Armor) { auto& armor = dynamic_cast&>(record).get(); - addParts(armor.mParts); + addParts(armor.mParts.mParts, 1); // Changing parts could affect what is picked for rendering data->addOtherDependency(itemId); } else if (type == UniversalId::Type_Clothing) { + int priority = 0; + // TODO: reserve bodyparts for robes and skirts auto& clothing = dynamic_cast&>(record).get(); - addParts(clothing.mParts); + + if (clothing.mData.mType == ESM::Clothing::Robe) + { + auto reservedList = std::vector(); + + ESM::PartReference pr; + pr.mMale = ""; + pr.mFemale = ""; + + ESM::PartReferenceType parts[] = { + ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, + ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, + ESM::PRT_RForearm, ESM::PRT_LForearm + }; + size_t parts_size = sizeof(parts)/sizeof(parts[0]); + for(size_t p = 0;p < parts_size;++p) + { + pr.mPart = parts[p]; + reservedList.push_back(pr); + } + + priority = parts_size; + addParts(reservedList, priority); + } + else if (clothing.mData.mType == ESM::Clothing::Skirt) + { + auto reservedList = std::vector(); + + ESM::PartReference pr; + pr.mMale = ""; + pr.mFemale = ""; + + ESM::PartReferenceType parts[] = { + ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg + }; + size_t parts_size = sizeof(parts)/sizeof(parts[0]); + for(size_t p = 0;p < parts_size;++p) + { + pr.mPart = parts[p]; + reservedList.push_back(pr); + } + + priority = parts_size; + addParts(reservedList, priority); + } + + addParts(clothing.mParts.mParts, priority); // Changing parts could affect what is picked for rendering data->addOtherDependency(itemId); diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index d842bca354..b97c652a83 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -2,6 +2,7 @@ #define CSM_WOLRD_ACTORADAPTER_H #include +#include #include #include @@ -31,7 +32,7 @@ namespace CSMWorld public: /// A list indexed by ESM::PartReferenceType - using ActorPartList = std::array; + using ActorPartList = std::map>; /// A list indexed by ESM::BodyPart::MeshPart using RacePartList = std::array; /// Tracks unique strings @@ -93,7 +94,7 @@ namespace CSMWorld bool hasDependency(const std::string& id) const; /// Sets the actor part used and marks a dependency - void setPart(ESM::PartReferenceType partIndex, const std::string& partId); + void setPart(ESM::PartReferenceType partIndex, const std::string& partId, int priority); /// Marks an additional dependency for the actor void addOtherDependency(const std::string& id); /// Clears race, parts, and dependencies From 43c7438e8e9907cfc0e895f11a7176cd03837d61 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 28 Sep 2018 16:47:47 +0400 Subject: [PATCH 18/18] Move WeakCache to components/misc --- apps/opencs/model/world/actoradapter.hpp | 6 +++--- components/CMakeLists.txt | 6 +----- components/{cache => misc}/weakcache.hpp | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) rename components/{cache => misc}/weakcache.hpp (99%) diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index b97c652a83..1c9265be42 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -8,9 +8,9 @@ #include #include -#include #include #include +#include #include "refidcollection.hpp" #include "idcollection.hpp" @@ -163,8 +163,8 @@ namespace CSMWorld IdCollection& mRaces; IdCollection& mBodyParts; - cache::WeakCache mCachedActors; // Key: referenceable id - cache::WeakCache mCachedRaces; // Key: race id + Misc::WeakCache mCachedActors; // Key: referenceable id + Misc::WeakCache mCachedRaces; // Key: race id StringSet mDirtyActors; // Actors that need updating StringSet mDirtyRaces; // Races that need updating diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 494e1c5ce8..5c245afd08 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -86,7 +86,7 @@ add_component_dir (esmterrain ) add_component_dir (misc - constants utf8stream stringops resourcehelpers rng messageformatparser + constants utf8stream stringops resourcehelpers rng messageformatparser weakcache ) add_component_dir (debug @@ -150,10 +150,6 @@ add_component_dir (fallback fallback validate ) -add_component_dir(cache - weakcache - ) - if(NOT WIN32 AND NOT ANDROID) add_component_dir (crashcatcher crashcatcher diff --git a/components/cache/weakcache.hpp b/components/misc/weakcache.hpp similarity index 99% rename from components/cache/weakcache.hpp rename to components/misc/weakcache.hpp index 976818cc21..022a722dba 100644 --- a/components/cache/weakcache.hpp +++ b/components/misc/weakcache.hpp @@ -5,7 +5,7 @@ #include #include -namespace cache +namespace Misc { /// \class WeakCache /// Provides a container to weakly store pointers to shared data.