Browse Source

Optimize the SceneNodeSelectionList implementation (#16593)

* Optimize the SceneNodeSelectionList implementation

Signed-off-by: Mike Balfour <[email protected]>

* Update Code/Tools/SceneAPI/SceneCore/Mocks/DataTypes/ManifestBase/MockISceneNodeSelectionList.h

Co-authored-by: lumberyard-employee-dm <[email protected]>
Signed-off-by: Mike Balfour <[email protected]>

* Update Code/Tools/SceneAPI/SceneData/ManifestBase/SceneNodeSelectionList.cpp

Co-authored-by: lumberyard-employee-dm <[email protected]>
Signed-off-by: Mike Balfour <[email protected]>

* Update Code/Tools/SceneAPI/SceneUI/RowWidgets/NodeTreeSelectionWidget.cpp

Co-authored-by: lumberyard-employee-dm <[email protected]>
Signed-off-by: Mike Balfour <[email protected]>

* Update Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/ActorBuilder.cpp

Co-authored-by: Steve Pham <[email protected]>
Signed-off-by: Mike Balfour <[email protected]>

* PR feedback - changed AZ_TracePrintf to AZ_Trace

Signed-off-by: Mike Balfour <[email protected]>

* Fix syntax of PR suggested change

Signed-off-by: Mike Balfour <[email protected]>

* Compile fix for linux

Signed-off-by: Mike Balfour <[email protected]>

* Added missing version converter, causing existing data to deserialize incorrectly.

Signed-off-by: Mike Balfour <[email protected]>

* PR feedback - better version check, explicit type registration.

Signed-off-by: Mike Balfour <[email protected]>

* Fix non-unity build compile error.

Signed-off-by: Mike Balfour <[email protected]>

* Fix memory leak in EMFX tests

Signed-off-by: Mike Balfour <[email protected]>

* Fixed more EMFX test memory leaks exposed by changing the tests to shut down correctly.

Signed-off-by: Mike Balfour <[email protected]>

---------

Signed-off-by: Mike Balfour <[email protected]>
Co-authored-by: lumberyard-employee-dm <[email protected]>
Co-authored-by: Steve Pham <[email protected]>
Mike Balfour 1 year ago
parent
commit
99c7fca8f1
60 changed files with 615 additions and 411 deletions
  1. 19 9
      Code/Tools/SceneAPI/SceneCore/DataTypes/ManifestBase/ISceneNodeSelectionList.h
  2. 9 9
      Code/Tools/SceneAPI/SceneCore/Mocks/DataTypes/ManifestBase/MockISceneNodeSelectionList.h
  3. 32 12
      Code/Tools/SceneAPI/SceneCore/Tests/Utilities/SceneGraphSelectorTests.cpp
  4. 21 16
      Code/Tools/SceneAPI/SceneCore/Utilities/SceneGraphSelector.cpp
  5. 6 2
      Code/Tools/SceneAPI/SceneCore/Utilities/SceneGraphSelector.h
  6. 84 70
      Code/Tools/SceneAPI/SceneData/ManifestBase/SceneNodeSelectionList.cpp
  7. 8 10
      Code/Tools/SceneAPI/SceneData/ManifestBase/SceneNodeSelectionList.h
  8. 42 39
      Code/Tools/SceneAPI/SceneUI/RowWidgets/NodeTreeSelectionWidget.cpp
  9. 1 9
      Code/Tools/SceneAPI/SceneUI/SceneWidgets/SceneGraphWidget.cpp
  10. 58 48
      Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/ActorBuilder.cpp
  11. 41 34
      Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/MorphTargetExporter.cpp
  12. 2 6
      Gems/EMotionFX/Code/EMotionFX/Pipeline/SceneAPIExt/Rules/LodRule.cpp
  13. 2 0
      Gems/EMotionFX/Code/Tests/ActorBuilderTests.cpp
  14. 8 1
      Gems/EMotionFX/Code/Tests/ActorComponentBusTests.cpp
  15. 1 0
      Gems/EMotionFX/Code/Tests/ActorFixture.cpp
  16. 6 0
      Gems/EMotionFX/Code/Tests/AnimGraphActionCommandTests.cpp
  17. 6 0
      Gems/EMotionFX/Code/Tests/AnimGraphComponentBusTests.cpp
  18. 37 1
      Gems/EMotionFX/Code/Tests/AnimGraphCopyPasteTests.cpp
  19. 4 0
      Gems/EMotionFX/Code/Tests/AnimGraphFixture.cpp
  20. 2 0
      Gems/EMotionFX/Code/Tests/AnimGraphFixture.h
  21. 1 0
      Gems/EMotionFX/Code/Tests/AnimGraphNetworkingBusTests.cpp
  22. 0 1
      Gems/EMotionFX/Code/Tests/AnimGraphNodeEventFilterTests.cpp
  23. 0 1
      Gems/EMotionFX/Code/Tests/AnimGraphNodeProcessingTests.cpp
  24. 18 0
      Gems/EMotionFX/Code/Tests/AnimGraphReferenceNodeTests.cpp
  25. 18 0
      Gems/EMotionFX/Code/Tests/AnimGraphStateMachineInterruptionTests.cpp
  26. 11 0
      Gems/EMotionFX/Code/Tests/AnimGraphStateMachineSyncTests.cpp
  27. 6 0
      Gems/EMotionFX/Code/Tests/AnimGraphTransitionCommandTests.cpp
  28. 2 0
      Gems/EMotionFX/Code/Tests/AnimGraphTransitionConditionFixture.cpp
  29. 2 0
      Gems/EMotionFX/Code/Tests/AnimGraphTransitionFixture.cpp
  30. 1 0
      Gems/EMotionFX/Code/Tests/AutoSkeletonLODTests.cpp
  31. 0 2
      Gems/EMotionFX/Code/Tests/BlendTreeBlendNNodeTests.cpp
  32. 0 1
      Gems/EMotionFX/Code/Tests/BlendTreeFloatConditionNodeTests.cpp
  33. 0 1
      Gems/EMotionFX/Code/Tests/BlendTreeFloatConstantNodeTests.cpp
  34. 0 1
      Gems/EMotionFX/Code/Tests/BlendTreeFloatMath1NodeTests.cpp
  35. 0 1
      Gems/EMotionFX/Code/Tests/BlendTreeMaskNodeTests.cpp
  36. 0 1
      Gems/EMotionFX/Code/Tests/BlendTreeMotionFrameNodeTests.cpp
  37. 0 1
      Gems/EMotionFX/Code/Tests/BlendTreeRagdollNodeTests.cpp
  38. 0 1
      Gems/EMotionFX/Code/Tests/BlendTreeRangeRemapperNodeTests.cpp
  39. 0 1
      Gems/EMotionFX/Code/Tests/BlendTreeRotationLimitNodeTests.cpp
  40. 0 1
      Gems/EMotionFX/Code/Tests/BlendTreeRotationMath2NodeTests.cpp
  41. 0 1
      Gems/EMotionFX/Code/Tests/BlendTreeTransformNodeTests.cpp
  42. 0 1
      Gems/EMotionFX/Code/Tests/BoolLogicNodeTests.cpp
  43. 1 0
      Gems/EMotionFX/Code/Tests/JackGraphFixture.cpp
  44. 1 0
      Gems/EMotionFX/Code/Tests/MorphSkinAttachmentTests.cpp
  45. 1 0
      Gems/EMotionFX/Code/Tests/MotionEventTrackTests.cpp
  46. 2 1
      Gems/EMotionFX/Code/Tests/MotionExtractionBusTests.cpp
  47. 1 0
      Gems/EMotionFX/Code/Tests/MotionInstanceTests.cpp
  48. 1 0
      Gems/EMotionFX/Code/Tests/PoseTests.cpp
  49. 1 0
      Gems/EMotionFX/Code/Tests/ProvidesUI/AnimGraph/AnimGraphActivateTests.cpp
  50. 0 1
      Gems/EMotionFX/Code/Tests/QuaternionParameterTests.cpp
  51. 2 1
      Gems/EMotionFX/Code/Tests/SimpleMotionComponentBusTests.cpp
  52. 6 0
      Gems/EMotionFX/Code/Tests/SkeletonNodeSearchTests.cpp
  53. 0 1
      Gems/EMotionFX/Code/Tests/SyncingSystemTests.cpp
  54. 2 0
      Gems/EMotionFX/Code/Tests/SystemComponentFixture.h
  55. 1 0
      Gems/EMotionFX/Code/Tests/UI/CanAddSimulatedObject.cpp
  56. 0 1
      Gems/EMotionFX/Code/Tests/Vector2ToVector3CompatibilityTests.cpp
  57. 0 1
      Gems/EMotionFX/Code/Tests/Vector3ParameterTests.cpp
  58. 1 8
      Gems/NvCloth/Code/Source/Pipeline/SceneAPIExt/ClothRuleBehavior.cpp
  59. 141 112
      Gems/PhysX/Code/Source/Pipeline/MeshExporter.cpp
  60. 6 4
      Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp

+ 19 - 9
Code/Tools/SceneAPI/SceneCore/DataTypes/ManifestBase/ISceneNodeSelectionList.h

@@ -29,20 +29,30 @@ namespace AZ
             {
             public:
                 AZ_RTTI(ISceneNodeSelectionList, "{DC3F9996-E550-4780-A03B-80B0DDA1DA45}");
-                
-                virtual ~ ISceneNodeSelectionList() = default;
+
+                //! Callback for enumerating through the list of selected or unselected nodes.
+                //! @param name The node name for each node enumerated through.
+                //! @return True if enumeration should continue, false if it should stop.
+                using EnumerateNodesCallback = AZStd::function<bool(const AZStd::string& name)>;
+
+                virtual ~ISceneNodeSelectionList() = default;
                 virtual size_t GetSelectedNodeCount() const = 0;
-                virtual const AZStd::string& GetSelectedNode(size_t index) const = 0;
-                virtual size_t AddSelectedNode(const AZStd::string& name) = 0;
-                virtual size_t AddSelectedNode(AZStd::string&& name) = 0;
-                virtual void RemoveSelectedNode(size_t index) = 0;
+                virtual void AddSelectedNode(const AZStd::string& name) = 0;
+                virtual void AddSelectedNode(AZStd::string&& name) = 0;
                 virtual void RemoveSelectedNode(const AZStd::string& name) = 0;
                 virtual void ClearSelectedNodes() = 0;
-
-                virtual size_t GetUnselectedNodeCount() const = 0;
-                virtual const AZStd::string& GetUnselectedNode(size_t index) const = 0;
                 virtual void ClearUnselectedNodes() = 0;
 
+                //! Check to see if the given name is a selected node.
+                //! @return True if the name appears in the selected node list, false if it doesn't.
+                virtual bool IsSelectedNode(const AZStd::string& name) const = 0;
+                //! Enumerate through the list of selected nodes, calling the callback with each node name.
+                //! @param callback The callback to call for each selected node.
+                virtual void EnumerateSelectedNodes(const EnumerateNodesCallback& callback) const = 0;
+                //! Enumerate through the list of unselected nodes, calling the callback with each node name.
+                //! @param callback The callback to call for each unselected node.
+                virtual void EnumerateUnselectedNodes(const EnumerateNodesCallback& callback) const = 0;
+
                 virtual AZStd::unique_ptr<ISceneNodeSelectionList> Copy() const = 0;
                 virtual void CopyTo(ISceneNodeSelectionList& other) const = 0;
             };

+ 9 - 9
Code/Tools/SceneAPI/SceneCore/Mocks/DataTypes/ManifestBase/MockISceneNodeSelectionList.h

@@ -26,27 +26,27 @@ namespace AZ
                 // IMeshGroup
                 MOCK_CONST_METHOD0(GetSelectedNodeCount,
                     size_t());
-                MOCK_CONST_METHOD1(GetSelectedNode,
-                    const AZStd::string & (size_t index));
                 MOCK_METHOD1(AddSelectedNode,
-                    size_t(const AZStd::string & name));
-                size_t AddSelectedNode(AZStd::string&& name)
+                    void(const AZStd::string & name));
+                void AddSelectedNode(AZStd::string&& name)
                 {
-                    return AddSelectedNode(name);
+                    // Passing in AZStd::move(Name) directly into AddSelectedNode() causes compile errors on Linux as it
+                    // thinks this has infinite recursion. Storing in a temp variable disambiguates the AddSelectedNode() call.
+                    const AZStd::string& movedName = AZStd::move(name);
+                    AddSelectedNode(movedName);
                 }
-                MOCK_METHOD1(RemoveSelectedNode,
-                    void(size_t index));
                 MOCK_METHOD1(RemoveSelectedNode,
                     void(const AZStd::string & name));
                 MOCK_METHOD0(ClearSelectedNodes,
                     void());
+                MOCK_CONST_METHOD1(IsSelectedNode, bool(const AZStd::string& name));
+                MOCK_CONST_METHOD1(EnumerateSelectedNodes, void(const EnumerateNodesCallback& callback));
 
                 MOCK_CONST_METHOD0(GetUnselectedNodeCount,
                     size_t());
-                MOCK_CONST_METHOD1(GetUnselectedNode,
-                    const AZStd::string & (size_t index));
                 MOCK_METHOD0(ClearUnselectedNodes,
                     void());
+                MOCK_CONST_METHOD1(EnumerateUnselectedNodes, void(const EnumerateNodesCallback& callback));
 
                 MOCK_CONST_METHOD0(CopyProxy,
                     ISceneNodeSelectionList*());

+ 32 - 12
Code/Tools/SceneAPI/SceneCore/Tests/Utilities/SceneGraphSelectorTests.cpp

@@ -62,23 +62,43 @@ namespace AZ
                     EXPECT_CALL(m_testNodeSelectionList, GetUnselectedNodeCount())
                         .WillRepeatedly(Return(unselectedNodes.size()));
 
-                    for (size_t i = 0; i < selectedNodes.size(); ++i)
+                    auto enumerateSelectedNodesInvoke =
+                        [&selectedNodes](const DataTypes::ISceneNodeSelectionList::EnumerateNodesCallback& callback)
                     {
-                        EXPECT_CALL(m_testNodeSelectionList, GetSelectedNode(Eq(i)))
-                            .WillRepeatedly(ReturnRef(selectedNodes[i]));
-                    }
-
-                    for (size_t i = 0; i < unselectedNodes.size(); ++i)
+                        for (auto& node : selectedNodes)
+                        {
+                            if (!callback(node))
+                            {
+                                break;
+                            }
+                        }
+                    };
+                    EXPECT_CALL(m_testNodeSelectionList, EnumerateSelectedNodes(_))
+                        .WillRepeatedly(Invoke(enumerateSelectedNodesInvoke));
+
+                    auto enumerateUnselectedNodesInvoke =
+                        [&unselectedNodes](const DataTypes::ISceneNodeSelectionList::EnumerateNodesCallback& callback)
                     {
-                        EXPECT_CALL(m_testNodeSelectionList, GetUnselectedNode(Eq(i)))
-                            .WillRepeatedly(ReturnRef(unselectedNodes[i]));
-                    }
+                        for (auto& node : unselectedNodes)
+                        {
+                            if (!callback(node))
+                            {
+                                break;
+                            }
+                        }
+                    };
+                    EXPECT_CALL(m_testNodeSelectionList, EnumerateUnselectedNodes(_))
+                        .WillRepeatedly(Invoke(enumerateUnselectedNodesInvoke));
+
+                    auto isSelectedNodeInvoke = [&selectedNodes](const AZStd::string& name)
+                    {
+                        return (AZStd::find(selectedNodes.begin(), selectedNodes.end(), name) != selectedNodes.end());
+                    };
+                    EXPECT_CALL(m_testNodeSelectionList, IsSelectedNode(_)).WillRepeatedly(Invoke(isSelectedNodeInvoke));
 
-                    auto selectedNodeInvoke = [&selectedNodes](const AZStd::string& name) -> size_t
+                    auto selectedNodeInvoke = [&selectedNodes](const AZStd::string& name)
                         {
-                            size_t index = selectedNodes.size();
                             selectedNodes.push_back(name);
-                            return index;
                         };
                     EXPECT_CALL(m_testNodeSelectionList, AddSelectedNode(_))
                         .WillRepeatedly(Invoke(selectedNodeInvoke));

+ 21 - 16
Code/Tools/SceneAPI/SceneCore/Utilities/SceneGraphSelector.cpp

@@ -106,8 +106,8 @@ namespace AZ
                 NodeRemapFunction nodeRemap)
             {
                 AZStd::vector<AZStd::string> targetNodes;
-                AZStd::set<AZStd::string> selectedNodesSet;
-                AZStd::set<AZStd::string> unselectedNodesSet;
+                AZStd::unordered_set<AZStd::string> selectedNodesSet;
+                AZStd::unordered_set<AZStd::string> unselectedNodesSet;
                 CopySelectionToSet(selectedNodesSet, unselectedNodesSet, list);
                 CorrectRootNode(graph, selectedNodesSet, unselectedNodesSet);
 
@@ -214,8 +214,8 @@ namespace AZ
 
             void SceneGraphSelector::UpdateNodeSelection(const Containers::SceneGraph& graph, DataTypes::ISceneNodeSelectionList& list)
             {
-                AZStd::set<AZStd::string> selectedNodesSet;
-                AZStd::set<AZStd::string> unselectedNodesSet;
+                AZStd::unordered_set<AZStd::string> selectedNodesSet;
+                AZStd::unordered_set<AZStd::string> unselectedNodesSet;
                 CopySelectionToSet(selectedNodesSet, unselectedNodesSet, list);
                 CorrectRootNode(graph, selectedNodesSet, unselectedNodesSet);
 
@@ -297,20 +297,25 @@ namespace AZ
                 }
             }
 
-            void SceneGraphSelector::CopySelectionToSet(AZStd::set<AZStd::string>& selected, AZStd::set<AZStd::string>& unselected, const DataTypes::ISceneNodeSelectionList& list)
+            void SceneGraphSelector::CopySelectionToSet(
+                AZStd::unordered_set<AZStd::string>& selected, AZStd::unordered_set<AZStd::string>& unselected,
+                const DataTypes::ISceneNodeSelectionList& list)
             {
-                for (size_t i = 0; i < list.GetSelectedNodeCount(); ++i)
-                {
-                    selected.insert(list.GetSelectedNode(i));
-                }
-                for (size_t i = 0; i < list.GetUnselectedNodeCount(); ++i)
-                {
-                    unselected.insert(list.GetUnselectedNode(i));
-                }
+                list.EnumerateSelectedNodes([&selected](const AZStd::string& name)
+                    {
+                        selected.insert(name);
+                        return true;
+                    });
+
+                list.EnumerateUnselectedNodes([&unselected](const AZStd::string& name)
+                    {
+                        unselected.insert(name);
+                        return true;
+                    });
             }
 
             void SceneGraphSelector::CorrectRootNode(const Containers::SceneGraph& graph,
-                AZStd::set<AZStd::string>& selected, AZStd::set<AZStd::string>& unselected)
+                AZStd::unordered_set<AZStd::string>& selected, AZStd::unordered_set<AZStd::string>& unselected)
             {
                 // If both of the unselected and selected node lists are empty or don't exist, deselect all the nodes
                 // in the graph by deselecting the root node.
@@ -322,8 +327,8 @@ namespace AZ
                 // remove selected nodes based on the deselected list.
                 bool selectRootNode = !unselected.empty();
                 AZStd::string rootNodeName = graph.GetNodeName(graph.GetRoot()).GetPath();
-                AZStd::set<AZStd::string>& nodeSetToAdd = selectRootNode ? selected : unselected;
-                AZStd::set<AZStd::string>& nodeSetToRemove = selectRootNode ? unselected : selected;
+                auto& nodeSetToAdd = selectRootNode ? selected : unselected;
+                auto& nodeSetToRemove = selectRootNode ? unselected : selected;
 
                 if (nodeSetToAdd.find(rootNodeName) == nodeSetToAdd.end())
                 {

+ 6 - 2
Code/Tools/SceneAPI/SceneCore/Utilities/SceneGraphSelector.h

@@ -90,7 +90,11 @@ namespace AZ::SceneAPI::Utilities
             const DataTypes::ISceneNodeGroup& meshGroup);
 
     private:
-        static void CopySelectionToSet(AZStd::set<AZStd::string>& selected, AZStd::set<AZStd::string>& unselected, const DataTypes::ISceneNodeSelectionList& list);
-        static void CorrectRootNode(const Containers::SceneGraph& graph, AZStd::set<AZStd::string>& selected, AZStd::set<AZStd::string>& unselected);
+        static void CopySelectionToSet(
+            AZStd::unordered_set<AZStd::string>& selected, AZStd::unordered_set<AZStd::string>& unselected,
+            const DataTypes::ISceneNodeSelectionList& list);
+        static void CorrectRootNode(
+            const Containers::SceneGraph& graph,
+            AZStd::unordered_set<AZStd::string>& selected, AZStd::unordered_set<AZStd::string>& unselected);
     };
 }

+ 84 - 70
Code/Tools/SceneAPI/SceneData/ManifestBase/SceneNodeSelectionList.cpp

@@ -10,6 +10,7 @@
 #include <AzCore/RTTI/ReflectContext.h>
 #include <AzCore/Serialization/SerializeContext.h>
 #include <AzCore/Serialization/EditContext.h>
+#include <AzCore/std/containers/vector.h>
 #include <SceneAPI/SceneData/ManifestBase/SceneNodeSelectionList.h>
 
 namespace AZ
@@ -23,80 +24,28 @@ namespace AZ
                 return m_selectedNodes.size();
             }
 
-            const AZStd::string& SceneNodeSelectionList::GetSelectedNode(size_t index) const
+            void SceneNodeSelectionList::AddSelectedNode(const AZStd::string& name)
             {
-                AZ_Assert(index < m_selectedNodes.size(), "Invalid index %i for selected node in mesh group.", index);
-                return m_selectedNodes[index];
-            }
-
-            size_t SceneNodeSelectionList::AddSelectedNode(const AZStd::string& name)
-            {
-                auto unselectEntry = AZStd::find(m_unselectedNodes.begin(), m_unselectedNodes.end(), name);
-                if (unselectEntry != m_unselectedNodes.end())
+                if (auto extractedNodeHandle = m_unselectedNodes.extract(name); extractedNodeHandle)
                 {
-                    m_unselectedNodes.erase(unselectEntry);
-                }
-
-                auto entry = AZStd::find(m_selectedNodes.begin(), m_selectedNodes.end(), name);
-                if (entry == m_selectedNodes.end())
-                {
-                    size_t index = m_selectedNodes.size();
-                    m_selectedNodes.push_back(name);
-                    return index;
+                    m_selectedNodes.insert(AZStd::move(extractedNodeHandle.value()));
                 }
                 else
                 {
-                    return entry - m_selectedNodes.begin();
+                    m_selectedNodes.emplace(name);
                 }
             }
 
-            size_t SceneNodeSelectionList::AddSelectedNode(AZStd::string&& name)
+            void SceneNodeSelectionList::AddSelectedNode(AZStd::string&& name)
             {
-                auto unselectedEntry = AZStd::find(m_unselectedNodes.begin(), m_unselectedNodes.end(), name);
-                if (unselectedEntry != m_unselectedNodes.end())
-                {
-                    m_unselectedNodes.erase(unselectedEntry);
-                }
-
-                auto entry = AZStd::find(m_selectedNodes.begin(), m_selectedNodes.end(), name);
-                if (entry == m_selectedNodes.end())
-                {
-                    size_t index = m_selectedNodes.size();
-                    m_selectedNodes.push_back(AZStd::move(name));
-                    return index;
-                }
-                else
-                {
-                    return entry - m_selectedNodes.begin();
-                }
-            }
-
-            void SceneNodeSelectionList::RemoveSelectedNode(size_t index)
-            {
-                if (index < m_selectedNodes.size())
-                {
-                    auto unselectedEntry = AZStd::find(m_unselectedNodes.begin(), m_unselectedNodes.end(), m_selectedNodes[index]);
-                    if (unselectedEntry == m_unselectedNodes.end())
-                    {
-                        m_unselectedNodes.push_back(m_selectedNodes[index]);
-                    }
-                    m_selectedNodes.erase(m_selectedNodes.begin() + index);
-                }
+                m_unselectedNodes.erase(name);
+                m_selectedNodes.emplace(AZStd::move(name));
             }
 
             void SceneNodeSelectionList::RemoveSelectedNode(const AZStd::string& name)
             {
-                auto selectEntry = AZStd::find(m_selectedNodes.begin(), m_selectedNodes.end(), name);
-                if (selectEntry != m_selectedNodes.end())
-                {
-                    m_selectedNodes.erase(selectEntry);
-                }
-
-                auto entry = AZStd::find(m_unselectedNodes.begin(), m_unselectedNodes.end(), name);
-                if (entry == m_unselectedNodes.end())
-                {
-                    m_unselectedNodes.push_back(name);
-                }
+                m_selectedNodes.erase(name);
+                m_unselectedNodes.emplace(name);
             }
 
             void SceneNodeSelectionList::ClearSelectedNodes()
@@ -104,22 +53,36 @@ namespace AZ
                 m_selectedNodes.clear();
             }
 
+            void SceneNodeSelectionList::ClearUnselectedNodes()
+            {
+                m_unselectedNodes.clear();
+            }
 
-
-            size_t SceneNodeSelectionList::GetUnselectedNodeCount() const
+            bool SceneNodeSelectionList::IsSelectedNode(const AZStd::string& name) const
             {
-                return m_unselectedNodes.size();
+                return m_selectedNodes.contains(name);
             }
 
-            const AZStd::string& SceneNodeSelectionList::GetUnselectedNode(size_t index) const
+            void SceneNodeSelectionList::EnumerateSelectedNodes(const EnumerateNodesCallback& callback) const
             {
-                AZ_Assert(index < m_unselectedNodes.size(), "Invalid index %i for unselected node in mesh group.", index);
-                return m_unselectedNodes[index];
+                for (auto& node : m_selectedNodes)
+                {
+                    if (!callback(node))
+                    {
+                        break;
+                    }
+                }
             }
 
-            void SceneNodeSelectionList::ClearUnselectedNodes()
+            void SceneNodeSelectionList::EnumerateUnselectedNodes(const EnumerateNodesCallback& callback) const
             {
-                m_unselectedNodes.clear();
+                for (auto& node : m_unselectedNodes)
+                {
+                    if (!callback(node))
+                    {
+                        break;
+                    }
+                }
             }
 
             AZStd::unique_ptr<DataTypes::ISceneNodeSelectionList> SceneNodeSelectionList::Copy() const
@@ -142,6 +105,50 @@ namespace AZ
                 }
             }
 
+            bool SceneNodeSelectionListVersionConverter(
+                AZ::SerializeContext& serializeContext, AZ::SerializeContext::DataElementNode& classElement)
+            {
+                // Version 3 - changed selectedNodes/unselectedNodes from vector to unordered_set.
+                if (classElement.GetVersion() < 3)
+                {
+                    // Convert a serialized field from vector<string> to unordered_set<string>
+                    auto convertVectorToUnorderedSet = [&serializeContext, &classElement](AZ::Crc32 element) -> bool
+                    {
+                        int nodesIndex = classElement.FindElement(element);
+
+                        if (nodesIndex < 0)
+                        {
+                            return false;
+                        }
+
+                        AZ::SerializeContext::DataElementNode& nodes = classElement.GetSubElement(nodesIndex);
+                        AZStd::vector<AZStd::string> nodesVector;
+                        AZStd::unordered_set<AZStd::string> nodesSet;
+                        if (!nodes.GetData<AZStd::vector<AZStd::string>>(nodesVector))
+                        {
+                            return false;
+                        }
+                        nodesSet.insert(nodesVector.begin(), nodesVector.end());
+                        nodes.Convert<AZStd::unordered_set<AZStd::string>>(serializeContext);
+                        if (!nodes.SetData<AZStd::unordered_set<AZStd::string>>(serializeContext, nodesSet))
+                        {
+                            return false;
+                        }
+
+                        return true;
+                    };
+
+                    // Convert selectedNodes and unselectedNodes from a vector to an unordered_set
+                    bool result = convertVectorToUnorderedSet(AZ_CRC_CE("selectedNodes"));
+                    result = result && convertVectorToUnorderedSet(AZ_CRC_CE("unselectedNodes"));
+
+                    return result;
+                }
+
+                return true;
+            }
+
+
             void SceneNodeSelectionList::Reflect(AZ::ReflectContext* context)
             {
                 AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
@@ -150,9 +157,16 @@ namespace AZ
                     return;
                 }
 
-                serializeContext->Class<SceneNodeSelectionList, DataTypes::ISceneNodeSelectionList>()->Version(1)
+                serializeContext->Class<SceneNodeSelectionList, DataTypes::ISceneNodeSelectionList>()
+                    ->Version(3, &SceneNodeSelectionListVersionConverter)
                     ->Field("selectedNodes", &SceneNodeSelectionList::m_selectedNodes)
                     ->Field("unselectedNodes", &SceneNodeSelectionList::m_unselectedNodes);
+
+                // Explicitly register the AZStd::vector<AZStd::string> type. The version converter needs it to be able to read
+                // in the old data, and the type itself only gets registered automatically on-demand through the serializeContext
+                // fields. Since the serializeContext no longer contains this type, there's no guarantee it would be created.
+                // By explicitly registering it here, we can ensure that it exists.
+                serializeContext->RegisterGenericType<AZStd::vector<AZStd::string>>();
             }
         } // SceneData
     } // SceneAPI

+ 8 - 10
Code/Tools/SceneAPI/SceneData/ManifestBase/SceneNodeSelectionList.h

@@ -10,9 +10,8 @@
 
 
 #include <AzCore/JSON/document.h>
-#include <AzCore/std/containers/vector.h>
+#include <AzCore/std/containers/unordered_set.h>
 #include <AzCore/std/string/string.h>
-#include <AzCore/std/smart_ptr/shared_ptr.h>
 #include <SceneAPI/SceneCore/DataTypes/ManifestBase/ISceneNodeSelectionList.h>
 #include <SceneAPI/SceneData/SceneDataConfiguration.h>
 
@@ -41,16 +40,15 @@ namespace AZ
                 ~SceneNodeSelectionList() override;
 
                 SCENE_DATA_API size_t GetSelectedNodeCount() const override;
-                SCENE_DATA_API const AZStd::string& GetSelectedNode(size_t index) const override;
-                SCENE_DATA_API size_t AddSelectedNode(const AZStd::string& name) override;
-                SCENE_DATA_API size_t AddSelectedNode(AZStd::string&& name) override;
-                SCENE_DATA_API void RemoveSelectedNode(size_t index) override;
+                SCENE_DATA_API void AddSelectedNode(const AZStd::string& name) override;
+                SCENE_DATA_API void AddSelectedNode(AZStd::string&& name) override;
                 SCENE_DATA_API void RemoveSelectedNode(const AZStd::string& name) override;
                 SCENE_DATA_API void ClearSelectedNodes() override;
+                SCENE_DATA_API bool IsSelectedNode(const AZStd::string& name) const override;
+                SCENE_DATA_API void EnumerateSelectedNodes(const EnumerateNodesCallback& callback) const override;
 
-                SCENE_DATA_API size_t GetUnselectedNodeCount() const override;
-                SCENE_DATA_API const AZStd::string& GetUnselectedNode(size_t index) const override;
                 SCENE_DATA_API void ClearUnselectedNodes() override;
+                SCENE_DATA_API void EnumerateUnselectedNodes(const EnumerateNodesCallback& callback) const override;
 
                 SCENE_DATA_API AZStd::unique_ptr<DataTypes::ISceneNodeSelectionList> Copy() const override;
                 SCENE_DATA_API void CopyTo(DataTypes::ISceneNodeSelectionList& other) const override;
@@ -58,8 +56,8 @@ namespace AZ
                 static void Reflect(AZ::ReflectContext* context);
                 
             protected:
-                AZStd::vector<AZStd::string> m_selectedNodes;
-                AZStd::vector<AZStd::string> m_unselectedNodes;
+                AZStd::unordered_set<AZStd::string> m_selectedNodes;
+                AZStd::unordered_set<AZStd::string> m_unselectedNodes;
             };
             
             inline SceneNodeSelectionList::~SceneNodeSelectionList() = default;

+ 42 - 39
Code/Tools/SceneAPI/SceneUI/RowWidgets/NodeTreeSelectionWidget.cpp

@@ -208,54 +208,57 @@ namespace AZ
                 size_t result = 0;
                 AZStd::unique_ptr<DataTypes::ISceneNodeSelectionList> tempList(m_list->Copy());
                 Utilities::SceneGraphSelector::UpdateNodeSelection(graph, *tempList);
-                size_t selectedCount = tempList->GetSelectedNodeCount();
-                for (size_t i = 0; i < selectedCount; ++i)
-                {
-                    Containers::SceneGraph::NodeIndex index = graph.Find(tempList->GetSelectedNode(i));
-                    if (!index.IsValid())
+                tempList->EnumerateSelectedNodes(
+                    [&](const AZStd::string& nodeName)
                     {
-                        continue;
-                    }
-
-                    AZStd::shared_ptr<const DataTypes::IGraphObject> object = graph.GetNodeContent(index);
-                    if (!object)
-                    {
-                        continue;
-                    }
+                        Containers::SceneGraph::NodeIndex index = graph.Find(nodeName);
+                        if (!index.IsValid())
+                        {
+                            return true;
+                        }
 
-                    if (m_filterTypes.empty() && m_filterVirtualTypes.empty())
-                    {
-                        result++;
-                        continue;
-                    }
+                        AZStd::shared_ptr<const DataTypes::IGraphObject> object = graph.GetNodeContent(index);
+                        if (!object)
+                        {
+                            return true;
+                        }
 
-                    bool foundType = false;
-                    for (const Uuid& type : m_filterTypes)
-                    {
-                        if (object->RTTI_IsTypeOf(type))
+                        if (m_filterTypes.empty() && m_filterVirtualTypes.empty())
                         {
                             result++;
-                            foundType = true;
-                            break;
+                            return true;
                         }
-                    }
-                    if (foundType)
-                    {
-                        continue;
-                    }
 
-                    // Check if the object is one of the registered virtual types.
-                    Events::GraphMetaInfo::VirtualTypesSet virtualTypes;
-                    Events::GraphMetaInfoBus::Broadcast(&Events::GraphMetaInfo::GetVirtualTypes, virtualTypes, *root->GetScene(), index);
-                    for (Crc32 name : virtualTypes)
-                    {
-                        if (m_filterVirtualTypes.find(name) != m_filterVirtualTypes.end())
+                        bool foundType = false;
+                        for (const Uuid& type : m_filterTypes)
                         {
-                            result++;
-                            break;
+                            if (object->RTTI_IsTypeOf(type))
+                            {
+                                result++;
+                                foundType = true;
+                                break;
+                            }
                         }
-                    }
-                }
+                        if (foundType)
+                        {
+                            return true;
+                        }
+
+                        // Check if the object is one of the registered virtual types.
+                        Events::GraphMetaInfo::VirtualTypesSet virtualTypes;
+                        Events::GraphMetaInfoBus::Broadcast(
+                            &Events::GraphMetaInfo::GetVirtualTypes, virtualTypes, *root->GetScene(), index);
+                        for (Crc32 name : virtualTypes)
+                        {
+                            if (m_filterVirtualTypes.contains(name))
+                            {
+                                result++;
+                                break;
+                            }
+                        }
+
+                        return true;
+                    });
                 return result;
             }
 

+ 1 - 9
Code/Tools/SceneAPI/SceneUI/SceneWidgets/SceneGraphWidget.cpp

@@ -450,15 +450,7 @@ namespace AZ
 
             bool SceneGraphWidget::IsSelectedInSelectionList(const Containers::SceneGraph::Name& name, const DataTypes::ISceneNodeSelectionList& targetList) const
             {
-                size_t count = targetList.GetSelectedNodeCount();
-                for (size_t selectedNodeIndex = 0; selectedNodeIndex < count; ++selectedNodeIndex)
-                {
-                    if (targetList.GetSelectedNode(selectedNodeIndex) == name.GetPath())
-                    {
-                        return true;
-                    }
-                }
-                return false;
+                return targetList.IsSelectedNode(name.GetPath());
             }
 
             bool SceneGraphWidget::AddSelection(const QStandardItem* item)

+ 58 - 48
Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/ActorBuilder.cpp

@@ -88,7 +88,7 @@ namespace EMotionFX
             SceneContainers::SceneGraph::NodeIndex rootBoneNodeIndex = graph.Find(rootBoneName);
             if (!rootBoneNodeIndex.IsValid())
             {
-                AZ_TracePrintf(SceneUtil::ErrorWindow, "Root bone cannot be found.\n");
+                AZ_Trace(SceneUtil::ErrorWindow, "Root bone cannot be found.\n");
                 return SceneEvents::ProcessingResult::Failure;
             }
 
@@ -206,37 +206,42 @@ namespace EMotionFX
             if (skeletonOptimizationRule)
             {
                 const SceneData::SceneNodeSelectionList& criticalBonesList = skeletonOptimizationRule->GetCriticalBonesList();
-                const size_t criticalBoneCount = criticalBonesList.GetSelectedNodeCount();
-                
-                for (size_t boneIndex = 0; boneIndex < criticalBoneCount; ++boneIndex)
-                {
-                    const AZStd::string& bonePath = criticalBonesList.GetSelectedNode(boneIndex);
 
-                    // The bone name stores the node path in the scene. We need to convert it to EMotionFX node name.
-                    const SceneContainers::SceneGraph::NodeIndex nodeIndex = graph.Find(bonePath);
-                    if (!nodeIndex.IsValid())
+                criticalBonesList.EnumerateSelectedNodes(
+                    [&](const AZStd::string& bonePath)
                     {
-                        AZ_TracePrintf(SceneUtil::WarningWindow, "Critical bone %s is not stored in the scene. Skipping it.", bonePath.c_str());
-                        continue;
-                    }
+                        // The bone name stores the node path in the scene. We need to convert it to EMotionFX node name.
+                        const SceneContainers::SceneGraph::NodeIndex nodeIndex = graph.Find(bonePath);
+                        if (!nodeIndex.IsValid())
+                        {
+                            AZ_Trace(
+                                SceneUtil::WarningWindow, "Critical bone %s is not stored in the scene. Skipping it.", bonePath.c_str());
+                            return true;
+                        }
 
-                    AZStd::shared_ptr<const SceneDataTypes::IBoneData> boneData = azrtti_cast<const SceneDataTypes::IBoneData*>(graph.GetNodeContent(nodeIndex));
-                    if (!boneData)
-                    {
-                        // Make sure we are dealing with bone here.
-                        continue;
-                    }
+                        AZStd::shared_ptr<const SceneDataTypes::IBoneData> boneData =
+                            azrtti_cast<const SceneDataTypes::IBoneData*>(graph.GetNodeContent(nodeIndex));
+                        if (!boneData)
+                        {
+                            // Make sure we are dealing with bone here.
+                            return true;
+                        }
 
-                    const SceneContainers::SceneGraph::Name& nodeName = graph.GetNodeName(nodeIndex);
-                    EMotionFX::Node* emfxNode = actorSkeleton->FindNodeByName(nodeName.GetName());
-                    if (!emfxNode)
-                    {
-                        AZ_TracePrintf(SceneUtil::WarningWindow, "Critical bone %s is not in the actor skeleton hierarchy. Skipping it.", nodeName.GetName());
-                        continue;
-                    }
-                    // Critical node cannot be optimized out.
-                    emfxNode->SetIsCritical(true);
-                }
+                        const SceneContainers::SceneGraph::Name& nodeName = graph.GetNodeName(nodeIndex);
+                        EMotionFX::Node* emfxNode = actorSkeleton->FindNodeByName(nodeName.GetName());
+                        if (!emfxNode)
+                        {
+                            AZ_Trace(
+                                SceneUtil::WarningWindow,
+                                "Critical bone %s is not in the actor skeleton hierarchy. Skipping it.",
+                                nodeName.GetName());
+                            return true;
+                        }
+                        // Critical node cannot be optimized out.
+                        emfxNode->SetIsCritical(true);
+
+                        return true;
+                    });
 
                 actor->SetOptimizeSkeleton(skeletonOptimizationRule->GetServerSkeletonOptimization());
             }
@@ -267,29 +272,34 @@ namespace EMotionFX
             {
                 AZStd::vector<AZStd::string> criticalJoints;
                 const auto& criticalBonesList = skeletonOptimizeRule->GetCriticalBonesList();
-                const size_t numSelectedBones = criticalBonesList.GetSelectedNodeCount();
-                for (size_t i = 0; i < numSelectedBones; ++i)
-                {
-                    const AZStd::string criticalBonePath = criticalBonesList.GetSelectedNode(i);
-                    SceneContainers::SceneGraph::NodeIndex nodeIndex = graph.Find(criticalBonePath);
-                    if (!nodeIndex.IsValid())
+                criticalBonesList.EnumerateSelectedNodes(
+                    [&](const AZStd::string& criticalBonePath)
                     {
-                        AZ_TracePrintf(SceneUtil::WarningWindow, "Critical bone '%s' is not stored in the scene. Skipping it.", criticalBonePath.c_str());
-                        continue;
-                    }
+                        SceneContainers::SceneGraph::NodeIndex nodeIndex = graph.Find(criticalBonePath);
+                        if (!nodeIndex.IsValid())
+                        {
+                            AZ_Trace(
+                                SceneUtil::WarningWindow,
+                                "Critical bone '%s' is not stored in the scene. Skipping it.",
+                                criticalBonePath.c_str());
+                            return true;
+                        }
 
-                    const AZStd::shared_ptr<const SceneDataTypes::IBoneData> boneData = azrtti_cast<const SceneDataTypes::IBoneData*>(graph.GetNodeContent(nodeIndex));
-                    if (!boneData)
-                    {
-                        continue;
-                    }
+                        const AZStd::shared_ptr<const SceneDataTypes::IBoneData> boneData =
+                            azrtti_cast<const SceneDataTypes::IBoneData*>(graph.GetNodeContent(nodeIndex));
+                        if (!boneData)
+                        {
+                            return true;
+                        }
 
-                    const SceneContainers::SceneGraph::Name& nodeName = graph.GetNodeName(nodeIndex);
-                    if (nodeName.GetNameLength() > 0)
-                    {
-                        criticalJoints.emplace_back(nodeName.GetName());
-                    }
-                }
+                        const SceneContainers::SceneGraph::Name& nodeName = graph.GetNodeName(nodeIndex);
+                        if (nodeName.GetNameLength() > 0)
+                        {
+                            criticalJoints.emplace_back(nodeName.GetName());
+                        }
+
+                        return true;
+                    });
 
                 // Mark all skeletal joints for each LOD to be enabled or disabled, based on the skinning data and critical list.
                 // Also print the results to the log.

+ 41 - 34
Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/MorphTargetExporter.cpp

@@ -84,42 +84,49 @@ namespace EMotionFX
             AZStd::string morphNodeName;
             AZStd::string morphParentNodeName;
 
-            // Outer loop isolates unique morph target names in the selection list
-            const size_t selectionNodeCount = morphTargetRule->GetSceneNodeSelectionList().GetSelectedNodeCount();
-            for (size_t index = 0; index < selectionNodeCount; ++index)
-            {
-                AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = graph.Find(morphTargetRule->GetSceneNodeSelectionList().GetSelectedNode(index));
-                morphTargetName = context.m_scene.GetGraph().GetNodeName(nodeIndex).GetName();
-                if (AZStd::find(visitedBlendShapeNames.begin(), visitedBlendShapeNames.end(), morphTargetName) != visitedBlendShapeNames.end())
-                {
-                    continue;
-                }
-                visitedBlendShapeNames.insert(morphTargetName);
-                AZStd::unique_ptr<Actor> morphTargetActor = baseActorImage->Clone();
-                if (!morphTargetActor)
-                {
-                    return SceneEvents::ProcessingResult::Failure;
-                }
-                morphTargetActor->SetName(morphTargetName.c_str());
-
-                // Add the morph target actor to the main actor
-                EMotionFX::MorphTargetStandard* morphTarget = EMotionFX::MorphTargetStandard::Create(false, context.m_actor, morphTargetActor.get(), morphTargetName.c_str());
+            AZ::SceneAPI::Events::ProcessingResult processingResult = SceneEvents::ProcessingResult::Success;
 
-                // Assume LOD 0
-                EMotionFX::MorphSetup* morphSetup = context.m_actor->GetMorphSetup(0);
-                if (!morphSetup)
-                {
-                    morphSetup = EMotionFX::MorphSetup::Create();
-                    context.m_actor->SetMorphSetup(0, morphSetup);
-                }
-
-                if (morphSetup)
+            // Outer loop isolates unique morph target names in the selection list
+            morphTargetRule->GetSceneNodeSelectionList().EnumerateSelectedNodes(
+                [&](const AZStd::string& name)
                 {
-                    morphSetup->AddMorphTarget(morphTarget);
-                }
-            }
-
-            return SceneEvents::ProcessingResult::Success;
+                    AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = graph.Find(name);
+                    morphTargetName = context.m_scene.GetGraph().GetNodeName(nodeIndex).GetName();
+                    if (AZStd::find(visitedBlendShapeNames.begin(), visitedBlendShapeNames.end(), morphTargetName) !=
+                        visitedBlendShapeNames.end())
+                    {
+                        return true;
+                    }
+                    visitedBlendShapeNames.insert(morphTargetName);
+                    AZStd::unique_ptr<Actor> morphTargetActor = baseActorImage->Clone();
+                    if (!morphTargetActor)
+                    {
+                        processingResult = SceneEvents::ProcessingResult::Failure;
+                        return false;
+                    }
+                    morphTargetActor->SetName(morphTargetName.c_str());
+
+                    // Add the morph target actor to the main actor
+                    EMotionFX::MorphTargetStandard* morphTarget =
+                        EMotionFX::MorphTargetStandard::Create(false, context.m_actor, morphTargetActor.get(), morphTargetName.c_str());
+
+                    // Assume LOD 0
+                    EMotionFX::MorphSetup* morphSetup = context.m_actor->GetMorphSetup(0);
+                    if (!morphSetup)
+                    {
+                        morphSetup = EMotionFX::MorphSetup::Create();
+                        context.m_actor->SetMorphSetup(0, morphSetup);
+                    }
+
+                    if (morphSetup)
+                    {
+                        morphSetup->AddMorphTarget(morphTarget);
+                    }
+
+                    return true;
+                });
+
+            return processingResult;
         }
     } // namespace Pipeline
 } // namespace EMotionFX

+ 2 - 6
Gems/EMotionFX/Code/EMotionFX/Pipeline/SceneAPIExt/Rules/LodRule.cpp

@@ -59,13 +59,9 @@ namespace EMotionFX
                 for (size_t i = 0; i < lodCount; ++i)
                 {
                     const auto& list = m_nodeSelectionLists[i];
-                    const size_t selectedNodeCount = list.GetSelectedNodeCount();
-                    for (size_t j = 0; j < selectedNodeCount; ++j)
+                    if (list.IsSelectedNode(nodePath))
                     {
-                        if (nodePath == list.GetSelectedNode(j))
-                        {
-                            return true;
-                        }
+                        return true;
                     }
                 }
                 return false;

+ 2 - 0
Gems/EMotionFX/Code/Tests/ActorBuilderTests.cpp

@@ -76,6 +76,8 @@ namespace EMotionFX
 
         void TearDown() override
         {
+            m_actor.reset();
+
             delete m_scene;
             m_scene = nullptr;
 

+ 8 - 1
Gems/EMotionFX/Code/Tests/ActorComponentBusTests.cpp

@@ -65,7 +65,7 @@ namespace EMotionFX
         : public EntityComponentFixture
     {
     public:
-        void SetUp()
+        void SetUp() override
         {
             EntityComponentFixture::SetUp();
 
@@ -85,6 +85,13 @@ namespace EMotionFX
 
             m_actorComponent->SetActorAsset(actorAsset);
         }
+
+        void TearDown() override
+        {
+            m_entity.reset();
+            EntityComponentFixture::TearDown();
+        }
+
         AZStd::unique_ptr<AZ::Entity> m_entity;
         Integration::ActorComponent* m_actorComponent;
         AzFramework::TransformComponent* m_transformComponent;

+ 1 - 0
Gems/EMotionFX/Code/Tests/ActorFixture.cpp

@@ -38,6 +38,7 @@ namespace EMotionFX
             m_actorInstance->Destroy();
             m_actorInstance = nullptr;
         }
+        m_actorAsset.Reset();
 
         GetEMotionFX().GetActorManager()->UnregisterAllActors();
         SystemComponentFixture::TearDown();

+ 6 - 0
Gems/EMotionFX/Code/Tests/AnimGraphActionCommandTests.cpp

@@ -37,6 +37,12 @@ namespace EMotionFX
             m_motionNodeAnimGraph->InitAfterLoading();
         }
 
+        void TearDown() override
+        {
+            m_motionNodeAnimGraph.reset();
+            AnimGraphFixture::TearDown();
+        }
+
         AZStd::unique_ptr<TwoMotionNodeAnimGraph> m_motionNodeAnimGraph;
         AnimGraphNode* m_stateA = nullptr;
         AnimGraphNode* m_stateB = nullptr;

+ 6 - 0
Gems/EMotionFX/Code/Tests/AnimGraphComponentBusTests.cpp

@@ -104,6 +104,12 @@ namespace EMotionFX
             m_animGraphComponent->OnAssetReady(motionSetAsset);
         }
 
+        void TearDown() override
+        {
+            m_entity.reset();
+            EntityComponentFixture::TearDown();
+        }
+
         void ActivateEntity()
         {
             // Set the actor asset and create the actor instance.

+ 37 - 1
Gems/EMotionFX/Code/Tests/AnimGraphCopyPasteTests.cpp

@@ -66,6 +66,18 @@ namespace EMotionFX
                 && (paramterActionA->GetParameterName() == paramterActionB->GetParameterName());
         }
 
+        void TearDown() override
+        {
+            if (m_animGraphInstance)
+            {
+                m_animGraphInstance->Destroy();
+                m_animGraphInstance = nullptr;
+            }
+            m_motionNodeAnimGraph.reset();
+
+            AnimGraphFixture::TearDown();
+        }
+
         AZStd::unique_ptr<TwoMotionNodeAnimGraph> m_motionNodeAnimGraph;
         AnimGraphNode* m_stateA = nullptr;
         AnimGraphNode* m_stateB = nullptr;
@@ -207,6 +219,19 @@ namespace EMotionFX
             }
         }
 
+        void TearDown() override
+        {
+            if (m_animGraphInstance)
+            {
+                m_animGraphInstance->Destroy();
+                m_animGraphInstance = nullptr;
+            }
+            m_motionNodeAnimGraph.reset();
+
+            AnimGraphFixture::TearDown();
+        }
+
+
         AZStd::unique_ptr<TwoMotionNodeAnimGraph> m_motionNodeAnimGraph;
         AnimGraphNode* m_stateA = nullptr;
         AnimGraphNode* m_stateB = nullptr;
@@ -521,6 +546,18 @@ namespace EMotionFX
             m_motionNodeAnimGraph->InitAfterLoading();
         }
 
+        void TearDown() override
+        {
+            if (m_animGraphInstance)
+            {
+                m_animGraphInstance->Destroy();
+                m_animGraphInstance = nullptr;
+            }
+            m_motionNodeAnimGraph.reset();
+
+            AnimGraphFixture::TearDown();
+        }
+
     public:
         AZStd::unique_ptr<TwoMotionNodeAnimGraph> m_motionNodeAnimGraph;
         AnimGraphNode* m_stateA = nullptr;
@@ -660,7 +697,6 @@ namespace EMotionFX
         }
 
     public:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         AZStd::unique_ptr<EMotionFX::BlendTreeConnection> m_testConnection;
         AnimGraphBindPoseNode* m_bindPoseNodeA = nullptr;
         AnimGraphBindPoseNode* m_bindPoseNodeB = nullptr;

+ 4 - 0
Gems/EMotionFX/Code/Tests/AnimGraphFixture.cpp

@@ -102,6 +102,10 @@ namespace EMotionFX
             m_motionSet = nullptr;
         }
 
+        m_blendTreeAnimGraph.reset();
+        m_animGraph.reset();
+        m_actor.reset();
+
         SystemComponentFixture::TearDown();
         AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
     }

+ 2 - 0
Gems/EMotionFX/Code/Tests/AnimGraphFixture.h

@@ -98,5 +98,7 @@ namespace EMotionFX
         AnimGraphStateMachine* m_rootStateMachine = nullptr;
         AnimGraphInstance* m_animGraphInstance = nullptr;
         MotionSet* m_motionSet = nullptr;
+
+        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
     };
 }

+ 1 - 0
Gems/EMotionFX/Code/Tests/AnimGraphNetworkingBusTests.cpp

@@ -40,6 +40,7 @@ namespace EMotionFX
         void TearDown() override
         {
             m_entity->Deactivate();
+            m_entity.reset();
             EntityComponentFixture::TearDown();
         }
 

+ 0 - 1
Gems/EMotionFX/Code/Tests/AnimGraphNodeEventFilterTests.cpp

@@ -139,7 +139,6 @@ namespace EMotionFX
         }
 
     public:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         std::vector<AnimGraphMotionNode*> m_motionNodes;
         std::vector<Motion*> m_motions;
         BlendTree* m_blendTree = nullptr;

+ 0 - 1
Gems/EMotionFX/Code/Tests/AnimGraphNodeProcessingTests.cpp

@@ -107,7 +107,6 @@ namespace EMotionFX
         }
 
     public:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         std::vector<AnimGraphMotionNode*> m_motionNodes;
         BlendTree* m_blendTree = nullptr;
         BlendTreeFloatConstantNode* m_floatNode = nullptr;

+ 18 - 0
Gems/EMotionFX/Code/Tests/AnimGraphReferenceNodeTests.cpp

@@ -250,6 +250,12 @@ namespace EMotionFX
             return referenceAnimGraphAsset;
         }
 
+        void TearDown() override
+        {
+            m_referencedAsset.Release();
+            AnimGraphReferenceNodeWithAssetTests::TearDown();
+        }
+
         AZ::Data::Asset<Integration::AnimGraphAsset> m_referencedAsset;
         Parameter* m_parameter = nullptr;
         BlendTreeTransformNode* m_transformNode = nullptr;
@@ -297,6 +303,12 @@ namespace EMotionFX
             m_topLevelParameter = static_cast<ReferenceNodeWithParameterGraph*>(m_animGraph.get())->GetParameter();
         }
 
+        void TearDown() override
+        {
+            m_secondLevelAsset.Release();
+            AnimGraphFixture::TearDown();
+        }
+
         AZ::Data::Asset<Integration::AnimGraphAsset> m_secondLevelAsset{};
 
         Parameter* m_topLevelParameter = nullptr;
@@ -394,6 +406,12 @@ namespace EMotionFX
             return referenceAnimGraph;
         }
 
+        void TearDown() override
+        {
+            m_referenceNode->GetReferencedAnimGraphAsset().Release();
+            AnimGraphFixture::TearDown();
+        }
+
     public:
         AnimGraphReferenceNode* m_referenceNode = nullptr;
     };

+ 18 - 0
Gems/EMotionFX/Code/Tests/AnimGraphStateMachineInterruptionTests.cpp

@@ -120,6 +120,12 @@ namespace EMotionFX
         {
             m_animGraphInstance->RemoveEventHandler(m_eventHandler);
             delete m_eventHandler;
+            if (m_animGraphInstance)
+            {
+                m_animGraphInstance->Destroy();
+                m_animGraphInstance = nullptr;
+            }
+            m_motionNodeAnimGraph.reset();
 
             AnimGraphFixture::TearDown();
         }
@@ -448,6 +454,18 @@ namespace EMotionFX
             m_animGraphInstance = m_motionNodeAnimGraph->GetAnimGraphInstance(m_actorInstance, m_motionSet);
         }
 
+        void TearDown() override
+        {
+            if (m_animGraphInstance)
+            {
+                m_animGraphInstance->Destroy();
+                m_animGraphInstance = nullptr;
+            }
+            m_motionNodeAnimGraph.reset();
+
+            AnimGraphFixture::TearDown();
+        }
+
         AZStd::unique_ptr<TwoMotionNodeAnimGraph> m_motionNodeAnimGraph;
         AnimGraphStateTransition* m_transitionLeft = nullptr;
         AnimGraphStateTransition* m_transitionRight = nullptr;

+ 11 - 0
Gems/EMotionFX/Code/Tests/AnimGraphStateMachineSyncTests.cpp

@@ -91,6 +91,17 @@ namespace EMotionFX
             GetEMotionFX().Update(0.0f);
         }
 
+        void TearDown() override
+        {
+            if (m_animGraphInstance)
+            {
+                m_animGraphInstance->Destroy();
+                m_animGraphInstance = nullptr;
+            }
+            m_motionNodeAnimGraph.reset();
+            AnimGraphFixture::TearDown();
+        }
+
     public:
         AZStd::unique_ptr<TwoMotionNodeAnimGraph> m_motionNodeAnimGraph;
         AnimGraphMotionNode* m_stateA = nullptr;

+ 6 - 0
Gems/EMotionFX/Code/Tests/AnimGraphTransitionCommandTests.cpp

@@ -71,6 +71,12 @@ namespace EMotionFX
 
         void TearDown() override
         {
+            if (m_animGraphInstance)
+            {
+                m_animGraphInstance->Destroy();
+                m_animGraphInstance = nullptr;
+            }
+            m_motionNodeAnimGraph.reset();
             AnimGraphFixture::TearDown();
         }
 

+ 2 - 0
Gems/EMotionFX/Code/Tests/AnimGraphTransitionConditionFixture.cpp

@@ -100,6 +100,8 @@ namespace EMotionFX
             m_actorInstance->Destroy();
             m_actorInstance = nullptr;
         }
+        m_actor.reset();
+        m_animGraph.reset();
 
         SystemComponentFixture::TearDown();
     }

+ 2 - 0
Gems/EMotionFX/Code/Tests/AnimGraphTransitionFixture.cpp

@@ -99,6 +99,8 @@ namespace EMotionFX
         {
             m_actorInstance->Destroy();
         }
+        m_actor.reset();
+        m_animGraph.reset();
         SystemComponentFixture::TearDown();
     }
 } // namespace EMotionFX

+ 1 - 0
Gems/EMotionFX/Code/Tests/AutoSkeletonLODTests.cpp

@@ -94,6 +94,7 @@ namespace EMotionFX
                 m_actorInstance->Destroy();
                 m_actorInstance = nullptr;
             }
+            m_actor.reset();
 
             SystemComponentFixture::TearDown();
         }

+ 0 - 2
Gems/EMotionFX/Code/Tests/BlendTreeBlendNNodeTests.cpp

@@ -97,7 +97,6 @@ namespace EMotionFX
             }
         }
 
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         AZStd::vector<AnimGraphMotionNode*>* m_motionNodes = nullptr;
         BlendTreeBlendNNode* m_blendNNode = nullptr;
         BlendTree* m_blendTree = nullptr;
@@ -253,7 +252,6 @@ namespace EMotionFX
         }
 
     public:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         std::vector<AnimGraphMotionNode*> m_motionNodes;
         BlendTree* m_blendTree = nullptr;
         BlendTreeFloatConstantNode* m_floatNode = nullptr;

+ 0 - 1
Gems/EMotionFX/Code/Tests/BlendTreeFloatConditionNodeTests.cpp

@@ -82,7 +82,6 @@ namespace EMotionFX
         }
 
     protected:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         BlendTree* m_blendTree = nullptr;
         BlendTreeFloatConstantNode* m_floatConstantNodeOne = nullptr;
         BlendTreeFloatConstantNode* m_floatConstantNodeTwo = nullptr;

+ 0 - 1
Gems/EMotionFX/Code/Tests/BlendTreeFloatConstantNodeTests.cpp

@@ -57,7 +57,6 @@ namespace EMotionFX
         }
 
     protected:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         BlendTreeFloatConstantNode* m_floatConstantNode = nullptr;
         BlendTreeBlend2Node* m_blend2Node = nullptr;
         BlendTree* m_blendTree = nullptr;

+ 0 - 1
Gems/EMotionFX/Code/Tests/BlendTreeFloatMath1NodeTests.cpp

@@ -148,7 +148,6 @@ namespace EMotionFX
         }
 
     protected:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         BlendTree* m_blendTree = nullptr;
         BlendTreeFloatMath1Node* m_floatMath1Node = nullptr;
         BlendTreeFloatMath1NodeTestData m_param;

+ 0 - 1
Gems/EMotionFX/Code/Tests/BlendTreeMaskNodeTests.cpp

@@ -203,7 +203,6 @@ namespace EMotionFX
         }
 
     public:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         BlendTreeMaskNode* m_maskNode = nullptr;
         BlendTreeTestInputNode* m_basePoseNode = nullptr;
         const size_t m_basePosePosValue = 100; // Special identification value for the base pose to easily distinguish it from the mask indices.

+ 0 - 1
Gems/EMotionFX/Code/Tests/BlendTreeMotionFrameNodeTests.cpp

@@ -102,7 +102,6 @@ namespace EMotionFX
         }
 
     public:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         float m_motionDuration = 1.0f;
         AnimGraphMotionNode* m_motionNode = nullptr;
         BlendTreeMotionFrameNode* m_motionFrameNode = nullptr;

+ 0 - 1
Gems/EMotionFX/Code/Tests/BlendTreeRagdollNodeTests.cpp

@@ -61,7 +61,6 @@ namespace EMotionFX
             m_animGraphInstance = m_blendTreeAnimGraph->GetAnimGraphInstance(m_actorInstance, m_motionSet);
         }
 
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         BlendTreeRagdollNode* m_ragdollNode = nullptr;
     };
 

+ 0 - 1
Gems/EMotionFX/Code/Tests/BlendTreeRangeRemapperNodeTests.cpp

@@ -86,7 +86,6 @@ namespace EMotionFX
         }
 
     protected:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         BlendTree* m_blendTree = nullptr;
         BlendTreeRangeRemapperNode* m_rangeRemapperNode = nullptr;
         BlendTreeFloatConstantNode* m_floatConstantNode = nullptr;

+ 0 - 1
Gems/EMotionFX/Code/Tests/BlendTreeRotationLimitNodeTests.cpp

@@ -74,7 +74,6 @@ namespace EMotionFX
             m_animGraphInstance = m_blendTreeAnimGraph->GetAnimGraphInstance(m_actorInstance, m_motionSet);
         }
 
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         BlendTree* m_blendTree = nullptr;
         BlendTreeGetTransformNode* m_getTransformNode = nullptr;
         BlendTreeRotationMath2Node* m_rotationMathNode = nullptr;

+ 0 - 1
Gems/EMotionFX/Code/Tests/BlendTreeRotationMath2NodeTests.cpp

@@ -62,7 +62,6 @@ namespace EMotionFX
             m_animGraphInstance = m_blendTreeAnimGraph->GetAnimGraphInstance(m_actorInstance, m_motionSet);
         }
 
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         BlendTree* m_blendTree = nullptr;
         BlendTreeGetTransformNode* m_getTransformNode = nullptr;
         BlendTreeRotationMath2Node* m_rotationMathNode = nullptr;

+ 0 - 1
Gems/EMotionFX/Code/Tests/BlendTreeTransformNodeTests.cpp

@@ -63,7 +63,6 @@ namespace EMotionFX
             m_animGraphInstance = m_blendTreeAnimGraph->GetAnimGraphInstance(m_actorInstance, m_motionSet);
         }
 
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         BlendTree* m_blendTree;
         BlendTreeTransformNode* m_transformNode;
     };

+ 0 - 1
Gems/EMotionFX/Code/Tests/BoolLogicNodeTests.cpp

@@ -132,7 +132,6 @@ namespace EMotionFX
             return result;
         }
 
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         AZStd::vector<AnimGraphMotionNode*>* m_motionNodes = nullptr;
         BlendTreeBlendNNode* m_blendNNode = nullptr;
         BlendTree* m_blendTree = nullptr;

+ 1 - 0
Gems/EMotionFX/Code/Tests/JackGraphFixture.cpp

@@ -69,6 +69,7 @@ namespace EMotionFX
         m_motionSet = nullptr;
 
         m_animGraph.reset();
+        m_actor.reset();
 
         SystemComponentFixture::TearDown();
     }

+ 1 - 0
Gems/EMotionFX/Code/Tests/MorphSkinAttachmentTests.cpp

@@ -112,6 +112,7 @@ namespace EMotionFX
         void TearDown() override
         {
             m_attachmentActorInstance->Destroy();
+            m_attachmentActor.reset();
 
             JackGraphFixture::TearDown();
         }

+ 1 - 0
Gems/EMotionFX/Code/Tests/MotionEventTrackTests.cpp

@@ -170,6 +170,7 @@ namespace EMotionFX
             m_motionInstance->Destroy();
             m_motion->Destroy();
             m_actorInstance->Destroy();
+            m_actor.reset();
             SystemComponentFixture::TearDown();
         }
 

+ 2 - 1
Gems/EMotionFX/Code/Tests/MotionExtractionBusTests.cpp

@@ -107,10 +107,11 @@ namespace EMotionFX
 
         void TearDown() override
         {
-            EntityComponentFixture::TearDown();
             m_motionSet->Clear();
             m_motion->Destroy();
             delete m_motionSet;
+            m_entity.reset();
+            EntityComponentFixture::TearDown();
         }
 
     public:

+ 1 - 0
Gems/EMotionFX/Code/Tests/MotionInstanceTests.cpp

@@ -92,6 +92,7 @@ namespace EMotionFX
             {
                 m_actorInstance->Destroy();
             }
+            m_actor.reset();
 
             SystemComponentFixture::TearDown();
         }

+ 1 - 0
Gems/EMotionFX/Code/Tests/PoseTests.cpp

@@ -69,6 +69,7 @@ namespace EMotionFX
         void TearDown() override
         {
             m_actorInstance->Destroy();
+            m_actor.reset();
             SystemComponentFixture::TearDown();
         }
 

+ 1 - 0
Gems/EMotionFX/Code/Tests/ProvidesUI/AnimGraph/AnimGraphActivateTests.cpp

@@ -96,6 +96,7 @@ namespace EMotionFX
             GetEMotionFX().GetActorManager()->UnregisterAllActors();
             QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
             delete m_animGraph;
+            m_actorAsset.Reset();
             UIFixture::TearDown();
         }
 

+ 0 - 1
Gems/EMotionFX/Code/Tests/QuaternionParameterTests.cpp

@@ -77,7 +77,6 @@ namespace EMotionFX
         };
 
     protected:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         AZ::Quaternion m_param;
         BlendTree* m_blendTree = nullptr;
         BlendTreeParameterNode* m_paramNode = nullptr;

+ 2 - 1
Gems/EMotionFX/Code/Tests/SimpleMotionComponentBusTests.cpp

@@ -77,10 +77,11 @@ namespace EMotionFX
 
         void TearDown() override
         {
-            EntityComponentFixture::TearDown();
             m_motionSet->Clear();
             // m_motion->Destroy(); // Will be destroyed through the m_entity destructor
             delete m_motionSet;
+            m_entity.reset();
+            EntityComponentFixture::TearDown();
         }
 
         AZ::Data::AssetId m_motionAssetId;

+ 6 - 0
Gems/EMotionFX/Code/Tests/SkeletonNodeSearchTests.cpp

@@ -33,6 +33,12 @@ namespace EMotionFX
             m_skeleton = m_actor->GetSkeleton();
         }
 
+        void TearDown() override
+        {
+            m_actor.reset();
+            SystemComponentFixture::TearDown();
+        }
+
     public:
         AZStd::unique_ptr<SimpleJointChainActor> m_actor = nullptr;
         Skeleton* m_skeleton = nullptr;

+ 0 - 1
Gems/EMotionFX/Code/Tests/SyncingSystemTests.cpp

@@ -129,7 +129,6 @@ namespace EMotionFX
         }
 
     public:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         AnimGraphObject::ESyncMode m_syncMode;
         AnimGraphMotionNode* m_motionNodeA = nullptr;
         AnimGraphMotionNode* m_motionNodeB = nullptr;

+ 2 - 0
Gems/EMotionFX/Code/Tests/SystemComponentFixture.h

@@ -148,6 +148,8 @@ namespace EMotionFX
             // Clear the queue of messages from unit tests on our buses
             EMotionFX::Integration::ActorNotificationBus::ClearQueuedEvents();
 
+            m_app.Stop();
+
             UnitTest::LeakDetectionFixture::TearDown();
         }
 

+ 1 - 0
Gems/EMotionFX/Code/Tests/UI/CanAddSimulatedObject.cpp

@@ -43,6 +43,7 @@ namespace EMotionFX
         void TearDown() override
         {
             QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+            m_actorAsset.Reset();
             UIFixture::TearDown();
         }
 

+ 0 - 1
Gems/EMotionFX/Code/Tests/Vector2ToVector3CompatibilityTests.cpp

@@ -98,7 +98,6 @@ namespace EMotionFX
             m_animGraphInstance = m_blendTreeAnimGraph->GetAnimGraphInstance(m_actorInstance, m_motionSet);
         }
 
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         BlendTreeBlendNNode* m_blendNNode = nullptr;
         BlendTree* m_blendTree = nullptr;
         BlendTreeVector3DecomposeNode* m_vector3DecomposeNode = nullptr;

+ 0 - 1
Gems/EMotionFX/Code/Tests/Vector3ParameterTests.cpp

@@ -77,7 +77,6 @@ namespace EMotionFX
         }
 
     protected:
-        AZStd::unique_ptr<OneBlendTreeNodeAnimGraph> m_blendTreeAnimGraph;
         BlendTree* m_blendTree = nullptr;
         BlendTreeParameterNode* m_paramNode = nullptr;
         BlendTreeTwoLinkIKNode* m_twoLinkIKNode = nullptr;

+ 1 - 8
Gems/NvCloth/Code/Source/Pipeline/SceneAPIExt/ClothRuleBehavior.cpp

@@ -156,14 +156,7 @@ namespace NvCloth
                 if (!meshNodeName.empty())
                 {
                     const auto& selectedNodesList = group.GetSceneNodeSelectionList();
-                    for (size_t i = 0; i < selectedNodesList.GetSelectedNodeCount(); ++i)
-                    {
-                        if (meshNodeName == selectedNodesList.GetSelectedNode(i))
-                        {
-                            foundMeshNode = true;
-                            break;
-                        }
-                    }
+                    foundMeshNode = selectedNodesList.IsSelectedNode(meshNodeName);
                 }
 
                 // Mesh node selected in the cloth rule is not part of the list of selected nodes anymore, set the default value.

+ 141 - 112
Gems/PhysX/Code/Source/Pipeline/MeshExporter.cpp

@@ -296,88 +296,104 @@ namespace PhysX
                 const AZ::SceneAPI::Containers::SceneGraph& sceneGraph)
             {
                 AssetMaterialsData assetMaterialData;
+                bool errorFound = false;
 
                 const auto& sceneNodeSelectionList = meshGroup.GetSceneNodeSelectionList();
-                size_t selectedNodeCount = sceneNodeSelectionList.GetSelectedNodeCount();
-
-                for (size_t index = 0; index < selectedNodeCount; index++)
-                {
-                    AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = sceneGraph.Find(sceneNodeSelectionList.GetSelectedNode(index));
-                    if (!nodeIndex.IsValid())
-                    {
-                        AZ_TracePrintf(
-                            AZ::SceneAPI::Utilities::WarningWindow,
-                            "Node '%s' was not found in the scene graph.",
-                            sceneNodeSelectionList.GetSelectedNode(index).c_str()
-                        );
-                        continue;
-                    }
-                    auto nodeMesh = azrtti_cast<const AZ::SceneAPI::DataTypes::IMeshData*>(*sceneGraph.ConvertToStorageIterator(nodeIndex));
-                    if (!nodeMesh)
+                sceneNodeSelectionList.EnumerateSelectedNodes(
+                    [&](const AZStd::string& name)
                     {
-                        continue;
-                    }
+                        AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = sceneGraph.Find(name);
+                        if (!nodeIndex.IsValid())
+                        {
+                            AZ_TracePrintf(
+                                AZ::SceneAPI::Utilities::WarningWindow,
+                                "Node '%s' was not found in the scene graph.",
+                                name.c_str());
+                            return true;
+                        }
+                        auto nodeMesh =
+                            azrtti_cast<const AZ::SceneAPI::DataTypes::IMeshData*>(*sceneGraph.ConvertToStorageIterator(nodeIndex));
+                        if (!nodeMesh)
+                        {
+                            return true;
+                        }
 
-                    AZStd::string_view nodeName = sceneGraph.GetNodeName(nodeIndex).GetName();
+                        AZStd::string_view nodeName = sceneGraph.GetNodeName(nodeIndex).GetName();
 
-                    const AZStd::vector<AZStd::string> localSourceSceneMaterialsList = GenerateLocalNodeMaterialMap(sceneGraph, nodeIndex);
-                    if (localSourceSceneMaterialsList.empty())
-                    {
-                        AZ_TracePrintf(
-                            AZ::SceneAPI::Utilities::WarningWindow,
-                            "Node '%.*s' does not have any material assigned to it. Material '%s' will be used.",
-                            AZ_STRING_ARG(nodeName), DefaultMaterialName
-                        );
-                    }
+                        const AZStd::vector<AZStd::string> localSourceSceneMaterialsList =
+                            GenerateLocalNodeMaterialMap(sceneGraph, nodeIndex);
+                        if (localSourceSceneMaterialsList.empty())
+                        {
+                            AZ_TracePrintf(
+                                AZ::SceneAPI::Utilities::WarningWindow,
+                                "Node '%.*s' does not have any material assigned to it. Material '%s' will be used.",
+                                AZ_STRING_ARG(nodeName),
+                                DefaultMaterialName);
+                        }
 
-                    const AZ::u32 faceCount = nodeMesh->GetFaceCount();
+                        const AZ::u32 faceCount = nodeMesh->GetFaceCount();
 
-                    assetMaterialData.m_nodesToPerFaceMaterialIndices.emplace(nodeName, AZStd::vector<AZ::u16>(faceCount));
+                        assetMaterialData.m_nodesToPerFaceMaterialIndices.emplace(nodeName, AZStd::vector<AZ::u16>(faceCount));
 
-                    // Convex and primitive methods can only have 1 material per node.
-                    const bool limitToOneMaterial = meshGroup.GetExportAsConvex() || meshGroup.GetExportAsPrimitive();
-                    AZStd::string firstMaterial;
-                    AZStd::set<AZStd::string> nodeMaterials;
+                        // Convex and primitive methods can only have 1 material per node.
+                        const bool limitToOneMaterial = meshGroup.GetExportAsConvex() || meshGroup.GetExportAsPrimitive();
+                        AZStd::string firstMaterial;
+                        AZStd::set<AZStd::string> nodeMaterials;
 
-                    for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex)
-                    {
-                        AZStd::string materialName = DefaultMaterialName;
-                        if (!localSourceSceneMaterialsList.empty())
+                        for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex)
                         {
-                            const int materialId = nodeMesh->GetFaceMaterialId(faceIndex);
-                            if (materialId >= localSourceSceneMaterialsList.size())
+                            AZStd::string materialName = DefaultMaterialName;
+                            if (!localSourceSceneMaterialsList.empty())
                             {
-                                AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow,
-                                    "materialId %d for face %d is out of bound for localSourceSceneMaterialsList (size %d).",
-                                    materialId, faceIndex, localSourceSceneMaterialsList.size());
-
-                                return AZStd::nullopt;
-                            }
+                                const int materialId = nodeMesh->GetFaceMaterialId(faceIndex);
+                                if (materialId >= localSourceSceneMaterialsList.size())
+                                {
+                                    AZ_TracePrintf(
+                                        AZ::SceneAPI::Utilities::ErrorWindow,
+                                        "materialId %d for face %d is out of bound for localSourceSceneMaterialsList (size %d).",
+                                        materialId,
+                                        faceIndex,
+                                        localSourceSceneMaterialsList.size());
+
+                                    errorFound = true;
+                                    return false;
+                                }
 
-                            materialName = localSourceSceneMaterialsList[materialId];
+                                materialName = localSourceSceneMaterialsList[materialId];
 
-                            // Use the first material found in the mesh when it has to be limited to one.
-                            if (limitToOneMaterial)
-                            {
-                                nodeMaterials.insert(materialName);
-                                if (firstMaterial.empty())
+                                // Use the first material found in the mesh when it has to be limited to one.
+                                if (limitToOneMaterial)
                                 {
-                                    firstMaterial = materialName;
+                                    nodeMaterials.insert(materialName);
+                                    if (firstMaterial.empty())
+                                    {
+                                        firstMaterial = materialName;
+                                    }
+                                    materialName = firstMaterial;
                                 }
-                                materialName = firstMaterial;
                             }
+
+                            const AZ::u16 materialIndex = InsertMaterialIndexByName(materialName, assetMaterialData);
+                            assetMaterialData.m_nodesToPerFaceMaterialIndices[nodeName][faceIndex] = materialIndex;
                         }
 
-                        const AZ::u16 materialIndex = InsertMaterialIndexByName(materialName, assetMaterialData);
-                        assetMaterialData.m_nodesToPerFaceMaterialIndices[nodeName][faceIndex] = materialIndex;
-                    }
+                        if (limitToOneMaterial && nodeMaterials.size() > 1)
+                        {
+                            AZ_TracePrintf(
+                                AZ::SceneAPI::Utilities::WarningWindow,
+                                "Node '%s' has %d materials, but cooking methods Convex and Primitive support one material per node. The "
+                                "first material '%s' will be used.",
+                                name.c_str(),
+                                nodeMaterials.size(),
+                                firstMaterial.c_str());
+                        }
 
-                    if (limitToOneMaterial && nodeMaterials.size() > 1)
-                    {
-                        AZ_TracePrintf(AZ::SceneAPI::Utilities::WarningWindow,
-                            "Node '%s' has %d materials, but cooking methods Convex and Primitive support one material per node. The first material '%s' will be used.",
-                            sceneNodeSelectionList.GetSelectedNode(index).c_str(), nodeMaterials.size(), firstMaterial.c_str());
-                    }
+                        return true;
+                    });
+
+                if (errorFound)
+                {
+                    return AZStd::nullopt;
                 }
 
                 return assetMaterialData;
@@ -843,67 +859,80 @@ namespace PhysX
                     coordSysConverter = coordinateSystemRule->GetCoordinateSystemConverter();
                 }
 
-                for (size_t index = 0; index < selectedNodeCount; index++)
-                {
-                    AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = graph.Find(sceneNodeSelectionList.GetSelectedNode(index));
-                    auto nodeMesh = azrtti_cast<const AZ::SceneAPI::DataTypes::IMeshData*>(*graph.ConvertToStorageIterator(nodeIndex));
+                SceneEvents::ProcessingResult enumerationResult = SceneEvents::ProcessingResult::Success;
 
-                    if (!nodeMesh)
+                sceneNodeSelectionList.EnumerateSelectedNodes(
+                    [&](const AZStd::string& name)
                     {
-                        continue;
-                    }
+                        AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = graph.Find(name);
+                        auto nodeMesh = azrtti_cast<const AZ::SceneAPI::DataTypes::IMeshData*>(*graph.ConvertToStorageIterator(nodeIndex));
 
-                    const AZ::SceneAPI::Containers::SceneGraph::Name& nodeName = graph.GetNodeName(nodeIndex);
+                        if (!nodeMesh)
+                        {
+                            return true;
+                        }
 
-                    // CoordinateSystemConverter covers the simple transformations of CoordinateSystemRule and
-                    // DetermineWorldTransform function covers the advanced mode of CoordinateSystemRule.
-                    const AZ::SceneAPI::DataTypes::MatrixType worldTransform = coordSysConverter.ConvertMatrix3x4(
-                        AZ::SceneAPI::Utilities::DetermineWorldTransform(scene, nodeIndex, pxMeshGroup.GetRuleContainerConst()));
+                        const AZ::SceneAPI::Containers::SceneGraph::Name& nodeName = graph.GetNodeName(nodeIndex);
 
-                    NodeCollisionGeomExportData nodeExportData;
-                    nodeExportData.m_nodeName = nodeName.GetName();
+                        // CoordinateSystemConverter covers the simple transformations of CoordinateSystemRule and
+                        // DetermineWorldTransform function covers the advanced mode of CoordinateSystemRule.
+                        const AZ::SceneAPI::DataTypes::MatrixType worldTransform = coordSysConverter.ConvertMatrix3x4(
+                            AZ::SceneAPI::Utilities::DetermineWorldTransform(scene, nodeIndex, pxMeshGroup.GetRuleContainerConst()));
 
-                    const AZ::u32 vertexCount = nodeMesh->GetVertexCount();
-                    const AZ::u32 faceCount = nodeMesh->GetFaceCount();
+                        NodeCollisionGeomExportData nodeExportData;
+                        nodeExportData.m_nodeName = nodeName.GetName();
 
-                    nodeExportData.m_vertices.resize(vertexCount);
+                        const AZ::u32 vertexCount = nodeMesh->GetVertexCount();
+                        const AZ::u32 faceCount = nodeMesh->GetFaceCount();
 
-                    for (AZ::u32 vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
-                    {
-                        AZ::Vector3 pos = nodeMesh->GetPosition(vertexIndex);
-                        pos = worldTransform * pos;
-                        nodeExportData.m_vertices[vertexIndex] = AZVec3ToLYVec3(pos);
-                    }
+                        nodeExportData.m_vertices.resize(vertexCount);
 
-                    nodeExportData.m_indices.resize(faceCount * 3);
+                        for (AZ::u32 vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
+                        {
+                            AZ::Vector3 pos = nodeMesh->GetPosition(vertexIndex);
+                            pos = worldTransform * pos;
+                            nodeExportData.m_vertices[vertexIndex] = AZVec3ToLYVec3(pos);
+                        }
 
-                    nodeExportData.m_perFaceMaterialIndices = assetMaterialData->m_nodesToPerFaceMaterialIndices[nodeExportData.m_nodeName];
-                    if (nodeExportData.m_perFaceMaterialIndices.size() != faceCount)
-                    {
-                        AZ_TracePrintf(
-                            AZ::SceneAPI::Utilities::WarningWindow,
-                            "Node '%s' material information face count %d does not match the node's %d.",
-                            nodeExportData.m_nodeName.c_str(), nodeExportData.m_perFaceMaterialIndices.size(), faceCount
-                        );
-                        return SceneEvents::ProcessingResult::Failure;
-                    }
+                        nodeExportData.m_indices.resize(faceCount * 3);
 
-                    for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex)
-                    {
-                        const AZ::SceneAPI::DataTypes::IMeshData::Face& face = nodeMesh->GetFaceInfo(faceIndex);
-                        nodeExportData.m_indices[faceIndex * 3] = face.vertexIndex[0];
-                        nodeExportData.m_indices[faceIndex * 3 + 1] = face.vertexIndex[1];
-                        nodeExportData.m_indices[faceIndex * 3 + 2] = face.vertexIndex[2];
-                    }
+                        nodeExportData.m_perFaceMaterialIndices =
+                            assetMaterialData->m_nodesToPerFaceMaterialIndices[nodeExportData.m_nodeName];
+                        if (nodeExportData.m_perFaceMaterialIndices.size() != faceCount)
+                        {
+                            AZ_TracePrintf(
+                                AZ::SceneAPI::Utilities::WarningWindow,
+                                "Node '%s' material information face count %d does not match the node's %d.",
+                                nodeExportData.m_nodeName.c_str(),
+                                nodeExportData.m_perFaceMaterialIndices.size(),
+                                faceCount);
+                            enumerationResult = SceneEvents::ProcessingResult::Failure;
+                            return false;
+                        }
 
-                    if (pxMeshGroup.GetDecomposeMeshes())
-                    {
-                        DecomposeAndAppendMeshes(decomposer, vhacdParams, totalExportData, nodeExportData);
-                    }
-                    else
-                    {
-                        totalExportData.emplace_back(AZStd::move(nodeExportData));
-                    }
+                        for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex)
+                        {
+                            const AZ::SceneAPI::DataTypes::IMeshData::Face& face = nodeMesh->GetFaceInfo(faceIndex);
+                            nodeExportData.m_indices[faceIndex * 3] = face.vertexIndex[0];
+                            nodeExportData.m_indices[faceIndex * 3 + 1] = face.vertexIndex[1];
+                            nodeExportData.m_indices[faceIndex * 3 + 2] = face.vertexIndex[2];
+                        }
+
+                        if (pxMeshGroup.GetDecomposeMeshes())
+                        {
+                            DecomposeAndAppendMeshes(decomposer, vhacdParams, totalExportData, nodeExportData);
+                        }
+                        else
+                        {
+                            totalExportData.emplace_back(AZStd::move(nodeExportData));
+                        }
+
+                        return true;
+                    });
+
+                if (enumerationResult == SceneEvents::ProcessingResult::Failure)
+                {
+                    return enumerationResult;
                 }
 
                 // Merge triangle meshes if there's more than 1

+ 6 - 4
Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp

@@ -328,10 +328,12 @@ namespace AZ::SceneGenerationComponents
 
             const auto addSelectionListToMap = [&selectedNodes](const IMeshGroup& meshGroup, const SceneAPI::DataTypes::ISceneNodeSelectionList& selectionList)
             {
-                for (size_t selectedNodeIndex = 0; selectedNodeIndex < selectionList.GetSelectedNodeCount(); ++selectedNodeIndex)
-                {
-                    selectedNodes[&meshGroup].emplace_back(selectionList.GetSelectedNode(selectedNodeIndex));
-                }
+                selectionList.EnumerateSelectedNodes(
+                    [&selectedNodes, &meshGroup](const AZStd::string& name)
+                    {
+                        selectedNodes[&meshGroup].emplace_back(name);
+                        return true;
+                    });
             };
 
             for (const IMeshGroup& meshGroup : meshGroups)