// Animancer // Copyright 2020 Kybernetik // using System; using System.Text; using UnityEngine; using UnityEngine.Playables; namespace Animancer { /// [Pro-Only] /// An which blends an array of other states together based on a two dimensional /// parameter and thresholds using Gradient Band Interpolation. /// /// This mixer type is similar to the 2D Freeform Cartesian Blend Type in Mecanim Blend Trees. /// public class CartesianMixerState : MixerState { /************************************************************************************************************************/ /// /// Constructs a new without connecting it to the . /// protected CartesianMixerState(AnimancerPlayable root) : base(root) { } /// /// Constructs a new and connects it to the `layer`. /// public CartesianMixerState(AnimancerLayer layer) : base(layer) { } /// /// Constructs a new and connects it to the `parent` at the specified /// `index`. /// public CartesianMixerState(AnimancerNode parent, int index) : base(parent, index) { } /************************************************************************************************************************/ /// Precalculated values to speed up the recalculation of weights. private Vector2[][] _BlendFactors; /// Indicates whether the need to be recalculated. private bool _BlendFactorsDirty = true; /************************************************************************************************************************/ /// Gets or sets Parameter.x. public float ParameterX { get { return Parameter.x; } set { Parameter = new Vector2(value, Parameter.y); } } /// Gets or sets Parameter.y. public float ParameterY { get { return Parameter.y; } set { Parameter = new Vector2(Parameter.x, value); } } /************************************************************************************************************************/ /// /// Called whenever the thresholds are changed. Indicates that the internal blend factors need to be /// recalculated and calls . /// public override void OnThresholdsChanged() { _BlendFactorsDirty = true; base.OnThresholdsChanged(); } /************************************************************************************************************************/ /// /// Recalculates the weights of all based on the current value of the /// and the . /// public override void RecalculateWeights() { WeightsAreDirty = false; CalculateBlendFactors(); var portCount = PortCount; float totalWeight = 0; for (int i = 0; i < portCount; i++) { var state = States[i]; if (state == null) continue; var blendFactors = _BlendFactors[i]; var threshold = GetThreshold(i); var thresholdToParameter = Parameter - threshold; float weight = 1; for (int j = 0; j < portCount; j++) { if (j == i || States[j] == null) continue; var newWeight = 1 - Vector2.Dot(thresholdToParameter, blendFactors[j]); if (weight > newWeight) weight = newWeight; } if (weight < 0.01f) weight = 0; state.Weight = weight; totalWeight += weight; } NormalizeWeights(totalWeight); } /************************************************************************************************************************/ private void CalculateBlendFactors() { if (!_BlendFactorsDirty) return; _BlendFactorsDirty = false; var portCount = PortCount; if (PortCount <= 1) return; // Resize the precalculated values. if (_BlendFactors == null || _BlendFactors.Length != portCount) { _BlendFactors = new Vector2[portCount][]; for (int i = 0; i < portCount; i++) _BlendFactors[i] = new Vector2[portCount]; } // Calculate the blend factors between each combination of thresholds. for (int i = 0; i < portCount; i++) { var blendFactors = _BlendFactors[i]; var thresholdI = GetThreshold(i); var j = i + 1; for (; j < portCount; j++) { var thresholdIToJ = GetThreshold(j) - thresholdI; thresholdIToJ *= 1f / thresholdIToJ.sqrMagnitude; // Each factor is used in [i][j] with it's opposite in [j][i]. blendFactors[j] = thresholdIToJ; _BlendFactors[j][i] = -thresholdIToJ; } } } /************************************************************************************************************************/ /// Appends the current parameter values of this mixer. public override void AppendParameter(StringBuilder description) { description.Append(ParameterX); description.Append(", "); description.Append(ParameterY); } /************************************************************************************************************************/ #region Inspector /************************************************************************************************************************/ /// The number of parameters being managed by this state. protected override int ParameterCount { get { return 2; } } /// Returns the name of a parameter being managed by this state. /// Thrown if this state doesn't manage any parameters. protected override string GetParameterName(int index) { switch (index) { case 0: return "Parameter X"; case 1: return "Parameter Y"; default: throw new ArgumentOutOfRangeException("index"); } } /// Returns the type of a parameter being managed by this state. /// Thrown if this state doesn't manage any parameters. protected override AnimatorControllerParameterType GetParameterType(int index) { return AnimatorControllerParameterType.Float; } /// Returns the value of a parameter being managed by this state. /// Thrown if this state doesn't manage any parameters. protected override object GetParameterValue(int index) { switch (index) { case 0: return ParameterX; case 1: return ParameterY; default: throw new ArgumentOutOfRangeException("index"); } } /// Sets the value of a parameter being managed by this state. /// Thrown if this state doesn't manage any parameters. 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("index"); } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }