mirror of
https://gitgud.io/AbstractConcept/rimworld-animation-studio.git
synced 2024-08-15 00:43:27 +00:00
Initial commit
This commit is contained in:
commit
3c7cc0c973
8391 changed files with 704313 additions and 0 deletions
|
@ -0,0 +1,96 @@
|
|||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
using ClipAction = UnityEditor.Timeline.ItemAction<UnityEngine.Timeline.TimelineClip>;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[MenuEntry("Match Offsets To Previous Clip", MenuOrder.CustomClipAction.AnimClipMatchPrevious), UsedImplicitly]
|
||||
class MatchOffsetsPreviousAction : ClipAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
AnimationOffsetMenu.MatchClipsToPrevious(state, items.Where(x => IsValidClip(x, TimelineEditor.inspectedDirector)).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidClip(TimelineClip clip, PlayableDirector director)
|
||||
{
|
||||
return clip != null &&
|
||||
clip.parentTrack != null &&
|
||||
(clip.asset as AnimationPlayableAsset) != null &&
|
||||
clip.parentTrack.clips.Any(x => x.start < clip.start) &&
|
||||
TimelineUtility.GetSceneGameObject(director, clip.parentTrack) != null;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
if (!items.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
var director = TimelineEditor.inspectedDirector;
|
||||
if (TimelineEditor.inspectedDirector == null)
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (items.Any(c => IsValidClip(c, director)))
|
||||
return MenuActionDisplayState.Visible;
|
||||
|
||||
return MenuActionDisplayState.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Match Offsets To Next Clip", MenuOrder.CustomClipAction.AnimClipMatchNext), UsedImplicitly]
|
||||
class MatchOffsetsNextAction : ClipAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
AnimationOffsetMenu.MatchClipsToNext(state, items.Where(x => IsValidClip(x, TimelineEditor.inspectedDirector)).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidClip(TimelineClip clip, PlayableDirector director)
|
||||
{
|
||||
return clip != null &&
|
||||
clip.parentTrack != null &&
|
||||
(clip.asset as AnimationPlayableAsset) != null &&
|
||||
clip.parentTrack.clips.Any(x => x.start > clip.start) &&
|
||||
TimelineUtility.GetSceneGameObject(director, clip.parentTrack) != null;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
if (!items.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
var director = TimelineEditor.inspectedDirector;
|
||||
if (TimelineEditor.inspectedDirector == null)
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (items.Any(c => IsValidClip(c, director)))
|
||||
return MenuActionDisplayState.Visible;
|
||||
|
||||
return MenuActionDisplayState.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Reset Offsets", MenuOrder.CustomClipAction.AnimClipResetOffset), UsedImplicitly]
|
||||
class ResetOffsets : ClipAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
AnimationOffsetMenu.ResetClipOffsets(state, items.Where(TimelineAnimationUtilities.IsAnimationClip).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
if (!items.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bf22284ca28e7ef4490033b61e9b52cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,436 @@
|
|||
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)
|
||||
{
|
||||
if (modification == AnimationUtility.CurveModifiedType.CurveDeleted)
|
||||
{
|
||||
m_ClipCache.Remove(clip);
|
||||
}
|
||||
else
|
||||
{
|
||||
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,82 @@
|
|||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class AnimationOffsetMenu
|
||||
{
|
||||
public static GUIContent MatchPreviousMenuItem = EditorGUIUtility.TrTextContent("Match Offsets To Previous Clip");
|
||||
public static GUIContent MatchNextMenuItem = EditorGUIUtility.TrTextContent("Match Offsets To Next Clip");
|
||||
public static string MatchFieldsPrefix = "Match Offsets Fields/";
|
||||
public static GUIContent ResetOffsetMenuItem = EditorGUIUtility.TrTextContent("Reset Offsets");
|
||||
|
||||
static bool EnforcePreviewMode(WindowState state)
|
||||
{
|
||||
state.previewMode = true; // try and set the preview mode
|
||||
if (!state.previewMode)
|
||||
{
|
||||
Debug.LogError("Match clips cannot be completed because preview mode cannot be enabed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void MatchClipsToPrevious(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
if (!EnforcePreviewMode(state))
|
||||
return;
|
||||
|
||||
clips = clips.OrderBy(x => x.start).ToArray();
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var sceneObject = TimelineUtility.GetSceneGameObject(state.editSequence.director, clip.parentTrack);
|
||||
if (sceneObject != null)
|
||||
{
|
||||
TimelineUndo.PushUndo(clip.asset, "Match Clip");
|
||||
TimelineAnimationUtilities.MatchPrevious(clip, sceneObject.transform, state.editSequence.director);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
internal static void MatchClipsToNext(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
if (!EnforcePreviewMode(state))
|
||||
return;
|
||||
|
||||
clips = clips.OrderByDescending(x => x.start).ToArray();
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var sceneObject = TimelineUtility.GetSceneGameObject(state.editSequence.director, clip.parentTrack);
|
||||
if (sceneObject != null)
|
||||
{
|
||||
TimelineUndo.PushUndo(clip.asset, "Match Clip");
|
||||
TimelineAnimationUtilities.MatchNext(clip, sceneObject.transform, state.editSequence.director);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
public static void ResetClipOffsets(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip.asset is AnimationPlayableAsset)
|
||||
{
|
||||
TimelineUndo.PushUndo(clip.asset, "Reset Offsets");
|
||||
var playableAsset = (AnimationPlayableAsset)clip.asset;
|
||||
playableAsset.ResetOffsets();
|
||||
}
|
||||
}
|
||||
state.rebuildGraph = true;
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.SceneNeedsUpdate);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = LocalizationDatabase.GetLocalizedString("No animation clip assigned");
|
||||
public static readonly string k_LegacyClipError = LocalizationDatabase.GetLocalizedString("Legacy animation clips are not supported");
|
||||
static readonly string k_MotionCurveError = LocalizationDatabase.GetLocalizedString("You are using motion curves without applyRootMotion enabled on the Animator. The root transform will not be animated");
|
||||
static readonly string k_RootCurveError = LocalizationDatabase.GetLocalizedString("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.parentTrack 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.ComponentModel;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[MenuEntry("Add Override Track", MenuOrder.CustomTrackAction.AnimAddOverrideTrack), UsedImplicitly]
|
||||
class AddOverrideTrackAction : TrackAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
{
|
||||
TimelineHelpers.CreateTrack(typeof(AnimationTrack), animTrack, "Override " + animTrack.GetChildTracks().Count());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(t => t.isSubTrack || !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return MenuActionDisplayState.Disabled;
|
||||
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Convert To Clip Track", MenuOrder.CustomTrackAction.AnimConvertToClipMode), UsedImplicitly]
|
||||
class ConvertToClipModeAction : TrackAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
animTrack.ConvertToClipMode();
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return MenuActionDisplayState.Disabled;
|
||||
|
||||
if (tracks.OfType<AnimationTrack>().All(a => a.CanConvertToClipMode()))
|
||||
return MenuActionDisplayState.Visible;
|
||||
|
||||
return MenuActionDisplayState.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Convert To Infinite Clip", MenuOrder.CustomTrackAction.AnimConvertFromClipMode), UsedImplicitly]
|
||||
class ConvertFromClipTrackAction : TrackAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
animTrack.ConvertFromClipMode(state.editSequence.asset);
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return MenuActionDisplayState.Disabled;
|
||||
|
||||
if (tracks.OfType<AnimationTrack>().All(a => a.CanConvertFromClipMode()))
|
||||
return MenuActionDisplayState.Visible;
|
||||
|
||||
return MenuActionDisplayState.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TrackOffsetBaseAction : TrackAction
|
||||
{
|
||||
public abstract TrackOffset trackOffset { get; }
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return MenuActionDisplayState.Disabled;
|
||||
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
protected override bool IsChecked(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
return tracks.OfType<AnimationTrack>().All(t => t.trackOffset == trackOffset);
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
{
|
||||
state.UnarmForRecord(animTrack);
|
||||
TimelineUndo.PushUndo(animTrack, "Set Transform Offsets");
|
||||
animTrack.trackOffset = trackOffset;
|
||||
}
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[MenuEntry("Track Offsets/Apply Transform Offsets", MenuOrder.CustomTrackAction.AnimApplyTrackOffset), UsedImplicitly]
|
||||
class ApplyTransformOffsetAction : TrackOffsetBaseAction
|
||||
{
|
||||
public override TrackOffset trackOffset
|
||||
{
|
||||
get { return TrackOffset.ApplyTransformOffsets; }
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Track Offsets/Apply Scene Offsets", MenuOrder.CustomTrackAction.AnimApplySceneOffset), UsedImplicitly]
|
||||
class ApplySceneOffsetAction : TrackOffsetBaseAction
|
||||
{
|
||||
public override TrackOffset trackOffset
|
||||
{
|
||||
get { return TrackOffset.ApplySceneOffsets; }
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Track Offsets/Auto (Deprecated)", MenuOrder.CustomTrackAction.AnimApplyAutoOffset), UsedImplicitly]
|
||||
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,224 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
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 bool selectable { get { return true; } }
|
||||
|
||||
public object selectableObject
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
public bool selected
|
||||
{
|
||||
get { return m_PartOfSelection; }
|
||||
set
|
||||
{
|
||||
m_PartOfSelection = value;
|
||||
|
||||
if (!m_PartOfSelection)
|
||||
{
|
||||
m_DopeLines.index = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Delete(WindowState state)
|
||||
{
|
||||
// we dont support deleting the summary
|
||||
if (m_DopeLines.index < 1)
|
||||
return;
|
||||
|
||||
if (m_CurveDataSource == null)
|
||||
return;
|
||||
|
||||
var clip = m_CurveDataSource.animationClip;
|
||||
if (clip == null)
|
||||
return;
|
||||
|
||||
int curveIndexToDelete = m_DopeLines.index - 1;
|
||||
var bindings = AnimationUtility.GetCurveBindings(clip);
|
||||
|
||||
if (curveIndexToDelete >= bindings.Length)
|
||||
return;
|
||||
|
||||
TimelineUndo.PushUndo(clip, "Delete Curve");
|
||||
AnimationUtility.SetEditorCurve(clip, bindings[m_DopeLines.index - 1], null);
|
||||
state.rebuildGraph = true;
|
||||
}
|
||||
|
||||
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 List<EditorCurveBinding>();
|
||||
foreach (int s in m_Selection)
|
||||
{
|
||||
var item = (CurveTreeViewNode)m_TreeView.FindItem(s);
|
||||
if (item != null && item.bindings != null)
|
||||
bindings.AddRange(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,139 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditorInternal
|
||||
{
|
||||
class BindingTreeViewDataSource : TreeViewDataSource
|
||||
{
|
||||
public const int RootID = int.MinValue;
|
||||
public const int GroupID = -1;
|
||||
|
||||
AnimationClip m_Clip;
|
||||
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);
|
||||
}
|
||||
|
||||
static string GroupName(EditorCurveBinding binding)
|
||||
{
|
||||
string property = AnimationWindowUtility.NicifyPropertyGroupName(binding.type, binding.propertyName);
|
||||
if (!string.IsNullOrEmpty(binding.path))
|
||||
{
|
||||
property = binding.path + " : " + property;
|
||||
}
|
||||
|
||||
int lastArrayIdx = property.LastIndexOf("Array.");
|
||||
if (lastArrayIdx != -1)
|
||||
{
|
||||
property = property.Substring(0, lastArrayIdx - 1);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
|
||||
static string PropertyName(EditorCurveBinding binding, string arrayPrefixToRemove = "")
|
||||
{
|
||||
string propertyName = AnimationWindowUtility.GetPropertyDisplayName(binding.propertyName);
|
||||
if (propertyName.Contains("Array"))
|
||||
{
|
||||
propertyName = propertyName.Replace("Array.", "");
|
||||
propertyName = propertyName.Replace(arrayPrefixToRemove, "");
|
||||
propertyName = propertyName.TrimStart('.');
|
||||
}
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
public override void FetchData()
|
||||
{
|
||||
if (m_Clip == null)
|
||||
return;
|
||||
|
||||
var bindings = AnimationUtility.GetCurveBindings(m_Clip)
|
||||
.Union(AnimationUtility.GetObjectReferenceCurveBindings(m_Clip))
|
||||
.ToArray();
|
||||
|
||||
var results = bindings.GroupBy(p => GroupName(p), p => p, (key, g) => new
|
||||
{
|
||||
parent = key,
|
||||
bindings = g.ToList()
|
||||
}).OrderBy(t =>
|
||||
{
|
||||
//Force transform order first
|
||||
if (t.parent == "Position") return -3;
|
||||
if (t.parent == "Rotation") return -2;
|
||||
if (t.parent == "Scale") return -1;
|
||||
return 0;
|
||||
}).ThenBy(t => t.parent);
|
||||
|
||||
m_RootItem = new CurveTreeViewNode(RootID, null, "root", null)
|
||||
{
|
||||
children = new List<TreeViewItem>(1)
|
||||
};
|
||||
|
||||
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 newNode = new CurveTreeViewNode(r.parent.GetHashCode(), groupingNode, r.parent, r.bindings.ToArray());
|
||||
groupingNode.children.Add(newNode);
|
||||
if (r.bindings.Count > 1)
|
||||
{
|
||||
for (int b = 0; b < r.bindings.Count; b++)
|
||||
{
|
||||
if (newNode.children == null)
|
||||
newNode.children = new List<TreeViewItem>();
|
||||
|
||||
var binding = r.bindings[b];
|
||||
var bindingNode = new CurveTreeViewNode(binding.GetHashCode(), newNode, PropertyName(binding, newNode.displayName), new[] {binding});
|
||||
newNode.children.Add(bindingNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetupRootNodeSettings();
|
||||
m_NeedRefreshRows = true;
|
||||
}
|
||||
|
||||
public void UpdateData()
|
||||
{
|
||||
m_TreeView.ReloadData();
|
||||
}
|
||||
}
|
||||
|
||||
class CurveTreeViewNode : TreeViewItem
|
||||
{
|
||||
EditorCurveBinding[] m_Bindings;
|
||||
|
||||
public EditorCurveBinding[] bindings
|
||||
{
|
||||
get { return m_Bindings; }
|
||||
}
|
||||
|
||||
public CurveTreeViewNode(int id, TreeViewItem parent, string displayName, EditorCurveBinding[] bindings)
|
||||
: base(id, parent != null ? parent.depth + 1 : -1, parent, displayName)
|
||||
{
|
||||
m_Bindings = bindings;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9c2177aaf0fde92439246adc2dc0bfa2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,80 @@
|
|||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditorInternal
|
||||
{
|
||||
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);
|
||||
|
||||
public BindingTreeViewGUI(TreeViewController treeView)
|
||||
: base(treeView, true)
|
||||
{
|
||||
k_IconWidth = 13.0f;
|
||||
}
|
||||
|
||||
public override void OnRowGUI(Rect rowRect, TreeViewItem node, int row, bool selected, bool focused)
|
||||
{
|
||||
Color originalColor = GUI.color;
|
||||
GUI.color = node.parent == null ||
|
||||
node.parent.id == BindingTreeViewDataSource.RootID ||
|
||||
node.parent.id == BindingTreeViewDataSource.GroupID ?
|
||||
Color.white :
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (node.bindings == null || node.bindings.Length == 0)
|
||||
return null;
|
||||
|
||||
return AssetPreview.GetMiniTypeThumbnail(node.bindings[0].type);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3c09dc5cd0a70cf40856b7d406106ee1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,342 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
class ClipCurveEditor
|
||||
{
|
||||
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;
|
||||
int m_LastClipVersion = -1;
|
||||
int m_LastCurveCount = -1;
|
||||
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; }
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
AnimationClip clip = m_DataSource.animationClip;
|
||||
|
||||
// something changed, manage the undo properly.
|
||||
Undo.RegisterCompleteObjectUndo(clip, "Edit Clip Curve");
|
||||
|
||||
foreach (CurveWrapper c in curvesToUpdate)
|
||||
{
|
||||
AnimationUtility.SetEditorCurve(clip, c.binding, c.curve);
|
||||
c.changed = false;
|
||||
}
|
||||
|
||||
m_DataSource.UpdateCurves(curvesToUpdate);
|
||||
}
|
||||
|
||||
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));
|
||||
GUILayout.EndScrollView();
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
class FrameFormatCurveEditorState : ICurveEditorState
|
||||
{
|
||||
public TimeArea.TimeFormat timeFormat
|
||||
{
|
||||
get { return TimeArea.TimeFormat.Frame; }
|
||||
}
|
||||
public Vector2 timeRange { get { return new Vector2(0, 1); } }
|
||||
public bool rippleTime { get { return false; } }
|
||||
}
|
||||
|
||||
class UnformattedCurveEditorState : ICurveEditorState
|
||||
{
|
||||
public TimeArea.TimeFormat timeFormat
|
||||
{
|
||||
get { return TimeArea.TimeFormat.None; }
|
||||
}
|
||||
public Vector2 timeRange { get { return new Vector2(0, 1); } }
|
||||
public bool rippleTime { get { return false; } }
|
||||
}
|
||||
|
||||
void UpdateCurveEditorIfNeeded(WindowState state)
|
||||
{
|
||||
if ((Event.current.type != EventType.Layout) || (m_DataSource == null) || (m_BindingHierarchy == null) || (m_DataSource.animationClip == null))
|
||||
return;
|
||||
|
||||
AnimationClipCurveInfo curveInfo = AnimationClipCurveCache.Instance.GetCurveInfo(m_DataSource.animationClip);
|
||||
int version = curveInfo.version;
|
||||
if (version != m_LastClipVersion)
|
||||
{
|
||||
// tree has changed
|
||||
if (m_LastCurveCount != curveInfo.curves.Length)
|
||||
{
|
||||
m_BindingHierarchy.RefreshTree();
|
||||
m_LastCurveCount = curveInfo.curves.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
// update just the curves
|
||||
m_BindingHierarchy.RefreshCurves();
|
||||
}
|
||||
m_LastClipVersion = version;
|
||||
}
|
||||
|
||||
if (state.timeInFrames)
|
||||
m_CurveEditor.state = new FrameFormatCurveEditorState();
|
||||
else
|
||||
m_CurveEditor.state = new UnformattedCurveEditorState();
|
||||
|
||||
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,272 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
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 IRowGUI trackGUI { get { return 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; }
|
||||
public virtual void UpdateCurves(List<CurveWrapper> updatedCurves) {}
|
||||
public virtual void RebuildCurves() {} // Only necessary when using proxies
|
||||
|
||||
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(List<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());
|
||||
}
|
||||
}
|
||||
|
||||
class ClipParametersCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Clip Properties");
|
||||
|
||||
readonly TimelineClipGUI m_ClipGUI;
|
||||
readonly CurvesProxy m_CurvesProxy;
|
||||
|
||||
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 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 UpdateCurves(List<CurveWrapper> updatedCurves)
|
||||
{
|
||||
m_CurvesProxy.UpdateCurves(updatedCurves);
|
||||
}
|
||||
|
||||
public override void RebuildCurves()
|
||||
{
|
||||
m_CurvesProxy.RebuildCurves();
|
||||
}
|
||||
|
||||
protected override void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
m_CurvesProxy.ConfigureCurveWrapper(wrapper);
|
||||
}
|
||||
|
||||
protected override int CreateHashCode()
|
||||
{
|
||||
return base.CreateHashCode().CombineHash(m_ClipGUI.clip.GetHashCode());
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
class TrackParametersCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Track Properties");
|
||||
|
||||
readonly CurvesProxy m_CurvesProxy;
|
||||
|
||||
public TrackParametersCurveDataSource(IRowGUI trackGui) : base(trackGui)
|
||||
{
|
||||
m_CurvesProxy = new CurvesProxy(trackGui.asset);
|
||||
}
|
||||
|
||||
public override AnimationClip animationClip
|
||||
{
|
||||
get { return m_CurvesProxy.curves; }
|
||||
}
|
||||
|
||||
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 UpdateCurves(List<CurveWrapper> updatedCurves)
|
||||
{
|
||||
m_CurvesProxy.UpdateCurves(updatedCurves);
|
||||
}
|
||||
|
||||
public override void RebuildCurves()
|
||||
{
|
||||
m_CurvesProxy.RebuildCurves();
|
||||
}
|
||||
|
||||
protected override void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
m_CurvesProxy.ConfigureCurveWrapper(wrapper);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 87a1ae9719ec25d44a4dbec20ec0f892
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,302 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
List<SerializedProperty> m_AllAnimatableParameters;
|
||||
List<SerializedProperty> allAnimatableParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
var so = AnimatedParameterUtility.GetSerializedPlayableAsset(m_OriginalOwner.asset);
|
||||
if (so == null)
|
||||
return null;
|
||||
|
||||
so.UpdateIfRequiredOrScript();
|
||||
|
||||
if (m_AllAnimatableParameters == null)
|
||||
m_AllAnimatableParameters = m_OriginalOwner.GetAllAnimatableParameters().ToList();
|
||||
|
||||
return m_AllAnimatableParameters;
|
||||
}
|
||||
}
|
||||
|
||||
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 UpdateCurves(List<CurveWrapper> updatedCurves)
|
||||
{
|
||||
if (m_ProxyIsRebuilding > 0)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(m_OriginalOwner.asset, "Edit Clip Curve");
|
||||
|
||||
if (m_OriginalOwner.curves != null)
|
||||
Undo.RegisterCompleteObjectUndo(m_OriginalOwner.curves, "Edit Clip Curve");
|
||||
|
||||
foreach (var curve in updatedCurves)
|
||||
{
|
||||
UpdateCurve(curve.binding, curve.curve);
|
||||
}
|
||||
|
||||
AnimatedParameterUtility.UpdateSerializedPlayableAsset(m_OriginalOwner.asset);
|
||||
}
|
||||
|
||||
void UpdateCurve(EditorCurveBinding binding, AnimationCurve curve)
|
||||
{
|
||||
ApplyConstraints(binding, curve);
|
||||
|
||||
if (curve.length == 0)
|
||||
{
|
||||
HandleAllKeysDeleted(binding);
|
||||
}
|
||||
else if (curve.length == 1)
|
||||
{
|
||||
HandleConstantCurveValueChanged(binding, curve);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleCurveUpdated(binding, curve);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void HandleConstantCurveValueChanged(EditorCurveBinding binding, AnimationCurve updatedCurve)
|
||||
{
|
||||
var prop = m_PropertiesMap[binding];
|
||||
if (prop == null)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(prop.serializedObject.targetObject, "Edit Clip Curve");
|
||||
prop.serializedObject.UpdateIfRequiredOrScript();
|
||||
CurveEditUtility.SetFromKeyValue(prop, updatedCurve.keys[0].value);
|
||||
prop.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void HandleAllKeysDeleted(EditorCurveBinding binding)
|
||||
{
|
||||
if (m_OriginalOwner.hasCurves)
|
||||
{
|
||||
// Remove curve from original asset
|
||||
AnimationUtility.SetEditorCurve(m_OriginalOwner.curves, binding, null);
|
||||
m_OriginalOwner.SanitizeCurvesData();
|
||||
}
|
||||
|
||||
// Ensure proxy still has constant value
|
||||
RebuildProxyCurves();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
foreach (var param in allAnimatableParameters)
|
||||
CreateProxyCurve(param, m_ProxyCurves, 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
|
||||
{
|
||||
var curve = new AnimationCurve();
|
||||
|
||||
CurveEditUtility.AddKeyFrameToCurve(
|
||||
curve, 0.0f, clip.frameRate, CurveEditUtility.GetKeyValue(prop),
|
||||
prop.propertyType == SerializedPropertyType.Boolean);
|
||||
|
||||
AnimationUtility.SetEditorCurve(clip, binding, curve);
|
||||
}
|
||||
|
||||
m_PropertiesMap[binding] = prop;
|
||||
}
|
||||
|
||||
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.parentTrack.clips)
|
||||
{
|
||||
if (c.start < clip.start && (previousClip == null || c.start >= previousClip.start))
|
||||
previousClip = c;
|
||||
}
|
||||
return previousClip;
|
||||
}
|
||||
|
||||
public static TimelineClip GetNextClip(TimelineClip clip)
|
||||
{
|
||||
return clip.parentTrack.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.parentTrack 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.parentTrack 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.parentTrack 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(WindowState state, TimelineClip clip)
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>();
|
||||
timeController.Init(animationWindow.state, clip);
|
||||
return timeController;
|
||||
}
|
||||
|
||||
public static TimelineWindowTimeControl CreateTimeController(WindowState state, 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