You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
11 KiB
C#
268 lines
11 KiB
C#
// 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);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
|