249 lines
6.3 KiB
C#
Raw Permalink Normal View History

2025-09-24 11:24:38 +05:00
namespace Fusion.Addons.AnimationController
{
using UnityEngine;
public sealed class AnimationBlendTree
{
// PUBLIC MEMBERS
public float Size => _size;
public float[] Weights => _weights;
// PRIVATE MEMBERS
private int _count;
private float _size;
private float _scale;
private float[] _weights;
private Vector2[] _basePositions;
private float[] _baseMagnitudes;
private Vector2[] _scaledPositions;
private float[] _scaledMagnitudes;
private Vector2[][] _scaledPolarDistances;
private float[][] _inverseAverageMagnitudes;
// CONSTRUCTORS
public AnimationBlendTree(Vector2[] positions)
{
_count = positions.Length;
_weights = new float[_count];
_basePositions = new Vector2[_count];
_baseMagnitudes = new float[_count];
_scaledPositions = new Vector2[_count];
_scaledMagnitudes = new float[_count];
_scaledPolarDistances = new Vector2[_count][];
_inverseAverageMagnitudes = new float[_count][];
for (int i = 0; i < _count; ++i)
{
_scaledPolarDistances[i] = new Vector2[_count];
_inverseAverageMagnitudes[i] = new float[_count];
}
for (int i = 0; i < _count; ++i)
{
_basePositions[i] = positions[i];
_baseMagnitudes[i] = positions[i].magnitude;
_size = Mathf.Max(_size, _baseMagnitudes[i]);
}
_scale = 1.0f;
PrecalculateWeights();
}
// PUBLIC METHODS
public void SetPositions(Vector2[] positions)
{
_count = positions.Length;
_weights = new float[_count];
_basePositions = new Vector2[_count];
_baseMagnitudes = new float[_count];
_scaledPositions = new Vector2[_count];
_scaledMagnitudes = new float[_count];
_scaledPolarDistances = new Vector2[_count][];
_inverseAverageMagnitudes = new float[_count][];
for (int i = 0; i < _count; ++i)
{
_scaledPolarDistances[i] = new Vector2[_count];
_inverseAverageMagnitudes[i] = new float[_count];
}
for (int i = 0; i < _count; ++i)
{
_basePositions[i] = positions[i];
_baseMagnitudes[i] = positions[i].magnitude;
}
PrecalculateWeights();
}
public void SetScale(float scale)
{
_scale = scale;
PrecalculateWeights();
}
public void CalculateWeights(Vector2 position)
{
float positionMagnitude = position.magnitude;
float accumulatedWeight = 0.0f;
for (int i = 0; i < _count; ++i)
{
float weight = 1.0f;
float positionAngle = GetAngleFast(_scaledPositions[i], position);
float positionPolarDistance = positionMagnitude - _scaledMagnitudes[i];
Vector2[] scaledPolarDistances = _scaledPolarDistances[i];
float[] inverseAverageMagnitudes = _inverseAverageMagnitudes[i];
for (int j = 0; j < _count; ++j)
{
if (i != j)
{
Vector2 scaledPolarDistanceAtoB = scaledPolarDistances[j];
Vector2 scaledPolarDistanceAtoP = new Vector2(positionPolarDistance * inverseAverageMagnitudes[j], positionAngle);
float desiredWeight = 1.0f - scaledPolarDistanceAtoB.x * scaledPolarDistanceAtoP.x - scaledPolarDistanceAtoB.y * scaledPolarDistanceAtoP.y;
if (desiredWeight < weight)
{
weight = desiredWeight;
}
}
}
if (weight < 0.0f)
{
weight = 0.0f;
}
_weights[i] = weight;
accumulatedWeight += weight;
}
if (accumulatedWeight > 0.0f)
{
float accumulatedWeightInverse = 1.0f / accumulatedWeight;
for (int i = 0; i < _count; ++i)
{
_weights[i] *= accumulatedWeightInverse;
}
}
}
// PRIVATE METHODS
private void PrecalculateWeights()
{
for (int i = 0; i < _count; ++i)
{
_scaledPositions[i] = _basePositions[i] * _scale;
_scaledMagnitudes[i] = _baseMagnitudes[i] * _scale;
}
for (int i = 0; i < _count; ++i)
{
Vector2 scaledPositionA = _scaledPositions[i];
float scaledMagnitudeA = _scaledMagnitudes[i];
Vector2[] scaledPolarDistancesA = _scaledPolarDistances[i];
float[] inverseAverageMagnitudesA = _inverseAverageMagnitudes[i];
for (int j = 0; j < _count; ++j)
{
Vector2 scaledPositionB = _scaledPositions[j];
float scaledMagnitudeB = _scaledMagnitudes[j];
Vector2[] scaledPolarDistancesB = _scaledPolarDistances[j];
float[] inverseAverageMagnitudesB = _inverseAverageMagnitudes[j];
float averageMagnitude = (scaledMagnitudeA + scaledMagnitudeB) * 0.5f;
float inverseAverageMagnitude = 1.0f / averageMagnitude;
float angle = GetAngle(scaledPositionA, scaledPositionB);
float polarDistance = scaledMagnitudeB - scaledMagnitudeA;
Vector2 scaledPolarDistanceAtoB = new Vector2(polarDistance * inverseAverageMagnitude, angle);
scaledPolarDistanceAtoB /= scaledPolarDistanceAtoB.sqrMagnitude;
scaledPolarDistancesA[j] = scaledPolarDistanceAtoB;
scaledPolarDistancesB[i] = -scaledPolarDistanceAtoB;
inverseAverageMagnitudesA[j] = inverseAverageMagnitude;
inverseAverageMagnitudesB[i] = inverseAverageMagnitude;
}
}
}
private static float GetAngle(Vector2 a, Vector2 b)
{
if ((a.x == 0 && a.y == 0) || (b.x == 0 && b.y == 0))
return 0.0f;
float x = a.x * b.x + a.y * b.y;
float y = a.x * b.y - a.y * b.x;
return Mathf.Atan2(y, x);
}
private static float GetAngleFast(Vector2 a, Vector2 b)
{
if ((a.x == 0 && a.y == 0) || (b.x == 0 && b.y == 0))
return 0.0f;
float x = a.x * b.x + a.y * b.y;
float y = a.x * b.y - a.y * b.x;
return FastAtan2(y, x);
}
private static float FastAtan(float x)
{
return (0.97239411f - 0.19194795f * x * x) * x;
}
private static float FastAtan2(float y, float x)
{
if (x != 0.0f)
{
float absX = x >= 0.0f ? x : -x;
float absY = y >= 0.0f ? y : -y;
if (absX > absY)
{
if (x > 0.0f)
return FastAtan(y / x);
if (y >= 0.0f)
return FastAtan(y / x) + Mathf.PI;
return FastAtan(y / x) - Mathf.PI;
}
else
{
if (y > 0.0f)
return -FastAtan(x / y) + Mathf.PI * 0.5f;
return -FastAtan(x / y) - Mathf.PI * 0.5f;
}
}
else
{
if (y > 0.0f)
return Mathf.PI * 0.5f;
if (y < 0.0f)
return -Mathf.PI * 0.5f;
}
return 0.0f;
}
}
}