diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 088ecd4600..919f8a9e8e 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -557,7 +557,8 @@ namespace MWRender mResetAccumRootCallback->setAccumulate(mAccumulate); } - size_t Animation::detectBlendMask(const osg::Node* node) const + // controllerName is used for Collada animated deforming models + size_t Animation::detectBlendMask(const osg::Node* node, const std::string& controllerName) const { static const std::string_view sBlendMaskRoots[sNumBlendMasks] = { "", /* Lower body / character root */ @@ -571,7 +572,7 @@ namespace MWRender const std::string& name = node->getName(); for (size_t i = 1; i < sNumBlendMasks; i++) { - if (name == sBlendMaskRoots[i]) + if (name == sBlendMaskRoots[i] || controllerName == sBlendMaskRoots[i]) return i; } @@ -646,7 +647,7 @@ namespace MWRender osg::Node* node = found->second; - size_t blendMask = detectBlendMask(node); + size_t blendMask = detectBlendMask(node, it->second->getName()); // clone the controller, because each Animation needs its own ControllerSource osg::ref_ptr cloned diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index d7d99dc303..b241c3b931 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -292,7 +292,7 @@ namespace MWRender */ void resetActiveGroups(); - size_t detectBlendMask(const osg::Node* node) const; + size_t detectBlendMask(const osg::Node* node, const std::string& controllerName) const; /* Updates the position of the accum root node for the given time, and * returns the wanted movement vector from the previous time. */ diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index e87520caa9..c547d0f9f0 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -32,73 +32,140 @@ 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" }; + + if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) + return true; + + return false; + } + + 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" }; + + if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) + return true; + + return false; + } + + bool RetrieveAnimationsVisitor::belongsToTorso(const std::string& name) + { + static const std::array boneNames + = { "bip01_spine1", "bip01_spine2", "bip01_neck", "bip01_head", "head", "neck", "chest", "groin" }; + + if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) + return true; + + return false; + } + + void RetrieveAnimationsVisitor::addKeyframeController(const std::string& name, const osg::Node& node) + { + osg::ref_ptr callback = new SceneUtil::OsgAnimationController(); + + callback->setName(name); + + std::vector emulatedAnimations; + + for (const auto& animation : mAnimationManager->getAnimationList()) + { + if (animation) + { + //"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(); + mergedAnimationTrack->setName(animationName); + + const osgAnimation::ChannelList& channels = animation->getChannels(); + for (const auto& channel : channels) + { + if (name == "Bip01 R Clavicle") + { + if (!belongsToRightUpperExtremity(channel->getTargetName())) + continue; + } + else if (name == "Bip01 L Clavicle") + { + if (!belongsToLeftUpperExtremity(channel->getTargetName())) + continue; + } + else if (name == "Bip01 Spine1") + { + if (!belongsToTorso(channel->getTargetName())) + continue; + } + else if (belongsToRightUpperExtremity(channel->getTargetName()) + || belongsToLeftUpperExtremity(channel->getTargetName()) + || belongsToTorso(channel->getTargetName())) + continue; + + mergedAnimationTrack->addChannel(channel.get()->clone()); + } + + callback->addMergedAnimationTrack(mergedAnimationTrack); + + float startTime = animation->getStartTime(); + float stopTime = startTime + animation->getDuration(); + + SceneUtil::EmulatedAnimation emulatedAnimation; + emulatedAnimation.mStartTime = startTime; + emulatedAnimation.mStopTime = stopTime; + emulatedAnimation.mName = animationName; + 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), parseTextKey(line)); + } + } + catch (std::exception&) + { + Log(Debug::Warning) << "No textkey file found for " << mNormalized; + } + + callback->setEmulatedAnimations(emulatedAnimations); + mTarget.mKeyframeControllers.emplace(name, callback); + } + void RetrieveAnimationsVisitor::apply(osg::Node& node) { if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone") && Misc::StringUtils::lowerCase(node.getName()) == std::string_view("bip01")) { - osg::ref_ptr callback = new SceneUtil::OsgAnimationController(); - - std::vector emulatedAnimations; - - for (const auto& animation : mAnimationManager->getAnimationList()) - { - if (animation) - { - if (animation->getName() - == "Default") //"Default" is osg dae plugin's default naming scheme for unnamed animations - { - animation->setName( - std::string("idle")); // animation naming scheme "idle: start" and "idle: stop" is the - // default idle animation that OpenMW seems to want to play - } - - osg::ref_ptr mergedAnimationTrack = new Resource::Animation; - const std::string animationName = animation->getName(); - mergedAnimationTrack->setName(animationName); - - const osgAnimation::ChannelList& channels = animation->getChannels(); - for (const auto& channel : channels) - { - mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed? - } - - callback->addMergedAnimationTrack(mergedAnimationTrack); - - float startTime = animation->getStartTime(); - float stopTime = startTime + animation->getDuration(); - - SceneUtil::EmulatedAnimation emulatedAnimation; - emulatedAnimation.mStartTime = startTime; - emulatedAnimation.mStopTime = stopTime; - emulatedAnimation.mName = animationName; - 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), parseTextKey(line)); - } - } - catch (std::exception&) - { - Log(Debug::Warning) << "No textkey file found for " << mNormalized; - } - - callback->setEmulatedAnimations(emulatedAnimations); - mTarget.mKeyframeControllers.emplace(node.getName(), callback); + addKeyframeController("bip01", node); /* Character root */ + addKeyframeController("Bip01 Spine1", node); /* Torso */ + addKeyframeController("Bip01 L Clavicle", node); /* Left arm */ + addKeyframeController("Bip01 R Clavicle", node); /* Right arm */ } traverse(node); diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index 2ed1696def..138838c0f7 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -11,7 +11,7 @@ namespace Resource { - /// @brief extract animations to OpenMW's animation system + /// @brief extract animations from OSG formats to OpenMW's animation system class RetrieveAnimationsVisitor : public osg::NodeVisitor { public: @@ -19,6 +19,11 @@ namespace Resource osg::ref_ptr animationManager, const std::string& normalized, const VFS::Manager* vfs); + bool belongsToLeftUpperExtremity(const std::string& name); + bool belongsToRightUpperExtremity(const std::string& name); + bool belongsToTorso(const std::string& name); + + void addKeyframeController(const std::string& name, const osg::Node& node); virtual void apply(osg::Node& node) override; private: