Merge branch 'dehardcodebaseanim' into 'master'

Dehardcode Base_animation and improve Collada support

See merge request OpenMW/openmw!510
symlink-ci
psi29a 3 years ago
commit c33b2e0100

@ -418,10 +418,10 @@ namespace MWClass
{
const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
std::string model = "meshes\\base_anim.nif";
std::string model = Settings::Manager::getString("baseanim", "Models");
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
if(race->mData.mFlags & ESM::Race::Beast)
model = "meshes\\base_animkna.nif";
model = Settings::Manager::getString("baseanimkna", "Models");
return model;
}
@ -431,12 +431,12 @@ namespace MWClass
const MWWorld::LiveCellRef<ESM::NPC> *npc = ptr.get<ESM::NPC>();
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().search(npc->mBase->mRace);
if(race && race->mData.mFlags & ESM::Race::Beast)
models.emplace_back("meshes\\base_animkna.nif");
models.emplace_back(Settings::Manager::getString("baseanimkna", "Models"));
// keep these always loaded just in case
models.emplace_back("meshes/xargonian_swimkna.nif");
models.emplace_back("meshes/xbase_anim_female.nif");
models.emplace_back("meshes/xbase_anim.nif");
models.emplace_back(Settings::Manager::getString("xargonianswimkna", "Models"));
models.emplace_back(Settings::Manager::getString("xbaseanimfemale", "Models"));
models.emplace_back(Settings::Manager::getString("xbaseanim", "Models"));
if (!npc->mBase->mModel.empty())
models.push_back("meshes/"+npc->mBase->mModel);

@ -1500,7 +1500,7 @@ namespace MWRender
MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
if(ref->mBase->mFlags & ESM::Creature::Bipedal)
{
defaultSkeleton = "meshes\\xbase_anim.nif";
defaultSkeleton = Settings::Manager::getString("xbaseanim", "Models");
inject = true;
}
}

@ -10,7 +10,7 @@
#include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/skeleton.hpp>
#include <components/settings/settings.hpp>
#include <components/misc/stringops.hpp>
#include "../mwbase/environment.hpp"
@ -35,7 +35,7 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr,
setObjectRoot(model, false, false, true);
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
addAnimSource("meshes\\xbase_anim.nif", model);
addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model);
addAnimSource(model, model);
}
}
@ -54,7 +54,7 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
{
addAnimSource("meshes\\xbase_anim.nif", model);
addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model);
}
addAnimSource(model, model);

@ -524,7 +524,7 @@ void NpcAnimation::updateNpcBase()
if(!is1stPerson)
{
const std::string base = "meshes\\xbase_anim.nif";
const std::string base = Settings::Manager::getString("xbaseanim", "Models");
if (smodel != base && !isWerewolf)
addAnimSource(base, smodel);
@ -538,7 +538,7 @@ void NpcAnimation::updateNpcBase()
}
else
{
const std::string base = "meshes\\xbase_anim.1st.nif";
const std::string base = Settings::Manager::getString("xbaseanim1st", "Models");
if (smodel != base && !isWerewolf)
addAnimSource(base, smodel);

@ -456,12 +456,15 @@ namespace MWRender
mSky->listAssetsToPreload(workItem->mModels, workItem->mTextures);
mWater->listAssetsToPreload(workItem->mTextures);
const char* basemodels[] = {"xbase_anim", "xbase_anim.1st", "xbase_anim_female", "xbase_animkna"};
for (size_t i=0; i<sizeof(basemodels)/sizeof(basemodels[0]); ++i)
{
workItem->mModels.push_back(std::string("meshes/") + basemodels[i] + ".nif");
workItem->mKeyframes.push_back(std::string("meshes/") + basemodels[i] + ".kf");
}
workItem->mModels.push_back(Settings::Manager::getString("xbaseanim", "Models"));
workItem->mModels.push_back(Settings::Manager::getString("xbaseanim1st", "Models"));
workItem->mModels.push_back(Settings::Manager::getString("xbaseanimfemale", "Models"));
workItem->mModels.push_back(Settings::Manager::getString("xargonianswimkna", "Models"));
workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimkf", "Models"));
workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanim1stkf", "Models"));
workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimfemalekf", "Models"));
workItem->mKeyframes.push_back(Settings::Manager::getString("xargonianswimknakf", "Models"));
workItem->mTextures.emplace_back("textures/_land_default.dds");

@ -8,6 +8,7 @@
#include <BulletCollision/CollisionShapes/btTriangleMesh.h>
#include <components/sceneutil/visitor.hpp>
#include <components/vfs/manager.hpp>
#include <components/nifbullet/bulletnifloader.hpp>
@ -145,11 +146,31 @@ osg::ref_ptr<const BulletShape> BulletShapeManager::getShape(const std::string &
osg::ref_ptr<const osg::Node> constNode (mSceneManager->getTemplate(normalized));
osg::ref_ptr<osg::Node> node (const_cast<osg::Node*>(constNode.get())); // const-trickery required because there is no const version of NodeVisitor
NodeToShapeVisitor visitor;
node->accept(visitor);
shape = visitor.getShape();
// Check first if there's a custom collision node
unsigned int visitAllNodesMask = 0xffffffff;
SceneUtil::FindByNameVisitor nameFinder("Collision");
nameFinder.setTraversalMask(visitAllNodesMask);
nameFinder.setNodeMaskOverride(visitAllNodesMask);
node->accept(nameFinder);
if (nameFinder.mFoundNode)
{
NodeToShapeVisitor visitor;
visitor.setTraversalMask(visitAllNodesMask);
visitor.setNodeMaskOverride(visitAllNodesMask);
nameFinder.mFoundNode->accept(visitor);
shape = visitor.getShape();
}
// Generate a collision shape from the mesh
if (!shape)
return osg::ref_ptr<BulletShape>();
{
NodeToShapeVisitor visitor;
node->accept(visitor);
shape = visitor.getShape();
if (!shape)
return osg::ref_ptr<BulletShape>();
}
}
mCache->addEntryToObjectCache(normalized, shape);

@ -9,6 +9,7 @@
#include <components/nifosg/nifloader.hpp>
#include <components/sceneutil/keyframe.hpp>
#include <components/sceneutil/osgacontroller.hpp>
#include <components/misc/stringops.hpp>
#include "animation.hpp"
#include "objectcache.hpp"
@ -17,11 +18,13 @@
namespace Resource
{
RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr<osgAnimation::BasicAnimationManager> animationManager) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager) {}
RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr<osgAnimation::BasicAnimationManager> animationManager,
const std::string& normalized, const VFS::Manager* vfs) :
osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager), mNormalized(normalized), mVFS(vfs) {}
void RetrieveAnimationsVisitor::apply(osg::Node& node)
{
if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("bip01"))
if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && Misc::StringUtils::lowerCase(node.getName()) == std::string("bip01"))
{
osg::ref_ptr<SceneUtil::OsgAnimationController> callback = new SceneUtil::OsgAnimationController();
@ -38,27 +41,19 @@ namespace Resource
osg::ref_ptr<Resource::Animation> mergedAnimationTrack = new Resource::Animation;
std::string animationName = animation->getName();
std::string start = animationName + std::string(": start");
std::string stop = animationName + std::string(": stop");
mergedAnimationTrack->setName(animationName);
const osgAnimation::ChannelList& channels = animation->getChannels();
for (const auto& channel: channels)
{
mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed?
}
mergedAnimationTrack->setName(animation->getName());
callback->addMergedAnimationTrack(mergedAnimationTrack);
float startTime = animation->getStartTime();
float stopTime = startTime + animation->getDuration();
// mTextKeys is a nif-thing, used by OpenMW's animation system
// Format is likely "AnimationName: [Keyword_optional] [Start OR Stop]"
// AnimationNames are keywords like idle2, idle3... AiPackages and various mechanics control which animations are played
// Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow"
mTarget.mTextKeys.emplace(startTime, std::move(start));
mTarget.mTextKeys.emplace(stopTime, std::move(stop));
SceneUtil::EmulatedAnimation emulatedAnimation;
emulatedAnimation.mStartTime = startTime;
emulatedAnimation.mStopTime = stopTime;
@ -66,12 +61,61 @@ namespace Resource
emulatedAnimations.emplace_back(emulatedAnimation);
}
}
// mTextKeys is a nif-thing, used by OpenMW's animation system
// Format is likely "AnimationName: [Keyword_optional] [Start OR Stop]"
// AnimationNames are keywords like idle2, idle3... AiPackages and various mechanics control which animations are played
// Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow"
// osgAnimation formats should have a .txt file with the same name, each line holding a textkey and whitespace separated time value
// e.g. idle: start 0.0333
try
{
Files::IStreamPtr textKeysFile = mVFS->get(changeFileExtension(mNormalized, "txt"));
std::string line;
while ( getline (*textKeysFile, line) )
{
mTarget.mTextKeys.emplace(parseTimeSignature(line), std::move(parseTextKey(line)));
}
}
catch (std::exception& e)
{
Log(Debug::Warning) << "No textkey file found for " << mNormalized;
}
callback->setEmulatedAnimations(emulatedAnimations);
mTarget.mKeyframeControllers.emplace(node.getName(), callback);
}
traverse(node);
}
std::string RetrieveAnimationsVisitor::parseTextKey(const std::string& line)
{
size_t spacePos = line.find_last_of(' ');
if (spacePos != std::string::npos)
return line.substr(0, spacePos);
return "";
}
double RetrieveAnimationsVisitor::parseTimeSignature(const std::string& line)
{
size_t spacePos = line.find_last_of(' ');
double time = 0.0;
if (spacePos != std::string::npos && spacePos + 1 < line.size())
time = std::stod(line.substr(spacePos + 1));
return time;
}
std::string RetrieveAnimationsVisitor::changeFileExtension(const std::string file, const std::string ext)
{
size_t extPos = file.find_last_of('.');
if (extPos != std::string::npos && extPos+1 < file.size())
{
return file.substr(0, extPos + 1) + ext;
}
return file;
}
}
namespace Resource
@ -109,7 +153,7 @@ namespace Resource
osg::ref_ptr<osgAnimation::BasicAnimationManager> bam = dynamic_cast<osgAnimation::BasicAnimationManager*> (scene->getUpdateCallback());
if (bam)
{
Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam);
Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam, normalized, mVFS);
scene->accept(rav);
}
}

@ -15,13 +15,21 @@ namespace Resource
class RetrieveAnimationsVisitor : public osg::NodeVisitor
{
public:
RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr<osgAnimation::BasicAnimationManager> animationManager);
RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr<osgAnimation::BasicAnimationManager> animationManager,
const std::string& normalized, const VFS::Manager* vfs);
virtual void apply(osg::Node& node) override;
private:
std::string changeFileExtension(const std::string file, const std::string ext);
std::string parseTextKey(const std::string& line);
double parseTimeSignature(const std::string& line);
SceneUtil::KeyframeHolder& mTarget;
osg::ref_ptr<osgAnimation::BasicAnimationManager> mAnimationManager;
std::string mNormalized;
const VFS::Manager* mVFS;
};
}

@ -25,6 +25,7 @@
#include <components/sceneutil/util.hpp>
#include <components/sceneutil/controller.hpp>
#include <components/sceneutil/optimizer.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/shader/shadervisitor.hpp>
#include <components/shader/shadermanager.hpp>
@ -373,6 +374,14 @@ namespace Resource
errormsg << "Error loading " << normalizedFilename << ": " << result.message() << " code " << result.status() << std::endl;
throw std::runtime_error(errormsg.str());
}
// Recognize and hide collision node
unsigned int hiddenNodeMask = 0;
SceneUtil::FindByNameVisitor nameFinder("Collision");
result.getNode()->accept(nameFinder);
if (nameFinder.mFoundNode)
nameFinder.mFoundNode->setNodeMask(hiddenNodeMask);
return result.getNode();
}
}
@ -390,7 +399,8 @@ namespace Resource
{
const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm",
"Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle",
"Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera"};
"Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera", "Collision", "Right_Wrist", "Left_Wrist",
"Shield_Bone", "Right_Forearm", "Left_Forearm", "Right_Upper_Arm", "Left_Clavicle", "Weapon_Bone", "Root_Bone"};
reservedNames = std::vector<std::string>(reserved, reserved + sizeof(reserved)/sizeof(reserved[0]));

@ -1,5 +1,7 @@
#include "actorutil.hpp"
#include <components/settings/settings.hpp>
namespace SceneUtil
{
std::string getActorSkeleton(bool firstPerson, bool isFemale, bool isBeast, bool isWerewolf)
@ -7,24 +9,24 @@ namespace SceneUtil
if (!firstPerson)
{
if (isWerewolf)
return "meshes\\wolf\\skin.nif";
return Settings::Manager::getString("wolfskin", "Models");
else if (isBeast)
return "meshes\\base_animkna.nif";
return Settings::Manager::getString("baseanimkna", "Models");
else if (isFemale)
return "meshes\\base_anim_female.nif";
return Settings::Manager::getString("baseanimfemale", "Models");
else
return "meshes\\base_anim.nif";
return Settings::Manager::getString("baseanim", "Models");
}
else
{
if (isWerewolf)
return "meshes\\wolf\\skin.1st.nif";
return Settings::Manager::getString("wolfskin1st", "Models");
else if (isBeast)
return "meshes\\base_animkna.1st.nif";
return Settings::Manager::getString("baseanimkna1st", "Models");
else if (isFemale)
return "meshes\\base_anim_female.1st.nif";
return Settings::Manager::getString("baseanimfemale1st", "Models");
else
return "meshes\\base_anim.1st.nif";
return Settings::Manager::getString("xbaseanim1st", "Models");
}
}
}

@ -17,6 +17,7 @@
#include <osgAnimation/UpdateMatrixTransform>
#include <components/debug/debuglog.hpp>
#include <components/misc/stringops.hpp>
#include <components/resource/animation.hpp>
#include <components/sceneutil/controller.hpp>
#include <components/sceneutil/keyframe.hpp>
@ -83,7 +84,7 @@ namespace SceneUtil
{
osgAnimation::UpdateMatrixTransform* umt = dynamic_cast<osgAnimation::UpdateMatrixTransform*>(cb);
if (umt)
if (node.getName() != "bip01") link(umt);
if (Misc::StringUtils::lowerCase(node.getName()) != "bip01") link(umt);
cb = cb->getNestedCallback();
}
@ -102,10 +103,14 @@ namespace SceneUtil
}
OsgAnimationController::OsgAnimationController(const OsgAnimationController &copy, const osg::CopyOp &copyop) : SceneUtil::KeyframeController(copy, copyop)
, mMergedAnimationTracks(copy.mMergedAnimationTracks)
, mEmulatedAnimations(copy.mEmulatedAnimations)
{
mLinker = nullptr;
for (const auto& mergedAnimationTrack : copy.mMergedAnimationTracks)
{
Resource::Animation* copiedAnimationTrack = static_cast<Resource::Animation*>(mergedAnimationTrack.get()->clone(copyop));
mMergedAnimationTracks.emplace_back(copiedAnimationTrack);
}
}
osg::Vec3f OsgAnimationController::getTranslation(float time) const

@ -60,7 +60,20 @@ namespace SceneUtil
void NodeMapVisitor::apply(osg::MatrixTransform& trans)
{
// Take transformation for first found node in file
const std::string nodeName = Misc::StringUtils::lowerCase(trans.getName());
std::string originalNodeName = Misc::StringUtils::lowerCase(trans.getName());
if (trans.libraryName() == std::string("osgAnimation"))
{
// Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses whitespace-separated names)
std::string underscore = "_";
std::size_t foundUnderscore = originalNodeName.find(underscore);
if (foundUnderscore != std::string::npos)
std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' ');
}
const std::string nodeName = originalNodeName;
mMap.emplace(nodeName, &trans);
traverse(trans);

@ -955,6 +955,51 @@ defer aabb update = true
# Loading arbitrary meshes is not advised and may cause instability.
load unsupported nif files = false
# 3rd person base animation model that looks also for the corresponding kf-file
xbaseanim = meshes/xbase_anim.nif
# 3rd person base model with textkeys-data
baseanim = meshes/base_anim.nif
# 1st person base animation model that looks also for corresponding kf-file
xbaseanim1st = meshes/xbase_anim.1st.nif
# 3rd person beast race base model with textkeys-data
baseanimkna = meshes/base_animkna.nif
# 1st person beast race base animation model
baseanimkna1st = meshes/base_animkna.1st.nif
# 3rd person female base animation model
xbaseanimfemale = meshes/xbase_anim_female.nif
# 3rd person female base model with textkeys-data
baseanimfemale = meshes/base_anim_female.nif
# 1st person female base model with textkeys-data
baseanimfemale1st = meshes/base_anim_female.1st.nif
# 3rd person werewolf skin
wolfskin = meshes/wolf/skin.nif
# 1st person werewolf skin
wolfskin1st = meshes/wolf/skin.1st.nif
# Argonian smimkna
xargonianswimkna = meshes/xargonian_swimkna.nif
# File to load xbaseanim 3rd person animations
xbaseanimkf = meshes/xbase_anim.kf
# File to load xbaseanim 3rd person animations
xbaseanim1stkf = meshes/xbase_anim.1st.kf
# File to load xbaseanim animations from
xbaseanimfemalekf = meshes/xbase_anim_female.kf
# File to load xargonianswimkna animations from
xargonianswimknakf = meshes/xargonian_swimkna.kf
[Groundcover]
# enable separate groundcover handling

Loading…
Cancel
Save