using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEditor; using UnityEditorInternal; namespace UnityEditor.Timeline { struct CurveBindingPair { public EditorCurveBinding binding; public AnimationCurve curve; public ObjectReferenceKeyframe[] objectCurve; } class CurveBindingGroup { public CurveBindingPair[] curveBindingPairs { get; set; } public Vector2 timeRange { get; set; } public Vector2 valueRange { get; set; } public bool isFloatCurve { get { return curveBindingPairs != null && curveBindingPairs.Length > 0 && curveBindingPairs[0].curve != null; } } public bool isObjectCurve { get { return curveBindingPairs != null && curveBindingPairs.Length > 0 && curveBindingPairs[0].objectCurve != null; } } public int count { get { if (curveBindingPairs == null) return 0; return curveBindingPairs.Length; } } } class AnimationClipCurveInfo { bool m_CurveDirty = true; bool m_KeysDirty = true; public bool dirty { get { return m_CurveDirty; } set { m_CurveDirty = value; if (m_CurveDirty) { m_KeysDirty = true; if (m_groupings != null) m_groupings.Clear(); } } } public AnimationCurve[] curves; public EditorCurveBinding[] bindings; public EditorCurveBinding[] objectBindings; public List objectCurves; Dictionary m_groupings; // to tell whether the cache has changed public int version { get; private set; } float[] m_KeyTimes; Dictionary m_individualBindinsKey; public float[] keyTimes { get { if (m_KeysDirty || m_KeyTimes == null) { RebuildKeyCache(); } return m_KeyTimes; } } public float[] GetCurveTimes(EditorCurveBinding curve) { return GetCurveTimes(new[] { curve }); } public float[] GetCurveTimes(EditorCurveBinding[] curves) { if (m_KeysDirty || m_KeyTimes == null) { RebuildKeyCache(); } var keyTimes = new List(); for (int i = 0; i < curves.Length; i++) { var c = curves[i]; if (m_individualBindinsKey.ContainsKey(c)) { keyTimes.AddRange(m_individualBindinsKey[c]); } } return keyTimes.ToArray(); } void RebuildKeyCache() { m_individualBindinsKey = new Dictionary(); List keys = curves.SelectMany(y => y.keys).Select(z => z.time).ToList(); for (int i = 0; i < objectCurves.Count; i++) { var kf = objectCurves[i]; keys.AddRange(kf.Select(x => x.time)); } for (int b = 0; b < bindings.Count(); b++) { m_individualBindinsKey.Add(bindings[b], curves[b].keys.Select(k => k.time).Distinct().ToArray()); } m_KeyTimes = keys.OrderBy(x => x).Distinct().ToArray(); m_KeysDirty = false; } public void Update(AnimationClip clip) { List postfilter = new List(); var clipBindings = AnimationUtility.GetCurveBindings(clip); for (int i = 0; i < clipBindings.Length; i++) { var bind = clipBindings[i]; if (!bind.propertyName.Contains("LocalRotation.w")) postfilter.Add(RotationCurveInterpolation.RemapAnimationBindingForRotationCurves(bind, clip)); } bindings = postfilter.ToArray(); curves = new AnimationCurve[bindings.Length]; for (int i = 0; i < bindings.Length; i++) { curves[i] = AnimationUtility.GetEditorCurve(clip, bindings[i]); } objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip); objectCurves = new List(objectBindings.Length); for (int i = 0; i < objectBindings.Length; i++) { objectCurves.Add(AnimationUtility.GetObjectReferenceCurve(clip, objectBindings[i])); } m_CurveDirty = false; m_KeysDirty = true; version = version + 1; } public bool GetBindingForCurve(AnimationCurve curve, ref EditorCurveBinding binding) { for (int i = 0; i < curves.Length; i++) { if (curve == curves[i]) { binding = bindings[i]; return true; } } return false; } public AnimationCurve GetCurveForBinding(EditorCurveBinding binding) { for (int i = 0; i < curves.Length; i++) { if (binding.Equals(bindings[i])) { return curves[i]; } } return null; } public ObjectReferenceKeyframe[] GetObjectCurveForBinding(EditorCurveBinding binding) { if (objectCurves == null) return null; for (int i = 0; i < objectCurves.Count; i++) { if (binding.Equals(objectBindings[i])) { return objectCurves[i]; } } return null; } // given a groupID, get the list of curve bindings public CurveBindingGroup GetGroupBinding(string groupID) { if (m_groupings == null) m_groupings = new Dictionary(); CurveBindingGroup result = null; if (!m_groupings.TryGetValue(groupID, out result)) { result = new CurveBindingGroup(); result.timeRange = new Vector2(float.MaxValue, float.MinValue); result.valueRange = new Vector2(float.MaxValue, float.MinValue); List found = new List(); for (int i = 0; i < bindings.Length; i++) { if (bindings[i].GetGroupID() == groupID) { CurveBindingPair pair = new CurveBindingPair(); pair.binding = bindings[i]; pair.curve = curves[i]; found.Add(pair); for (int k = 0; k < curves[i].keys.Length; k++) { var key = curves[i].keys[k]; result.timeRange = new Vector2(Mathf.Min(key.time, result.timeRange.x), Mathf.Max(key.time, result.timeRange.y)); result.valueRange = new Vector2(Mathf.Min(key.value, result.valueRange.x), Mathf.Max(key.value, result.valueRange.y)); } } } for (int i = 0; i < objectBindings.Length; i++) { if (objectBindings[i].GetGroupID() == groupID) { CurveBindingPair pair = new CurveBindingPair(); pair.binding = objectBindings[i]; pair.objectCurve = objectCurves[i]; found.Add(pair); for (int k = 0; k < objectCurves[i].Length; k++) { var key = objectCurves[i][k]; result.timeRange = new Vector2(Mathf.Min(key.time, result.timeRange.x), Mathf.Max(key.time, result.timeRange.y)); } } } result.curveBindingPairs = found.OrderBy(x => AnimationWindowUtility.GetComponentIndex(x.binding.propertyName)).ToArray(); m_groupings.Add(groupID, result); } return result; } } // Cache for storing the animation clip data class AnimationClipCurveCache { static AnimationClipCurveCache s_Instance; Dictionary m_ClipCache = new Dictionary(); bool m_IsEnabled; public static AnimationClipCurveCache Instance { get { if (s_Instance == null) { s_Instance = new AnimationClipCurveCache(); } return s_Instance; } } public void OnEnable() { if (!m_IsEnabled) { AnimationUtility.onCurveWasModified += OnCurveWasModified; m_IsEnabled = true; } } public void OnDisable() { if (m_IsEnabled) { AnimationUtility.onCurveWasModified -= OnCurveWasModified; m_IsEnabled = false; } } // callback when a curve is edited. Force the cache to update next time it's accessed void OnCurveWasModified(AnimationClip clip, EditorCurveBinding binding, AnimationUtility.CurveModifiedType modification) { AnimationClipCurveInfo data; if (m_ClipCache.TryGetValue(clip, out data)) { data.dirty = true; } } public AnimationClipCurveInfo GetCurveInfo(AnimationClip clip) { AnimationClipCurveInfo data; if (clip == null) return null; if (!m_ClipCache.TryGetValue(clip, out data)) { data = new AnimationClipCurveInfo(); data.dirty = true; m_ClipCache[clip] = data; } if (data.dirty) { data.Update(clip); } return data; } public void ClearCachedProxyClips() { var toRemove = new List(); foreach (var entry in m_ClipCache) { var clip = entry.Key; if (clip != null && (clip.hideFlags & HideFlags.HideAndDontSave) == HideFlags.HideAndDontSave) toRemove.Add(clip); } foreach (var clip in toRemove) { m_ClipCache.Remove(clip); Object.DestroyImmediate(clip, true); } } public void Clear() { ClearCachedProxyClips(); m_ClipCache.Clear(); } } static class EditorCurveBindingExtension { // identifier to generate an id thats the same for all curves in the same group public static string GetGroupID(this EditorCurveBinding binding) { return binding.type + AnimationWindowUtility.GetPropertyGroupName(binding.propertyName); } } static class CurveBindingGroupExtensions { // Extentions to determine curve types public static bool IsEnableGroup(this CurveBindingGroup curves) { return curves.isFloatCurve && curves.count == 1 && curves.curveBindingPairs[0].binding.propertyName == "m_Enabled"; } public static bool IsVectorGroup(this CurveBindingGroup curves) { if (!curves.isFloatCurve) return false; if (curves.count <= 1 || curves.count > 4) return false; char l = curves.curveBindingPairs[0].binding.propertyName.Last(); return l == 'x' || l == 'y' || l == 'z' || l == 'w'; } public static bool IsColorGroup(this CurveBindingGroup curves) { if (!curves.isFloatCurve) return false; if (curves.count != 3 && curves.count != 4) return false; char l = curves.curveBindingPairs[0].binding.propertyName.Last(); return l == 'r' || l == 'g' || l == 'b' || l == 'a'; } public static string GetDescription(this CurveBindingGroup group, float t) { string result = string.Empty; if (group.isFloatCurve) { if (group.count > 1) { result += "(" + group.curveBindingPairs[0].curve.Evaluate(t).ToString("0.##"); for (int j = 1; j < group.curveBindingPairs.Length; j++) { result += "," + group.curveBindingPairs[j].curve.Evaluate(t).ToString("0.##"); } result += ")"; } else { result = group.curveBindingPairs[0].curve.Evaluate(t).ToString("0.##"); } } else if (group.isObjectCurve) { Object obj = null; if (group.curveBindingPairs[0].objectCurve.Length > 0) obj = CurveEditUtility.Evaluate(group.curveBindingPairs[0].objectCurve, t); result = (obj == null ? "None" : obj.name); } return result; } } }