diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f2ee0eb63e..35cf145f827 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/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; }; } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0baf68ed5d1..31b5d36c794 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; @@ -1479,6 +1489,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); 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/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index d7f8bb902ce..61a2a2628a5 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -1,6 +1,7 @@ #include "rotatecontroller.hpp" #include +#include namespace MWRender { @@ -43,6 +44,17 @@ namespace MWRender 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) + matrix *= parent->getMatrixInSkeletonSpace(); + + b->setMatrixInSkeletonSpace(matrix); + } + traverse(node, nv); } 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..309de4a878c --- /dev/null +++ b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp @@ -0,0 +1,131 @@ +#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/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 28bc696cd3b..18f72104bd5 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..84e7a7e311c 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; @@ -99,6 +99,9 @@ namespace Resource const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel : channels) { + // Replace 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..ab3f92f10d8 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -9,7 +9,10 @@ #include #include +#include #include +#include +#include #include @@ -268,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 (dynamic_cast(&node)) + node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); + if (osg::StateSet* stateset = node.getStateSet()) { if (stateset->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) @@ -353,6 +361,53 @@ namespace Resource std::vector> mRigGeometryHolders; }; + void updateVertexInfluenceMap(osgAnimation::RigGeometry& rig) + { + osgAnimation::VertexInfluenceMap* vertexInfluenceMap = rig.getInfluenceMap(); + if (!vertexInfluenceMap) + return; + + std::vector renameList; + for (const auto& [boneName, unused] : *vertexInfluenceMap) + { + if (boneName.find('_') != std::string::npos) + renameList.push_back(boneName); + } + + for (const std::string& oldName : renameList) + { + const std::string newName = Misc::StringUtils::underscoresToSpaces(oldName); + if (vertexInfluenceMap->find(newName) == vertexInfluenceMap->end()) + (*vertexInfluenceMap)[newName] = std::move((*vertexInfluenceMap)[oldName]); + vertexInfluenceMap->erase(oldName); + } + } + + class RenameAnimCallbacksVisitor : public osg::NodeVisitor + { + public: + RenameAnimCallbacksVisitor() + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + { + } + + void apply(osg::MatrixTransform& node) override + { + // osgAnimation update callback name must match bone name/channel targets + osg::Callback* cb = node.getUpdateCallback(); + while (cb) + { + auto 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 +611,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 +627,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 +655,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 +676,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::RenameAnimCallbacksVisitor 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..5e91830bbab 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 06b693a6caf..d3268fef36f 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) { @@ -85,9 +91,8 @@ namespace SceneUtil } } - osg::Vec3f OsgAnimationController::getTranslation(float time) const + osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string_view name) const { - osg::Vec3f translationValue; std::string animationName; float newTime = time; @@ -98,10 +103,11 @@ namespace SceneUtil { newTime = time - emulatedAnimation.mStartTime; animationName = emulatedAnimation.mName; + break; } } - // Find the root transform track in animation + // Find the bone's transform track in animation for (const auto& mergedAnimationTrack : mMergedAnimationTracks) { if (mergedAnimationTrack->getName() != animationName) @@ -111,7 +117,7 @@ namespace SceneUtil for (const auto& channel : channels) { - if (channel->getTargetName() != "bip01" || channel->getName() != "transform") + if (!Misc::StringUtils::ciEqual(name, channel->getTargetName()) || channel->getName() != "transform") continue; if (osgAnimation::MatrixLinearSampler* templateSampler @@ -119,13 +125,17 @@ namespace SceneUtil { osg::Matrixf matrix; templateSampler->getValueAt(newTime, matrix); - translationValue = matrix.getTrans(); - return osg::Vec3f(translationValue[0], translationValue[1], translationValue[2]); + return matrix; } } } - return osg::Vec3f(); + 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) @@ -162,6 +172,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..bb9a6217601 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_view 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..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) @@ -70,6 +70,22 @@ namespace SceneUtil NodeMap& mMap; }; + /// Maps names to bone nodes + class NodeMapVisitorBoneOnly : public osg::NodeVisitor + { + public: + 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();