Initial Commit
This commit is contained in:
parent
53eb92e9af
commit
270ab7d11f
15341 changed files with 700234 additions and 0 deletions
|
@ -0,0 +1,301 @@
|
|||
using System;
|
||||
#if UNITY_EDITOR
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class AnimationPreviewUtilities
|
||||
{
|
||||
private const string k_PosX = "m_LocalPosition.x";
|
||||
private const string k_PosY = "m_LocalPosition.y";
|
||||
private const string k_PosZ = "m_LocalPosition.z";
|
||||
private const string k_RotX = "m_LocalRotation.x";
|
||||
private const string k_RotY = "m_LocalRotation.y";
|
||||
private const string k_RotZ = "m_LocalRotation.z";
|
||||
private const string k_RotW = "m_LocalRotation.w";
|
||||
private const string k_ScaleX = "m_LocalScale.x";
|
||||
private const string k_ScaleY = "m_LocalScale.y";
|
||||
private const string k_ScaleZ = "m_LocalScale.z";
|
||||
private const string k_EulerAnglesRaw = "localEulerAnglesRaw";
|
||||
private const string k_EulerHint = "m_LocalEulerAnglesHint";
|
||||
private const string k_Pos = "m_LocalPosition";
|
||||
private const string k_Rot = "m_LocalRotation";
|
||||
private const string k_MotionT = "MotionT";
|
||||
private const string k_MotionQ = "MotionQ";
|
||||
private const string k_RootT = "RootT";
|
||||
private const string k_RootQ = "RootQ";
|
||||
|
||||
|
||||
internal static Object s_PreviewDriver;
|
||||
|
||||
|
||||
internal class EditorCurveBindingComparer : IEqualityComparer<EditorCurveBinding>
|
||||
{
|
||||
public bool Equals(EditorCurveBinding x, EditorCurveBinding y) { return x.path.Equals(y.path) && x.type == y.type && x.propertyName == y.propertyName; }
|
||||
public int GetHashCode(EditorCurveBinding obj)
|
||||
{
|
||||
return obj.propertyName.GetHashCode() ^ obj.path.GetHashCode();
|
||||
}
|
||||
|
||||
public static readonly EditorCurveBindingComparer Instance = new EditorCurveBindingComparer();
|
||||
}
|
||||
|
||||
// a dictionary is faster than a hashset, because the capacity can be pre-set
|
||||
private static readonly Dictionary<EditorCurveBinding, int> s_CurveSet = new Dictionary<EditorCurveBinding, int>(10000, EditorCurveBindingComparer.Instance);
|
||||
private static readonly AnimatorBindingCache s_BindingCache = new AnimatorBindingCache();
|
||||
|
||||
// string.StartsWith is slow (https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity5.html)
|
||||
// hand rolled version has best performance.
|
||||
private static bool FastStartsWith(string a, string toCompare)
|
||||
{
|
||||
int aLen = a.Length;
|
||||
int bLen = toCompare.Length;
|
||||
|
||||
int ap = 0;
|
||||
int bp = 0;
|
||||
|
||||
while (ap < aLen && bp < bLen && a[ap] == toCompare[bp])
|
||||
{
|
||||
ap++;
|
||||
bp++;
|
||||
}
|
||||
|
||||
return (bp == bLen);
|
||||
}
|
||||
|
||||
public static void ClearCaches()
|
||||
{
|
||||
s_BindingCache.Clear();
|
||||
s_CurveSet.Clear();
|
||||
}
|
||||
|
||||
public static EditorCurveBinding[] GetBindings(GameObject animatorRoot, IEnumerable<AnimationClip> clips)
|
||||
{
|
||||
s_CurveSet.Clear();
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
AddBindings(s_BindingCache.GetCurveBindings(clip));
|
||||
}
|
||||
|
||||
// if we have a transform binding, bind the entire skeleton
|
||||
if (NeedsSkeletonBindings(s_CurveSet.Keys))
|
||||
AddBindings(s_BindingCache.GetAnimatorBindings(animatorRoot));
|
||||
|
||||
var bindings = new EditorCurveBinding[s_CurveSet.Keys.Count];
|
||||
s_CurveSet.Keys.CopyTo(bindings, 0);
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public static int GetClipHash(List<AnimationClip> clips)
|
||||
{
|
||||
int hash = 0;
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var stats = AnimationUtility.GetAnimationClipStats(clip);
|
||||
hash = HashUtility.CombineHash(hash, clip.GetHashCode(), stats.clips, stats.size, stats.totalCurves);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static void PreviewFromCurves(GameObject animatorRoot, IEnumerable<EditorCurveBinding> keys)
|
||||
{
|
||||
if (!AnimationMode.InAnimationMode())
|
||||
return;
|
||||
|
||||
var avatarRoot = GetAvatarRoot(animatorRoot);
|
||||
foreach (var binding in keys)
|
||||
{
|
||||
if (IsAvatarBinding(binding) || IsEuler(binding))
|
||||
continue;
|
||||
|
||||
bool isTransform = typeof(Transform).IsAssignableFrom(binding.type);
|
||||
if (isTransform && binding.propertyName.Equals(AnimatorBindingCache.TRPlaceHolder))
|
||||
AddTRBinding(animatorRoot, binding);
|
||||
else if (isTransform && binding.propertyName.Equals(AnimatorBindingCache.ScalePlaceholder))
|
||||
AddScaleBinding(animatorRoot, binding);
|
||||
else
|
||||
AnimationMode.AddEditorCurveBinding(avatarRoot, binding);
|
||||
}
|
||||
}
|
||||
|
||||
public static AnimationClip CreateDefaultClip(GameObject animatorRoot, IEnumerable<EditorCurveBinding> keys)
|
||||
{
|
||||
AnimationClip animClip = new AnimationClip() { name = "DefaultPose" };
|
||||
var keyFrames = new[] {new Keyframe(0, 0)};
|
||||
var curve = new AnimationCurve(keyFrames);
|
||||
bool rootMotion = false;
|
||||
var avatarRoot = GetAvatarRoot(animatorRoot);
|
||||
|
||||
foreach (var binding in keys)
|
||||
{
|
||||
if (IsRootMotion(binding))
|
||||
{
|
||||
rootMotion = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName.Equals(AnimatorBindingCache.TRPlaceHolder))
|
||||
{
|
||||
if (string.IsNullOrEmpty(binding.path))
|
||||
rootMotion = true;
|
||||
else
|
||||
{
|
||||
var transform = animatorRoot.transform.Find(binding.path);
|
||||
if (transform != null)
|
||||
{
|
||||
var pos = transform.localPosition;
|
||||
var rot = transform.localRotation;
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_PosX, SetZeroKey(curve, keyFrames, pos.x));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_PosY, SetZeroKey(curve, keyFrames, pos.y));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_PosZ, SetZeroKey(curve, keyFrames, pos.z));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_RotX, SetZeroKey(curve, keyFrames, rot.x));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_RotY, SetZeroKey(curve, keyFrames, rot.y));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_RotZ, SetZeroKey(curve, keyFrames, rot.z));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_RotW, SetZeroKey(curve, keyFrames, rot.w));
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName == AnimatorBindingCache.ScalePlaceholder)
|
||||
{
|
||||
var transform = animatorRoot.transform.Find(binding.path);
|
||||
if (transform != null)
|
||||
{
|
||||
var scale = transform.localScale;
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_ScaleX, SetZeroKey(curve, keyFrames, scale.x));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_ScaleY, SetZeroKey(curve, keyFrames, scale.y));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_ScaleZ, SetZeroKey(curve, keyFrames, scale.z));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not setting curves through AnimationUtility.SetEditorCurve to avoid reentrant
|
||||
// onCurveWasModified calls in timeline. This means we don't get sprite curves
|
||||
// in the default clip right now.
|
||||
if (IsAvatarBinding(binding) || IsEulerHint(binding) || binding.isPPtrCurve)
|
||||
continue;
|
||||
|
||||
float floatValue;
|
||||
AnimationUtility.GetFloatValue(avatarRoot, binding, out floatValue);
|
||||
animClip.SetCurve(binding.path, binding.type, binding.propertyName, SetZeroKey(curve, keyFrames, floatValue));
|
||||
}
|
||||
|
||||
// add root motion explicitly.
|
||||
if (rootMotion)
|
||||
{
|
||||
var pos = Vector3.zero; // the appropriate root motion offsets are applied by timeline
|
||||
var rot = Quaternion.identity;
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_PosX, SetZeroKey(curve, keyFrames, pos.x));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_PosY, SetZeroKey(curve, keyFrames, pos.y));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_PosZ, SetZeroKey(curve, keyFrames, pos.z));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_RotX, SetZeroKey(curve, keyFrames, rot.x));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_RotY, SetZeroKey(curve, keyFrames, rot.y));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_RotZ, SetZeroKey(curve, keyFrames, rot.z));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_RotW, SetZeroKey(curve, keyFrames, rot.w));
|
||||
}
|
||||
|
||||
return animClip;
|
||||
}
|
||||
|
||||
public static bool IsRootMotion(EditorCurveBinding binding)
|
||||
{
|
||||
// Root Transform TR.
|
||||
if (typeof(Transform).IsAssignableFrom(binding.type) && string.IsNullOrEmpty(binding.path))
|
||||
{
|
||||
return FastStartsWith(binding.propertyName, k_Pos) || FastStartsWith(binding.propertyName, k_Rot);
|
||||
}
|
||||
|
||||
// MotionCurves/RootCurves.
|
||||
if (binding.type == typeof(Animator))
|
||||
{
|
||||
return FastStartsWith(binding.propertyName, k_MotionT) ||
|
||||
FastStartsWith(binding.propertyName, k_MotionQ) ||
|
||||
FastStartsWith(binding.propertyName, k_RootT) ||
|
||||
FastStartsWith(binding.propertyName, k_RootQ);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool NeedsSkeletonBindings(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
foreach (var b in bindings)
|
||||
{
|
||||
if (IsSkeletalBinding(b))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void AddBindings(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
foreach (var b in bindings)
|
||||
{
|
||||
if (!s_CurveSet.ContainsKey(b))
|
||||
s_CurveSet[b] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddTRBinding(GameObject root, EditorCurveBinding binding)
|
||||
{
|
||||
var t = root.transform.Find(binding.path);
|
||||
if (t != null)
|
||||
{
|
||||
DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalPosition");
|
||||
DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalRotation");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddScaleBinding(GameObject root, EditorCurveBinding binding)
|
||||
{
|
||||
var t = root.transform.Find(binding.path);
|
||||
if (t != null)
|
||||
DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalScale");
|
||||
}
|
||||
|
||||
private static bool IsEuler(EditorCurveBinding binding)
|
||||
{
|
||||
return FastStartsWith(binding.propertyName, k_EulerAnglesRaw) &&
|
||||
typeof(Transform).IsAssignableFrom(binding.type);
|
||||
}
|
||||
|
||||
private static bool IsAvatarBinding(EditorCurveBinding binding)
|
||||
{
|
||||
return string.IsNullOrEmpty(binding.path) && typeof(Animator) == binding.type;
|
||||
}
|
||||
|
||||
private static bool IsSkeletalBinding(EditorCurveBinding binding)
|
||||
{
|
||||
// skin mesh incorporates blend shapes
|
||||
return typeof(Transform).IsAssignableFrom(binding.type) || typeof(SkinnedMeshRenderer).IsAssignableFrom(binding.type);
|
||||
}
|
||||
|
||||
private static AnimationCurve SetZeroKey(AnimationCurve curve, Keyframe[] keys, float val)
|
||||
{
|
||||
keys[0].value = val;
|
||||
curve.keys = keys;
|
||||
return curve;
|
||||
}
|
||||
|
||||
private static bool IsEulerHint(EditorCurveBinding binding)
|
||||
{
|
||||
return typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName.StartsWith(k_EulerHint);
|
||||
}
|
||||
|
||||
private static GameObject GetAvatarRoot(GameObject animatorRoot)
|
||||
{
|
||||
var animator = animatorRoot.GetComponent<Animator>();
|
||||
if (animator != null && animator.avatarRoot != animatorRoot.transform)
|
||||
return animator.avatarRoot.gameObject;
|
||||
return animatorRoot;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 01da6c5b7c781174d818662ce6f39b8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,133 @@
|
|||
#if UNITY_EDITOR
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Animator to Editor Curve Binding cache. Used to prevent frequent calls to GetAnimatorBindings which can be costly
|
||||
/// </summary>
|
||||
class AnimatorBindingCache
|
||||
{
|
||||
public const string TRPlaceHolder = "TransformTR";
|
||||
public const string ScalePlaceholder = "TransformScale";
|
||||
|
||||
struct AnimatorEntry
|
||||
{
|
||||
public int animatorID;
|
||||
public bool applyRootMotion;
|
||||
public bool humanoid;
|
||||
}
|
||||
|
||||
class AnimatorEntryComparer : IEqualityComparer<AnimatorEntry>
|
||||
{
|
||||
public bool Equals(AnimatorEntry x, AnimatorEntry y) { return x.animatorID == y.animatorID && x.applyRootMotion == y.applyRootMotion && x.humanoid == y.humanoid; }
|
||||
public int GetHashCode(AnimatorEntry obj) { return HashUtility.CombineHash(obj.animatorID, obj.applyRootMotion.GetHashCode(), obj.humanoid.GetHashCode()); }
|
||||
public static readonly AnimatorEntryComparer Instance = new AnimatorEntryComparer();
|
||||
}
|
||||
|
||||
readonly Dictionary<AnimatorEntry, EditorCurveBinding[]> m_AnimatorCache = new Dictionary<AnimatorEntry, EditorCurveBinding[]>(AnimatorEntryComparer.Instance);
|
||||
readonly Dictionary<AnimationClip, EditorCurveBinding[]> m_ClipCache = new Dictionary<AnimationClip, EditorCurveBinding[]>();
|
||||
|
||||
private static readonly EditorCurveBinding[] kEmptyArray = new EditorCurveBinding[0];
|
||||
private static readonly List<EditorCurveBinding> s_BindingScratchPad = new List<EditorCurveBinding>(1000);
|
||||
|
||||
public AnimatorBindingCache()
|
||||
{
|
||||
AnimationUtility.onCurveWasModified += OnCurveWasModified;
|
||||
}
|
||||
|
||||
public EditorCurveBinding[] GetAnimatorBindings(GameObject gameObject)
|
||||
{
|
||||
if (gameObject == null)
|
||||
return kEmptyArray;
|
||||
|
||||
Animator animator = gameObject.GetComponent<Animator>();
|
||||
if (animator == null)
|
||||
return kEmptyArray;
|
||||
|
||||
AnimatorEntry entry = new AnimatorEntry()
|
||||
{
|
||||
animatorID = animator.GetInstanceID(),
|
||||
applyRootMotion = animator.applyRootMotion,
|
||||
humanoid = animator.isHuman
|
||||
};
|
||||
|
||||
EditorCurveBinding[] result = null;
|
||||
if (m_AnimatorCache.TryGetValue(entry, out result))
|
||||
return result;
|
||||
|
||||
s_BindingScratchPad.Clear();
|
||||
|
||||
// Replacement for AnimationMode.GetAnimatorBinding - this is faster and allocates kB instead of MB
|
||||
var transforms = animator.GetComponentsInChildren<Transform>();
|
||||
foreach (var t in transforms)
|
||||
{
|
||||
if (animator.IsBoneTransform(t))
|
||||
s_BindingScratchPad.Add(EditorCurveBinding.FloatCurve(AnimationUtility.CalculateTransformPath(t, animator.transform), typeof(Transform), TRPlaceHolder));
|
||||
}
|
||||
|
||||
var streamBindings = AnimationUtility.GetAnimationStreamBindings(animator.gameObject);
|
||||
UpdateTransformBindings(streamBindings);
|
||||
s_BindingScratchPad.AddRange(streamBindings);
|
||||
|
||||
result = new EditorCurveBinding[s_BindingScratchPad.Count];
|
||||
s_BindingScratchPad.CopyTo(result);
|
||||
m_AnimatorCache[entry] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
public EditorCurveBinding[] GetCurveBindings(AnimationClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
return kEmptyArray;
|
||||
|
||||
EditorCurveBinding[] result;
|
||||
if (!m_ClipCache.TryGetValue(clip, out result))
|
||||
{
|
||||
result = AnimationMode.GetCurveBindings(clip);
|
||||
UpdateTransformBindings(result);
|
||||
m_ClipCache[clip] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void UpdateTransformBindings(EditorCurveBinding[] bindings)
|
||||
{
|
||||
for (int i = 0; i < bindings.Length; i++)
|
||||
{
|
||||
var binding = bindings[i];
|
||||
if (AnimationPreviewUtilities.IsRootMotion(binding))
|
||||
{
|
||||
binding.type = typeof(Transform);
|
||||
binding.propertyName = TRPlaceHolder;
|
||||
}
|
||||
else if (typeof(Transform).IsAssignableFrom(binding.type) && (binding.propertyName.StartsWith("m_LocalRotation.") || binding.propertyName.StartsWith("m_LocalPosition.")))
|
||||
{
|
||||
binding.propertyName = TRPlaceHolder;
|
||||
}
|
||||
else if (typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName.StartsWith("m_LocalScale."))
|
||||
{
|
||||
binding.propertyName = ScalePlaceholder;
|
||||
}
|
||||
bindings[i] = binding;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_AnimatorCache.Clear();
|
||||
m_ClipCache.Clear();
|
||||
}
|
||||
|
||||
void OnCurveWasModified(AnimationClip clip, EditorCurveBinding binding, AnimationUtility.CurveModifiedType modification)
|
||||
{
|
||||
m_ClipCache.Remove(clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d0080567f62c3f94cb75b2927a349e22
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
// Extension methods responsible for managing extrapolation time
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class Extrapolation
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum amount of extrapolation time to apply
|
||||
/// </summary>
|
||||
internal static readonly double kMinExtrapolationTime = TimeUtility.kTimeEpsilon * 1000;
|
||||
|
||||
// Calculates the extrapolation times
|
||||
internal static void CalculateExtrapolationTimes(this TrackAsset asset)
|
||||
{
|
||||
TimelineClip[] clips = asset.clips;
|
||||
if (clips == null || clips.Length == 0)
|
||||
return;
|
||||
|
||||
// extrapolation not supported
|
||||
if (!clips[0].SupportsExtrapolation())
|
||||
return;
|
||||
|
||||
var orderedClips = SortClipsByStartTime(clips);
|
||||
if (orderedClips.Length > 0)
|
||||
{
|
||||
// post extrapolation is the minimum time to the next clip
|
||||
for (int i = 0; i < orderedClips.Length; i++)
|
||||
{
|
||||
double minTime = double.PositiveInfinity;
|
||||
for (int j = 0; j < orderedClips.Length; j++)
|
||||
{
|
||||
if (i == j)
|
||||
continue;
|
||||
|
||||
double deltaTime = orderedClips[j].start - orderedClips[i].end;
|
||||
if (deltaTime >= -TimeUtility.kTimeEpsilon && deltaTime < minTime)
|
||||
minTime = Math.Min(minTime, deltaTime);
|
||||
// check for overlapped clips
|
||||
if (orderedClips[j].start <= orderedClips[i].end && orderedClips[j].end > orderedClips[i].end)
|
||||
minTime = 0;
|
||||
}
|
||||
minTime = minTime <= kMinExtrapolationTime ? 0 : minTime;
|
||||
orderedClips[i].SetPostExtrapolationTime(minTime);
|
||||
}
|
||||
|
||||
// the first clip gets pre-extrapolation, then it's only respected if there is no post extrapolation
|
||||
orderedClips[0].SetPreExtrapolationTime(Math.Max(0, orderedClips[0].start));
|
||||
for (int i = 1; i < orderedClips.Length; i++)
|
||||
{
|
||||
double preTime = 0;
|
||||
int prevClip = -1;
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
// overlap, no pre-time
|
||||
if (orderedClips[j].end > orderedClips[i].start)
|
||||
{
|
||||
prevClip = -1;
|
||||
preTime = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
double gap = orderedClips[i].start - orderedClips[j].end;
|
||||
if (prevClip == -1 || gap < preTime)
|
||||
{
|
||||
preTime = gap;
|
||||
prevClip = j;
|
||||
}
|
||||
}
|
||||
// check for a post extrapolation time
|
||||
if (prevClip >= 0)
|
||||
{
|
||||
if (orderedClips[prevClip].postExtrapolationMode != TimelineClip.ClipExtrapolation.None)
|
||||
preTime = 0;
|
||||
}
|
||||
|
||||
preTime = preTime <= kMinExtrapolationTime ? 0 : preTime;
|
||||
orderedClips[i].SetPreExtrapolationTime(preTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TimelineClip[] SortClipsByStartTime(TimelineClip[] clips)
|
||||
{
|
||||
var orderedClips = new TimelineClip[clips.Length];
|
||||
Array.Copy(clips, orderedClips, clips.Length);
|
||||
Array.Sort(orderedClips, (clip1, clip2) => clip1.start.CompareTo(clip2.start));
|
||||
return orderedClips;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 32535dd294c621e4297fba34b15b1c52
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,51 @@
|
|||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class HashUtility
|
||||
{
|
||||
// Note. We could have used "params int[] hashes" but we want to avoid allocating.
|
||||
|
||||
public static int CombineHash(this int h1, int h2)
|
||||
{
|
||||
return h1 ^ (int)(h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); // Similar to c++ boost::hash_combine
|
||||
}
|
||||
|
||||
public static int CombineHash(int h1, int h2, int h3)
|
||||
{
|
||||
return CombineHash(h1, h2).CombineHash(h3);
|
||||
}
|
||||
|
||||
public static int CombineHash(int h1, int h2, int h3, int h4)
|
||||
{
|
||||
return CombineHash(h1, h2, h3).CombineHash(h4);
|
||||
}
|
||||
|
||||
public static int CombineHash(int h1, int h2, int h3, int h4, int h5)
|
||||
{
|
||||
return CombineHash(h1, h2, h3, h4).CombineHash(h5);
|
||||
}
|
||||
|
||||
public static int CombineHash(int h1, int h2, int h3, int h4, int h5, int h6)
|
||||
{
|
||||
return CombineHash(h1, h2, h3, h4, h5).CombineHash(h6);
|
||||
}
|
||||
|
||||
public static int CombineHash(int h1, int h2, int h3, int h4, int h5, int h6, int h7)
|
||||
{
|
||||
return CombineHash(h1, h2, h3, h4, h5, h6).CombineHash(h7);
|
||||
}
|
||||
|
||||
public static int CombineHash(int[] hashes)
|
||||
{
|
||||
if (hashes == null || hashes.Length == 0)
|
||||
return 0;
|
||||
|
||||
var h = hashes[0];
|
||||
for (int i = 1; i < hashes.Length; ++i)
|
||||
{
|
||||
h = CombineHash(h, hashes[i]);
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d0ca7b2e84542bf4ab9987087e8d79ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,102 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface used to inform the Timeline Editor about potential property modifications that may occur while previewing.
|
||||
/// </summary>
|
||||
public interface IPropertyCollector
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the active game object for subsequent property modifications.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject to push.</param>
|
||||
void PushActiveGameObject(GameObject gameObject);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the active GameObject from the modification stack, restoring the previous value.
|
||||
/// </summary>
|
||||
void PopActiveGameObject();
|
||||
|
||||
/// <summary>
|
||||
/// Add properties modified by an animation clip.
|
||||
/// </summary>
|
||||
/// <param name="clip">The animation clip that contains the properties</param>
|
||||
void AddFromClip(AnimationClip clip);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications specified by a list of animation clips.
|
||||
/// </summary>
|
||||
/// <param name="clips">The list of animation clips used to determine which property modifications to apply.</param>
|
||||
void AddFromClips(IEnumerable<AnimationClip> clips);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications using the serialized property name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the serialized property</param>
|
||||
/// <typeparam name="T">The type of the component the property exists on</typeparam>
|
||||
/// <remarks>
|
||||
/// This method uses the most recent gameObject from PushActiveGameObject
|
||||
/// </remarks>
|
||||
void AddFromName<T>(string name) where T : Component;
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications using the serialized property name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the serialized property</param>
|
||||
/// <remarks>
|
||||
/// This method uses the most recent gameObject from PushActiveGameObject
|
||||
/// </remarks>
|
||||
void AddFromName(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications modified by an animation clip.
|
||||
/// </summary>
|
||||
/// <param name="obj">The GameObject where the properties exist</param>
|
||||
/// <param name="clip">The animation clip that contains the properties</param>
|
||||
void AddFromClip(GameObject obj, AnimationClip clip);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications specified by a list of animation clips.
|
||||
/// </summary>
|
||||
/// <param name="obj">The gameObject that will be animated</param>
|
||||
/// <param name="clips">The list of animation clips used to determine which property modifications to apply.</param>
|
||||
void AddFromClips(GameObject obj, IEnumerable<AnimationClip> clips);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications using the serialized property name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the serialized property</param>
|
||||
/// <param name="obj">The gameObject where the properties exist</param>
|
||||
/// <typeparam name="T">The type of the component the property exists on</typeparam>>
|
||||
void AddFromName<T>(GameObject obj, string name) where T : Component;
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications using the serialized property name.
|
||||
/// </summary>
|
||||
/// <param name="obj">The gameObject where the properties exist</param>
|
||||
/// <param name="name">The name of the serialized property</param>
|
||||
void AddFromName(GameObject obj, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications using the serialized property name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the serialized property</param>
|
||||
/// <param name="component">The component where the properties exist</param>
|
||||
void AddFromName(Component component, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Set all serializable properties on a component to be under preview control.
|
||||
/// </summary>
|
||||
/// <param name="obj">The gameObject where the properties exist</param>
|
||||
/// <param name="component">The component to set in preview mode</param>
|
||||
void AddFromComponent(GameObject obj, Component component);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications modified by an animation clip.
|
||||
/// </summary>
|
||||
/// <param name="obj">The Object where the properties exist</param>
|
||||
/// <param name="clip">The animation clip that contains the properties</param>
|
||||
void AddObjectProperties(Object obj, AnimationClip clip);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 66b2b8fd1d9b4bc4c96b07335ad822f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,17 @@
|
|||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement this interface in a PlayableAsset to specify which properties will be modified when Timeline is in preview mode.
|
||||
/// </summary>
|
||||
public interface IPropertyPreview
|
||||
{
|
||||
/// <summary>
|
||||
/// Called by the Timeline Editor to gather properties requiring preview.
|
||||
/// </summary>
|
||||
/// <param name="director">The PlayableDirector invoking the preview</param>
|
||||
/// <param name="driver">PropertyCollector used to gather previewable properties</param>
|
||||
void GatherProperties(PlayableDirector director, IPropertyCollector driver);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b5f0881228e5827438f74e9b7b33c2dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class NotificationUtilities
|
||||
{
|
||||
public static ScriptPlayable<TimeNotificationBehaviour> CreateNotificationsPlayable(PlayableGraph graph, IEnumerable<IMarker> markers, GameObject go)
|
||||
{
|
||||
var notificationPlayable = ScriptPlayable<TimeNotificationBehaviour>.Null;
|
||||
var director = go.GetComponent<PlayableDirector>();
|
||||
foreach (var e in markers)
|
||||
{
|
||||
var notif = e as INotification;
|
||||
if (notif == null)
|
||||
continue;
|
||||
|
||||
if (notificationPlayable.Equals(ScriptPlayable<TimeNotificationBehaviour>.Null))
|
||||
{
|
||||
notificationPlayable = TimeNotificationBehaviour.Create(graph,
|
||||
director.playableAsset.duration, director.extrapolationMode);
|
||||
}
|
||||
|
||||
var time = (DiscreteTime)e.time;
|
||||
var tlDuration = (DiscreteTime)director.playableAsset.duration;
|
||||
if (time >= tlDuration && time <= tlDuration.OneTickAfter() && tlDuration != 0)
|
||||
{
|
||||
time = tlDuration.OneTickBefore();
|
||||
}
|
||||
|
||||
var notificationOptionProvider = e as INotificationOptionProvider;
|
||||
if (notificationOptionProvider != null)
|
||||
{
|
||||
notificationPlayable.GetBehaviour().AddNotification((double)time, notif, notificationOptionProvider.flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
notificationPlayable.GetBehaviour().AddNotification((double)time, notif);
|
||||
}
|
||||
}
|
||||
|
||||
return notificationPlayable;
|
||||
}
|
||||
|
||||
public static bool TrackTypeSupportsNotifications(Type type)
|
||||
{
|
||||
var binding = (TrackBindingTypeAttribute)Attribute.GetCustomAttribute(type, typeof(TrackBindingTypeAttribute));
|
||||
return binding != null &&
|
||||
(typeof(Component).IsAssignableFrom(binding.type) ||
|
||||
typeof(GameObject).IsAssignableFrom(binding.type));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b90311a8f07b00f4bbeb2fff3b128d25
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,251 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
// Sequence specific utilities for time manipulation
|
||||
static class TimeUtility
|
||||
{
|
||||
// chosen because it will cause no rounding errors between time/frames for frames values up to at least 10 million
|
||||
public static readonly double kTimeEpsilon = 1e-14;
|
||||
public static readonly double kFrameRateEpsilon = 1e-6;
|
||||
public static readonly double k_MaxTimelineDurationInSeconds = 9e6; //104 days of running time
|
||||
|
||||
static void ValidateFrameRate(double frameRate)
|
||||
{
|
||||
if (frameRate <= kTimeEpsilon)
|
||||
throw new ArgumentException("frame rate cannot be 0 or negative");
|
||||
}
|
||||
|
||||
public static int ToFrames(double time, double frameRate)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
time = Math.Min(Math.Max(time, -k_MaxTimelineDurationInSeconds), k_MaxTimelineDurationInSeconds);
|
||||
// this matches OnFrameBoundary
|
||||
double tolerance = GetEpsilon(time, frameRate);
|
||||
if (time < 0)
|
||||
{
|
||||
return (int)Math.Ceiling(time * frameRate - tolerance);
|
||||
}
|
||||
return (int)Math.Floor(time * frameRate + tolerance);
|
||||
}
|
||||
|
||||
public static double ToExactFrames(double time, double frameRate)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
return time * frameRate;
|
||||
}
|
||||
|
||||
public static double FromFrames(int frames, double frameRate)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
return (frames / frameRate);
|
||||
}
|
||||
|
||||
public static double FromFrames(double frames, double frameRate)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
return frames / frameRate;
|
||||
}
|
||||
|
||||
public static bool OnFrameBoundary(double time, double frameRate)
|
||||
{
|
||||
return OnFrameBoundary(time, frameRate, GetEpsilon(time, frameRate));
|
||||
}
|
||||
|
||||
public static double GetEpsilon(double time, double frameRate)
|
||||
{
|
||||
return Math.Max(Math.Abs(time), 1) * frameRate * kTimeEpsilon;
|
||||
}
|
||||
|
||||
public static bool OnFrameBoundary(double time, double frameRate, double epsilon)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
|
||||
double exact = ToExactFrames(time, frameRate);
|
||||
double rounded = Math.Round(exact);
|
||||
|
||||
return Math.Abs(exact - rounded) < epsilon;
|
||||
}
|
||||
|
||||
public static double RoundToFrame(double time, double frameRate)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
|
||||
var frameBefore = (int)Math.Floor(time * frameRate) / frameRate;
|
||||
var frameAfter = (int)Math.Ceiling(time * frameRate) / frameRate;
|
||||
|
||||
return Math.Abs(time - frameBefore) < Math.Abs(time - frameAfter) ? frameBefore : frameAfter;
|
||||
}
|
||||
|
||||
public static string TimeAsFrames(double timeValue, double frameRate, string format = "F2")
|
||||
{
|
||||
if (OnFrameBoundary(timeValue, frameRate)) // make integral values when on time borders
|
||||
return ToFrames(timeValue, frameRate).ToString();
|
||||
return ToExactFrames(timeValue, frameRate).ToString(format);
|
||||
}
|
||||
|
||||
public static string TimeAsTimeCode(double timeValue, double frameRate, string format = "F2")
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
|
||||
int intTime = (int)Math.Abs(timeValue);
|
||||
|
||||
int hours = intTime / 3600;
|
||||
int minutes = (intTime % 3600) / 60;
|
||||
int seconds = intTime % 60;
|
||||
|
||||
string result;
|
||||
string sign = timeValue < 0 ? "-" : string.Empty;
|
||||
if (hours > 0)
|
||||
result = hours + ":" + minutes.ToString("D2") + ":" + seconds.ToString("D2");
|
||||
else if (minutes > 0)
|
||||
result = minutes + ":" + seconds.ToString("D2");
|
||||
else
|
||||
result = seconds.ToString();
|
||||
|
||||
int frameDigits = (int)Math.Floor(Math.Log10(frameRate) + 1);
|
||||
|
||||
// Add partial digits on the frame if needed.
|
||||
// we are testing the original value (not the truncated), because the truncation can cause rounding errors leading
|
||||
// to invalid strings for items on frame boundaries
|
||||
string frames = (ToFrames(timeValue, frameRate) - ToFrames(intTime, frameRate)).ToString().PadLeft(frameDigits, '0');
|
||||
if (!OnFrameBoundary(timeValue, frameRate))
|
||||
{
|
||||
string decimals = ToExactFrames(timeValue, frameRate).ToString(format);
|
||||
int decPlace = decimals.IndexOf('.');
|
||||
if (decPlace >= 0)
|
||||
frames += " [" + decimals.Substring(decPlace) + "]";
|
||||
}
|
||||
|
||||
return sign + result + ":" + frames;
|
||||
}
|
||||
|
||||
// Given a time code string, return the time in seconds
|
||||
// 1.5 -> 1.5 seconds
|
||||
// 1:1.5 -> 1 minute, 1.5 seconds
|
||||
// 1:1[.5] -> 1 second, 1.5 frames
|
||||
// 2:3:4 -> 2 minutes, 3 seconds, 4 frames
|
||||
// 1[.6] -> 1.6 frames
|
||||
public static double ParseTimeCode(string timeCode, double frameRate, double defaultValue)
|
||||
{
|
||||
timeCode = RemoveChar(timeCode, c => char.IsWhiteSpace(c));
|
||||
string[] sections = timeCode.Split(':');
|
||||
if (sections.Length == 0 || sections.Length > 4)
|
||||
return defaultValue;
|
||||
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
double seconds = 0;
|
||||
double frames = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// depending on the format of the last numbers
|
||||
// seconds format
|
||||
string lastSection = sections[sections.Length - 1];
|
||||
if (Regex.Match(lastSection, @"^\d+\.\d+$").Success)
|
||||
{
|
||||
seconds = double.Parse(lastSection);
|
||||
if (sections.Length > 3) return defaultValue;
|
||||
if (sections.Length > 1) minutes = int.Parse(sections[sections.Length - 2]);
|
||||
if (sections.Length > 2) hours = int.Parse(sections[sections.Length - 3]);
|
||||
}
|
||||
// frame formats
|
||||
else
|
||||
{
|
||||
if (Regex.Match(lastSection, @"^\d+\[\.\d+\]$").Success)
|
||||
{
|
||||
string stripped = RemoveChar(lastSection, c => c == '[' || c == ']');
|
||||
frames = double.Parse(stripped);
|
||||
}
|
||||
else if (Regex.Match(lastSection, @"^\d*$").Success)
|
||||
{
|
||||
frames = int.Parse(lastSection);
|
||||
}
|
||||
else
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (sections.Length > 1) seconds = int.Parse(sections[sections.Length - 2]);
|
||||
if (sections.Length > 2) minutes = int.Parse(sections[sections.Length - 3]);
|
||||
if (sections.Length > 3) hours = int.Parse(sections[sections.Length - 4]);
|
||||
}
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return frames / frameRate + seconds + minutes * 60 + hours * 3600;
|
||||
}
|
||||
public static double ParseTimeSeconds(string timeCode, double frameRate, double defaultValue)
|
||||
{
|
||||
timeCode = RemoveChar(timeCode, c => char.IsWhiteSpace(c));
|
||||
string[] sections = timeCode.Split(':');
|
||||
if (sections.Length == 0 || sections.Length > 4)
|
||||
return defaultValue;
|
||||
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
double seconds = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// depending on the format of the last numbers
|
||||
// seconds format
|
||||
string lastSection = sections[sections.Length - 1];
|
||||
{
|
||||
if (!double.TryParse(lastSection, NumberStyles.Integer, CultureInfo.InvariantCulture, out seconds))
|
||||
if (Regex.Match(lastSection, @"^\d+\.\d+$").Success)
|
||||
seconds = double.Parse(lastSection);
|
||||
else
|
||||
return defaultValue;
|
||||
|
||||
if (!double.TryParse(lastSection, NumberStyles.Float, CultureInfo.InvariantCulture, out seconds))
|
||||
return defaultValue;
|
||||
|
||||
if (sections.Length > 3) return defaultValue;
|
||||
if (sections.Length > 1) minutes = int.Parse(sections[sections.Length - 2]);
|
||||
if (sections.Length > 2) hours = int.Parse(sections[sections.Length - 3]);
|
||||
}
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return seconds + minutes * 60 + hours * 3600;
|
||||
}
|
||||
|
||||
// fixes rounding errors from using single precision for length
|
||||
public static double GetAnimationClipLength(AnimationClip clip)
|
||||
{
|
||||
if (clip == null || clip.empty)
|
||||
return 0;
|
||||
|
||||
double length = clip.length;
|
||||
if (clip.frameRate > 0)
|
||||
{
|
||||
double frames = Mathf.Round(clip.length * clip.frameRate);
|
||||
length = frames / clip.frameRate;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static string RemoveChar(string str, Func<char, bool> charToRemoveFunc)
|
||||
{
|
||||
var len = str.Length;
|
||||
var src = str.ToCharArray();
|
||||
var dstIdx = 0;
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
if (!charToRemoveFunc(src[i]))
|
||||
src[dstIdx++] = src[i];
|
||||
}
|
||||
return new string(src, 0, dstIdx);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f779e779d62b5ca49b658236c337845d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for TimelineClip
|
||||
/// </summary>
|
||||
public static class TimelineClipExtensions
|
||||
{
|
||||
static readonly string k_UndoSetParentTrackText = "Move Clip";
|
||||
|
||||
/// <summary>
|
||||
/// Tries to move a TimelineClip to a different track. Validates that the destination track can accept the clip before performing the move.
|
||||
/// Fails if the clip's PlayableAsset is null, the current and destination tracks are the same or the destination track cannot accept the clip.
|
||||
/// </summary>
|
||||
/// <param name="clip">Clip that is being moved</param>
|
||||
/// <param name="destinationTrack">Desired destination track</param>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="clip"/> or <paramref name="destinationTrack"/> are null</exception>
|
||||
/// <exception cref="System.InvalidOperationException">Thrown if the PlayableAsset in the TimelineClip is null</exception>
|
||||
/// <exception cref="System.InvalidOperationException">Thrown if the current parent track and destination track are the same</exception>
|
||||
/// <exception cref="System.InvalidOperationException">Thrown if the destination track cannot hold tracks of the given type</exception>
|
||||
public static void MoveToTrack(this TimelineClip clip, TrackAsset destinationTrack)
|
||||
{
|
||||
if (clip == null)
|
||||
{
|
||||
throw new ArgumentNullException($"'this' argument for {nameof(MoveToTrack)} cannot be null.");
|
||||
}
|
||||
|
||||
if (destinationTrack == null)
|
||||
{
|
||||
throw new ArgumentNullException("Cannot move TimelineClip to a null track.");
|
||||
}
|
||||
|
||||
TrackAsset parentTrack = clip.GetParentTrack();
|
||||
Object asset = clip.asset;
|
||||
|
||||
// If the asset is null we cannot validate its type against the destination track
|
||||
if (asset == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot move a TimelineClip to a different track if the TimelineClip's PlayableAsset is null.");
|
||||
}
|
||||
|
||||
if (parentTrack == destinationTrack)
|
||||
{
|
||||
throw new InvalidOperationException($"TimelineClip is already on {destinationTrack.name}.");
|
||||
}
|
||||
|
||||
if (!destinationTrack.ValidateClipType(asset.GetType()))
|
||||
{
|
||||
throw new InvalidOperationException($"Track {destinationTrack.name} cannot contain clips of type {clip.GetType().Name}.");
|
||||
}
|
||||
|
||||
MoveToTrack_Impl(clip, destinationTrack, asset, parentTrack);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to move a TimelineClip to a different track. Validates that the destination track can accept the clip before performing the move.
|
||||
/// Fails if the clip's PlayableAsset is null, the current and destination tracks are the same or the destination track cannot accept the clip.
|
||||
/// </summary>
|
||||
/// <param name="clip">Clip that is being moved</param>
|
||||
/// <param name="destinationTrack">Desired destination track</param>
|
||||
/// <returns>Returns true if the clip was successfully moved to the destination track, false otherwise. Also returns false if either argument is null</returns>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="clip"/> or <paramref name="destinationTrack"/> are null</exception>
|
||||
public static bool TryMoveToTrack(this TimelineClip clip, TrackAsset destinationTrack)
|
||||
{
|
||||
if (clip == null)
|
||||
{
|
||||
throw new ArgumentNullException($"'this' argument for {nameof(TryMoveToTrack)} cannot be null.");
|
||||
}
|
||||
|
||||
if (destinationTrack == null)
|
||||
{
|
||||
throw new ArgumentNullException("Cannot move TimelineClip to a null parent.");
|
||||
}
|
||||
|
||||
TrackAsset parentTrack = clip.GetParentTrack();
|
||||
Object asset = clip.asset;
|
||||
|
||||
// If the asset is null we cannot validate its type against the destination track
|
||||
if (asset == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parentTrack != destinationTrack)
|
||||
{
|
||||
if (!destinationTrack.ValidateClipType(asset.GetType()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MoveToTrack_Impl(clip, destinationTrack, asset, parentTrack);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void MoveToTrack_Impl(TimelineClip clip, TrackAsset destinationTrack, Object asset, TrackAsset parentTrack)
|
||||
{
|
||||
TimelineUndo.PushUndo(asset, k_UndoSetParentTrackText);
|
||||
if (parentTrack != null)
|
||||
{
|
||||
TimelineUndo.PushUndo(parentTrack, k_UndoSetParentTrackText);
|
||||
}
|
||||
|
||||
TimelineUndo.PushUndo(destinationTrack, k_UndoSetParentTrackText);
|
||||
|
||||
clip.SetParentTrack_Internal(destinationTrack);
|
||||
|
||||
if (parentTrack == null)
|
||||
{
|
||||
TimelineCreateUtilities.SaveAssetIntoObject(asset, destinationTrack);
|
||||
}
|
||||
else if (parentTrack.timelineAsset != destinationTrack.timelineAsset)
|
||||
{
|
||||
TimelineCreateUtilities.RemoveAssetFromObject(asset, parentTrack);
|
||||
TimelineCreateUtilities.SaveAssetIntoObject(asset, destinationTrack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7a02f7efcd3e4f3baa0079e620bddbd7
|
||||
timeCreated: 1602852593
|
|
@ -0,0 +1,142 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class TimelineCreateUtilities
|
||||
{
|
||||
// based off of ObjectNames.GetUniqueName, but can exist in runtime
|
||||
public static string GenerateUniqueActorName(List<ScriptableObject> tracks, string name)
|
||||
{
|
||||
if (!tracks.Exists(x => ((object)x) != null && x.name == name))
|
||||
return name;
|
||||
|
||||
int numberInParentheses = 0;
|
||||
string baseName = name;
|
||||
|
||||
if (!string.IsNullOrEmpty(name) && name[name.Length - 1] == ')')
|
||||
{
|
||||
int index = name.LastIndexOf('(');
|
||||
if (index > 0)
|
||||
{
|
||||
string numberString = name.Substring(index + 1, name.Length - index - 2);
|
||||
if (int.TryParse(numberString, out numberInParentheses))
|
||||
{
|
||||
numberInParentheses++;
|
||||
baseName = name.Substring(0, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baseName = baseName.TrimEnd();
|
||||
|
||||
for (int i = numberInParentheses; i < numberInParentheses + 5000; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
string result = string.Format("{0} ({1})", baseName, i);
|
||||
if (!tracks.Exists(x => ((object)x) != null && x.name == result))
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return name;
|
||||
}
|
||||
|
||||
public static void SaveAssetIntoObject(Object childAsset, Object masterAsset)
|
||||
{
|
||||
if (childAsset == null || masterAsset == null)
|
||||
return;
|
||||
|
||||
if ((masterAsset.hideFlags & HideFlags.DontSave) != 0)
|
||||
{
|
||||
childAsset.hideFlags |= HideFlags.DontSave;
|
||||
}
|
||||
else
|
||||
{
|
||||
childAsset.hideFlags |= HideFlags.HideInHierarchy;
|
||||
#if UNITY_EDITOR
|
||||
if (!AssetDatabase.Contains(childAsset) && AssetDatabase.Contains(masterAsset))
|
||||
AssetDatabase.AddObjectToAsset(childAsset, masterAsset);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveAssetFromObject(Object childAsset, Object masterAsset)
|
||||
{
|
||||
if (childAsset == null || masterAsset == null)
|
||||
return;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (AssetDatabase.Contains(childAsset) && AssetDatabase.Contains(masterAsset))
|
||||
AssetDatabase.RemoveObjectFromAsset(childAsset);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static AnimationClip CreateAnimationClipForTrack(string name, TrackAsset track, bool isLegacy)
|
||||
{
|
||||
var timelineAsset = track != null ? track.timelineAsset : null;
|
||||
var trackFlags = track != null ? track.hideFlags : HideFlags.None;
|
||||
|
||||
var curves = new AnimationClip
|
||||
{
|
||||
legacy = isLegacy,
|
||||
|
||||
name = name,
|
||||
|
||||
frameRate = timelineAsset == null
|
||||
? TimelineAsset.EditorSettings.kDefaultFps
|
||||
: timelineAsset.editorSettings.fps
|
||||
};
|
||||
|
||||
SaveAssetIntoObject(curves, timelineAsset);
|
||||
curves.hideFlags = trackFlags & ~HideFlags.HideInHierarchy; // Never hide in hierarchy
|
||||
|
||||
TimelineUndo.RegisterCreatedObjectUndo(curves, "Create Curves");
|
||||
|
||||
return curves;
|
||||
}
|
||||
|
||||
public static bool ValidateParentTrack(TrackAsset parent, Type childType)
|
||||
{
|
||||
if (childType == null || !typeof(TrackAsset).IsAssignableFrom(childType))
|
||||
return false;
|
||||
|
||||
// no parent is valid for any type
|
||||
if (parent == null)
|
||||
return true;
|
||||
|
||||
// A track supports layers if it implements ILayerable. Only supported for parents that are
|
||||
// the same exact type as the child class, and 1 level of nesting only
|
||||
if (parent is ILayerable && !parent.isSubTrack && parent.GetType() == childType)
|
||||
return true;
|
||||
|
||||
var attr = Attribute.GetCustomAttribute(parent.GetType(), typeof(SupportsChildTracksAttribute)) as SupportsChildTracksAttribute;
|
||||
if (attr == null)
|
||||
return false;
|
||||
|
||||
// group track case, accepts all
|
||||
if (attr.childType == null)
|
||||
return true;
|
||||
|
||||
// specific case. Specifies nesting level
|
||||
if (childType == attr.childType)
|
||||
{
|
||||
int nestCount = 0;
|
||||
var p = parent;
|
||||
while (p != null && p.isSubTrack)
|
||||
{
|
||||
nestCount++;
|
||||
p = p.parent as TrackAsset;
|
||||
}
|
||||
|
||||
return nestCount < attr.levels;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 40cb137d0e9816e48a4141ed13afedad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class TimelineUndo
|
||||
{
|
||||
public static void PushDestroyUndo(TimelineAsset timeline, Object thingToDirty, Object objectToDestroy)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (objectToDestroy == null || !DisableUndoGuard.enableUndo)
|
||||
return;
|
||||
|
||||
if (thingToDirty != null)
|
||||
EditorUtility.SetDirty(thingToDirty);
|
||||
|
||||
if (timeline != null)
|
||||
EditorUtility.SetDirty(timeline);
|
||||
|
||||
Undo.DestroyObjectImmediate(objectToDestroy);
|
||||
#else
|
||||
if (objectToDestroy != null)
|
||||
Object.Destroy(objectToDestroy);
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void PushUndo(Object[] thingsToDirty, string operation)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (thingsToDirty == null || !DisableUndoGuard.enableUndo)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < thingsToDirty.Length; i++)
|
||||
{
|
||||
if (thingsToDirty[i] is TrackAsset track)
|
||||
track.MarkDirty();
|
||||
EditorUtility.SetDirty(thingsToDirty[i]);
|
||||
}
|
||||
Undo.RegisterCompleteObjectUndo(thingsToDirty, UndoName(operation));
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void PushUndo(Object thingToDirty, string operation)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (thingToDirty != null && DisableUndoGuard.enableUndo)
|
||||
{
|
||||
var track = thingToDirty as TrackAsset;
|
||||
if (track != null)
|
||||
track.MarkDirty();
|
||||
|
||||
EditorUtility.SetDirty(thingToDirty);
|
||||
Undo.RegisterCompleteObjectUndo(thingToDirty, UndoName(operation));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void RegisterCreatedObjectUndo(Object thingCreated, string operation)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (DisableUndoGuard.enableUndo)
|
||||
{
|
||||
Undo.RegisterCreatedObjectUndo(thingCreated, UndoName(operation));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string UndoName(string name) => "Timeline " + name;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal struct DisableUndoGuard : IDisposable
|
||||
{
|
||||
internal static bool enableUndo = true;
|
||||
static readonly Stack<bool> m_UndoStateStack = new Stack<bool>();
|
||||
bool m_Disposed;
|
||||
public DisableUndoGuard(bool disable)
|
||||
{
|
||||
m_Disposed = false;
|
||||
m_UndoStateStack.Push(enableUndo);
|
||||
enableUndo = !disable;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!m_Disposed)
|
||||
{
|
||||
if (m_UndoStateStack.Count == 0)
|
||||
{
|
||||
Debug.LogError("UnMatched DisableUndoGuard calls");
|
||||
enableUndo = true;
|
||||
return;
|
||||
}
|
||||
enableUndo = m_UndoStateStack.Pop();
|
||||
m_Disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1f2a7e0d1b6bbba408a41e206945c23c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,30 @@
|
|||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class WeightUtility
|
||||
{
|
||||
// Given a mixer, normalizes the mixer if required
|
||||
// returns the output weight that should be applied to the mixer as input
|
||||
public static float NormalizeMixer(Playable mixer)
|
||||
{
|
||||
if (!mixer.IsValid())
|
||||
return 0;
|
||||
int count = mixer.GetInputCount();
|
||||
float weight = 0.0f;
|
||||
for (int c = 0; c < count; c++)
|
||||
{
|
||||
weight += mixer.GetInputWeight(c);
|
||||
}
|
||||
|
||||
if (weight > Mathf.Epsilon && weight < 1)
|
||||
{
|
||||
for (int c = 0; c < count; c++)
|
||||
{
|
||||
mixer.SetInputWeight(c, mixer.GetInputWeight(c) / weight);
|
||||
}
|
||||
}
|
||||
return Mathf.Clamp01(weight);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e7a505b341283e14696e86433a5b1ae9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue