Move data handling out of rendering code, equip armor/clothes

pull/1944/head
Kyle Cooley 7 years ago committed by Andrei Kortunov
parent 6b42f37918
commit 97ac0a92dd

@ -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
)

@ -0,0 +1,242 @@
#include "actoradapter.hpp"
#include <iostream>
#include <components/esm/loadarmo.hpp>
#include <components/esm/loadclot.hpp>
#include <components/esm/loadnpc.hpp>
#include <components/esm/loadrace.hpp>
#include <components/esm/mappings.hpp>
#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<const Record<ESM::NPC>&>(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<const Record<ESM::Armor>&>(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<ESM::PartReferenceType>(part.mPart), bodyPartId);
}
}
else if (recordType == CSMWorld::UniversalId::Type_Clothing)
{
auto& clothing = dynamic_cast<const Record<ESM::Clothing>&>(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<ESM::PartReferenceType>(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<ESM::PartReferenceType>(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
}
}

@ -0,0 +1,80 @@
#ifndef CSM_WOLRD_ACTORADAPTER_H
#define CSM_WOLRD_ACTORADAPTER_H
#include <functional>
#include <unordered_map>
#include <utility>
#include <QObject>
#include <components/esm/loadarmo.hpp>
#include <components/esm/loadbody.hpp>
#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<std::string, bool>& value) const noexcept
{
auto stringHash = std::hash<std::string>();
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<ESM::PartReferenceType, std::string>;
// Maps mesh part type to 'body part' id
using RacePartMap = std::unordered_map<ESM::BodyPart::MeshPart, std::string>;
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<ESM::Race>& mRaces;
IdCollection<ESM::BodyPart>& mBodyParts;
// Key: referenceable id
std::unordered_map<std::string, ActorPartMap> mActorPartMaps;
// Key: race id, is female
std::unordered_map<std::pair<std::string, bool>, RacePartMap, StringBoolPairHash> mRacePartMaps;
};
}
#endif

@ -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();

@ -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<ESM::Filter> mFilters;
Collection<MetaData> mMetaData;
std::unique_ptr<ActorAdapter> mActorAdapter;
const Fallback::Map* mFallbackMap;
std::vector<QAbstractItemModel *> mModels;
std::map<UniversalId::Type, QAbstractItemModel *> 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.

@ -12,7 +12,6 @@
#include <components/sceneutil/actorutil.hpp>
#include <components/sceneutil/attach.hpp>
#include <components/sceneutil/skeleton.hpp>
#include <components/sceneutil/visitor.hpp>
#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<const CSMWorld::Record<ESM::Creature>& >(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<const CSMWorld::Record<ESM::NPC>& >(referenceables.getRecord(mId)).get();
auto& race = dynamic_cast<const CSMWorld::Record<ESM::Race>& >(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<int, int, std::string>;
using RaceToBPMap = std::map<BPRaceKey, std::string>;
// 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<ESM::PartReferenceType>(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)

@ -5,6 +5,9 @@
#include <osg/ref_ptr>
#include <components/esm/loadarmo.hpp>
#include <components/sceneutil/visitor.hpp>
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<osg::Group> mBaseNode;
SceneUtil::Skeleton* mSkeleton;
SceneUtil::NodeMapVisitor::NodeMap mNodeMap;
};
}

Loading…
Cancel
Save