From 2288a691d28ee6df2cd2d386a345a65c18002f9b Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Apr 2024 00:10:51 +0100 Subject: [PATCH 01/10] Replace osgAnimation bone underscore naming at load time, map bone instances, reset root bone transform each frame --- CHANGELOG.md | 1 + apps/openmw/mwrender/animation.cpp | 24 ++++++-- apps/openmw/mwrender/animation.hpp | 1 + components/misc/strings/algorithm.hpp | 7 +++ components/resource/keyframemanager.cpp | 27 +++++---- components/resource/scenemanager.cpp | 79 ++++++++++++++++++++++++- components/sceneutil/extradata.cpp | 7 ++- components/sceneutil/osgacontroller.cpp | 53 +++++++++++++++++ components/sceneutil/osgacontroller.hpp | 3 + components/sceneutil/visitor.cpp | 49 ++++----------- components/sceneutil/visitor.hpp | 20 +++++++ 11 files changed, 211 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f20ecf8bb8a..6c223026b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Bug #6657: Distant terrain tiles become black when using FWIW mod Bug #6661: Saved games that have no preview screenshot cause issues or crashes Bug #6716: mwscript comparison operator handling is too restrictive + Bug #6723: "Turn to movement direction" makes the player rotate wildly with COLLADA Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW Bug #6758: Main menu background video can be stopped by opening the options menu Bug #6807: Ultimate Galleon is not working properly diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0baf68ed5d1..1807b0050ff 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -530,6 +530,7 @@ namespace MWRender , mHasMagicEffects(false) , mAlpha(1.f) , mPlayScriptedOnly(false) + , mRequiresBoneMap(false) { for (size_t i = 0; i < sNumBlendMasks; i++) mAnimationTimePtr[i] = std::make_shared(); @@ -964,8 +965,17 @@ namespace MWRender { if (!mNodeMapCreated && mObjectRoot) { - SceneUtil::NodeMapVisitor visitor(mNodeMap); - mObjectRoot->accept(visitor); + // If the base of this animation is an osgAnimation, we should map the bones not matrix transforms + if (mRequiresBoneMap) + { + SceneUtil::NodeMapVisitorBoneOnly visitor(mNodeMap); + mObjectRoot->accept(visitor); + } + else + { + SceneUtil::NodeMapVisitor visitor(mNodeMap); + mObjectRoot->accept(visitor); + } mNodeMapCreated = true; } return mNodeMap; @@ -1447,10 +1457,9 @@ namespace MWRender } } + osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); if (!forceskeleton) { - osg::ref_ptr created - = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); mInsert->addChild(created); mObjectRoot = created->asGroup(); if (!mObjectRoot) @@ -1466,8 +1475,6 @@ namespace MWRender } else { - osg::ref_ptr created - = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { @@ -1479,6 +1486,10 @@ namespace MWRender mInsert->addChild(mObjectRoot); } + // osgAnimation formats with skeletons should have their nodemap be bone instances + // FIXME: better way to detect osgAnimation here instead of relying on extension? + mRequiresBoneMap = mSkeleton != nullptr && !Misc::StringUtils::ciEndsWith(model, "nif"); + if (previousStateset) mObjectRoot->setStateSet(previousStateset); @@ -1791,6 +1802,7 @@ namespace MWRender osg::ref_ptr controller(new RotateController(mObjectRoot.get())); node->addUpdateCallback(controller); mActiveControllers.emplace_back(node, controller); + return controller; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 22b7167a9c7..4cff658011b 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -246,6 +246,7 @@ namespace MWRender osg::ref_ptr mLightListCallback; bool mPlayScriptedOnly; + bool mRequiresBoneMap; const NodeMap& getNodeMap() const; diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 28bc696cd3b..2bf7125c8ba 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -15,6 +15,13 @@ namespace Misc::StringUtils bool operator()(char x, char y) const { return toLower(x) < toLower(y); } }; + inline std::string underscoresToSpaces(const std::string_view& oldName) + { + std::string newName(oldName); + std::replace(newName.begin(), newName.end(), '_', ' '); + return newName; + } + inline bool ciLess(std::string_view x, std::string_view y) { return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), CiCharLess()); diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 0cbbe40d60e..67a434e47b4 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -37,11 +37,11 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToLeftUpperExtremity(const std::string& name) { - static const std::array boneNames = { "bip01_l_clavicle", "left_clavicle", "bip01_l_upperarm", "left_upper_arm", - "bip01_l_forearm", "bip01_l_hand", "left_hand", "left_wrist", "shield_bone", "bip01_l_pinky1", - "bip01_l_pinky2", "bip01_l_pinky3", "bip01_l_ring1", "bip01_l_ring2", "bip01_l_ring3", "bip01_l_middle1", - "bip01_l_middle2", "bip01_l_middle3", "bip01_l_pointer1", "bip01_l_pointer2", "bip01_l_pointer3", - "bip01_l_thumb1", "bip01_l_thumb2", "bip01_l_thumb3", "left_forearm" }; + static const std::array boneNames = { "bip01 l clavicle", "left clavicle", "bip01 l upperarm", "left upper arm", + "bip01 l forearm", "bip01 l hand", "left hand", "left wrist", "shield bone", "bip01 l pinky1", + "bip01 l pinky2", "bip01 l pinky3", "bip01 l ring1", "bip01 l ring2", "bip01 l ring3", "bip01 l middle1", + "bip01 l middle2", "bip01 l middle3", "bip01 l pointer1", "bip01 l pointer2", "bip01 l pointer3", + "bip01 l thumb1", "bip01 l thumb2", "bip01 l thumb3", "left forearm" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -51,11 +51,11 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToRightUpperExtremity(const std::string& name) { - static const std::array boneNames = { "bip01_r_clavicle", "right_clavicle", "bip01_r_upperarm", - "right_upper_arm", "bip01_r_forearm", "bip01_r_hand", "right_hand", "right_wrist", "bip01_r_thumb1", - "bip01_r_thumb2", "bip01_r_thumb3", "weapon_bone", "bip01_r_pinky1", "bip01_r_pinky2", "bip01_r_pinky3", - "bip01_r_ring1", "bip01_r_ring2", "bip01_r_ring3", "bip01_r_middle1", "bip01_r_middle2", "bip01_r_middle3", - "bip01_r_pointer1", "bip01_r_pointer2", "bip01_r_pointer3", "right_forearm" }; + static const std::array boneNames = { "bip01 r clavicle", "right clavicle", "bip01 r upperarm", + "right upper arm", "bip01 r forearm", "bip01 r hand", "right hand", "right wrist", "bip01 r thumb1", + "bip01 r thumb2", "bip01 r thumb3", "weapon bone", "bip01 r pinky1", "bip01 r pinky2", "bip01 r pinky3", + "bip01 r ring1", "bip01 r ring2", "bip01 r ring3", "bip01 r middle1", "bip01 r middle2", "bip01 r middle3", + "bip01 r pointer1", "bip01 r pointer2", "bip01 r pointer3", "right forearm" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -66,7 +66,7 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToTorso(const std::string& name) { static const std::array boneNames - = { "bip01_spine1", "bip01_spine2", "bip01_neck", "bip01_head", "head", "neck", "chest", "groin" }; + = { "bip01 spine1", "bip01 spine2", "bip01 neck", "bip01 head", "head", "neck", "chest", "groin" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -88,9 +88,7 @@ namespace Resource { //"Default" is osg dae plugin's default naming scheme for unnamed animations if (animation->getName() == "Default") - { animation->setName(std::string("idle")); - } osg::ref_ptr mergedAnimationTrack = new Resource::Animation; const std::string animationName = animation->getName(); @@ -99,6 +97,9 @@ namespace Resource const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel : channels) { + // Repalce channel target name to match the renamed bones/transforms + channel->setTargetName(Misc::StringUtils::underscoresToSpaces(channel->getTargetName())); + if (name == "Bip01 R Clavicle") { if (!belongsToRightUpperExtremity(channel->getTargetName())) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e4d0b4363de..b8272c8b26c 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -9,7 +9,10 @@ #include #include +#include #include +#include +#include #include @@ -353,6 +356,66 @@ namespace Resource std::vector> mRigGeometryHolders; }; + void updateVertexInfluenceMap(osgAnimation::RigGeometry& rig) + { + osgAnimation::VertexInfluenceMap* vertexInfluenceMap = rig.getInfluenceMap(); + if (!vertexInfluenceMap) + return; + + std::vector> renameList; + + // Collecting updates + for (const auto& influence : *vertexInfluenceMap) + { + const std::string& oldBoneName = influence.first; + std::string newBoneName = Misc::StringUtils::underscoresToSpaces(oldBoneName); + if (newBoneName != oldBoneName) + renameList.emplace_back(oldBoneName, newBoneName); + } + + // Applying updates (cant update map while iterating it!) + for (const auto& rename : renameList) + { + const std::string& oldName = rename.first; + const std::string& newName = rename.second; + + // Check if new name already exists to avoid overwriting + if (vertexInfluenceMap->find(newName) == vertexInfluenceMap->end()) + (*vertexInfluenceMap)[newName] = std::move((*vertexInfluenceMap)[oldName]); + + vertexInfluenceMap->erase(oldName); + } + } + + class RenameBonesVisitor : public osg::NodeVisitor + { + public: + RenameBonesVisitor() + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + { + } + + void apply(osg::MatrixTransform& node) override + { + node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); + + // osgAnimation update callback name must match bone name/channel targets + osg::Callback* cb = node.getUpdateCallback(); + while (cb) + { + osgAnimation::AnimationUpdateCallback* animCb + = dynamic_cast*>(cb); + + if (animCb) + animCb->setName(Misc::StringUtils::underscoresToSpaces(animCb->getName())); + + cb = cb->getNestedCallback(); + } + + traverse(node); + } + }; + SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager, double expiryDelay) : ResourceManager(vfs, expiryDelay) @@ -556,6 +619,7 @@ namespace Resource VFS::Path::NormalizedView normalizedFilename, std::istream& model, Resource::ImageManager* imageManager) { const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); + const bool isColladaFile = ext == "dae"; osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext)); if (!reader) { @@ -571,7 +635,7 @@ namespace Resource // findFileCallback would be necessary. but findFileCallback does not support virtual files, so we can't // implement it. options->setReadFileCallback(new ImageReadCallback(imageManager)); - if (ext == "dae") + if (isColladaFile) options->setOptionString("daeUseSequencedTextureUnits"); const std::array fileHash = Files::getHash(normalizedFilename.value(), model); @@ -599,9 +663,13 @@ namespace Resource node->accept(rigFinder); for (osg::Node* foundRigNode : rigFinder.mFoundNodes) { - if (foundRigNode->libraryName() == std::string("osgAnimation")) + if (foundRigNode->libraryName() == std::string_view("osgAnimation")) { osgAnimation::RigGeometry* foundRigGeometry = static_cast(foundRigNode); + + if (isColladaFile) + Resource::updateVertexInfluenceMap(*foundRigGeometry); + osg::ref_ptr newRig = new SceneUtil::RigGeometryHolder(*foundRigGeometry, osg::CopyOp::DEEP_COPY_ALL); @@ -616,13 +684,18 @@ namespace Resource } } - if (ext == "dae") + if (isColladaFile) { Resource::ColladaDescriptionVisitor colladaDescriptionVisitor; node->accept(colladaDescriptionVisitor); if (colladaDescriptionVisitor.mSkeleton) { + // Collada bones may have underscores in place of spaces due to a collada limitation + // we should rename the bones and update callbacks here at load time + Resource::RenameBonesVisitor renameBoneVisitor; + node->accept(renameBoneVisitor); + if (osg::Group* group = dynamic_cast(node)) { group->removeChildren(0, group->getNumChildren()); diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index bd82e9abba6..8d024d58242 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -44,11 +45,15 @@ namespace SceneUtil void ProcessExtraDataVisitor::apply(osg::Node& node) { + // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces + // this is for compatibility reasons + if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) + node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); + if (!mSceneMgr->getSoftParticles()) return; std::string source; - constexpr float defaultFalloffDepth = 300.f; // arbitrary value that simply looks good with common cases if (node.getUserValue(Misc::OsgUserValues::sExtraData, source) && !source.empty()) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 06b693a6caf..40b66409734 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -9,6 +10,7 @@ #include #include +#include #include #include #include @@ -24,6 +26,10 @@ namespace SceneUtil void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt) { + // If osgAnimation had underscores, we should update the umt name also + // otherwise the animation channel and updates wont be applied + umt->setName(Misc::StringUtils::underscoresToSpaces(umt->getName())); + const osgAnimation::ChannelList& channels = mAnimation->getChannels(); for (const auto& channel : channels) { @@ -128,6 +134,47 @@ namespace SceneUtil return osg::Vec3f(); } + osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string& name) const + { + std::string animationName; + float newTime = time; + + // Find the correct animation based on time + for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) + { + if (time >= emulatedAnimation.mStartTime && time <= emulatedAnimation.mStopTime) + { + newTime = time - emulatedAnimation.mStartTime; + animationName = emulatedAnimation.mName; + } + } + + // Find the bone's transform track in animation + for (const auto& mergedAnimationTrack : mMergedAnimationTracks) + { + if (mergedAnimationTrack->getName() != animationName) + continue; + + const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); + + for (const auto& channel : channels) + { + if (!Misc::StringUtils::ciEqual(name, channel->getTargetName()) || channel->getName() != "transform") + continue; + + if (osgAnimation::MatrixLinearSampler* templateSampler + = dynamic_cast(channel->getSampler())) + { + osg::Matrixf matrix; + templateSampler->getValueAt(newTime, matrix); + return matrix; + } + } + } + + return osg::Matrixf::identity(); + } + void OsgAnimationController::update(float time, const std::string& animationName) { for (const auto& mergedAnimationTrack : mMergedAnimationTracks) @@ -162,6 +209,12 @@ namespace SceneUtil update(time - emulatedAnimation.mStartTime, emulatedAnimation.mName); } } + + // Reset the transform of this node to whats in the animation + // we force this here because downstream some code relies on the bone having a non-modified transform + // as this is how the NIF controller behaves. RotationController is a good example of this. + // Without this here, it causes osgAnimation skeletons to spin wildly + static_cast(node)->setMatrix(getTransformForNode(time, node->getName())); } traverse(node, nv); diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 8739d68b996..000580631ca 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -59,6 +59,9 @@ namespace SceneUtil /// @brief Handles the location of the instance osg::Vec3f getTranslation(float time) const override; + /// @brief Handles finding bone position in the animation + osg::Matrixf getTransformForNode(float time, const std::string& name) const; + /// @brief Calls animation track update() void update(float time, const std::string& animationName); diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index ffcf12b1673..8d1bf463220 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -5,6 +5,8 @@ #include +#include + #include #include @@ -13,7 +15,6 @@ namespace SceneUtil { - bool FindByNameVisitor::checkGroup(osg::Group& group) { if (Misc::StringUtils::ciEqual(group.getName(), mNameToFind)) @@ -22,35 +23,13 @@ namespace SceneUtil return true; } - // FIXME: can the nodes/bones be renamed at loading stage rather than each time? - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses - // whitespace-separated names) - std::string nodeName = group.getName(); - std::replace(nodeName.begin(), nodeName.end(), '_', ' '); - if (Misc::StringUtils::ciEqual(nodeName, mNameToFind)) - { - mFoundNode = &group; - return true; - } return false; } void FindByClassVisitor::apply(osg::Node& node) { if (Misc::StringUtils::ciEqual(node.className(), mNameToFind)) - { mFoundNodes.push_back(&node); - } - else - { - // FIXME: can the nodes/bones be renamed at loading stage rather than each time? - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses - // whitespace-separated names) - std::string nodeName = node.className(); - std::replace(nodeName.begin(), nodeName.end(), '_', ' '); - if (Misc::StringUtils::ciEqual(nodeName, mNameToFind)) - mFoundNodes.push_back(&node); - } traverse(node); } @@ -69,26 +48,22 @@ namespace SceneUtil void FindByNameVisitor::apply(osg::Geometry&) {} - void NodeMapVisitor::apply(osg::MatrixTransform& trans) + void NodeMapVisitorBoneOnly::apply(osg::MatrixTransform& trans) { - // Choose first found node in file - - if (trans.libraryName() == std::string_view("osgAnimation")) - { - std::string nodeName = trans.getName(); - - // FIXME: can the nodes/bones be renamed at loading stage rather than each time? - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses - // whitespace-separated names) - std::replace(nodeName.begin(), nodeName.end(), '_', ' '); - mMap.emplace(nodeName, &trans); - } - else + // Choose first found bone in file + if (dynamic_cast(&trans) != nullptr) mMap.emplace(trans.getName(), &trans); traverse(trans); } + void NodeMapVisitor::apply(osg::MatrixTransform& trans) + { + // Choose first found node in file + mMap.emplace(trans.getName(), &trans); + traverse(trans); + } + void RemoveVisitor::remove() { for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index 3e3df4d4b37..a5af88d7de1 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -70,6 +70,26 @@ namespace SceneUtil NodeMap& mMap; }; + /// Maps names to bone nodes + class NodeMapVisitorBoneOnly : public osg::NodeVisitor + { + public: + typedef std::unordered_map, Misc::StringUtils::CiHash, + Misc::StringUtils::CiEqual> + NodeMap; + + NodeMapVisitorBoneOnly(NodeMap& map) + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + , mMap(map) + { + } + + void apply(osg::MatrixTransform& trans) override; + + 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(); From ceabeab0fd106cbcaf9b33c9c36712e3a620d7c8 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Apr 2024 00:11:15 +0100 Subject: [PATCH 02/10] Fix RotateController not updating skeleton --- apps/openmw/mwrender/rotatecontroller.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index d7f8bb902ce..73c4cddff59 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -1,6 +1,7 @@ #include "rotatecontroller.hpp" #include +#include namespace MWRender { @@ -40,9 +41,19 @@ namespace MWRender osg::Quat orient = worldOrient * mRotate * worldOrientInverse * matrix.getRotate(); matrix.setRotate(orient); matrix.setTrans(matrix.getTrans() + worldOrientInverse * mOffset); - node->setMatrix(matrix); + // If we are linked to a bone we must call setMatrixInSkeletonSpace + osgAnimation::Bone* b = dynamic_cast(node); + if (b) + { + osgAnimation::Bone* parent = b->getBoneParent(); + if (parent) + b->setMatrixInSkeletonSpace(matrix * parent->getMatrixInSkeletonSpace()); + else + b->setMatrixInSkeletonSpace(matrix); + } + traverse(node, nv); } From a51d560174d90ac55049fd8ab687533dfe24fbfc Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 5 Apr 2024 01:59:40 +0100 Subject: [PATCH 03/10] Move bone rename logic to ColladaDescriptionVisitor, undo formatting/refactoring --- apps/openmw/mwrender/animation.cpp | 5 ++++- apps/openmw/mwrender/rotatecontroller.cpp | 1 + components/misc/strings/algorithm.hpp | 2 +- components/resource/keyframemanager.cpp | 4 +++- components/resource/scenemanager.cpp | 26 ++++++++++------------- components/sceneutil/extradata.cpp | 6 +----- components/sceneutil/osgacontroller.cpp | 2 +- components/sceneutil/osgacontroller.hpp | 2 +- 8 files changed, 23 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1807b0050ff..729c69f58b5 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1457,9 +1457,10 @@ namespace MWRender } } - osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); if (!forceskeleton) { + osg::ref_ptr created + = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); mInsert->addChild(created); mObjectRoot = created->asGroup(); if (!mObjectRoot) @@ -1475,6 +1476,8 @@ namespace MWRender } else { + osg::ref_ptr created + = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 73c4cddff59..47b271c40de 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -41,6 +41,7 @@ namespace MWRender osg::Quat orient = worldOrient * mRotate * worldOrientInverse * matrix.getRotate(); matrix.setRotate(orient); matrix.setTrans(matrix.getTrans() + worldOrientInverse * mOffset); + node->setMatrix(matrix); // If we are linked to a bone we must call setMatrixInSkeletonSpace diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 2bf7125c8ba..18f72104bd5 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -15,7 +15,7 @@ namespace Misc::StringUtils bool operator()(char x, char y) const { return toLower(x) < toLower(y); } }; - inline std::string underscoresToSpaces(const std::string_view& oldName) + inline std::string underscoresToSpaces(const std::string_view oldName) { std::string newName(oldName); std::replace(newName.begin(), newName.end(), '_', ' '); diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 67a434e47b4..84e7a7e311c 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -88,7 +88,9 @@ namespace Resource { //"Default" is osg dae plugin's default naming scheme for unnamed animations if (animation->getName() == "Default") + { animation->setName(std::string("idle")); + } osg::ref_ptr mergedAnimationTrack = new Resource::Animation; const std::string animationName = animation->getName(); @@ -97,7 +99,7 @@ namespace Resource const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel : channels) { - // Repalce channel target name to match the renamed bones/transforms + // Replace channel target name to match the renamed bones/transforms channel->setTargetName(Misc::StringUtils::underscoresToSpaces(channel->getTargetName())); if (name == "Bip01 R Clavicle") diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b8272c8b26c..3b7deeaab32 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -271,6 +271,11 @@ namespace Resource void apply(osg::Node& node) override { + // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces + // this is for compatibility reasons + if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) + node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); + if (osg::StateSet* stateset = node.getStateSet()) { if (stateset->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) @@ -362,27 +367,18 @@ namespace Resource if (!vertexInfluenceMap) return; - std::vector> renameList; - - // Collecting updates - for (const auto& influence : *vertexInfluenceMap) + std::vector renameList; + for (const auto& [boneName, unused] : *vertexInfluenceMap) { - const std::string& oldBoneName = influence.first; - std::string newBoneName = Misc::StringUtils::underscoresToSpaces(oldBoneName); - if (newBoneName != oldBoneName) - renameList.emplace_back(oldBoneName, newBoneName); + if (boneName.find('_') != std::string::npos) + renameList.push_back(boneName); } - // Applying updates (cant update map while iterating it!) - for (const auto& rename : renameList) + for (const std::string& oldName : renameList) { - const std::string& oldName = rename.first; - const std::string& newName = rename.second; - - // Check if new name already exists to avoid overwriting + const std::string newName = Misc::StringUtils::underscoresToSpaces(oldName); if (vertexInfluenceMap->find(newName) == vertexInfluenceMap->end()) (*vertexInfluenceMap)[newName] = std::move((*vertexInfluenceMap)[oldName]); - vertexInfluenceMap->erase(oldName); } } diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index 8d024d58242..5e91830bbab 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -45,15 +45,11 @@ namespace SceneUtil void ProcessExtraDataVisitor::apply(osg::Node& node) { - // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces - // this is for compatibility reasons - if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) - node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); - if (!mSceneMgr->getSoftParticles()) return; std::string source; + constexpr float defaultFalloffDepth = 300.f; // arbitrary value that simply looks good with common cases if (node.getUserValue(Misc::OsgUserValues::sExtraData, source) && !source.empty()) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 40b66409734..5a3ae60293e 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -134,7 +134,7 @@ namespace SceneUtil return osg::Vec3f(); } - osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string& name) const + osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string_view name) const { std::string animationName; float newTime = time; diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 000580631ca..bb9a6217601 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -60,7 +60,7 @@ namespace SceneUtil osg::Vec3f getTranslation(float time) const override; /// @brief Handles finding bone position in the animation - osg::Matrixf getTransformForNode(float time, const std::string& name) const; + osg::Matrixf getTransformForNode(float time, const std::string_view name) const; /// @brief Calls animation track update() void update(float time, const std::string& animationName); From 36cccef6067d674384f5039f95efababf73664ba Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 5 Apr 2024 23:43:59 +0100 Subject: [PATCH 04/10] Fix formatting --- apps/openmw/mwrender/animation.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 729c69f58b5..82081658a6d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1805,7 +1805,6 @@ namespace MWRender osg::ref_ptr controller(new RotateController(mObjectRoot.get())); node->addUpdateCallback(controller); mActiveControllers.emplace_back(node, controller); - return controller; } From d23c10622d143043dbb1415b55b8798dfe05ac48 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 7 Apr 2024 21:02:54 +0100 Subject: [PATCH 05/10] Use dynamic cast to check for bone --- components/resource/scenemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 3b7deeaab32..bf2674a98f7 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -273,7 +273,7 @@ namespace Resource { // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces // this is for compatibility reasons - if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) + if (dynamic_cast(&node)) node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); if (osg::StateSet* stateset = node.getStateSet()) From 8c2c66d59e033c6ae8467ad04f6f791d9721d38d Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 11 Apr 2024 02:16:06 +0100 Subject: [PATCH 06/10] .nif check, matrix mult feedback, auto usage, reuse NodeMap typedef --- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/rotatecontroller.cpp | 6 +++--- components/resource/scenemanager.cpp | 4 +--- components/sceneutil/visitor.hpp | 12 ++++-------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 82081658a6d..31b5d36c794 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1491,7 +1491,7 @@ namespace MWRender // osgAnimation formats with skeletons should have their nodemap be bone instances // FIXME: better way to detect osgAnimation here instead of relying on extension? - mRequiresBoneMap = mSkeleton != nullptr && !Misc::StringUtils::ciEndsWith(model, "nif"); + mRequiresBoneMap = mSkeleton != nullptr && !Misc::StringUtils::ciEndsWith(model, ".nif"); if (previousStateset) mObjectRoot->setStateSet(previousStateset); diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 47b271c40de..61a2a2628a5 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -50,9 +50,9 @@ namespace MWRender { osgAnimation::Bone* parent = b->getBoneParent(); if (parent) - b->setMatrixInSkeletonSpace(matrix * parent->getMatrixInSkeletonSpace()); - else - b->setMatrixInSkeletonSpace(matrix); + matrix *= parent->getMatrixInSkeletonSpace(); + + b->setMatrixInSkeletonSpace(matrix); } traverse(node, nv); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index bf2674a98f7..4bdd6208199 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -399,9 +399,7 @@ namespace Resource osg::Callback* cb = node.getUpdateCallback(); while (cb) { - osgAnimation::AnimationUpdateCallback* animCb - = dynamic_cast*>(cb); - + auto animCb = dynamic_cast*>(cb); if (animCb) animCb->setName(Misc::StringUtils::underscoresToSpaces(animCb->getName())); diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index a5af88d7de1..a9a943423ce 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -50,14 +50,14 @@ namespace SceneUtil std::vector mFoundNodes; }; + typedef std::unordered_map, Misc::StringUtils::CiHash, + Misc::StringUtils::CiEqual> + NodeMap; + /// Maps names to nodes class NodeMapVisitor : public osg::NodeVisitor { public: - typedef std::unordered_map, Misc::StringUtils::CiHash, - Misc::StringUtils::CiEqual> - NodeMap; - NodeMapVisitor(NodeMap& map) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) , mMap(map) @@ -74,10 +74,6 @@ namespace SceneUtil class NodeMapVisitorBoneOnly : public osg::NodeVisitor { public: - typedef std::unordered_map, Misc::StringUtils::CiHash, - Misc::StringUtils::CiEqual> - NodeMap; - NodeMapVisitorBoneOnly(NodeMap& map) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) , mMap(map) From c3420ed306ddc2a9c369dc4e0903e5e566f49e5f Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 11 Apr 2024 03:01:00 +0100 Subject: [PATCH 07/10] Fix build --- apps/opencs/view/render/actor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index a9cc34b00da..09d896e7e79 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -62,7 +62,7 @@ namespace CSVRender osg::ref_ptr mBaseNode; SceneUtil::Skeleton* mSkeleton; - SceneUtil::NodeMapVisitor::NodeMap mNodeMap; + SceneUtil::NodeMap mNodeMap; }; } From 2653b76db9753fc4eabe68422e8a09182cedfc84 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Wed, 17 Apr 2024 08:16:48 +0100 Subject: [PATCH 08/10] getTranslation/getTransformForNode refactor, unit tests --- apps/openmw_test_suite/CMakeLists.txt | 2 + .../sceneutil/osgacontroller.cpp | 129 ++++++++++++++++++ components/sceneutil/osgacontroller.cpp | 49 +------ 3 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 apps/openmw_test_suite/sceneutil/osgacontroller.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index b91940cd772..db6ecb816a5 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -100,6 +100,8 @@ file(GLOB UNITTEST_SRC_FILES resource/testobjectcache.cpp vfs/testpathutil.cpp + + sceneutil/osgacontroller.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/sceneutil/osgacontroller.cpp b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp new file mode 100644 index 00000000000..2ab6aeb37ee --- /dev/null +++ b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp @@ -0,0 +1,129 @@ +#include + +#include +#include + +#include +#include + +namespace +{ + using namespace SceneUtil; + + static const std::string ROOT_BONE_NAME = "bip01"; + + // Creates a merged anim track with a single root channel with two start/end matrix transforms + osg::ref_ptr createMergedAnimationTrack(std::string name, osg::Matrixf startTransform, + osg::Matrixf endTransform, float startTime = 0.0f, float endTime = 1.0f) + { + osg::ref_ptr mergedAnimationTrack = new Resource::Animation; + mergedAnimationTrack->setName(name); + + osgAnimation::MatrixKeyframeContainer* cbCntr = new osgAnimation::MatrixKeyframeContainer; + cbCntr->push_back(osgAnimation::MatrixKeyframe(startTime, startTransform)); + cbCntr->push_back(osgAnimation::MatrixKeyframe(endTime, endTransform)); + + osg::ref_ptr rootChannel = new osgAnimation::MatrixLinearChannel; + rootChannel->setName("transform"); + rootChannel->setTargetName(ROOT_BONE_NAME); + rootChannel->getOrCreateSampler()->setKeyframeContainer(cbCntr); + mergedAnimationTrack->addChannel(rootChannel); + return mergedAnimationTrack; + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnSampledChannelTranslationForBip01) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this + emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform2 = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + endTransform2.setTrans(2.0f, 2.0f, 2.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + + // should be halfway between 0,0,0 and 1,1,1 + osg::Vec3f translation = controller.getTranslation(0.5f); + EXPECT_EQ(translation, osg::Vec3f(0.5f, 0.5f, 0.5f)); + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnZeroVectorIfNotFound) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + + // Has no emulated animation at time so will return 0,0,0 + osg::Vec3f translation = controller.getTranslation(100.0f); + EXPECT_EQ(translation, osg::Vec3f(0.0f, 0.0f, 0.0f)); + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnZeroVectorIfNoMergedTracks) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + // Has no merged tracks so will return 0,0,0 + osg::Vec3f translation = controller.getTranslation(0.5); + EXPECT_EQ(translation, osg::Vec3f(0.0f, 0.0f, 0.0f)); + } + + TEST(OsgAnimationControllerTest, getTransformShouldReturnIdentityIfNotFound) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + + // Has no emulated animation at time so will return identity + EXPECT_EQ(controller.getTransformForNode(100.0f, ROOT_BONE_NAME), osg::Matrixf::identity()); + + // Has no bone animation at time so will return identity + EXPECT_EQ(controller.getTransformForNode(0.5f, "wrongbone"), osg::Matrixf::identity()); + } + + TEST(OsgAnimationControllerTest, getTransformShouldReturnSampledAnimMatrixAtTime) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this + emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + osg::Matrixf endTransform2 = osg::Matrixf::identity(); + endTransform2.setTrans(2.0f, 2.0f, 2.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + + EXPECT_EQ(controller.getTransformForNode(0.0f, ROOT_BONE_NAME), startTransform); // start of test1 + EXPECT_EQ(controller.getTransformForNode(1.0f, ROOT_BONE_NAME), endTransform); // end of test1 + EXPECT_EQ(controller.getTransformForNode(1.1f, ROOT_BONE_NAME), endTransform); // start of test2 + EXPECT_EQ(controller.getTransformForNode(2.0f, ROOT_BONE_NAME), endTransform2); // end of test2 + } +} diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 5a3ae60293e..d3268fef36f 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -91,49 +91,6 @@ namespace SceneUtil } } - osg::Vec3f OsgAnimationController::getTranslation(float time) const - { - osg::Vec3f translationValue; - std::string animationName; - float newTime = time; - - // Find the correct animation based on time - for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) - { - if (time >= emulatedAnimation.mStartTime && time <= emulatedAnimation.mStopTime) - { - newTime = time - emulatedAnimation.mStartTime; - animationName = emulatedAnimation.mName; - } - } - - // Find the root transform track in animation - for (const auto& mergedAnimationTrack : mMergedAnimationTracks) - { - if (mergedAnimationTrack->getName() != animationName) - continue; - - const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); - - for (const auto& channel : channels) - { - if (channel->getTargetName() != "bip01" || channel->getName() != "transform") - continue; - - if (osgAnimation::MatrixLinearSampler* templateSampler - = dynamic_cast(channel->getSampler())) - { - osg::Matrixf matrix; - templateSampler->getValueAt(newTime, matrix); - translationValue = matrix.getTrans(); - return osg::Vec3f(translationValue[0], translationValue[1], translationValue[2]); - } - } - } - - return osg::Vec3f(); - } - osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string_view name) const { std::string animationName; @@ -146,6 +103,7 @@ namespace SceneUtil { newTime = time - emulatedAnimation.mStartTime; animationName = emulatedAnimation.mName; + break; } } @@ -175,6 +133,11 @@ namespace SceneUtil return osg::Matrixf::identity(); } + osg::Vec3f OsgAnimationController::getTranslation(float time) const + { + return getTransformForNode(time, "bip01").getTrans(); + } + void OsgAnimationController::update(float time, const std::string& animationName) { for (const auto& mergedAnimationTrack : mMergedAnimationTracks) From d09f32d9e4362a4d47c2fac4d32e9aef62ac0e15 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Wed, 17 Apr 2024 08:19:49 +0100 Subject: [PATCH 09/10] Yes sir clang --- apps/openmw_test_suite/sceneutil/osgacontroller.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw_test_suite/sceneutil/osgacontroller.cpp b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp index 2ab6aeb37ee..309de4a878c 100644 --- a/apps/openmw_test_suite/sceneutil/osgacontroller.cpp +++ b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp @@ -1,7 +1,7 @@ #include -#include #include +#include #include #include @@ -36,7 +36,7 @@ namespace std::vector emulatedAnimations; emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this - + OsgAnimationController controller; controller.setEmulatedAnimations(emulatedAnimations); @@ -46,7 +46,8 @@ namespace endTransform.setTrans(1.0f, 1.0f, 1.0f); controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); endTransform2.setTrans(2.0f, 2.0f, 2.0f); - controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + controller.addMergedAnimationTrack( + createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); // should be halfway between 0,0,0 and 1,1,1 osg::Vec3f translation = controller.getTranslation(0.5f); @@ -109,7 +110,7 @@ namespace std::vector emulatedAnimations; emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this - + OsgAnimationController controller; controller.setEmulatedAnimations(emulatedAnimations); @@ -119,7 +120,8 @@ namespace controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); osg::Matrixf endTransform2 = osg::Matrixf::identity(); endTransform2.setTrans(2.0f, 2.0f, 2.0f); - controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + controller.addMergedAnimationTrack( + createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); EXPECT_EQ(controller.getTransformForNode(0.0f, ROOT_BONE_NAME), startTransform); // start of test1 EXPECT_EQ(controller.getTransformForNode(1.0f, ROOT_BONE_NAME), endTransform); // end of test1 From b7aa3b9f47a1c28413bf30a67da7428701344d39 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 19 Apr 2024 07:48:26 +0100 Subject: [PATCH 10/10] Remove rename from RenameBonesVisitor, rename to RenameAnimCallbacksVisitor --- components/resource/scenemanager.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 4bdd6208199..ab3f92f10d8 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -383,18 +383,16 @@ namespace Resource } } - class RenameBonesVisitor : public osg::NodeVisitor + class RenameAnimCallbacksVisitor : public osg::NodeVisitor { public: - RenameBonesVisitor() + RenameAnimCallbacksVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) { } void apply(osg::MatrixTransform& node) override { - node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); - // osgAnimation update callback name must match bone name/channel targets osg::Callback* cb = node.getUpdateCallback(); while (cb) @@ -687,7 +685,7 @@ namespace Resource { // Collada bones may have underscores in place of spaces due to a collada limitation // we should rename the bones and update callbacks here at load time - Resource::RenameBonesVisitor renameBoneVisitor; + Resource::RenameAnimCallbacksVisitor renameBoneVisitor; node->accept(renameBoneVisitor); if (osg::Group* group = dynamic_cast(node))