Browse Source

Motion Matching: Debug visualization improvements

* Moved reposibility from the instance to the system component to render debug visualizations. Why? To make sure all motion extraction deltas got applied to the character already and avoid any mismatches between last and current frame (resulting in visual jittering).
* Added frame database stats to the ImGui monitor.
* Switched ImGuiMonitor from internal histogram group to the now shared version in LYImGuiUtils.
* Added a new debug draw bus that the motion matching instance hooks to, so that the system component can control when to render debug visualizations.
* Added class description for the motion matching instance.

Signed-off-by: Benjamin Jillich <[email protected]>
Benjamin Jillich 3 years ago
parent
commit
1a23dc7b93

+ 12 - 0
Gems/MotionMatching/Code/Include/MotionMatching/MotionMatchingBus.h

@@ -11,8 +11,20 @@
 #include <AzCore/EBus/EBus.h>
 #include <AzCore/Interface/Interface.h>
 
+#include <AzFramework/Entity/EntityDebugDisplayBus.h>
+
 namespace EMotionFX::MotionMatching
 {
+    class DebugDrawRequests
+        : public AZ::EBusTraits
+    {
+    public:
+        AZ_RTTI(DebugDrawRequests, "{7BBA4249-EC00-445C-8A0C-4472841049C3}");
+
+        virtual void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay) = 0;
+    };
+    using DebugDrawRequestBus = AZ::EBus<DebugDrawRequests>;
+
     class MotionMatchingRequests
     {
     public:

+ 0 - 2
Gems/MotionMatching/Code/Source/BlendTreeMotionMatchNode.cpp

@@ -297,8 +297,6 @@ namespace EMotionFX::MotionMatching
             ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::PushPerformanceHistogramValue, "Output", m_outputTimeInMs);
 #endif
         }
-
-        instance->DebugDraw();
     }
 
     AZ::Crc32 BlendTreeMotionMatchNode::GetTrajectoryPathSettingsVisibility() const

+ 34 - 72
Gems/MotionMatching/Code/Source/ImGuiMonitor.cpp

@@ -16,10 +16,11 @@ namespace EMotionFX::MotionMatching
 
     ImGuiMonitor::ImGuiMonitor()
     {
-        m_performanceStats.m_name = "Performance Statistics";
+        m_performanceStats.SetName("Performance Statistics");
+        m_performanceStats.SetHistogramBinCount(500);
 
-        m_featureCosts.m_name = "Feature Costs";
-        m_featureCosts.m_histogramContainerCount = 100;
+        m_featureCosts.SetName("Feature Costs");
+        m_featureCosts.SetHistogramBinCount(100);
 
         ImGui::ImGuiUpdateListenerBus::Handler::BusConnect();
         ImGuiMonitorRequestBus::Handler::BusConnect();
@@ -40,18 +41,40 @@ namespace EMotionFX::MotionMatching
 
         if (ImGui::Begin("Motion Matching"))
         {
+            if (ImGui::CollapsingHeader("Motion Database", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
+            {
+                if (ImGui::BeginTable("MDB", 2))
+                {
+                    ImGui::TableNextColumn(); ImGui::Text("Memory Usage: %.2f MB", m_frameDatabaseInfo.m_memoryUsedInBytes / 1024.0f / 1024.0f);
+                    ImGui::TableNextColumn(); ImGui::Text("Motion Data: %.0f minutes", m_frameDatabaseInfo.m_motionDataInSeconds / 60.0f);
+                    ImGui::TableNextColumn(); ImGui::Text("Num Frames: %zu", m_frameDatabaseInfo.m_numFrames);
+                    ImGui::TableNextColumn(); ImGui::Text("Num Motions: %zu", m_frameDatabaseInfo.m_numMotions);
+                    ImGui::EndTable();
+                }
+            }
+
             if (ImGui::CollapsingHeader("Feature Matrix", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
             {
-                ImGui::Text("Memory Usage: %.2f MB", m_featureMatrixMemoryUsageInBytes / 1024.0f / 1024.0f);
-                ImGui::Text("Num Frames: %zu", m_featureMatrixNumFrames);
-                ImGui::Text("Num Feature Components: %zu", m_featureMatrixNumComponents);
+                if (ImGui::BeginTable("FM", 2))
+                {
+                    ImGui::TableNextColumn(); ImGui::Text("Memory Usage: %.2f MB", m_featurMatrixInfo.m_memoryUsedInBytes / 1024.0f / 1024.0f);
+                    ImGui::TableNextColumn();
+                    ImGui::TableNextColumn(); ImGui::Text("Num Frames: %zu", m_featurMatrixInfo.m_numFrames);
+                    ImGui::TableNextColumn(); ImGui::Text("Num Feature Components: %zu", m_featurMatrixInfo.m_numDimensions);
+                    ImGui::EndTable();
+                }
             }
 
             if (ImGui::CollapsingHeader("Kd-Tree", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
             {
-                ImGui::Text("Memory Usage: %.2f MB", m_kdTreeMemoryUsageInBytes / 1024.0f / 1024.0f);
-                ImGui::Text("Num Nodes: %zu", m_kdTreeNumNodes);
-                ImGui::Text("Num Dimensions: %zu", m_kdTreeNumDimensions);
+                if (ImGui::BeginTable("KDT", 2))
+                {
+                    ImGui::TableNextColumn(); ImGui::Text("Memory Usage: %.2f MB", m_kdTreeInfo.m_memoryUsedInBytes / 1024.0f / 1024.0f);
+                    ImGui::TableNextColumn();
+                    ImGui::TableNextColumn(); ImGui::Text("Num Nodes: %zu", m_kdTreeInfo.m_numNodes);
+                    ImGui::TableNextColumn(); ImGui::Text("Num Dimensions: %zu", m_kdTreeInfo.m_numDimensions);
+                    ImGui::EndTable();
+                }
             }
 
             m_performanceStats.OnImGuiUpdate();
@@ -63,8 +86,8 @@ namespace EMotionFX::MotionMatching
     {
         if (ImGui::BeginMenu("Motion Matching"))
         {
-            ImGui::MenuItem(m_performanceStats.m_name.c_str(), "", &m_performanceStats.m_show);
-            ImGui::MenuItem(m_featureCosts.m_name.c_str(), "", &m_featureCosts.m_show);
+            ImGui::MenuItem(m_performanceStats.GetName(), "", &m_performanceStats.m_show);
+            ImGui::MenuItem(m_featureCosts.GetName(), "", &m_featureCosts.m_show);
             ImGui::EndMenu();
         }
     }
@@ -78,67 +101,6 @@ namespace EMotionFX::MotionMatching
     {
         m_featureCosts.PushHistogramValue(costName, value, color);
     }
-
-    void ImGuiMonitor::HistogramGroup::PushHistogramValue(const char* valueName, float value, const AZ::Color& color)
-    {
-        auto iterator = m_histogramIndexByName.find(valueName);
-        if (iterator != m_histogramIndexByName.end())
-        {
-            ImGui::LYImGuiUtils::HistogramContainer& histogramContiner = m_histograms[iterator->second];
-            histogramContiner.PushValue(value);
-            histogramContiner.SetBarLineColor(ImColor(color.GetR(), color.GetG(), color.GetB(), color.GetA()));
-        }
-        else
-        {
-            ImGui::LYImGuiUtils::HistogramContainer newHistogram;
-            newHistogram.Init(/*histogramName=*/valueName,
-                /*containerCount=*/m_histogramContainerCount,
-                /*viewType=*/ImGui::LYImGuiUtils::HistogramContainer::ViewType::Histogram,
-                /*displayOverlays=*/true,
-                /*min=*/0.0f,
-                /*max=*/0.0f);
-
-            newHistogram.SetMoveDirection(ImGui::LYImGuiUtils::HistogramContainer::PushRightMoveLeft);
-            newHistogram.PushValue(value);
-
-            m_histogramIndexByName[valueName] = m_histograms.size();
-            m_histograms.push_back(newHistogram);
-        }
-    }
-
-    void ImGuiMonitor::HistogramGroup::OnImGuiUpdate()
-    {
-        if (!m_show)
-        {
-            return;
-        }
-
-        if (ImGui::CollapsingHeader(m_name.c_str(), ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
-        {
-            for (auto& histogram : m_histograms)
-            {
-                ImGui::BeginGroup();
-                {
-                    histogram.Draw(ImGui::GetColumnWidth() - 70, s_histogramHeight);
-
-                    ImGui::SameLine();
-
-                    ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0,0,0,255));
-                    {
-                        const ImColor color = histogram.GetBarLineColor();
-                        ImGui::PushStyleColor(ImGuiCol_Button, color.Value);
-                        {
-                            const AZStd::string valueString = AZStd::string::format("%.2f", histogram.GetLastValue());
-                            ImGui::Button(valueString.c_str());
-                        }
-                        ImGui::PopStyleColor();
-                    }
-                    ImGui::PopStyleColor();
-                }
-                ImGui::EndGroup();
-            }
-        }
-    }
 } // namespace EMotionFX::MotionMatching
 
 #endif // IMGUI_ENABLED

+ 9 - 33
Gems/MotionMatching/Code/Source/ImGuiMonitor.h

@@ -20,7 +20,7 @@
 #include <imgui/imgui.h>
 #include <ImGuiBus.h>
 #include <ImGuiMonitorBus.h>
-#include <LYImGuiUtils/HistogramContainer.h>
+#include <LYImGuiUtils/HistogramGroup.h>
 
 namespace EMotionFX::MotionMatching
 {
@@ -43,41 +43,17 @@ namespace EMotionFX::MotionMatching
         void PushPerformanceHistogramValue(const char* performanceMetricName, float value) override;
         void PushCostHistogramValue(const char* costName, float value, const AZ::Color& color) override;
 
-        void SetFeatureMatrixMemoryUsage(size_t sizeInBytes) override { m_featureMatrixMemoryUsageInBytes = sizeInBytes; }
-        void SetFeatureMatrixNumFrames(size_t numFrames) override { m_featureMatrixNumFrames = numFrames; }
-        void SetFeatureMatrixNumComponents(size_t numFeatureComponents) override { m_featureMatrixNumComponents = numFeatureComponents; }
-
-        void SetKdTreeMemoryUsage(size_t sizeInBytes) override { m_kdTreeMemoryUsageInBytes = sizeInBytes; }
-        void SetKdTreeNumNodes(size_t numNodes) override { m_kdTreeNumNodes = numNodes; }
-        void SetKdTreeNumDimensions(size_t numDimensions) override { m_kdTreeNumDimensions = numDimensions; }
+        void SetFrameDatabaseInfo(const ImGuiMonitorRequests::FrameDatabaseInfo& info) override { m_frameDatabaseInfo = info; }
+        void SetFeatureMatrixInfo(const ImGuiMonitorRequests::FeatureMatrixInfo& info) override { m_featurMatrixInfo = info; }
+        void SetKdTreeInfo(const ImGuiMonitorRequests::KdTreeInfo& info) override { m_kdTreeInfo = info; }
 
     private:
-        //! Named and sub-divided group containing several histograms.
-        struct HistogramGroup
-        {
-            void OnImGuiUpdate();
-            void PushHistogramValue(const char* valueName, float value, const AZ::Color& color);
-
-            bool m_show = true;
-            AZStd::string m_name;
-            using HistogramIndexByNames = AZStd::unordered_map<const char*, size_t>;
-            HistogramIndexByNames m_histogramIndexByName;
-            AZStd::vector<ImGui::LYImGuiUtils::HistogramContainer> m_histograms;
-            int m_histogramContainerCount = 500;
-
-            static constexpr float s_histogramHeight = 95.0f;
-        };
-
-        HistogramGroup m_performanceStats;
-        HistogramGroup m_featureCosts;
-
-        size_t m_featureMatrixMemoryUsageInBytes = 0;
-        size_t m_featureMatrixNumFrames = 0;
-        size_t m_featureMatrixNumComponents = 0;
+        ImGui::LYImGuiUtils::HistogramGroup m_performanceStats;
+        ImGui::LYImGuiUtils::HistogramGroup m_featureCosts;
 
-        size_t m_kdTreeMemoryUsageInBytes = 0;
-        size_t m_kdTreeNumNodes = 0;
-        size_t m_kdTreeNumDimensions = 0;
+        ImGuiMonitorRequests::FrameDatabaseInfo m_frameDatabaseInfo;
+        ImGuiMonitorRequests::FeatureMatrixInfo m_featurMatrixInfo;
+        ImGuiMonitorRequests::KdTreeInfo m_kdTreeInfo;
     };
 } // namespace EMotionFX::MotionMatching
 

+ 23 - 6
Gems/MotionMatching/Code/Source/ImGuiMonitorBus.h

@@ -25,13 +25,30 @@ namespace EMotionFX::MotionMatching
         virtual void PushPerformanceHistogramValue(const char* performanceMetricName, float value) = 0;
         virtual void PushCostHistogramValue(const char* costName, float value, const AZ::Color& color) = 0;
 
-        virtual void SetFeatureMatrixMemoryUsage(size_t sizeInBytes) = 0;
-        virtual void SetFeatureMatrixNumFrames(size_t numFrames) = 0;
-        virtual void SetFeatureMatrixNumComponents(size_t numFeatureComponents) = 0;
+        struct FrameDatabaseInfo
+        {
+            size_t m_memoryUsedInBytes = 0;
+            size_t m_numFrames;
+            size_t m_numMotions;
+            float m_motionDataInSeconds;
+        };
+        virtual void SetFrameDatabaseInfo(const FrameDatabaseInfo& info) = 0;
 
-        virtual void SetKdTreeMemoryUsage(size_t sizeInBytes) = 0;
-        virtual void SetKdTreeNumNodes(size_t numNodes) = 0;
-        virtual void SetKdTreeNumDimensions(size_t numDimensions) = 0;
+        struct FeatureMatrixInfo
+        {
+            size_t m_memoryUsedInBytes = 0;
+            size_t m_numFrames = 0;
+            size_t m_numDimensions = 0;
+        };
+        virtual void SetFeatureMatrixInfo(const FeatureMatrixInfo& info) = 0;
+
+        struct KdTreeInfo
+        {
+            size_t m_memoryUsedInBytes = 0;
+            size_t m_numNodes = 0;
+            size_t m_numDimensions = 0;
+        };
+        virtual void SetKdTreeInfo(const KdTreeInfo& info) = 0;
     };
     using ImGuiMonitorRequestBus = AZ::EBus<ImGuiMonitorRequests>;
 } // namespace EMotionFX::MotionMatching

+ 24 - 58
Gems/MotionMatching/Code/Source/MotionMatchingInstance.cpp

@@ -28,14 +28,14 @@
 #include <EMotionFX/Source/TransformData.h>
 #include <PoseDataJointVelocities.h>
 
-#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/ViewportPluginBus.h>
-
 namespace EMotionFX::MotionMatching
 {
     AZ_CLASS_ALLOCATOR_IMPL(MotionMatchingInstance, MotionMatchAllocator, 0)
 
     MotionMatchingInstance::~MotionMatchingInstance()
     {
+        DebugDrawRequestBus::Handler::BusDisconnect();
+
         if (m_motionInstance)
         {
             GetMotionInstancePool().Free(m_motionInstance);
@@ -58,6 +58,8 @@ namespace EMotionFX::MotionMatching
         AZ_Assert(settings.m_actorInstance, "The actor instance cannot be a nullptr.");
         AZ_Assert(settings.m_data, "The motion match data cannot be nullptr.");
 
+        DebugDrawRequestBus::Handler::BusConnect();
+
         // Update the cached pointer to the trajectory feature.
         const FeatureSchema& featureSchema = settings.m_data->GetFeatureSchema();
         for (Feature* feature : featureSchema.GetFeatures())
@@ -69,29 +71,6 @@ namespace EMotionFX::MotionMatching
             }
         }
 
-        // Debug display initialization.
-        const auto AddDebugDisplay = [=](AZ::s32 debugDisplayId)
-        {
-            if (debugDisplayId == -1)
-            {
-                return;
-            }
-
-            AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
-            AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, debugDisplayId);
-
-            AzFramework::DebugDisplayRequests* debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus);
-            if (debugDisplay)
-            {
-                m_debugDisplays.emplace_back(debugDisplay);
-            }
-        };
-        // Draw the debug visualizations to the Animation Editor as well as the LY Editor viewport.
-        AZ::s32 animationEditorViewportId = -1;
-        EMStudio::ViewportPluginRequestBus::BroadcastResult(animationEditorViewportId, &EMStudio::ViewportPluginRequestBus::Events::GetViewportId);
-        AddDebugDisplay(animationEditorViewportId);
-        AddDebugDisplay(AzFramework::g_defaultSceneEntityDebugDisplayId);
-
         m_actorInstance = settings.m_actorInstance;
         m_data = settings.m_data;
         if (settings.m_data->GetFrameDatabase().GetNumFrames() == 0)
@@ -123,30 +102,17 @@ namespace EMotionFX::MotionMatching
         m_queryFeatureValues.resize(numValuesInKdTree);
 
         // Initialize the trajectory history.
-        size_t rootJointIndex = m_actorInstance->GetActor()->GetMotionExtractionNodeIndex();
-        if (rootJointIndex == InvalidIndex32)
+        if (m_cachedTrajectoryFeature)
         {
-            rootJointIndex = 0;
-        }
-        m_trajectoryHistory.Init(*m_actorInstance->GetTransformData()->GetCurrentPose(),
-            rootJointIndex,
-            m_cachedTrajectoryFeature->GetFacingAxisDir(),
-            m_trajectorySecsToTrack);
-    }
-
-    void MotionMatchingInstance::DebugDraw()
-    {
-        if (m_data && !m_debugDisplays.empty())
-        {
-            for (AzFramework::DebugDisplayRequests* debugDisplay : m_debugDisplays)
+            size_t rootJointIndex = m_actorInstance->GetActor()->GetMotionExtractionNodeIndex();
+            if (rootJointIndex == InvalidIndex32)
             {
-                if (debugDisplay)
-                {
-                    const AZ::u32 prevState = debugDisplay->GetState();
-                    DebugDraw(*debugDisplay);
-                    debugDisplay->SetState(prevState);
-                }
+                rootJointIndex = 0;
             }
+            m_trajectoryHistory.Init(*m_actorInstance->GetTransformData()->GetCurrentPose(),
+                rootJointIndex,
+                m_cachedTrajectoryFeature->GetFacingAxisDir(),
+                m_trajectorySecsToTrack);
         }
     }
 
@@ -306,7 +272,7 @@ namespace EMotionFX::MotionMatching
     {
         AZ_PROFILE_SCOPE(Animation, "MotionMatchingInstance::Update");
 
-        if (!m_data)
+        if (!m_data || !m_motionInstance)
         {
             return;
         }
@@ -322,7 +288,7 @@ namespace EMotionFX::MotionMatching
         // Update the time. After this there is no sample for the updated time in the history as we're about to prepare this with the current update.
         m_trajectoryHistory.Update(timePassedInSeconds);
 
-        // Register the current actor instance position to the history data of the spline.
+        // Update the trajectory query control points.
         m_trajectoryQuery.Update(m_actorInstance,
             m_cachedTrajectoryFeature,
             m_trajectoryHistory,
@@ -371,8 +337,7 @@ namespace EMotionFX::MotionMatching
                 SamplePose(m_motionInstance->GetMotion(), m_queryPose, newMotionTime);
 
                 // Copy over the motion extraction joint transform from the current pose to the newly sampled pose.
-                // When sampling a motion, the motion extraction joint is in animation space, while we need the query pose to be in
-                // world space.
+                // When sampling a motion, the motion extraction joint is in animation space, while we need the query pose to be in world space.
                 // Note: This does not yet take the extraction delta from the current tick into account.
                 if (m_actorInstance->GetActor()->GetMotionExtractionNode())
                 {
@@ -433,16 +398,17 @@ namespace EMotionFX::MotionMatching
         // ImGui monitor
         {
 #ifdef IMGUI_ENABLED
-            const KdTree& kdTree = m_data->GetKdTree();
-            ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetKdTreeMemoryUsage, kdTree.CalcMemoryUsageInBytes());
-            ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetKdTreeNumNodes, kdTree.GetNumNodes());
-            ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetKdTreeNumDimensions, kdTree.GetNumDimensions());
-            // TODO: add memory usage for frame database
+            const FrameDatabase& frameDatabase = m_data->GetFrameDatabase();
+            ImGuiMonitorRequests::FrameDatabaseInfo frameDatabaseInfo{frameDatabase.CalcMemoryUsageInBytes(), frameDatabase.GetNumFrames(), frameDatabase.GetNumUsedMotions(), frameDatabase.GetNumFrames() / (float)frameDatabase.GetSampleRate()};
+            ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFrameDatabaseInfo, frameDatabaseInfo);
 
+            const KdTree& kdTree = m_data->GetKdTree();
+            ImGuiMonitorRequests::KdTreeInfo kdTreeInfo{kdTree.CalcMemoryUsageInBytes(), kdTree.GetNumNodes(), kdTree.GetNumDimensions()};
+            ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetKdTreeInfo, kdTreeInfo);
+            
             const FeatureMatrix& featureMatrix = m_data->GetFeatureMatrix();
-            ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFeatureMatrixMemoryUsage, featureMatrix.CalcMemoryUsageInBytes());
-            ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFeatureMatrixNumFrames, featureMatrix.rows());
-            ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFeatureMatrixNumComponents, featureMatrix.cols());
+            ImGuiMonitorRequests::FeatureMatrixInfo featureMatrixInfo{featureMatrix.CalcMemoryUsageInBytes(), static_cast<size_t>(featureMatrix.rows()), static_cast<size_t>(featureMatrix.cols())};
+            ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFeatureMatrixInfo, featureMatrixInfo);
 #endif
         }
     }

+ 15 - 10
Gems/MotionMatching/Code/Source/MotionMatchingInstance.h

@@ -19,6 +19,8 @@
 #include <TrajectoryHistory.h>
 #include <TrajectoryQuery.h>
 
+#include <MotionMatching/MotionMatchingBus.h>
+
 namespace AZ
 {
     class ReflectContext;
@@ -34,12 +36,17 @@ namespace EMotionFX::MotionMatching
 {
     class MotionMatchingData;
 
+    //! The instance is where everything comes together. It stores the trajectory history, the trajectory query along with the query vector, knows about the
+    //! last lowest cost frame frame index and stores the time of the animation that the instance is currently playing. It is responsible for motion extraction,
+    //! blending towards a new frame in the motion capture database in case the algorithm found a better matching frame and executes the actual search.
     class EMFX_API MotionMatchingInstance
+        : public DebugDrawRequestBus::Handler
     {
     public:
         AZ_RTTI(MotionMatchingInstance, "{1ED03AD8-0FB2-431B-AF01-02F7E930EB73}")
         AZ_CLASS_ALLOCATOR_DECL
 
+        MotionMatchingInstance() = default;
         virtual ~MotionMatchingInstance();
 
         struct EMFX_API InitSettings
@@ -49,8 +56,8 @@ namespace EMotionFX::MotionMatching
         };
         void Init(const InitSettings& settings);
 
-        void DebugDraw();
-        void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay);
+        // DebugDrawRequestBus::Handler overrides
+        void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay) override;
 
         void Update(float timePassedInSeconds, const AZ::Vector3& targetPos, const AZ::Vector3& targetFacingDir, TrajectoryQuery::EMode mode, float pathRadius, float pathSpeed);
         void PostUpdate(float timeDelta);
@@ -64,10 +71,8 @@ namespace EMotionFX::MotionMatching
         void SetLowestCostSearchFrequency(float frequency) { m_lowestCostSearchFrequency = frequency; }
         float GetNewMotionTime() const { return m_newMotionTime; }
 
-        /**
-         * Get the cached trajectory feature.
-         * The trajectory feature is searched in the feature schema used in the current instance at init time.
-         */
+        //! Get the cached trajectory feature.
+        //! The trajectory feature is searched in the feature schema used in the current instance at init time.
         FeatureTrajectory* GetTrajectoryFeature() const { return m_cachedTrajectoryFeature; }
         const TrajectoryQuery& GetTrajectoryQuery() const { return m_trajectoryQuery; }
         const TrajectoryHistory& GetTrajectoryHistory() const { return m_trajectoryHistory; }
@@ -90,10 +95,10 @@ namespace EMotionFX::MotionMatching
         Transform m_motionExtractionDelta = Transform::CreateIdentity();
 
         /// Buffers used for the broad-phase KD-tree search.
-        AZStd::vector<float> m_queryFeatureValues; /** The input query features to be compared to every entry/row in the feature matrix with the motion matching search. */
-        AZStd::vector<size_t> m_nearestFrames; /** Stores the nearest matching frames / search result from the KD-tree. */
+        AZStd::vector<float> m_queryFeatureValues; //< The input query features to be compared to every entry/row in the feature matrix with the motion matching search.
+        AZStd::vector<size_t> m_nearestFrames; //< Stores the nearest matching frames / search result from the KD-tree.
 
-        FeatureTrajectory* m_cachedTrajectoryFeature = nullptr; /** Cached pointer to the trajectory feature in the feature schema. */
+        FeatureTrajectory* m_cachedTrajectoryFeature = nullptr; //< Cached pointer to the trajectory feature in the feature schema.
         TrajectoryQuery m_trajectoryQuery;
         TrajectoryHistory m_trajectoryHistory;
         static constexpr float m_trajectorySecsToTrack = 5.0f;
@@ -105,7 +110,7 @@ namespace EMotionFX::MotionMatching
 
         bool m_blending = false;
         float m_blendWeight = 1.0f;
-        float m_blendProgressTime = 0.0f; // How long are we already blending? In seconds.
+        float m_blendProgressTime = 0.0f; //< How long are we already blending? In seconds.
 
         /// Buffers used for FindLowestCostFrameIndex().
         AZStd::vector<float> m_tempCosts;

+ 31 - 3
Gems/MotionMatching/Code/Source/MotionMatchingSystemComponent.cpp

@@ -16,6 +16,8 @@
 
 #include <Integration/EMotionFXBus.h>
 
+#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/ViewportPluginBus.h>
+
 #include <BlendTreeMotionMatchNode.h>
 #include <Feature.h>
 #include <FeaturePosition.h>
@@ -39,7 +41,7 @@ namespace EMotionFX::MotionMatching
             {
                 ec->Class<MotionMatchingSystemComponent>("MotionMatching", "[Description of functionality provided by this System Component]")
                     ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
-                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System"))
+                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System"))
                         ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                     ;
             }
@@ -69,9 +71,9 @@ namespace EMotionFX::MotionMatching
         incompatible.push_back(AZ_CRC_CE("MotionMatchingService"));
     }
 
-    void MotionMatchingSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+    void MotionMatchingSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
     {
-        required.push_back(AZ_CRC("EMotionFXAnimationService", 0x3f8a6369));
+        required.push_back(AZ_CRC_CE("EMotionFXAnimationService"));
     }
 
     void MotionMatchingSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
@@ -122,7 +124,33 @@ namespace EMotionFX::MotionMatching
         MotionMatchingRequestBus::Handler::BusDisconnect();
     }
 
+    void MotionMatchingSystemComponent::DebugDraw(AZ::s32 debugDisplayId)
+    {
+        AZ_PROFILE_SCOPE(Animation, "MotionMatchingSystemComponent::DebugDraw");
+
+        if (debugDisplayId == -1)
+        {
+            return;
+        }
+
+        AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
+        AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, debugDisplayId);
+
+        AzFramework::DebugDisplayRequests* debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus);
+        if (debugDisplay)
+        {
+            const AZ::u32 prevState = debugDisplay->GetState();
+            EMotionFX::MotionMatching::DebugDrawRequestBus::Broadcast(&EMotionFX::MotionMatching::DebugDrawRequests::DebugDraw, *debugDisplay);
+            debugDisplay->SetState(prevState);
+        }
+    }
+
     void MotionMatchingSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
     {
+        // Draw the debug visualizations to the Animation Editor as well as the LY Editor viewport.
+        AZ::s32 animationEditorViewportId = -1;
+        EMStudio::ViewportPluginRequestBus::BroadcastResult(animationEditorViewportId, &EMStudio::ViewportPluginRequestBus::Events::GetViewportId);
+        DebugDraw(animationEditorViewportId);
+        DebugDraw(AzFramework::g_defaultSceneEntityDebugDisplayId);
     }
 } // namespace EMotionFX::MotionMatching

+ 5 - 2
Gems/MotionMatching/Code/Source/MotionMatchingSystemComponent.h

@@ -33,8 +33,7 @@ namespace EMotionFX::MotionMatching
         ~MotionMatchingSystemComponent();
 
     protected:
-        ////////////////////////////////////////////////////////////////////////
-        // MotionMatchingRequestBus interface implementation
+        void DebugDraw(AZ::s32 debugDisplayId);
 
         ////////////////////////////////////////////////////////////////////////
         // AZ::Component interface implementation
@@ -45,6 +44,10 @@ namespace EMotionFX::MotionMatching
 
         ////////////////////////////////////////////////////////////////////////
         // AZTickBus interface implementation
+        int GetTickOrder() override
+        {
+            return AZ::TICK_PRE_RENDER;
+        }
         void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
         ////////////////////////////////////////////////////////////////////////
     };