323 lines
13 KiB
C#
323 lines
13 KiB
C#
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
|
|
|
using System;
|
|
using System.Runtime.CompilerServices;
|
|
using UnityEngine;
|
|
|
|
namespace Animancer
|
|
{
|
|
/// <summary>Serializable data which defines how to control a <see cref="WeightedMaskLayerList"/>.</summary>
|
|
/// https://kybernetik.com.au/animancer/api/Animancer/WeightedMaskLayersDefinition
|
|
[Serializable]
|
|
public class WeightedMaskLayersDefinition :
|
|
ICopyable<WeightedMaskLayersDefinition>,
|
|
IEquatable<WeightedMaskLayersDefinition>
|
|
#if UNITY_EDITOR
|
|
, ISerializationCallbackReceiver
|
|
#endif
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The name of the serialized backing field of <see cref="Transforms"/>.</summary>
|
|
public const string
|
|
TransformsField = nameof(_Transforms);
|
|
|
|
[SerializeField]
|
|
private Transform[] _Transforms;
|
|
|
|
/// <summary><see cref="Transform"/>s being controlled by this definition.</summary>
|
|
public ref Transform[] Transforms
|
|
=> ref _Transforms;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The name of the serialized backing field of <see cref="Weights"/>.</summary>
|
|
public const string
|
|
WeightsField = nameof(_Weights);
|
|
|
|
[SerializeField]
|
|
private float[] _Weights;
|
|
|
|
/// <summary>Groups of weights which will be applied to the <see cref="Transforms"/>.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public ref float[] Weights
|
|
=> ref _Weights;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The number of weight groups in this definition.</summary>
|
|
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<float>();
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Assert-Conditional] Asserts that the `groupIndex` is valid.</summary>
|
|
[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})");
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Calculates the index of each of the <see cref="Transforms"/>.</summary>
|
|
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;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Adds the `transform` at the specified `index`
|
|
/// along with any associated <see cref="_Weights"/>.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the `index` from the <see cref="_Transforms"/>
|
|
/// along with any associated <see cref="_Weights"/>.
|
|
/// </summary>
|
|
public void RemoveTransform(int index)
|
|
{
|
|
AnimancerUtilities.RemoveAt(ref _Transforms, index);
|
|
|
|
while (index < _Weights.Length)
|
|
{
|
|
AnimancerUtilities.RemoveAt(ref _Weights, index);
|
|
|
|
index += _Transforms.Length;
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Calculates the index in the <see cref="Weights"/> corresponding to the specified values.</summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public int IndexOfGroup(int groupIndex)
|
|
=> groupIndex * _Transforms.Length;
|
|
|
|
/// <summary>Calculates the index in the <see cref="Weights"/> corresponding to the specified values.</summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public int IndexOf(int groupIndex, int transformIndex)
|
|
=> groupIndex * _Transforms.Length + transformIndex;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Gets the specified weight.</summary>
|
|
/// <remarks>Returns <see cref="float.NaN"/> if the indices are outside the <see cref="Weights"/>.</remarks>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Sets the specified weight.</summary>
|
|
/// <remarks>Returns false if the indices are outside the <see cref="Weights"/>.</remarks>
|
|
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;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <inheritdoc/>
|
|
public void CopyFrom(WeightedMaskLayersDefinition copyFrom, CloneContext context)
|
|
{
|
|
AnimancerUtilities.CopyExactArray(copyFrom._Transforms, ref _Transforms);
|
|
AnimancerUtilities.CopyExactArray(copyFrom._Weights, ref _Weights);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Does this definition contain valid data?</summary>
|
|
public bool IsValid
|
|
=> !_Transforms.IsNullOrEmpty()
|
|
&& _Weights != null && _Weights.Length > _Transforms.Length;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <inheritdoc/>
|
|
public bool OnValidate()
|
|
=> ValidateArraySizes()
|
|
|| RemoveMissingAndDuplicate();
|
|
|
|
/// <summary>Ensures that all the arrays have valid sizes.</summary>
|
|
public bool ValidateArraySizes()
|
|
{
|
|
if (_Transforms.IsNullOrEmpty())
|
|
{
|
|
_Transforms = Array.Empty<Transform>();
|
|
_Weights = Array.Empty<float>();
|
|
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);
|
|
}
|
|
|
|
/// <summary>Removes any missing or identical <see cref="_Transforms"/>.</summary>
|
|
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
|
|
/************************************************************************************************************************/
|
|
|
|
/// <inheritdoc/>
|
|
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
|
=> OnValidate();
|
|
|
|
/// <inheritdoc/>
|
|
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
|
=> OnValidate();
|
|
|
|
/************************************************************************************************************************/
|
|
#endif
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Returns a summary of this definition.</summary>
|
|
public override string ToString()
|
|
=> $"{nameof(WeightedMaskLayersDefinition)}(" +
|
|
$"{nameof(Transforms)}={(Transforms != null ? Transforms.Length : 0)}, " +
|
|
$"{nameof(Weights)}={(Weights != null ? Weights.Length : 0)})";
|
|
|
|
/************************************************************************************************************************/
|
|
#region Equality
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Are all fields in this object equal to the equivalent in `obj`?</summary>
|
|
public override bool Equals(object obj)
|
|
=> Equals(obj as WeightedMaskLayersDefinition);
|
|
|
|
/// <summary>Are all fields in this object equal to the equivalent fields in `other`?</summary>
|
|
public bool Equals(WeightedMaskLayersDefinition other)
|
|
=> other != null
|
|
&& AnimancerUtilities.ContentsAreEqual(_Transforms, other._Transforms)
|
|
&& AnimancerUtilities.ContentsAreEqual(_Weights, other._Weights);
|
|
|
|
/// <summary>Are all fields in `a` equal to the equivalent fields in `b`?</summary>
|
|
public static bool operator ==(WeightedMaskLayersDefinition a, WeightedMaskLayersDefinition b)
|
|
=> a is null
|
|
? b is null
|
|
: a.Equals(b);
|
|
|
|
/// <summary>Are any fields in `a` not equal to the equivalent fields in `b`?</summary>
|
|
public static bool operator !=(WeightedMaskLayersDefinition a, WeightedMaskLayersDefinition b)
|
|
=> !(a == b);
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Returns a hash code based on the values of this object's fields.</summary>
|
|
public override int GetHashCode()
|
|
=> AnimancerUtilities.Hash(-871379578,
|
|
_Transforms.SafeGetHashCode(),
|
|
_Weights.SafeGetHashCode());
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
|