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 { 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 s_CurveSet = new Dictionary(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 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 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 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 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 bindings) { foreach (var b in bindings) { if (IsSkeletalBinding(b)) return true; } return false; } private static void AddBindings(IEnumerable 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(); if (animator != null && animator.avatarRoot != animatorRoot.transform) return animator.avatarRoot.gameObject; return animatorRoot; } } } #endif