diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 7da29a1cf0..6e18fb51a1 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -35,6 +35,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/ptr.hpp" +#include "actorutil.hpp" #include "vismask.hpp" namespace MWRender @@ -144,8 +145,7 @@ namespace MWRender if (mesh.empty()) return mesh; - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); if (mResourceSystem->getVFS()->exists(holsteredName)) { osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); @@ -222,8 +222,7 @@ namespace MWRender std::string_view boneName = "Bip01 AttachShield"; osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield); - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); // If we have no dedicated sheath model, use basic shield model as fallback. @@ -340,14 +339,12 @@ namespace MWRender showHolsteredWeapons = false; std::string mesh = weapon->getClass().getCorrectedModel(*weapon); - std::string scabbardName = mesh; - std::string_view boneName = getHolsteredWeaponBoneName(*weapon); if (mesh.empty() || boneName.empty()) return; // If the scabbard is not found, use the weapon mesh as fallback. - scabbardName = scabbardName.replace(scabbardName.size() - 4, 4, "_sh.nif"); + const std::string scabbardName = addSuffixBeforeExtension(mesh, "_sh"); bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); if (!mResourceSystem->getVFS()->exists(scabbardName)) { diff --git a/apps/openmw/mwrender/actorutil.cpp b/apps/openmw/mwrender/actorutil.cpp index 8da921e532..7739d5a6f6 100644 --- a/apps/openmw/mwrender/actorutil.cpp +++ b/apps/openmw/mwrender/actorutil.cpp @@ -37,4 +37,16 @@ namespace MWRender || VFS::Path::pathEqual(Settings::models().mBaseanimfemale.get(), model) || VFS::Path::pathEqual(Settings::models().mBaseanim.get(), model); } + + std::string addSuffixBeforeExtension(const std::string& filename, const std::string& suffix) + { + size_t dotPos = filename.rfind('.'); + + // No extension found; return the original filename with suffix appended + if (dotPos == std::string::npos) + return filename + suffix; + + // Insert the suffix before the dot (extension) and return the new filename + return filename.substr(0, dotPos) + suffix + filename.substr(dotPos); + } } diff --git a/apps/openmw/mwrender/actorutil.hpp b/apps/openmw/mwrender/actorutil.hpp index 3107bf0183..6a5ab12dea 100644 --- a/apps/openmw/mwrender/actorutil.hpp +++ b/apps/openmw/mwrender/actorutil.hpp @@ -8,6 +8,7 @@ namespace MWRender { const std::string& getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf); bool isDefaultActorSkeleton(std::string_view model); + std::string addSuffixBeforeExtension(const std::string& filename, const std::string& suffix); } #endif diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b9ad471bf5..721806d992 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -539,8 +539,7 @@ namespace MWRender if (mesh.empty()) return std::string(); - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); if (mResourceSystem->getVFS()->exists(holsteredName)) { osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index aadbf35af8..ffcf12b167 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -21,13 +21,36 @@ namespace SceneUtil mFoundNode = &group; 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); } @@ -53,6 +76,8 @@ namespace SceneUtil 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(), '_', ' ');