Merged pull request #1944

pull/541/head
Marc Zinnschlag 6 years ago
commit 38758a9555

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

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

@ -0,0 +1,686 @@
#include "actoradapter.hpp"
#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 <components/sceneutil/actorutil.hpp>
#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<const Record<ESM::NPC>&>(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<ESM::PartReference>& 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<const Record<ESM::Armor>&>(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<const Record<ESM::Clothing>&>(record).get();
if (clothing.mData.mType == ESM::Clothing::Robe)
{
auto reservedList = std::vector<ESM::PartReference>();
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>();
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<const Record<ESM::Creature>&>(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();
}
}

@ -0,0 +1,174 @@
#ifndef CSM_WOLRD_ACTORADAPTER_H
#define CSM_WOLRD_ACTORADAPTER_H
#include <array>
#include <map>
#include <unordered_set>
#include <QObject>
#include <QModelIndex>
#include <components/esm/loadarmo.hpp>
#include <components/esm/loadbody.hpp>
#include <components/misc/weakcache.hpp>
#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<ESM::PartReferenceType, std::pair<std::string, int>>;
/// A list indexed by ESM::BodyPart::MeshPart
using RacePartList = std::array<std::string, ESM::BodyPart::MP_Count>;
/// Tracks unique strings
using StringSet = std::unordered_set<std::string>;
/// 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<RaceData>;
/// 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<ActorData>;
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<ESM::Race>& mRaces;
IdCollection<ESM::BodyPart>& mBodyParts;
Misc::WeakCache<std::string, ActorData> mCachedActors; // Key: referenceable id
Misc::WeakCache<std::string, RaceData> mCachedRaces; // Key: race id
StringSet mDirtyActors; // Actors that need updating
StringSet mDirtyRaces; // Races that need updating
};
}
#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"
@ -110,6 +111,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 +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.

@ -0,0 +1,128 @@
#include "actor.hpp"
#include <osg/Group>
#include <osg/Node>
#include <components/esm/mappings.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcemanager.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/attach.hpp>
#include <components/sceneutil/skeleton.hpp>
#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<osg::Node> temp = sceneMgr->getInstance(model);
mSkeleton = dynamic_cast<SceneUtil::Skeleton*>(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 "";
}
}

@ -0,0 +1,71 @@
#ifndef OPENCS_VIEW_RENDER_ACTOR_H
#define OPENCS_VIEW_RENDER_ACTOR_H
#include <string>
#include <osg/ref_ptr>
#include <QObject>
#include <components/esm/loadarmo.hpp>
#include <components/sceneutil/visitor.hpp>
#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<osg::Group> mBaseNode;
SceneUtil::Skeleton* mSkeleton;
SceneUtil::NodeMapVisitor::NodeMap mNodeMap;
};
}
#endif

@ -1,6 +1,8 @@
#include "object.hpp"
#include <stdexcept>
#include <string>
#include <iostream>
#include <osg/Depth>
#include <osg/Group>
@ -29,6 +31,7 @@
#include <components/sceneutil/lightmanager.hpp>
#include <components/fallback/fallback.hpp>
#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)
mBaseNode->removeChildren(0, mBaseNode->getNumChildren());
model = referenceables.getData (index,
referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model)).
toString().toUtf8().constData();
if (index == -1)
{
mBaseNode->addChild(createErrorCube());
return;
}
int recordType =
referenceables.getData (index,
referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType)).toInt();
if (recordType == CSMWorld::UniversalId::Type_Light)
{
light = &dynamic_cast<const CSMWorld::Record<ESM::Light>& >(referenceables.getRecord(index)).get();
if (model.empty())
model = "marker_light.nif";
}
/// \todo check for Deleted state (error 1)
if (recordType == CSMWorld::UniversalId::Type_CreatureLevelledList)
{
if (model.empty())
model = "marker_creature.nif";
}
int recordType = referenceables.getData(index, TypeIndex).toInt();
std::string model = referenceables.getData(index, ModelIndex).toString().toUtf8().constData();
if (recordType == CSMWorld::UniversalId::Type_Light)
{
light = &dynamic_cast<const CSMWorld::Record<ESM::Light>& >(referenceables.getRecord(index)).get();
if (model.empty())
error = 2;
model = "marker_light.nif";
}
mBaseNode->removeChildren(0, mBaseNode->getNumChildren());
if (error)
if (recordType == CSMWorld::UniversalId::Type_CreatureLevelledList)
{
mBaseNode->addChild(createErrorCube());
if (model.empty())
model = "marker_creature.nif";
}
else
try
{
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)
{

@ -1,6 +1,7 @@
#ifndef OPENCS_VIEW_OBJECT_H
#define OPENCS_VIEW_OBJECT_H
#include <memory>
#include <string>
#include <osg/ref_ptr>
@ -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<osg::Node> mMarker[3];
int mSubMode;
float mMarkerTransparency;
std::unique_ptr<Actor> mActor;
/// Not implemented
Object (const Object&);

@ -91,32 +91,6 @@ namespace
std::vector<osg::ref_ptr<osg::Node> > mToRemove;
};
class NodeMapVisitor : public osg::NodeVisitor
{
public:
typedef std::map<std::string, osg::ref_ptr<osg::MatrixTransform> > 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());
@ -1042,7 +1016,7 @@ namespace MWRender
{
if (!mNodeMapCreated && mObjectRoot)
{
NodeMapVisitor visitor(mNodeMap);
SceneUtil::NodeMapVisitor visitor(mNodeMap);
mObjectRoot->accept(visitor);
mNodeMapCreated = true;
}
@ -1365,7 +1339,7 @@ namespace MWRender
{
osg::ref_ptr<osg::Node> 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();
}

@ -15,6 +15,7 @@
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/actorutil.hpp>
#include <components/sceneutil/attach.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/skeleton.hpp>
@ -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)

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

@ -0,0 +1,134 @@
#include "mappings.hpp"
#include <stdexcept>
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);
}
}
}

@ -0,0 +1,16 @@
#ifndef OPENMW_ESM_MAPPINGS_H
#define OPENMW_ESM_MAPPINGS_H
#include <string>
#include <components/esm/loadarmo.hpp>
#include <components/esm/loadbody.hpp>
namespace ESM
{
ESM::BodyPart::MeshPart getMeshPart(ESM::PartReferenceType type);
std::string getBoneName(ESM::PartReferenceType type);
std::string getMeshFilter(ESM::PartReferenceType type);
}
#endif

@ -0,0 +1,138 @@
#ifndef OPENMW_COMPONENTS_WEAKCACHE_HPP
#define OPENMW_COMPONENTS_WEAKCACHE_HPP
#include <memory>
#include <unordered_map>
#include <vector>
namespace Misc
{
/// \class WeakCache
/// Provides a container to weakly store pointers to shared data.
template <typename Key, typename T>
class WeakCache
{
public:
using WeakPtr = std::weak_ptr<T>;
using StrongPtr = std::shared_ptr<T>;
using Map = std::unordered_map<Key, WeakPtr>;
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<Key> mDirty;
};
template <typename Key, typename T>
WeakCache<Key, T>::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 Key, typename T>
typename WeakCache<Key, T>::iterator& WeakCache<Key, T>::iterator::operator++()
{
auto next = mCurrent;
++next;
return *this = iterator(mCache, next, mEnd);
}
template <typename Key, typename T>
bool WeakCache<Key, T>::iterator::operator==(const iterator& other)
{
return mCurrent == other.mCurrent;
}
template <typename Key, typename T>
bool WeakCache<Key, T>::iterator::operator!=(const iterator& other)
{
return !(*this == other);
}
template <typename Key, typename T>
typename WeakCache<Key, T>::StrongPtr WeakCache<Key, T>::iterator::operator*()
{
return mPtr;
}
template <typename Key, typename T>
void WeakCache<Key, T>::insert(Key key, StrongPtr value, bool shouldPrune)
{
mData[key] = WeakPtr(value);
if (shouldPrune) prune();
}
template <typename Key, typename T>
typename WeakCache<Key, T>::StrongPtr WeakCache<Key, T>::get(Key key)
{
auto searchIt = mData.find(key);
if (searchIt != mData.end())
return searchIt->second.lock();
else
return StrongPtr();
}
template <typename Key, typename T>
typename WeakCache<Key, T>::iterator WeakCache<Key, T>::begin()
{
return iterator(this, mData.begin(), mData.end());
}
template <typename Key, typename T>
typename WeakCache<Key, T>::iterator WeakCache<Key, T>::end()
{
return iterator(this, mData.end(), mData.end());
}
template <typename Key, typename T>
void WeakCache<Key, T>::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

@ -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";
}
}
}

@ -0,0 +1,11 @@
#ifndef OPENMW_COMPONENTS_SCENEUTIL_ACTORUTIL_HPP
#define OPENMW_COMPONENTS_SCENEUTIL_ACTORUTIL_HPP
#include <string>
namespace SceneUtil
{
std::string getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf);
}
#endif

@ -1,9 +1,12 @@
#include "visitor.hpp"
#include <osg/Drawable>
#include <osg/MatrixTransform>
#include <osgParticle/ParticleSystem>
#include <components/debug/debuglog.hpp>
#include <components/misc/stringops.hpp>
namespace SceneUtil
@ -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<osg::Group*>(*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<osg::Group*>(*(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<osg::Group*>(*(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));
}
}
}

@ -1,6 +1,7 @@
#ifndef OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H
#define OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H
#include <osg/MatrixTransform>
#include <osg/NodeVisitor>
// 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<std::string, osg::ref_ptr<osg::MatrixTransform> > 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:
// <node to remove, parent node to remove it from>
typedef std::vector<std::pair<osg::Node*, osg::Group*> > RemoveVec;
std::vector<std::pair<osg::Node*, osg::Group*> > 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

Loading…
Cancel
Save