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