TimelineAnimationUtilities.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. using UnityEditor;
  6. using UnityEngineInternal;
  7. using UnityEngine.Timeline;
  8. using UnityEngine.Playables;
  9. using Object = UnityEngine.Object;
  10. namespace UnityEditor.Timeline
  11. {
  12. class TimelineAnimationUtilities
  13. {
  14. public enum OffsetEditMode
  15. {
  16. None = -1,
  17. Translation = 0,
  18. Rotation = 1
  19. }
  20. public static bool ValidateOffsetAvailabitity(PlayableDirector director, Animator animator)
  21. {
  22. if (director == null || animator == null)
  23. return false;
  24. return true;
  25. }
  26. public static TimelineClip GetPreviousClip(TimelineClip clip)
  27. {
  28. TimelineClip previousClip = null;
  29. foreach (var c in clip.parentTrack.clips)
  30. {
  31. if (c.start < clip.start && (previousClip == null || c.start >= previousClip.start))
  32. previousClip = c;
  33. }
  34. return previousClip;
  35. }
  36. public static TimelineClip GetNextClip(TimelineClip clip)
  37. {
  38. return clip.parentTrack.clips.Where(c => c.start > clip.start).OrderBy(c => c.start).FirstOrDefault();
  39. }
  40. public struct RigidTransform
  41. {
  42. public Vector3 position;
  43. public Quaternion rotation;
  44. public static RigidTransform Compose(Vector3 pos, Quaternion rot)
  45. {
  46. RigidTransform ret;
  47. ret.position = pos;
  48. ret.rotation = rot;
  49. return ret;
  50. }
  51. public static RigidTransform Mul(RigidTransform a, RigidTransform b)
  52. {
  53. RigidTransform ret;
  54. ret.rotation = a.rotation * b.rotation;
  55. ret.position = a.position + a.rotation * b.position;
  56. return ret;
  57. }
  58. public static RigidTransform Inverse(RigidTransform a)
  59. {
  60. RigidTransform ret;
  61. ret.rotation = Quaternion.Inverse(a.rotation);
  62. ret.position = ret.rotation * (-a.position);
  63. return ret;
  64. }
  65. public static RigidTransform identity
  66. {
  67. get { return Compose(Vector3.zero, Quaternion.identity); }
  68. }
  69. }
  70. private static Matrix4x4 GetTrackMatrix(Transform transform, AnimationTrack track)
  71. {
  72. Matrix4x4 trackMatrix = Matrix4x4.TRS(track.position, track.rotation, Vector3.one);
  73. // in scene off mode, the track offsets are set to the preview position which is stored in the track
  74. if (track.trackOffset == TrackOffset.ApplySceneOffsets)
  75. {
  76. trackMatrix = Matrix4x4.TRS(track.sceneOffsetPosition, Quaternion.Euler(track.sceneOffsetRotation), Vector3.one);
  77. }
  78. // put the parent transform on to the track matrix
  79. if (transform.parent != null)
  80. {
  81. trackMatrix = transform.parent.localToWorldMatrix * trackMatrix;
  82. }
  83. return trackMatrix;
  84. }
  85. // Given a world space position and rotation, updates the clip offsets to match that
  86. public static RigidTransform UpdateClipOffsets(AnimationPlayableAsset asset, AnimationTrack track, Transform transform, Vector3 globalPosition, Quaternion globalRotation)
  87. {
  88. Matrix4x4 worldToLocal = transform.worldToLocalMatrix;
  89. Matrix4x4 clipMatrix = Matrix4x4.TRS(asset.position, asset.rotation, Vector3.one);
  90. Matrix4x4 trackMatrix = GetTrackMatrix(transform, track);
  91. // Use the transform to find the proper goal matrix with scale taken into account
  92. var oldPos = transform.position;
  93. var oldRot = transform.rotation;
  94. transform.position = globalPosition;
  95. transform.rotation = globalRotation;
  96. Matrix4x4 goal = transform.localToWorldMatrix;
  97. transform.position = oldPos;
  98. transform.rotation = oldRot;
  99. // compute the new clip matrix.
  100. Matrix4x4 newClip = trackMatrix.inverse * goal * worldToLocal * trackMatrix * clipMatrix;
  101. return RigidTransform.Compose(newClip.GetColumn(3), MathUtils.QuaternionFromMatrix(newClip));
  102. }
  103. public static RigidTransform GetTrackOffsets(AnimationTrack track, Transform transform)
  104. {
  105. Vector3 position = track.position;
  106. Quaternion rotation = track.rotation;
  107. if (transform != null && transform.parent != null)
  108. {
  109. position = transform.parent.TransformPoint(position);
  110. rotation = transform.parent.rotation * rotation;
  111. MathUtils.QuaternionNormalize(ref rotation);
  112. }
  113. return RigidTransform.Compose(position, rotation);
  114. }
  115. public static void UpdateTrackOffset(AnimationTrack track, Transform transform, RigidTransform offsets)
  116. {
  117. if (transform != null && transform.parent != null)
  118. {
  119. offsets.position = transform.parent.InverseTransformPoint(offsets.position);
  120. offsets.rotation = Quaternion.Inverse(transform.parent.rotation) * offsets.rotation;
  121. MathUtils.QuaternionNormalize(ref offsets.rotation);
  122. }
  123. track.position = offsets.position;
  124. track.eulerAngles = AnimationUtility.GetClosestEuler(offsets.rotation, track.eulerAngles, RotationOrder.OrderZXY);
  125. track.UpdateClipOffsets();
  126. }
  127. static MatchTargetFields GetMatchFields(TimelineClip clip)
  128. {
  129. var track = clip.parentTrack as AnimationTrack;
  130. if (track == null)
  131. return MatchTargetFieldConstants.None;
  132. var asset = clip.asset as AnimationPlayableAsset;
  133. var fields = track.matchTargetFields;
  134. if (asset != null && !asset.useTrackMatchFields)
  135. fields = asset.matchTargetFields;
  136. return fields;
  137. }
  138. static void WriteMatchFields(AnimationPlayableAsset asset, RigidTransform result, MatchTargetFields fields)
  139. {
  140. Vector3 position = asset.position;
  141. position.x = fields.HasAny(MatchTargetFields.PositionX) ? result.position.x : position.x;
  142. position.y = fields.HasAny(MatchTargetFields.PositionY) ? result.position.y : position.y;
  143. position.z = fields.HasAny(MatchTargetFields.PositionZ) ? result.position.z : position.z;
  144. asset.position = position;
  145. // check first to avoid unnecessary conversion errors
  146. if (fields.HasAny(MatchTargetFieldConstants.Rotation))
  147. {
  148. Vector3 eulers = asset.eulerAngles;
  149. Vector3 resultEulers = result.rotation.eulerAngles;
  150. eulers.x = fields.HasAny(MatchTargetFields.RotationX) ? resultEulers.x : eulers.x;
  151. eulers.y = fields.HasAny(MatchTargetFields.RotationY) ? resultEulers.y : eulers.y;
  152. eulers.z = fields.HasAny(MatchTargetFields.RotationZ) ? resultEulers.z : eulers.z;
  153. asset.eulerAngles = AnimationUtility.GetClosestEuler(Quaternion.Euler(eulers), asset.eulerAngles, RotationOrder.OrderZXY);
  154. }
  155. }
  156. public static void MatchPrevious(TimelineClip currentClip, Transform matchPoint, PlayableDirector director)
  157. {
  158. const double timeEpsilon = 0.00001;
  159. MatchTargetFields matchFields = GetMatchFields(currentClip);
  160. if (matchFields == MatchTargetFieldConstants.None || matchPoint == null)
  161. return;
  162. double cachedTime = director.time;
  163. // finds previous clip
  164. TimelineClip previousClip = GetPreviousClip(currentClip);
  165. if (previousClip == null || currentClip == previousClip)
  166. return;
  167. // make sure the transform is properly updated before modifying the graph
  168. director.Evaluate();
  169. var parentTrack = currentClip.parentTrack as AnimationTrack;
  170. var blendIn = currentClip.blendInDuration;
  171. currentClip.blendInDuration = 0;
  172. var blendOut = previousClip.blendOutDuration;
  173. previousClip.blendOutDuration = 0;
  174. //evaluate previous without current
  175. parentTrack.RemoveClip(currentClip);
  176. director.RebuildGraph();
  177. double previousEndTime = currentClip.start > previousClip.end ? previousClip.end : currentClip.start;
  178. director.time = previousEndTime - timeEpsilon;
  179. director.Evaluate(); // add port to evaluate only track
  180. var targetPosition = matchPoint.position;
  181. var targetRotation = matchPoint.rotation;
  182. // evaluate current without previous
  183. parentTrack.AddClip(currentClip);
  184. parentTrack.RemoveClip(previousClip);
  185. director.RebuildGraph();
  186. director.time = currentClip.start + timeEpsilon;
  187. director.Evaluate();
  188. //////////////////////////////////////////////////////////////////////
  189. //compute offsets
  190. var animationPlayable = currentClip.asset as AnimationPlayableAsset;
  191. var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation);
  192. WriteMatchFields(animationPlayable, match, matchFields);
  193. //////////////////////////////////////////////////////////////////////
  194. currentClip.blendInDuration = blendIn;
  195. previousClip.blendOutDuration = blendOut;
  196. parentTrack.AddClip(previousClip);
  197. director.RebuildGraph();
  198. director.time = cachedTime;
  199. director.Evaluate();
  200. }
  201. public static void MatchNext(TimelineClip currentClip, Transform matchPoint, PlayableDirector director)
  202. {
  203. const double timeEpsilon = 0.00001;
  204. MatchTargetFields matchFields = GetMatchFields(currentClip);
  205. if (matchFields == MatchTargetFieldConstants.None || matchPoint == null)
  206. return;
  207. double cachedTime = director.time;
  208. // finds next clip
  209. TimelineClip nextClip = GetNextClip(currentClip);
  210. if (nextClip == null || currentClip == nextClip)
  211. return;
  212. // make sure the transform is properly updated before modifying the graph
  213. director.Evaluate();
  214. var parentTrack = currentClip.parentTrack as AnimationTrack;
  215. var blendOut = currentClip.blendOutDuration;
  216. var blendIn = nextClip.blendInDuration;
  217. currentClip.blendOutDuration = 0;
  218. nextClip.blendInDuration = 0;
  219. //evaluate previous without current
  220. parentTrack.RemoveClip(currentClip);
  221. director.RebuildGraph();
  222. director.time = nextClip.start + timeEpsilon;
  223. director.Evaluate(); // add port to evaluate only track
  224. var targetPosition = matchPoint.position;
  225. var targetRotation = matchPoint.rotation;
  226. // evaluate current without next
  227. parentTrack.AddClip(currentClip);
  228. parentTrack.RemoveClip(nextClip);
  229. director.RebuildGraph();
  230. director.time = Math.Min(nextClip.start, currentClip.end - timeEpsilon);
  231. director.Evaluate();
  232. //////////////////////////////////////////////////////////////////////
  233. //compute offsets
  234. var animationPlayable = currentClip.asset as AnimationPlayableAsset;
  235. var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation);
  236. WriteMatchFields(animationPlayable, match, matchFields);
  237. //////////////////////////////////////////////////////////////////////
  238. currentClip.blendOutDuration = blendOut;
  239. nextClip.blendInDuration = blendIn;
  240. parentTrack.AddClip(nextClip);
  241. director.RebuildGraph();
  242. director.time = cachedTime;
  243. director.Evaluate();
  244. }
  245. public static TimelineWindowTimeControl CreateTimeController(WindowState state, TimelineClip clip)
  246. {
  247. var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
  248. var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>();
  249. timeController.Init(animationWindow.state, clip);
  250. return timeController;
  251. }
  252. public static TimelineWindowTimeControl CreateTimeController(WindowState state, TimelineWindowTimeControl.ClipData clipData)
  253. {
  254. var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
  255. var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>();
  256. timeController.Init(animationWindow.state, clipData);
  257. return timeController;
  258. }
  259. public static void EditAnimationClipWithTimeController(AnimationClip animationClip, TimelineWindowTimeControl timeController, Object sourceObject)
  260. {
  261. var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
  262. animationWindow.EditSequencerClip(animationClip, sourceObject, timeController);
  263. }
  264. public static void UnlinkAnimationWindowFromTracks(IEnumerable<TrackAsset> tracks)
  265. {
  266. var clips = new List<AnimationClip>();
  267. foreach (var track in tracks)
  268. {
  269. var animationTrack = track as AnimationTrack;
  270. if (animationTrack != null && animationTrack.infiniteClip != null)
  271. clips.Add(animationTrack.infiniteClip);
  272. GetAnimationClips(track.GetClips(), clips);
  273. }
  274. UnlinkAnimationWindowFromAnimationClips(clips);
  275. }
  276. public static void UnlinkAnimationWindowFromClips(IEnumerable<TimelineClip> timelineClips)
  277. {
  278. var clips = new List<AnimationClip>();
  279. GetAnimationClips(timelineClips, clips);
  280. UnlinkAnimationWindowFromAnimationClips(clips);
  281. }
  282. public static void UnlinkAnimationWindowFromAnimationClips(ICollection<AnimationClip> clips)
  283. {
  284. if (clips.Count == 0)
  285. return;
  286. UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow));
  287. foreach (var animWindow in windows.OfType<AnimationWindow>())
  288. {
  289. if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer && clips.Contains(animWindow.state.activeAnimationClip))
  290. animWindow.UnlinkSequencer();
  291. }
  292. }
  293. public static void UnlinkAnimationWindow()
  294. {
  295. UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow));
  296. foreach (var animWindow in windows.OfType<AnimationWindow>())
  297. {
  298. if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer)
  299. animWindow.UnlinkSequencer();
  300. }
  301. }
  302. private static void GetAnimationClips(IEnumerable<TimelineClip> timelineClips, List<AnimationClip> clips)
  303. {
  304. foreach (var timelineClip in timelineClips)
  305. {
  306. if (timelineClip.curves != null)
  307. clips.Add(timelineClip.curves);
  308. AnimationPlayableAsset apa = timelineClip.asset as AnimationPlayableAsset;
  309. if (apa != null && apa.clip != null)
  310. clips.Add(apa.clip);
  311. }
  312. }
  313. public static int GetAnimationWindowCurrentFrame()
  314. {
  315. var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
  316. if (animationWindow)
  317. return animationWindow.state.currentFrame;
  318. return -1;
  319. }
  320. public static void SetAnimationWindowCurrentFrame(int frame)
  321. {
  322. var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
  323. if (animationWindow)
  324. animationWindow.state.currentFrame = frame;
  325. }
  326. public static void ConstrainCurveToBooleanValues(AnimationCurve curve)
  327. {
  328. // Clamp the values first
  329. var keys = curve.keys;
  330. for (var i = 0; i < keys.Length; i++)
  331. {
  332. var key = keys[i];
  333. key.value = key.value < 0.5f ? 0.0f : 1.0f;
  334. keys[i] = key;
  335. }
  336. curve.keys = keys;
  337. // Update the tangents once all the values are clamped
  338. for (var i = 0; i < curve.length; i++)
  339. {
  340. AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Constant);
  341. AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Constant);
  342. }
  343. }
  344. public static void ConstrainCurveToRange(AnimationCurve curve, float minValue, float maxValue)
  345. {
  346. var keys = curve.keys;
  347. for (var i = 0; i < keys.Length; i++)
  348. {
  349. var key = keys[i];
  350. key.value = Mathf.Clamp(key.value, minValue, maxValue);
  351. keys[i] = key;
  352. }
  353. curve.keys = keys;
  354. }
  355. public static bool IsAnimationClip(TimelineClip clip)
  356. {
  357. return clip != null && (clip.asset as AnimationPlayableAsset) != null;
  358. }
  359. }
  360. }