TransitionLibraryDefinition.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. using UnityEngine;
  6. namespace Animancer.TransitionLibraries
  7. {
  8. /// <summary>[<see cref="SerializableAttribute"/>]
  9. /// A library of transitions and other details which can create a <see cref="TransitionLibrary"/>.
  10. /// </summary>
  11. /// <remarks>
  12. /// <strong>Documentation:</strong>
  13. /// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions/libraries">
  14. /// Transition Libraries</see>
  15. /// </remarks>
  16. /// https://kybernetik.com.au/animancer/api/Animancer.TransitionLibraries/TransitionLibraryDefinition
  17. [Serializable]
  18. public class TransitionLibraryDefinition :
  19. IAnimationClipSource,
  20. ICopyable<TransitionLibraryDefinition>,
  21. IEquatable<TransitionLibraryDefinition>,
  22. IHasDescription
  23. {
  24. /************************************************************************************************************************/
  25. #region Fields and Properties
  26. /************************************************************************************************************************/
  27. [SerializeField]
  28. private TransitionAssetBase[]
  29. _Transitions = Array.Empty<TransitionAssetBase>();
  30. /// <summary>[<see cref="SerializeField"/>] The transitions in this library.</summary>
  31. /// <remarks>This property uses an empty array instead of <c>null</c>.</remarks>
  32. public TransitionAssetBase[] Transitions
  33. {
  34. get => _Transitions;
  35. set => _Transitions = value.NullIsEmpty();
  36. }
  37. /************************************************************************************************************************/
  38. [SerializeField]
  39. private TransitionModifierDefinition[]
  40. _Modifiers = Array.Empty<TransitionModifierDefinition>();
  41. /// <summary>[<see cref="SerializeField"/>] Modified fade durations for specific transition combinations.</summary>
  42. /// <remarks>This property uses an empty array instead of <c>null</c>.</remarks>
  43. public TransitionModifierDefinition[] Modifiers
  44. {
  45. get => _Modifiers;
  46. set => _Modifiers = value.NullIsEmpty();
  47. }
  48. /************************************************************************************************************************/
  49. [SerializeField]
  50. private NamedIndex[]
  51. _Aliases = Array.Empty<NamedIndex>();
  52. /// <summary>[<see cref="SerializeField"/>] Alternate names that can be used to look up transitions.</summary>
  53. /// <remarks>
  54. /// This array should always be sorted, use <see cref="SortAliases"/> if necessary.
  55. /// <para></para>
  56. /// This property uses an empty array instead of <c>null</c>.
  57. /// </remarks>
  58. public NamedIndex[] Aliases
  59. {
  60. get => _Aliases;
  61. set => _Aliases = value.NullIsEmpty();
  62. }
  63. /************************************************************************************************************************/
  64. [SerializeField]
  65. [Tooltip(AliasAllTransitionsTooltip)]
  66. private bool _AliasAllTransitions;
  67. /// <summary>[<see cref="SerializeField"/>]
  68. /// Should all Transitions automatically be registered using their name as an Alias?
  69. /// </summary>
  70. public ref bool AliasAllTransitions
  71. => ref _AliasAllTransitions;
  72. #if UNITY_EDITOR
  73. /// <summary>[Editor-Only] [Internal]
  74. /// The name of the field which stores the <see cref="AliasAllTransitions"/>.
  75. /// </summary>
  76. internal const string AliasAllTransitionsField = nameof(_AliasAllTransitions);
  77. #endif
  78. /// <summary>Tooltip for the <see cref="AliasAllTransitions"/> field.</summary>
  79. public const string AliasAllTransitionsTooltip =
  80. "Should all Transitions automatically be registered using their name as an Alias?";
  81. /************************************************************************************************************************/
  82. #endregion
  83. /************************************************************************************************************************/
  84. #region Transitions
  85. /************************************************************************************************************************/
  86. /// <summary>
  87. /// <see cref="AnimancerUtilities.TryGet{T}(IList{T}, int, out T)"/> for the <see cref="Transitions"/>.
  88. /// </summary>
  89. public bool TryGetTransition(
  90. int index,
  91. out TransitionAssetBase transition)
  92. => _Transitions.TryGet(index, out transition)
  93. && transition != null;
  94. /************************************************************************************************************************/
  95. /// <summary>Adds an item to the end of the <see cref="Transitions"/>.</summary>
  96. public void AddTransition(
  97. TransitionAssetBase transition)
  98. => AnimancerUtilities.InsertAt(
  99. ref _Transitions,
  100. _Transitions.Length,
  101. transition);
  102. /************************************************************************************************************************/
  103. /// <summary>
  104. /// Removes an item from the <see cref="Transitions"/>
  105. /// and adjusts the other fields to account for the moved indices.
  106. /// </summary>
  107. public void RemoveTransition(int index)
  108. {
  109. if ((uint)index >= _Transitions.Length)
  110. return;
  111. AnimancerUtilities.RemoveAt(ref _Transitions, index);
  112. for (int i = _Modifiers.Length - 1; i >= 0; i--)
  113. {
  114. var modifier = _Modifiers[i];
  115. // Remove any modifiers targeting that transition.
  116. if (modifier.FromIndex == index ||
  117. modifier.ToIndex == index)
  118. {
  119. AnimancerUtilities.RemoveAt(ref _Modifiers, i);
  120. }
  121. else// Adjust the indices of any modifiers after it.
  122. {
  123. var fromIndex = modifier.FromIndex;
  124. if (fromIndex > index)
  125. fromIndex--;
  126. var toIndex = modifier.ToIndex;
  127. if (toIndex > index)
  128. toIndex--;
  129. _Modifiers[i] = modifier.WithIndices(fromIndex, toIndex);
  130. }
  131. }
  132. for (int i = _Aliases.Length - 1; i >= 0; i--)
  133. {
  134. var alias = _Aliases[i];
  135. // Remove any aliases targeting that transition.
  136. if (alias.Index == index)
  137. {
  138. AnimancerUtilities.RemoveAt(ref _Aliases, i);
  139. }
  140. else// Adjust the indices of any aliases after it.
  141. {
  142. if (alias.Index > index)
  143. _Aliases[i] = alias.With(alias.Index - 1);
  144. }
  145. }
  146. }
  147. /************************************************************************************************************************/
  148. #endregion
  149. /************************************************************************************************************************/
  150. #region Modifiers
  151. /************************************************************************************************************************/
  152. /// <summary>Tries to find an item in the <see cref="Modifiers"/> with the specified indices.</summary>
  153. /// <remarks>
  154. /// If unsuccessful, the `modifier` is given the <see cref="ITransition.FadeDuration"/>
  155. /// from the <see cref="Transitions"/> at the `toIndex`. and this method returns false.
  156. /// </remarks>
  157. public bool TryGetModifier(
  158. int fromIndex,
  159. int toIndex,
  160. out TransitionModifierDefinition modifier)
  161. {
  162. var index = IndexOfModifier(fromIndex, toIndex);
  163. if (index >= 0)
  164. {
  165. modifier = _Modifiers[index];
  166. return true;
  167. }
  168. var fadeDuration = TryGetTransition(toIndex, out var transition)
  169. ? transition.TryGetFadeDuration()
  170. : float.NaN;
  171. modifier = new(fromIndex, toIndex, fadeDuration);
  172. return false;
  173. }
  174. /************************************************************************************************************************/
  175. /// <summary>
  176. /// Returns the index in the <see cref="Modifiers"/> which matches the given
  177. /// <see cref="TransitionModifierDefinition.FromIndex"/> and
  178. /// <see cref="TransitionModifierDefinition.ToIndex"/> or -1 if no such item exists.
  179. /// </summary>
  180. public int IndexOfModifier(int fromIndex, int toIndex)
  181. {
  182. for (int i = _Modifiers.Length - 1; i >= 0; i--)
  183. {
  184. var modifier = _Modifiers[i];
  185. if (modifier.FromIndex == fromIndex &&
  186. modifier.ToIndex == toIndex)
  187. return i;
  188. }
  189. return -1;
  190. }
  191. /************************************************************************************************************************/
  192. /// <summary>Adds or replaces an item in the <see cref="Modifiers"/>.</summary>
  193. public void SetModifier(
  194. TransitionModifierDefinition modifier)
  195. {
  196. if (float.IsNaN(modifier.FadeDuration))
  197. {
  198. RemoveModifier(modifier);
  199. return;
  200. }
  201. if (modifier.FadeDuration < 0)
  202. modifier = modifier.WithFadeDuration(0);
  203. var index = IndexOfModifier(modifier.FromIndex, modifier.ToIndex);
  204. if (index >= 0)
  205. {
  206. _Modifiers[index] = modifier;
  207. }
  208. else
  209. {
  210. AnimancerUtilities.InsertAt(ref _Modifiers, _Modifiers.Length, modifier);
  211. }
  212. }
  213. /************************************************************************************************************************/
  214. /// <summary>Removes an item from the <see cref="Modifiers"/>.</summary>
  215. public bool RemoveModifier(
  216. TransitionModifierDefinition modifier)
  217. => RemoveModifier(modifier.FromIndex, modifier.ToIndex);
  218. /// <summary>Removes an item from the <see cref="Modifiers"/>.</summary>
  219. public bool RemoveModifier(int fromIndex, int toIndex)
  220. {
  221. var index = IndexOfModifier(fromIndex, toIndex);
  222. if (index < 0)
  223. return false;
  224. AnimancerUtilities.RemoveAt(ref _Modifiers, index);
  225. return true;
  226. }
  227. /************************************************************************************************************************/
  228. #endregion
  229. /************************************************************************************************************************/
  230. #region Aliases
  231. /************************************************************************************************************************/
  232. /// <summary>Adds an item to the <see cref="Aliases"/>, sorted by its values.</summary>
  233. public int AddAlias(NamedIndex alias)
  234. {
  235. int i = 0;
  236. for (; i < _Aliases.Length; i++)
  237. if (alias.CompareTo(_Aliases[i]) <= 0)
  238. break;
  239. AnimancerUtilities.InsertAt(ref _Aliases, i, alias);
  240. return i;
  241. }
  242. /************************************************************************************************************************/
  243. /// <summary>Removes an item from the <see cref="Aliases"/>.</summary>
  244. public bool RemoveAlias(NamedIndex alias)
  245. {
  246. var index = Array.IndexOf(_Aliases, alias);
  247. if (index < 0)
  248. return false;
  249. RemoveAlias(index);
  250. return true;
  251. }
  252. /// <summary>Removes an item from the <see cref="Aliases"/>.</summary>
  253. public void RemoveAlias(int index)
  254. => AnimancerUtilities.RemoveAt(ref _Aliases, index);
  255. /************************************************************************************************************************/
  256. /// <summary>Ensures that the <see cref="Aliases"/> are sorted.</summary>
  257. /// <remarks>This method shouldn't need to be called manually since aliases are always added in order.</remarks>
  258. public void SortAliases()
  259. => Array.Sort(_Aliases, (a, b) => a.CompareTo(b));
  260. /************************************************************************************************************************/
  261. #endregion
  262. /************************************************************************************************************************/
  263. #region Equality
  264. /************************************************************************************************************************/
  265. /// <summary>Are all fields in this object equal to the equivalent in `obj`?</summary>
  266. public override bool Equals(object obj)
  267. => Equals(obj as TransitionLibraryDefinition);
  268. /// <summary>Are all fields in this object equal to the equivalent fields in `other`?</summary>
  269. public bool Equals(TransitionLibraryDefinition other)
  270. => other != null
  271. && AnimancerUtilities.ContentsAreEqual(_Transitions, other._Transitions)
  272. && AnimancerUtilities.ContentsAreEqual(_Modifiers, other._Modifiers)
  273. && AnimancerUtilities.ContentsAreEqual(_Aliases, other._Aliases)
  274. && _AliasAllTransitions == other._AliasAllTransitions;
  275. /// <summary>Are all fields in `a` equal to the equivalent fields in `b`?</summary>
  276. public static bool operator ==(TransitionLibraryDefinition a, TransitionLibraryDefinition b)
  277. => a is null
  278. ? b is null
  279. : a.Equals(b);
  280. /// <summary>Are any fields in `a` not equal to the equivalent fields in `b`?</summary>
  281. public static bool operator !=(TransitionLibraryDefinition a, TransitionLibraryDefinition b)
  282. => !(a == b);
  283. /************************************************************************************************************************/
  284. /// <summary>Returns a hash code based on the values of this object's fields.</summary>
  285. public override int GetHashCode()
  286. => AnimancerUtilities.Hash(-871379578,
  287. _Transitions.SafeGetHashCode(),
  288. _Modifiers.SafeGetHashCode(),
  289. _Aliases.SafeGetHashCode(),
  290. _AliasAllTransitions.SafeGetHashCode());
  291. /************************************************************************************************************************/
  292. #endregion
  293. /************************************************************************************************************************/
  294. #region Other
  295. /************************************************************************************************************************/
  296. /// <summary>Gathers all the animations in this definition.</summary>
  297. public void GetAnimationClips(List<AnimationClip> results)
  298. => results.GatherFromSource(_Transitions);
  299. /************************************************************************************************************************/
  300. /// <inheritdoc/>
  301. public void CopyFrom(TransitionLibraryDefinition copyFrom, CloneContext context)
  302. {
  303. AnimancerUtilities.CopyExactArray(copyFrom._Transitions, ref _Transitions);
  304. AnimancerUtilities.CopyExactArray(copyFrom._Modifiers, ref _Modifiers);
  305. AnimancerUtilities.CopyExactArray(copyFrom._Aliases, ref _Aliases);
  306. _AliasAllTransitions = copyFrom._AliasAllTransitions;
  307. }
  308. /************************************************************************************************************************/
  309. /// <inheritdoc/>
  310. public void AppendDescription(StringBuilder text, string separator = "\n")
  311. {
  312. text.Append(GetType().Name);
  313. if (!separator.StartsWithNewLine())
  314. separator = "\n" + separator;
  315. var indentedSeparator = separator + Strings.Indent;
  316. text.AppendField(separator, nameof(Transitions), Transitions.Length);
  317. for (int i = 0; i < Transitions.Length; i++)
  318. text.AppendField(indentedSeparator, i.ToString(), Transitions[i]);
  319. text.AppendField(separator, nameof(Modifiers), Modifiers.Length);
  320. for (int i = 0; i < Modifiers.Length; i++)
  321. text.AppendField(indentedSeparator, i.ToString(), Modifiers[i]);
  322. text.AppendField(separator, nameof(Aliases), Aliases.Length);
  323. for (int i = 0; i < Aliases.Length; i++)
  324. text.AppendField(indentedSeparator, i.ToString(), Aliases[i]);
  325. }
  326. /************************************************************************************************************************/
  327. #endregion
  328. /************************************************************************************************************************/
  329. }
  330. }