首页 > 其他分享 >AnimationClip优化工具 - 删除连续相同的帧

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

时间:2024-09-30 23:44:25浏览次数:9  
标签:info AnimationClip 删除 int private path var EditorGUILayout 优化

下图中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);
        }
    }

}

 

标签:info,AnimationClip,删除,int,private,path,var,EditorGUILayout,优化
From: https://www.cnblogs.com/sailJs/p/18442561

相关文章

  • Unity实战案例全解析:RTS游戏的框选和阵型功能(5)阵型功能 优化一
    前篇:Unity实战案例全解析:RTS游戏的框选和阵型功能(4)阵型功能-CSDN博客本案例来源于unity唐老狮,有兴趣的小伙伴可以去泰克在线观看该课程我只是对重要功能进行分析和做出笔记分享,并未无师自通,吃水不忘打井人本案例的实现流程图 兵种排序  首先在一个阵型中,我们希望远......
  • 解析2024年电工杯A题:园区微电网风光储协调优化配置(完整代码分享)
    引言2024年电工杯数学建模竞赛的A题聚焦于园区微电网的风光储协调优化配置问题。这一问题旨在通过数学建模和优化算法,提高风光发电在园区总发电量中的占比,同时减少因风光发电与负荷不匹配导致的弃电问题。本文将介绍题目背景、解题思路,并提供代码获取方式。题目背景园区微电......
  • codeforces round 975 E(div.2)(lambda表达式实现dfs,min_element函数,一定要优化重复的
    解题历程:看到题目要求要用最少的消除次数让所有叶子深度相同,通过观察,那么就只需要将所有深度都尝试一遍就行了,可是我当时没多想就用dfs记录所有节点的深度,单独将所有叶子和该叶子的深度存起来,记录最大的深度,从最大深度尝试到深度0,对于深度小于当前尝试深度的叶子,用dfs的方式将与......
  • Winform控件优化之圆角按钮【各种实现中的推荐做法】
    简介: Windows11下所有控件已经默认采用圆角,其效果更好、相对有着更好的优化...尝试介绍很常见的圆角效果,通过重写控件的OnPaint方法实现绘制,并在后面进一步探索对应的优化和可能的问题Windows11下所有控件已经默认采用圆角,其效果更好、相对有着更好的优化,只是这是默认的行为......
  • PICO 2 RP2350使用官方推荐RISC-V编译器在O3优化下的coremark跑分,与Hazard3库宣传跑分
    编译环境:WSLUbuntu22.04GCC13.2.0 Hazard3存储库https://github.com/Wren6991/Hazard3/RP2350默认频率150MHz,编译内核为其RISC-V架构内核,在此频率下实测O3等级跑分453左右,O2等级跑分429左右。在测试时,当我打开第二个核心后,并且第二个核心只用来控制led灯,此时coremark跑......
  • Winform控件优化之自定义控件的本质【从圆角控件看自定义的本质,Region区域无法反锯齿
    自定义控件的本质自定义控件的本质只有两点:重绘控件Region区域(圆角、多边形、图片等),这是整个控件的真实范围。缺点是Region无法抗锯齿,自定义的Region范围是有锯齿的,无法消除;此外新的Region还会和绘制的背景产生1像素的白边(在圆角或图形拐角部分),且几乎无法有效的消除。【后......
  • sizeof vs strlen - 关于代码可读性、性能考量和编译器优化
    1、起因经常在咱们代码里面见到sizeof(“HEADER”)这类代码来计算常量字符串的长度,例如上次的一个代码review:之所以这么写可能基于以下几点考虑:(1)sizeof()是运算符而不是函数调用,编译时确定而不是运行时执行,因此不占用运行时时间(2)strlen()是GLIBC标准库函数,运行时需要进行......
  • HIVE优化系列之数据倾斜
    数据倾斜在hive表中进行一系列join关联时经常会出现数据倾斜问题,可以通过hint将小表进行广播,从而提高查询的执行效率。第一种hint方法:/*+BROADCAST(small_table)*/SELECT/*+BROADCAST(small_table)*/*FROMlarge_tableJOINsmall_tableONlarge_table.id=s......
  • Svnlook使用浅谈(配置svn上传必须添加备注和删除权限)
      在配置svn上传必须添加备注和删除权限前,我先隆重介绍下今天用到的svn命令svnlook。svnlook是检验Subversion版本库不同方面的命令行工具,不会对版本库有任何修改,只是查看,包括作者信息、文件内容、更改历史、文件大小、属性等。当然它有自己的独特语法(1)语法格式:svnlookREPOS......
  • 上周面的一个985女生,问了Transformer模型的内存优化
    Transformer模型现在很火,内存优化又很重要。上周面试了一个985大学的女生,跟她谈到了Transformer模型的内存优化问题。那么这个女生到底给出了哪些关于Transformer模型内存优化的独特思路呢?一起来看看。01什么是Transformer模型中的KV缓存?Transformer中文本是逐......