| using System; |
| using System.Collections.Generic; |
| using System.Linq; |
| using UnityEditorInternal; |
| using UnityEngine; |
| using Object = UnityEngine.Object; |
|
|
| namespace UnityEditor.Timeline |
| { |
| |
| static class CurveEditUtility |
| { |
| public 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]; |
|
|
| |
| var evalIndex = EvaluateIndex(curve, (float)time); |
|
|
| if (KeyCompare(curve[evalIndex].time, (float)time, clip.frameRate) == 0) |
| { |
| curve[evalIndex].value = prop.objectReferenceValue; |
| } |
| |
| else if (evalIndex < curve.Length - 1 && KeyCompare(curve[evalIndex + 1].time, (float)time, clip.frameRate) == 0) |
| { |
| curve[evalIndex + 1].value = prop.objectReferenceValue; |
| } |
| |
| 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 = 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]); |
| } |
| } |
|
|
| |
| |
| 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); |
| } |
|
|
| |
| 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]; |
|
|
| |
| if (prop.propertyPath.Equals(binding.propertyName)) |
| { |
| valProp = prop; |
| } |
| |
| else if (binding.propertyName.Contains(prop.propertyPath)) |
| { |
| valProp = prop.serializedObject.FindProperty(binding.propertyName); |
| } |
|
|
| if (valProp != null) |
| { |
| var value = GetKeyValue(valProp); |
| if (!float.IsNaN(value)) |
| { |
| updated = true; |
| AddKeyFrameToCurve(curve, (float)time, clip.frameRate, value, valProp.propertyType == SerializedPropertyType.Boolean); |
| updateCurves.Add(curve); |
| updateBindings.Add(binding); |
| } |
| } |
| } |
|
|
| |
| 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 |
| { |
| |
| 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); |
| } |
|
|
| |
| 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]; |
|
|
| |
| if (prop.propertyPath.Equals(binding.propertyName)) |
| { |
| valProp = prop; |
| } |
| |
| 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); |
| } |
| } |
|
|
| |
| 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]; |
|
|
| |
| 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); |
| } |
| } |
|
|
| |
| 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]; |
| 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); |
| } |
| } |
|
|
| |
| 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; |
| } |
|
|
| |
| 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()); |
| } |
|
|
| |
| 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; |
| } |
|
|
| |
| public static Object Evaluate(ObjectReferenceKeyframe[] curve, float time) |
| { |
| return curve[EvaluateIndex(curve, time)].value; |
| } |
|
|
| |
| 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"); |
|
|
| |
| if (time <= curve[0].time) |
| return 0; |
| if (time >= curve.Last().time) |
| return curve.Length - 1; |
|
|
| |
| 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; |
| } |
|
|
| |
| public static void ShiftBySeconds(this AnimationClip clip, float time) |
| { |
| var floatBindings = AnimationUtility.GetCurveBindings(clip); |
| var objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip); |
|
|
| |
| 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); |
| } |
|
|
| |
| 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); |
|
|
| |
| 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); |
| } |
|
|
| |
| 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); |
| } |
|
|
| |
| 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); |
| } |
|
|
| |
| 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; |
| } |
| } |
| } |
|
|