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

news/2024/12/27 0:44:35/文章来源:https://www.cnblogs.com/sailJs/p/18442561

下图中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";elsem_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}");elseEditorGUILayout.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}]");elseEditorGUILayout.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--;elseDebug.Log("first page");}if (GUILayout.Button($"下一页")){if (m_NoChangeInfosPage < totalPage - 1)m_NoChangeInfosPage++;elseDebug.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);}}}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/806438.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实验1 C语言输入输出和简单程序编写

一,实验目的 1. 会使用C语言程序开发环境(vs2010/devc++等),能熟练、正确使用它们编写、编译、运行、调 试C程序 2. 知道C程序结构和编码规范,能正确使用 3. 能正确、熟练使用C语言输入输出函数: scanf() , printf() , getchar() , putchar() 4. 能灵活、组合使用基本数据…

VScode Cmake-tools 部分问题记录

我的 Visual Studio Code 先前一直安装了 cpp-tools 和 cmake-tools。随后,我升级了我的 GCC 环境版本。然而,重新启动 Visual Studio Code 后,旧的 GCC 版本仍保留在工具包中。起初,我以为是 cpp-tools 插件的问题,一直无法解决这个 bug。后来卸载了相关插件后才发现是 c…

数组0.1

一维数组 数组的运用场合 当我们需要涉及的变量特别多,光想名字都要想半天 所以引入数组 Q: (1)在程序中怎样存放100个学生的成绩? (2)定义100个整型变量吗? (3)C语言中的解决方案是……? A: (1)存储学生成绩用整型数组 mark[100]; (2)存储一行文字用字符数组 …

opencascade AIS_WalkDelta、AIS_ViewInputBuffer源码学习工作

opencascade AIS_WalkDelta 前言 运行方法 1. 空构造函数。 AIS_WalkDelta() : myIsDefined(false), myIsJumping(false), myIsCrouching(false), myIsRunning(false) {} 2. 返回平移组件。 const AIS_WalkPart& operator[] (AIS_WalkTranslation thePart) ; 3. 返回平移组…

2023-9-30

标签之文本标签列表标签之有序列表列表标签之无序列表

[物理]运动学基础理论串讲

运动学基础理论串讲 公式 推论 前言:运动学中,所有的公式都有其对应的几何意义。解决问题时,我们不应死套公式,应当在图像中解决问题。在图像中看清问题的本质。 \(v_t=v_0+at\)。已知初速度和加速度求末速度。 \(x=v_0t+\dfrac{1}{2}at^2\)。算位移的基础公式。 \(v_t^2-…

深度学习(输出模型中间特征)

深度学习骨干网络一般会包含很多层,这里写了一个脚本,可以保存骨干网络的所有特征图。 代码主要用了get_graph_node_names和create_featrue_extractor这两个函数。 get_graph_node_names是得到所有特征节点名字。 create_featrue_extractor是提取对应节点输出的特征tensor。 …

9月30日记录

完成了一个能够列出30道四则运算的java程序, 题目要求:乘法不超过四位数,减法大于零,除法结果为整数; 实现可视化界面,并且能够计算得分与计时;点击查看代码 import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.Actio…

Connector C++ 连接 MySQL 数据库之增删改查

在 vcpkg 中折腾了 mysql-connector-cpp 8.0 很久,一直连接不上远程数据库,后面查官方文档,mysql-connector-cpp 8.0 好像只支持 MySQL 8.0 以上的数据库,本来想把远程服务器上的 MySQL 升级到 MySQL 8.0,后面发现测试服务器的配置有点拉跨,架不住 MySQL 8.0,但是 vcpkg…

9.28 开发MES系统日志四

今天开发MES系统的流程图以及数据库表,因为对MES系统的不了解,所以先加上了最基本的人员管理以及车间管理等基本表信息。

Hadoop 配置hbase

首先要启动hadoop start-dfs.shstart-yarn.sh 查看一下自己的hadoop版本,确保自己下载的hbase与自己的hadoop版本匹配 hadoop version Index of /apache/hbase (tsinghua.edu.cn) 下载hbase 选择倒数第三个下载 下载完成后 进入 /export/server/ 上传压缩包后 完成解压 重命…

.net core elsa工作流程框架源码学习之Pipeline管道的理解

elsa这个框架运用管道来实现切面编程,切面编程的意义我的理解是在于:把业务逻辑和其他与业务不相关的逻辑进行解耦,或者把通用的逻辑:异常处理,日志处理等在不侵入业务逻辑的情况下,服务与这些业务。接下来,详细看看elsa框架的管道是怎么实现的。 主要依靠,下面这个委托…