From 97ac0a92dd430c6a7b17a2b812fde52ecbc5b010 Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Sat, 28 Jul 2018 12:23:43 -0500 Subject: [PATCH] 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; }; }