// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // using System; using System.Collections.Generic; using System.Text; using UnityEngine; namespace Animancer.TransitionLibraries { /// [] /// A library of transitions and other details which can create a . /// /// /// Documentation: /// /// Transition Libraries /// /// https://kybernetik.com.au/animancer/api/Animancer.TransitionLibraries/TransitionLibraryDefinition [Serializable] public class TransitionLibraryDefinition : IAnimationClipSource, ICopyable, IEquatable, IHasDescription { /************************************************************************************************************************/ #region Fields and Properties /************************************************************************************************************************/ [SerializeField] private TransitionAssetBase[] _Transitions = Array.Empty(); /// [] The transitions in this library. /// This property uses an empty array instead of null. public TransitionAssetBase[] Transitions { get => _Transitions; set => _Transitions = value.NullIsEmpty(); } /************************************************************************************************************************/ [SerializeField] private TransitionModifierDefinition[] _Modifiers = Array.Empty(); /// [] Modified fade durations for specific transition combinations. /// This property uses an empty array instead of null. public TransitionModifierDefinition[] Modifiers { get => _Modifiers; set => _Modifiers = value.NullIsEmpty(); } /************************************************************************************************************************/ [SerializeField] private NamedIndex[] _Aliases = Array.Empty(); /// [] Alternate names that can be used to look up transitions. /// /// This array should always be sorted, use if necessary. /// /// This property uses an empty array instead of null. /// public NamedIndex[] Aliases { get => _Aliases; set => _Aliases = value.NullIsEmpty(); } /************************************************************************************************************************/ [SerializeField] [Tooltip(AliasAllTransitionsTooltip)] private bool _AliasAllTransitions; /// [] /// Should all Transitions automatically be registered using their name as an Alias? /// public ref bool AliasAllTransitions => ref _AliasAllTransitions; #if UNITY_EDITOR /// [Editor-Only] [Internal] /// The name of the field which stores the . /// internal const string AliasAllTransitionsField = nameof(_AliasAllTransitions); #endif /// Tooltip for the field. public const string AliasAllTransitionsTooltip = "Should all Transitions automatically be registered using their name as an Alias?"; /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Transitions /************************************************************************************************************************/ /// /// for the . /// public bool TryGetTransition( int index, out TransitionAssetBase transition) => _Transitions.TryGet(index, out transition) && transition != null; /************************************************************************************************************************/ /// Adds an item to the end of the . public void AddTransition( TransitionAssetBase transition) => AnimancerUtilities.InsertAt( ref _Transitions, _Transitions.Length, transition); /************************************************************************************************************************/ /// /// Removes an item from the /// and adjusts the other fields to account for the moved indices. /// public void RemoveTransition(int index) { if ((uint)index >= _Transitions.Length) return; AnimancerUtilities.RemoveAt(ref _Transitions, index); for (int i = _Modifiers.Length - 1; i >= 0; i--) { var modifier = _Modifiers[i]; // Remove any modifiers targeting that transition. if (modifier.FromIndex == index || modifier.ToIndex == index) { AnimancerUtilities.RemoveAt(ref _Modifiers, i); } else// Adjust the indices of any modifiers after it. { var fromIndex = modifier.FromIndex; if (fromIndex > index) fromIndex--; var toIndex = modifier.ToIndex; if (toIndex > index) toIndex--; _Modifiers[i] = modifier.WithIndices(fromIndex, toIndex); } } for (int i = _Aliases.Length - 1; i >= 0; i--) { var alias = _Aliases[i]; // Remove any aliases targeting that transition. if (alias.Index == index) { AnimancerUtilities.RemoveAt(ref _Aliases, i); } else// Adjust the indices of any aliases after it. { if (alias.Index > index) _Aliases[i] = alias.With(alias.Index - 1); } } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Modifiers /************************************************************************************************************************/ /// Tries to find an item in the with the specified indices. /// /// If unsuccessful, the `modifier` is given the /// from the at the `toIndex`. and this method returns false. /// public bool TryGetModifier( int fromIndex, int toIndex, out TransitionModifierDefinition modifier) { var index = IndexOfModifier(fromIndex, toIndex); if (index >= 0) { modifier = _Modifiers[index]; return true; } var fadeDuration = TryGetTransition(toIndex, out var transition) ? transition.TryGetFadeDuration() : float.NaN; modifier = new(fromIndex, toIndex, fadeDuration); return false; } /************************************************************************************************************************/ /// /// Returns the index in the which matches the given /// and /// or -1 if no such item exists. /// public int IndexOfModifier(int fromIndex, int toIndex) { for (int i = _Modifiers.Length - 1; i >= 0; i--) { var modifier = _Modifiers[i]; if (modifier.FromIndex == fromIndex && modifier.ToIndex == toIndex) return i; } return -1; } /************************************************************************************************************************/ /// Adds or replaces an item in the . public void SetModifier( TransitionModifierDefinition modifier) { if (float.IsNaN(modifier.FadeDuration)) { RemoveModifier(modifier); return; } if (modifier.FadeDuration < 0) modifier = modifier.WithFadeDuration(0); var index = IndexOfModifier(modifier.FromIndex, modifier.ToIndex); if (index >= 0) { _Modifiers[index] = modifier; } else { AnimancerUtilities.InsertAt(ref _Modifiers, _Modifiers.Length, modifier); } } /************************************************************************************************************************/ /// Removes an item from the . public bool RemoveModifier( TransitionModifierDefinition modifier) => RemoveModifier(modifier.FromIndex, modifier.ToIndex); /// Removes an item from the . public bool RemoveModifier(int fromIndex, int toIndex) { var index = IndexOfModifier(fromIndex, toIndex); if (index < 0) return false; AnimancerUtilities.RemoveAt(ref _Modifiers, index); return true; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Aliases /************************************************************************************************************************/ /// Adds an item to the , sorted by its values. public int AddAlias(NamedIndex alias) { int i = 0; for (; i < _Aliases.Length; i++) if (alias.CompareTo(_Aliases[i]) <= 0) break; AnimancerUtilities.InsertAt(ref _Aliases, i, alias); return i; } /************************************************************************************************************************/ /// Removes an item from the . public bool RemoveAlias(NamedIndex alias) { var index = Array.IndexOf(_Aliases, alias); if (index < 0) return false; RemoveAlias(index); return true; } /// Removes an item from the . public void RemoveAlias(int index) => AnimancerUtilities.RemoveAt(ref _Aliases, index); /************************************************************************************************************************/ /// Ensures that the are sorted. /// This method shouldn't need to be called manually since aliases are always added in order. public void SortAliases() => Array.Sort(_Aliases, (a, b) => a.CompareTo(b)); /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Equality /************************************************************************************************************************/ /// Are all fields in this object equal to the equivalent in `obj`? public override bool Equals(object obj) => Equals(obj as TransitionLibraryDefinition); /// Are all fields in this object equal to the equivalent fields in `other`? public bool Equals(TransitionLibraryDefinition other) => other != null && AnimancerUtilities.ContentsAreEqual(_Transitions, other._Transitions) && AnimancerUtilities.ContentsAreEqual(_Modifiers, other._Modifiers) && AnimancerUtilities.ContentsAreEqual(_Aliases, other._Aliases) && _AliasAllTransitions == other._AliasAllTransitions; /// Are all fields in `a` equal to the equivalent fields in `b`? public static bool operator ==(TransitionLibraryDefinition a, TransitionLibraryDefinition 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 !=(TransitionLibraryDefinition a, TransitionLibraryDefinition b) => !(a == b); /************************************************************************************************************************/ /// Returns a hash code based on the values of this object's fields. public override int GetHashCode() => AnimancerUtilities.Hash(-871379578, _Transitions.SafeGetHashCode(), _Modifiers.SafeGetHashCode(), _Aliases.SafeGetHashCode(), _AliasAllTransitions.SafeGetHashCode()); /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Other /************************************************************************************************************************/ /// Gathers all the animations in this definition. public void GetAnimationClips(List results) => results.GatherFromSource(_Transitions); /************************************************************************************************************************/ /// public void CopyFrom(TransitionLibraryDefinition copyFrom, CloneContext context) { AnimancerUtilities.CopyExactArray(copyFrom._Transitions, ref _Transitions); AnimancerUtilities.CopyExactArray(copyFrom._Modifiers, ref _Modifiers); AnimancerUtilities.CopyExactArray(copyFrom._Aliases, ref _Aliases); _AliasAllTransitions = copyFrom._AliasAllTransitions; } /************************************************************************************************************************/ /// public void AppendDescription(StringBuilder text, string separator = "\n") { text.Append(GetType().Name); if (!separator.StartsWithNewLine()) separator = "\n" + separator; var indentedSeparator = separator + Strings.Indent; text.AppendField(separator, nameof(Transitions), Transitions.Length); for (int i = 0; i < Transitions.Length; i++) text.AppendField(indentedSeparator, i.ToString(), Transitions[i]); text.AppendField(separator, nameof(Modifiers), Modifiers.Length); for (int i = 0; i < Modifiers.Length; i++) text.AppendField(indentedSeparator, i.ToString(), Modifiers[i]); text.AppendField(separator, nameof(Aliases), Aliases.Length); for (int i = 0; i < Aliases.Length; i++) text.AppendField(indentedSeparator, i.ToString(), Aliases[i]); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }