From 275b29ea0c1cf5028dc794734db3af1cf9c4e0f9 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 23 Mar 2024 17:04:38 +0000 Subject: [PATCH] Fix OSGAnimation keyframe controller not resetting transform Signed-off-by: Sam Hellawell --- components/sceneutil/osgacontroller.cpp | 70 +++++++++++++++++++++++++ components/sceneutil/osgacontroller.hpp | 3 ++ 2 files changed, 73 insertions(+) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 06b693a6ca..7ea460f915 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -16,6 +17,28 @@ namespace SceneUtil { + inline bool isEqualCharUnderscores(char a, char b) + { + if (a == '_') + a = ' '; // Treat underscore as space + if (b == '_') + b = ' '; // Treat underscore as space + return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); + } + + inline bool isEqualWithUnderscores(const std::string_view& str1, const std::string_view& str2) + { + if (str1.length() != str2.length()) + return false; + + for (size_t i = 0; i < str1.length(); ++i) + { + if (!isEqualCharUnderscores(str1[i], str2[i])) + return false; + } + return true; + } + LinkVisitor::LinkVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { @@ -128,6 +151,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 (!isEqualWithUnderscores(channel->getTargetName(), name) || 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 +226,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 8739d68b99..000580631c 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);