You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
8.8 KiB
C#
227 lines
8.8 KiB
C#
3 months ago
|
// Animancer // Copyright 2020 Kybernetik //
|
||
|
|
||
|
using System;
|
||
|
using System.Text;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Playables;
|
||
|
|
||
|
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 Gradient Band Interpolation.
|
||
|
/// <para></para>
|
||
|
/// This mixer type is similar to the 2D Freeform Cartesian Blend Type in Mecanim Blend Trees.
|
||
|
/// </summary>
|
||
|
public class CartesianMixerState : MixerState<Vector2>
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs a new <see cref="CartesianMixerState"/> without connecting it to the <see cref="PlayableGraph"/>.
|
||
|
/// </summary>
|
||
|
protected CartesianMixerState(AnimancerPlayable root) : base(root) { }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs a new <see cref="CartesianMixerState"/> and connects it to the `layer`.
|
||
|
/// </summary>
|
||
|
public CartesianMixerState(AnimancerLayer layer) : base(layer) { }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs a new <see cref="CartesianMixerState"/> and connects it to the `parent` at the specified
|
||
|
/// `index`.
|
||
|
/// </summary>
|
||
|
public CartesianMixerState(AnimancerNode parent, int index) : base(parent, index) { }
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <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>Gets or sets Parameter.x.</summary>
|
||
|
public float ParameterX
|
||
|
{
|
||
|
get { return Parameter.x; }
|
||
|
set { Parameter = new Vector2(value, Parameter.y); }
|
||
|
}
|
||
|
|
||
|
/// <summary>Gets or sets Parameter.y.</summary>
|
||
|
public float ParameterY
|
||
|
{
|
||
|
get { return Parameter.y; }
|
||
|
set { Parameter = new Vector2(Parameter.x, value); }
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Called whenever the thresholds are changed. Indicates that the internal blend factors need to be
|
||
|
/// recalculated and calls <see cref="RecalculateWeights"/>.
|
||
|
/// </summary>
|
||
|
public override void OnThresholdsChanged()
|
||
|
{
|
||
|
_BlendFactorsDirty = true;
|
||
|
base.OnThresholdsChanged();
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Recalculates the weights of all <see cref="ManualMixerState.States"/> based on the current value of the
|
||
|
/// <see cref="MixerState{TParameter}.Parameter"/> and the <see cref="MixerState{TParameter}._Thresholds"/>.
|
||
|
/// </summary>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Appends the current parameter values of this mixer.</summary>
|
||
|
public override void AppendParameter(StringBuilder description)
|
||
|
{
|
||
|
description.Append(ParameterX);
|
||
|
description.Append(", ");
|
||
|
description.Append(ParameterY);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#region Inspector
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>The number of parameters being managed by this state.</summary>
|
||
|
protected override int ParameterCount { get { return 2; } }
|
||
|
|
||
|
/// <summary>Returns the name of a parameter being managed by this state.</summary>
|
||
|
/// <exception cref="NotSupportedException">Thrown if this state doesn't manage any parameters.</exception>
|
||
|
protected override string GetParameterName(int index)
|
||
|
{
|
||
|
switch (index)
|
||
|
{
|
||
|
case 0: return "Parameter X";
|
||
|
case 1: return "Parameter Y";
|
||
|
default: throw new ArgumentOutOfRangeException("index");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Returns the type of a parameter being managed by this state.</summary>
|
||
|
/// <exception cref="NotSupportedException">Thrown if this state doesn't manage any parameters.</exception>
|
||
|
protected override AnimatorControllerParameterType GetParameterType(int index) { return AnimatorControllerParameterType.Float; }
|
||
|
|
||
|
/// <summary>Returns the value of a parameter being managed by this state.</summary>
|
||
|
/// <exception cref="NotSupportedException">Thrown if this state doesn't manage any parameters.</exception>
|
||
|
protected override object GetParameterValue(int index)
|
||
|
{
|
||
|
switch (index)
|
||
|
{
|
||
|
case 0: return ParameterX;
|
||
|
case 1: return ParameterY;
|
||
|
default: throw new ArgumentOutOfRangeException("index");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Sets the value of a parameter being managed by this state.</summary>
|
||
|
/// <exception cref="NotSupportedException">Thrown if this state doesn't manage any parameters.</exception>
|
||
|
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
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
}
|
||
|
|