645 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			645 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | 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 | ||
|  |     { | ||
|  |         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]; | ||
|  | 
 | ||
|  |                 // 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) => UnityEngine.Timeline.CurveEditUtility.CreateMatchingCurve(curve); | ||
|  | 
 | ||
|  |         // 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; | ||
|  |         } | ||
|  |     } | ||
|  | } |