LinearMixerState.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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. using UnityEngine.Playables;
  7. namespace Animancer
  8. {
  9. /// <summary>[Pro-Only]
  10. /// An <see cref="AnimancerState"/> which blends an array of other states together
  11. /// using linear interpolation between the specified thresholds.
  12. /// </summary>
  13. /// <remarks>
  14. /// This mixer type is similar to the 1D Blend Type in Mecanim Blend Trees.
  15. /// <para></para>
  16. /// <strong>Documentation:</strong>
  17. /// <see href="https://kybernetik.com.au/animancer/docs/manual/blending/mixers">
  18. /// Mixers</see>
  19. /// </remarks>
  20. /// https://kybernetik.com.au/animancer/api/Animancer/LinearMixerState
  21. public class LinearMixerState : MixerState<float>,
  22. ICopyable<LinearMixerState>
  23. {
  24. /************************************************************************************************************************/
  25. private bool _ExtrapolateSpeed = true;
  26. /// <summary>
  27. /// Should setting the <see cref="MixerState{TParameter}.Parameter"/> above the highest threshold
  28. /// increase the <see cref="AnimancerNodeBase.Speed"/> of this mixer proportionally?
  29. /// </summary>
  30. public bool ExtrapolateSpeed
  31. {
  32. get => _ExtrapolateSpeed;
  33. set
  34. {
  35. if (_ExtrapolateSpeed == value)
  36. return;
  37. _ExtrapolateSpeed = value;
  38. if (!_Playable.IsValid())
  39. return;
  40. var speed = Speed;
  41. var childCount = ChildCount;
  42. if (value && childCount > 0)
  43. {
  44. var threshold = GetThreshold(childCount - 1);
  45. if (Parameter > threshold)
  46. speed *= Parameter / threshold;
  47. }
  48. _Playable.SetSpeed(speed);
  49. }
  50. }
  51. /************************************************************************************************************************/
  52. /// <inheritdoc/>
  53. public override string GetParameterError(float value)
  54. => value.IsFinite() ? null : Strings.MustBeFinite;
  55. /************************************************************************************************************************/
  56. /// <summary>The lowest threshold (which is for the first child because they must be sorted).</summary>
  57. public float MinThreshold => GetThreshold(0);
  58. /// <summary>The highest threshold (which is for the last child because they must be sorted).</summary>
  59. public float MaxThreshold => GetThreshold(ChildCount - 1);
  60. /// <inheritdoc/>
  61. public override float NormalizedParameter
  62. {
  63. get => AnimancerUtilities.InverseLerpUnclamped(MinThreshold, MaxThreshold, Parameter);
  64. set => Parameter = Mathf.LerpUnclamped(MinThreshold, MaxThreshold, value);
  65. }
  66. /************************************************************************************************************************/
  67. #region Parameter Binding
  68. /************************************************************************************************************************/
  69. private NodeParameter<float> _ParameterBinding;
  70. /// <summary>
  71. /// If set, this will be used as a key in the <see cref="ParameterDictionary"/> so any
  72. /// changes to that parameter will automatically set the <see cref="MixerState{TParameter}.Parameter"/>.
  73. /// </summary>
  74. public StringReference ParameterName
  75. {
  76. get => _ParameterBinding.Key;
  77. set
  78. {
  79. if (_ParameterBinding.SetKeyCheckNeedsInitialize(value))
  80. _ParameterBinding.Initialize(this, parameter => Parameter = parameter);
  81. }
  82. }
  83. /************************************************************************************************************************/
  84. /// <inheritdoc/>
  85. public override void SetGraph(AnimancerGraph graph)
  86. {
  87. if (Graph == graph)
  88. return;
  89. _ParameterBinding.UnBindIfInitialized();
  90. base.SetGraph(graph);
  91. _ParameterBinding.BindIfInitialized();
  92. }
  93. /************************************************************************************************************************/
  94. /// <inheritdoc/>
  95. public override void Destroy()
  96. {
  97. base.Destroy();
  98. _ParameterBinding.UnBindIfInitialized();
  99. }
  100. /************************************************************************************************************************/
  101. #endregion
  102. /************************************************************************************************************************/
  103. /// <inheritdoc/>
  104. public override AnimancerState Clone(CloneContext context)
  105. {
  106. var clone = new LinearMixerState();
  107. clone.CopyFrom(this, context);
  108. return clone;
  109. }
  110. /************************************************************************************************************************/
  111. /// <inheritdoc/>
  112. public sealed override void CopyFrom(MixerState<float> copyFrom, CloneContext context)
  113. => this.CopyFromBase(copyFrom, context);
  114. /// <inheritdoc/>
  115. public virtual void CopyFrom(LinearMixerState copyFrom, CloneContext context)
  116. {
  117. _ExtrapolateSpeed = copyFrom._ExtrapolateSpeed;
  118. ParameterName = copyFrom.ParameterName;
  119. base.CopyFrom(copyFrom, context);
  120. }
  121. /************************************************************************************************************************/
  122. #if UNITY_ASSERTIONS
  123. /************************************************************************************************************************/
  124. private bool _ShouldCheckThresholdSorting;
  125. /// <summary>
  126. /// Called whenever the thresholds are changed. Indicates that <see cref="AssertThresholdsSorted"/> needs to
  127. /// be called by the next <see cref="ForceRecalculateWeights"/> if <c>UNITY_ASSERTIONS</c> is defined, then
  128. /// calls <see cref="MixerState{TParameter}.OnThresholdsChanged"/>.
  129. /// </summary>
  130. public override void OnThresholdsChanged()
  131. {
  132. _ShouldCheckThresholdSorting = true;
  133. base.OnThresholdsChanged();
  134. }
  135. /************************************************************************************************************************/
  136. #endif
  137. /************************************************************************************************************************/
  138. /// <summary>
  139. /// Throws an <see cref="ArgumentException"/> if the thresholds are not sorted from lowest to highest without
  140. /// any duplicates.
  141. /// </summary>
  142. /// <exception cref="ArgumentException"/>
  143. /// <exception cref="InvalidOperationException">The thresholds have not been initialized.</exception>
  144. public void AssertThresholdsSorted()
  145. {
  146. #if UNITY_ASSERTIONS
  147. _ShouldCheckThresholdSorting = false;
  148. #endif
  149. if (!HasThresholds)
  150. {
  151. MarkAsUsed(this);
  152. throw new InvalidOperationException("Thresholds have not been initialized");
  153. }
  154. var previous = float.NegativeInfinity;
  155. var childCount = ChildCount;
  156. for (int i = 0; i < childCount; i++)
  157. {
  158. var state = ChildStates[i];
  159. if (state == null)
  160. continue;
  161. var next = GetThreshold(i);
  162. if (next > previous)
  163. {
  164. previous = next;
  165. }
  166. else
  167. {
  168. MarkAsUsed(this);
  169. var reason = next == previous
  170. ? "Mixer has multiple identical thresholds."
  171. : "Mixer has thresholds out of order.";
  172. throw new ArgumentException(
  173. $"{reason} They must be sorted from lowest to highest with no equal values." +
  174. $"\n{this.GetDescription()}");
  175. }
  176. }
  177. }
  178. /************************************************************************************************************************/
  179. /// <inheritdoc/>
  180. protected override void ForceRecalculateWeights()
  181. {
  182. #if UNITY_ASSERTIONS
  183. if (_ShouldCheckThresholdSorting)
  184. AssertThresholdsSorted();
  185. #endif
  186. // Go through all states, figure out how much weight to give those with thresholds adjacent to the
  187. // current parameter value using linear interpolation, and set all others to 0 weight.
  188. var childCount = ChildCount;
  189. if (childCount == 0)
  190. goto ResetExtrapolatedSpeed;
  191. var index = 0;
  192. var previousState = ChildStates[index];
  193. var parameter = Parameter;
  194. var previousThreshold = GetThreshold(index);
  195. if (parameter <= previousThreshold)
  196. {
  197. DisableRemainingStates(index);
  198. if (previousThreshold >= 0)
  199. {
  200. Playable.SetChildWeight(previousState, 1);
  201. goto ResetExtrapolatedSpeed;
  202. }
  203. }
  204. else
  205. {
  206. while (++index < childCount)
  207. {
  208. var nextState = ChildStates[index];
  209. var nextThreshold = GetThreshold(index);
  210. if (parameter > previousThreshold && parameter <= nextThreshold)
  211. {
  212. var t = (parameter - previousThreshold) / (nextThreshold - previousThreshold);
  213. Playable.SetChildWeight(previousState, 1 - t);
  214. Playable.SetChildWeight(nextState, t);
  215. DisableRemainingStates(index);
  216. goto ResetExtrapolatedSpeed;
  217. }
  218. else
  219. {
  220. Playable.SetChildWeight(previousState, 0);
  221. }
  222. previousState = nextState;
  223. previousThreshold = nextThreshold;
  224. }
  225. }
  226. Playable.SetChildWeight(previousState, 1);
  227. if (ExtrapolateSpeed)
  228. _Playable.SetSpeed(Speed * (parameter / previousThreshold));
  229. return;
  230. ResetExtrapolatedSpeed:
  231. if (ExtrapolateSpeed && _Playable.IsValid())
  232. _Playable.SetSpeed(Speed);
  233. }
  234. /************************************************************************************************************************/
  235. /// <summary>
  236. /// Assigns the thresholds to be evenly spaced between the specified min and max (inclusive).
  237. /// </summary>
  238. public LinearMixerState AssignLinearThresholds(float min = 0, float max = 1)
  239. {
  240. #if UNITY_ASSERTIONS
  241. if (min >= max)
  242. {
  243. MarkAsUsed(this);
  244. throw new ArgumentException($"{nameof(min)} must be less than {nameof(max)}");
  245. }
  246. #endif
  247. var childCount = ChildCount;
  248. var thresholds = new float[childCount];
  249. var increment = (max - min) / (childCount - 1);
  250. for (int i = 0; i < childCount; i++)
  251. {
  252. thresholds[i] =
  253. i < childCount - 1 ?
  254. min + i * increment :// Assign each threshold linearly spaced between the min and max.
  255. max;// and ensure that the last one is exactly at the max (to avoid floating-point error).
  256. }
  257. SetThresholds(thresholds);
  258. return this;
  259. }
  260. /************************************************************************************************************************/
  261. /// <inheritdoc/>
  262. protected override void AppendDetails(StringBuilder text, string separator)
  263. {
  264. text.AppendField(separator, nameof(ExtrapolateSpeed), ExtrapolateSpeed);
  265. base.AppendDetails(text, separator);
  266. }
  267. /************************************************************************************************************************/
  268. #region Inspector
  269. /************************************************************************************************************************/
  270. /// <inheritdoc/>
  271. public override void GetParameters(List<StateParameterDetails> parameters)
  272. {
  273. parameters.Add(new(
  274. "Parameter",
  275. ParameterName,
  276. AnimatorControllerParameterType.Float,
  277. Parameter));
  278. }
  279. /// <inheritdoc/>
  280. public override void SetParameters(List<StateParameterDetails> parameters)
  281. {
  282. var parameter = parameters[0];
  283. ParameterName = parameter.name;
  284. Parameter = (float)parameter.value;
  285. }
  286. /************************************************************************************************************************/
  287. #endregion
  288. /************************************************************************************************************************/
  289. }
  290. }