summaryrefslogtreecommitdiff
path: root/Library/PackageCache/com.unity.timeline@1.2.13/Editor/CurveEditUtility.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Library/PackageCache/com.unity.timeline@1.2.13/Editor/CurveEditUtility.cs')
-rw-r--r--Library/PackageCache/com.unity.timeline@1.2.13/Editor/CurveEditUtility.cs657
1 files changed, 657 insertions, 0 deletions
diff --git a/Library/PackageCache/com.unity.timeline@1.2.13/Editor/CurveEditUtility.cs b/Library/PackageCache/com.unity.timeline@1.2.13/Editor/CurveEditUtility.cs
new file mode 100644
index 0000000..0a4321e
--- /dev/null
+++ b/Library/PackageCache/com.unity.timeline@1.2.13/Editor/CurveEditUtility.cs
@@ -0,0 +1,657 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEditorInternal;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+namespace UnityEditor.Timeline
+{
+ // Utility class for editing animation clips from serialized properties
+ static class CurveEditUtility
+ {
+ static bool IsRotationKey(EditorCurveBinding binding)
+ {
+ return binding.propertyName.Contains("localEulerAnglesRaw");
+ }
+
+ public static void AddKey(AnimationClip clip, EditorCurveBinding sourceBinding, SerializedProperty prop, double time)
+ {
+ if (sourceBinding.isPPtrCurve)
+ {
+ AddObjectKey(clip, sourceBinding, prop, time);
+ }
+ else if (IsRotationKey(sourceBinding))
+ {
+ AddRotationKey(clip, sourceBinding, prop, time);
+ }
+ else
+ {
+ AddFloatKey(clip, sourceBinding, prop, time);
+ }
+ }
+
+ static void AddObjectKey(AnimationClip clip, EditorCurveBinding sourceBinding, SerializedProperty prop, double time)
+ {
+ if (prop.propertyType != SerializedPropertyType.ObjectReference)
+ return;
+
+ ObjectReferenceKeyframe[] curve = null;
+ var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
+ var curveIndex = Array.IndexOf(info.objectBindings, sourceBinding);
+ if (curveIndex >= 0)
+ {
+ curve = info.objectCurves[curveIndex];
+
+ // where in the array does the evaluation land?
+ var evalIndex = EvaluateIndex(curve, (float)time);
+
+ if (KeyCompare(curve[evalIndex].time, (float)time, clip.frameRate) == 0)
+ {
+ curve[evalIndex].value = prop.objectReferenceValue;
+ }
+ // check the next key (always return the minimum value)
+ else if (evalIndex < curve.Length - 1 && KeyCompare(curve[evalIndex + 1].time, (float)time, clip.frameRate) == 0)
+ {
+ curve[evalIndex + 1].value = prop.objectReferenceValue;
+ }
+ // resize the array
+ else
+ {
+ if (time > curve[0].time)
+ evalIndex++;
+ var key = new ObjectReferenceKeyframe();
+ key.time = (float)time;
+ key.value = prop.objectReferenceValue;
+ ArrayUtility.Insert(ref curve, evalIndex, key);
+ }
+ }
+ else // curve doesn't exist, add it
+ {
+ curve = new ObjectReferenceKeyframe[1];
+ curve[0].time = (float)time;
+ curve[0].value = prop.objectReferenceValue;
+ }
+
+ AnimationUtility.SetObjectReferenceCurve(clip, sourceBinding, curve);
+ EditorUtility.SetDirty(clip);
+ }
+
+ static void AddRotationKey(AnimationClip clip, EditorCurveBinding sourceBind, SerializedProperty prop, double time)
+ {
+ if (prop.propertyType != SerializedPropertyType.Quaternion)
+ {
+ return;
+ }
+
+ var updateCurves = new List<AnimationCurve>();
+ var updateBindings = new List<EditorCurveBinding>();
+
+ var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
+ for (var i = 0; i < info.bindings.Length; i++)
+ {
+ if (sourceBind.type != info.bindings[i].type)
+ continue;
+
+ if (info.bindings[i].propertyName.Contains("localEuler"))
+ {
+ updateBindings.Add(info.bindings[i]);
+ updateCurves.Add(info.curves[i]);
+ }
+ }
+
+ // use this instead of serialized properties because the editor will attempt to maintain
+ // correct localeulers
+ var eulers = ((Transform)prop.serializedObject.targetObject).localEulerAngles;
+ if (updateBindings.Count == 0)
+ {
+ var propName = AnimationWindowUtility.GetPropertyGroupName(sourceBind.propertyName);
+ updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".x"));
+ updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".y"));
+ updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".z"));
+
+ var curveX = new AnimationCurve();
+ var curveY = new AnimationCurve();
+ var curveZ = new AnimationCurve();
+ AddKeyFrameToCurve(curveX, (float)time, clip.frameRate, eulers.x, false);
+ AddKeyFrameToCurve(curveY, (float)time, clip.frameRate, eulers.y, false);
+ AddKeyFrameToCurve(curveZ, (float)time, clip.frameRate, eulers.z, false);
+
+ updateCurves.Add(curveX);
+ updateCurves.Add(curveY);
+ updateCurves.Add(curveZ);
+ }
+
+ for (var i = 0; i < updateBindings.Count; i++)
+ {
+ var c = updateBindings[i].propertyName.Last();
+ var value = eulers.x;
+ if (c == 'y') value = eulers.y;
+ else if (c == 'z') value = eulers.z;
+ AddKeyFrameToCurve(updateCurves[i], (float)time, clip.frameRate, value, false);
+ }
+
+ UpdateEditorCurves(clip, updateBindings, updateCurves);
+ }
+
+ // Add a floating point curve key
+ static void AddFloatKey(AnimationClip clip, EditorCurveBinding sourceBind, SerializedProperty prop, double time)
+ {
+ var updateCurves = new List<AnimationCurve>();
+ var updateBindings = new List<EditorCurveBinding>();
+
+ var updated = false;
+ var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
+ for (var i = 0; i < info.bindings.Length; i++)
+ {
+ var binding = info.bindings[i];
+ if (binding.type != sourceBind.type)
+ continue;
+
+ SerializedProperty valProp = null;
+ var curve = info.curves[i];
+
+ // perfect match on property path, editting a float
+ if (prop.propertyPath.Equals(binding.propertyName))
+ {
+ valProp = prop;
+ }
+ // this is a child object
+ else if (binding.propertyName.Contains(prop.propertyPath))
+ {
+ valProp = prop.serializedObject.FindProperty(binding.propertyName);
+ }
+
+ if (valProp != null)
+ {
+ var value = GetKeyValue(valProp);
+ if (!float.IsNaN(value)) // Nan indicates an error retrieving the property value
+ {
+ updated = true;
+ AddKeyFrameToCurve(curve, (float)time, clip.frameRate, value, valProp.propertyType == SerializedPropertyType.Boolean);
+ updateCurves.Add(curve);
+ updateBindings.Add(binding);
+ }
+ }
+ }
+
+ // Curves don't exist, add them
+ if (!updated)
+ {
+ var propName = AnimationWindowUtility.GetPropertyGroupName(sourceBind.propertyName);
+ if (!prop.hasChildren)
+ {
+ var value = GetKeyValue(prop);
+ if (!float.IsNaN(value))
+ {
+ updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, sourceBind.propertyName));
+ var curve = new AnimationCurve();
+ AddKeyFrameToCurve(curve, (float)time, clip.frameRate, value, prop.propertyType == SerializedPropertyType.Boolean);
+ updateCurves.Add(curve);
+ }
+ }
+ else
+ {
+ // special case because subproperties on color aren't 'visible' so you can't iterate over them
+ if (prop.propertyType == SerializedPropertyType.Color)
+ {
+ updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".r"));
+ updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".g"));
+ updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".b"));
+ updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".a"));
+
+ var c = prop.colorValue;
+ for (var i = 0; i < 4; i++)
+ {
+ var curve = new AnimationCurve();
+ AddKeyFrameToCurve(curve, (float)time, clip.frameRate, c[i], prop.propertyType == SerializedPropertyType.Boolean);
+ updateCurves.Add(curve);
+ }
+ }
+ else
+ {
+ prop = prop.Copy();
+ foreach (SerializedProperty cp in prop)
+ {
+ updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, cp.propertyPath));
+ var curve = new AnimationCurve();
+ AddKeyFrameToCurve(curve, (float)time, clip.frameRate, GetKeyValue(cp), cp.propertyType == SerializedPropertyType.Boolean);
+ updateCurves.Add(curve);
+ }
+ }
+ }
+ }
+
+ UpdateEditorCurves(clip, updateBindings, updateCurves);
+ }
+
+ public static void RemoveKey(AnimationClip clip, EditorCurveBinding sourceBinding, SerializedProperty prop, double time)
+ {
+ if (sourceBinding.isPPtrCurve)
+ {
+ RemoveObjectKey(clip, sourceBinding, time);
+ }
+ else if (IsRotationKey(sourceBinding))
+ {
+ RemoveRotationKey(clip, sourceBinding, prop, time);
+ }
+ else
+ {
+ RemoveFloatKey(clip, sourceBinding, prop, time);
+ }
+ }
+
+ public static void RemoveObjectKey(AnimationClip clip, EditorCurveBinding sourceBinding, double time)
+ {
+ var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
+ var curveIndex = Array.IndexOf(info.objectBindings, sourceBinding);
+ if (curveIndex >= 0)
+ {
+ var curve = info.objectCurves[curveIndex];
+ var evalIndex = GetKeyframeAtTime(curve, (float)time, clip.frameRate);
+ if (evalIndex >= 0)
+ {
+ ArrayUtility.RemoveAt(ref curve, evalIndex);
+ AnimationUtility.SetObjectReferenceCurve(clip, sourceBinding, curve.Length == 0 ? null : curve);
+ EditorUtility.SetDirty(clip);
+ }
+ }
+ }
+
+ public static int GetObjectKeyCount(AnimationClip clip, EditorCurveBinding sourceBinding)
+ {
+ var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
+ var curveIndex = Array.IndexOf(info.objectBindings, sourceBinding);
+ if (curveIndex >= 0)
+ {
+ var curve = info.objectCurves[curveIndex];
+ return curve.Length;
+ }
+
+ return 0;
+ }
+
+ static void RemoveRotationKey(AnimationClip clip, EditorCurveBinding sourceBind, SerializedProperty prop, double time)
+ {
+ if (prop.propertyType != SerializedPropertyType.Quaternion)
+ {
+ return;
+ }
+
+ var updateCurves = new List<AnimationCurve>();
+ var updateBindings = new List<EditorCurveBinding>();
+
+ var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
+ for (var i = 0; i < info.bindings.Length; i++)
+ {
+ if (sourceBind.type != info.bindings[i].type)
+ continue;
+
+ if (info.bindings[i].propertyName.Contains("localEuler"))
+ {
+ updateBindings.Add(info.bindings[i]);
+ updateCurves.Add(info.curves[i]);
+ }
+ }
+
+ foreach (var c in updateCurves)
+ {
+ RemoveKeyFrameFromCurve(c, (float)time, clip.frameRate);
+ }
+
+ UpdateEditorCurves(clip, updateBindings, updateCurves);
+ }
+
+ // Removes the float keys from curves
+ static void RemoveFloatKey(AnimationClip clip, EditorCurveBinding sourceBind, SerializedProperty prop, double time)
+ {
+ var updateCurves = new List<AnimationCurve>();
+ var updateBindings = new List<EditorCurveBinding>();
+
+ var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
+ for (var i = 0; i < info.bindings.Length; i++)
+ {
+ var binding = info.bindings[i];
+ if (binding.type != sourceBind.type)
+ continue;
+
+ SerializedProperty valProp = null;
+ var curve = info.curves[i];
+
+ // perfect match on property path, editting a float
+ if (prop.propertyPath.Equals(binding.propertyName))
+ {
+ valProp = prop;
+ }
+ // this is a child object
+ else if (binding.propertyName.Contains(prop.propertyPath))
+ {
+ valProp = prop.serializedObject.FindProperty(binding.propertyName);
+ }
+ if (valProp != null)
+ {
+ RemoveKeyFrameFromCurve(curve, (float)time, clip.frameRate);
+ updateCurves.Add(curve);
+ updateBindings.Add(binding);
+ }
+ }
+
+ // update the curve. Do this last to not mess with the curve caches we are iterating over
+ UpdateEditorCurves(clip, updateBindings, updateCurves);
+ }
+
+ static void UpdateEditorCurve(AnimationClip clip, EditorCurveBinding binding, AnimationCurve curve)
+ {
+ if (curve.keys.Length == 0)
+ AnimationUtility.SetEditorCurve(clip, binding, null);
+ else
+ AnimationUtility.SetEditorCurve(clip, binding, curve);
+ }
+
+ static void UpdateEditorCurves(AnimationClip clip, List<EditorCurveBinding> bindings, List<AnimationCurve> curves)
+ {
+ if (curves.Count == 0)
+ return;
+
+ for (var i = 0; i < curves.Count; i++)
+ {
+ UpdateEditorCurve(clip, bindings[i], curves[i]);
+ }
+ EditorUtility.SetDirty(clip);
+ }
+
+ public static void RemoveCurves(AnimationClip clip, SerializedProperty prop)
+ {
+ if (clip == null || prop == null)
+ return;
+
+ var toRemove = new List<EditorCurveBinding>();
+ var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
+ for (var i = 0; i < info.bindings.Length; i++)
+ {
+ var binding = info.bindings[i];
+
+ // check if we match directly, or with a child object
+ if (prop.propertyPath.Equals(binding.propertyName) || binding.propertyName.Contains(prop.propertyPath))
+ {
+ toRemove.Add(binding);
+ }
+ }
+ for (int i = 0; i < toRemove.Count; i++)
+ {
+ AnimationUtility.SetEditorCurve(clip, toRemove[i], null);
+ }
+ }
+
+ // adds a stepped key frame to the given curve
+ public static void AddKeyFrameToCurve(AnimationCurve curve, float time, float framerate, float value, bool stepped)
+ {
+ var key = new Keyframe();
+
+ bool add = true;
+ var keyIndex = GetKeyframeAtTime(curve, time, framerate);
+ if (keyIndex != -1)
+ {
+ add = false;
+ key = curve[keyIndex]; // retain the tangents and mode
+ curve.RemoveKey(keyIndex);
+ }
+
+ key.value = value;
+ key.time = GetKeyTime(time, framerate);
+ keyIndex = curve.AddKey(key);
+
+ if (stepped)
+ {
+ AnimationUtility.SetKeyBroken(curve, keyIndex, stepped);
+ AnimationUtility.SetKeyLeftTangentMode(curve, keyIndex, AnimationUtility.TangentMode.Constant);
+ AnimationUtility.SetKeyRightTangentMode(curve, keyIndex, AnimationUtility.TangentMode.Constant);
+ key.outTangent = Mathf.Infinity;
+ key.inTangent = Mathf.Infinity;
+ }
+ else if (add)
+ {
+ AnimationUtility.SetKeyLeftTangentMode(curve, keyIndex, AnimationUtility.TangentMode.ClampedAuto);
+ AnimationUtility.SetKeyRightTangentMode(curve, keyIndex, AnimationUtility.TangentMode.ClampedAuto);
+ }
+
+ if (keyIndex != -1 && !stepped)
+ {
+ AnimationUtility.UpdateTangentsFromModeSurrounding(curve, keyIndex);
+ AnimationUtility.SetKeyBroken(curve, keyIndex, false);
+ }
+ }
+
+ // Removes a keyframe at the given time from the animation curve
+ public static bool RemoveKeyFrameFromCurve(AnimationCurve curve, float time, float framerate)
+ {
+ var keyIndex = GetKeyframeAtTime(curve, time, framerate);
+ if (keyIndex == -1)
+ return false;
+
+ curve.RemoveKey(keyIndex);
+ return true;
+ }
+
+ // gets the value of the key
+ public static float GetKeyValue(SerializedProperty prop)
+ {
+ switch (prop.propertyType)
+ {
+ case SerializedPropertyType.Integer:
+ return prop.intValue;
+ case SerializedPropertyType.Boolean:
+ return prop.boolValue ? 1.0f : 0.0f;
+ case SerializedPropertyType.Float:
+ return prop.floatValue;
+ default:
+ Debug.LogError("Could not convert property type " + prop.propertyType.ToString() + " to float");
+ break;
+ }
+ return float.NaN;
+ }
+
+ public static void SetFromKeyValue(SerializedProperty prop, float keyValue)
+ {
+ switch (prop.propertyType)
+ {
+ case SerializedPropertyType.Float:
+ {
+ prop.floatValue = keyValue;
+ return;
+ }
+ case SerializedPropertyType.Integer:
+ {
+ prop.intValue = (int)keyValue;
+ return;
+ }
+ case SerializedPropertyType.Boolean:
+ {
+ prop.boolValue = Math.Abs(keyValue) > 0.001f;
+ return;
+ }
+ }
+
+ Debug.LogError("Could not convert float to property type " + prop.propertyType.ToString());
+ }
+
+ // gets the index of the key, -1 if not found
+ public static int GetKeyframeAtTime(AnimationCurve curve, float time, float frameRate)
+ {
+ var range = 0.5f / frameRate;
+ var keys = curve.keys;
+ for (var i = 0; i < keys.Length; i++)
+ {
+ var k = keys[i];
+ if (k.time >= time - range && k.time < time + range)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public static int GetKeyframeAtTime(ObjectReferenceKeyframe[] curve, float time, float frameRate)
+ {
+ if (curve == null || curve.Length == 0)
+ return -1;
+
+ var range = 0.5f / frameRate;
+ for (var i = 0; i < curve.Length; i++)
+ {
+ var t = curve[i].time;
+ if (t >= time - range && t < time + range)
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static float GetKeyTime(float time, float frameRate)
+ {
+ return Mathf.Round(time * frameRate) / frameRate;
+ }
+
+ public static int KeyCompare(float timeA, float timeB, float frameRate)
+ {
+ if (Mathf.Abs(timeA - timeB) <= 0.5f / frameRate)
+ return 0;
+ return timeA < timeB ? -1 : 1;
+ }
+
+ // Evaluates an object (bool curve)
+ public static Object Evaluate(ObjectReferenceKeyframe[] curve, float time)
+ {
+ return curve[EvaluateIndex(curve, time)].value;
+ }
+
+ // returns the index from evaluation
+ public static int EvaluateIndex(ObjectReferenceKeyframe[] curve, float time)
+ {
+ if (curve == null || curve.Length == 0)
+ throw new InvalidOperationException("Can not evaluate a PPtr curve with no entries");
+
+ // clamp conditions
+ if (time <= curve[0].time)
+ return 0;
+ if (time >= curve.Last().time)
+ return curve.Length - 1;
+
+ // binary search
+ var max = curve.Length - 1;
+ var min = 0;
+ while (max - min > 1)
+ {
+ var imid = (min + max) / 2;
+ if (Mathf.Approximately(curve[imid].time, time))
+ return imid;
+ if (curve[imid].time < time)
+ min = imid;
+ else if (curve[imid].time > time)
+ max = imid;
+ }
+ return min;
+ }
+
+ // Shifts the animation clip so the time start at 0
+ public static void ShiftBySeconds(this AnimationClip clip, float time)
+ {
+ var floatBindings = AnimationUtility.GetCurveBindings(clip);
+ var objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
+
+ // update the float curves
+ foreach (var bind in floatBindings)
+ {
+ var curve = AnimationUtility.GetEditorCurve(clip, bind);
+ var keys = curve.keys;
+ for (var i = 0; i < keys.Length; i++)
+ keys[i].time += time;
+ curve.keys = keys;
+ AnimationUtility.SetEditorCurve(clip, bind, curve);
+ }
+
+ // update the PPtr curves
+ foreach (var bind in objectBindings)
+ {
+ var curve = AnimationUtility.GetObjectReferenceCurve(clip, bind);
+ for (var i = 0; i < curve.Length; i++)
+ curve[i].time += time;
+ AnimationUtility.SetObjectReferenceCurve(clip, bind, curve);
+ }
+
+ EditorUtility.SetDirty(clip);
+ }
+
+ public static void ScaleTime(this AnimationClip clip, float scale)
+ {
+ var floatBindings = AnimationUtility.GetCurveBindings(clip);
+ var objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
+
+ // update the float curves
+ foreach (var bind in floatBindings)
+ {
+ var curve = AnimationUtility.GetEditorCurve(clip, bind);
+ var keys = curve.keys;
+ for (var i = 0; i < keys.Length; i++)
+ keys[i].time *= scale;
+ curve.keys = keys.OrderBy(x => x.time).ToArray();
+ AnimationUtility.SetEditorCurve(clip, bind, curve);
+ }
+
+ // update the PPtr curves
+ foreach (var bind in objectBindings)
+ {
+ var curve = AnimationUtility.GetObjectReferenceCurve(clip, bind);
+ for (var i = 0; i < curve.Length; i++)
+ curve[i].time *= scale;
+ curve = curve.OrderBy(x => x.time).ToArray();
+ AnimationUtility.SetObjectReferenceCurve(clip, bind, curve);
+ }
+
+ EditorUtility.SetDirty(clip);
+ }
+
+ // Creates an opposing blend curve that matches the given curve to make sure the result is normalized
+ public static AnimationCurve CreateMatchingCurve(AnimationCurve curve)
+ {
+ Keyframe[] keys = curve.keys;
+
+ for (var i = 0; i != keys.Length; i++)
+ {
+ if (!Single.IsPositiveInfinity(keys[i].inTangent))
+ keys[i].inTangent = -keys[i].inTangent;
+ if (!Single.IsPositiveInfinity(keys[i].outTangent))
+ keys[i].outTangent = -keys[i].outTangent;
+ keys[i].value = 1.0f - keys[i].value;
+ }
+ return new AnimationCurve(keys);
+ }
+
+ // Sanitizes the keys on an animation to force the property to be normalized
+ public static Keyframe[] SanitizeCurveKeys(Keyframe[] keys, bool easeIn)
+ {
+ if (keys.Length < 2)
+ {
+ if (easeIn)
+ keys = new[] { new Keyframe(0, 0), new Keyframe(1, 1) };
+ else
+ keys = new[] { new Keyframe(0, 1), new Keyframe(1, 0) };
+ }
+ else if (easeIn)
+ {
+ keys[0].time = 0;
+ keys[keys.Length - 1].time = 1;
+ keys[keys.Length - 1].value = 1;
+ }
+ else
+ {
+ keys[0].time = 0;
+ keys[0].value = 1;
+ keys[keys.Length - 1].time = 1;
+ }
+ return keys;
+ }
+ }
+}