123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- // 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
- /************************************************************************************************************************/
- }
- }
|