AnimationClip优化工具 - 删除连续相同的帧

下图中Rotation.z的前4个关键帧[0, 3](即15帧, 30帧, 45帧, 60帧),值都没变;

(3, 4)Rotation.z变为60(即61帧到90帧);

后3个关键帧[5, 7]一直维持在60没变。

可以分析下:前4个关键帧,[1, 2]删除对动画没影响,后3个关键帧[5, 7]删除对动画也没影响。

 

public class AnimClipCurveOptWnd : EditorWindow
{

    [MenuItem("MyTools/Anim/AnimClipCurveOptWnd", false)]
    static void ShowWindow()
    {
        var win = GetWindow(typeof(AnimClipCurveOptWnd), false, "AnimClipCurveOptWnd");
    }

    private Vector2 m_ScrollPos;
    private AnimationClip m_Clip;

    ///对应AnimationWnd的一行轨道
    private EditorCurveBinding[] m_CurveBindings;

    ///有动画的节点的路径
    private List<string> m_PathList = new List<string>();
    private Dictionary<string, List<string>> m_PathToPropNamesDict = new Dictionary<string, List<string>>();

    private int m_PathOptionIndex;
    private string[] m_PathOptions;

    private int m_PropNameOptionIndex;
    private string[] m_PropNameOptions;

    ///关键帧数据都保存在Curve.keys上
    private AnimationCurve m_Curve;
    private int m_CurveBindingIndex = -1;

    private void OnEnable() 
    {
        if (null != m_Clip && null == m_CurveBindings)
        {
            m_PathList.Clear();
            m_PathToPropNamesDict.Clear(); 

            m_CurveBindings = AnimationUtility.GetCurveBindings(m_Clip);
            foreach (var b in m_CurveBindings)
            {
                if (!m_PathToPropNamesDict.TryGetValue(b.path, out var propNames))
                {
                    propNames = new List<string>();
                    m_PathToPropNamesDict.Add(b.path, propNames);
                    m_PathList.Add(b.path);
                }
            }
        }
    }

    private void OnGUI()
    {
        m_ScrollPos = EditorGUILayout.BeginScrollView(m_ScrollPos);
        {
            OnGUI_ScrollView();
        }
        EditorGUILayout.EndScrollView();
    }

    private void OnGUI_ScrollView()
    {
        var clip = (AnimationClip)EditorGUILayout.ObjectField("Clip", m_Clip, typeof(AnimationClip), false);
        bool isClipChange = m_Clip != clip;
        m_Clip = clip;

        if (null != m_Clip)
        {
            if (GUILayout.Button($"Refresh"))
                isClipChange = true;

            if (GUILayout.Button("检测连续相同的帧"))
            {
                CheckAllNoChange();
            }

            if (m_NoChangeInfos.Count > 0)
            {
                if (GUILayout.Button("清除多余的连续相同帧数据"))
                {
                    ClearAllNoChange();
                }
            }
        }

        bool isPathOptionChange = false;
        if (isClipChange)
        {
            Debug.Log($"clip change");
            isPathOptionChange = true;

            m_CurveBindings = null;
            
            m_NoChangeInfos.Clear();
            m_NoChangeInfosPage = 0;

            m_PathList.Clear();
            m_PathToPropNamesDict.Clear();

            m_PathOptionIndex = 0;
            m_PathOptions = null;

            m_PropNameOptionIndex = 0;
            m_PropNameOptions = null;

            if (null != m_Clip)
            {
                m_CurveBindings = AnimationUtility.GetCurveBindings(m_Clip);
                foreach (var b in m_CurveBindings)
                {
                    if (!m_PathToPropNamesDict.TryGetValue(b.path, out var propNames))
                    {
                        propNames = new List<string>();
                        m_PathToPropNamesDict.Add(b.path, propNames);
                        m_PathList.Add(b.path);
                    }

                    propNames.Add(b.propertyName);
                }

                m_PathOptions = m_PathList.ToArray();
                for (int i = 0; i < m_PathList.Count; ++i)
                {
                    var path = m_PathList[i];
                    if ("" == path)
                        m_PathOptions[i] = "Root";
                    else
                        m_PathOptions[i] = path.Replace("/", "\\");
                }
            }
        }
        if (null == m_Clip) return;

        bool isPropNameOptionChange = false;
        GUILayout.Space(10);
        int pathOptionIndex = EditorGUILayout.Popup("path", m_PathOptionIndex, m_PathOptions);
        if (isPathOptionChange || m_PathOptionIndex != pathOptionIndex)
        {
            Debug.Log($"PathOptionChange: {m_PathOptionIndex} -> {pathOptionIndex}");
            m_PathOptionIndex = pathOptionIndex;
            string path = m_PathList[pathOptionIndex];
            var propNameList = m_PathToPropNamesDict[path];
            m_PropNameOptions = propNameList.ToArray();
            m_PropNameOptionIndex = 0;
            isPropNameOptionChange = true;
        }

        int propNameOptionIndex = EditorGUILayout.Popup("propName", m_PropNameOptionIndex, m_PropNameOptions);
        if (isPropNameOptionChange || m_PropNameOptionIndex != propNameOptionIndex) 
        {
            Debug.Log($"PropNameOptionChange: {m_PropNameOptionIndex} -> {propNameOptionIndex}");
            m_PropNameOptionIndex = propNameOptionIndex;
            string path = m_PathList[pathOptionIndex];

            UpdateCurBindingIndexAndCurve(m_Clip, path, m_PropNameOptionIndex);
        }

        if (null != m_Curve)
        {
            GUILayout.Space(10);
            EditorGUILayout.CurveField(m_Curve, GUILayout.Height(100));

            Keyframe[] keyFrames = m_Curve.keys;
            EditorGUILayout.LabelField("所有关键帧:");
            EditorGUILayout.BeginVertical("box");
            for (int i = 0; i < keyFrames.Length; ++i)
            {
                var kf = keyFrames[i];
                
                EditorGUILayout.LabelField($"{i} -> t: {kf.time}, v: {kf.value}, 帧: {Utils.TrimFloat2(m_Clip.frameRate * kf.time)}");
            }
            EditorGUILayout.EndVertical();
        }

        if (m_NoChangeInfos.Count > 0)
        {
            GUILayout.Space(10);
            
            EditorGUILayout.LabelField($"连续相同的帧信息:");
            EditorGUILayout.BeginVertical("box");
            for (int i = 0; i < 10; ++i)
            {
                int pageIndex = m_NoChangeInfosPage * 10 + i;
                if (pageIndex >= m_NoChangeInfos.Count)
                    break;
                
                var info = m_NoChangeInfos[pageIndex];
                int lastFrameIndex = info.totalFrames - 1;
                if ("" == info.binding.path)
                    EditorGUILayout.LabelField($"Root -> {info.binding.propertyName}, last:{lastFrameIndex}");
                else
                    EditorGUILayout.LabelField($"{info.binding.path} -> {info.binding.propertyName}, last:{lastFrameIndex}");

                for (int i2 = 0; i2 < info.frames.Count; i2 += 2)
                {
                    int index1 = info.frames[i2];
                    int index2 = info.frames[i2+1];
                    if (index2 == lastFrameIndex)
                        EditorGUILayout.LabelField($"相同: [{index1}, {index2}], 可删除: [{index1+1}, {index2}]");
                    else
                        EditorGUILayout.LabelField($"相同: [{index1}, {index2}], 可删除: [{index1+1}, {index2-1}]");
                }
            }

            int totalPage = Mathf.CeilToInt(m_NoChangeInfos.Count / 10.0f);
            if (totalPage > 1)
            {
                EditorGUILayout.BeginHorizontal();
                
                if (GUILayout.Button($"上一页"))
                {
                    if (m_NoChangeInfosPage > 0)
                        m_NoChangeInfosPage--;
                    else
                        Debug.Log("first page");
                }
                
                if (GUILayout.Button($"下一页"))
                {
                    if (m_NoChangeInfosPage < totalPage - 1)
                        m_NoChangeInfosPage++;
                    else
                        Debug.Log("last page");
                }
                
                GUILayout.Label($"({m_NoChangeInfosPage+1}/{totalPage})");

                EditorGUILayout.EndHorizontal();
            }

            EditorGUILayout.EndVertical();
        }
    }


    struct NoChangeInfo
    {
        public EditorCurveBinding binding;
        public List<int> frames; // i, i+1的方式存放连续相同帧区间
        public int totalFrames; //总帧数
    }
    
    private List<NoChangeInfo> m_NoChangeInfos = new List<NoChangeInfo>();
    private int m_NoChangeInfosPage = 0;

    private void ClearAllNoChange()
    {
        Undo.RecordObject(m_Clip, "opti");
        
        foreach (var info in m_NoChangeInfos)
        {
            var curve = AnimationUtility.GetEditorCurve(m_Clip, info.binding);
            int lastFrameIndex = curve.length - 1;
            Debug.Log($"----- removeSameFrame: {info.binding.path} -> {info.binding.propertyName}");
            for (int i = info.frames.Count - 2; i >= 0; i -= 2)
            {
                int index1 = info.frames[i];
                int index2 = info.frames[i + 1];
                if (index2 == lastFrameIndex)
                {
                    Debug.Log($"[{index1 + 1}, {index2}]");
                    for (int j = index2; j > index1; --j)
                        curve.RemoveKey(j);
                }
                else
                {
                    Debug.Log($"[{index1 + 1}, {index2 - 1}]");
                    for (int j = index2 - 1; j > index1; --j) //中间的帧全部删除
                    {
                        curve.RemoveKey(j);
                    }
                }
            }

            Debug.Log($"-----");
            AnimationUtility.SetEditorCurve(m_Clip, info.binding, curve);
        }
        
        var binding = m_CurveBindings[m_CurveBindingIndex];
        m_Curve = AnimationUtility.GetEditorCurve(m_Clip, binding);
        m_NoChangeInfos.Clear();
        m_NoChangeInfosPage = 0;
        
        AssetDatabase.SaveAssets(); //内存写到磁盘
        //Repaint();
    }
    
    private void CheckAllNoChange()
    {
        m_NoChangeInfos.Clear();
        m_NoChangeInfosPage = 0;
        
        var list = new List<int>();
        foreach (var b in m_CurveBindings)
        {
            var curve = AnimationUtility.GetEditorCurve(m_Clip, b);
            Utils.CheckNoChangeFrames(curve, b, list);
            if (list.Count > 0)
            {
                var info = new NoChangeInfo();
                info.binding = b;
                info.frames = list;
                info.totalFrames = curve.length;
                m_NoChangeInfos.Add(info);
                
                list = new List<int>();
            }
        }
    }

    private void UpdateCurBindingIndexAndCurve(AnimationClip clip, string path, int propNameOptIndex)
    {
        m_CurveBindingIndex = -1;
        m_Curve = null;

        if (m_PropNameOptions.Length <= 0)
        {
            Debug.Log($"PropNameOptions len == 0");
            return;
        }

        string propName = m_PropNameOptions[propNameOptIndex];
        int index = 0;
        foreach (var b in m_CurveBindings)
        {
            if (b.path == path)
            {
                if (b.propertyName == propName)
                {
                    m_CurveBindingIndex = index;
                    m_Curve = AnimationUtility.GetEditorCurve(clip, b);
                    break;
                }
            }
            index++;
        }
    }

}

 

public static class Utils
{

    //保留2位小数
    public static float TrimFloat2(float f1)
    {
        int i = (int)(f1 * 100);
        float result = i / 100.0f;
        return result;
    }

    //保留4位小数
    public static float TrimFloat4(float f1)
    {
        int i = (int)(f1 * 10000);
        float result = i / 10000.0f;
        return result;
    }

    public static bool IsFloatEquals(float f1, float f2)
    {
        float delta = f1 - f2;
        if (Mathf.Abs(delta) <= 0.0001f)
            return true;
        return false;
    }

    ///两帧没有变化
    public static bool IsFrameNoChange(Keyframe kf1, Keyframe kf2)
    {
        if (Utils.IsFloatEquals(kf1.value, kf2.value))
        {
            if (kf1.outTangent == 0 && kf2.inTangent == 0) //flat, 水平
                return true;

            if (float.IsInfinity(kf1.outTangent) || float.IsInfinity(kf2.inTangent))
                return true;
        }

        return false;
    }

    public static void CheckNoChangeFrames(AnimationCurve curve, EditorCurveBinding binding, List<int> outList)
    {
        int index1 = -1;

        //获取前面不变的帧下标和时间
        var keys = curve.keys;
        for (int i = 0; i < keys.Length - 1; ++i)
        {
            var kf1 = keys[i];
            var kf2 = keys[i + 1];
            if (-1 == index1) //找开始
            {
                if (IsFrameNoChange(kf1, kf2))
                {
                    index1 = i;
                }
            }
            else
            {
                if (!IsFrameNoChange(kf1, kf2))
                {
                    int deltaFrame = i - index1;
                    if (deltaFrame <= 1)
                    {
                        //相邻帧相互的忽略
                    }
                    else
                    {
                        outList.Add(index1);
                        outList.Add(i);
                    }
                    index1 = -1;
                }
            }
        }

        if (-1 != index1)
        {
            outList.Add(index1);
            outList.Add(keys.Length - 1);
        }
    }

}

 

posted @ 2024-09-30 23:52  yanghui01  阅读(8)  评论(0编辑  收藏  举报