412 lines
13 KiB
C#
412 lines
13 KiB
C#
|
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<ObjectReferenceKeyframe[]> objectCurves;
|
||
|
|
||
|
Dictionary<string, CurveBindingGroup> m_groupings;
|
||
|
|
||
|
// to tell whether the cache has changed
|
||
|
public int version { get; private set; }
|
||
|
|
||
|
float[] m_KeyTimes;
|
||
|
|
||
|
Dictionary<EditorCurveBinding, float[]> 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<float>();
|
||
|
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<EditorCurveBinding, float[]>();
|
||
|
|
||
|
List<float> 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<EditorCurveBinding> postfilter = new List<EditorCurveBinding>();
|
||
|
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<ObjectReferenceKeyframe[]>(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<string, CurveBindingGroup>();
|
||
|
|
||
|
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<CurveBindingPair> found = new List<CurveBindingPair>();
|
||
|
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<AnimationClip, AnimationClipCurveInfo> m_ClipCache = new Dictionary<AnimationClip, AnimationClipCurveInfo>();
|
||
|
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)
|
||
|
{
|
||
|
if (modification == AnimationUtility.CurveModifiedType.CurveDeleted)
|
||
|
{
|
||
|
m_ClipCache.Remove(clip);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|