// Animancer // Copyright 2020 Kybernetik // using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEngine; using UnityEngine.Playables; namespace Animancer { /// <summary> /// Proxy for an identical interface introduced in Unity 2018.3 to have a class provide its own list of /// <see cref="AnimationClip"/>s to the Animation Window without an Animator Controller. /// </summary> public interface IAnimationClipSource #if UNITY_2018_3_OR_NEWER : UnityEngine.IAnimationClipSource { } #else { /************************************************************************************************************************/ /// <summary>Gathers all the animations associated with this object.</summary> void GetAnimationClips(List<AnimationClip> clips); /************************************************************************************************************************/ } #endif /// <summary> /// A variant of <see cref="IAnimationClipSource"/> which uses a <see cref="ICollection{T}"/> instead of a /// <see cref="List{T}"/> so that it can take a <see cref="HashSet{T}"/> to efficiently avoid adding duplicates. /// <see cref="AnimancerUtilities"/> contains various extension methods for this purpose. /// <para></para> /// <see cref="IAnimationClipSource"/> still needs to be the main point of entry for the Animation Window, so this /// interface is only used internally. /// </summary> public interface IAnimationClipCollection { /************************************************************************************************************************/ /// <summary>Gathers all the animations associated with this object.</summary> void GatherAnimationClips(ICollection<AnimationClip> clips); /************************************************************************************************************************/ } /************************************************************************************************************************/ public static partial class AnimancerUtilities { /************************************************************************************************************************/ /// <summary>[Animancer Extension] /// Adds the `clip` to the `clips` if it wasn't there already. /// </summary> public static void Gather(this ICollection<AnimationClip> clips, AnimationClip clip) { if (clip != null && !clips.Contains(clip)) clips.Add(clip); } /************************************************************************************************************************/ /// <summary>[Animancer Extension] /// Calls <see cref="Gather(ICollection{AnimationClip}, AnimationClip)"/> for each of the `newClips`. /// </summary> public static void Gather(this ICollection<AnimationClip> clips, IList<AnimationClip> newClips) { if (newClips == null) return; var count = newClips.Count; while (--count >= 0) clips.Gather(newClips[count]); } /************************************************************************************************************************/ /// <summary>[Animancer Extension] /// Calls <see cref="Gather(ICollection{AnimationClip}, AnimationClip)"/> for each of the `newClips`. /// </summary> public static void Gather(this ICollection<AnimationClip> clips, IEnumerable<AnimationClip> newClips) { if (newClips == null) return; foreach (var clip in newClips) clips.Gather(clip); } /************************************************************************************************************************/ private static Editor.ConversionCache<Type, MethodInfo> _TypeToGetRootTracks; /// <summary>[Animancer Extension] /// Calls <see cref="Gather(ICollection{AnimationClip}, AnimationClip)"/> for each clip in the `asset`. /// </summary> public static void GatherFromAsset(this ICollection<AnimationClip> clips, PlayableAsset asset) { if (asset == null) return; // We want to get the tracks out of a TimelineAsset without actually referencing that class directly // because it comes from an optional package and Animancer does not need to depend on that package. if (_TypeToGetRootTracks == null) { _TypeToGetRootTracks = new Editor.ConversionCache<Type, MethodInfo>((type) => { var method = type.GetMethod("GetRootTracks"); if (method != null && typeof(IEnumerable).IsAssignableFrom(method.ReturnType) && method.GetParameters().Length == 0) return method; else return null; }); } var getRootTracks = _TypeToGetRootTracks.Convert(asset.GetType()); if (getRootTracks != null) { var rootTracks = getRootTracks.Invoke(asset, null); GatherAnimationClips(rootTracks as IEnumerable, clips); } } /************************************************************************************************************************/ private static Editor.ConversionCache<Type, MethodInfo> _TrackAssetToGetClips, _TrackAssetToGetChildTracks, _TimelineClipToAnimationClip; /// <summary>Gathers all the animations in the `tracks`.</summary> private static void GatherAnimationClips(IEnumerable tracks, ICollection<AnimationClip> clips) { if (tracks == null) return; if (_TrackAssetToGetClips == null) { _TrackAssetToGetClips = new Editor.ConversionCache<Type, MethodInfo>((type) => { var method = type.GetMethod("GetClips"); if (method != null && typeof(IEnumerable).IsAssignableFrom(method.ReturnType) && method.GetParameters().Length == 0) return method; else return null; }); _TimelineClipToAnimationClip = new Editor.ConversionCache<Type, MethodInfo>((type) => { var property = type.GetProperty("animationClip"); if (property != null && property.PropertyType == typeof(AnimationClip)) return property.GetGetMethod(); else return null; }); _TrackAssetToGetChildTracks = new Editor.ConversionCache<Type, MethodInfo>((type) => { var method = type.GetMethod("GetChildTracks"); if (method != null && typeof(IEnumerable).IsAssignableFrom(method.ReturnType) && method.GetParameters().Length == 0) return method; else return null; }); } foreach (var track in tracks) { if (track == null) continue; var trackType = track.GetType(); var getClips = _TrackAssetToGetClips.Convert(trackType); if (getClips != null) { var trackClips = getClips.Invoke(track, null) as IEnumerable; if (trackClips != null) { foreach (var clip in trackClips) { var getClip = _TimelineClipToAnimationClip.Convert(clip.GetType()); if (getClip != null) clips.Gather(getClip.Invoke(clip, null) as AnimationClip); } } } var getChildTracks = _TrackAssetToGetChildTracks.Convert(trackType); if (getChildTracks != null) { var childTracks = getChildTracks.Invoke(track, null); GatherAnimationClips(childTracks as IEnumerable, clips); } } } /************************************************************************************************************************/ /// <summary>[Animancer Extension] /// Calls <see cref="Gather(ICollection{AnimationClip}, AnimationClip)"/> for each clip gathered by /// <see cref="IAnimationClipSource.GetAnimationClips"/>. /// </summary> public static void GatherFromSource(this ICollection<AnimationClip> clips, IAnimationClipSource source) { if (source == null) return; var list = ObjectPool.AcquireList<AnimationClip>(); source.GetAnimationClips(list); clips.Gather((IEnumerable<AnimationClip>)list); ObjectPool.Release(list); } /************************************************************************************************************************/ /// <summary>[Animancer Extension] /// Calls <see cref="Gather(ICollection{AnimationClip}, AnimationClip)"/> for each clip in the `source`, /// supporting both <see cref="IAnimationClipSource"/> and <see cref="IAnimationClipCollection"/>. /// </summary> public static bool GatherFromSource(this ICollection<AnimationClip> clips, object source) { var collectionSource = source as IAnimationClipCollection; if (collectionSource != null) { collectionSource.GatherAnimationClips(clips); return true; } var listSource = source as IAnimationClipSource; if (listSource != null) { clips.GatherFromSource(listSource); return true; } return false; } /************************************************************************************************************************/ /// <summary>[Animancer Extension] /// Calls <see cref="GatherFromSource(ICollection{AnimationClip}, object)"/> for each of the `sources`. /// </summary> public static void GatherFromSources(this ICollection<AnimationClip> clips, IList sources) { if (sources == null) return; foreach (var source in sources) clips.GatherFromSource(source); } /************************************************************************************************************************/ } }