Initial Commit

This commit is contained in:
Sebastian Cabrera 2021-08-02 05:44:37 -04:00
parent 53eb92e9af
commit 270ab7d11f
15341 changed files with 700234 additions and 0 deletions

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bf22284ca28e7ef4490033b61e9b52cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 07a967d2fca95324f8922df8394a5655
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c3b87750f9f94e8c918b71ec7d369bd4
timeCreated: 1602859563

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9ace5095cc37ed849b52109d2ee305d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f7fed0d9d0f7a7f41a8525aa79e790b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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; }
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d4553f2006f48b6448553cb525d2876e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c171b9ca03610ea4faa426e082a1075d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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()
};
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9c2177aaf0fde92439246adc2dc0bfa2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c09dc5cd0a70cf40856b7d406106ee1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d49b2ed20045e034f9cdf6a6d95e6183
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 87a1ae9719ec25d44a4dbec20ec0f892
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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));
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1ec45eaf72174ca189827d7896952167
timeCreated: 1599683561

View file

@ -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;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d72ccd2c66ea846fc842adf682b11526
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9685354eb873b8d4699078b307b0f260
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: