// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik // using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEngine; using UnityEngine.Playables; namespace Animancer { /// /// 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. /// /// https://kybernetik.com.au/animancer/api/Animancer/IAnimationClipCollection /// public interface IAnimationClipCollection { /************************************************************************************************************************/ /// Adds all the animations associated with this object to the `clips`. void GatherAnimationClips(ICollection clips); /************************************************************************************************************************/ } /************************************************************************************************************************/ /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerUtilities 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 gatherFrom) { if (gatherFrom == null) return; for (int i = gatherFrom.Count - 1; i >= 0; i--) clips.Gather(gatherFrom[i]); } /************************************************************************************************************************/ /// [Animancer Extension] /// Calls for each of the `newClips`. /// public static void Gather(this ICollection clips, IEnumerable gatherFrom) { if (gatherFrom == null) return; foreach (var clip in gatherFrom) clips.Gather(clip); } /************************************************************************************************************************/ /// [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. var method = asset.GetType().GetMethod("GetRootTracks"); if (method != null && typeof(IEnumerable).IsAssignableFrom(method.ReturnType) && method.GetParameters().Length == 0) { var rootTracks = method.Invoke(asset, null); GatherFromTracks(clips, rootTracks as IEnumerable); } } /************************************************************************************************************************/ /// Gathers all the animations in the `tracks`. private static void GatherFromTracks(ICollection clips, IEnumerable tracks) { if (tracks == null) return; foreach (var track in tracks) { if (track == null) continue; var trackType = track.GetType(); var getClips = trackType.GetMethod("GetClips"); if (getClips != null && typeof(IEnumerable).IsAssignableFrom(getClips.ReturnType) && getClips.GetParameters().Length == 0) { var trackClips = getClips.Invoke(track, null) as IEnumerable; if (trackClips != null) { foreach (var clip in trackClips) { var animationClip = clip.GetType().GetProperty("animationClip"); if (animationClip != null && animationClip.PropertyType == typeof(AnimationClip)) { var getClip = animationClip.GetGetMethod(); clips.Gather(getClip.Invoke(clip, null) as AnimationClip); } } } } var getChildTracks = trackType.GetMethod("GetChildTracks"); if (getChildTracks != null && typeof(IEnumerable).IsAssignableFrom(getChildTracks.ReturnType) && getChildTracks.GetParameters().Length == 0) { var childTracks = getChildTracks.Invoke(track, null); GatherFromTracks(clips, childTracks as IEnumerable); } } } /************************************************************************************************************************/ /// [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(list); ObjectPool.Release(list); } /************************************************************************************************************************/ /// [Animancer Extension] /// Calls for each item in the `source`. /// public static void GatherFromSource(this ICollection clips, IEnumerable source) { if (source != null) foreach (var item in source) clips.GatherFromSource(item); } /************************************************************************************************************************/ /// [Animancer Extension] /// Calls for each clip in the `source`, /// supporting both and . /// public static bool GatherFromSource(this ICollection clips, object source) { if (TryGetWrappedObject(source, out AnimationClip clip)) { clips.Gather(clip); return true; } if (TryGetWrappedObject(source, out IAnimationClipCollection collectionSource)) { collectionSource.GatherAnimationClips(clips); return true; } if (TryGetWrappedObject(source, out IAnimationClipSource listSource)) { clips.GatherFromSource(listSource); return true; } if (TryGetWrappedObject(source, out IEnumerable enumerable)) { clips.GatherFromSource(enumerable); return true; } return false; } /************************************************************************************************************************/ /// /// Attempts to get the from the `clipSource` and returns true if /// successful. If it has multiple animations with different rates, this method returns false. /// public static bool TryGetFrameRate(object clipSource, out float frameRate) { using (ObjectPool.Disposable.AcquireSet(out var clips)) { clips.GatherFromSource(clipSource); if (clips.Count == 0) { frameRate = float.NaN; return false; } frameRate = float.NaN; foreach (var clip in clips) { if (float.IsNaN(frameRate)) { frameRate = clip.frameRate; } else if (frameRate != clip.frameRate) { frameRate = float.NaN; return false; } } return frameRate > 0; } } /************************************************************************************************************************/ } }