360 lines
13 KiB
C#
360 lines
13 KiB
C#
|
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);
|
||
|
TimelineEditor.window.state.rebuildGraph = true;
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
CreateCurves(String.Empty);
|
||
|
|
||
|
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
|
||
|
? (float)TimelineAsset.EditorSettings.kDefaultFrameRate
|
||
|
: (float)m_OriginalOwner.targetTrack.timelineAsset.editorSettings.frameRate
|
||
|
};
|
||
|
}
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|