AnimationBindings.cs 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Text;
  6. using UnityEditor;
  7. using UnityEngine;
  8. namespace Animancer.Editor
  9. {
  10. /// <summary>[Editor-Only] The general type of object an <see cref="AnimationClip"/> can animate.</summary>
  11. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimationType
  12. ///
  13. public enum AnimationType
  14. {
  15. /// <summary>Unable to determine a type.</summary>
  16. None,
  17. /// <summary>A Humanoid rig.</summary>
  18. Humanoid,
  19. /// <summary>A Generic rig.</summary>
  20. Generic,
  21. /// <summary>A <see cref="Generic"/> rig which only animates a <see cref="SpriteRenderer.sprite"/>.</summary>
  22. Sprite,
  23. }
  24. /// <summary>[Editor-Only]
  25. /// Various utility functions relating to the properties animated by an <see cref="AnimationClip"/>.
  26. /// </summary>
  27. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimationBindings
  28. ///
  29. public class AnimationBindings : AssetPostprocessor
  30. {
  31. /************************************************************************************************************************/
  32. #region Animation Types
  33. /************************************************************************************************************************/
  34. private static Dictionary<AnimationClip, bool> _ClipToIsSprite;
  35. /// <summary>Determines the <see cref="AnimationType"/> of the specified `clip`.</summary>
  36. public static AnimationType GetAnimationType(AnimationClip clip)
  37. {
  38. if (clip == null)
  39. return AnimationType.None;
  40. if (clip.isHumanMotion)
  41. return AnimationType.Humanoid;
  42. AnimancerEditorUtilities.InitializeCleanDictionary(ref _ClipToIsSprite);
  43. if (!_ClipToIsSprite.TryGetValue(clip, out var isSprite))
  44. {
  45. var bindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
  46. for (int i = 0; i < bindings.Length; i++)
  47. {
  48. var binding = bindings[i];
  49. if (binding.type == typeof(SpriteRenderer) &&
  50. binding.propertyName == "m_Sprite")
  51. {
  52. isSprite = true;
  53. break;
  54. }
  55. }
  56. _ClipToIsSprite.Add(clip, isSprite);
  57. }
  58. return isSprite ? AnimationType.Sprite : AnimationType.Generic;
  59. }
  60. /************************************************************************************************************************/
  61. /// <summary>Determines the <see cref="AnimationType"/> of the specified `animator`.</summary>
  62. public static AnimationType GetAnimationType(Animator animator)
  63. {
  64. if (animator == null)
  65. return AnimationType.None;
  66. if (animator.isHuman)
  67. return AnimationType.Humanoid;
  68. // If all renderers are SpriteRenderers, it's a Sprite animation.
  69. // Otherwise it's Generic.
  70. var renderers = animator.GetComponentsInChildren<Renderer>();
  71. if (renderers.Length == 0)
  72. return AnimationType.Generic;
  73. for (int i = 0; i < renderers.Length; i++)
  74. {
  75. if (!(renderers[i] is SpriteRenderer))
  76. return AnimationType.Generic;
  77. }
  78. return AnimationType.Sprite;
  79. }
  80. /************************************************************************************************************************/
  81. /// <summary>Determines the <see cref="AnimationType"/> of the specified `gameObject`.</summary>
  82. public static AnimationType GetAnimationType(GameObject gameObject)
  83. {
  84. var type = AnimationType.None;
  85. var animators = gameObject.GetComponentsInChildren<Animator>();
  86. for (int i = 0; i < animators.Length; i++)
  87. {
  88. var animatorType = GetAnimationType(animators[i]);
  89. switch (animatorType)
  90. {
  91. case AnimationType.Humanoid: return AnimationType.Humanoid;
  92. case AnimationType.Generic: return AnimationType.Generic;
  93. case AnimationType.Sprite:
  94. if (type == AnimationType.None)
  95. type = AnimationType.Sprite;
  96. break;
  97. case AnimationType.None:
  98. default:
  99. break;
  100. }
  101. }
  102. return type;
  103. }
  104. /************************************************************************************************************************/
  105. #endregion
  106. /************************************************************************************************************************/
  107. private static bool _CanGatherBindings = true;
  108. /// <summary>No more than one set of bindings should be gathered per frame.</summary>
  109. private static bool CanGatherBindings()
  110. {
  111. if (!_CanGatherBindings)
  112. return false;
  113. _CanGatherBindings = false;
  114. EditorApplication.delayCall += () => _CanGatherBindings = true;
  115. return true;
  116. }
  117. /************************************************************************************************************************/
  118. private static Dictionary<GameObject, BindingData> _ObjectToBindings;
  119. /// <summary>Returns a cached <see cref="BindingData"/> representing the specified `gameObject`.</summary>
  120. /// <remarks>Note that the cache is cleared by <see cref="EditorApplication.hierarchyChanged"/>.</remarks>
  121. public static BindingData GetBindings(GameObject gameObject, bool forceGather = true)
  122. {
  123. AnimancerEditorUtilities.InitializeCleanDictionary(ref _ObjectToBindings);
  124. if (!_ObjectToBindings.TryGetValue(gameObject, out var bindings))
  125. {
  126. if (!forceGather && !CanGatherBindings())
  127. return null;
  128. bindings = new BindingData(gameObject);
  129. _ObjectToBindings.Add(gameObject, bindings);
  130. }
  131. return bindings;
  132. }
  133. /************************************************************************************************************************/
  134. private static Dictionary<AnimationClip, EditorCurveBinding[]> _ClipToBindings;
  135. /// <summary>Returns a cached array of all properties animated by the specified `clip`.</summary>
  136. public static EditorCurveBinding[] GetBindings(AnimationClip clip)
  137. {
  138. AnimancerEditorUtilities.InitializeCleanDictionary(ref _ClipToBindings);
  139. if (!_ClipToBindings.TryGetValue(clip, out var bindings))
  140. {
  141. var curveBindings = AnimationUtility.GetCurveBindings(clip);
  142. var objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
  143. bindings = new EditorCurveBinding[curveBindings.Length + objectBindings.Length];
  144. Array.Copy(curveBindings, bindings, curveBindings.Length);
  145. Array.Copy(objectBindings, 0, bindings, curveBindings.Length, objectBindings.Length);
  146. _ClipToBindings.Add(clip, bindings);
  147. }
  148. return bindings;
  149. }
  150. /************************************************************************************************************************/
  151. /// <summary>Called when Unity imports an animation.</summary>
  152. protected virtual void OnPostprocessAnimation(GameObject root, AnimationClip clip)
  153. => OnAnimationChanged(clip);
  154. /// <summary>Clears any cached values relating to the `clip` since they may no longer be correct.</summary>
  155. public static void OnAnimationChanged(AnimationClip clip)
  156. {
  157. if (_ObjectToBindings != null)
  158. foreach (var binding in _ObjectToBindings.Values)
  159. binding.OnAnimationChanged(clip);
  160. if (_ClipToBindings != null)
  161. _ClipToBindings.Remove(clip);
  162. }
  163. /************************************************************************************************************************/
  164. /// <summary>Clears all cached values in this class.</summary>
  165. public static void ClearCache()
  166. {
  167. _ObjectToBindings.Clear();
  168. _ClipToBindings.Clear();
  169. }
  170. /************************************************************************************************************************/
  171. /// <summary>
  172. /// A collection of data about the properties on a <see cref="UnityEngine.GameObject"/> and its children
  173. /// which can be animated and the relationships between those properties and the properties that individual
  174. /// <see cref="AnimationClip"/>s are trying to animate.
  175. /// </summary>
  176. public class BindingData
  177. {
  178. /************************************************************************************************************************/
  179. /// <summary>The target object that this data represents.</summary>
  180. public readonly GameObject GameObject;
  181. /// <summary>Creates a new <see cref="BindingData"/> representing the specified `gameObject`.</summary>
  182. public BindingData(GameObject gameObject) => GameObject = gameObject;
  183. /************************************************************************************************************************/
  184. private AnimationType? _ObjectType;
  185. /// <summary>The cached <see cref="AnimationType"/> of the <see cref="GameObject"/>.</summary>
  186. public AnimationType ObjectType
  187. {
  188. get
  189. {
  190. if (_ObjectType == null)
  191. _ObjectType = GetAnimationType(GameObject);
  192. return _ObjectType.Value;
  193. }
  194. }
  195. /************************************************************************************************************************/
  196. private HashSet<EditorCurveBinding> _ObjectBindings;
  197. /// <summary>The cached properties of the <see cref="GameObject"/> and its children which can be animated.</summary>
  198. public HashSet<EditorCurveBinding> ObjectBindings
  199. {
  200. get
  201. {
  202. if (_ObjectBindings == null)
  203. {
  204. _ObjectBindings = new HashSet<EditorCurveBinding>();
  205. var transforms = GameObject.GetComponentsInChildren<Transform>();
  206. for (int i = 0; i < transforms.Length; i++)
  207. {
  208. var bindings = AnimationUtility.GetAnimatableBindings(transforms[i].gameObject, GameObject);
  209. _ObjectBindings.UnionWith(bindings);
  210. }
  211. }
  212. return _ObjectBindings;
  213. }
  214. }
  215. /************************************************************************************************************************/
  216. private HashSet<string> _ObjectTransformBindings;
  217. /// <summary>
  218. /// The <see cref="EditorCurveBinding.path"/> of all <see cref="Transform"/> bindings in
  219. /// <see cref="ObjectBindings"/>.
  220. /// </summary>
  221. public HashSet<string> ObjectTransformBindings
  222. {
  223. get
  224. {
  225. if (_ObjectTransformBindings == null)
  226. {
  227. _ObjectTransformBindings = new HashSet<string>();
  228. foreach (var binding in ObjectBindings)
  229. {
  230. if (binding.type == typeof(Transform))
  231. _ObjectTransformBindings.Add(binding.path);
  232. }
  233. }
  234. return _ObjectTransformBindings;
  235. }
  236. }
  237. /************************************************************************************************************************/
  238. /// <summary>
  239. /// Determines the <see cref="MatchType"/> representing the properties animated by the `state` in
  240. /// comparison to the properties that actually exist on the target <see cref="GameObject"/> and its
  241. /// children.
  242. /// <para></para>
  243. /// Also compiles a `message` explaining the differences if that paraneter is not null.
  244. /// </summary>
  245. public MatchType GetMatchType(Animator animator, AnimancerState state, StringBuilder message, bool forceGather = true)
  246. {
  247. using (ObjectPool.Disposable.AcquireSet<AnimationClip>(out var clips))
  248. {
  249. state.GatherAnimationClips(clips);
  250. var bindings = message != null ? new Dictionary<EditorCurveBinding, bool>() : null;
  251. var existingBindings = 0;
  252. var match = default(MatchType);
  253. if (animator.avatar == null)
  254. {
  255. message?.AppendLine()
  256. .Append($"{LinePrefix}The {nameof(Animator)} has no {nameof(Avatar)}.");
  257. if (animator.isHuman)
  258. match = MatchType.Error;
  259. }
  260. foreach (var clip in clips)
  261. {
  262. var clipMatch = GetMatchType(clip, message, bindings, ref existingBindings, forceGather);
  263. if (match < clipMatch)
  264. match = clipMatch;
  265. }
  266. AppendBindings(message, bindings, existingBindings);
  267. return match;
  268. }
  269. }
  270. /************************************************************************************************************************/
  271. private const string LinePrefix = "- ";
  272. private Dictionary<AnimationClip, MatchType> _BindingMatches;
  273. /// <summary>
  274. /// Determines the <see cref="MatchType"/> representing the properties animated by the `clip` in
  275. /// comparison to the properties that actually exist on the target <see cref="GameObject"/> and its
  276. /// children.
  277. /// <para></para>
  278. /// Also compiles a `message` explaining the differences if that paraneter is not null.
  279. /// </summary>
  280. public MatchType GetMatchType(AnimationClip clip, StringBuilder message,
  281. Dictionary<EditorCurveBinding, bool> bindingsInMessage, ref int existingBindings, bool forceGather = true)
  282. {
  283. AnimancerEditorUtilities.InitializeCleanDictionary(ref _BindingMatches);
  284. if (_BindingMatches.TryGetValue(clip, out var match))
  285. {
  286. if (bindingsInMessage == null)
  287. return match;
  288. }
  289. else if (!forceGather && !CanGatherBindings())
  290. {
  291. return MatchType.Unknown;
  292. }
  293. var objectType = ObjectType;
  294. var clipType = GetAnimationType(clip);
  295. if (clipType != objectType)
  296. {
  297. if (message != null)
  298. {
  299. message.AppendLine()
  300. .Append($"{LinePrefix}This message does not necessarily mean anything is wrong," +
  301. $" but if something is wrong then this might help you identify the problem.");
  302. message.AppendLine()
  303. .Append($"{LinePrefix}The {nameof(AnimationType)} of the '")
  304. .Append(clip.name)
  305. .Append("' animation is ")
  306. .Append(clipType)
  307. .Append(" while the '")
  308. .Append(GameObject.name)
  309. .Append("' Rig is ")
  310. .Append(objectType)
  311. .Append(". See the documentation for more information about Animation Types:" +
  312. $" {Strings.DocsURLs.Inspector}#animation-types");
  313. }
  314. switch (clipType)
  315. {
  316. default:
  317. case AnimationType.None:
  318. case AnimationType.Humanoid:
  319. match = MatchType.Error;
  320. if (message == null)
  321. goto SetMatch;
  322. else
  323. break;
  324. case AnimationType.Generic:
  325. case AnimationType.Sprite:
  326. match = MatchType.Warning;
  327. break;
  328. }
  329. }
  330. var bindingMatch = GetMatchType(GetBindings(clip), bindingsInMessage, ref existingBindings);
  331. if (match < bindingMatch)
  332. match = bindingMatch;
  333. SetMatch:
  334. _BindingMatches[clip] = match;
  335. return match;
  336. }
  337. /************************************************************************************************************************/
  338. private MatchType GetMatchType(EditorCurveBinding[] bindings,
  339. Dictionary<EditorCurveBinding, bool> bindingsInMessage, ref int existingBindings)
  340. {
  341. if (bindings.Length == 0)
  342. return MatchType.Empty;
  343. var bindingCount = bindings.Length;
  344. var matchCount = 0;
  345. for (int i = 0; i < bindings.Length; i++)
  346. {
  347. var binding = bindings[i];
  348. if (ShouldIgnoreBinding(binding))
  349. {
  350. bindingCount--;
  351. continue;
  352. }
  353. var matches = MatchesObjectBinding(binding);
  354. if (matches)
  355. matchCount++;
  356. if (bindingsInMessage != null && !bindingsInMessage.ContainsKey(binding))
  357. {
  358. bindingsInMessage.Add(binding, matches);
  359. if (matches)
  360. existingBindings++;
  361. }
  362. }
  363. if (matchCount == bindingCount)
  364. return MatchType.Correct;
  365. else if (matchCount != 0)
  366. return MatchType.Warning;
  367. else
  368. return MatchType.Error;
  369. }
  370. /************************************************************************************************************************/
  371. private static bool ShouldIgnoreBinding(EditorCurveBinding binding)
  372. {
  373. if (binding.type == typeof(Animator) && string.IsNullOrEmpty(binding.path))
  374. {
  375. switch (binding.propertyName)
  376. {
  377. case "RootQ.w":
  378. case "RootQ.x":
  379. case "RootQ.y":
  380. case "RootQ.z":
  381. case "RootT.x":
  382. case "RootT.y":
  383. case "RootT.z":
  384. return true;
  385. }
  386. }
  387. return false;
  388. }
  389. /************************************************************************************************************************/
  390. private bool MatchesObjectBinding(EditorCurveBinding binding)
  391. {
  392. if (binding.type == typeof(Transform))
  393. {
  394. switch (binding.propertyName)
  395. {
  396. case "m_LocalEulerAngles.x":
  397. case "m_LocalEulerAngles.y":
  398. case "m_LocalEulerAngles.z":
  399. case "localEulerAnglesRaw.x":
  400. case "localEulerAnglesRaw.y":
  401. case "localEulerAnglesRaw.z":
  402. return ObjectTransformBindings.Contains(binding.path);
  403. }
  404. }
  405. return ObjectBindings.Contains(binding);
  406. }
  407. /************************************************************************************************************************/
  408. private static void AppendBindings(StringBuilder message, Dictionary<EditorCurveBinding, bool> bindings, int existingBindings)
  409. {
  410. if (bindings == null ||
  411. bindings.Count <= existingBindings)
  412. return;
  413. message.AppendLine()
  414. .Append(LinePrefix + "This message has been copied to the clipboard" +
  415. " (in case it is too long for Unity to display in the Console).");
  416. message.AppendLine()
  417. .Append(LinePrefix)
  418. .Append(bindings.Count - existingBindings)
  419. .Append(" of ")
  420. .Append(bindings.Count)
  421. .Append(" bindings do not exist in the Rig: [x] = Missing, [o] = Exists");
  422. using (ObjectPool.Disposable.AcquireList<EditorCurveBinding>(out var sortedBindings))
  423. {
  424. sortedBindings.AddRange(bindings.Keys);
  425. sortedBindings.Sort((a, b) =>
  426. {
  427. var result = a.path.CompareTo(b.path);
  428. if (result != 0)
  429. return result;
  430. if (a.type != b.type)
  431. {
  432. if (a.type == typeof(Transform))
  433. return -1;
  434. else if (b.type == typeof(Transform))
  435. return 1;
  436. result = a.type.Name.CompareTo(b.type.Name);
  437. if (result != 0)
  438. return result;
  439. }
  440. return a.propertyName.CompareTo(b.propertyName);
  441. });
  442. var previousBinding = default(EditorCurveBinding);
  443. var pathSplit = Array.Empty<string>();
  444. for (int iBinding = 0; iBinding < sortedBindings.Count; iBinding++)
  445. {
  446. var binding = sortedBindings[iBinding];
  447. if (binding.path != previousBinding.path)
  448. {
  449. var newPathSplit = binding.path.Split('/');
  450. var iSegment = Math.Min(newPathSplit.Length - 1, pathSplit.Length - 1);
  451. for (; iSegment >= 0; iSegment--)
  452. {
  453. if (pathSplit[iSegment] == newPathSplit[iSegment])
  454. break;
  455. }
  456. iSegment++;
  457. if (!string.IsNullOrEmpty(binding.path))
  458. {
  459. for (; iSegment < newPathSplit.Length; iSegment++)
  460. {
  461. message.AppendLine();
  462. for (int iIndent = 0; iIndent < iSegment; iIndent++)
  463. message.Append(Strings.Indent);
  464. message.Append("> ").Append(newPathSplit[iSegment]);
  465. }
  466. }
  467. pathSplit = newPathSplit;
  468. }
  469. if (TransformBindings.Append(bindings, sortedBindings, ref iBinding, message))
  470. continue;
  471. message.AppendLine();
  472. if (binding.path.Length > 0)
  473. for (int iIndent = 0; iIndent < pathSplit.Length; iIndent++)
  474. message.Append(Strings.Indent);
  475. message
  476. .Append(bindings[binding] ? "[o] " : "[x] ")
  477. .Append(binding.type.GetNameCS(false))
  478. .Append('.')
  479. .Append(binding.propertyName);
  480. previousBinding = binding;
  481. }
  482. }
  483. }
  484. /************************************************************************************************************************/
  485. private static class TransformBindings
  486. {
  487. [Flags]
  488. private enum Flags
  489. {
  490. None = 0,
  491. PositionX = 1 << 0,
  492. PositionY = 1 << 1,
  493. PositionZ = 1 << 2,
  494. RotationX = 1 << 3,
  495. RotationY = 1 << 4,
  496. RotationZ = 1 << 5,
  497. RotationW = 1 << 6,
  498. EulerX = 1 << 7,
  499. EulerY = 1 << 8,
  500. EulerZ = 1 << 9,
  501. ScaleX = 1 << 10,
  502. ScaleY = 1 << 11,
  503. ScaleZ = 1 << 12,
  504. }
  505. private static bool HasAll(Flags flag, Flags has) => (flag & has) == has;
  506. private static bool HasAny(Flags flag, Flags has) => (flag & has) != Flags.None;
  507. /************************************************************************************************************************/
  508. private static readonly Flags[]
  509. PositionFlags = { Flags.PositionX, Flags.PositionY, Flags.PositionZ },
  510. RotationFlags = { Flags.RotationX, Flags.RotationY, Flags.RotationZ, Flags.RotationW },
  511. EulerFlags = { Flags.EulerX, Flags.EulerY, Flags.EulerZ },
  512. ScaleFlags = { Flags.ScaleX, Flags.ScaleY, Flags.ScaleZ };
  513. /************************************************************************************************************************/
  514. public static bool Append(Dictionary<EditorCurveBinding, bool> bindings,
  515. List<EditorCurveBinding> sortedBindings, ref int index, StringBuilder message)
  516. {
  517. var binding = sortedBindings[index];
  518. if (binding.type != typeof(Transform))
  519. return false;
  520. if (string.IsNullOrEmpty(binding.path))
  521. message.AppendLine().Append('>');
  522. else
  523. message.Append(':');
  524. using (ObjectPool.Disposable.AcquireList<EditorCurveBinding>(out var otherBindings))
  525. {
  526. var flags = GetFlags(bindings, sortedBindings, ref index, otherBindings, out var anyExists);
  527. message.Append(anyExists ? " [o]" : " [x]");
  528. var first = true;
  529. AppendProperty(message, ref first, flags, PositionFlags, "position", "xyz");
  530. AppendProperty(message, ref first, flags, RotationFlags, "rotation", "wxyz");
  531. AppendProperty(message, ref first, flags, EulerFlags, "euler", "xyz");
  532. AppendProperty(message, ref first, flags, ScaleFlags, "scale", "xyz");
  533. for (int i = 0; i < otherBindings.Count; i++)
  534. {
  535. if (anyExists)
  536. message.Append(',');
  537. binding = otherBindings[i];
  538. message
  539. .Append(" [")
  540. .Append(bindings[binding] ? 'o' : 'x')
  541. .Append("] ")
  542. .Append(binding.propertyName);
  543. }
  544. }
  545. return true;
  546. }
  547. /************************************************************************************************************************/
  548. private static Flags GetFlags(Dictionary<EditorCurveBinding, bool> bindings,
  549. List<EditorCurveBinding> sortedBindings, ref int index, List<EditorCurveBinding> otherBindings, out bool anyExists)
  550. {
  551. var flags = Flags.None;
  552. anyExists = false;
  553. var binding = sortedBindings[index];
  554. CheckFlags:
  555. switch (binding.propertyName)
  556. {
  557. case "m_LocalPosition.x": flags |= Flags.PositionX; break;
  558. case "m_LocalPosition.y": flags |= Flags.PositionY; break;
  559. case "m_LocalPosition.z": flags |= Flags.PositionZ; break;
  560. case "m_LocalRotation.x": flags |= Flags.RotationX; break;
  561. case "m_LocalRotation.y": flags |= Flags.RotationY; break;
  562. case "m_LocalRotation.z": flags |= Flags.RotationZ; break;
  563. case "m_LocalRotation.w": flags |= Flags.RotationW; break;
  564. case "m_LocalEulerAngles.x": flags |= Flags.EulerX; break;
  565. case "m_LocalEulerAngles.y": flags |= Flags.EulerY; break;
  566. case "m_LocalEulerAngles.z": flags |= Flags.EulerZ; break;
  567. case "localEulerAnglesRaw.x": flags |= Flags.EulerX; break;
  568. case "localEulerAnglesRaw.y": flags |= Flags.EulerY; break;
  569. case "localEulerAnglesRaw.z": flags |= Flags.EulerZ; break;
  570. case "m_LocalScale.x": flags |= Flags.ScaleX; break;
  571. case "m_LocalScale.y": flags |= Flags.ScaleY; break;
  572. case "m_LocalScale.z": flags |= Flags.ScaleZ; break;
  573. default: otherBindings.Add(binding); goto SkipFlagExistence;
  574. }
  575. if (bindings != null &&
  576. bindings.TryGetValue(binding, out var exists))
  577. {
  578. bindings = null;
  579. anyExists = exists;
  580. }
  581. SkipFlagExistence:
  582. if (index + 1 < sortedBindings.Count)
  583. {
  584. var nextBinding = sortedBindings[index + 1];
  585. if (nextBinding.type == typeof(Transform) &&
  586. nextBinding.path == binding.path)
  587. {
  588. index++;
  589. binding = nextBinding;
  590. goto CheckFlags;
  591. }
  592. }
  593. return flags;
  594. }
  595. /************************************************************************************************************************/
  596. private static void AppendProperty(StringBuilder message, ref bool first, Flags flags,
  597. Flags[] propertyFlags, string propertyName, string flagNames)
  598. {
  599. var all = Flags.None;
  600. for (int i = 0; i < propertyFlags.Length; i++)
  601. all |= propertyFlags[i];
  602. if (!HasAny(flags, all))
  603. return;
  604. AppendSeparator(message, ref first, " ", ", ").Append(propertyName);
  605. if (!HasAll(flags, all))
  606. {
  607. var firstSub = true;
  608. for (int i = 0; i < propertyFlags.Length; i++)
  609. {
  610. if (HasAll(flags, propertyFlags[i]))
  611. {
  612. AppendSeparator(message, ref firstSub, "(", ", ").Append(flagNames[i]);
  613. }
  614. }
  615. message.Append(')');
  616. }
  617. }
  618. /************************************************************************************************************************/
  619. private static StringBuilder AppendSeparator(StringBuilder message, ref bool first, string prefix, string separator)
  620. {
  621. if (first)
  622. {
  623. first = false;
  624. return message.Append(prefix);
  625. }
  626. else return message.Append(separator);
  627. }
  628. /************************************************************************************************************************/
  629. }
  630. /************************************************************************************************************************/
  631. /// <summary>
  632. /// Logs a description of the issues found when comparing the properties animated by the `state` to the
  633. /// properties that actually exist on the target <see cref="GameObject"/> and its children.
  634. /// </summary>
  635. public void LogIssues(AnimancerState state, MatchType match)
  636. {
  637. var animator = state.Root?.Component?.Animator;
  638. var newMatch = match;
  639. var message = ObjectPool.AcquireStringBuilder();
  640. switch (match)
  641. {
  642. default:
  643. case MatchType.Unknown:
  644. message.Append("The animation bindings are still being checked.");
  645. Debug.Log(EditorGUIUtility.systemCopyBuffer = message.ReleaseToString(), animator);
  646. break;
  647. case MatchType.Correct:
  648. message.Append("No issues were found when comparing the properties animated by '")
  649. .Append(state)
  650. .Append("' to the Rig of '")
  651. .Append(animator.name)
  652. .Append("'.");
  653. Debug.Log(EditorGUIUtility.systemCopyBuffer = message.ReleaseToString(), animator);
  654. break;
  655. case MatchType.Empty:
  656. message.Append("'")
  657. .Append(state)
  658. .Append("' does not animate any properties so it will not do anything.");
  659. Debug.Log(EditorGUIUtility.systemCopyBuffer = message.ReleaseToString(), animator);
  660. break;
  661. case MatchType.Warning:
  662. message.Append("Possible Bug Detected: some of the details of '")
  663. .Append(state)
  664. .Append("' do not match the Rig of '")
  665. .Append(animator.name)
  666. .Append("' so the animation might not work correctly.");
  667. newMatch = GetMatchType(animator, state, message);
  668. Debug.LogWarning(EditorGUIUtility.systemCopyBuffer = message.ReleaseToString(), animator);
  669. break;
  670. case MatchType.Error:
  671. message.Append("Possible Bug Detected: the details of '")
  672. .Append(state)
  673. .Append("' do not match the Rig of '")
  674. .Append(animator.name)
  675. .Append("' so the animation might not work correctly.");
  676. newMatch = GetMatchType(animator, state, message);
  677. Debug.LogError(EditorGUIUtility.systemCopyBuffer = message.ReleaseToString(), animator);
  678. break;
  679. }
  680. if (newMatch != match)
  681. Debug.LogWarning($"{nameof(MatchType)} changed from {match} to {newMatch}" +
  682. " between the initial check and the button press.");
  683. if (animator != null)
  684. _ObjectToBindings.Remove(animator.gameObject);
  685. }
  686. /************************************************************************************************************************/
  687. /// <summary>[Internal] Removes any cached values relating to the `clip`.</summary>
  688. internal void OnAnimationChanged(AnimationClip clip)
  689. {
  690. if (_BindingMatches != null)
  691. _BindingMatches.Remove(clip);
  692. }
  693. /************************************************************************************************************************/
  694. }
  695. /************************************************************************************************************************/
  696. #region GUI
  697. /************************************************************************************************************************/
  698. /// <summary>
  699. /// A summary of the compatability between the properties animated by an <see cref="AnimationClip"/> and the
  700. /// properties that actually exist on a particular <see cref="GameObject"/> (and its children).
  701. /// </summary>
  702. public enum MatchType
  703. {
  704. /// <summary>All properties exist.</summary>
  705. Correct,
  706. /// <summary>Not yet checked.</summary>
  707. Unknown,
  708. /// <summary>The <see cref="AnimationClip"/> does not animate anything.</summary>
  709. Empty,
  710. /// <summary>Some of the animated properties do not exist on the object.</summary>
  711. Warning,
  712. /// <summary>None of the animated properties exist on the object.</summary>
  713. Error,
  714. }
  715. /************************************************************************************************************************/
  716. private static readonly GUIStyle ButtonStyle = new GUIStyle();// No margins or anything.
  717. /************************************************************************************************************************/
  718. private static readonly int ButtonHash = "Button".GetHashCode();
  719. /// <summary>
  720. /// Draws a <see cref="GUI.Button(Rect, GUIContent, GUIStyle)"/> indicating the <see cref="MatchType"/> of the
  721. /// `state` compared to the object it is being played on.
  722. /// <para></para>
  723. /// Clicking the button calls <see cref="BindingData.LogIssues"/>.
  724. /// </summary>
  725. public static void DoBindingMatchGUI(ref Rect area, AnimancerState state)
  726. {
  727. if (AnimancerEditorUtilities.IsChangingPlayMode ||
  728. !AnimancerPlayableDrawer.VerifyAnimationBindings ||
  729. state.Root == null ||
  730. state.Root.Component == null ||
  731. state.Root.Component.Animator == null)
  732. goto Hide;
  733. var animator = state.Root.Component.Animator;
  734. var bindings = GetBindings(animator.gameObject, false);
  735. if (bindings == null)
  736. goto Hide;
  737. var match = bindings.GetMatchType(animator, state, null, false);
  738. var icon = GetIcon(match);
  739. if (icon == null)
  740. goto Hide;
  741. var buttonArea = AnimancerGUI.StealFromRight(ref area, area.height, AnimancerGUI.StandardSpacing);
  742. if (GUI.Button(buttonArea, icon, ButtonStyle))
  743. bindings.LogIssues(state, match);
  744. return;
  745. Hide:
  746. GUI.Button(default, GUIContent.none, ButtonStyle);
  747. }
  748. /************************************************************************************************************************/
  749. #endregion
  750. /************************************************************************************************************************/
  751. #region Icons
  752. /************************************************************************************************************************/
  753. /// <summary>Get an icon = corresponding to the specified <see cref="MatchType"/>.</summary>
  754. public static Texture GetIcon(MatchType match)
  755. {
  756. switch (match)
  757. {
  758. default:
  759. case MatchType.Correct: return null;
  760. case MatchType.Unknown: return Icons.GetUnknown();
  761. case MatchType.Empty: return Icons.Empty;
  762. case MatchType.Warning: return Icons.Warning;
  763. case MatchType.Error: return Icons.Error;
  764. }
  765. }
  766. /************************************************************************************************************************/
  767. /// <summary>A unit test to make sure that the icons are properly loaded.</summary>
  768. public static void AssertIcons()
  769. {
  770. var matchTypes = (MatchType[])Enum.GetValues(typeof(MatchType));
  771. for (int i = 0; i < matchTypes.Length; i++)
  772. {
  773. var match = matchTypes[i];
  774. var icon = GetIcon(match);
  775. switch (matchTypes[i])
  776. {
  777. case MatchType.Correct:
  778. Debug.Assert(icon == null, $"The icon for {nameof(MatchType)}.{match} should be null.");
  779. break;
  780. case MatchType.Unknown:
  781. for (int iIcon = 0; iIcon < Icons.Unknown.Length; iIcon++)
  782. Debug.Assert(Icons.Unknown[iIcon] != null,
  783. $"The icon for {nameof(MatchType)}.{nameof(MatchType.Unknown)}[{iIcon}] was not loaded.");
  784. break;
  785. case MatchType.Empty:
  786. case MatchType.Warning:
  787. case MatchType.Error:
  788. default:
  789. Debug.Assert(icon != null, $"The icon for {nameof(MatchType)}.{match} was not loaded.");
  790. break;
  791. }
  792. }
  793. }
  794. /************************************************************************************************************************/
  795. private static class Icons
  796. {
  797. /************************************************************************************************************************/
  798. public static readonly Texture Empty = AnimancerGUI.LoadIcon("console.infoicon.sml");
  799. public static readonly Texture Warning = AnimancerGUI.LoadIcon("console.warnicon.sml");
  800. public static readonly Texture Error = AnimancerGUI.LoadIcon("console.erroricon.sml");
  801. /************************************************************************************************************************/
  802. public static readonly Texture[] Unknown =
  803. {
  804. AnimancerGUI.LoadIcon("WaitSpin00"),
  805. AnimancerGUI.LoadIcon("WaitSpin01"),
  806. AnimancerGUI.LoadIcon("WaitSpin02"),
  807. AnimancerGUI.LoadIcon("WaitSpin03"),
  808. AnimancerGUI.LoadIcon("WaitSpin04"),
  809. AnimancerGUI.LoadIcon("WaitSpin05"),
  810. AnimancerGUI.LoadIcon("WaitSpin06"),
  811. AnimancerGUI.LoadIcon("WaitSpin07"),
  812. AnimancerGUI.LoadIcon("WaitSpin08"),
  813. AnimancerGUI.LoadIcon("WaitSpin09"),
  814. AnimancerGUI.LoadIcon("WaitSpin10"),
  815. AnimancerGUI.LoadIcon("WaitSpin11"),
  816. };
  817. public static Texture GetUnknown()
  818. {
  819. var i = (int)(EditorApplication.timeSinceStartup * 10) % Unknown.Length;
  820. return Unknown[i];
  821. }
  822. /************************************************************************************************************************/
  823. }
  824. /************************************************************************************************************************/
  825. #endregion
  826. /************************************************************************************************************************/
  827. }
  828. }
  829. #endif