// Animancer // Copyright 2020 Kybernetik // using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEngine; using UnityEngine.Playables; namespace Animancer { /// /// Proxy for an identical interface introduced in Unity 2018.3 to have a class provide its own list of /// s to the Animation Window without an Animator Controller. /// public interface IAnimationClipSource #if UNITY_2018_3_OR_NEWER : UnityEngine.IAnimationClipSource { } #else { /************************************************************************************************************************/ /// Gathers all the animations associated with this object. void GetAnimationClips(List clips); /************************************************************************************************************************/ } #endif /// /// A variant of which uses a instead of a /// so that it can take a to efficiently avoid adding duplicates. /// contains various extension methods for this purpose. /// /// still needs to be the main point of entry for the Animation Window, so this /// interface is only used internally. /// public interface IAnimationClipCollection { /************************************************************************************************************************/ /// Gathers all the animations associated with this object. void GatherAnimationClips(ICollection clips); /************************************************************************************************************************/ } /************************************************************************************************************************/ public static partial class AnimancerUtilities { /************************************************************************************************************************/ /// [Animancer Extension] /// Adds the `clip` to the `clips` if it wasn't there already. /// public static void Gather(this ICollection clips, AnimationClip clip) { if (clip != null && !clips.Contains(clip)) clips.Add(clip); } /************************************************************************************************************************/ /// [Animancer Extension] /// Calls for each of the `newClips`. /// public static void Gather(this ICollection clips, IList newClips) { if (newClips == null) return; var count = newClips.Count; while (--count >= 0) clips.Gather(newClips[count]); } /************************************************************************************************************************/ /// [Animancer Extension] /// Calls for each of the `newClips`. /// public static void Gather(this ICollection clips, IEnumerable newClips) { if (newClips == null) return; foreach (var clip in newClips) clips.Gather(clip); } /************************************************************************************************************************/ private static Editor.ConversionCache _TypeToGetRootTracks; /// [Animancer Extension] /// Calls for each clip in the `asset`. /// public static void GatherFromAsset(this ICollection 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) => { 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 _TrackAssetToGetClips, _TrackAssetToGetChildTracks, _TimelineClipToAnimationClip; /// Gathers all the animations in the `tracks`. private static void GatherAnimationClips(IEnumerable tracks, ICollection clips) { if (tracks == null) return; if (_TrackAssetToGetClips == null) { _TrackAssetToGetClips = new Editor.ConversionCache((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) => { var property = type.GetProperty("animationClip"); if (property != null && property.PropertyType == typeof(AnimationClip)) return property.GetGetMethod(); else return null; }); _TrackAssetToGetChildTracks = new Editor.ConversionCache((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); } } } /************************************************************************************************************************/ /// [Animancer Extension] /// Calls for each clip gathered by /// . /// public static void GatherFromSource(this ICollection clips, IAnimationClipSource source) { if (source == null) return; var list = ObjectPool.AcquireList(); source.GetAnimationClips(list); clips.Gather((IEnumerable)list); ObjectPool.Release(list); } /************************************************************************************************************************/ /// [Animancer Extension] /// Calls for each clip in the `source`, /// supporting both and . /// public static bool GatherFromSource(this ICollection 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; } /************************************************************************************************************************/ /// [Animancer Extension] /// Calls for each of the `sources`. /// public static void GatherFromSources(this ICollection clips, IList sources) { if (sources == null) return; foreach (var source in sources) clips.GatherFromSource(source); } /************************************************************************************************************************/ } }