249 lines
6.3 KiB
C#
249 lines
6.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|