123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
- using System;
- using System.Text;
- using UnityEngine;
- namespace Animancer
- {
- /// <summary>[Pro-Only]
- /// An <see cref="AnimancerState"/> which blends an array of other states together based on a two dimensional
- /// parameter and thresholds using Polar Gradient Band Interpolation.
- /// </summary>
- /// <remarks>
- /// This mixer type is similar to the 2D Freeform Directional Blend Type in Mecanim Blend Trees.
- /// <para></para>
- /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/blending/mixers">Mixers</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer/DirectionalMixerState
- ///
- public class DirectionalMixerState : MixerState<Vector2>
- {
- /************************************************************************************************************************/
- /// <summary><see cref="MixerState{TParameter}.Parameter"/>.x.</summary>
- public float ParameterX
- {
- get => Parameter.x;
- set => Parameter = new Vector2(value, Parameter.y);
- }
- /// <summary><see cref="MixerState{TParameter}.Parameter"/>.y.</summary>
- public float ParameterY
- {
- get => Parameter.y;
- set => Parameter = new Vector2(Parameter.x, value);
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override string GetParameterError(Vector2 value)
- => value.IsFinite() ? null : $"value.x and value.y {Strings.MustBeFinite}";
- /************************************************************************************************************************/
- /// <summary>Precalculated magnitudes of all thresholds to speed up the recalculation of weights.</summary>
- private float[] _ThresholdMagnitudes;
- /// <summary>Precalculated values to speed up the recalculation of weights.</summary>
- private Vector2[][] _BlendFactors;
- /// <summary>Indicates whether the <see cref="_BlendFactors"/> need to be recalculated.</summary>
- private bool _BlendFactorsDirty = true;
- /// <summary>The multiplier that controls how much an angle (in radians) is worth compared to normalized distance.</summary>
- private const float AngleFactor = 2;
- /************************************************************************************************************************/
- /// <summary>
- /// Called whenever the thresholds are changed. Indicates that the internal blend factors need to be
- /// recalculated and calls <see cref="ForceRecalculateWeights"/>.
- /// </summary>
- public override void OnThresholdsChanged()
- {
- _BlendFactorsDirty = true;
- base.OnThresholdsChanged();
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Recalculates the weights of all <see cref="MixerState.ChildStates"/> based on the current value of the
- /// <see cref="MixerState{TParameter}.Parameter"/> and the thresholds.
- /// </summary>
- protected override void ForceRecalculateWeights()
- {
- WeightsAreDirty = false;
- var childCount = ChildCount;
- if (childCount == 0)
- {
- return;
- }
- else if (childCount == 1)
- {
- var state = GetChild(0);
- state.Weight = 1;
- return;
- }
- CalculateBlendFactors(childCount);
- var parameterMagnitude = Parameter.magnitude;
- float totalWeight = 0;
- for (int i = 0; i < childCount; i++)
- {
- var state = GetChild(i);
- if (state == null)
- continue;
- var blendFactors = _BlendFactors[i];
- var thresholdI = GetThreshold(i);
- var magnitudeI = _ThresholdMagnitudes[i];
- // Convert the threshold to polar coordinates (distance, angle) and interpolate the weight based on those.
- var differenceIToParameter = parameterMagnitude - magnitudeI;
- var angleIToParameter = SignedAngle(thresholdI, Parameter) * AngleFactor;
- float weight = 1;
- for (int j = 0; j < childCount; j++)
- {
- if (j == i || GetChild(j) == null)
- continue;
- var magnitudeJ = _ThresholdMagnitudes[j];
- var averageMagnitude = (magnitudeJ + magnitudeI) * 0.5f;
- var polarIToParameter = new Vector2(
- differenceIToParameter / averageMagnitude,
- angleIToParameter);
- var newWeight = 1 - Vector2.Dot(polarIToParameter, blendFactors[j]);
- if (weight > newWeight)
- weight = newWeight;
- }
- if (weight < 0.01f)
- weight = 0;
- state.Weight = weight;
- totalWeight += weight;
- }
- NormalizeWeights(totalWeight);
- }
- /************************************************************************************************************************/
- private void CalculateBlendFactors(int childCount)
- {
- if (!_BlendFactorsDirty)
- return;
- _BlendFactorsDirty = false;
- // Resize the precalculated values.
- if (_BlendFactors == null || _BlendFactors.Length != childCount)
- {
- _ThresholdMagnitudes = new float[childCount];
- _BlendFactors = new Vector2[childCount][];
- for (int i = 0; i < childCount; i++)
- _BlendFactors[i] = new Vector2[childCount];
- }
- // Calculate the magnitude of each threshold.
- for (int i = 0; i < childCount; i++)
- {
- _ThresholdMagnitudes[i] = GetThreshold(i).magnitude;
- }
- // Calculate the blend factors between each combination of thresholds.
- for (int i = 0; i < childCount; i++)
- {
- var blendFactors = _BlendFactors[i];
- var thresholdI = GetThreshold(i);
- var magnitudeI = _ThresholdMagnitudes[i];
- var j = 0;// i + 1;
- for (; j < childCount; j++)
- {
- if (i == j)
- continue;
- var thresholdJ = GetThreshold(j);
- var magnitudeJ = _ThresholdMagnitudes[j];
- var averageMagnitude = (magnitudeI + magnitudeJ) * 0.5f;
- // Convert the thresholds to polar coordinates (distance, angle) and interpolate the weight based on those.
- var differenceIToJ = magnitudeJ - magnitudeI;
- var angleIToJ = SignedAngle(thresholdI, thresholdJ);
- var polarIToJ = new Vector2(
- differenceIToJ / averageMagnitude,
- angleIToJ * AngleFactor);
- polarIToJ *= 1f / polarIToJ.sqrMagnitude;
- // Each factor is used in [i][j] with it's opposite in [j][i].
- blendFactors[j] = polarIToJ;
- _BlendFactors[j][i] = -polarIToJ;
- }
- }
- }
- /************************************************************************************************************************/
- private static float SignedAngle(Vector2 a, Vector2 b)
- {
- // If either vector is exactly at the origin, the angle is 0.
- if ((a.x == 0 && a.y == 0) || (b.x == 0 && b.y == 0))
- {
- // Due to floating point error the formula below usually gives 0 but sometimes Pi,
- // which screws up our other calculations so we need it to always be 0 properly.
- return 0;
- }
- return Mathf.Atan2(
- a.x * b.y - a.y * b.x,
- a.x * b.x + a.y * b.y);
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override void AppendParameter(StringBuilder text, Vector2 parameter)
- {
- text.Append('(')
- .Append(parameter.x)
- .Append(", ")
- .Append(parameter.y)
- .Append(')');
- }
- /************************************************************************************************************************/
- #region Inspector
- /************************************************************************************************************************/
- /// <inheritdoc/>
- protected override int ParameterCount => 2;
- /// <inheritdoc/>
- protected override string GetParameterName(int index)
- {
- switch (index)
- {
- case 0: return "Parameter X";
- case 1: return "Parameter Y";
- default: throw new ArgumentOutOfRangeException(nameof(index));
- }
- }
- /// <inheritdoc/>
- protected override AnimatorControllerParameterType GetParameterType(int index) => AnimatorControllerParameterType.Float;
- /// <inheritdoc/>
- protected override object GetParameterValue(int index)
- {
- switch (index)
- {
- case 0: return ParameterX;
- case 1: return ParameterY;
- default: throw new ArgumentOutOfRangeException(nameof(index));
- }
- }
- /// <inheritdoc/>
- protected override void SetParameterValue(int index, object value)
- {
- switch (index)
- {
- case 0: ParameterX = (float)value; break;
- case 1: ParameterY = (float)value; break;
- default: throw new ArgumentOutOfRangeException(nameof(index));
- }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- }
- }
|