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