diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 088ecd4600..d8926adb38 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -571,6 +571,7 @@ namespace MWRender const std::string& name = node->getName(); for (size_t i = 1; i < sNumBlendMasks; i++) { + Log(Debug::Warning) << "blendmaskname:" << name; if (name == sBlendMaskRoots[i]) return i; } @@ -583,6 +584,27 @@ namespace MWRender return 0; } + size_t Animation::detectColladaBlendMask(const osg::Node* node, const std::string& blendmaskName) const + { + static const std::string_view sBlendMaskRoots[sNumBlendMasks] = { + "", /* Lower body / character root */ + "Bip01 Spine1", /* Torso */ + "Bip01 L Clavicle", /* Left arm */ + "Bip01 R Clavicle", /* Right arm */ + }; + + for (size_t i = 1; i < sNumBlendMasks; i++) + { + Log(Debug::Warning) << "blendmaskname:" << blendmaskName; + if (blendmaskName == sBlendMaskRoots[i]) + return i; + } + assert(node->getNumParents() > 0); + node = node->getParent(0); + + return 0; + } + const SceneUtil::TextKeyMap& Animation::AnimSource::getTextKeys() const { return mKeyframes->mTextKeys; @@ -646,7 +668,8 @@ namespace MWRender osg::Node* node = found->second; - size_t blendMask = detectBlendMask(node); + size_t blendMask = detectColladaBlendMask(node, it->second->getName()); + Log(Debug::Warning) << "blendmask " << blendMask << " ctrl name: " << 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..5a3658b67b 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -293,6 +293,7 @@ namespace MWRender void resetActiveGroups(); size_t detectBlendMask(const osg::Node* node) const; + size_t detectColladaBlendMask(const osg::Node* node, const std::string& blendmaskName) 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..6881078a17 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -31,74 +31,172 @@ namespace Resource , mVFS(vfs) { } + + bool RetrieveAnimationsVisitor::belongsToLeftUpperExtremity(const std::string& name) + { + static const std::string_view boneNames[25] = { + "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" + }; + + for (size_t i = 0; i < 25; i++) + { + if (name == boneNames[i]) + return true; + } + + return false; + } + + bool RetrieveAnimationsVisitor::belongsToRightUpperExtremity(const std::string& name) + { + static const std::string_view boneNames[25] = { + "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" + }; + + for (size_t i = 0; i < 25; i++) + { + if (name == boneNames[i]) + 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) + { + 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) + { + if (name == "Bip01 R Clavicle") + { + if (!belongsToRightUpperExtremity(channel->getTargetName())) continue; + } + else if (name == "Bip01 L Clavicle") + { + if (!belongsToLeftUpperExtremity(channel->getTargetName())) continue; + } + else if (belongsToRightUpperExtremity(channel->getTargetName()) || belongsToLeftUpperExtremity(channel->getTargetName())) continue; + + 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(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..22cb011c75 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -18,7 +18,11 @@ namespace Resource RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager, const std::string& normalized, const VFS::Manager* vfs); + + bool belongsToLeftUpperExtremity(const std::string& name); + bool belongsToRightUpperExtremity(const std::string& name); + void addKeyframeController(const std::string& name, const osg::Node& node); virtual void apply(osg::Node& node) override; private: