Initial Commit
This commit is contained in:
parent
53eb92e9af
commit
270ab7d11f
15341 changed files with 700234 additions and 0 deletions
|
@ -0,0 +1,101 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor.Timeline.Actions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[ApplyDefaultUndo("Match Offsets")]
|
||||
[MenuEntry("Match Offsets To Previous Clip", MenuPriority.CustomClipActionSection.matchPrevious), UsedImplicitly]
|
||||
class MatchOffsetsPreviousAction : ClipAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (clips == null || !clips.Any())
|
||||
return false;
|
||||
AnimationOffsetMenu.MatchClipsToPrevious(clips.Where(x => IsValidClip(x, TimelineEditor.inspectedDirector)).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsValidClip(TimelineClip clip, PlayableDirector director)
|
||||
{
|
||||
return clip != null &&
|
||||
clip.GetParentTrack() != null &&
|
||||
(clip.asset as AnimationPlayableAsset) != null &&
|
||||
clip.GetParentTrack().clips.Any(x => x.start < clip.start) &&
|
||||
TimelineUtility.GetSceneGameObject(director, clip.GetParentTrack()) != null;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (!clips.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
var director = TimelineEditor.inspectedDirector;
|
||||
if (TimelineEditor.inspectedDirector == null)
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (clips.Any(c => IsValidClip(c, director)))
|
||||
return ActionValidity.Valid;
|
||||
|
||||
return ActionValidity.NotApplicable;
|
||||
}
|
||||
}
|
||||
|
||||
[ApplyDefaultUndo("Match Offsets")]
|
||||
[MenuEntry("Match Offsets To Next Clip", MenuPriority.CustomClipActionSection.matchNext), UsedImplicitly]
|
||||
class MatchOffsetsNextAction : ClipAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
AnimationOffsetMenu.MatchClipsToNext(clips.Where(x => IsValidClip(x, TimelineEditor.inspectedDirector)).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsValidClip(TimelineClip clip, PlayableDirector director)
|
||||
{
|
||||
return clip != null &&
|
||||
clip.GetParentTrack() != null &&
|
||||
(clip.asset as AnimationPlayableAsset) != null &&
|
||||
clip.GetParentTrack().clips.Any(x => x.start > clip.start) &&
|
||||
TimelineUtility.GetSceneGameObject(director, clip.GetParentTrack()) != null;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (!clips.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
var director = TimelineEditor.inspectedDirector;
|
||||
if (TimelineEditor.inspectedDirector == null)
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (clips.Any(c => IsValidClip(c, director)))
|
||||
return ActionValidity.Valid;
|
||||
|
||||
return ActionValidity.NotApplicable;
|
||||
}
|
||||
}
|
||||
|
||||
[ApplyDefaultUndo]
|
||||
[MenuEntry("Reset Offsets", MenuPriority.CustomClipActionSection.resetOffset), UsedImplicitly]
|
||||
class ResetOffsets : ClipAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
AnimationOffsetMenu.ResetClipOffsets(clips.Where(TimelineAnimationUtilities.IsAnimationClip).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (!clips.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bf22284ca28e7ef4490033b61e9b52cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,427 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
struct CurveBindingPair
|
||||
{
|
||||
public EditorCurveBinding binding;
|
||||
public AnimationCurve curve;
|
||||
public ObjectReferenceKeyframe[] objectCurve;
|
||||
}
|
||||
|
||||
class CurveBindingGroup
|
||||
{
|
||||
public CurveBindingPair[] curveBindingPairs { get; set; }
|
||||
public Vector2 timeRange { get; set; }
|
||||
public Vector2 valueRange { get; set; }
|
||||
|
||||
public bool isFloatCurve
|
||||
{
|
||||
get
|
||||
{
|
||||
return curveBindingPairs != null && curveBindingPairs.Length > 0 &&
|
||||
curveBindingPairs[0].curve != null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool isObjectCurve
|
||||
{
|
||||
get
|
||||
{
|
||||
return curveBindingPairs != null && curveBindingPairs.Length > 0 &&
|
||||
curveBindingPairs[0].objectCurve != null;
|
||||
}
|
||||
}
|
||||
|
||||
public int count
|
||||
{
|
||||
get
|
||||
{
|
||||
if (curveBindingPairs == null)
|
||||
return 0;
|
||||
return curveBindingPairs.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AnimationClipCurveInfo
|
||||
{
|
||||
bool m_CurveDirty = true;
|
||||
bool m_KeysDirty = true;
|
||||
|
||||
public bool dirty
|
||||
{
|
||||
get { return m_CurveDirty; }
|
||||
set
|
||||
{
|
||||
m_CurveDirty = value;
|
||||
if (m_CurveDirty)
|
||||
{
|
||||
m_KeysDirty = true;
|
||||
if (m_groupings != null)
|
||||
m_groupings.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AnimationCurve[] curves;
|
||||
public EditorCurveBinding[] bindings;
|
||||
|
||||
public EditorCurveBinding[] objectBindings;
|
||||
public List<ObjectReferenceKeyframe[]> objectCurves;
|
||||
|
||||
Dictionary<string, CurveBindingGroup> m_groupings;
|
||||
|
||||
// to tell whether the cache has changed
|
||||
public int version { get; private set; }
|
||||
|
||||
float[] m_KeyTimes;
|
||||
|
||||
Dictionary<EditorCurveBinding, float[]> m_individualBindinsKey;
|
||||
|
||||
public float[] keyTimes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_KeysDirty || m_KeyTimes == null)
|
||||
{
|
||||
RebuildKeyCache();
|
||||
}
|
||||
return m_KeyTimes;
|
||||
}
|
||||
}
|
||||
|
||||
public float[] GetCurveTimes(EditorCurveBinding curve)
|
||||
{
|
||||
return GetCurveTimes(new[] { curve });
|
||||
}
|
||||
|
||||
public float[] GetCurveTimes(EditorCurveBinding[] curves)
|
||||
{
|
||||
if (m_KeysDirty || m_KeyTimes == null)
|
||||
{
|
||||
RebuildKeyCache();
|
||||
}
|
||||
|
||||
var keyTimes = new List<float>();
|
||||
for (int i = 0; i < curves.Length; i++)
|
||||
{
|
||||
var c = curves[i];
|
||||
if (m_individualBindinsKey.ContainsKey(c))
|
||||
{
|
||||
keyTimes.AddRange(m_individualBindinsKey[c]);
|
||||
}
|
||||
}
|
||||
return keyTimes.ToArray();
|
||||
}
|
||||
|
||||
void RebuildKeyCache()
|
||||
{
|
||||
m_individualBindinsKey = new Dictionary<EditorCurveBinding, float[]>();
|
||||
|
||||
List<float> keys = curves.SelectMany(y => y.keys).Select(z => z.time).ToList();
|
||||
for (int i = 0; i < objectCurves.Count; i++)
|
||||
{
|
||||
var kf = objectCurves[i];
|
||||
keys.AddRange(kf.Select(x => x.time));
|
||||
}
|
||||
|
||||
for (int b = 0; b < bindings.Count(); b++)
|
||||
{
|
||||
m_individualBindinsKey.Add(bindings[b], curves[b].keys.Select(k => k.time).Distinct().ToArray());
|
||||
}
|
||||
|
||||
m_KeyTimes = keys.OrderBy(x => x).Distinct().ToArray();
|
||||
m_KeysDirty = false;
|
||||
}
|
||||
|
||||
public void Update(AnimationClip clip)
|
||||
{
|
||||
List<EditorCurveBinding> postfilter = new List<EditorCurveBinding>();
|
||||
var clipBindings = AnimationUtility.GetCurveBindings(clip);
|
||||
for (int i = 0; i < clipBindings.Length; i++)
|
||||
{
|
||||
var bind = clipBindings[i];
|
||||
if (!bind.propertyName.Contains("LocalRotation.w"))
|
||||
postfilter.Add(RotationCurveInterpolation.RemapAnimationBindingForRotationCurves(bind, clip));
|
||||
}
|
||||
bindings = postfilter.ToArray();
|
||||
|
||||
curves = new AnimationCurve[bindings.Length];
|
||||
for (int i = 0; i < bindings.Length; i++)
|
||||
{
|
||||
curves[i] = AnimationUtility.GetEditorCurve(clip, bindings[i]);
|
||||
}
|
||||
|
||||
objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
|
||||
objectCurves = new List<ObjectReferenceKeyframe[]>(objectBindings.Length);
|
||||
for (int i = 0; i < objectBindings.Length; i++)
|
||||
{
|
||||
objectCurves.Add(AnimationUtility.GetObjectReferenceCurve(clip, objectBindings[i]));
|
||||
}
|
||||
|
||||
m_CurveDirty = false;
|
||||
m_KeysDirty = true;
|
||||
|
||||
version = version + 1;
|
||||
}
|
||||
|
||||
public bool GetBindingForCurve(AnimationCurve curve, ref EditorCurveBinding binding)
|
||||
{
|
||||
for (int i = 0; i < curves.Length; i++)
|
||||
{
|
||||
if (curve == curves[i])
|
||||
{
|
||||
binding = bindings[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public AnimationCurve GetCurveForBinding(EditorCurveBinding binding)
|
||||
{
|
||||
for (int i = 0; i < curves.Length; i++)
|
||||
{
|
||||
if (binding.Equals(bindings[i]))
|
||||
{
|
||||
return curves[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ObjectReferenceKeyframe[] GetObjectCurveForBinding(EditorCurveBinding binding)
|
||||
{
|
||||
if (objectCurves == null)
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < objectCurves.Count; i++)
|
||||
{
|
||||
if (binding.Equals(objectBindings[i]))
|
||||
{
|
||||
return objectCurves[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// given a groupID, get the list of curve bindings
|
||||
public CurveBindingGroup GetGroupBinding(string groupID)
|
||||
{
|
||||
if (m_groupings == null)
|
||||
m_groupings = new Dictionary<string, CurveBindingGroup>();
|
||||
|
||||
CurveBindingGroup result = null;
|
||||
if (!m_groupings.TryGetValue(groupID, out result))
|
||||
{
|
||||
result = new CurveBindingGroup();
|
||||
result.timeRange = new Vector2(float.MaxValue, float.MinValue);
|
||||
result.valueRange = new Vector2(float.MaxValue, float.MinValue);
|
||||
List<CurveBindingPair> found = new List<CurveBindingPair>();
|
||||
for (int i = 0; i < bindings.Length; i++)
|
||||
{
|
||||
if (bindings[i].GetGroupID() == groupID)
|
||||
{
|
||||
CurveBindingPair pair = new CurveBindingPair();
|
||||
pair.binding = bindings[i];
|
||||
pair.curve = curves[i];
|
||||
found.Add(pair);
|
||||
|
||||
for (int k = 0; k < curves[i].keys.Length; k++)
|
||||
{
|
||||
var key = curves[i].keys[k];
|
||||
result.timeRange = new Vector2(Mathf.Min(key.time, result.timeRange.x), Mathf.Max(key.time, result.timeRange.y));
|
||||
result.valueRange = new Vector2(Mathf.Min(key.value, result.valueRange.x), Mathf.Max(key.value, result.valueRange.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < objectBindings.Length; i++)
|
||||
{
|
||||
if (objectBindings[i].GetGroupID() == groupID)
|
||||
{
|
||||
CurveBindingPair pair = new CurveBindingPair();
|
||||
pair.binding = objectBindings[i];
|
||||
pair.objectCurve = objectCurves[i];
|
||||
found.Add(pair);
|
||||
|
||||
for (int k = 0; k < objectCurves[i].Length; k++)
|
||||
{
|
||||
var key = objectCurves[i][k];
|
||||
result.timeRange = new Vector2(Mathf.Min(key.time, result.timeRange.x), Mathf.Max(key.time, result.timeRange.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.curveBindingPairs = found.OrderBy(x => AnimationWindowUtility.GetComponentIndex(x.binding.propertyName)).ToArray();
|
||||
|
||||
m_groupings.Add(groupID, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache for storing the animation clip data
|
||||
class AnimationClipCurveCache
|
||||
{
|
||||
static AnimationClipCurveCache s_Instance;
|
||||
Dictionary<AnimationClip, AnimationClipCurveInfo> m_ClipCache = new Dictionary<AnimationClip, AnimationClipCurveInfo>();
|
||||
bool m_IsEnabled;
|
||||
|
||||
|
||||
public static AnimationClipCurveCache Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_Instance == null)
|
||||
{
|
||||
s_Instance = new AnimationClipCurveCache();
|
||||
}
|
||||
|
||||
return s_Instance;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
if (!m_IsEnabled)
|
||||
{
|
||||
AnimationUtility.onCurveWasModified += OnCurveWasModified;
|
||||
m_IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
if (m_IsEnabled)
|
||||
{
|
||||
AnimationUtility.onCurveWasModified -= OnCurveWasModified;
|
||||
m_IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// callback when a curve is edited. Force the cache to update next time it's accessed
|
||||
void OnCurveWasModified(AnimationClip clip, EditorCurveBinding binding, AnimationUtility.CurveModifiedType modification)
|
||||
{
|
||||
AnimationClipCurveInfo data;
|
||||
if (m_ClipCache.TryGetValue(clip, out data))
|
||||
{
|
||||
data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public AnimationClipCurveInfo GetCurveInfo(AnimationClip clip)
|
||||
{
|
||||
AnimationClipCurveInfo data;
|
||||
if (clip == null)
|
||||
return null;
|
||||
if (!m_ClipCache.TryGetValue(clip, out data))
|
||||
{
|
||||
data = new AnimationClipCurveInfo();
|
||||
data.dirty = true;
|
||||
m_ClipCache[clip] = data;
|
||||
}
|
||||
if (data.dirty)
|
||||
{
|
||||
data.Update(clip);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public void ClearCachedProxyClips()
|
||||
{
|
||||
var toRemove = new List<AnimationClip>();
|
||||
foreach (var entry in m_ClipCache)
|
||||
{
|
||||
var clip = entry.Key;
|
||||
if (clip != null && (clip.hideFlags & HideFlags.HideAndDontSave) == HideFlags.HideAndDontSave)
|
||||
toRemove.Add(clip);
|
||||
}
|
||||
|
||||
foreach (var clip in toRemove)
|
||||
{
|
||||
m_ClipCache.Remove(clip);
|
||||
Object.DestroyImmediate(clip, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ClearCachedProxyClips();
|
||||
m_ClipCache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
static class EditorCurveBindingExtension
|
||||
{
|
||||
// identifier to generate an id thats the same for all curves in the same group
|
||||
public static string GetGroupID(this EditorCurveBinding binding)
|
||||
{
|
||||
return binding.type + AnimationWindowUtility.GetPropertyGroupName(binding.propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class CurveBindingGroupExtensions
|
||||
{
|
||||
// Extentions to determine curve types
|
||||
public static bool IsEnableGroup(this CurveBindingGroup curves)
|
||||
{
|
||||
return curves.isFloatCurve && curves.count == 1 && curves.curveBindingPairs[0].binding.propertyName == "m_Enabled";
|
||||
}
|
||||
|
||||
public static bool IsVectorGroup(this CurveBindingGroup curves)
|
||||
{
|
||||
if (!curves.isFloatCurve)
|
||||
return false;
|
||||
if (curves.count <= 1 || curves.count > 4)
|
||||
return false;
|
||||
char l = curves.curveBindingPairs[0].binding.propertyName.Last();
|
||||
return l == 'x' || l == 'y' || l == 'z' || l == 'w';
|
||||
}
|
||||
|
||||
public static bool IsColorGroup(this CurveBindingGroup curves)
|
||||
{
|
||||
if (!curves.isFloatCurve)
|
||||
return false;
|
||||
if (curves.count != 3 && curves.count != 4)
|
||||
return false;
|
||||
char l = curves.curveBindingPairs[0].binding.propertyName.Last();
|
||||
return l == 'r' || l == 'g' || l == 'b' || l == 'a';
|
||||
}
|
||||
|
||||
public static string GetDescription(this CurveBindingGroup group, float t)
|
||||
{
|
||||
string result = string.Empty;
|
||||
if (group.isFloatCurve)
|
||||
{
|
||||
if (group.count > 1)
|
||||
{
|
||||
result += "(" + group.curveBindingPairs[0].curve.Evaluate(t).ToString("0.##");
|
||||
for (int j = 1; j < group.curveBindingPairs.Length; j++)
|
||||
{
|
||||
result += "," + group.curveBindingPairs[j].curve.Evaluate(t).ToString("0.##");
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = group.curveBindingPairs[0].curve.Evaluate(t).ToString("0.##");
|
||||
}
|
||||
}
|
||||
else if (group.isObjectCurve)
|
||||
{
|
||||
Object obj = null;
|
||||
if (group.curveBindingPairs[0].objectCurve.Length > 0)
|
||||
obj = CurveEditUtility.Evaluate(group.curveBindingPairs[0].objectCurve, t);
|
||||
result = (obj == null ? "None" : obj.name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 07a967d2fca95324f8922df8394a5655
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class AnimationClipExtensions
|
||||
{
|
||||
public static UInt64 ClipVersion(this AnimationClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
return 0;
|
||||
|
||||
var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
|
||||
var version = (UInt32)info.version;
|
||||
var count = (UInt32)info.curves.Length;
|
||||
var result = (UInt64)version;
|
||||
result |= ((UInt64)count) << 32;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static CurveChangeType GetChangeType(this AnimationClip clip, ref UInt64 curveVersion)
|
||||
{
|
||||
var version = clip.ClipVersion();
|
||||
var changeType = CurveChangeType.None;
|
||||
if ((curveVersion >> 32) != (version >> 32))
|
||||
changeType = CurveChangeType.CurveAddedOrRemoved;
|
||||
else if (curveVersion != version)
|
||||
changeType = CurveChangeType.CurveModified;
|
||||
|
||||
curveVersion = version;
|
||||
return changeType;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c3b87750f9f94e8c918b71ec7d369bd4
|
||||
timeCreated: 1602859563
|
|
@ -0,0 +1,73 @@
|
|||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class AnimationOffsetMenu
|
||||
{
|
||||
public static string MatchFieldsPrefix = L10n.Tr("Match Offsets Fields/");
|
||||
|
||||
static bool EnforcePreviewMode()
|
||||
{
|
||||
TimelineEditor.state.previewMode = true; // try and set the preview mode
|
||||
if (!TimelineEditor.state.previewMode)
|
||||
{
|
||||
Debug.LogError("Match clips cannot be completed because preview mode cannot be enabed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void MatchClipsToPrevious(TimelineClip[] clips)
|
||||
{
|
||||
if (!EnforcePreviewMode())
|
||||
return;
|
||||
|
||||
clips = clips.OrderBy(x => x.start).ToArray();
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var sceneObject = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, clip.GetParentTrack());
|
||||
if (sceneObject != null)
|
||||
{
|
||||
TimelineAnimationUtilities.MatchPrevious(clip, sceneObject.transform, TimelineEditor.inspectedDirector);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
internal static void MatchClipsToNext(TimelineClip[] clips)
|
||||
{
|
||||
if (!EnforcePreviewMode())
|
||||
return;
|
||||
|
||||
clips = clips.OrderByDescending(x => x.start).ToArray();
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var sceneObject = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, clip.GetParentTrack());
|
||||
if (sceneObject != null)
|
||||
{
|
||||
TimelineAnimationUtilities.MatchNext(clip, sceneObject.transform, TimelineEditor.inspectedDirector);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
public static void ResetClipOffsets(TimelineClip[] clips)
|
||||
{
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var asset = clip.asset as AnimationPlayableAsset;
|
||||
if (asset != null)
|
||||
asset.ResetOffsets();
|
||||
}
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9ace5095cc37ed849b52109d2ee305d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,65 @@
|
|||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomTimelineEditor(typeof(AnimationPlayableAsset)), UsedImplicitly]
|
||||
class AnimationPlayableAssetEditor : ClipEditor
|
||||
{
|
||||
public static readonly string k_NoClipAssignedError = L10n.Tr("No animation clip assigned");
|
||||
public static readonly string k_LegacyClipError = L10n.Tr("Legacy animation clips are not supported");
|
||||
static readonly string k_MotionCurveError = L10n.Tr("You are using motion curves without applyRootMotion enabled on the Animator. The root transform will not be animated");
|
||||
static readonly string k_RootCurveError = L10n.Tr("You are using root curves without applyRootMotion enabled on the Animator. The root transform will not be animated");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ClipDrawOptions GetClipOptions(TimelineClip clip)
|
||||
{
|
||||
var clipOptions = base.GetClipOptions(clip);
|
||||
var asset = clip.asset as AnimationPlayableAsset;
|
||||
|
||||
if (asset != null)
|
||||
clipOptions.errorText = GetErrorText(asset, clip.GetParentTrack() as AnimationTrack, clipOptions.errorText);
|
||||
|
||||
if (clip.recordable)
|
||||
clipOptions.highlightColor = DirectorStyles.Instance.customSkin.colorAnimationRecorded;
|
||||
|
||||
return clipOptions;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnCreate(TimelineClip clip, TrackAsset track, TimelineClip clonedFrom)
|
||||
{
|
||||
var asset = clip.asset as AnimationPlayableAsset;
|
||||
if (asset != null && asset.clip != null && asset.clip.legacy)
|
||||
{
|
||||
asset.clip = null;
|
||||
Debug.LogError("Legacy Animation Clips are not supported");
|
||||
}
|
||||
}
|
||||
|
||||
string GetErrorText(AnimationPlayableAsset animationAsset, AnimationTrack track, string defaultError)
|
||||
{
|
||||
if (animationAsset.clip == null)
|
||||
return k_NoClipAssignedError;
|
||||
if (animationAsset.clip.legacy)
|
||||
return k_LegacyClipError;
|
||||
if (animationAsset.clip.hasMotionCurves || animationAsset.clip.hasRootCurves)
|
||||
{
|
||||
if (track != null && track.trackOffset == TrackOffset.Auto)
|
||||
{
|
||||
var animator = track.GetBinding(TimelineEditor.inspectedDirector);
|
||||
if (animator != null && !animator.applyRootMotion && !animationAsset.clip.hasGenericRootTransform)
|
||||
{
|
||||
if (animationAsset.clip.hasMotionCurves)
|
||||
return k_MotionCurveError;
|
||||
return k_RootCurveError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultError;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f7fed0d9d0f7a7f41a8525aa79e790b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,151 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor.Timeline.Actions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[MenuEntry("Add Override Track", MenuPriority.CustomTrackActionSection.addOverrideTrack), UsedImplicitly]
|
||||
class AddOverrideTrackAction : TrackAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
{
|
||||
TimelineHelpers.CreateTrack(typeof(AnimationTrack), animTrack, "Override " + animTrack.GetChildTracks().Count());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (tracks.Any(t => t.isSubTrack || !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return ActionValidity.Invalid;
|
||||
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Convert To Clip Track", MenuPriority.CustomTrackActionSection.convertToClipMode), UsedImplicitly]
|
||||
class ConvertToClipModeAction : TrackAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
animTrack.ConvertToClipMode();
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return ActionValidity.Invalid;
|
||||
|
||||
if (tracks.OfType<AnimationTrack>().All(a => a.CanConvertToClipMode()))
|
||||
return ActionValidity.Valid;
|
||||
|
||||
return ActionValidity.NotApplicable;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Convert To Infinite Clip", MenuPriority.CustomTrackActionSection.convertFromClipMode), UsedImplicitly]
|
||||
class ConvertFromClipTrackAction : TrackAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
animTrack.ConvertFromClipMode(TimelineEditor.inspectedAsset);
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return ActionValidity.Invalid;
|
||||
|
||||
if (tracks.OfType<AnimationTrack>().All(a => a.CanConvertFromClipMode()))
|
||||
return ActionValidity.Valid;
|
||||
|
||||
return ActionValidity.NotApplicable;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TrackOffsetBaseAction : TrackAction
|
||||
{
|
||||
public abstract TrackOffset trackOffset { get; }
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
{
|
||||
return ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
{
|
||||
animTrack.UnarmForRecord();
|
||||
animTrack.trackOffset = trackOffset;
|
||||
}
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[MenuEntry("Track Offsets/Apply Transform Offsets", MenuPriority.CustomTrackActionSection.applyTrackOffset), UsedImplicitly]
|
||||
[ApplyDefaultUndo]
|
||||
class ApplyTransformOffsetAction : TrackOffsetBaseAction
|
||||
{
|
||||
public override TrackOffset trackOffset
|
||||
{
|
||||
get { return TrackOffset.ApplyTransformOffsets; }
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Track Offsets/Apply Scene Offsets", MenuPriority.CustomTrackActionSection.applySceneOffset), UsedImplicitly]
|
||||
[ApplyDefaultUndo]
|
||||
class ApplySceneOffsetAction : TrackOffsetBaseAction
|
||||
{
|
||||
public override TrackOffset trackOffset
|
||||
{
|
||||
get { return TrackOffset.ApplySceneOffsets; }
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Track Offsets/Auto (Deprecated)", MenuPriority.CustomTrackActionSection.applyAutoOffset), UsedImplicitly]
|
||||
[ApplyDefaultUndo]
|
||||
class ApplyAutoAction : TrackOffsetBaseAction
|
||||
{
|
||||
public override TrackOffset trackOffset
|
||||
{
|
||||
get { return TrackOffset.Auto; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d4553f2006f48b6448553cb525d2876e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,179 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class BindingSelector
|
||||
{
|
||||
TreeViewController m_TreeView;
|
||||
public TreeViewController treeViewController
|
||||
{
|
||||
get { return m_TreeView; }
|
||||
}
|
||||
|
||||
TreeViewState m_TrackGlobalTreeViewState;
|
||||
TreeViewState m_TreeViewState;
|
||||
BindingTreeViewDataSource m_TreeViewDataSource;
|
||||
CurveDataSource m_CurveDataSource;
|
||||
TimelineWindow m_Window;
|
||||
CurveEditor m_CurveEditor;
|
||||
ReorderableList m_DopeLines;
|
||||
|
||||
string[] m_StringList = {};
|
||||
int[] m_Selection;
|
||||
bool m_PartOfSelection;
|
||||
public BindingSelector(EditorWindow window, CurveEditor curveEditor, TreeViewState trackGlobalTreeViewState)
|
||||
{
|
||||
m_Window = window as TimelineWindow;
|
||||
m_CurveEditor = curveEditor;
|
||||
m_TrackGlobalTreeViewState = trackGlobalTreeViewState;
|
||||
|
||||
m_DopeLines = new ReorderableList(m_StringList, typeof(string), false, false, false, false);
|
||||
m_DopeLines.drawElementBackgroundCallback = null;
|
||||
m_DopeLines.showDefaultBackground = false;
|
||||
m_DopeLines.index = 0;
|
||||
m_DopeLines.headerHeight = 0;
|
||||
m_DopeLines.elementHeight = 20;
|
||||
m_DopeLines.draggable = false;
|
||||
}
|
||||
|
||||
public void OnGUI(Rect targetRect)
|
||||
{
|
||||
if (m_TreeView == null)
|
||||
return;
|
||||
|
||||
m_TreeView.OnEvent();
|
||||
m_TreeView.OnGUI(targetRect, GUIUtility.GetControlID(FocusType.Passive));
|
||||
}
|
||||
|
||||
public void InitIfNeeded(Rect rect, CurveDataSource dataSource, bool isNewSelection)
|
||||
{
|
||||
if (Event.current.type != EventType.Layout)
|
||||
return;
|
||||
|
||||
m_CurveDataSource = dataSource;
|
||||
var clip = dataSource.animationClip;
|
||||
|
||||
List<EditorCurveBinding> allBindings = new List<EditorCurveBinding>();
|
||||
allBindings.Add(new EditorCurveBinding { propertyName = "Summary" });
|
||||
if (clip != null)
|
||||
allBindings.AddRange(AnimationUtility.GetCurveBindings(clip));
|
||||
|
||||
m_DopeLines.list = allBindings.ToArray();
|
||||
|
||||
if (m_TreeViewState != null)
|
||||
{
|
||||
if (isNewSelection)
|
||||
RefreshAll();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_TreeViewState = m_TrackGlobalTreeViewState != null ? m_TrackGlobalTreeViewState : new TreeViewState();
|
||||
|
||||
m_TreeView = new TreeViewController(m_Window, m_TreeViewState)
|
||||
{
|
||||
useExpansionAnimation = false,
|
||||
deselectOnUnhandledMouseDown = true
|
||||
};
|
||||
|
||||
m_TreeView.selectionChangedCallback += OnItemSelectionChanged;
|
||||
|
||||
m_TreeViewDataSource = new BindingTreeViewDataSource(m_TreeView, clip, m_CurveDataSource);
|
||||
|
||||
m_TreeView.Init(rect, m_TreeViewDataSource, new BindingTreeViewGUI(m_TreeView), null);
|
||||
|
||||
m_TreeViewDataSource.UpdateData();
|
||||
|
||||
RefreshSelection();
|
||||
}
|
||||
|
||||
void OnItemSelectionChanged(int[] selection)
|
||||
{
|
||||
RefreshSelection(selection);
|
||||
}
|
||||
|
||||
void RefreshAll()
|
||||
{
|
||||
RefreshTree();
|
||||
RefreshSelection();
|
||||
}
|
||||
|
||||
void RefreshSelection()
|
||||
{
|
||||
RefreshSelection(m_TreeViewState.selectedIDs != null ? m_TreeViewState.selectedIDs.ToArray() : null);
|
||||
}
|
||||
|
||||
void RefreshSelection(int[] selection)
|
||||
{
|
||||
if (selection == null || selection.Length == 0)
|
||||
{
|
||||
// select all.
|
||||
if (m_TreeViewDataSource.GetRows().Count > 0)
|
||||
{
|
||||
m_Selection = m_TreeViewDataSource.GetRows().Select(r => r.id).ToArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Selection = selection;
|
||||
}
|
||||
|
||||
RefreshCurves();
|
||||
}
|
||||
|
||||
public void RefreshCurves()
|
||||
{
|
||||
if (m_CurveDataSource == null || m_Selection == null)
|
||||
return;
|
||||
|
||||
var bindings = new HashSet<EditorCurveBinding>(AnimationPreviewUtilities.EditorCurveBindingComparer.Instance);
|
||||
foreach (int s in m_Selection)
|
||||
{
|
||||
var item = (CurveTreeViewNode)m_TreeView.FindItem(s);
|
||||
if (item != null && item.bindings != null)
|
||||
bindings.UnionWith(item.bindings);
|
||||
}
|
||||
var wrappers = m_CurveDataSource.GenerateWrappers(bindings);
|
||||
m_CurveEditor.animationCurves = wrappers.ToArray();
|
||||
}
|
||||
|
||||
public void RefreshTree()
|
||||
{
|
||||
if (m_TreeViewDataSource == null)
|
||||
return;
|
||||
|
||||
if (m_Selection == null)
|
||||
m_Selection = new int[0];
|
||||
|
||||
// get the names of the previous items
|
||||
var selected = m_Selection.Select(x => m_TreeViewDataSource.FindItem(x)).Where(t => t != null).Select(c => c.displayName).ToArray();
|
||||
|
||||
// update the source
|
||||
m_TreeViewDataSource.UpdateData();
|
||||
|
||||
// find the same items
|
||||
var reselected = m_TreeViewDataSource.GetRows().Where(x => selected.Contains(x.displayName)).Select(x => x.id).ToArray();
|
||||
if (!reselected.Any())
|
||||
{
|
||||
if (m_TreeViewDataSource.GetRows().Count > 0)
|
||||
{
|
||||
reselected = new[] { m_TreeViewDataSource.GetItem(0).id };
|
||||
}
|
||||
}
|
||||
|
||||
// update the selection
|
||||
OnItemSelectionChanged(reselected);
|
||||
}
|
||||
|
||||
internal virtual bool IsRenamingNodeAllowed(TreeViewItem node)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c171b9ca03610ea4faa426e082a1075d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,220 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
using L10n = UnityEditor.Timeline.L10n;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class BindingTreeViewDataSource : TreeViewDataSource
|
||||
{
|
||||
struct BindingGroup : IEquatable<BindingGroup>, IComparable<BindingGroup>
|
||||
{
|
||||
public readonly string GroupName;
|
||||
public readonly string Path;
|
||||
public readonly Type Type;
|
||||
|
||||
public BindingGroup(string path, string groupName, Type type)
|
||||
{
|
||||
Path = path;
|
||||
GroupName = groupName;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public string groupDisplayName => string.IsNullOrEmpty(Path) ? GroupName : string.Format($"{Path} : {GroupName}");
|
||||
public bool Equals(BindingGroup other) => GroupName == other.GroupName && Type == other.Type && Path == other.Path;
|
||||
public int CompareTo(BindingGroup other) => GetHashCode() - other.GetHashCode();
|
||||
public override bool Equals(object obj) => obj is BindingGroup other && Equals(other);
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashUtility.CombineHash(GroupName != null ? GroupName.GetHashCode() : 0, Type != null ? Type.GetHashCode() : 0, Path != null ? Path.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
|
||||
static readonly string s_DefaultValue = L10n.Tr("{0} (Default Value)");
|
||||
|
||||
public const int RootID = int.MinValue;
|
||||
public const int GroupID = -1;
|
||||
|
||||
private readonly AnimationClip m_Clip;
|
||||
private readonly CurveDataSource m_CurveDataSource;
|
||||
|
||||
public BindingTreeViewDataSource(
|
||||
TreeViewController treeView, AnimationClip clip, CurveDataSource curveDataSource)
|
||||
: base(treeView)
|
||||
{
|
||||
m_Clip = clip;
|
||||
showRootItem = false;
|
||||
m_CurveDataSource = curveDataSource;
|
||||
}
|
||||
|
||||
void SetupRootNodeSettings()
|
||||
{
|
||||
showRootItem = false;
|
||||
SetExpanded(RootID, true);
|
||||
SetExpanded(GroupID, true);
|
||||
}
|
||||
|
||||
public override void FetchData()
|
||||
{
|
||||
if (m_Clip == null)
|
||||
return;
|
||||
|
||||
var bindings = AnimationUtility.GetCurveBindings(m_Clip)
|
||||
.Union(AnimationUtility.GetObjectReferenceCurveBindings(m_Clip))
|
||||
.ToArray();
|
||||
|
||||
// a sorted linear list of nodes
|
||||
var results = bindings.GroupBy(GetBindingGroup, p => p, CreateTuple)
|
||||
.OrderBy(t => t.Item1.Path)
|
||||
.ThenBy(NamePrioritySort)
|
||||
// this makes component ordering match the animation window
|
||||
.ThenBy(t => t.Item1.Type.ToString())
|
||||
.ThenBy(t => t.Item1.GroupName).ToArray();
|
||||
|
||||
m_RootItem = new CurveTreeViewNode(RootID, null, "root", null)
|
||||
{
|
||||
children = new List<TreeViewItem>(1)
|
||||
};
|
||||
|
||||
if (results.Any())
|
||||
{
|
||||
var groupingNode = new CurveTreeViewNode(GroupID, m_RootItem, m_CurveDataSource.groupingName, bindings)
|
||||
{
|
||||
children = new List<TreeViewItem>()
|
||||
};
|
||||
|
||||
m_RootItem.children.Add(groupingNode);
|
||||
|
||||
foreach (var r in results)
|
||||
{
|
||||
var key = r.Item1;
|
||||
var nodeBindings = r.Item2;
|
||||
|
||||
FillMissingTransformCurves(nodeBindings);
|
||||
if (nodeBindings.Count == 1)
|
||||
groupingNode.children.Add(CreateLeafNode(nodeBindings[0], groupingNode, PropertyName(nodeBindings[0], true)));
|
||||
else if (nodeBindings.Count > 1)
|
||||
{
|
||||
var childBindings = nodeBindings.OrderBy(BindingSort).ToArray();
|
||||
var parent = new CurveTreeViewNode(key.GetHashCode(), groupingNode, key.groupDisplayName, childBindings) {children = new List<TreeViewItem>()};
|
||||
groupingNode.children.Add(parent);
|
||||
foreach (var b in childBindings)
|
||||
parent.children.Add(CreateLeafNode(b, parent, PropertyName(b, false)));
|
||||
}
|
||||
}
|
||||
SetupRootNodeSettings();
|
||||
}
|
||||
|
||||
m_NeedRefreshRows = true;
|
||||
}
|
||||
|
||||
public void UpdateData()
|
||||
{
|
||||
m_TreeView.ReloadData();
|
||||
}
|
||||
|
||||
string GroupName(EditorCurveBinding binding)
|
||||
{
|
||||
var propertyName = m_CurveDataSource.ModifyPropertyDisplayName(binding.path, binding.propertyName);
|
||||
return CleanUpArrayBinding(AnimationWindowUtility.NicifyPropertyGroupName(binding.type, propertyName), true);
|
||||
}
|
||||
|
||||
static string CleanUpArrayBinding(string propertyName, bool isGroup)
|
||||
{
|
||||
const string arrayIndicator = ".Array.data[";
|
||||
const string arrayDisplay = ".data[";
|
||||
|
||||
var arrayIndex = propertyName.LastIndexOf(arrayIndicator, StringComparison.Ordinal);
|
||||
if (arrayIndex == -1)
|
||||
return propertyName;
|
||||
if (isGroup)
|
||||
propertyName = propertyName.Substring(0, arrayIndex);
|
||||
return propertyName.Replace(arrayIndicator, arrayDisplay);
|
||||
}
|
||||
|
||||
string PropertyName(EditorCurveBinding binding, bool prependPathName)
|
||||
{
|
||||
var propertyName = m_CurveDataSource.ModifyPropertyDisplayName(binding.path, binding.propertyName);
|
||||
propertyName = CleanUpArrayBinding(AnimationWindowUtility.GetPropertyDisplayName(propertyName), false);
|
||||
if (binding.isPhantom)
|
||||
propertyName = string.Format(s_DefaultValue, propertyName);
|
||||
if (prependPathName && !string.IsNullOrEmpty(binding.path))
|
||||
propertyName = $"{binding.path} : {propertyName}";
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
BindingGroup GetBindingGroup(EditorCurveBinding binding)
|
||||
{
|
||||
return new BindingGroup(binding.path ?? string.Empty, GroupName(binding), binding.type);
|
||||
}
|
||||
|
||||
static CurveTreeViewNode CreateLeafNode(EditorCurveBinding binding, TreeViewItem parent, string displayName)
|
||||
{
|
||||
return new CurveTreeViewNode(binding.GetHashCode(), parent, displayName, new[] { binding }, AnimationWindowUtility.ForceGrouping(binding));
|
||||
}
|
||||
|
||||
static void FillMissingTransformCurves(List<EditorCurveBinding> bindings)
|
||||
{
|
||||
if (!AnimationWindowUtility.IsActualTransformCurve(bindings[0]) || bindings.Count >= 3)
|
||||
return;
|
||||
|
||||
var binding = bindings[0];
|
||||
var prefixPropertyName = binding.propertyName.Split('.').First();
|
||||
|
||||
binding.isPhantom = true;
|
||||
if (!bindings.Any(p => p.propertyName.EndsWith(".x")))
|
||||
{
|
||||
binding.propertyName = prefixPropertyName + ".x";
|
||||
bindings.Insert(0, binding);
|
||||
}
|
||||
|
||||
if (!bindings.Any(p => p.propertyName.EndsWith(".y")))
|
||||
{
|
||||
binding.propertyName = prefixPropertyName + ".y";
|
||||
bindings.Insert(1, binding);
|
||||
}
|
||||
|
||||
if (!bindings.Any(p => p.propertyName.EndsWith(".z")))
|
||||
{
|
||||
binding.propertyName = prefixPropertyName + ".z";
|
||||
bindings.Insert(2, binding);
|
||||
}
|
||||
}
|
||||
|
||||
// make sure vectors and colors are sorted correctly in their subgroups
|
||||
static int BindingSort(EditorCurveBinding b)
|
||||
{
|
||||
return AnimationWindowUtility.GetComponentIndex(b.propertyName);
|
||||
}
|
||||
|
||||
static int NamePrioritySort(ValueTuple<BindingGroup, List<EditorCurveBinding>> group)
|
||||
{
|
||||
if (group.Item1.Type != typeof(Transform))
|
||||
return 0;
|
||||
|
||||
switch (group.Item1.GroupName)
|
||||
{
|
||||
case "Position": return Int32.MinValue;
|
||||
case "Rotation": return Int32.MinValue + 1;
|
||||
case "Scale": return Int32.MinValue + 2;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static ValueTuple<BindingGroup, List<EditorCurveBinding>> CreateTuple(BindingGroup key, IEnumerable<EditorCurveBinding> items)
|
||||
{
|
||||
return new ValueTuple<BindingGroup, List<EditorCurveBinding>>()
|
||||
{
|
||||
Item1 = key,
|
||||
Item2 = items.ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9c2177aaf0fde92439246adc2dc0bfa2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,113 @@
|
|||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class BindingTreeViewGUI : TreeViewGUI
|
||||
{
|
||||
static readonly float s_RowRightOffset = 10;
|
||||
static readonly float s_ColorIndicatorTopMargin = 3;
|
||||
static readonly Color s_KeyColorForNonCurves = new Color(0.7f, 0.7f, 0.7f, 0.5f);
|
||||
static readonly Color s_ChildrenCurveLabelColor = new Color(1.0f, 1.0f, 1.0f, 0.7f);
|
||||
static readonly Color s_PhantomPropertyLabelColor = new Color(0.0f, 0.8f, 0.8f, 1f);
|
||||
static readonly Texture2D s_DefaultScriptTexture = EditorGUIUtility.LoadIcon("cs Script Icon");
|
||||
static readonly Texture2D s_TrackDefault = EditorGUIUtility.LoadIcon("UnityEngine/ScriptableObject Icon");
|
||||
|
||||
public BindingTreeViewGUI(TreeViewController treeView)
|
||||
: base(treeView, true)
|
||||
{
|
||||
k_IconWidth = 13.0f;
|
||||
iconOverlayGUI += OnItemIconOverlay;
|
||||
}
|
||||
|
||||
public override void OnRowGUI(Rect rowRect, TreeViewItem node, int row, bool selected, bool focused)
|
||||
{
|
||||
Color originalColor = GUI.color;
|
||||
bool leafNode = node.parent != null && node.parent.id != BindingTreeViewDataSource.RootID && node.parent.id != BindingTreeViewDataSource.GroupID;
|
||||
GUI.color = Color.white;
|
||||
if (leafNode)
|
||||
{
|
||||
CurveTreeViewNode curveNode = node as CurveTreeViewNode;
|
||||
if (curveNode != null && curveNode.bindings.Any() && curveNode.bindings.First().isPhantom)
|
||||
GUI.color = s_PhantomPropertyLabelColor;
|
||||
else
|
||||
GUI.color = s_ChildrenCurveLabelColor;
|
||||
}
|
||||
|
||||
base.OnRowGUI(rowRect, node, row, selected, focused);
|
||||
|
||||
GUI.color = originalColor;
|
||||
DoCurveColorIndicator(rowRect, node as CurveTreeViewNode);
|
||||
}
|
||||
|
||||
protected override bool IsRenaming(int id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool BeginRename(TreeViewItem item, float delay)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static void DoCurveColorIndicator(Rect rect, CurveTreeViewNode node)
|
||||
{
|
||||
if (node == null)
|
||||
return;
|
||||
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
Color originalColor = GUI.color;
|
||||
|
||||
if (node.bindings.Length == 1 && !node.bindings[0].isPPtrCurve)
|
||||
GUI.color = CurveUtility.GetPropertyColor(node.bindings[0].propertyName);
|
||||
else
|
||||
GUI.color = s_KeyColorForNonCurves;
|
||||
|
||||
Texture icon = CurveUtility.GetIconCurve();
|
||||
rect = new Rect(rect.xMax - s_RowRightOffset - (icon.width * 0.5f) - 5, rect.yMin + s_ColorIndicatorTopMargin, icon.width, icon.height);
|
||||
|
||||
GUI.DrawTexture(rect, icon, ScaleMode.ScaleToFit, true, 1);
|
||||
|
||||
GUI.color = originalColor;
|
||||
}
|
||||
|
||||
protected override Texture GetIconForItem(TreeViewItem item)
|
||||
{
|
||||
var node = item as CurveTreeViewNode;
|
||||
if (node == null)
|
||||
return null;
|
||||
|
||||
var type = node.iconType;
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
// track type icon
|
||||
if (typeof(TrackAsset).IsAssignableFrom(type))
|
||||
{
|
||||
var icon = TrackResourceCache.GetTrackIconForType(type);
|
||||
return icon == s_TrackDefault ? s_DefaultScriptTexture : icon;
|
||||
}
|
||||
|
||||
// custom clip icons always use the script texture
|
||||
if (typeof(PlayableAsset).IsAssignableFrom(type))
|
||||
return s_DefaultScriptTexture;
|
||||
|
||||
// this will return null for MonoBehaviours without a custom icon.
|
||||
// use the scripting icon instead
|
||||
return AssetPreview.GetMiniTypeThumbnail(type) ?? s_DefaultScriptTexture;
|
||||
}
|
||||
|
||||
static void OnItemIconOverlay(TreeViewItem item, Rect rect)
|
||||
{
|
||||
var curveNodeItem = item as CurveTreeViewNode;
|
||||
if (curveNodeItem != null && curveNodeItem.iconOverlay != null)
|
||||
GUI.Label(rect, curveNodeItem.iconOverlay);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3c09dc5cd0a70cf40856b7d406106ee1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,367 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
class ClipCurveEditor
|
||||
{
|
||||
static readonly GUIContent s_RemoveCurveContent = new GUIContent(L10n.Tr("Remove Curve"));
|
||||
static readonly GUIContent s_RemoveCurvesContent = new GUIContent(L10n.Tr("Remove Curves"));
|
||||
|
||||
internal readonly CurveEditor m_CurveEditor;
|
||||
static readonly CurveEditorSettings s_CurveEditorSettings = new CurveEditorSettings
|
||||
{
|
||||
hSlider = false,
|
||||
vSlider = false,
|
||||
hRangeLocked = false,
|
||||
vRangeLocked = false,
|
||||
scaleWithWindow = true,
|
||||
hRangeMin = 0.0f,
|
||||
showAxisLabels = true,
|
||||
allowDeleteLastKeyInCurve = true,
|
||||
rectangleToolFlags = CurveEditorSettings.RectangleToolFlags.MiniRectangleTool
|
||||
};
|
||||
|
||||
static readonly float s_GridLabelWidth = 40.0f;
|
||||
|
||||
readonly BindingSelector m_BindingHierarchy;
|
||||
public BindingSelector bindingHierarchy
|
||||
{
|
||||
get { return m_BindingHierarchy; }
|
||||
}
|
||||
|
||||
public Rect shownAreaInsideMargins
|
||||
{
|
||||
get { return m_CurveEditor != null ? m_CurveEditor.shownAreaInsideMargins : new Rect(1, 1, 1, 1); }
|
||||
}
|
||||
|
||||
Vector2 m_ScrollPosition = Vector2.zero;
|
||||
|
||||
readonly CurveDataSource m_DataSource;
|
||||
|
||||
float m_LastFrameRate = 30.0f;
|
||||
|
||||
UInt64 m_LastClipVersion = UInt64.MaxValue;
|
||||
|
||||
TrackViewModelData m_ViewModel;
|
||||
bool m_ShouldRestoreShownArea;
|
||||
|
||||
bool isNewSelection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ViewModel == null || m_DataSource == null)
|
||||
return true;
|
||||
|
||||
return m_ViewModel.lastInlineCurveDataID != m_DataSource.id;
|
||||
}
|
||||
}
|
||||
|
||||
internal CurveEditor curveEditor
|
||||
{
|
||||
get { return m_CurveEditor; }
|
||||
}
|
||||
|
||||
public ClipCurveEditor(CurveDataSource dataSource, TimelineWindow parentWindow, TrackAsset hostTrack)
|
||||
{
|
||||
m_DataSource = dataSource;
|
||||
|
||||
m_CurveEditor = new CurveEditor(new Rect(0, 0, 1000, 100), new CurveWrapper[0], false);
|
||||
|
||||
s_CurveEditorSettings.vTickStyle = new TickStyle
|
||||
{
|
||||
tickColor = { color = DirectorStyles.Instance.customSkin.colorInlineCurveVerticalLines },
|
||||
distLabel = 20,
|
||||
stubs = true
|
||||
};
|
||||
|
||||
s_CurveEditorSettings.hTickStyle = new TickStyle
|
||||
{
|
||||
// hide horizontal lines by giving them a transparent color
|
||||
tickColor = { color = new Color(0.0f, 0.0f, 0.0f, 0.0f) },
|
||||
distLabel = 0
|
||||
};
|
||||
|
||||
m_CurveEditor.settings = s_CurveEditorSettings;
|
||||
|
||||
m_ViewModel = TimelineWindowViewPrefs.GetTrackViewModelData(hostTrack);
|
||||
|
||||
m_ShouldRestoreShownArea = true;
|
||||
m_CurveEditor.ignoreScrollWheelUntilClicked = true;
|
||||
m_CurveEditor.curvesUpdated = OnCurvesUpdated;
|
||||
|
||||
m_BindingHierarchy = new BindingSelector(parentWindow, m_CurveEditor, m_ViewModel.inlineCurvesState);
|
||||
}
|
||||
|
||||
public void SelectAllKeys()
|
||||
{
|
||||
m_CurveEditor.SelectAll();
|
||||
}
|
||||
|
||||
public void FrameClip()
|
||||
{
|
||||
m_CurveEditor.InvalidateBounds();
|
||||
m_CurveEditor.FrameClip(false, true);
|
||||
}
|
||||
|
||||
public CurveDataSource dataSource
|
||||
{
|
||||
get { return m_DataSource; }
|
||||
}
|
||||
|
||||
// called when curves are edited
|
||||
internal void OnCurvesUpdated()
|
||||
{
|
||||
if (m_DataSource == null)
|
||||
return;
|
||||
|
||||
if (m_CurveEditor == null)
|
||||
return;
|
||||
|
||||
if (m_CurveEditor.animationCurves.Length == 0)
|
||||
return;
|
||||
|
||||
List<CurveWrapper> curvesToUpdate = m_CurveEditor.animationCurves.Where(c => c.changed).ToList();
|
||||
|
||||
// nothing changed, return.
|
||||
if (curvesToUpdate.Count == 0)
|
||||
return;
|
||||
|
||||
// something changed, manage the undo properly.
|
||||
m_DataSource.ApplyCurveChanges(curvesToUpdate);
|
||||
m_LastClipVersion = m_DataSource.GetClipVersion();
|
||||
}
|
||||
|
||||
public void DrawHeader(Rect headerRect)
|
||||
{
|
||||
m_BindingHierarchy.InitIfNeeded(headerRect, m_DataSource, isNewSelection);
|
||||
|
||||
try
|
||||
{
|
||||
GUILayout.BeginArea(headerRect);
|
||||
m_ScrollPosition = GUILayout.BeginScrollView(m_ScrollPosition, GUIStyle.none, GUI.skin.verticalScrollbar);
|
||||
m_BindingHierarchy.OnGUI(new Rect(0, 0, headerRect.width, headerRect.height));
|
||||
if (m_BindingHierarchy.treeViewController != null)
|
||||
m_BindingHierarchy.treeViewController.contextClickItemCallback = ContextClickItemCallback;
|
||||
GUILayout.EndScrollView();
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
void ContextClickItemCallback(int obj)
|
||||
{
|
||||
GenerateContextMenu(obj);
|
||||
}
|
||||
|
||||
void GenerateContextMenu(int obj = -1)
|
||||
{
|
||||
if (Event.current.type != EventType.ContextClick)
|
||||
return;
|
||||
|
||||
var selectedCurves = GetSelectedProperties().ToArray();
|
||||
if (selectedCurves.Length > 0)
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
var content = selectedCurves.Length == 1 ? s_RemoveCurveContent : s_RemoveCurvesContent;
|
||||
menu.AddItem(content,
|
||||
false,
|
||||
() => RemoveCurves(selectedCurves)
|
||||
);
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<EditorCurveBinding> GetSelectedProperties(bool useForcedGroups = false)
|
||||
{
|
||||
var bindings = new HashSet<EditorCurveBinding>();
|
||||
var bindingTree = m_BindingHierarchy.treeViewController.data as BindingTreeViewDataSource;
|
||||
foreach (var selectedId in m_BindingHierarchy.treeViewController.GetSelection())
|
||||
{
|
||||
var node = bindingTree.FindItem(selectedId) as CurveTreeViewNode;
|
||||
if (node == null)
|
||||
continue;
|
||||
|
||||
var curveNodeParent = node.parent as CurveTreeViewNode;
|
||||
if (useForcedGroups && node.forceGroup && curveNodeParent != null)
|
||||
bindings.UnionWith(curveNodeParent.bindings);
|
||||
else
|
||||
bindings.UnionWith(node.bindings);
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public void RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
m_DataSource.RemoveCurves(bindings);
|
||||
m_BindingHierarchy.RefreshTree();
|
||||
TimelineWindow.instance.state.CalculateRowRects();
|
||||
m_LastClipVersion = m_DataSource.GetClipVersion();
|
||||
}
|
||||
|
||||
class CurveEditorState : ICurveEditorState
|
||||
{
|
||||
public TimeArea.TimeFormat timeFormat { get; set; }
|
||||
public Vector2 timeRange => new Vector2(0, 1);
|
||||
public bool rippleTime => false;
|
||||
}
|
||||
|
||||
void UpdateCurveEditorIfNeeded(WindowState state)
|
||||
{
|
||||
if ((Event.current.type != EventType.Layout) || (m_DataSource == null) || (m_BindingHierarchy == null))
|
||||
return;
|
||||
|
||||
// check if the curves have changed externally
|
||||
var curveChange = m_DataSource.UpdateExternalChanges(ref m_LastClipVersion);
|
||||
if (curveChange == CurveChangeType.None)
|
||||
return;
|
||||
|
||||
if (curveChange == CurveChangeType.CurveAddedOrRemoved)
|
||||
m_BindingHierarchy.RefreshTree();
|
||||
else // curve modified
|
||||
m_BindingHierarchy.RefreshCurves();
|
||||
|
||||
m_CurveEditor.InvalidateSelectionBounds();
|
||||
|
||||
m_CurveEditor.state = new CurveEditorState() {timeFormat = state.timeFormat.ToTimeAreaFormat()};
|
||||
m_CurveEditor.invSnap = state.referenceSequence.frameRate;
|
||||
}
|
||||
|
||||
public void DrawCurveEditor(Rect rect, WindowState state, Vector2 clipRange, bool loop, bool selected)
|
||||
{
|
||||
SetupMarginsAndRect(rect, state);
|
||||
UpdateCurveEditorIfNeeded(state);
|
||||
|
||||
if (m_ShouldRestoreShownArea)
|
||||
RestoreShownArea();
|
||||
|
||||
var curveVisibleTimeRange = CalculateCurveVisibleTimeRange(state.timeAreaShownRange, m_DataSource);
|
||||
m_CurveEditor.SetShownHRangeInsideMargins(curveVisibleTimeRange.x, curveVisibleTimeRange.y); //align the curve with the clip.
|
||||
|
||||
if (m_LastFrameRate != state.referenceSequence.frameRate)
|
||||
{
|
||||
m_CurveEditor.hTicks.SetTickModulosForFrameRate(state.referenceSequence.frameRate);
|
||||
m_LastFrameRate = state.referenceSequence.frameRate;
|
||||
}
|
||||
|
||||
foreach (var cw in m_CurveEditor.animationCurves)
|
||||
cw.renderer.SetWrap(WrapMode.Default, loop ? WrapMode.Loop : WrapMode.Default);
|
||||
|
||||
using (new GUIGroupScope(rect))
|
||||
{
|
||||
var localRect = new Rect(0.0f, 0.0f, rect.width, rect.height);
|
||||
var localClipRange = new Vector2(Mathf.Floor(clipRange.x - rect.xMin), Mathf.Ceil(clipRange.y - rect.xMin));
|
||||
var curveStartPosX = Mathf.Floor(state.TimeToPixel(m_DataSource.start) - rect.xMin);
|
||||
|
||||
EditorGUI.DrawRect(new Rect(curveStartPosX, 0.0f, 1.0f, rect.height), new Color(1.0f, 1.0f, 1.0f, 0.5f));
|
||||
DrawCurveEditorBackground(localRect);
|
||||
|
||||
if (selected)
|
||||
{
|
||||
var selectionRect = new Rect(localClipRange.x, 0.0f, localClipRange.y - localClipRange.x, localRect.height);
|
||||
DrawOutline(selectionRect);
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
{
|
||||
var evt = Event.current;
|
||||
if (evt.type == EventType.Layout || evt.type == EventType.Repaint || selected)
|
||||
m_CurveEditor.CurveGUI();
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
OnCurvesUpdated();
|
||||
|
||||
DrawOverlay(localRect, localClipRange, DirectorStyles.Instance.customSkin.colorInlineCurveOutOfRangeOverlay);
|
||||
DrawGrid(localRect, curveStartPosX);
|
||||
}
|
||||
}
|
||||
|
||||
static Vector2 CalculateCurveVisibleTimeRange(Vector2 timeAreaShownRange, CurveDataSource curve)
|
||||
{
|
||||
var curveVisibleTimeRange = new Vector2
|
||||
{
|
||||
x = Math.Max(0.0f, timeAreaShownRange.x - curve.start),
|
||||
y = timeAreaShownRange.y - curve.start
|
||||
};
|
||||
return curveVisibleTimeRange * curve.timeScale;
|
||||
}
|
||||
|
||||
void SetupMarginsAndRect(Rect rect, WindowState state)
|
||||
{
|
||||
var startX = state.TimeToPixel(m_DataSource.start) - rect.x;
|
||||
var timelineWidth = state.timeAreaRect.width;
|
||||
m_CurveEditor.rect = new Rect(0.0f, 0.0f, timelineWidth, rect.height);
|
||||
m_CurveEditor.leftmargin = Math.Max(startX, 0.0f);
|
||||
m_CurveEditor.rightmargin = 0.0f;
|
||||
m_CurveEditor.topmargin = m_CurveEditor.bottommargin = CalculateTopMargin(rect.height);
|
||||
}
|
||||
|
||||
void RestoreShownArea()
|
||||
{
|
||||
if (isNewSelection)
|
||||
FrameClip();
|
||||
else
|
||||
m_CurveEditor.shownAreaInsideMargins = m_ViewModel.inlineCurvesShownAreaInsideMargins;
|
||||
m_ShouldRestoreShownArea = false;
|
||||
}
|
||||
|
||||
static void DrawCurveEditorBackground(Rect rect)
|
||||
{
|
||||
if (EditorGUIUtility.isProSkin)
|
||||
return;
|
||||
|
||||
var animEditorBackgroundRect = Rect.MinMaxRect(0.0f, rect.yMin, rect.xMax, rect.yMax);
|
||||
|
||||
// Curves are not legible in Personal Skin so we need to darken the background a bit.
|
||||
EditorGUI.DrawRect(animEditorBackgroundRect, DirectorStyles.Instance.customSkin.colorInlineCurvesBackground);
|
||||
}
|
||||
|
||||
static float CalculateTopMargin(float height)
|
||||
{
|
||||
return Mathf.Clamp(0.15f * height, 10.0f, 40.0f);
|
||||
}
|
||||
|
||||
static void DrawOutline(Rect rect, float thickness = 2.0f)
|
||||
{
|
||||
// Draw top selected lines.
|
||||
EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMin, rect.width, thickness), Color.white);
|
||||
|
||||
// Draw bottom selected lines.
|
||||
EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMax - thickness, rect.width, thickness), Color.white);
|
||||
|
||||
// Draw Left Selected Lines
|
||||
EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMin, thickness, rect.height), Color.white);
|
||||
|
||||
// Draw Right Selected Lines
|
||||
EditorGUI.DrawRect(new Rect(rect.xMax - thickness, rect.yMin, thickness, rect.height), Color.white);
|
||||
}
|
||||
|
||||
static void DrawOverlay(Rect rect, Vector2 clipRange, Color color)
|
||||
{
|
||||
var leftSide = new Rect(rect.xMin, rect.yMin, clipRange.x - rect.xMin, rect.height);
|
||||
EditorGUI.DrawRect(leftSide, color);
|
||||
|
||||
var rightSide = new Rect(Mathf.Max(0.0f, clipRange.y), rect.yMin, rect.xMax, rect.height);
|
||||
EditorGUI.DrawRect(rightSide, color);
|
||||
}
|
||||
|
||||
void DrawGrid(Rect rect, float curveXPosition)
|
||||
{
|
||||
var gridXPos = Mathf.Max(curveXPosition - s_GridLabelWidth, rect.xMin);
|
||||
var gridRect = new Rect(gridXPos, rect.y, s_GridLabelWidth, rect.height);
|
||||
var originalRect = m_CurveEditor.rect;
|
||||
|
||||
m_CurveEditor.rect = new Rect(0.0f, 0.0f, rect.width, rect.height);
|
||||
using (new GUIGroupScope(gridRect))
|
||||
m_CurveEditor.GridGUI();
|
||||
m_CurveEditor.rect = originalRect;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d49b2ed20045e034f9cdf6a6d95e6183
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,439 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
enum CurveChangeType
|
||||
{
|
||||
None,
|
||||
CurveModified,
|
||||
CurveAddedOrRemoved
|
||||
}
|
||||
|
||||
abstract class CurveDataSource
|
||||
{
|
||||
public static CurveDataSource Create(IRowGUI trackGUI)
|
||||
{
|
||||
if (trackGUI.asset is AnimationTrack)
|
||||
return new InfiniteClipCurveDataSource(trackGUI);
|
||||
|
||||
return new TrackParametersCurveDataSource(trackGUI);
|
||||
}
|
||||
|
||||
public static CurveDataSource Create(TimelineClipGUI clipGUI)
|
||||
{
|
||||
if (clipGUI.clip.animationClip != null)
|
||||
return new ClipAnimationCurveDataSource(clipGUI);
|
||||
|
||||
return new ClipParametersCurveDataSource(clipGUI);
|
||||
}
|
||||
|
||||
int? m_ID = null;
|
||||
public int id
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_ID.HasValue)
|
||||
m_ID = CreateHashCode();
|
||||
|
||||
return m_ID.Value;
|
||||
}
|
||||
}
|
||||
|
||||
readonly IRowGUI m_TrackGUI;
|
||||
protected CurveDataSource(IRowGUI trackGUI)
|
||||
{
|
||||
m_TrackGUI = trackGUI;
|
||||
}
|
||||
|
||||
public abstract AnimationClip animationClip { get; }
|
||||
|
||||
public abstract float start { get; }
|
||||
public abstract float timeScale { get; }
|
||||
public abstract string groupingName { get; }
|
||||
|
||||
// Applies changes from the visual curve in the curve wrapper back to the animation clips
|
||||
public virtual void ApplyCurveChanges(IEnumerable<CurveWrapper> updatedCurves)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(animationClip, "Edit Clip Curve");
|
||||
foreach (CurveWrapper c in updatedCurves)
|
||||
{
|
||||
if (c.curve.length > 0)
|
||||
AnimationUtility.SetEditorCurve(animationClip, c.binding, c.curve);
|
||||
else
|
||||
RemoveCurves(new[] {c.binding});
|
||||
c.changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The clip version is a value that will change when a curve gets updated.
|
||||
/// it's used to detect when an animation clip has been changed externally </summary>
|
||||
/// <returns>A versioning value indicating the state of the curve. If the curve is updated externally this value will change. </returns>
|
||||
public virtual UInt64 GetClipVersion()
|
||||
{
|
||||
return animationClip.ClipVersion();
|
||||
}
|
||||
|
||||
/// <summary>Call this method to check if the underlying clip has changed</summary>
|
||||
/// <param name="curveVersion">A versioning value. This will be updated to the latest version</param>
|
||||
/// <returns>A value indicating how the clip has changed</returns>
|
||||
public virtual CurveChangeType UpdateExternalChanges(ref UInt64 curveVersion)
|
||||
{
|
||||
return animationClip.GetChangeType(ref curveVersion);
|
||||
}
|
||||
|
||||
public virtual string ModifyPropertyDisplayName(string path, string propertyName) => propertyName;
|
||||
|
||||
public virtual void RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(animationClip, "Remove Curve(s)");
|
||||
foreach (var binding in bindings)
|
||||
{
|
||||
if (binding.isPPtrCurve)
|
||||
AnimationUtility.SetObjectReferenceCurve(animationClip, binding, null);
|
||||
else
|
||||
AnimationUtility.SetEditorCurve(animationClip, binding, null);
|
||||
}
|
||||
}
|
||||
|
||||
public Rect GetBackgroundRect(WindowState state)
|
||||
{
|
||||
var trackRect = m_TrackGUI.boundingRect;
|
||||
return new Rect(
|
||||
state.timeAreaTranslation.x + trackRect.xMin,
|
||||
trackRect.y,
|
||||
(float)state.editSequence.asset.duration * state.timeAreaScale.x,
|
||||
trackRect.height
|
||||
);
|
||||
}
|
||||
|
||||
public List<CurveWrapper> GenerateWrappers(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
var wrappers = new List<CurveWrapper>(bindings.Count());
|
||||
int curveWrapperId = 0;
|
||||
|
||||
foreach (EditorCurveBinding b in bindings)
|
||||
{
|
||||
// General configuration
|
||||
var wrapper = new CurveWrapper
|
||||
{
|
||||
id = curveWrapperId++,
|
||||
binding = b,
|
||||
groupId = -1,
|
||||
hidden = false,
|
||||
readOnly = false,
|
||||
getAxisUiScalarsCallback = () => new Vector2(1, 1)
|
||||
};
|
||||
|
||||
// Specific configuration
|
||||
ConfigureCurveWrapper(wrapper);
|
||||
|
||||
wrappers.Add(wrapper);
|
||||
}
|
||||
|
||||
return wrappers;
|
||||
}
|
||||
|
||||
protected virtual void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
wrapper.color = CurveUtility.GetPropertyColor(wrapper.binding.propertyName);
|
||||
wrapper.renderer = new NormalCurveRenderer(AnimationUtility.GetEditorCurve(animationClip, wrapper.binding));
|
||||
wrapper.renderer.SetCustomRange(0.0f, animationClip.length);
|
||||
}
|
||||
|
||||
protected virtual int CreateHashCode()
|
||||
{
|
||||
return m_TrackGUI.asset.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
class ClipAnimationCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Animated Values");
|
||||
|
||||
readonly TimelineClipGUI m_ClipGUI;
|
||||
|
||||
public ClipAnimationCurveDataSource(TimelineClipGUI clipGUI) : base(clipGUI.parent)
|
||||
{
|
||||
m_ClipGUI = clipGUI;
|
||||
}
|
||||
|
||||
public override AnimationClip animationClip
|
||||
{
|
||||
get { return m_ClipGUI.clip.animationClip; }
|
||||
}
|
||||
|
||||
public override float start
|
||||
{
|
||||
get { return (float)m_ClipGUI.clip.FromLocalTimeUnbound(0.0); }
|
||||
}
|
||||
|
||||
public override float timeScale
|
||||
{
|
||||
get { return (float)m_ClipGUI.clip.timeScale; }
|
||||
}
|
||||
|
||||
public override string groupingName
|
||||
{
|
||||
get { return k_GroupingName; }
|
||||
}
|
||||
|
||||
protected override int CreateHashCode()
|
||||
{
|
||||
return base.CreateHashCode().CombineHash(m_ClipGUI.clip.GetHashCode());
|
||||
}
|
||||
|
||||
public override string ModifyPropertyDisplayName(string path, string propertyName)
|
||||
{
|
||||
if (!AnimatedPropertyUtility.IsMaterialProperty(propertyName))
|
||||
return propertyName;
|
||||
|
||||
var track = m_ClipGUI.clip.GetParentTrack();
|
||||
if (track == null)
|
||||
return propertyName;
|
||||
|
||||
var gameObjectBinding = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, track);
|
||||
if (gameObjectBinding == null)
|
||||
return propertyName;
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var transform = gameObjectBinding.transform.Find(path);
|
||||
if (transform == null)
|
||||
return propertyName;
|
||||
gameObjectBinding = transform.gameObject;
|
||||
}
|
||||
|
||||
return AnimatedPropertyUtility.RemapMaterialName(gameObjectBinding, propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
class ClipParametersCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Clip Properties");
|
||||
|
||||
readonly TimelineClipGUI m_ClipGUI;
|
||||
readonly CurvesProxy m_CurvesProxy;
|
||||
|
||||
private int m_ClipDirtyVersion;
|
||||
|
||||
public ClipParametersCurveDataSource(TimelineClipGUI clipGUI) : base(clipGUI.parent)
|
||||
{
|
||||
m_ClipGUI = clipGUI;
|
||||
m_CurvesProxy = new CurvesProxy(clipGUI.clip);
|
||||
}
|
||||
|
||||
public override AnimationClip animationClip
|
||||
{
|
||||
get { return m_CurvesProxy.curves; }
|
||||
}
|
||||
|
||||
public override UInt64 GetClipVersion()
|
||||
{
|
||||
return sourceAnimationClip.ClipVersion();
|
||||
}
|
||||
|
||||
public override CurveChangeType UpdateExternalChanges(ref ulong curveVersion)
|
||||
{
|
||||
if (m_ClipGUI == null || m_ClipGUI.clip == null)
|
||||
return CurveChangeType.None;
|
||||
|
||||
var changeType = sourceAnimationClip.GetChangeType(ref curveVersion);
|
||||
if (changeType != CurveChangeType.None)
|
||||
{
|
||||
m_CurvesProxy.ApplyExternalChangesToProxy();
|
||||
}
|
||||
else if (m_ClipDirtyVersion != m_ClipGUI.clip.DirtyIndex)
|
||||
{
|
||||
m_CurvesProxy.UpdateProxyCurves();
|
||||
if (changeType == CurveChangeType.None)
|
||||
changeType = CurveChangeType.CurveModified;
|
||||
}
|
||||
m_ClipDirtyVersion = m_ClipGUI.clip.DirtyIndex;
|
||||
return changeType;
|
||||
}
|
||||
|
||||
public override float start
|
||||
{
|
||||
get { return (float)m_ClipGUI.clip.FromLocalTimeUnbound(0.0); }
|
||||
}
|
||||
|
||||
public override float timeScale
|
||||
{
|
||||
get { return (float)m_ClipGUI.clip.timeScale; }
|
||||
}
|
||||
|
||||
public override string groupingName
|
||||
{
|
||||
get { return k_GroupingName; }
|
||||
}
|
||||
|
||||
public override void RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
m_CurvesProxy.RemoveCurves(bindings);
|
||||
}
|
||||
|
||||
public override void ApplyCurveChanges(IEnumerable<CurveWrapper> updatedCurves)
|
||||
{
|
||||
m_CurvesProxy.UpdateCurves(updatedCurves);
|
||||
}
|
||||
|
||||
protected override void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
m_CurvesProxy.ConfigureCurveWrapper(wrapper);
|
||||
}
|
||||
|
||||
protected override int CreateHashCode()
|
||||
{
|
||||
return base.CreateHashCode().CombineHash(m_ClipGUI.clip.GetHashCode());
|
||||
}
|
||||
|
||||
private AnimationClip sourceAnimationClip
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ClipGUI == null || m_ClipGUI.clip == null || m_ClipGUI.clip.curves == null)
|
||||
return null;
|
||||
return m_ClipGUI.clip.curves;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InfiniteClipCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Animated Values");
|
||||
|
||||
readonly AnimationTrack m_AnimationTrack;
|
||||
|
||||
public InfiniteClipCurveDataSource(IRowGUI trackGui) : base(trackGui)
|
||||
{
|
||||
m_AnimationTrack = trackGui.asset as AnimationTrack;
|
||||
}
|
||||
|
||||
public override AnimationClip animationClip
|
||||
{
|
||||
get { return m_AnimationTrack.infiniteClip; }
|
||||
}
|
||||
|
||||
public override float start
|
||||
{
|
||||
get { return 0.0f; }
|
||||
}
|
||||
|
||||
public override float timeScale
|
||||
{
|
||||
get { return 1.0f; }
|
||||
}
|
||||
|
||||
public override string groupingName
|
||||
{
|
||||
get { return k_GroupingName; }
|
||||
}
|
||||
|
||||
public override string ModifyPropertyDisplayName(string path, string propertyName)
|
||||
{
|
||||
if (m_AnimationTrack == null || !AnimatedPropertyUtility.IsMaterialProperty(propertyName))
|
||||
return propertyName;
|
||||
|
||||
var binding = m_AnimationTrack.GetBinding(TimelineEditor.inspectedDirector);
|
||||
if (binding == null)
|
||||
return propertyName;
|
||||
|
||||
var target = binding.transform;
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
target = target.Find(path);
|
||||
|
||||
if (target == null)
|
||||
return propertyName;
|
||||
|
||||
return AnimatedPropertyUtility.RemapMaterialName(target.gameObject, propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
class TrackParametersCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Track Properties");
|
||||
|
||||
readonly CurvesProxy m_CurvesProxy;
|
||||
private int m_TrackDirtyVersion;
|
||||
|
||||
public TrackParametersCurveDataSource(IRowGUI trackGui) : base(trackGui)
|
||||
{
|
||||
m_CurvesProxy = new CurvesProxy(trackGui.asset);
|
||||
}
|
||||
|
||||
public override AnimationClip animationClip
|
||||
{
|
||||
get { return m_CurvesProxy.curves; }
|
||||
}
|
||||
|
||||
public override UInt64 GetClipVersion()
|
||||
{
|
||||
return sourceAnimationClip.ClipVersion();
|
||||
}
|
||||
|
||||
public override CurveChangeType UpdateExternalChanges(ref ulong curveVersion)
|
||||
{
|
||||
if (m_CurvesProxy.targetTrack == null)
|
||||
return CurveChangeType.None;
|
||||
|
||||
var changeType = sourceAnimationClip.GetChangeType(ref curveVersion);
|
||||
if (changeType != CurveChangeType.None)
|
||||
{
|
||||
m_CurvesProxy.ApplyExternalChangesToProxy();
|
||||
}
|
||||
// track property has changed externally, update the curve proxies
|
||||
else if (m_TrackDirtyVersion != m_CurvesProxy.targetTrack.DirtyIndex)
|
||||
{
|
||||
if (changeType == CurveChangeType.None)
|
||||
changeType = CurveChangeType.CurveModified;
|
||||
m_CurvesProxy.UpdateProxyCurves();
|
||||
}
|
||||
m_TrackDirtyVersion = m_CurvesProxy.targetTrack.DirtyIndex;
|
||||
return changeType;
|
||||
}
|
||||
|
||||
public override float start
|
||||
{
|
||||
get { return 0.0f; }
|
||||
}
|
||||
|
||||
public override float timeScale
|
||||
{
|
||||
get { return 1.0f; }
|
||||
}
|
||||
|
||||
public override string groupingName
|
||||
{
|
||||
get { return k_GroupingName; }
|
||||
}
|
||||
|
||||
public override void RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
m_CurvesProxy.RemoveCurves(bindings);
|
||||
}
|
||||
|
||||
public override void ApplyCurveChanges(IEnumerable<CurveWrapper> updatedCurves)
|
||||
{
|
||||
m_CurvesProxy.UpdateCurves(updatedCurves);
|
||||
}
|
||||
|
||||
protected override void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
m_CurvesProxy.ConfigureCurveWrapper(wrapper);
|
||||
}
|
||||
|
||||
private AnimationClip sourceAnimationClip
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_CurvesProxy.targetTrack == null || m_CurvesProxy.targetTrack.curves == null)
|
||||
return null;
|
||||
return m_CurvesProxy.targetTrack.curves;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 87a1ae9719ec25d44a4dbec20ec0f892
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,39 @@
|
|||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class CurveTreeViewNode : TreeViewItem
|
||||
{
|
||||
public bool forceGroup { get; }
|
||||
public System.Type iconType { get; }
|
||||
public GUIContent iconOverlay { get; }
|
||||
|
||||
EditorCurveBinding[] m_Bindings;
|
||||
|
||||
public EditorCurveBinding[] bindings
|
||||
{
|
||||
get { return m_Bindings; }
|
||||
}
|
||||
|
||||
public CurveTreeViewNode(int id, TreeViewItem parent, string displayName, EditorCurveBinding[] bindings, bool _forceGroup = false)
|
||||
: base(id, parent != null ? parent.depth + 1 : -1, parent, displayName)
|
||||
{
|
||||
m_Bindings = bindings;
|
||||
forceGroup = _forceGroup;
|
||||
|
||||
|
||||
// capture the preview icon type. If all subbindings are the same type, use that. Otherwise use null as a default
|
||||
iconType = null;
|
||||
if (parent != null && parent.depth >= 0 && bindings != null && bindings.Length > 0 && bindings.All(b => b.type == bindings[0].type))
|
||||
{
|
||||
iconType = bindings[0].type;
|
||||
|
||||
// for components put the component type in a tooltip
|
||||
if (iconType != null && typeof(Component).IsAssignableFrom(iconType))
|
||||
iconOverlay = new GUIContent(string.Empty, ObjectNames.NicifyVariableName(iconType.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1ec45eaf72174ca189827d7896952167
|
||||
timeCreated: 1599683561
|
|
@ -0,0 +1,358 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class CurvesProxy : ICurvesOwner
|
||||
{
|
||||
public AnimationClip curves
|
||||
{
|
||||
get { return proxyCurves != null ? proxyCurves : m_OriginalOwner.curves; }
|
||||
}
|
||||
|
||||
public bool hasCurves
|
||||
{
|
||||
get { return m_IsAnimatable || m_OriginalOwner.hasCurves; }
|
||||
}
|
||||
|
||||
public double duration
|
||||
{
|
||||
get { return m_OriginalOwner.duration; }
|
||||
}
|
||||
|
||||
public string defaultCurvesName
|
||||
{
|
||||
get { return m_OriginalOwner.defaultCurvesName; }
|
||||
}
|
||||
|
||||
public UnityObject asset
|
||||
{
|
||||
get { return m_OriginalOwner.asset; }
|
||||
}
|
||||
|
||||
public UnityObject assetOwner
|
||||
{
|
||||
get { return m_OriginalOwner.assetOwner; }
|
||||
}
|
||||
|
||||
public TrackAsset targetTrack
|
||||
{
|
||||
get { return m_OriginalOwner.targetTrack; }
|
||||
}
|
||||
|
||||
readonly ICurvesOwner m_OriginalOwner;
|
||||
readonly bool m_IsAnimatable;
|
||||
readonly Dictionary<EditorCurveBinding, SerializedProperty> m_PropertiesMap = new Dictionary<EditorCurveBinding, SerializedProperty>();
|
||||
int m_ProxyIsRebuilding = 0;
|
||||
|
||||
AnimationClip m_ProxyCurves;
|
||||
AnimationClip proxyCurves
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_IsAnimatable) return null;
|
||||
|
||||
if (m_ProxyCurves == null)
|
||||
RebuildProxyCurves();
|
||||
|
||||
return m_ProxyCurves;
|
||||
}
|
||||
}
|
||||
|
||||
public CurvesProxy([NotNull] ICurvesOwner originalOwner)
|
||||
{
|
||||
m_OriginalOwner = originalOwner;
|
||||
m_IsAnimatable = originalOwner.HasAnyAnimatableParameters();
|
||||
|
||||
RebuildProxyCurves();
|
||||
}
|
||||
|
||||
public void CreateCurves(string curvesClipName)
|
||||
{
|
||||
m_OriginalOwner.CreateCurves(curvesClipName);
|
||||
}
|
||||
|
||||
public void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
var color = CurveUtility.GetPropertyColor(wrapper.binding.propertyName);
|
||||
wrapper.color = color;
|
||||
|
||||
float h, s, v;
|
||||
Color.RGBToHSV(color, out h, out s, out v);
|
||||
wrapper.wrapColorMultiplier = Color.HSVToRGB(h, s * 0.33f, v * 1.15f);
|
||||
|
||||
var curve = AnimationUtility.GetEditorCurve(proxyCurves, wrapper.binding);
|
||||
|
||||
wrapper.renderer = new NormalCurveRenderer(curve);
|
||||
|
||||
// Use curve length instead of animation clip length
|
||||
wrapper.renderer.SetCustomRange(0.0f, curve.keys.Last().time);
|
||||
}
|
||||
|
||||
public void RebuildCurves()
|
||||
{
|
||||
RebuildProxyCurves();
|
||||
}
|
||||
|
||||
public void RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
if (m_ProxyIsRebuilding > 0 || !m_OriginalOwner.hasCurves)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(m_OriginalOwner.curves, L10n.Tr("Remove Clip Curve"));
|
||||
foreach (var binding in bindings)
|
||||
AnimationUtility.SetEditorCurve(m_OriginalOwner.curves, binding, null);
|
||||
m_OriginalOwner.SanitizeCurvesData();
|
||||
RebuildProxyCurves();
|
||||
}
|
||||
|
||||
public void UpdateCurves(IEnumerable<CurveWrapper> updatedCurves)
|
||||
{
|
||||
if (m_ProxyIsRebuilding > 0)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(m_OriginalOwner.asset, L10n.Tr("Edit Clip Curve"));
|
||||
if (m_OriginalOwner.curves != null)
|
||||
Undo.RegisterCompleteObjectUndo(m_OriginalOwner.curves, L10n.Tr("Edit Clip Curve"));
|
||||
|
||||
var requireRebuild = false;
|
||||
foreach (var curve in updatedCurves)
|
||||
{
|
||||
requireRebuild |= curve.curve.length == 0;
|
||||
UpdateCurve(curve.binding, curve.curve);
|
||||
}
|
||||
|
||||
if (requireRebuild)
|
||||
m_OriginalOwner.SanitizeCurvesData();
|
||||
|
||||
AnimatedParameterUtility.UpdateSerializedPlayableAsset(m_OriginalOwner.asset);
|
||||
}
|
||||
|
||||
public void ApplyExternalChangesToProxy()
|
||||
{
|
||||
using (new RebuildGuard(this))
|
||||
{
|
||||
if (m_OriginalOwner.curves == null)
|
||||
return;
|
||||
|
||||
var curveInfo = AnimationClipCurveCache.Instance.GetCurveInfo(m_OriginalOwner.curves);
|
||||
for (int i = 0; i < curveInfo.bindings.Length; i++)
|
||||
{
|
||||
if (curveInfo.curves[i] != null && curveInfo.curves.Length != 0)
|
||||
{
|
||||
if (m_PropertiesMap.TryGetValue(curveInfo.bindings[i], out var prop) && AnimatedParameterUtility.IsParameterAnimatable(prop))
|
||||
AnimationUtility.SetEditorCurve(m_ProxyCurves, curveInfo.bindings[i], curveInfo.curves[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateCurve(EditorCurveBinding binding, AnimationCurve curve)
|
||||
{
|
||||
ApplyConstraints(binding, curve);
|
||||
|
||||
if (curve.length == 0)
|
||||
{
|
||||
HandleAllKeysDeleted(binding);
|
||||
return;
|
||||
}
|
||||
|
||||
// there is no curve in the animation clip, this is a proxy curve
|
||||
if (IsConstantCurve(binding, curve))
|
||||
HandleConstantCurveValueChanged(binding, curve);
|
||||
else
|
||||
HandleCurveUpdated(binding, curve);
|
||||
}
|
||||
|
||||
bool IsConstantCurve(EditorCurveBinding binding, AnimationCurve curve)
|
||||
{
|
||||
if (curve.length != 1)
|
||||
return false;
|
||||
return m_OriginalOwner.curves == null || AnimationUtility.GetEditorCurve(m_OriginalOwner.curves, binding) == null;
|
||||
}
|
||||
|
||||
void ApplyConstraints(EditorCurveBinding binding, AnimationCurve curve)
|
||||
{
|
||||
if (curve.length == 0)
|
||||
return;
|
||||
|
||||
var curveUpdated = false;
|
||||
|
||||
var property = m_PropertiesMap[binding];
|
||||
if (property.propertyType == SerializedPropertyType.Boolean)
|
||||
{
|
||||
TimelineAnimationUtilities.ConstrainCurveToBooleanValues(curve);
|
||||
curveUpdated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var range = AnimatedParameterUtility.GetAttributeForProperty<RangeAttribute>(property);
|
||||
if (range != null)
|
||||
{
|
||||
TimelineAnimationUtilities.ConstrainCurveToRange(curve, range.min, range.max);
|
||||
curveUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!curveUpdated)
|
||||
return;
|
||||
|
||||
using (new RebuildGuard(this))
|
||||
{
|
||||
AnimationUtility.SetEditorCurve(m_ProxyCurves, binding, curve);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleCurveUpdated(EditorCurveBinding binding, AnimationCurve updatedCurve)
|
||||
{
|
||||
if (!m_OriginalOwner.hasCurves)
|
||||
m_OriginalOwner.CreateCurves(null);
|
||||
|
||||
AnimationUtility.SetEditorCurve(m_OriginalOwner.curves, binding, updatedCurve);
|
||||
AnimationUtility.SetEditorCurve(m_ProxyCurves, binding, updatedCurve);
|
||||
}
|
||||
|
||||
void HandleConstantCurveValueChanged(EditorCurveBinding binding, AnimationCurve updatedCurve)
|
||||
{
|
||||
var prop = m_PropertiesMap[binding];
|
||||
if (prop == null)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(prop.serializedObject.targetObject, L10n.Tr("Edit Clip Curve"));
|
||||
prop.serializedObject.UpdateIfRequiredOrScript();
|
||||
CurveEditUtility.SetFromKeyValue(prop, updatedCurve.keys[0].value);
|
||||
prop.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
AnimationUtility.SetEditorCurve(m_ProxyCurves, binding, updatedCurve);
|
||||
}
|
||||
|
||||
void HandleAllKeysDeleted(EditorCurveBinding binding)
|
||||
{
|
||||
if (m_OriginalOwner.hasCurves)
|
||||
{
|
||||
// Remove curve from original asset
|
||||
AnimationUtility.SetEditorCurve(m_OriginalOwner.curves, binding, null);
|
||||
SetProxyCurve(m_PropertiesMap[binding], binding);
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildProxyCurves()
|
||||
{
|
||||
if (!m_IsAnimatable)
|
||||
return;
|
||||
|
||||
using (new RebuildGuard(this))
|
||||
{
|
||||
if (m_ProxyCurves == null)
|
||||
{
|
||||
m_ProxyCurves = new AnimationClip
|
||||
{
|
||||
legacy = true,
|
||||
name = "Constant Curves",
|
||||
hideFlags = HideFlags.HideAndDontSave,
|
||||
frameRate = m_OriginalOwner.targetTrack.timelineAsset == null
|
||||
? TimelineAsset.EditorSettings.kDefaultFps
|
||||
: m_OriginalOwner.targetTrack.timelineAsset.editorSettings.fps
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ProxyCurves.ClearCurves();
|
||||
}
|
||||
|
||||
m_OriginalOwner.SanitizeCurvesData();
|
||||
AnimatedParameterUtility.UpdateSerializedPlayableAsset(m_OriginalOwner.asset);
|
||||
var parameters = m_OriginalOwner.GetAllAnimatableParameters().ToArray();
|
||||
foreach (var param in parameters)
|
||||
CreateProxyCurve(param, m_ProxyCurves, m_OriginalOwner.asset, param.propertyPath);
|
||||
|
||||
AnimationClipCurveCache.Instance.GetCurveInfo(m_ProxyCurves).dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// updates the just the proxied values. This can be called when the asset changes, so the proxy values are properly updated
|
||||
public void UpdateProxyCurves()
|
||||
{
|
||||
if (!m_IsAnimatable || m_ProxyCurves == null || m_ProxyCurves.empty)
|
||||
return;
|
||||
|
||||
AnimatedParameterUtility.UpdateSerializedPlayableAsset(m_OriginalOwner.asset);
|
||||
var parameters = m_OriginalOwner.GetAllAnimatableParameters().ToArray();
|
||||
using (new RebuildGuard(this))
|
||||
{
|
||||
if (m_OriginalOwner.hasCurves)
|
||||
{
|
||||
var bindingInfo = AnimationClipCurveCache.Instance.GetCurveInfo(m_OriginalOwner.curves);
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
var binding = AnimatedParameterUtility.GetCurveBinding(m_OriginalOwner.asset, param.propertyPath);
|
||||
if (!bindingInfo.bindings.Contains(binding, AnimationPreviewUtilities.EditorCurveBindingComparer.Instance))
|
||||
SetProxyCurve(param, AnimatedParameterUtility.GetCurveBinding(m_OriginalOwner.asset, param.propertyPath));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var param in parameters)
|
||||
SetProxyCurve(param, AnimatedParameterUtility.GetCurveBinding(m_OriginalOwner.asset, param.propertyPath));
|
||||
}
|
||||
}
|
||||
|
||||
AnimationClipCurveCache.Instance.GetCurveInfo(m_ProxyCurves).dirty = true;
|
||||
}
|
||||
|
||||
void CreateProxyCurve(SerializedProperty prop, AnimationClip clip, UnityObject owner, string propertyName)
|
||||
{
|
||||
var binding = AnimatedParameterUtility.GetCurveBinding(owner, propertyName);
|
||||
|
||||
var originalCurve = m_OriginalOwner.hasCurves
|
||||
? AnimationUtility.GetEditorCurve(m_OriginalOwner.curves, binding)
|
||||
: null;
|
||||
|
||||
if (originalCurve != null)
|
||||
{
|
||||
AnimationUtility.SetEditorCurve(clip, binding, originalCurve);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetProxyCurve(prop, binding);
|
||||
}
|
||||
|
||||
m_PropertiesMap[binding] = prop;
|
||||
}
|
||||
|
||||
void SetProxyCurve(SerializedProperty prop, EditorCurveBinding binding)
|
||||
{
|
||||
var curve = new AnimationCurve();
|
||||
CurveEditUtility.AddKeyFrameToCurve(
|
||||
curve, 0.0f, m_ProxyCurves.frameRate, CurveEditUtility.GetKeyValue(prop),
|
||||
prop.propertyType == SerializedPropertyType.Boolean);
|
||||
AnimationUtility.SetEditorCurve(m_ProxyCurves, binding, curve);
|
||||
}
|
||||
|
||||
struct RebuildGuard : IDisposable
|
||||
{
|
||||
CurvesProxy m_Owner;
|
||||
AnimationUtility.OnCurveWasModified m_Callback;
|
||||
|
||||
public RebuildGuard(CurvesProxy owner)
|
||||
{
|
||||
m_Callback = AnimationUtility.onCurveWasModified;
|
||||
AnimationUtility.onCurveWasModified = null;
|
||||
m_Owner = owner;
|
||||
m_Owner.m_ProxyIsRebuilding++;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AnimationUtility.onCurveWasModified = m_Callback;
|
||||
m_Owner.m_ProxyIsRebuilding--;
|
||||
m_Owner = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d72ccd2c66ea846fc842adf682b11526
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,435 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngineInternal;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class TimelineAnimationUtilities
|
||||
{
|
||||
public enum OffsetEditMode
|
||||
{
|
||||
None = -1,
|
||||
Translation = 0,
|
||||
Rotation = 1
|
||||
}
|
||||
|
||||
public static bool ValidateOffsetAvailabitity(PlayableDirector director, Animator animator)
|
||||
{
|
||||
if (director == null || animator == null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static TimelineClip GetPreviousClip(TimelineClip clip)
|
||||
{
|
||||
TimelineClip previousClip = null;
|
||||
foreach (var c in clip.GetParentTrack().clips)
|
||||
{
|
||||
if (c.start < clip.start && (previousClip == null || c.start >= previousClip.start))
|
||||
previousClip = c;
|
||||
}
|
||||
return previousClip;
|
||||
}
|
||||
|
||||
public static TimelineClip GetNextClip(TimelineClip clip)
|
||||
{
|
||||
return clip.GetParentTrack().clips.Where(c => c.start > clip.start).OrderBy(c => c.start).FirstOrDefault();
|
||||
}
|
||||
|
||||
public struct RigidTransform
|
||||
{
|
||||
public Vector3 position;
|
||||
public Quaternion rotation;
|
||||
|
||||
public static RigidTransform Compose(Vector3 pos, Quaternion rot)
|
||||
{
|
||||
RigidTransform ret;
|
||||
ret.position = pos;
|
||||
ret.rotation = rot;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static RigidTransform Mul(RigidTransform a, RigidTransform b)
|
||||
{
|
||||
RigidTransform ret;
|
||||
ret.rotation = a.rotation * b.rotation;
|
||||
ret.position = a.position + a.rotation * b.position;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static RigidTransform Inverse(RigidTransform a)
|
||||
{
|
||||
RigidTransform ret;
|
||||
ret.rotation = Quaternion.Inverse(a.rotation);
|
||||
ret.position = ret.rotation * (-a.position);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static RigidTransform identity
|
||||
{
|
||||
get { return Compose(Vector3.zero, Quaternion.identity); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Matrix4x4 GetTrackMatrix(Transform transform, AnimationTrack track)
|
||||
{
|
||||
Matrix4x4 trackMatrix = Matrix4x4.TRS(track.position, track.rotation, Vector3.one);
|
||||
|
||||
// in scene off mode, the track offsets are set to the preview position which is stored in the track
|
||||
if (track.trackOffset == TrackOffset.ApplySceneOffsets)
|
||||
{
|
||||
trackMatrix = Matrix4x4.TRS(track.sceneOffsetPosition, Quaternion.Euler(track.sceneOffsetRotation), Vector3.one);
|
||||
}
|
||||
|
||||
// put the parent transform on to the track matrix
|
||||
if (transform.parent != null)
|
||||
{
|
||||
trackMatrix = transform.parent.localToWorldMatrix * trackMatrix;
|
||||
}
|
||||
|
||||
return trackMatrix;
|
||||
}
|
||||
|
||||
// Given a world space position and rotation, updates the clip offsets to match that
|
||||
public static RigidTransform UpdateClipOffsets(AnimationPlayableAsset asset, AnimationTrack track, Transform transform, Vector3 globalPosition, Quaternion globalRotation)
|
||||
{
|
||||
Matrix4x4 worldToLocal = transform.worldToLocalMatrix;
|
||||
Matrix4x4 clipMatrix = Matrix4x4.TRS(asset.position, asset.rotation, Vector3.one);
|
||||
Matrix4x4 trackMatrix = GetTrackMatrix(transform, track);
|
||||
|
||||
|
||||
// Use the transform to find the proper goal matrix with scale taken into account
|
||||
var oldPos = transform.position;
|
||||
var oldRot = transform.rotation;
|
||||
transform.position = globalPosition;
|
||||
transform.rotation = globalRotation;
|
||||
Matrix4x4 goal = transform.localToWorldMatrix;
|
||||
transform.position = oldPos;
|
||||
transform.rotation = oldRot;
|
||||
|
||||
// compute the new clip matrix.
|
||||
Matrix4x4 newClip = trackMatrix.inverse * goal * worldToLocal * trackMatrix * clipMatrix;
|
||||
return RigidTransform.Compose(newClip.GetColumn(3), MathUtils.QuaternionFromMatrix(newClip));
|
||||
}
|
||||
|
||||
public static RigidTransform GetTrackOffsets(AnimationTrack track, Transform transform)
|
||||
{
|
||||
Vector3 position = track.position;
|
||||
Quaternion rotation = track.rotation;
|
||||
if (transform != null && transform.parent != null)
|
||||
{
|
||||
position = transform.parent.TransformPoint(position);
|
||||
rotation = transform.parent.rotation * rotation;
|
||||
MathUtils.QuaternionNormalize(ref rotation);
|
||||
}
|
||||
|
||||
return RigidTransform.Compose(position, rotation);
|
||||
}
|
||||
|
||||
public static void UpdateTrackOffset(AnimationTrack track, Transform transform, RigidTransform offsets)
|
||||
{
|
||||
if (transform != null && transform.parent != null)
|
||||
{
|
||||
offsets.position = transform.parent.InverseTransformPoint(offsets.position);
|
||||
offsets.rotation = Quaternion.Inverse(transform.parent.rotation) * offsets.rotation;
|
||||
MathUtils.QuaternionNormalize(ref offsets.rotation);
|
||||
}
|
||||
|
||||
track.position = offsets.position;
|
||||
track.eulerAngles = AnimationUtility.GetClosestEuler(offsets.rotation, track.eulerAngles, RotationOrder.OrderZXY);
|
||||
track.UpdateClipOffsets();
|
||||
}
|
||||
|
||||
static MatchTargetFields GetMatchFields(TimelineClip clip)
|
||||
{
|
||||
var track = clip.GetParentTrack() as AnimationTrack;
|
||||
if (track == null)
|
||||
return MatchTargetFieldConstants.None;
|
||||
|
||||
var asset = clip.asset as AnimationPlayableAsset;
|
||||
var fields = track.matchTargetFields;
|
||||
if (asset != null && !asset.useTrackMatchFields)
|
||||
fields = asset.matchTargetFields;
|
||||
return fields;
|
||||
}
|
||||
|
||||
static void WriteMatchFields(AnimationPlayableAsset asset, RigidTransform result, MatchTargetFields fields)
|
||||
{
|
||||
Vector3 position = asset.position;
|
||||
|
||||
position.x = fields.HasAny(MatchTargetFields.PositionX) ? result.position.x : position.x;
|
||||
position.y = fields.HasAny(MatchTargetFields.PositionY) ? result.position.y : position.y;
|
||||
position.z = fields.HasAny(MatchTargetFields.PositionZ) ? result.position.z : position.z;
|
||||
|
||||
asset.position = position;
|
||||
|
||||
// check first to avoid unnecessary conversion errors
|
||||
if (fields.HasAny(MatchTargetFieldConstants.Rotation))
|
||||
{
|
||||
Vector3 eulers = asset.eulerAngles;
|
||||
Vector3 resultEulers = result.rotation.eulerAngles;
|
||||
|
||||
eulers.x = fields.HasAny(MatchTargetFields.RotationX) ? resultEulers.x : eulers.x;
|
||||
eulers.y = fields.HasAny(MatchTargetFields.RotationY) ? resultEulers.y : eulers.y;
|
||||
eulers.z = fields.HasAny(MatchTargetFields.RotationZ) ? resultEulers.z : eulers.z;
|
||||
|
||||
asset.eulerAngles = AnimationUtility.GetClosestEuler(Quaternion.Euler(eulers), asset.eulerAngles, RotationOrder.OrderZXY);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MatchPrevious(TimelineClip currentClip, Transform matchPoint, PlayableDirector director)
|
||||
{
|
||||
const double timeEpsilon = 0.00001;
|
||||
MatchTargetFields matchFields = GetMatchFields(currentClip);
|
||||
if (matchFields == MatchTargetFieldConstants.None || matchPoint == null)
|
||||
return;
|
||||
|
||||
double cachedTime = director.time;
|
||||
|
||||
// finds previous clip
|
||||
TimelineClip previousClip = GetPreviousClip(currentClip);
|
||||
if (previousClip == null || currentClip == previousClip)
|
||||
return;
|
||||
|
||||
// make sure the transform is properly updated before modifying the graph
|
||||
director.Evaluate();
|
||||
|
||||
var parentTrack = currentClip.GetParentTrack() as AnimationTrack;
|
||||
|
||||
var blendIn = currentClip.blendInDuration;
|
||||
currentClip.blendInDuration = 0;
|
||||
var blendOut = previousClip.blendOutDuration;
|
||||
previousClip.blendOutDuration = 0;
|
||||
|
||||
//evaluate previous without current
|
||||
parentTrack.RemoveClip(currentClip);
|
||||
director.RebuildGraph();
|
||||
double previousEndTime = currentClip.start > previousClip.end ? previousClip.end : currentClip.start;
|
||||
director.time = previousEndTime - timeEpsilon;
|
||||
director.Evaluate(); // add port to evaluate only track
|
||||
|
||||
var targetPosition = matchPoint.position;
|
||||
var targetRotation = matchPoint.rotation;
|
||||
|
||||
// evaluate current without previous
|
||||
parentTrack.AddClip(currentClip);
|
||||
parentTrack.RemoveClip(previousClip);
|
||||
director.RebuildGraph();
|
||||
director.time = currentClip.start + timeEpsilon;
|
||||
director.Evaluate();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//compute offsets
|
||||
|
||||
var animationPlayable = currentClip.asset as AnimationPlayableAsset;
|
||||
var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation);
|
||||
WriteMatchFields(animationPlayable, match, matchFields);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
currentClip.blendInDuration = blendIn;
|
||||
previousClip.blendOutDuration = blendOut;
|
||||
|
||||
parentTrack.AddClip(previousClip);
|
||||
director.RebuildGraph();
|
||||
director.time = cachedTime;
|
||||
director.Evaluate();
|
||||
}
|
||||
|
||||
public static void MatchNext(TimelineClip currentClip, Transform matchPoint, PlayableDirector director)
|
||||
{
|
||||
const double timeEpsilon = 0.00001;
|
||||
MatchTargetFields matchFields = GetMatchFields(currentClip);
|
||||
if (matchFields == MatchTargetFieldConstants.None || matchPoint == null)
|
||||
return;
|
||||
|
||||
double cachedTime = director.time;
|
||||
|
||||
// finds next clip
|
||||
TimelineClip nextClip = GetNextClip(currentClip);
|
||||
if (nextClip == null || currentClip == nextClip)
|
||||
return;
|
||||
|
||||
// make sure the transform is properly updated before modifying the graph
|
||||
director.Evaluate();
|
||||
|
||||
var parentTrack = currentClip.GetParentTrack() as AnimationTrack;
|
||||
|
||||
var blendOut = currentClip.blendOutDuration;
|
||||
var blendIn = nextClip.blendInDuration;
|
||||
currentClip.blendOutDuration = 0;
|
||||
nextClip.blendInDuration = 0;
|
||||
|
||||
//evaluate previous without current
|
||||
parentTrack.RemoveClip(currentClip);
|
||||
director.RebuildGraph();
|
||||
director.time = nextClip.start + timeEpsilon;
|
||||
director.Evaluate(); // add port to evaluate only track
|
||||
|
||||
var targetPosition = matchPoint.position;
|
||||
var targetRotation = matchPoint.rotation;
|
||||
|
||||
// evaluate current without next
|
||||
parentTrack.AddClip(currentClip);
|
||||
parentTrack.RemoveClip(nextClip);
|
||||
director.RebuildGraph();
|
||||
director.time = Math.Min(nextClip.start, currentClip.end - timeEpsilon);
|
||||
director.Evaluate();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//compute offsets
|
||||
|
||||
var animationPlayable = currentClip.asset as AnimationPlayableAsset;
|
||||
var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation);
|
||||
WriteMatchFields(animationPlayable, match, matchFields);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
currentClip.blendOutDuration = blendOut;
|
||||
nextClip.blendInDuration = blendIn;
|
||||
|
||||
parentTrack.AddClip(nextClip);
|
||||
director.RebuildGraph();
|
||||
director.time = cachedTime;
|
||||
director.Evaluate();
|
||||
}
|
||||
|
||||
public static TimelineWindowTimeControl CreateTimeController(TimelineClip clip)
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>();
|
||||
timeController.Init(animationWindow.state, clip);
|
||||
return timeController;
|
||||
}
|
||||
|
||||
public static TimelineWindowTimeControl CreateTimeController(TimelineWindowTimeControl.ClipData clipData)
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>();
|
||||
timeController.Init(animationWindow.state, clipData);
|
||||
return timeController;
|
||||
}
|
||||
|
||||
public static void EditAnimationClipWithTimeController(AnimationClip animationClip, TimelineWindowTimeControl timeController, Object sourceObject)
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
animationWindow.EditSequencerClip(animationClip, sourceObject, timeController);
|
||||
}
|
||||
|
||||
public static void UnlinkAnimationWindowFromTracks(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
var clips = new List<AnimationClip>();
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
var animationTrack = track as AnimationTrack;
|
||||
if (animationTrack != null && animationTrack.infiniteClip != null)
|
||||
clips.Add(animationTrack.infiniteClip);
|
||||
|
||||
GetAnimationClips(track.GetClips(), clips);
|
||||
}
|
||||
UnlinkAnimationWindowFromAnimationClips(clips);
|
||||
}
|
||||
|
||||
public static void UnlinkAnimationWindowFromClips(IEnumerable<TimelineClip> timelineClips)
|
||||
{
|
||||
var clips = new List<AnimationClip>();
|
||||
GetAnimationClips(timelineClips, clips);
|
||||
UnlinkAnimationWindowFromAnimationClips(clips);
|
||||
}
|
||||
|
||||
public static void UnlinkAnimationWindowFromAnimationClips(ICollection<AnimationClip> clips)
|
||||
{
|
||||
if (clips.Count == 0)
|
||||
return;
|
||||
|
||||
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow));
|
||||
foreach (var animWindow in windows.OfType<AnimationWindow>())
|
||||
{
|
||||
if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer && clips.Contains(animWindow.state.activeAnimationClip))
|
||||
animWindow.UnlinkSequencer();
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnlinkAnimationWindow()
|
||||
{
|
||||
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow));
|
||||
foreach (var animWindow in windows.OfType<AnimationWindow>())
|
||||
{
|
||||
if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer)
|
||||
animWindow.UnlinkSequencer();
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetAnimationClips(IEnumerable<TimelineClip> timelineClips, List<AnimationClip> clips)
|
||||
{
|
||||
foreach (var timelineClip in timelineClips)
|
||||
{
|
||||
if (timelineClip.curves != null)
|
||||
clips.Add(timelineClip.curves);
|
||||
AnimationPlayableAsset apa = timelineClip.asset as AnimationPlayableAsset;
|
||||
if (apa != null && apa.clip != null)
|
||||
clips.Add(apa.clip);
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetAnimationWindowCurrentFrame()
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
if (animationWindow)
|
||||
return animationWindow.state.currentFrame;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void SetAnimationWindowCurrentFrame(int frame)
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
if (animationWindow)
|
||||
animationWindow.state.currentFrame = frame;
|
||||
}
|
||||
|
||||
public static void ConstrainCurveToBooleanValues(AnimationCurve curve)
|
||||
{
|
||||
// Clamp the values first
|
||||
var keys = curve.keys;
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
{
|
||||
var key = keys[i];
|
||||
key.value = key.value < 0.5f ? 0.0f : 1.0f;
|
||||
keys[i] = key;
|
||||
}
|
||||
curve.keys = keys;
|
||||
|
||||
// Update the tangents once all the values are clamped
|
||||
for (var i = 0; i < curve.length; i++)
|
||||
{
|
||||
AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Constant);
|
||||
AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Constant);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ConstrainCurveToRange(AnimationCurve curve, float minValue, float maxValue)
|
||||
{
|
||||
var keys = curve.keys;
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
{
|
||||
var key = keys[i];
|
||||
key.value = Mathf.Clamp(key.value, minValue, maxValue);
|
||||
keys[i] = key;
|
||||
}
|
||||
curve.keys = keys;
|
||||
}
|
||||
|
||||
public static bool IsAnimationClip(TimelineClip clip)
|
||||
{
|
||||
return clip != null && (clip.asset as AnimationPlayableAsset) != null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9685354eb873b8d4699078b307b0f260
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue