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