// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Animancer
{
/// Serializable data which defines how to control a .
/// https://kybernetik.com.au/animancer/api/Animancer/WeightedMaskLayersDefinition
[Serializable]
public class WeightedMaskLayersDefinition :
ICopyable,
IEquatable
#if UNITY_EDITOR
, ISerializationCallbackReceiver
#endif
{
/************************************************************************************************************************/
/// The name of the serialized backing field of .
public const string
TransformsField = nameof(_Transforms);
[SerializeField]
private Transform[] _Transforms;
/// s being controlled by this definition.
public ref Transform[] Transforms
=> ref _Transforms;
/************************************************************************************************************************/
/// The name of the serialized backing field of .
public const string
WeightsField = nameof(_Weights);
[SerializeField]
private float[] _Weights;
/// Groups of weights which will be applied to the .
///
/// This is a flattened 2D array containing groups of target weights corresponding to the transforms.
/// With n transforms, indices 0 to n-1 are Group 0, n to n*2-1 are Group 1, etc.
///
public ref float[] Weights
=> ref _Weights;
/************************************************************************************************************************/
/// The number of weight groups in this definition.
public int GroupCount
{
get => _Transforms == null || _Transforms.Length == 0 || _Weights == null
? 0
: _Weights.Length / _Transforms.Length;
set
{
if (_Transforms != null && value > 0)
Array.Resize(ref _Weights, _Transforms.Length * value);
else
_Weights = Array.Empty();
}
}
/************************************************************************************************************************/
/// [Assert-Conditional] Asserts that the `groupIndex` is valid.
[System.Diagnostics.Conditional(Strings.Assertions)]
public void AssertGroupIndex(int groupIndex)
{
if ((uint)groupIndex >= (uint)GroupCount)
throw new ArgumentOutOfRangeException(
nameof(groupIndex),
groupIndex,
$"Must be 0 <= {nameof(groupIndex)} < Group Count ({GroupCount})");
}
/************************************************************************************************************************/
/// Calculates the index of each of the .
public int[] CalculateIndices(WeightedMaskLayerList layers)
{
var indices = new int[_Transforms.Length];
for (int i = 0; i < _Transforms.Length; i++)
{
indices[i] = layers.IndexOf(_Transforms[i]);
#if UNITY_ASSERTIONS
if (indices[i] < 0)
Debug.LogWarning(
$"Unable to find index of {_Transforms[i]} in {nameof(WeightedMaskLayerList)}",
_Transforms[i]);
#endif
}
return indices;
}
/************************************************************************************************************************/
///
/// Adds the `transform` at the specified `index`
/// along with any associated .
///
public void AddTransform(Transform transform)
{
var index = _Transforms.Length;
AnimancerUtilities.InsertAt(ref _Transforms, index, transform);
if (_Transforms.Length == 1 && _Weights.IsNullOrEmpty())
{
_Weights = new float[1];
return;
}
while (index <= _Weights.Length)
{
AnimancerUtilities.InsertAt(ref _Weights, index, 0);
index += _Transforms.Length;
}
}
///
/// Removes the `index` from the
/// along with any associated .
///
public void RemoveTransform(int index)
{
AnimancerUtilities.RemoveAt(ref _Transforms, index);
while (index < _Weights.Length)
{
AnimancerUtilities.RemoveAt(ref _Weights, index);
index += _Transforms.Length;
}
}
/************************************************************************************************************************/
/// Calculates the index in the corresponding to the specified values.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int IndexOfGroup(int groupIndex)
=> groupIndex * _Transforms.Length;
/// Calculates the index in the corresponding to the specified values.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int IndexOf(int groupIndex, int transformIndex)
=> groupIndex * _Transforms.Length + transformIndex;
/************************************************************************************************************************/
/// Gets the specified weight.
/// Returns if the indices are outside the .
public float GetWeight(int groupIndex, int transformIndex)
{
if (Weights == null)
return float.NaN;
var index = IndexOf(groupIndex, transformIndex);
return (uint)index < (uint)Weights.Length
? Weights[index]
: float.NaN;
}
/// Sets the specified weight.
/// Returns false if the indices are outside the .
public bool SetWeight(int groupIndex, int transformIndex, float value)
{
if (Weights == null)
return false;
var index = IndexOf(groupIndex, transformIndex);
if ((uint)index < (uint)Weights.Length)
{
Weights[index] = value;
return true;
}
return false;
}
/************************************************************************************************************************/
///
public void CopyFrom(WeightedMaskLayersDefinition copyFrom, CloneContext context)
{
AnimancerUtilities.CopyExactArray(copyFrom._Transforms, ref _Transforms);
AnimancerUtilities.CopyExactArray(copyFrom._Weights, ref _Weights);
}
/************************************************************************************************************************/
/// Does this definition contain valid data?
public bool IsValid
=> !_Transforms.IsNullOrEmpty()
&& _Weights != null && _Weights.Length > _Transforms.Length;
/************************************************************************************************************************/
///
public bool OnValidate()
=> ValidateArraySizes()
|| RemoveMissingAndDuplicate();
/// Ensures that all the arrays have valid sizes.
public bool ValidateArraySizes()
{
if (_Transforms.IsNullOrEmpty())
{
_Transforms = Array.Empty();
_Weights = Array.Empty();
return true;
}
if (_Weights == null ||
_Weights.Length < _Transforms.Length)
{
AnimancerUtilities.SetLength(ref _Weights, _Transforms.Length);
return true;
}
var expectedWeightCount = (int)Math.Ceiling(_Weights.Length / (double)_Transforms.Length);
expectedWeightCount *= _Transforms.Length;
return AnimancerUtilities.SetLength(ref _Weights, expectedWeightCount);
}
/// Removes any missing or identical .
public bool RemoveMissingAndDuplicate()
{
var removedAny = false;
for (int i = 0; i < _Transforms.Length; i++)
{
var transform = _Transforms[i];
if (transform == null)
{
RemoveTransform(i);
removedAny = true;
}
else
{
var nextIndex = i + 1;
RemoveDuplicates:
nextIndex = Array.IndexOf(_Transforms, transform, nextIndex);
if (nextIndex > i)
{
RemoveTransform(nextIndex);
removedAny = true;
goto RemoveDuplicates;
}
}
}
return removedAny;
}
/************************************************************************************************************************/
#if UNITY_EDITOR
/************************************************************************************************************************/
///
void ISerializationCallbackReceiver.OnBeforeSerialize()
=> OnValidate();
///
void ISerializationCallbackReceiver.OnAfterDeserialize()
=> OnValidate();
/************************************************************************************************************************/
#endif
/************************************************************************************************************************/
/// Returns a summary of this definition.
public override string ToString()
=> $"{nameof(WeightedMaskLayersDefinition)}(" +
$"{nameof(Transforms)}={(Transforms != null ? Transforms.Length : 0)}, " +
$"{nameof(Weights)}={(Weights != null ? Weights.Length : 0)})";
/************************************************************************************************************************/
#region Equality
/************************************************************************************************************************/
/// Are all fields in this object equal to the equivalent in `obj`?
public override bool Equals(object obj)
=> Equals(obj as WeightedMaskLayersDefinition);
/// Are all fields in this object equal to the equivalent fields in `other`?
public bool Equals(WeightedMaskLayersDefinition other)
=> other != null
&& AnimancerUtilities.ContentsAreEqual(_Transforms, other._Transforms)
&& AnimancerUtilities.ContentsAreEqual(_Weights, other._Weights);
/// Are all fields in `a` equal to the equivalent fields in `b`?
public static bool operator ==(WeightedMaskLayersDefinition a, WeightedMaskLayersDefinition b)
=> a is null
? b is null
: a.Equals(b);
/// Are any fields in `a` not equal to the equivalent fields in `b`?
public static bool operator !=(WeightedMaskLayersDefinition a, WeightedMaskLayersDefinition b)
=> !(a == b);
/************************************************************************************************************************/
/// Returns a hash code based on the values of this object's fields.
public override int GetHashCode()
=> AnimancerUtilities.Hash(-871379578,
_Transforms.SafeGetHashCode(),
_Weights.SafeGetHashCode());
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
}