mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-06 00:55:50 +00:00
190 lines
7.2 KiB
C++
190 lines
7.2 KiB
C++
#include "esm4npcanimation.hpp"
|
|
|
|
#include <components/esm4/loadarma.hpp>
|
|
#include <components/esm4/loadarmo.hpp>
|
|
#include <components/esm4/loadclot.hpp>
|
|
#include <components/esm4/loadhair.hpp>
|
|
#include <components/esm4/loadhdpt.hpp>
|
|
#include <components/esm4/loadnpc.hpp>
|
|
#include <components/esm4/loadrace.hpp>
|
|
|
|
#include <components/misc/resourcehelpers.hpp>
|
|
#include <components/resource/resourcesystem.hpp>
|
|
#include <components/resource/scenemanager.hpp>
|
|
|
|
#include "../mwclass/esm4npc.hpp"
|
|
#include "../mwworld/customdata.hpp"
|
|
#include "../mwworld/esmstore.hpp"
|
|
|
|
namespace MWRender
|
|
{
|
|
ESM4NpcAnimation::ESM4NpcAnimation(
|
|
const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem)
|
|
: Animation(ptr, std::move(parentNode), resourceSystem)
|
|
{
|
|
setObjectRoot(mPtr.getClass().getModel(mPtr), true, true, false);
|
|
updateParts();
|
|
}
|
|
|
|
void ESM4NpcAnimation::updateParts()
|
|
{
|
|
if (!mObjectRoot.get())
|
|
return;
|
|
const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr);
|
|
if (traits->mIsTES4)
|
|
updatePartsTES4();
|
|
else if (traits->mIsFONV)
|
|
{
|
|
// Not implemented yet
|
|
}
|
|
else
|
|
{
|
|
// There is no easy way to distinguish TES5 and FO3.
|
|
// In case of FO3 the function shouldn't crash the game and will
|
|
// only lead to the NPC not being rendered.
|
|
updatePartsTES5();
|
|
}
|
|
}
|
|
|
|
void ESM4NpcAnimation::insertPart(std::string_view model)
|
|
{
|
|
if (model.empty())
|
|
return;
|
|
mResourceSystem->getSceneManager()->getInstance(
|
|
Misc::ResourceHelpers::correctMeshPath(model, mResourceSystem->getVFS()), mObjectRoot.get());
|
|
}
|
|
|
|
template <class Record>
|
|
static std::string_view chooseTes4EquipmentModel(const Record* rec, bool isFemale)
|
|
{
|
|
if (isFemale && !rec->mModelFemale.empty())
|
|
return rec->mModelFemale;
|
|
else if (!isFemale && !rec->mModelMale.empty())
|
|
return rec->mModelMale;
|
|
else
|
|
return rec->mModel;
|
|
}
|
|
|
|
void ESM4NpcAnimation::updatePartsTES4()
|
|
{
|
|
const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr);
|
|
const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr);
|
|
bool isFemale = MWClass::ESM4Npc::isFemale(mPtr);
|
|
|
|
// TODO: Body and head parts are placed incorrectly, need to attach to bones
|
|
|
|
for (const ESM4::Race::BodyPart& bodyPart : (isFemale ? race->mBodyPartsFemale : race->mBodyPartsMale))
|
|
insertPart(bodyPart.mesh);
|
|
for (const ESM4::Race::BodyPart& bodyPart : race->mHeadParts)
|
|
insertPart(bodyPart.mesh);
|
|
if (!traits->mHair.isZeroOrUnset())
|
|
{
|
|
const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore();
|
|
if (const ESM4::Hair* hair = store->get<ESM4::Hair>().search(traits->mHair))
|
|
insertPart(hair->mModel);
|
|
else
|
|
Log(Debug::Error) << "Hair not found: " << ESM::RefId(traits->mHair);
|
|
}
|
|
|
|
for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr))
|
|
insertPart(chooseTes4EquipmentModel(armor, isFemale));
|
|
for (const ESM4::Clothing* clothing : MWClass::ESM4Npc::getEquippedClothing(mPtr))
|
|
insertPart(chooseTes4EquipmentModel(clothing, isFemale));
|
|
}
|
|
|
|
void ESM4NpcAnimation::insertHeadParts(
|
|
const std::vector<ESM::FormId>& partIds, std::set<uint32_t>& usedHeadPartTypes)
|
|
{
|
|
const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore();
|
|
for (ESM::FormId partId : partIds)
|
|
{
|
|
if (partId.isZeroOrUnset())
|
|
continue;
|
|
const ESM4::HeadPart* part = store->get<ESM4::HeadPart>().search(partId);
|
|
if (!part)
|
|
{
|
|
Log(Debug::Error) << "Head part not found: " << ESM::RefId(partId);
|
|
continue;
|
|
}
|
|
if (usedHeadPartTypes.emplace(part->mType).second)
|
|
insertPart(part->mModel);
|
|
}
|
|
}
|
|
|
|
void ESM4NpcAnimation::updatePartsTES5()
|
|
{
|
|
const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore();
|
|
|
|
const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr);
|
|
const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr);
|
|
bool isFemale = MWClass::ESM4Npc::isFemale(mPtr);
|
|
|
|
std::vector<const ESM4::ArmorAddon*> armorAddons;
|
|
|
|
auto findArmorAddons = [&](const ESM4::Armor* armor) {
|
|
for (ESM::FormId armaId : armor->mAddOns)
|
|
{
|
|
if (armaId.isZeroOrUnset())
|
|
continue;
|
|
const ESM4::ArmorAddon* arma = store->get<ESM4::ArmorAddon>().search(armaId);
|
|
if (!arma)
|
|
{
|
|
Log(Debug::Error) << "ArmorAddon not found: " << ESM::RefId(armaId);
|
|
continue;
|
|
}
|
|
bool compatibleRace = arma->mRacePrimary == traits->mRace;
|
|
for (auto r : arma->mRaces)
|
|
if (r == traits->mRace)
|
|
compatibleRace = true;
|
|
if (compatibleRace)
|
|
armorAddons.push_back(arma);
|
|
}
|
|
};
|
|
|
|
for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr))
|
|
findArmorAddons(armor);
|
|
if (!traits->mWornArmor.isZeroOrUnset())
|
|
{
|
|
if (const ESM4::Armor* armor = store->get<ESM4::Armor>().search(traits->mWornArmor))
|
|
findArmorAddons(armor);
|
|
else
|
|
Log(Debug::Error) << "Worn armor not found: " << ESM::RefId(traits->mWornArmor);
|
|
}
|
|
if (!race->mSkin.isZeroOrUnset())
|
|
{
|
|
if (const ESM4::Armor* armor = store->get<ESM4::Armor>().search(race->mSkin))
|
|
findArmorAddons(armor);
|
|
else
|
|
Log(Debug::Error) << "Skin not found: " << ESM::RefId(race->mSkin);
|
|
}
|
|
|
|
if (isFemale)
|
|
std::sort(armorAddons.begin(), armorAddons.end(),
|
|
[](auto x, auto y) { return x->mFemalePriority > y->mFemalePriority; });
|
|
else
|
|
std::sort(armorAddons.begin(), armorAddons.end(),
|
|
[](auto x, auto y) { return x->mMalePriority > y->mMalePriority; });
|
|
|
|
uint32_t usedParts = 0;
|
|
for (const ESM4::ArmorAddon* arma : armorAddons)
|
|
{
|
|
const uint32_t covers = arma->mBodyTemplate.bodyPart;
|
|
// if body is already covered, skip to avoid clipping
|
|
if (covers & usedParts & ESM4::Armor::TES5_Body)
|
|
continue;
|
|
// if covers at least something that wasn't covered before - add model
|
|
if (covers & ~usedParts)
|
|
{
|
|
usedParts |= covers;
|
|
insertPart(isFemale ? arma->mModelFemale : arma->mModelMale);
|
|
}
|
|
}
|
|
|
|
std::set<uint32_t> usedHeadPartTypes;
|
|
if (usedParts & ESM4::Armor::TES5_Hair)
|
|
usedHeadPartTypes.insert(ESM4::HeadPart::Type_Hair);
|
|
insertHeadParts(traits->mHeadParts, usedHeadPartTypes);
|
|
insertHeadParts(isFemale ? race->mHeadPartIdsFemale : race->mHeadPartIdsMale, usedHeadPartTypes);
|
|
}
|
|
}
|