436 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			436 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Collections.Generic; | ||
|  | using System.Linq; | ||
|  | using UnityEngine; | ||
|  | using UnityEditor; | ||
|  | using UnityEngineInternal; | ||
|  | using UnityEngine.Timeline; | ||
|  | using UnityEngine.Playables; | ||
|  | using Object = UnityEngine.Object; | ||
|  | 
 | ||
|  | namespace UnityEditor.Timeline | ||
|  | { | ||
|  |     class TimelineAnimationUtilities | ||
|  |     { | ||
|  |         public enum OffsetEditMode | ||
|  |         { | ||
|  |             None = -1, | ||
|  |             Translation = 0, | ||
|  |             Rotation = 1 | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool ValidateOffsetAvailabitity(PlayableDirector director, Animator animator) | ||
|  |         { | ||
|  |             if (director == null || animator == null) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static TimelineClip GetPreviousClip(TimelineClip clip) | ||
|  |         { | ||
|  |             TimelineClip previousClip = null; | ||
|  |             foreach (var c in clip.GetParentTrack().clips) | ||
|  |             { | ||
|  |                 if (c.start < clip.start && (previousClip == null || c.start >= previousClip.start)) | ||
|  |                     previousClip = c; | ||
|  |             } | ||
|  |             return previousClip; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static TimelineClip GetNextClip(TimelineClip clip) | ||
|  |         { | ||
|  |             return clip.GetParentTrack().clips.Where(c => c.start > clip.start).OrderBy(c => c.start).FirstOrDefault(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public struct RigidTransform | ||
|  |         { | ||
|  |             public Vector3 position; | ||
|  |             public Quaternion rotation; | ||
|  | 
 | ||
|  |             public static RigidTransform Compose(Vector3 pos, Quaternion rot) | ||
|  |             { | ||
|  |                 RigidTransform ret; | ||
|  |                 ret.position = pos; | ||
|  |                 ret.rotation = rot; | ||
|  |                 return ret; | ||
|  |             } | ||
|  | 
 | ||
|  |             public static RigidTransform Mul(RigidTransform a, RigidTransform b) | ||
|  |             { | ||
|  |                 RigidTransform ret; | ||
|  |                 ret.rotation = a.rotation * b.rotation; | ||
|  |                 ret.position = a.position + a.rotation * b.position; | ||
|  |                 return ret; | ||
|  |             } | ||
|  | 
 | ||
|  |             public static RigidTransform Inverse(RigidTransform a) | ||
|  |             { | ||
|  |                 RigidTransform ret; | ||
|  |                 ret.rotation = Quaternion.Inverse(a.rotation); | ||
|  |                 ret.position = ret.rotation * (-a.position); | ||
|  |                 return ret; | ||
|  |             } | ||
|  | 
 | ||
|  |             public static RigidTransform identity | ||
|  |             { | ||
|  |                 get { return Compose(Vector3.zero, Quaternion.identity); } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         private static Matrix4x4 GetTrackMatrix(Transform transform, AnimationTrack track) | ||
|  |         { | ||
|  |             Matrix4x4 trackMatrix = Matrix4x4.TRS(track.position, track.rotation, Vector3.one); | ||
|  | 
 | ||
|  |             // in scene off mode, the track offsets are set to the preview position which is stored in the track | ||
|  |             if (track.trackOffset == TrackOffset.ApplySceneOffsets) | ||
|  |             { | ||
|  |                 trackMatrix = Matrix4x4.TRS(track.sceneOffsetPosition, Quaternion.Euler(track.sceneOffsetRotation), Vector3.one); | ||
|  |             } | ||
|  | 
 | ||
|  |             // put the parent transform on to the track matrix | ||
|  |             if (transform.parent != null) | ||
|  |             { | ||
|  |                 trackMatrix = transform.parent.localToWorldMatrix * trackMatrix; | ||
|  |             } | ||
|  | 
 | ||
|  |             return trackMatrix; | ||
|  |         } | ||
|  | 
 | ||
|  |         // Given a world space position and rotation, updates the clip offsets to match that | ||
|  |         public static RigidTransform UpdateClipOffsets(AnimationPlayableAsset asset, AnimationTrack track, Transform transform, Vector3 globalPosition, Quaternion globalRotation) | ||
|  |         { | ||
|  |             Matrix4x4 worldToLocal = transform.worldToLocalMatrix; | ||
|  |             Matrix4x4 clipMatrix = Matrix4x4.TRS(asset.position, asset.rotation, Vector3.one); | ||
|  |             Matrix4x4 trackMatrix = GetTrackMatrix(transform, track); | ||
|  | 
 | ||
|  | 
 | ||
|  |             // Use the transform to find the proper goal matrix with scale taken into account | ||
|  |             var oldPos = transform.position; | ||
|  |             var oldRot = transform.rotation; | ||
|  |             transform.position = globalPosition; | ||
|  |             transform.rotation = globalRotation; | ||
|  |             Matrix4x4 goal = transform.localToWorldMatrix; | ||
|  |             transform.position = oldPos; | ||
|  |             transform.rotation = oldRot; | ||
|  | 
 | ||
|  |             // compute the new clip matrix. | ||
|  |             Matrix4x4 newClip = trackMatrix.inverse * goal * worldToLocal * trackMatrix * clipMatrix; | ||
|  |             return RigidTransform.Compose(newClip.GetColumn(3), MathUtils.QuaternionFromMatrix(newClip)); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static RigidTransform GetTrackOffsets(AnimationTrack track, Transform transform) | ||
|  |         { | ||
|  |             Vector3 position = track.position; | ||
|  |             Quaternion rotation = track.rotation; | ||
|  |             if (transform != null && transform.parent != null) | ||
|  |             { | ||
|  |                 position = transform.parent.TransformPoint(position); | ||
|  |                 rotation = transform.parent.rotation * rotation; | ||
|  |                 MathUtils.QuaternionNormalize(ref rotation); | ||
|  |             } | ||
|  | 
 | ||
|  |             return RigidTransform.Compose(position, rotation); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void UpdateTrackOffset(AnimationTrack track, Transform transform, RigidTransform offsets) | ||
|  |         { | ||
|  |             if (transform != null && transform.parent != null) | ||
|  |             { | ||
|  |                 offsets.position = transform.parent.InverseTransformPoint(offsets.position); | ||
|  |                 offsets.rotation = Quaternion.Inverse(transform.parent.rotation) * offsets.rotation; | ||
|  |                 MathUtils.QuaternionNormalize(ref offsets.rotation); | ||
|  |             } | ||
|  | 
 | ||
|  |             track.position = offsets.position; | ||
|  |             track.eulerAngles = AnimationUtility.GetClosestEuler(offsets.rotation, track.eulerAngles, RotationOrder.OrderZXY); | ||
|  |             track.UpdateClipOffsets(); | ||
|  |         } | ||
|  | 
 | ||
|  |         static MatchTargetFields GetMatchFields(TimelineClip clip) | ||
|  |         { | ||
|  |             var track = clip.GetParentTrack() as AnimationTrack; | ||
|  |             if (track == null) | ||
|  |                 return MatchTargetFieldConstants.None; | ||
|  | 
 | ||
|  |             var asset = clip.asset as AnimationPlayableAsset; | ||
|  |             var fields = track.matchTargetFields; | ||
|  |             if (asset != null && !asset.useTrackMatchFields) | ||
|  |                 fields = asset.matchTargetFields; | ||
|  |             return fields; | ||
|  |         } | ||
|  | 
 | ||
|  |         static void WriteMatchFields(AnimationPlayableAsset asset, RigidTransform result, MatchTargetFields fields) | ||
|  |         { | ||
|  |             Vector3 position = asset.position; | ||
|  | 
 | ||
|  |             position.x = fields.HasAny(MatchTargetFields.PositionX) ? result.position.x : position.x; | ||
|  |             position.y = fields.HasAny(MatchTargetFields.PositionY) ? result.position.y : position.y; | ||
|  |             position.z = fields.HasAny(MatchTargetFields.PositionZ) ? result.position.z : position.z; | ||
|  | 
 | ||
|  |             asset.position = position; | ||
|  | 
 | ||
|  |             // check first to avoid unnecessary conversion errors | ||
|  |             if (fields.HasAny(MatchTargetFieldConstants.Rotation)) | ||
|  |             { | ||
|  |                 Vector3 eulers = asset.eulerAngles; | ||
|  |                 Vector3 resultEulers = result.rotation.eulerAngles; | ||
|  | 
 | ||
|  |                 eulers.x = fields.HasAny(MatchTargetFields.RotationX) ? resultEulers.x : eulers.x; | ||
|  |                 eulers.y = fields.HasAny(MatchTargetFields.RotationY) ? resultEulers.y : eulers.y; | ||
|  |                 eulers.z = fields.HasAny(MatchTargetFields.RotationZ) ? resultEulers.z : eulers.z; | ||
|  | 
 | ||
|  |                 asset.eulerAngles = AnimationUtility.GetClosestEuler(Quaternion.Euler(eulers), asset.eulerAngles, RotationOrder.OrderZXY); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void MatchPrevious(TimelineClip currentClip, Transform matchPoint, PlayableDirector director) | ||
|  |         { | ||
|  |             const double timeEpsilon = 0.00001; | ||
|  |             MatchTargetFields matchFields = GetMatchFields(currentClip); | ||
|  |             if (matchFields == MatchTargetFieldConstants.None || matchPoint == null) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             double cachedTime = director.time; | ||
|  | 
 | ||
|  |             // finds previous clip | ||
|  |             TimelineClip previousClip = GetPreviousClip(currentClip); | ||
|  |             if (previousClip == null || currentClip == previousClip) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             // make sure the transform is properly updated before modifying the graph | ||
|  |             director.Evaluate(); | ||
|  | 
 | ||
|  |             var parentTrack = currentClip.GetParentTrack() as AnimationTrack; | ||
|  | 
 | ||
|  |             var blendIn = currentClip.blendInDuration; | ||
|  |             currentClip.blendInDuration = 0; | ||
|  |             var blendOut = previousClip.blendOutDuration; | ||
|  |             previousClip.blendOutDuration = 0; | ||
|  | 
 | ||
|  |             //evaluate previous without current | ||
|  |             parentTrack.RemoveClip(currentClip); | ||
|  |             director.RebuildGraph(); | ||
|  |             double previousEndTime = currentClip.start > previousClip.end ? previousClip.end : currentClip.start; | ||
|  |             director.time = previousEndTime - timeEpsilon; | ||
|  |             director.Evaluate(); // add port to evaluate only track | ||
|  | 
 | ||
|  |             var targetPosition = matchPoint.position; | ||
|  |             var targetRotation = matchPoint.rotation; | ||
|  | 
 | ||
|  |             // evaluate current without previous | ||
|  |             parentTrack.AddClip(currentClip); | ||
|  |             parentTrack.RemoveClip(previousClip); | ||
|  |             director.RebuildGraph(); | ||
|  |             director.time = currentClip.start + timeEpsilon; | ||
|  |             director.Evaluate(); | ||
|  | 
 | ||
|  |             ////////////////////////////////////////////////////////////////////// | ||
|  |             //compute offsets | ||
|  | 
 | ||
|  |             var animationPlayable = currentClip.asset as AnimationPlayableAsset; | ||
|  |             var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation); | ||
|  |             WriteMatchFields(animationPlayable, match, matchFields); | ||
|  | 
 | ||
|  |             ////////////////////////////////////////////////////////////////////// | ||
|  | 
 | ||
|  |             currentClip.blendInDuration = blendIn; | ||
|  |             previousClip.blendOutDuration = blendOut; | ||
|  | 
 | ||
|  |             parentTrack.AddClip(previousClip); | ||
|  |             director.RebuildGraph(); | ||
|  |             director.time = cachedTime; | ||
|  |             director.Evaluate(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void MatchNext(TimelineClip currentClip, Transform matchPoint, PlayableDirector director) | ||
|  |         { | ||
|  |             const double timeEpsilon = 0.00001; | ||
|  |             MatchTargetFields matchFields = GetMatchFields(currentClip); | ||
|  |             if (matchFields == MatchTargetFieldConstants.None || matchPoint == null) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             double cachedTime = director.time; | ||
|  | 
 | ||
|  |             // finds next clip | ||
|  |             TimelineClip nextClip = GetNextClip(currentClip); | ||
|  |             if (nextClip == null || currentClip == nextClip) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             // make sure the transform is properly updated before modifying the graph | ||
|  |             director.Evaluate(); | ||
|  | 
 | ||
|  |             var parentTrack = currentClip.GetParentTrack() as AnimationTrack; | ||
|  | 
 | ||
|  |             var blendOut = currentClip.blendOutDuration; | ||
|  |             var blendIn = nextClip.blendInDuration; | ||
|  |             currentClip.blendOutDuration = 0; | ||
|  |             nextClip.blendInDuration = 0; | ||
|  | 
 | ||
|  |             //evaluate previous without current | ||
|  |             parentTrack.RemoveClip(currentClip); | ||
|  |             director.RebuildGraph(); | ||
|  |             director.time = nextClip.start + timeEpsilon; | ||
|  |             director.Evaluate(); // add port to evaluate only track | ||
|  | 
 | ||
|  |             var targetPosition = matchPoint.position; | ||
|  |             var targetRotation = matchPoint.rotation; | ||
|  | 
 | ||
|  |             // evaluate current without next | ||
|  |             parentTrack.AddClip(currentClip); | ||
|  |             parentTrack.RemoveClip(nextClip); | ||
|  |             director.RebuildGraph(); | ||
|  |             director.time = Math.Min(nextClip.start, currentClip.end - timeEpsilon); | ||
|  |             director.Evaluate(); | ||
|  | 
 | ||
|  |             ////////////////////////////////////////////////////////////////////// | ||
|  |             //compute offsets | ||
|  | 
 | ||
|  |             var animationPlayable = currentClip.asset as AnimationPlayableAsset; | ||
|  |             var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation); | ||
|  |             WriteMatchFields(animationPlayable, match, matchFields); | ||
|  | 
 | ||
|  |             ////////////////////////////////////////////////////////////////////// | ||
|  | 
 | ||
|  |             currentClip.blendOutDuration = blendOut; | ||
|  |             nextClip.blendInDuration = blendIn; | ||
|  | 
 | ||
|  |             parentTrack.AddClip(nextClip); | ||
|  |             director.RebuildGraph(); | ||
|  |             director.time = cachedTime; | ||
|  |             director.Evaluate(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static TimelineWindowTimeControl CreateTimeController(TimelineClip clip) | ||
|  |         { | ||
|  |             var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); | ||
|  |             var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>(); | ||
|  |             timeController.Init(animationWindow.state, clip); | ||
|  |             return timeController; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static TimelineWindowTimeControl CreateTimeController(TimelineWindowTimeControl.ClipData clipData) | ||
|  |         { | ||
|  |             var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); | ||
|  |             var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>(); | ||
|  |             timeController.Init(animationWindow.state, clipData); | ||
|  |             return timeController; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void EditAnimationClipWithTimeController(AnimationClip animationClip, TimelineWindowTimeControl timeController, Object sourceObject) | ||
|  |         { | ||
|  |             var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); | ||
|  |             animationWindow.EditSequencerClip(animationClip, sourceObject, timeController); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void UnlinkAnimationWindowFromTracks(IEnumerable<TrackAsset> tracks) | ||
|  |         { | ||
|  |             var clips = new List<AnimationClip>(); | ||
|  |             foreach (var track in tracks) | ||
|  |             { | ||
|  |                 var animationTrack = track as AnimationTrack; | ||
|  |                 if (animationTrack != null && animationTrack.infiniteClip != null) | ||
|  |                     clips.Add(animationTrack.infiniteClip); | ||
|  | 
 | ||
|  |                 GetAnimationClips(track.GetClips(), clips); | ||
|  |             } | ||
|  |             UnlinkAnimationWindowFromAnimationClips(clips); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void UnlinkAnimationWindowFromClips(IEnumerable<TimelineClip> timelineClips) | ||
|  |         { | ||
|  |             var clips = new List<AnimationClip>(); | ||
|  |             GetAnimationClips(timelineClips, clips); | ||
|  |             UnlinkAnimationWindowFromAnimationClips(clips); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void UnlinkAnimationWindowFromAnimationClips(ICollection<AnimationClip> clips) | ||
|  |         { | ||
|  |             if (clips.Count == 0) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow)); | ||
|  |             foreach (var animWindow in windows.OfType<AnimationWindow>()) | ||
|  |             { | ||
|  |                 if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer && clips.Contains(animWindow.state.activeAnimationClip)) | ||
|  |                     animWindow.UnlinkSequencer(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void UnlinkAnimationWindow() | ||
|  |         { | ||
|  |             UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow)); | ||
|  |             foreach (var animWindow in windows.OfType<AnimationWindow>()) | ||
|  |             { | ||
|  |                 if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer) | ||
|  |                     animWindow.UnlinkSequencer(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         private static void GetAnimationClips(IEnumerable<TimelineClip> timelineClips, List<AnimationClip> clips) | ||
|  |         { | ||
|  |             foreach (var timelineClip in timelineClips) | ||
|  |             { | ||
|  |                 if (timelineClip.curves != null) | ||
|  |                     clips.Add(timelineClip.curves); | ||
|  |                 AnimationPlayableAsset apa = timelineClip.asset as AnimationPlayableAsset; | ||
|  |                 if (apa != null && apa.clip != null) | ||
|  |                     clips.Add(apa.clip); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public static int GetAnimationWindowCurrentFrame() | ||
|  |         { | ||
|  |             var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); | ||
|  |             if (animationWindow) | ||
|  |                 return animationWindow.state.currentFrame; | ||
|  |             return -1; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void SetAnimationWindowCurrentFrame(int frame) | ||
|  |         { | ||
|  |             var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); | ||
|  |             if (animationWindow) | ||
|  |                 animationWindow.state.currentFrame = frame; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void ConstrainCurveToBooleanValues(AnimationCurve curve) | ||
|  |         { | ||
|  |             // Clamp the values first | ||
|  |             var keys = curve.keys; | ||
|  |             for (var i = 0; i < keys.Length; i++) | ||
|  |             { | ||
|  |                 var key = keys[i]; | ||
|  |                 key.value = key.value < 0.5f ? 0.0f : 1.0f; | ||
|  |                 keys[i] = key; | ||
|  |             } | ||
|  |             curve.keys = keys; | ||
|  | 
 | ||
|  |             // Update the tangents once all the values are clamped | ||
|  |             for (var i = 0; i < curve.length; i++) | ||
|  |             { | ||
|  |                 AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Constant); | ||
|  |                 AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Constant); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void ConstrainCurveToRange(AnimationCurve curve, float minValue, float maxValue) | ||
|  |         { | ||
|  |             var keys = curve.keys; | ||
|  |             for (var i = 0; i < keys.Length; i++) | ||
|  |             { | ||
|  |                 var key = keys[i]; | ||
|  |                 key.value = Mathf.Clamp(key.value, minValue, maxValue); | ||
|  |                 keys[i] = key; | ||
|  |             } | ||
|  |             curve.keys = keys; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool IsAnimationClip(TimelineClip clip) | ||
|  |         { | ||
|  |             return clip != null && (clip.asset as AnimationPlayableAsset) != null; | ||
|  |         } | ||
|  |     } | ||
|  | } |