diff --git a/CHANGELOG.md b/CHANGELOG.md index 93fba19f63..bf1e9e5e0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,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/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f2821f184a..b0bd95eb95 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 ) @@ -88,7 +89,7 @@ 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 diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp new file mode 100644 index 0000000000..ea40a1f8dd --- /dev/null +++ b/apps/opencs/model/world/actoradapter.cpp @@ -0,0 +1,686 @@ +#include "actoradapter.hpp" + +#include +#include +#include +#include +#include +#include + +#include "data.hpp" + +namespace CSMWorld +{ + const std::string& ActorAdapter::RaceData::getId() const + { + return mId; + } + + bool ActorAdapter::RaceData::isBeast() const + { + return mIsBeast; + } + + 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_data(const std::string& id, bool isBeast) + { + mId = id; + mIsBeast = isBeast; + 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::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 + { + auto it = mParts.find(index); + if (it == mParts.end() && mRaceData && mRaceData->handlesPart(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); + } + + const std::string& partName = it->second.first; + return partName; + } + + 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, int priority) + { + auto it = mParts.find(index); + if (it != mParts.end()) + { + if (it->second.second >= priority) + return; + } + + mParts[index] = std::make_pair(partId, priority); + addOtherDependency(partId); + } + + void ActorAdapter::ActorData::addOtherDependency(const std::string& id) + { + if (!id.empty()) mDependencies.emplace(id); + } + + 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; + mParts.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()) + { + // 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(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(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))); + } + + ActorAdapter::ActorDataPtr ActorAdapter::getActorData(const std::string& id) + { + // Return cached actor data if it exists + ActorDataPtr data = mCachedActors.get(id); + if (data) + { + return data; + } + + // 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()) + { + 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) + { + 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) + { + 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); + 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 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); + 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) + { + 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); + if (!record.isDeleted()) + { + markDirtyDependency(record.get().mRace); + } + + // Update entries with a tracked dependency + std::string partId = mBodyParts.getId(row); + 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 + { + while (index.parent().isValid()) + index = index.parent(); + return index; + } + + bool ActorAdapter::is1stPersonPart(const std::string& name) const + { + return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; + } + + ActorAdapter::RaceDataPtr ActorAdapter::getRaceData(const std::string& id) + { + // 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) + { + // Record does not exist + data->reset_data(id); + emit actorChanged(id); + return; + } + + auto& record = mReferenceables.getRecord(index); + if (record.isDeleted()) + { + // Record is deleted and therefore not accessible + data->reset_data(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 + { + // Wrong record type + data->reset_data(id); + emit actorChanged(id); + } + } + + void ActorAdapter::setupRace(const std::string& id, RaceDataPtr data) + { + int index = mRaces.searchId(id); + if (index == -1) + { + // Record does not exist + data->reset_data(id); + return; + } + + auto& raceRecord = mRaces.getRecord(index); + if (raceRecord.isDeleted()) + { + // Record is deleted, so not accessible + data->reset_data(id); + return; + } + + 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) + { + 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_data(id, "", false, !npc.isMale(), raceData); + + // Add inventory items + for (auto& item : npc.mInventory.mList) + { + 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, 0); + data->setPart(ESM::PRT_Hair, npc.mHair, 0); + } + + 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 std::vector& list, int priority) { + for (auto& part : list) + { + std::string partId; + auto partType = (ESM::PartReferenceType) part.mPart; + + if (data->isFemale()) + partId = part.mFemale; + if (partId.empty()) + partId = part.mMale; + + data->setPart(partType, partId, priority); + } + }; + + 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.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(); + + 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); + } + } + + void ActorAdapter::setupCreature(const std::string& id, ActorDataPtr data) + { + // Record is known to exist and is not deleted + int index = mReferenceables.searchId(id); + auto& creature = dynamic_cast&>(mReferenceables.getRecord(index)).get(); + + data->reset_data(id, creature.mModel, true); + } + + 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); + // 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(); + + for (auto& actor : mDirtyActors) + { + ActorDataPtr data = mCachedActors.get(actor); + if (data) + { + setupActor(actor, data); + } + } + mDirtyActors.clear(); + } +} diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp new file mode 100644 index 0000000000..1c9265be42 --- /dev/null +++ b/apps/opencs/model/world/actoradapter.hpp @@ -0,0 +1,174 @@ +#ifndef CSM_WOLRD_ACTORADAPTER_H +#define CSM_WOLRD_ACTORADAPTER_H + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "refidcollection.hpp" +#include "idcollection.hpp" + +namespace ESM +{ + struct Race; +} + +namespace CSMWorld +{ + class Data; + + /// Adapts multiple collections to provide the data needed to render + /// an npc or creature. + class ActorAdapter : public QObject + { + Q_OBJECT + public: + + /// A list indexed by ESM::PartReferenceType + using ActorPartList = std::map>; + /// 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 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 + 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; + + /// 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_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; + }; + using RaceDataPtr = std::shared_ptr; + + /// 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 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 + 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, int priority); + /// Marks an additional dependency for the actor + void addOtherDependency(const std::string& id); + /// Clears race, parts, and dependencies + 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; + }; + using ActorDataPtr = std::shared_ptr; + + + ActorAdapter(Data& data); + + /// Obtains the shared data for a given actor + ActorDataPtr getActorData(const std::string& refId); + + signals: + + void actorChanged(const std::string& refId); + + 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; + + Misc::WeakCache mCachedActors; // Key: referenceable id + Misc::WeakCache mCachedRaces; // Key: race id + + StringSet mDirtyActors; // Actors that need updating + StringSet mDirtyRaces; // Races that need updating + }; +} + +#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..e50780f507 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" @@ -110,6 +111,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 +289,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 new file mode 100644 index 0000000000..d6077a65a5 --- /dev/null +++ b/apps/opencs/view/render/actor.cpp @@ -0,0 +1,128 @@ +#include "actor.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../../model/world/data.hpp" + +namespace CSVRender +{ + const std::string Actor::MeshPrefix = "meshes\\"; + + Actor::Actor(const std::string& id, CSMWorld::Data& data) + : mId(id) + , 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() + { + return mBaseNode; + } + + void Actor::update() + { + mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); + + // Load skeleton + std::string skeletonModel = mActorData->getSkeleton(); + skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); + loadSkeleton(skeletonModel); + + if (!mActorData->isCreature()) + { + // 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(); + } + + // Post setup + mSkeleton->markDirty(); + mSkeleton->setActive(SceneUtil::Skeleton::Active); + } + + void Actor::handleActorChanged(const std::string& refId) + { + if (mId == refId) + { + update(); + } + } + + 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) + { + mSkeleton = new SceneUtil::Skeleton(); + mSkeleton->addChild(temp); + } + mBaseNode->addChild(mSkeleton); + + // Map bone names to bones + mNodeMap.clear(); + SceneUtil::NodeMapVisitor nmVisitor(mNodeMap); + mSkeleton->accept(nmVisitor); + + } + + void Actor::loadBodyParts() + { + for (int i = 0; i < ESM::PRT_Count; ++i) + { + auto type = (ESM::PartReferenceType) i; + std::string partId = mActorData->getPart(type); + attachBodyPart(type, getBodyPartMesh(partId)); + } + } + + 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) + { + 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 new file mode 100644 index 0000000000..2f19454f78 --- /dev/null +++ b/apps/opencs/view/render/actor.hpp @@ -0,0 +1,71 @@ +#ifndef OPENCS_VIEW_RENDER_ACTOR_H +#define OPENCS_VIEW_RENDER_ACTOR_H + +#include + +#include + +#include + +#include +#include + +#include "../../model/world/actoradapter.hpp" + +namespace osg +{ + class Group; +} + +namespace CSMWorld +{ + class Data; +} + +namespace SceneUtil +{ + class Skeleton; +} + +namespace CSVRender +{ + /// Handles loading an npc or creature + class Actor : public QObject + { + Q_OBJECT + public: + /// Creates an actor. + /// \param id The referenceable id + /// \param type The record type + /// \param data The data store + Actor(const std::string& id, 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 slots: + void handleActorChanged(const std::string& refId); + + private: + void loadSkeleton(const std::string& model); + void loadBodyParts(); + void attachBodyPart(ESM::PartReferenceType, const std::string& mesh); + + std::string getBodyPartMesh(const std::string& bodyPartId); + + static const std::string MeshPrefix; + + std::string mId; + CSMWorld::Data& mData; + CSMWorld::ActorAdapter::ActorDataPtr mActorData; + + osg::ref_ptr mBaseNode; + SceneUtil::Skeleton* mSkeleton; + SceneUtil::NodeMapVisitor::NodeMap mNodeMap; + }; +} + +#endif diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 2b1e3addee..36d80fa4b7 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 @@ -29,6 +31,7 @@ #include #include +#include "actor.hpp" #include "mask.hpp" @@ -78,64 +81,62 @@ 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); 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(); - - int 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 (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 (!mActor) mActor.reset(new Actor(mReferenceableId, mData)); + mActor->update(); + mBaseNode->addChild(mActor->getBaseNode()); + } + else if (!model.empty()) { std::string path = "meshes\\" + model; - mResourceSystem->getSceneManager()->getInstance(path, mBaseNode); } - catch (std::exception& e) + else { - // TODO: use error marker mesh - Log(Debug::Error) << e.what(); + throw std::runtime_error(mReferenceableId + " has no model"); } } + catch (std::exception& e) + { + // TODO: use error marker mesh + Log(Debug::Error) << e.what(); + } if (light) { 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&); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 7410dbf037..484f90ab5c 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; @@ -1365,7 +1339,7 @@ namespace MWRender { osg::ref_ptr created = sceneMgr->getInstance(model); - CleanObjectRootVisitor removeDrawableVisitor; + SceneUtil::CleanObjectRootVisitor removeDrawableVisitor; created->accept(removeDrawableVisitor); removeDrawableVisitor.remove(); @@ -1434,7 +1408,7 @@ namespace MWRender if (isCreature) { - RemoveTriBipVisitor removeTriBipVisitor; + SceneUtil::RemoveTriBipVisitor removeTriBipVisitor; mObjectRoot->accept(removeTriBipVisitor); removeTriBipVisitor.remove(); } @@ -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/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 2da7c80bfa..5c245afd08 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 @@ -77,7 +78,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 @@ -85,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 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/misc/weakcache.hpp b/components/misc/weakcache.hpp new file mode 100644 index 0000000000..022a722dba --- /dev/null +++ b/components/misc/weakcache.hpp @@ -0,0 +1,138 @@ +#ifndef OPENMW_COMPONENTS_WEAKCACHE_HPP +#define OPENMW_COMPONENTS_WEAKCACHE_HPP + +#include +#include +#include + +namespace Misc +{ + /// \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(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, bool prune=true); + + /// Retrieves the item associated with the key. + /// \return An item or null. + StrongPtr get(Key key); + + iterator begin(); + iterator end(); + + /// Removes known invalid entries + void prune(); + + private: + Map mData; + std::vector mDirty; + }; + + + template + 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 + for ( ; mCurrent != mEnd; ++mCurrent) + { + mPtr = mCurrent->second.lock(); + if (mPtr) break; + else mCache->mDirty.push_back(mCurrent->first); + } + } + + template + typename WeakCache::iterator& WeakCache::iterator::operator++() + { + auto next = mCurrent; + ++next; + return *this = iterator(mCache, 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, bool shouldPrune) + { + mData[key] = WeakPtr(value); + if (shouldPrune) prune(); + } + + 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(this, mData.begin(), mData.end()); + } + + template + typename WeakCache::iterator WeakCache::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(); + } +} + +#endif 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 2f6123e34d..536331132d 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -1,9 +1,12 @@ #include "visitor.hpp" +#include #include #include +#include + #include namespace SceneUtil @@ -23,7 +26,7 @@ namespace SceneUtil { if (Misc::StringUtils::ciEqual(node.className(), mNameToFind)) mFoundNodes.push_back(&node); - + traverse(node); } @@ -54,4 +57,103 @@ 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 RemoveVisitor::remove() + { + for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) + { + if (!it->second->removeChild(it->first)) + Log(Debug::Error) << "error removing " << it->first->getName(); + } + } + + 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 265fd6d02a..3e9a565c03 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,65 @@ 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; + }; + + /// @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: + RemoveVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + 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