// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using System;
using UnityEngine;
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.
///
/// Documentation:
///
/// Mixers
///
/// https://kybernetik.com.au/animancer/api/Animancer/CartesianMixerState
///
public class CartesianMixerState : Vector2MixerState,
ICopyable
{
/************************************************************************************************************************/
/// Precalculated values to speed up the recalculation of weights.
private Vector2[][] _BlendFactors;
/// Indicates whether the need to be recalculated.
private bool _BlendFactorsAreDirty = true;
/************************************************************************************************************************/
///
/// Called whenever the thresholds are changed. Indicates that the internal blend factors need to be
/// recalculated and triggers weight recalculation.
///
public override void OnThresholdsChanged()
{
_BlendFactorsAreDirty = true;
base.OnThresholdsChanged();
}
/************************************************************************************************************************/
///
protected override void ForceRecalculateWeights()
{
var childCount = ChildCount;
if (childCount == 0)
{
return;
}
else if (childCount == 1)
{
Playable.SetChildWeight(ChildStates[0], 1);
return;
}
CalculateBlendFactors(childCount);
float totalWeight = 0;
var weights = GetTemporaryFloatArray(childCount);
for (int i = 0; i < childCount; i++)
{
var state = ChildStates[i];
var blendFactors = _BlendFactors[i];
var threshold = GetThreshold(i);
var thresholdToParameter = Parameter - threshold;
float weight = 1;
for (int j = 0; j < childCount; j++)
{
if (j == i)
continue;
var newWeight = 1 - Vector2.Dot(thresholdToParameter, blendFactors[j]);
if (weight > newWeight)
weight = newWeight;
}
if (weight < 0.01f)
weight = 0;
weights[i] = weight;
totalWeight += weight;
}
NormalizeAndApplyWeights(totalWeight, weights);
}
/************************************************************************************************************************/
private void CalculateBlendFactors(int childCount)
{
if (!_BlendFactorsAreDirty)
return;
_BlendFactorsAreDirty = false;
// Resize the precalculated values.
if (AnimancerUtilities.SetLength(ref _BlendFactors, childCount))
{
for (int i = 0; i < childCount; i++)
_BlendFactors[i] = new Vector2[childCount];
}
// 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 j = i + 1;
for (; j < childCount; j++)
{
var thresholdIToJ = GetThreshold(j) - thresholdI;
#if UNITY_ASSERTIONS
if (thresholdIToJ == default)
{
MarkAsUsed(this);
throw new ArgumentException(
$"Mixer has multiple identical thresholds.\n{this.GetDescription()}");
}
#endif
thresholdIToJ /= thresholdIToJ.sqrMagnitude;
// Each factor is used in [i][j] with it's opposite in [j][i].
blendFactors[j] = thresholdIToJ;
_BlendFactors[j][i] = -thresholdIToJ;
}
}
}
/************************************************************************************************************************/
///
public override AnimancerState Clone(CloneContext context)
{
var clone = new CartesianMixerState();
clone.CopyFrom(this, context);
return clone;
}
/************************************************************************************************************************/
///
public sealed override void CopyFrom(Vector2MixerState copyFrom, CloneContext context)
=> this.CopyFromBase(copyFrom, context);
///
public virtual void CopyFrom(CartesianMixerState copyFrom, CloneContext context)
{
_BlendFactorsAreDirty = copyFrom._BlendFactorsAreDirty;
if (!_BlendFactorsAreDirty)
_BlendFactors = copyFrom._BlendFactors;
base.CopyFrom(copyFrom, context);
}
/************************************************************************************************************************/
}
}