Pārlūkot izejas kodu

Motion Matching: Added min max scaler

Signed-off-by: Benjamin Jillich <[email protected]>
Benjamin Jillich 3 gadi atpakaļ
vecāks
revīzija
66ed45cd68

+ 208 - 0
Gems/MotionMatching/Code/Source/FeatureMatrixMinMaxScaler.cpp

@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <Allocators.h>
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include <FeatureMatrixMinMaxScaler.h>
+
+namespace EMotionFX::MotionMatching
+{
+    AZ_CLASS_ALLOCATOR_IMPL(MinMaxScaler, MotionMatchAllocator, 0)
+
+    bool MinMaxScaler::Fit(const FeatureMatrix& in, const Settings& settings)
+    {
+        const FeatureMatrix::Index numRows = in.rows();
+        const FeatureMatrix::Index numColumns = in.cols();
+
+        m_clip = settings.m_clip;
+        m_featureMin = settings.m_featureMin;
+        m_featureMax = settings.m_featureMax;
+        m_featureRange = settings.m_featureMax - settings.m_featureMin;
+        AZ_Assert(m_featureRange > s_epsilon, "Feature range too small. This will lead to divisions by infinity.");
+        m_dataMin.clear();
+        m_dataMin.resize(numColumns, std::numeric_limits<float>::max());
+        m_dataMax.clear();
+        m_dataMax.resize(numColumns, -std::numeric_limits<float>::max());
+
+        for (FeatureMatrix::Index row = 0; row < numRows; ++row)
+        {
+            for (FeatureMatrix::Index column = 0; column < numColumns; ++column)
+            {
+                m_dataMin[column] = AZStd::min(m_dataMin[column], in(row, column));
+                m_dataMax[column] = AZStd::max(m_dataMax[column], in(row, column));
+            }
+        }
+
+        m_dataRange.clear();
+        m_dataRange.resize(numColumns);
+        for (FeatureMatrix::Index column = 0; column < numColumns; ++column)
+        {
+            const float columnMin = m_dataMin[column];
+            const float columnMax = m_dataMax[column];
+            const float range = columnMax - columnMin;
+            m_dataRange[column] = range;
+        }
+
+        return true;
+    }
+
+    //-------------------------------------------------------------------------
+
+    float MinMaxScaler::Transform(float value, FeatureMatrix::Index column) const
+    {
+        const float& min = m_dataMin[column];
+        const float& range = m_dataRange[column];
+
+        float result = value;
+        if (range > s_epsilon)
+        {
+            const float normalizedValue = (value - min) / (range);
+            const float scaled = normalizedValue * m_featureRange + m_featureMin;
+
+            result = scaled;
+        }
+
+        if (m_clip)
+        {
+            result = AZ::GetClamp(result, m_featureMin, m_featureMax);
+        }
+
+        return result;
+    }
+
+    AZ::Vector2 MinMaxScaler::Transform(const AZ::Vector2& value, FeatureMatrix::Index column) const
+    {
+        return AZ::Vector2(
+            Transform(value.GetX(), column + 0),
+            Transform(value.GetY(), column + 1));
+    }
+
+    AZ::Vector3 MinMaxScaler::Transform(const AZ::Vector3& value, FeatureMatrix::Index column) const
+    {
+        return AZ::Vector3(
+            Transform(value.GetX(), column + 0),
+            Transform(value.GetY(), column + 1),
+            Transform(value.GetZ(), column + 2));
+    }
+
+    void MinMaxScaler::Transform(AZStd::span<float> data) const
+    {
+        const size_t numValues = data.size();
+        AZ_Assert(numValues == m_dataMin.size(), "Input data needs to have the same number of elements.");
+        for (size_t i = 0; i < numValues; ++i)
+        {
+            data[i] = Transform(data[i], i);
+        }
+    }
+
+    FeatureMatrix MinMaxScaler::Transform(const FeatureMatrix& in) const
+    {
+        const FeatureMatrix::Index numRows = in.rows();
+        const FeatureMatrix::Index numColumns = in.cols();
+        FeatureMatrix result;
+        result.resize(numRows, numColumns);
+
+        for (FeatureMatrix::Index row = 0; row < numRows; ++row)
+        {
+            for (FeatureMatrix::Index column = 0; column < numColumns; ++column)
+            {
+                result(row, column) = Transform(in(row, column), column);
+            }
+        }
+
+        return result;
+    }
+
+    //-------------------------------------------------------------------------
+
+    FeatureMatrix MinMaxScaler::InverseTransform(const FeatureMatrix& in) const
+    {
+        const FeatureMatrix::Index numRows = in.rows();
+        const FeatureMatrix::Index numColumns = in.cols();
+        FeatureMatrix result;
+        result.resize(numRows, numColumns);
+
+        for (FeatureMatrix::Index row = 0; row < numRows; ++row)
+        {
+            for (FeatureMatrix::Index column = 0; column < numColumns; ++column)
+            {
+                result(row, column) = InverseTransform(in(row, column), column);
+            }
+        }
+
+        return result;
+    }
+
+    AZ::Vector2 MinMaxScaler::InverseTransform(const AZ::Vector2& value, FeatureMatrix::Index column) const
+    {
+        return AZ::Vector2(
+            InverseTransform(value.GetX(), column + 0),
+            InverseTransform(value.GetY(), column + 1));
+    }
+
+    AZ::Vector3 MinMaxScaler::InverseTransform(const AZ::Vector3& value, FeatureMatrix::Index column) const
+    {
+        return AZ::Vector3(
+            InverseTransform(value.GetX(), column + 0),
+            InverseTransform(value.GetY(), column + 1),
+            InverseTransform(value.GetZ(), column + 2));
+    }
+
+    float MinMaxScaler::InverseTransform(float value, FeatureMatrix::Index column) const
+    {
+        const float normalizedValue = (value - m_featureMin) / m_featureRange;
+        return normalizedValue * m_dataRange[column] + m_dataMin[column];
+    }
+
+    void MinMaxScaler::SaveMinMaxAsCsv(const AZStd::string& filename, const AZStd::vector<AZStd::string>& columnNames)
+    {
+        std::ofstream file(filename.c_str());
+
+        // Save column names in the first row
+        if (!columnNames.empty())
+        {
+            for (size_t i = 0; i < columnNames.size(); ++i)
+            {
+                if (i != 0)
+                {
+                    file << ",";
+                }
+
+                file << columnNames[i].c_str();
+            }
+            file << "\n";
+        }
+
+        for (size_t i = 0; i < m_dataMin.size(); ++i)
+        {
+            if (i != 0)
+            {
+                file << ",";
+            }
+
+            file << m_dataMin[i];
+        }
+
+        file << "\n";
+
+        for (size_t i = 0; i < m_dataMax.size(); ++i)
+        {
+            if (i != 0)
+            {
+                file << ",";
+            }
+
+            file << m_dataMax[i];
+        }
+
+        file << "\n";
+        file.close();
+    }
+} // namespace EMotionFX::MotionMatching

+ 59 - 0
Gems/MotionMatching/Code/Source/FeatureMatrixMinMaxScaler.h

@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <FeatureMatrix.h>
+#include <FeatureMatrixTransformer.h>
+
+namespace EMotionFX::MotionMatching
+{
+    class MinMaxScaler
+        : public FeatureMatrixTransformer
+    {
+    public:
+        AZ_RTTI(MinMaxScaler, "{95D5BBA7-6144-4219-82F0-34C2DAB7DD3E}", FeatureMatrixTransformer);
+        AZ_CLASS_ALLOCATOR_DECL
+
+        MinMaxScaler() = default;
+
+        bool Fit(const FeatureMatrix& in, const Settings& settings = {}) override;
+
+        // Normalize and scale the values.
+        float Transform(float value, FeatureMatrix::Index column) const override;
+        AZ::Vector2 Transform(const AZ::Vector2& value, FeatureMatrix::Index column) const override;
+        AZ::Vector3 Transform(const AZ::Vector3& value, FeatureMatrix::Index column) const override;
+        void Transform(AZStd::span<float> data) const override;
+        FeatureMatrix Transform(const FeatureMatrix& in) const override;
+
+        // From normalized and scaled back to the original values.
+        FeatureMatrix InverseTransform(const FeatureMatrix& in) const override;
+        AZ::Vector2 InverseTransform(const AZ::Vector2& value, FeatureMatrix::Index column) const override;
+        AZ::Vector3 InverseTransform(const AZ::Vector3& value, FeatureMatrix::Index column) const override;
+        float InverseTransform(float value, FeatureMatrix::Index column) const override;
+
+        const AZStd::vector<float>& GetMin() const { return m_dataMin; }
+        const AZStd::vector<float>& GetMax() const { return m_dataMax; }
+
+        static constexpr float s_epsilon = AZ::Constants::FloatEpsilon;
+
+        void SaveMinMaxAsCsv(const AZStd::string& filename, const AZStd::vector<AZStd::string>& columnNames = {});
+
+    private:
+        AZStd::vector<float> m_dataMin; //!< Minimum value per column seen in the given input feature matrix.
+        AZStd::vector<float> m_dataMax; //!< Maximum value per column seen in the given input feature matrix.
+        AZStd::vector<float> m_dataRange;//!< Per column range (m_dataMax[col] - m_dataMin[col]) seen in the given input feature matrix.
+
+        bool m_clip = false; //!< Clip transformed values out of feature range to provided feature range.
+
+        //! Desired range of the transformed data.
+        float m_featureMin = 0.0f;
+        float m_featureMax = 1.0f;
+        float m_featureRange = 1.0f;
+    };
+} // namespace EMotionFX::MotionMatching

+ 3 - 0
Gems/MotionMatching/Code/motionmatching_files.cmake

@@ -22,6 +22,9 @@ set(FILES
     Source/Feature.h
     Source/FeatureMatrix.cpp
     Source/FeatureMatrix.h
+    Source/FeatureMatrixMinMaxScaler.cpp
+    Source/FeatureMatrixMinMaxScaler.h
+    Source/FeatureMatrixTransformer.h
     Source/FeaturePosition.cpp
     Source/FeaturePosition.h
     Source/FeatureSchema.cpp