302 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			302 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | #if UNITY_EDITOR | ||
|  | using System.Collections.Generic; | ||
|  | using UnityEditor; | ||
|  | 
 | ||
|  | 
 | ||
|  | namespace UnityEngine.Timeline | ||
|  | { | ||
|  |     static class AnimationPreviewUtilities | ||
|  |     { | ||
|  |         private const string k_PosX = "m_LocalPosition.x"; | ||
|  |         private const string k_PosY = "m_LocalPosition.y"; | ||
|  |         private const string k_PosZ = "m_LocalPosition.z"; | ||
|  |         private const string k_RotX = "m_LocalRotation.x"; | ||
|  |         private const string k_RotY = "m_LocalRotation.y"; | ||
|  |         private const string k_RotZ = "m_LocalRotation.z"; | ||
|  |         private const string k_RotW = "m_LocalRotation.w"; | ||
|  |         private const string k_ScaleX = "m_LocalScale.x"; | ||
|  |         private const string k_ScaleY = "m_LocalScale.y"; | ||
|  |         private const string k_ScaleZ = "m_LocalScale.z"; | ||
|  |         private const string k_EulerAnglesRaw = "localEulerAnglesRaw"; | ||
|  |         private const string k_EulerHint = "m_LocalEulerAnglesHint"; | ||
|  |         private const string k_Pos = "m_LocalPosition"; | ||
|  |         private const string k_Rot = "m_LocalRotation"; | ||
|  |         private const string k_MotionT = "MotionT"; | ||
|  |         private const string k_MotionQ = "MotionQ"; | ||
|  |         private const string k_RootT = "RootT"; | ||
|  |         private const string k_RootQ = "RootQ"; | ||
|  | 
 | ||
|  | 
 | ||
|  |         internal static Object s_PreviewDriver; | ||
|  | 
 | ||
|  | 
 | ||
|  |         internal class EditorCurveBindingComparer : IEqualityComparer<EditorCurveBinding> | ||
|  |         { | ||
|  |             public bool Equals(EditorCurveBinding x, EditorCurveBinding y) { return x.path.Equals(y.path) && x.type == y.type && x.propertyName == y.propertyName; } | ||
|  |             public int GetHashCode(EditorCurveBinding obj) | ||
|  |             { | ||
|  |                 return obj.propertyName.GetHashCode() ^ obj.path.GetHashCode(); | ||
|  |             } | ||
|  | 
 | ||
|  |             public static readonly EditorCurveBindingComparer Instance = new EditorCurveBindingComparer(); | ||
|  |         } | ||
|  | 
 | ||
|  |         // a dictionary is faster than a hashset, because the capacity can be pre-set | ||
|  |         private static readonly Dictionary<EditorCurveBinding, int> s_CurveSet = new Dictionary<EditorCurveBinding, int>(10000, EditorCurveBindingComparer.Instance); | ||
|  |         private static readonly AnimatorBindingCache s_BindingCache = new AnimatorBindingCache(); | ||
|  | 
 | ||
|  |         // string.StartsWith is slow (https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity5.html) | ||
|  |         // hand rolled version has best performance. | ||
|  |         private static bool FastStartsWith(string a, string toCompare) | ||
|  |         { | ||
|  |             int aLen = a.Length; | ||
|  |             int bLen = toCompare.Length; | ||
|  | 
 | ||
|  |             int ap = 0; | ||
|  |             int bp = 0; | ||
|  | 
 | ||
|  |             while (ap < aLen && bp < bLen && a[ap] == toCompare[bp]) | ||
|  |             { | ||
|  |                 ap++; | ||
|  |                 bp++; | ||
|  |             } | ||
|  | 
 | ||
|  |             return (bp == bLen); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void ClearCaches() | ||
|  |         { | ||
|  |             s_BindingCache.Clear(); | ||
|  |             s_CurveSet.Clear(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static EditorCurveBinding[] GetBindings(GameObject animatorRoot, IEnumerable<AnimationClip> clips) | ||
|  |         { | ||
|  |             s_CurveSet.Clear(); | ||
|  |             foreach (var clip in clips) | ||
|  |             { | ||
|  |                 AddBindings(s_BindingCache.GetCurveBindings(clip)); | ||
|  |             } | ||
|  | 
 | ||
|  |             // if we have a transform binding, bind the entire skeleton | ||
|  |             if (NeedsSkeletonBindings(s_CurveSet.Keys)) | ||
|  |                 AddBindings(s_BindingCache.GetAnimatorBindings(animatorRoot)); | ||
|  | 
 | ||
|  |             var bindings = new EditorCurveBinding[s_CurveSet.Keys.Count]; | ||
|  |             s_CurveSet.Keys.CopyTo(bindings, 0); | ||
|  |             return bindings; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static int GetClipHash(List<AnimationClip> clips) | ||
|  |         { | ||
|  |             int hash = 0; | ||
|  | 
 | ||
|  |             foreach (var clip in clips) | ||
|  |             { | ||
|  |                 var stats = AnimationUtility.GetAnimationClipStats(clip); | ||
|  |                 hash = HashUtility.CombineHash(hash, clip.GetHashCode(), stats.clips, stats.size, stats.totalCurves); | ||
|  |             } | ||
|  |             return hash; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void PreviewFromCurves(GameObject animatorRoot, IEnumerable<EditorCurveBinding> keys) | ||
|  |         { | ||
|  |             if (!AnimationMode.InAnimationMode()) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             var avatarRoot = GetAvatarRoot(animatorRoot); | ||
|  |             foreach (var binding in keys) | ||
|  |             { | ||
|  |                 if (IsAvatarBinding(binding) || IsEuler(binding)) | ||
|  |                     continue; | ||
|  | 
 | ||
|  |                 bool isTransform = typeof(Transform).IsAssignableFrom(binding.type); | ||
|  |                 if (isTransform && binding.propertyName.Equals(AnimatorBindingCache.TRPlaceHolder)) | ||
|  |                     AddTRBinding(animatorRoot, binding); | ||
|  |                 else if (isTransform && binding.propertyName.Equals(AnimatorBindingCache.ScalePlaceholder)) | ||
|  |                     AddScaleBinding(animatorRoot, binding); | ||
|  |                 else | ||
|  |                     AnimationMode.AddEditorCurveBinding(avatarRoot, binding); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public static AnimationClip CreateDefaultClip(GameObject animatorRoot, IEnumerable<EditorCurveBinding> keys) | ||
|  |         { | ||
|  |             AnimationClip animClip = new AnimationClip() { name = "DefaultPose" }; | ||
|  |             var keyFrames = new[] {new Keyframe(0, 0)}; | ||
|  |             var curve = new AnimationCurve(keyFrames); | ||
|  |             bool rootMotion = false; | ||
|  |             var avatarRoot = GetAvatarRoot(animatorRoot); | ||
|  | 
 | ||
|  |             foreach (var binding in keys) | ||
|  |             { | ||
|  |                 if (IsRootMotion(binding)) | ||
|  |                 { | ||
|  |                     rootMotion = true; | ||
|  |                     continue; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName.Equals(AnimatorBindingCache.TRPlaceHolder)) | ||
|  |                 { | ||
|  |                     if (string.IsNullOrEmpty(binding.path)) | ||
|  |                         rootMotion = true; | ||
|  |                     else | ||
|  |                     { | ||
|  |                         var transform = animatorRoot.transform.Find(binding.path); | ||
|  |                         if (transform != null) | ||
|  |                         { | ||
|  |                             var pos = transform.localPosition; | ||
|  |                             var rot = transform.localRotation; | ||
|  |                             animClip.SetCurve(binding.path, typeof(Transform), k_PosX, SetZeroKey(curve, keyFrames, pos.x)); | ||
|  |                             animClip.SetCurve(binding.path, typeof(Transform), k_PosY, SetZeroKey(curve, keyFrames, pos.y)); | ||
|  |                             animClip.SetCurve(binding.path, typeof(Transform), k_PosZ, SetZeroKey(curve, keyFrames, pos.z)); | ||
|  |                             animClip.SetCurve(binding.path, typeof(Transform), k_RotX, SetZeroKey(curve, keyFrames, rot.x)); | ||
|  |                             animClip.SetCurve(binding.path, typeof(Transform), k_RotY, SetZeroKey(curve, keyFrames, rot.y)); | ||
|  |                             animClip.SetCurve(binding.path, typeof(Transform), k_RotZ, SetZeroKey(curve, keyFrames, rot.z)); | ||
|  |                             animClip.SetCurve(binding.path, typeof(Transform), k_RotW, SetZeroKey(curve, keyFrames, rot.w)); | ||
|  |                         } | ||
|  |                     } | ||
|  | 
 | ||
|  |                     continue; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName == AnimatorBindingCache.ScalePlaceholder) | ||
|  |                 { | ||
|  |                     var transform = animatorRoot.transform.Find(binding.path); | ||
|  |                     if (transform != null) | ||
|  |                     { | ||
|  |                         var scale = transform.localScale; | ||
|  |                         animClip.SetCurve(binding.path, typeof(Transform), k_ScaleX, SetZeroKey(curve, keyFrames, scale.x)); | ||
|  |                         animClip.SetCurve(binding.path, typeof(Transform), k_ScaleY, SetZeroKey(curve, keyFrames, scale.y)); | ||
|  |                         animClip.SetCurve(binding.path, typeof(Transform), k_ScaleZ, SetZeroKey(curve, keyFrames, scale.z)); | ||
|  |                     } | ||
|  | 
 | ||
|  |                     continue; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 // Not setting curves through AnimationUtility.SetEditorCurve to avoid reentrant | ||
|  |                 // onCurveWasModified calls in timeline.  This means we don't get sprite curves | ||
|  |                 // in the default clip right now. | ||
|  |                 if (IsAvatarBinding(binding) || IsEulerHint(binding) || binding.isPPtrCurve) | ||
|  |                     continue; | ||
|  | 
 | ||
|  |                 float floatValue; | ||
|  |                 AnimationUtility.GetFloatValue(avatarRoot, binding, out floatValue); | ||
|  |                 animClip.SetCurve(binding.path, binding.type, binding.propertyName, SetZeroKey(curve, keyFrames, floatValue)); | ||
|  |             } | ||
|  | 
 | ||
|  |             // add root motion explicitly. | ||
|  |             if (rootMotion) | ||
|  |             { | ||
|  |                 var pos = Vector3.zero;           // the appropriate root motion offsets are applied by timeline | ||
|  |                 var rot = Quaternion.identity; | ||
|  |                 animClip.SetCurve(string.Empty, typeof(Transform), k_PosX, SetZeroKey(curve, keyFrames, pos.x)); | ||
|  |                 animClip.SetCurve(string.Empty, typeof(Transform), k_PosY, SetZeroKey(curve, keyFrames, pos.y)); | ||
|  |                 animClip.SetCurve(string.Empty, typeof(Transform), k_PosZ, SetZeroKey(curve, keyFrames, pos.z)); | ||
|  |                 animClip.SetCurve(string.Empty, typeof(Transform), k_RotX, SetZeroKey(curve, keyFrames, rot.x)); | ||
|  |                 animClip.SetCurve(string.Empty, typeof(Transform), k_RotY, SetZeroKey(curve, keyFrames, rot.y)); | ||
|  |                 animClip.SetCurve(string.Empty, typeof(Transform), k_RotZ, SetZeroKey(curve, keyFrames, rot.z)); | ||
|  |                 animClip.SetCurve(string.Empty, typeof(Transform), k_RotW, SetZeroKey(curve, keyFrames, rot.w)); | ||
|  |             } | ||
|  | 
 | ||
|  |             return animClip; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool IsRootMotion(EditorCurveBinding binding) | ||
|  |         { | ||
|  |             // Root Transform TR. | ||
|  |             if (typeof(Transform).IsAssignableFrom(binding.type) && string.IsNullOrEmpty(binding.path)) | ||
|  |             { | ||
|  |                 return FastStartsWith(binding.propertyName, k_Pos)  || FastStartsWith(binding.propertyName, k_Rot); | ||
|  |             } | ||
|  | 
 | ||
|  |             // MotionCurves/RootCurves. | ||
|  |             if (binding.type == typeof(Animator)) | ||
|  |             { | ||
|  |                 return FastStartsWith(binding.propertyName, k_MotionT) || | ||
|  |                     FastStartsWith(binding.propertyName, k_MotionQ) || | ||
|  |                     FastStartsWith(binding.propertyName, k_RootT) || | ||
|  |                     FastStartsWith(binding.propertyName, k_RootQ); | ||
|  |             } | ||
|  | 
 | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         private static bool NeedsSkeletonBindings(IEnumerable<EditorCurveBinding> bindings) | ||
|  |         { | ||
|  |             foreach (var b in bindings) | ||
|  |             { | ||
|  |                 if (IsSkeletalBinding(b)) | ||
|  |                     return true; | ||
|  |             } | ||
|  | 
 | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         private static void AddBindings(IEnumerable<EditorCurveBinding> bindings) | ||
|  |         { | ||
|  |             foreach (var b in bindings) | ||
|  |             { | ||
|  |                 if (!s_CurveSet.ContainsKey(b)) | ||
|  |                     s_CurveSet[b] = 1; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         private static void AddTRBinding(GameObject root, EditorCurveBinding binding) | ||
|  |         { | ||
|  |             var t = root.transform.Find(binding.path); | ||
|  |             if (t != null) | ||
|  |             { | ||
|  |                 DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalPosition"); | ||
|  |                 DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalRotation"); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         private static void AddScaleBinding(GameObject root, EditorCurveBinding binding) | ||
|  |         { | ||
|  |             var t = root.transform.Find(binding.path); | ||
|  |             if (t != null) | ||
|  |                 DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalScale"); | ||
|  |         } | ||
|  | 
 | ||
|  |         private static bool IsEuler(EditorCurveBinding binding) | ||
|  |         { | ||
|  |             return FastStartsWith(binding.propertyName, k_EulerAnglesRaw) && | ||
|  |                 typeof(Transform).IsAssignableFrom(binding.type); | ||
|  |         } | ||
|  | 
 | ||
|  |         private static bool IsAvatarBinding(EditorCurveBinding binding) | ||
|  |         { | ||
|  |             return string.IsNullOrEmpty(binding.path) && typeof(Animator) == binding.type; | ||
|  |         } | ||
|  | 
 | ||
|  |         private static bool IsSkeletalBinding(EditorCurveBinding binding) | ||
|  |         { | ||
|  |             // skin mesh incorporates blend shapes | ||
|  |             return typeof(Transform).IsAssignableFrom(binding.type) || typeof(SkinnedMeshRenderer).IsAssignableFrom(binding.type); | ||
|  |         } | ||
|  | 
 | ||
|  |         private static AnimationCurve SetZeroKey(AnimationCurve curve, Keyframe[] keys, float val) | ||
|  |         { | ||
|  |             keys[0].value = val; | ||
|  |             curve.keys = keys; | ||
|  |             return curve; | ||
|  |         } | ||
|  | 
 | ||
|  |         private static bool IsEulerHint(EditorCurveBinding binding) | ||
|  |         { | ||
|  |             return typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName.StartsWith(k_EulerHint); | ||
|  |         } | ||
|  | 
 | ||
|  |         private static GameObject GetAvatarRoot(GameObject animatorRoot) | ||
|  |         { | ||
|  |             var animator = animatorRoot.GetComponent<Animator>(); | ||
|  |             if (animator != null && animator.avatarRoot != animatorRoot.transform) | ||
|  |                 return animator.avatarRoot.gameObject; | ||
|  |             return animatorRoot; | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | #endif |