A game about forced loneliness, made by TACStudios
1using System;
2#if UNITY_EDITOR
3using System.Collections.Generic;
4using UnityEditor;
5
6
7namespace UnityEngine.Timeline
8{
9 static class AnimationPreviewUtilities
10 {
11 private const string k_PosX = "m_LocalPosition.x";
12 private const string k_PosY = "m_LocalPosition.y";
13 private const string k_PosZ = "m_LocalPosition.z";
14 private const string k_RotX = "m_LocalRotation.x";
15 private const string k_RotY = "m_LocalRotation.y";
16 private const string k_RotZ = "m_LocalRotation.z";
17 private const string k_RotW = "m_LocalRotation.w";
18 private const string k_ScaleX = "m_LocalScale.x";
19 private const string k_ScaleY = "m_LocalScale.y";
20 private const string k_ScaleZ = "m_LocalScale.z";
21 private const string k_EulerAnglesRaw = "localEulerAnglesRaw";
22 private const string k_EulerHint = "m_LocalEulerAnglesHint";
23 private const string k_Pos = "m_LocalPosition";
24 private const string k_Rot = "m_LocalRotation";
25 private const string k_MotionT = "MotionT";
26 private const string k_MotionQ = "MotionQ";
27 private const string k_RootT = "RootT";
28 private const string k_RootQ = "RootQ";
29
30
31 internal static Object s_PreviewDriver;
32
33
34 internal class EditorCurveBindingComparer : IEqualityComparer<EditorCurveBinding>
35 {
36 public bool Equals(EditorCurveBinding x, EditorCurveBinding y) { return x.path.Equals(y.path) && x.type == y.type && x.propertyName == y.propertyName; }
37 public int GetHashCode(EditorCurveBinding obj)
38 {
39 return obj.propertyName.GetHashCode() ^ obj.path.GetHashCode();
40 }
41
42 public static readonly EditorCurveBindingComparer Instance = new EditorCurveBindingComparer();
43 }
44
45 // a dictionary is faster than a hashset, because the capacity can be pre-set
46 private static readonly Dictionary<EditorCurveBinding, int> s_CurveSet = new Dictionary<EditorCurveBinding, int>(10000, EditorCurveBindingComparer.Instance);
47 private static readonly AnimatorBindingCache s_BindingCache = new AnimatorBindingCache();
48
49 // string.StartsWith is slow (https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity5.html)
50 // hand rolled version has best performance.
51 private static bool FastStartsWith(string a, string toCompare)
52 {
53 int aLen = a.Length;
54 int bLen = toCompare.Length;
55
56 int ap = 0;
57 int bp = 0;
58
59 while (ap < aLen && bp < bLen && a[ap] == toCompare[bp])
60 {
61 ap++;
62 bp++;
63 }
64
65 return (bp == bLen);
66 }
67
68 public static void ClearCaches()
69 {
70 s_BindingCache.Clear();
71 s_CurveSet.Clear();
72 }
73
74 public static EditorCurveBinding[] GetBindings(GameObject animatorRoot, IEnumerable<AnimationClip> clips)
75 {
76 s_CurveSet.Clear();
77 foreach (var clip in clips)
78 {
79 AddBindings(s_BindingCache.GetCurveBindings(clip));
80 }
81
82 // if we have a transform binding, bind the entire skeleton
83 if (NeedsSkeletonBindings(s_CurveSet.Keys))
84 AddBindings(s_BindingCache.GetAnimatorBindings(animatorRoot));
85
86 var bindings = new EditorCurveBinding[s_CurveSet.Keys.Count];
87 s_CurveSet.Keys.CopyTo(bindings, 0);
88 return bindings;
89 }
90
91 public static int GetClipHash(List<AnimationClip> clips)
92 {
93 int hash = 0;
94
95 foreach (var clip in clips)
96 {
97 var stats = AnimationUtility.GetAnimationClipStats(clip);
98 hash = HashUtility.CombineHash(hash, clip.GetHashCode(), stats.clips, stats.size, stats.totalCurves);
99 }
100 return hash;
101 }
102
103 public static void PreviewFromCurves(GameObject animatorRoot, IEnumerable<EditorCurveBinding> keys)
104 {
105 if (!AnimationMode.InAnimationMode())
106 return;
107
108 var avatarRoot = GetAvatarRoot(animatorRoot);
109 foreach (var binding in keys)
110 {
111 if (IsAvatarBinding(binding) || IsEuler(binding))
112 continue;
113
114 bool isTransform = typeof(Transform).IsAssignableFrom(binding.type);
115 if (isTransform && binding.propertyName.Equals(AnimatorBindingCache.TRPlaceHolder))
116 AddTRBinding(animatorRoot, binding);
117 else if (isTransform && binding.propertyName.Equals(AnimatorBindingCache.ScalePlaceholder))
118 AddScaleBinding(animatorRoot, binding);
119 else
120 AnimationMode.AddEditorCurveBinding(avatarRoot, binding);
121 }
122 }
123
124 public static AnimationClip CreateDefaultClip(GameObject animatorRoot, IEnumerable<EditorCurveBinding> keys)
125 {
126 AnimationClip animClip = new AnimationClip() { name = "DefaultPose" };
127 var keyFrames = new[] {new Keyframe(0, 0)};
128 var curve = new AnimationCurve(keyFrames);
129 bool rootMotion = false;
130 var avatarRoot = GetAvatarRoot(animatorRoot);
131
132 foreach (var binding in keys)
133 {
134 if (IsRootMotion(binding))
135 {
136 rootMotion = true;
137 continue;
138 }
139
140 if (typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName.Equals(AnimatorBindingCache.TRPlaceHolder))
141 {
142 if (string.IsNullOrEmpty(binding.path))
143 rootMotion = true;
144 else
145 {
146 var transform = animatorRoot.transform.Find(binding.path);
147 if (transform != null)
148 {
149 var pos = transform.localPosition;
150 var rot = transform.localRotation;
151 animClip.SetCurve(binding.path, typeof(Transform), k_PosX, SetZeroKey(curve, keyFrames, pos.x));
152 animClip.SetCurve(binding.path, typeof(Transform), k_PosY, SetZeroKey(curve, keyFrames, pos.y));
153 animClip.SetCurve(binding.path, typeof(Transform), k_PosZ, SetZeroKey(curve, keyFrames, pos.z));
154 animClip.SetCurve(binding.path, typeof(Transform), k_RotX, SetZeroKey(curve, keyFrames, rot.x));
155 animClip.SetCurve(binding.path, typeof(Transform), k_RotY, SetZeroKey(curve, keyFrames, rot.y));
156 animClip.SetCurve(binding.path, typeof(Transform), k_RotZ, SetZeroKey(curve, keyFrames, rot.z));
157 animClip.SetCurve(binding.path, typeof(Transform), k_RotW, SetZeroKey(curve, keyFrames, rot.w));
158 }
159 }
160
161 continue;
162 }
163
164 if (typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName == AnimatorBindingCache.ScalePlaceholder)
165 {
166 var transform = animatorRoot.transform.Find(binding.path);
167 if (transform != null)
168 {
169 var scale = transform.localScale;
170 animClip.SetCurve(binding.path, typeof(Transform), k_ScaleX, SetZeroKey(curve, keyFrames, scale.x));
171 animClip.SetCurve(binding.path, typeof(Transform), k_ScaleY, SetZeroKey(curve, keyFrames, scale.y));
172 animClip.SetCurve(binding.path, typeof(Transform), k_ScaleZ, SetZeroKey(curve, keyFrames, scale.z));
173 }
174
175 continue;
176 }
177
178 // Not setting curves through AnimationUtility.SetEditorCurve to avoid reentrant
179 // onCurveWasModified calls in timeline. This means we don't get sprite curves
180 // in the default clip right now.
181 if (IsAvatarBinding(binding) || IsEulerHint(binding) || binding.isPPtrCurve)
182 continue;
183
184 float floatValue;
185 AnimationUtility.GetFloatValue(avatarRoot, binding, out floatValue);
186 animClip.SetCurve(binding.path, binding.type, binding.propertyName, SetZeroKey(curve, keyFrames, floatValue));
187 }
188
189 // add root motion explicitly.
190 if (rootMotion)
191 {
192 var pos = Vector3.zero; // the appropriate root motion offsets are applied by timeline
193 var rot = Quaternion.identity;
194 animClip.SetCurve(string.Empty, typeof(Transform), k_PosX, SetZeroKey(curve, keyFrames, pos.x));
195 animClip.SetCurve(string.Empty, typeof(Transform), k_PosY, SetZeroKey(curve, keyFrames, pos.y));
196 animClip.SetCurve(string.Empty, typeof(Transform), k_PosZ, SetZeroKey(curve, keyFrames, pos.z));
197 animClip.SetCurve(string.Empty, typeof(Transform), k_RotX, SetZeroKey(curve, keyFrames, rot.x));
198 animClip.SetCurve(string.Empty, typeof(Transform), k_RotY, SetZeroKey(curve, keyFrames, rot.y));
199 animClip.SetCurve(string.Empty, typeof(Transform), k_RotZ, SetZeroKey(curve, keyFrames, rot.z));
200 animClip.SetCurve(string.Empty, typeof(Transform), k_RotW, SetZeroKey(curve, keyFrames, rot.w));
201 }
202
203 return animClip;
204 }
205
206 public static bool IsRootMotion(EditorCurveBinding binding)
207 {
208 // Root Transform TR.
209 if (typeof(Transform).IsAssignableFrom(binding.type) && string.IsNullOrEmpty(binding.path))
210 {
211 return FastStartsWith(binding.propertyName, k_Pos) || FastStartsWith(binding.propertyName, k_Rot);
212 }
213
214 // MotionCurves/RootCurves.
215 if (binding.type == typeof(Animator))
216 {
217 return FastStartsWith(binding.propertyName, k_MotionT) ||
218 FastStartsWith(binding.propertyName, k_MotionQ) ||
219 FastStartsWith(binding.propertyName, k_RootT) ||
220 FastStartsWith(binding.propertyName, k_RootQ);
221 }
222
223 return false;
224 }
225
226 private static bool NeedsSkeletonBindings(IEnumerable<EditorCurveBinding> bindings)
227 {
228 foreach (var b in bindings)
229 {
230 if (IsSkeletalBinding(b))
231 return true;
232 }
233
234 return false;
235 }
236
237 private static void AddBindings(IEnumerable<EditorCurveBinding> bindings)
238 {
239 foreach (var b in bindings)
240 {
241 if (!s_CurveSet.ContainsKey(b))
242 s_CurveSet[b] = 1;
243 }
244 }
245
246 private static void AddTRBinding(GameObject root, EditorCurveBinding binding)
247 {
248 var t = root.transform.Find(binding.path);
249 if (t != null)
250 {
251 DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalPosition");
252 DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalRotation");
253 }
254 }
255
256 private static void AddScaleBinding(GameObject root, EditorCurveBinding binding)
257 {
258 var t = root.transform.Find(binding.path);
259 if (t != null)
260 DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalScale");
261 }
262
263 private static bool IsEuler(EditorCurveBinding binding)
264 {
265 return FastStartsWith(binding.propertyName, k_EulerAnglesRaw) &&
266 typeof(Transform).IsAssignableFrom(binding.type);
267 }
268
269 private static bool IsAvatarBinding(EditorCurveBinding binding)
270 {
271 return string.IsNullOrEmpty(binding.path) && typeof(Animator) == binding.type;
272 }
273
274 private static bool IsSkeletalBinding(EditorCurveBinding binding)
275 {
276 // skin mesh incorporates blend shapes
277 return typeof(Transform).IsAssignableFrom(binding.type) || typeof(SkinnedMeshRenderer).IsAssignableFrom(binding.type);
278 }
279
280 private static AnimationCurve SetZeroKey(AnimationCurve curve, Keyframe[] keys, float val)
281 {
282 keys[0].value = val;
283 curve.keys = keys;
284 return curve;
285 }
286
287 private static bool IsEulerHint(EditorCurveBinding binding)
288 {
289 return typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName.StartsWith(k_EulerHint);
290 }
291
292 private static GameObject GetAvatarRoot(GameObject animatorRoot)
293 {
294 var animator = animatorRoot.GetComponent<Animator>();
295 if (animator != null && animator.avatarRoot != animatorRoot.transform)
296 return animator.avatarRoot.gameObject;
297 return animatorRoot;
298 }
299 }
300}
301#endif