WeightedMaskLayersDefinition.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. using System;
  3. using System.Runtime.CompilerServices;
  4. using UnityEngine;
  5. namespace Animancer
  6. {
  7. /// <summary>Serializable data which defines how to control a <see cref="WeightedMaskLayerList"/>.</summary>
  8. /// https://kybernetik.com.au/animancer/api/Animancer/WeightedMaskLayersDefinition
  9. [Serializable]
  10. public class WeightedMaskLayersDefinition :
  11. ICopyable<WeightedMaskLayersDefinition>,
  12. IEquatable<WeightedMaskLayersDefinition>
  13. #if UNITY_EDITOR
  14. , ISerializationCallbackReceiver
  15. #endif
  16. {
  17. /************************************************************************************************************************/
  18. /// <summary>The name of the serialized backing field of <see cref="Transforms"/>.</summary>
  19. public const string
  20. TransformsField = nameof(_Transforms);
  21. [SerializeField]
  22. private Transform[] _Transforms;
  23. /// <summary><see cref="Transform"/>s being controlled by this definition.</summary>
  24. public ref Transform[] Transforms
  25. => ref _Transforms;
  26. /************************************************************************************************************************/
  27. /// <summary>The name of the serialized backing field of <see cref="Weights"/>.</summary>
  28. public const string
  29. WeightsField = nameof(_Weights);
  30. [SerializeField]
  31. private float[] _Weights;
  32. /// <summary>Groups of weights which will be applied to the <see cref="Transforms"/>.</summary>
  33. /// <remarks>
  34. /// This is a flattened 2D array containing groups of target weights corresponding to the transforms.
  35. /// With n transforms, indices 0 to n-1 are Group 0, n to n*2-1 are Group 1, etc.
  36. /// </remarks>
  37. public ref float[] Weights
  38. => ref _Weights;
  39. /************************************************************************************************************************/
  40. /// <summary>The number of weight groups in this definition.</summary>
  41. public int GroupCount
  42. {
  43. get => _Transforms == null || _Transforms.Length == 0 || _Weights == null
  44. ? 0
  45. : _Weights.Length / _Transforms.Length;
  46. set
  47. {
  48. if (_Transforms != null && value > 0)
  49. Array.Resize(ref _Weights, _Transforms.Length * value);
  50. else
  51. _Weights = Array.Empty<float>();
  52. }
  53. }
  54. /************************************************************************************************************************/
  55. /// <summary>[Assert-Conditional] Asserts that the `groupIndex` is valid.</summary>
  56. [System.Diagnostics.Conditional(Strings.Assertions)]
  57. public void AssertGroupIndex(int groupIndex)
  58. {
  59. if ((uint)groupIndex >= (uint)GroupCount)
  60. throw new ArgumentOutOfRangeException(
  61. nameof(groupIndex),
  62. groupIndex,
  63. $"Must be 0 <= {nameof(groupIndex)} < Group Count ({GroupCount})");
  64. }
  65. /************************************************************************************************************************/
  66. /// <summary>Calculates the index of each of the <see cref="Transforms"/>.</summary>
  67. public int[] CalculateIndices(WeightedMaskLayerList layers)
  68. {
  69. var indices = new int[_Transforms.Length];
  70. for (int i = 0; i < _Transforms.Length; i++)
  71. {
  72. indices[i] = layers.IndexOf(_Transforms[i]);
  73. #if UNITY_ASSERTIONS
  74. if (indices[i] < 0)
  75. Debug.LogWarning(
  76. $"Unable to find index of {_Transforms[i]} in {nameof(WeightedMaskLayerList)}",
  77. _Transforms[i]);
  78. #endif
  79. }
  80. return indices;
  81. }
  82. /************************************************************************************************************************/
  83. /// <summary>
  84. /// Adds the `transform` at the specified `index`
  85. /// along with any associated <see cref="_Weights"/>.
  86. /// </summary>
  87. public void AddTransform(Transform transform)
  88. {
  89. var index = _Transforms.Length;
  90. AnimancerUtilities.InsertAt(ref _Transforms, index, transform);
  91. if (_Transforms.Length == 1 && _Weights.IsNullOrEmpty())
  92. {
  93. _Weights = new float[1];
  94. return;
  95. }
  96. while (index <= _Weights.Length)
  97. {
  98. AnimancerUtilities.InsertAt(ref _Weights, index, 0);
  99. index += _Transforms.Length;
  100. }
  101. }
  102. /// <summary>
  103. /// Removes the `index` from the <see cref="_Transforms"/>
  104. /// along with any associated <see cref="_Weights"/>.
  105. /// </summary>
  106. public void RemoveTransform(int index)
  107. {
  108. AnimancerUtilities.RemoveAt(ref _Transforms, index);
  109. while (index < _Weights.Length)
  110. {
  111. AnimancerUtilities.RemoveAt(ref _Weights, index);
  112. index += _Transforms.Length;
  113. }
  114. }
  115. /************************************************************************************************************************/
  116. /// <summary>Calculates the index in the <see cref="Weights"/> corresponding to the specified values.</summary>
  117. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  118. public int IndexOfGroup(int groupIndex)
  119. => groupIndex * _Transforms.Length;
  120. /// <summary>Calculates the index in the <see cref="Weights"/> corresponding to the specified values.</summary>
  121. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  122. public int IndexOf(int groupIndex, int transformIndex)
  123. => groupIndex * _Transforms.Length + transformIndex;
  124. /************************************************************************************************************************/
  125. /// <summary>Gets the specified weight.</summary>
  126. /// <remarks>Returns <see cref="float.NaN"/> if the indices are outside the <see cref="Weights"/>.</remarks>
  127. public float GetWeight(int groupIndex, int transformIndex)
  128. {
  129. if (Weights == null)
  130. return float.NaN;
  131. var index = IndexOf(groupIndex, transformIndex);
  132. return (uint)index < (uint)Weights.Length
  133. ? Weights[index]
  134. : float.NaN;
  135. }
  136. /// <summary>Sets the specified weight.</summary>
  137. /// <remarks>Returns false if the indices are outside the <see cref="Weights"/>.</remarks>
  138. public bool SetWeight(int groupIndex, int transformIndex, float value)
  139. {
  140. if (Weights == null)
  141. return false;
  142. var index = IndexOf(groupIndex, transformIndex);
  143. if ((uint)index < (uint)Weights.Length)
  144. {
  145. Weights[index] = value;
  146. return true;
  147. }
  148. return false;
  149. }
  150. /************************************************************************************************************************/
  151. /// <inheritdoc/>
  152. public void CopyFrom(WeightedMaskLayersDefinition copyFrom, CloneContext context)
  153. {
  154. AnimancerUtilities.CopyExactArray(copyFrom._Transforms, ref _Transforms);
  155. AnimancerUtilities.CopyExactArray(copyFrom._Weights, ref _Weights);
  156. }
  157. /************************************************************************************************************************/
  158. /// <summary>Does this definition contain valid data?</summary>
  159. public bool IsValid
  160. => !_Transforms.IsNullOrEmpty()
  161. && _Weights != null && _Weights.Length > _Transforms.Length;
  162. /************************************************************************************************************************/
  163. /// <inheritdoc/>
  164. public bool OnValidate()
  165. => ValidateArraySizes()
  166. || RemoveMissingAndDuplicate();
  167. /// <summary>Ensures that all the arrays have valid sizes.</summary>
  168. public bool ValidateArraySizes()
  169. {
  170. if (_Transforms.IsNullOrEmpty())
  171. {
  172. _Transforms = Array.Empty<Transform>();
  173. _Weights = Array.Empty<float>();
  174. return true;
  175. }
  176. if (_Weights == null ||
  177. _Weights.Length < _Transforms.Length)
  178. {
  179. AnimancerUtilities.SetLength(ref _Weights, _Transforms.Length);
  180. return true;
  181. }
  182. var expectedWeightCount = (int)Math.Ceiling(_Weights.Length / (double)_Transforms.Length);
  183. expectedWeightCount *= _Transforms.Length;
  184. return AnimancerUtilities.SetLength(ref _Weights, expectedWeightCount);
  185. }
  186. /// <summary>Removes any missing or identical <see cref="_Transforms"/>.</summary>
  187. public bool RemoveMissingAndDuplicate()
  188. {
  189. var removedAny = false;
  190. for (int i = 0; i < _Transforms.Length; i++)
  191. {
  192. var transform = _Transforms[i];
  193. if (transform == null)
  194. {
  195. RemoveTransform(i);
  196. removedAny = true;
  197. }
  198. else
  199. {
  200. var nextIndex = i + 1;
  201. RemoveDuplicates:
  202. nextIndex = Array.IndexOf(_Transforms, transform, nextIndex);
  203. if (nextIndex > i)
  204. {
  205. RemoveTransform(nextIndex);
  206. removedAny = true;
  207. goto RemoveDuplicates;
  208. }
  209. }
  210. }
  211. return removedAny;
  212. }
  213. /************************************************************************************************************************/
  214. #if UNITY_EDITOR
  215. /************************************************************************************************************************/
  216. /// <inheritdoc/>
  217. void ISerializationCallbackReceiver.OnBeforeSerialize()
  218. => OnValidate();
  219. /// <inheritdoc/>
  220. void ISerializationCallbackReceiver.OnAfterDeserialize()
  221. => OnValidate();
  222. /************************************************************************************************************************/
  223. #endif
  224. /************************************************************************************************************************/
  225. /// <summary>Returns a summary of this definition.</summary>
  226. public override string ToString()
  227. => $"{nameof(WeightedMaskLayersDefinition)}(" +
  228. $"{nameof(Transforms)}={(Transforms != null ? Transforms.Length : 0)}, " +
  229. $"{nameof(Weights)}={(Weights != null ? Weights.Length : 0)})";
  230. /************************************************************************************************************************/
  231. #region Equality
  232. /************************************************************************************************************************/
  233. /// <summary>Are all fields in this object equal to the equivalent in `obj`?</summary>
  234. public override bool Equals(object obj)
  235. => Equals(obj as WeightedMaskLayersDefinition);
  236. /// <summary>Are all fields in this object equal to the equivalent fields in `other`?</summary>
  237. public bool Equals(WeightedMaskLayersDefinition other)
  238. => other != null
  239. && AnimancerUtilities.ContentsAreEqual(_Transforms, other._Transforms)
  240. && AnimancerUtilities.ContentsAreEqual(_Weights, other._Weights);
  241. /// <summary>Are all fields in `a` equal to the equivalent fields in `b`?</summary>
  242. public static bool operator ==(WeightedMaskLayersDefinition a, WeightedMaskLayersDefinition b)
  243. => a is null
  244. ? b is null
  245. : a.Equals(b);
  246. /// <summary>Are any fields in `a` not equal to the equivalent fields in `b`?</summary>
  247. public static bool operator !=(WeightedMaskLayersDefinition a, WeightedMaskLayersDefinition b)
  248. => !(a == b);
  249. /************************************************************************************************************************/
  250. /// <summary>Returns a hash code based on the values of this object's fields.</summary>
  251. public override int GetHashCode()
  252. => AnimancerUtilities.Hash(-871379578,
  253. _Transforms.SafeGetHashCode(),
  254. _Weights.SafeGetHashCode());
  255. /************************************************************************************************************************/
  256. #endregion
  257. /************************************************************************************************************************/
  258. }
  259. }