mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-19 12:40:49 +00:00
Merged pull request #1944
This commit is contained in:
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
|
||||
|
686
apps/opencs/model/world/actoradapter.cpp
Normal file
686
apps/opencs/model/world/actoradapter.cpp
Normal file
@ -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();
|
||||
}
|
||||
}
|
174
apps/opencs/model/world/actoradapter.hpp
Normal file
174
apps/opencs/model/world/actoradapter.hpp
Normal file
@ -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.
|
||||
|
||||
|
128
apps/opencs/view/render/actor.cpp
Normal file
128
apps/opencs/view/render/actor.cpp
Normal file
@ -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 "";
|
||||
}
|
||||
}
|
71
apps/opencs/view/render/actor.hpp
Normal file
71
apps/opencs/view/render/actor.hpp
Normal file
@ -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)
|
||||
|
||||
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<const CSMWorld::Record<ESM::Light>& >(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<const CSMWorld::Record<ESM::Light>& >(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)
|
||||
{
|
||||
|
@ -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());
|
||||
@ -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<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();
|
||||
}
|
||||
@ -1528,7 +1502,7 @@ namespace MWRender
|
||||
osg::ref_ptr<GlowUpdater> 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<osg::StateSet> writableStateSet = NULL;
|
||||
if (!node->getStateSet())
|
||||
|
@ -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>
|
||||
@ -304,7 +305,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> 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);
|
||||
|
@ -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
|
||||
|
134
components/esm/mappings.cpp
Normal file
134
components/esm/mappings.cpp
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
16
components/esm/mappings.hpp
Normal file
16
components/esm/mappings.hpp
Normal file
@ -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
|
138
components/misc/weakcache.hpp
Normal file
138
components/misc/weakcache.hpp
Normal file
@ -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
|
30
components/sceneutil/actorutil.cpp
Normal file
30
components/sceneutil/actorutil.cpp
Normal file
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
11
components/sceneutil/actorutil.hpp
Normal file
11
components/sceneutil/actorutil.hpp
Normal file
@ -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
|
||||
@ -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<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…
x
Reference in New Issue
Block a user