TimelineRecording_Monobehaviour.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using UnityEditorInternal;
  4. using UnityEngine;
  5. using UnityEngine.Timeline;
  6. using System.Globalization;
  7. namespace UnityEditor.Timeline
  8. {
  9. // Methods and data for handling recording to monobehaviours
  10. static partial class TimelineRecording
  11. {
  12. internal class RecordingState : IAnimationRecordingState
  13. {
  14. public GameObject activeGameObject { get; set; }
  15. public GameObject activeRootGameObject { get; set; }
  16. public AnimationClip activeAnimationClip { get; set; }
  17. public void SaveCurve(AnimationWindowCurve curve)
  18. {
  19. Undo.RegisterCompleteObjectUndo(activeAnimationClip, "Edit Curve");
  20. AnimationWindowUtility.SaveCurve(activeAnimationClip, curve);
  21. }
  22. public void AddPropertyModification(EditorCurveBinding binding, PropertyModification propertyModification, bool keepPrefabOverride)
  23. {
  24. AnimationMode.AddPropertyModification(binding, propertyModification, keepPrefabOverride);
  25. }
  26. public bool addZeroFrame
  27. {
  28. get { return false; }
  29. }
  30. public int currentFrame { get; set; }
  31. public bool DiscardModification(PropertyModification modification)
  32. {
  33. return false;
  34. }
  35. }
  36. static readonly RecordingState s_RecordState = new RecordingState();
  37. static readonly AnimationTrackRecorder s_TrackRecorder = new AnimationTrackRecorder();
  38. static readonly List<UndoPropertyModification> s_UnprocessedMods = new List<UndoPropertyModification>();
  39. static readonly List<UndoPropertyModification> s_ModsToProcess = new List<UndoPropertyModification>();
  40. static AnimationTrack s_LastTrackWarning;
  41. public const string kLocalPosition = "m_LocalPosition";
  42. public const string kLocalRotation = "m_LocalRotation";
  43. public const string kLocalEulerHint = "m_LocalEulerAnglesHint";
  44. const string kRotationWarning = "You are recording with an initial rotation offset. This may result in a misrepresentation of euler angles. When recording transform properties, it is recommended to reset rotation prior to recording";
  45. public static bool IsRecordingAnimationTrack { get; private set; }
  46. internal static UndoPropertyModification[] ProcessMonoBehaviourModification(UndoPropertyModification[] modifications, WindowState state)
  47. {
  48. if (state == null || state.editSequence.director == null)
  49. return modifications;
  50. s_UnprocessedMods.Clear();
  51. s_TrackRecorder.PrepareForRecord(state);
  52. s_ModsToProcess.Clear();
  53. s_ModsToProcess.AddRange(modifications.Reverse());
  54. while (s_ModsToProcess.Count > 0)
  55. {
  56. var modification = s_ModsToProcess[s_ModsToProcess.Count - 1];
  57. s_ModsToProcess.RemoveAt(s_ModsToProcess.Count - 1);
  58. // grab the clip we need to apply to
  59. var modifiedGO = GetGameObjectFromModification(modification);
  60. var track = GetTrackForGameObject(modifiedGO, state);
  61. if (track != null)
  62. {
  63. IsRecordingAnimationTrack = true;
  64. double startTime = 0;
  65. var clip = s_TrackRecorder.PrepareTrack(track, state, modifiedGO, out startTime);
  66. if (clip == null)
  67. {
  68. s_ModsToProcess.Reverse();
  69. return s_ModsToProcess.ToArray();
  70. }
  71. s_RecordState.activeAnimationClip = clip;
  72. s_RecordState.activeRootGameObject = state.GetSceneReference(track);
  73. s_RecordState.activeGameObject = modifiedGO;
  74. s_RecordState.currentFrame = Mathf.RoundToInt((float)startTime);
  75. EditorUtility.SetDirty(clip);
  76. var toProcess = GatherRelatedModifications(modification, s_ModsToProcess);
  77. var animator = s_RecordState.activeRootGameObject.GetComponent<Animator>();
  78. var animTrack = track as AnimationTrack;
  79. // update preview mode before recording so the correct values get placed (in case we modify offsets)
  80. // Case 900624
  81. UpdatePreviewMode(toProcess, modifiedGO);
  82. // if this is the first position/rotation recording, copy the current position / rotation to the track offset
  83. AddTrackOffset(animTrack, toProcess, clip, animator);
  84. // same for clip mod clips being created
  85. AddClipOffset(animTrack, toProcess, s_TrackRecorder.recordClip, animator);
  86. // Check if we need to handle position/rotation offsets
  87. var handleOffsets = animator != null && modification.currentValue != null &&
  88. modification.currentValue.target == s_RecordState.activeRootGameObject.transform &&
  89. HasOffsets(animTrack, s_TrackRecorder.recordClip);
  90. if (handleOffsets)
  91. {
  92. toProcess = HandleEulerModifications(animTrack, s_TrackRecorder.recordClip, clip, s_RecordState.currentFrame * clip.frameRate, toProcess);
  93. RemoveOffsets(modification, animTrack, s_TrackRecorder.recordClip, toProcess);
  94. }
  95. var remaining = AnimationRecording.Process(s_RecordState, toProcess);
  96. if (remaining != null && remaining.Length != 0)
  97. {
  98. s_UnprocessedMods.AddRange(remaining);
  99. }
  100. if (handleOffsets)
  101. {
  102. ReapplyOffsets(modification, animTrack, s_TrackRecorder.recordClip, toProcess);
  103. }
  104. s_TrackRecorder.FinializeTrack(track, state);
  105. IsRecordingAnimationTrack = false;
  106. }
  107. else
  108. {
  109. s_UnprocessedMods.Add(modification);
  110. }
  111. }
  112. s_TrackRecorder.FinalizeRecording(state);
  113. return s_UnprocessedMods.ToArray();
  114. }
  115. internal static bool IsPosition(UndoPropertyModification modification)
  116. {
  117. if (modification.currentValue != null)
  118. return modification.currentValue.propertyPath.StartsWith(kLocalPosition);
  119. else if (modification.previousValue != null)
  120. return modification.previousValue.propertyPath.StartsWith(kLocalPosition);
  121. return false;
  122. }
  123. internal static bool IsRotation(UndoPropertyModification modification)
  124. {
  125. if (modification.currentValue != null)
  126. return modification.currentValue.propertyPath.StartsWith(kLocalRotation) ||
  127. modification.currentValue.propertyPath.StartsWith(kLocalEulerHint);
  128. if (modification.previousValue != null)
  129. return modification.previousValue.propertyPath.StartsWith(kLocalRotation) ||
  130. modification.previousValue.propertyPath.StartsWith(kLocalEulerHint);
  131. return false;
  132. }
  133. // Test if this modification position or rotation
  134. internal static bool IsPositionOrRotation(UndoPropertyModification modification)
  135. {
  136. return IsPosition(modification) || IsRotation(modification);
  137. }
  138. internal static void UpdatePreviewMode(UndoPropertyModification[] mods, GameObject go)
  139. {
  140. if (mods.Any(x => IsPositionOrRotation(x) && IsRootModification(x)))
  141. {
  142. bool hasPosition = false;
  143. bool hasRotation = false;
  144. foreach (var mod in mods)
  145. {
  146. EditorCurveBinding binding = new EditorCurveBinding();
  147. if (AnimationUtility.PropertyModificationToEditorCurveBinding(mod.previousValue, go, out binding) != null)
  148. {
  149. hasPosition |= IsPosition(mod);
  150. hasRotation |= IsRotation(mod);
  151. AnimationMode.AddPropertyModification(binding, mod.previousValue, true);
  152. }
  153. }
  154. // case 931859 - if we are only changing one field, all fields must be registered before
  155. // any recording modifications
  156. var driver = WindowState.previewDriver;
  157. if (driver != null && AnimationMode.InAnimationMode(driver))
  158. {
  159. if (hasPosition)
  160. {
  161. DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalPosition + ".x");
  162. DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalPosition + ".y");
  163. DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalPosition + ".z");
  164. }
  165. else if (hasRotation)
  166. {
  167. DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalRotation + ".x");
  168. DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalRotation + ".y");
  169. DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalRotation + ".z");
  170. DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalRotation + ".w");
  171. }
  172. }
  173. }
  174. }
  175. internal static bool IsRootModification(UndoPropertyModification modification)
  176. {
  177. string path = string.Empty;
  178. if (modification.currentValue != null)
  179. path = modification.currentValue.propertyPath;
  180. else if (modification.previousValue != null)
  181. path = modification.previousValue.propertyPath;
  182. return !path.Contains('/') && !path.Contains('\\');
  183. }
  184. // test if the clip has any position or rotation bindings
  185. internal static bool ClipHasPositionOrRotation(AnimationClip clip)
  186. {
  187. if (clip == null || clip.empty)
  188. return false;
  189. var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
  190. for (var i = 0; i < info.bindings.Length; i++)
  191. {
  192. bool isPositionOrRotation =
  193. info.bindings[i].type != null &&
  194. typeof(Transform).IsAssignableFrom(info.bindings[i].type) &&
  195. (
  196. info.bindings[i].propertyName.StartsWith(kLocalPosition) ||
  197. info.bindings[i].propertyName.StartsWith(kLocalRotation) ||
  198. info.bindings[i].propertyName.StartsWith("localEuler")
  199. );
  200. if (isPositionOrRotation)
  201. return true;
  202. }
  203. return false;
  204. }
  205. internal static TimelineAnimationUtilities.RigidTransform ComputeInitialClipOffsets(AnimationTrack track, UndoPropertyModification[] mods, Animator animator)
  206. {
  207. // take into account the track transform
  208. var target = GetInitialTransform(mods, animator);
  209. var trackToClip = TimelineAnimationUtilities.RigidTransform.identity;
  210. if (track.trackOffset == TrackOffset.ApplyTransformOffsets)
  211. trackToClip = TimelineAnimationUtilities.RigidTransform.Compose(track.position, track.rotation);
  212. else if (track.trackOffset == TrackOffset.ApplySceneOffsets)
  213. trackToClip = TimelineAnimationUtilities.RigidTransform.Compose(track.sceneOffsetPosition, Quaternion.Euler(track.sceneOffsetRotation));
  214. target = TimelineAnimationUtilities.RigidTransform.Mul(TimelineAnimationUtilities.RigidTransform.Inverse(trackToClip), target);
  215. // set the previous position in case the animation system adds a default key
  216. SetPreviousPositionAndRotation(mods, animator, trackToClip.position, trackToClip.rotation);
  217. return target;
  218. }
  219. internal static TimelineAnimationUtilities.RigidTransform GetInitialTransform(UndoPropertyModification[] mods, Animator animator)
  220. {
  221. var pos = Vector3.zero;
  222. var rot = Quaternion.identity;
  223. // if we are operating on the root, grab the transform from the undo
  224. if (mods[0].previousValue.target == animator.transform)
  225. {
  226. GetPreviousPositionAndRotation(mods, ref pos, ref rot);
  227. }
  228. // otherwise we need to grab it from the root object, which is the one with the actual animator
  229. else
  230. {
  231. pos = animator.transform.localPosition;
  232. rot = animator.transform.localRotation;
  233. }
  234. // take into account the track transform
  235. return TimelineAnimationUtilities.RigidTransform.Compose(pos, rot);
  236. }
  237. internal static void SetPreviousPositionAndRotation(UndoPropertyModification[] mods, Animator animator, Vector3 pos, Quaternion rot)
  238. {
  239. if (mods[0].previousValue.target == animator.transform)
  240. {
  241. SetPreviousPositionAndRotation(mods, pos, rot);
  242. }
  243. }
  244. // If we are adding to an infinite clip, strip the objects position and rotation and set it as the clip offset
  245. internal static void AddTrackOffset(AnimationTrack track, UndoPropertyModification[] mods, AnimationClip clip, Animator animator)
  246. {
  247. var copyTrackOffset = !track.inClipMode &&
  248. !ClipHasPositionOrRotation(clip) &&
  249. mods.Any(x => IsPositionOrRotation(x) && IsRootModification(x)) &&
  250. animator != null;
  251. if (copyTrackOffset)
  252. {
  253. // in scene offset mode, makes sure we have the correct initial transform set
  254. if (track.trackOffset == TrackOffset.ApplySceneOffsets)
  255. {
  256. var rigidTransform = GetInitialTransform(mods, animator);
  257. track.sceneOffsetPosition = rigidTransform.position;
  258. track.sceneOffsetRotation = rigidTransform.rotation.eulerAngles;
  259. SetPreviousPositionAndRotation(mods, animator, rigidTransform.position, rigidTransform.rotation);
  260. }
  261. else
  262. {
  263. var rigidTransform = ComputeInitialClipOffsets(track, mods, animator);
  264. track.infiniteClipOffsetPosition = rigidTransform.position;
  265. track.infiniteClipOffsetEulerAngles = rigidTransform.rotation.eulerAngles;
  266. }
  267. }
  268. }
  269. internal static void AddClipOffset(AnimationTrack track, UndoPropertyModification[] mods, TimelineClip clip, Animator animator)
  270. {
  271. if (clip == null || clip.asset == null)
  272. return;
  273. var clipAsset = clip.asset as AnimationPlayableAsset;
  274. var copyClipOffset = track.inClipMode &&
  275. clipAsset != null && !ClipHasPositionOrRotation(clipAsset.clip) &&
  276. mods.Any(x => IsPositionOrRotation(x) && IsRootModification(x)) &&
  277. animator != null;
  278. if (copyClipOffset)
  279. {
  280. var rigidTransform = ComputeInitialClipOffsets(track, mods, animator);
  281. clipAsset.position = rigidTransform.position;
  282. clipAsset.rotation = rigidTransform.rotation;
  283. }
  284. }
  285. internal static TimelineAnimationUtilities.RigidTransform GetLocalToTrack(AnimationTrack track, TimelineClip clip)
  286. {
  287. if (track == null)
  288. return TimelineAnimationUtilities.RigidTransform.Compose(Vector3.zero, Quaternion.identity);
  289. var trackPos = track.position;
  290. var trackRot = track.rotation;
  291. if (track.trackOffset == TrackOffset.ApplySceneOffsets)
  292. {
  293. trackPos = track.sceneOffsetPosition;
  294. trackRot = Quaternion.Euler(track.sceneOffsetRotation);
  295. }
  296. var clipWrapper = clip == null ? null : clip.asset as AnimationPlayableAsset;
  297. var clipTransform = TimelineAnimationUtilities.RigidTransform.Compose(Vector3.zero, Quaternion.identity);
  298. if (clipWrapper != null)
  299. {
  300. clipTransform = TimelineAnimationUtilities.RigidTransform.Compose(clipWrapper.position, clipWrapper.rotation);
  301. }
  302. else
  303. {
  304. clipTransform = TimelineAnimationUtilities.RigidTransform.Compose(track.infiniteClipOffsetPosition, track.infiniteClipOffsetRotation);
  305. }
  306. var trackTransform = TimelineAnimationUtilities.RigidTransform.Compose(trackPos, trackRot);
  307. return TimelineAnimationUtilities.RigidTransform.Mul(trackTransform, clipTransform);
  308. }
  309. // Checks whether there are any offsets applied to a clip
  310. internal static bool HasOffsets(AnimationTrack track, TimelineClip clip)
  311. {
  312. if (track == null)
  313. return false;
  314. bool hasClipOffsets = false;
  315. bool hasTrackOffsets = false;
  316. var clipWrapper = clip == null ? null : clip.asset as AnimationPlayableAsset;
  317. if (clipWrapper != null)
  318. hasClipOffsets |= clipWrapper.position != Vector3.zero || clipWrapper.rotation != Quaternion.identity;
  319. if (track.trackOffset == TrackOffset.ApplySceneOffsets)
  320. {
  321. hasTrackOffsets = track.sceneOffsetPosition != Vector3.zero || track.sceneOffsetRotation != Vector3.zero;
  322. }
  323. else
  324. {
  325. hasTrackOffsets = (track.position != Vector3.zero || track.rotation != Quaternion.identity);
  326. if (!track.inClipMode)
  327. hasClipOffsets |= track.infiniteClipOffsetPosition != Vector3.zero || track.infiniteClipOffsetRotation != Quaternion.identity;
  328. }
  329. return hasTrackOffsets || hasClipOffsets;
  330. }
  331. internal static void RemoveOffsets(UndoPropertyModification modification, AnimationTrack track, TimelineClip clip, UndoPropertyModification[] mods)
  332. {
  333. if (IsPositionOrRotation(modification))
  334. {
  335. var modifiedGO = GetGameObjectFromModification(modification);
  336. var target = TimelineAnimationUtilities.RigidTransform.Compose(modifiedGO.transform.localPosition, modifiedGO.transform.localRotation);
  337. var localToTrack = GetLocalToTrack(track, clip);
  338. var trackToLocal = TimelineAnimationUtilities.RigidTransform.Inverse(localToTrack);
  339. var localSpace = TimelineAnimationUtilities.RigidTransform.Mul(trackToLocal, target);
  340. // Update the undo call values
  341. var prevPos = modifiedGO.transform.localPosition;
  342. var prevRot = modifiedGO.transform.localRotation;
  343. GetPreviousPositionAndRotation(mods, ref prevPos, ref prevRot);
  344. var previousRigidTransform = TimelineAnimationUtilities.RigidTransform.Mul(trackToLocal, TimelineAnimationUtilities.RigidTransform.Compose(prevPos, prevRot));
  345. SetPreviousPositionAndRotation(mods, previousRigidTransform.position, previousRigidTransform.rotation);
  346. var currentPos = modifiedGO.transform.localPosition;
  347. var currentRot = modifiedGO.transform.localRotation;
  348. GetCurrentPositionAndRotation(mods, ref currentPos, ref currentRot);
  349. var currentRigidTransform = TimelineAnimationUtilities.RigidTransform.Mul(trackToLocal, TimelineAnimationUtilities.RigidTransform.Compose(currentPos, currentRot));
  350. SetCurrentPositionAndRotation(mods, currentRigidTransform.position, currentRigidTransform.rotation);
  351. modifiedGO.transform.localPosition = localSpace.position;
  352. modifiedGO.transform.localRotation = localSpace.rotation;
  353. }
  354. }
  355. internal static void ReapplyOffsets(UndoPropertyModification modification, AnimationTrack track, TimelineClip clip, UndoPropertyModification[] mods)
  356. {
  357. if (IsPositionOrRotation(modification))
  358. {
  359. var modifiedGO = GetGameObjectFromModification(modification);
  360. var target = TimelineAnimationUtilities.RigidTransform.Compose(modifiedGO.transform.localPosition, modifiedGO.transform.localRotation);
  361. var localToTrack = GetLocalToTrack(track, clip);
  362. var trackSpace = TimelineAnimationUtilities.RigidTransform.Mul(localToTrack, target);
  363. // Update the undo call values
  364. var prevPos = modifiedGO.transform.localPosition;
  365. var prevRot = modifiedGO.transform.localRotation;
  366. GetPreviousPositionAndRotation(mods, ref prevPos, ref prevRot);
  367. var previousRigidTransform = TimelineAnimationUtilities.RigidTransform.Mul(localToTrack, TimelineAnimationUtilities.RigidTransform.Compose(prevPos, prevRot));
  368. SetPreviousPositionAndRotation(mods, previousRigidTransform.position, previousRigidTransform.rotation);
  369. var currentPos = modifiedGO.transform.localPosition;
  370. var currentRot = modifiedGO.transform.localRotation;
  371. GetCurrentPositionAndRotation(mods, ref currentPos, ref currentRot);
  372. var currentRigidTransform = TimelineAnimationUtilities.RigidTransform.Mul(localToTrack, TimelineAnimationUtilities.RigidTransform.Compose(currentPos, currentRot));
  373. SetCurrentPositionAndRotation(mods, currentRigidTransform.position, currentRigidTransform.rotation);
  374. modifiedGO.transform.localPosition = trackSpace.position;
  375. modifiedGO.transform.localRotation = trackSpace.rotation;
  376. }
  377. }
  378. // This will gather the modifications that modify the same property on the same object (rgba of a color, xyzw of a vector)
  379. // Note: This will modify the list, removing any elements that match
  380. static UndoPropertyModification[] GatherRelatedModifications(UndoPropertyModification toMatch, List<UndoPropertyModification> list)
  381. {
  382. var matching = new List<UndoPropertyModification> {toMatch};
  383. for (var i = list.Count - 1; i >= 0; i--)
  384. {
  385. var undo = list[i];
  386. if (undo.previousValue.target == toMatch.previousValue.target &&
  387. DoesPropertyPathMatch(undo.previousValue.propertyPath, toMatch.previousValue.propertyPath))
  388. {
  389. matching.Add(undo);
  390. list.RemoveAt(i);
  391. }
  392. }
  393. return matching.ToArray();
  394. }
  395. // Grab the game object out of the modification object
  396. static GameObject GetGameObjectFromModification(UndoPropertyModification mod)
  397. {
  398. // grab the GO this is modifying
  399. GameObject modifiedGO = null;
  400. if (mod.previousValue.target is GameObject)
  401. modifiedGO = mod.previousValue.target as GameObject;
  402. else if (mod.previousValue.target is Component)
  403. modifiedGO = (mod.previousValue.target as Component).gameObject;
  404. return modifiedGO;
  405. }
  406. // returns the level of the child in the hierarchy relative to the parent,
  407. // or -1 if the child is not the parent or a descendent of it
  408. static int GetChildLevel(GameObject parent, GameObject child)
  409. {
  410. var level = 0;
  411. while (child != null)
  412. {
  413. if (parent == child)
  414. break;
  415. if (child.transform.parent == null)
  416. return -1;
  417. child = child.transform.parent.gameObject;
  418. level++;
  419. }
  420. if (child != null)
  421. return level;
  422. return -1;
  423. }
  424. static bool DoesPropertyPathMatch(string a, string b)
  425. {
  426. return AnimationWindowUtility.GetPropertyGroupName(a).Equals(AnimationWindowUtility.GetPropertyGroupName(a));
  427. }
  428. internal static void GetPreviousPositionAndRotation(UndoPropertyModification[] mods, ref Vector3 position, ref Quaternion rotation)
  429. {
  430. var t = mods[0].previousValue.target as Transform;
  431. if (t == null)
  432. t = (Transform)mods[0].currentValue.target;
  433. position = t.localPosition;
  434. rotation = t.localRotation;
  435. foreach (var mod in mods)
  436. {
  437. switch (mod.previousValue.propertyPath)
  438. {
  439. case kLocalPosition + ".x":
  440. position.x = ParseFloat(mod.previousValue.value, position.x);
  441. break;
  442. case kLocalPosition + ".y":
  443. position.y = ParseFloat(mod.previousValue.value, position.y);
  444. break;
  445. case kLocalPosition + ".z":
  446. position.z = ParseFloat(mod.previousValue.value, position.z);
  447. break;
  448. case kLocalRotation + ".x":
  449. rotation.x = ParseFloat(mod.previousValue.value, rotation.x);
  450. break;
  451. case kLocalRotation + ".y":
  452. rotation.y = ParseFloat(mod.previousValue.value, rotation.y);
  453. break;
  454. case kLocalRotation + ".z":
  455. rotation.z = ParseFloat(mod.previousValue.value, rotation.z);
  456. break;
  457. case kLocalRotation + ".w":
  458. rotation.w = ParseFloat(mod.previousValue.value, rotation.w);
  459. break;
  460. }
  461. }
  462. }
  463. internal static void GetCurrentPositionAndRotation(UndoPropertyModification[] mods, ref Vector3 position, ref Quaternion rotation)
  464. {
  465. var t = (Transform)mods[0].currentValue.target;
  466. position = t.localPosition;
  467. rotation = t.localRotation;
  468. foreach (var mod in mods)
  469. {
  470. switch (mod.currentValue.propertyPath)
  471. {
  472. case kLocalPosition + ".x":
  473. position.x = ParseFloat(mod.currentValue.value, position.x);
  474. break;
  475. case kLocalPosition + ".y":
  476. position.y = ParseFloat(mod.currentValue.value, position.y);
  477. break;
  478. case kLocalPosition + ".z":
  479. position.z = ParseFloat(mod.currentValue.value, position.z);
  480. break;
  481. case kLocalRotation + ".x":
  482. rotation.x = ParseFloat(mod.currentValue.value, rotation.x);
  483. break;
  484. case kLocalRotation + ".y":
  485. rotation.y = ParseFloat(mod.currentValue.value, rotation.y);
  486. break;
  487. case kLocalRotation + ".z":
  488. rotation.z = ParseFloat(mod.currentValue.value, rotation.z);
  489. break;
  490. case kLocalRotation + ".w":
  491. rotation.w = ParseFloat(mod.currentValue.value, rotation.w);
  492. break;
  493. }
  494. }
  495. }
  496. // when making the previous position and rotation
  497. internal static void SetPreviousPositionAndRotation(UndoPropertyModification[] mods, Vector3 pos, Quaternion rot)
  498. {
  499. foreach (var mod in mods)
  500. {
  501. switch (mod.previousValue.propertyPath)
  502. {
  503. case kLocalPosition + ".x":
  504. mod.previousValue.value = pos.x.ToString(EditorGUI.kFloatFieldFormatString);
  505. break;
  506. case kLocalPosition + ".y":
  507. mod.previousValue.value = pos.y.ToString(EditorGUI.kFloatFieldFormatString);
  508. break;
  509. case kLocalPosition + ".z":
  510. mod.previousValue.value = pos.z.ToString(EditorGUI.kFloatFieldFormatString);
  511. break;
  512. case kLocalRotation + ".x":
  513. mod.previousValue.value = rot.x.ToString(EditorGUI.kFloatFieldFormatString);
  514. break;
  515. case kLocalRotation + ".y":
  516. mod.previousValue.value = rot.y.ToString(EditorGUI.kFloatFieldFormatString);
  517. break;
  518. case kLocalRotation + ".z":
  519. mod.previousValue.value = rot.z.ToString(EditorGUI.kFloatFieldFormatString);
  520. break;
  521. case kLocalRotation + ".w":
  522. mod.previousValue.value = rot.w.ToString(EditorGUI.kFloatFieldFormatString);
  523. break;
  524. }
  525. }
  526. }
  527. internal static void SetCurrentPositionAndRotation(UndoPropertyModification[] mods, Vector3 pos, Quaternion rot)
  528. {
  529. foreach (var mod in mods)
  530. {
  531. switch (mod.previousValue.propertyPath)
  532. {
  533. case kLocalPosition + ".x":
  534. mod.currentValue.value = pos.x.ToString(EditorGUI.kFloatFieldFormatString);
  535. break;
  536. case kLocalPosition + ".y":
  537. mod.currentValue.value = pos.y.ToString(EditorGUI.kFloatFieldFormatString);
  538. break;
  539. case kLocalPosition + ".z":
  540. mod.currentValue.value = pos.z.ToString(EditorGUI.kFloatFieldFormatString);
  541. break;
  542. case kLocalRotation + ".x":
  543. mod.currentValue.value = rot.x.ToString(EditorGUI.kFloatFieldFormatString);
  544. break;
  545. case kLocalRotation + ".y":
  546. mod.currentValue.value = rot.y.ToString(EditorGUI.kFloatFieldFormatString);
  547. break;
  548. case kLocalRotation + ".z":
  549. mod.currentValue.value = rot.z.ToString(EditorGUI.kFloatFieldFormatString);
  550. break;
  551. case kLocalRotation + ".w":
  552. mod.currentValue.value = rot.w.ToString(EditorGUI.kFloatFieldFormatString);
  553. break;
  554. }
  555. }
  556. }
  557. internal static float ParseFloat(string str, float defaultVal)
  558. {
  559. float temp = 0.0f;
  560. if (float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out temp))
  561. return temp;
  562. return defaultVal;
  563. }
  564. internal static UndoPropertyModification[] HandleEulerModifications(AnimationTrack track, TimelineClip clip, AnimationClip animClip, float time, UndoPropertyModification[] mods)
  565. {
  566. if (mods.Any(x => x.currentValue.propertyPath.StartsWith(kLocalEulerHint) || x.currentValue.propertyPath.StartsWith(kLocalRotation)))
  567. {
  568. // if there is a rotational offsets, we need to strip the euler hints, since they are used by the animation recording system
  569. // over the quaternion.
  570. var localToTrack = GetLocalToTrack(track, clip);
  571. if (localToTrack.rotation != Quaternion.identity)
  572. {
  573. if (s_LastTrackWarning != track)
  574. {
  575. s_LastTrackWarning = track;
  576. Debug.LogWarning(kRotationWarning);
  577. }
  578. Transform transform = mods[0].currentValue.target as Transform;
  579. if (transform != null)
  580. {
  581. var trackToLocal = TimelineAnimationUtilities.RigidTransform.Inverse(localToTrack);
  582. // since the euler angles are going to be transformed, we do a best guess at a euler that gives the shortest path
  583. var quatMods = mods.Where(x => !x.currentValue.propertyPath.StartsWith(kLocalEulerHint));
  584. var eulerMods = FindBestEulerHint(trackToLocal.rotation * transform.localRotation, animClip, time, transform);
  585. return quatMods.Union(eulerMods).ToArray();
  586. }
  587. return mods.Where(x => !x.currentValue.propertyPath.StartsWith(kLocalEulerHint)).ToArray();
  588. }
  589. }
  590. return mods;
  591. }
  592. internal static IEnumerable<UndoPropertyModification> FindBestEulerHint(Quaternion rotation, AnimationClip clip, float time, Transform transform)
  593. {
  594. Vector3 euler = rotation.eulerAngles;
  595. var xCurve = AnimationUtility.GetEditorCurve(clip, EditorCurveBinding.FloatCurve(string.Empty, typeof(Transform), "localEulerAnglesRaw.x"));
  596. var yCurve = AnimationUtility.GetEditorCurve(clip, EditorCurveBinding.FloatCurve(string.Empty, typeof(Transform), "localEulerAnglesRaw.y"));
  597. var zCurve = AnimationUtility.GetEditorCurve(clip, EditorCurveBinding.FloatCurve(string.Empty, typeof(Transform), "localEulerAnglesRaw.z"));
  598. if (xCurve != null)
  599. euler.x = xCurve.Evaluate(time);
  600. if (yCurve != null)
  601. euler.y = yCurve.Evaluate(time);
  602. if (zCurve != null)
  603. euler.z = zCurve.Evaluate(time);
  604. euler = QuaternionCurveTangentCalculation.GetEulerFromQuaternion(rotation, euler);
  605. return new[]
  606. {
  607. PropertyModificationToUndoPropertyModification(new PropertyModification {target = transform, propertyPath = kLocalEulerHint + ".x", value = euler.x.ToString() }),
  608. PropertyModificationToUndoPropertyModification(new PropertyModification {target = transform, propertyPath = kLocalEulerHint + ".y", value = euler.y.ToString() }),
  609. PropertyModificationToUndoPropertyModification(new PropertyModification {target = transform, propertyPath = kLocalEulerHint + ".z", value = euler.z.ToString() })
  610. };
  611. }
  612. }
  613. }