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