RemapAnimationBindingsTool.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using UnityEditor;
  7. using UnityEditorInternal;
  8. using UnityEngine;
  9. namespace Animancer.Editor.Tools
  10. {
  11. /// <summary>[Editor-Only] [Pro-Only]
  12. /// A <see cref="AnimancerToolsWindow.Tool"/> for changing which bones an <see cref="AnimationClip"/>s controls.
  13. /// </summary>
  14. /// <remarks>
  15. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/tools/remap-animation-bindings">Remap Animation Bindings</see>
  16. /// </remarks>
  17. /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Tools/RemapAnimationBindingsTool
  18. ///
  19. [Serializable]
  20. public class RemapAnimationBindingsTool : AnimationModifierTool
  21. {
  22. /************************************************************************************************************************/
  23. [SerializeField] private List<string> _NewBindingPaths;
  24. [NonSerialized] private List<List<EditorCurveBinding>> _BindingGroups;
  25. [NonSerialized] private List<string> _OldBindingPaths;
  26. [NonSerialized] private bool _OldBindingPathsAreDirty;
  27. [NonSerialized] private ReorderableList _OldBindingPathsDisplay;
  28. [NonSerialized] private ReorderableList _NewBindingPathsDisplay;
  29. /************************************************************************************************************************/
  30. /// <inheritdoc/>
  31. public override int DisplayOrder => 5;
  32. /// <inheritdoc/>
  33. public override string Name => "Remap Animation Bindings";
  34. /// <inheritdoc/>
  35. public override string HelpURL => Strings.DocsURLs.RemapAnimationBindings;
  36. /// <inheritdoc/>
  37. public override string Instructions
  38. {
  39. get
  40. {
  41. if (Animation == null)
  42. return "Select the animation you want to remap.";
  43. if (_OldBindingPaths.Count == 0)
  44. {
  45. if (Animation.humanMotion)
  46. return "The selected animation only has Humanoid bindings which cannot be remapped.";
  47. return "The selected animation does not have any bindings.";
  48. }
  49. return "Enter the new paths to change the bindings into then click Save As.";
  50. }
  51. }
  52. /************************************************************************************************************************/
  53. /// <inheritdoc/>
  54. public override void OnEnable(int index)
  55. {
  56. base.OnEnable(index);
  57. _BindingGroups = new List<List<EditorCurveBinding>>();
  58. _OldBindingPaths = new List<string>();
  59. if (_NewBindingPaths == null)
  60. _NewBindingPaths = new List<string>();
  61. if (Animation == null)
  62. _NewBindingPaths.Clear();
  63. _OldBindingPathsDisplay = AnimancerToolsWindow.CreateReorderableStringList(_OldBindingPaths, "Old Binding Paths");
  64. _NewBindingPathsDisplay = AnimancerToolsWindow.CreateReorderableStringList(_NewBindingPaths, "New Binding Paths", (area, i) =>
  65. {
  66. var color = GUI.color;
  67. var path = _NewBindingPaths[i];
  68. if (path != _OldBindingPaths[i])
  69. GUI.color = new Color(0.15f, 0.7f, 0.15f, 1);
  70. path = EditorGUI.TextField(area, path);
  71. GUI.color = color;
  72. return path;
  73. });
  74. }
  75. /************************************************************************************************************************/
  76. /// <inheritdoc/>
  77. protected override void OnAnimationChanged()
  78. {
  79. base.OnAnimationChanged();
  80. _OldBindingPathsAreDirty = true;
  81. }
  82. /************************************************************************************************************************/
  83. /// <inheritdoc/>
  84. public override void DoBodyGUI()
  85. {
  86. base.DoBodyGUI();
  87. GatherBindings();
  88. GUILayout.BeginHorizontal();
  89. {
  90. GUILayout.BeginVertical();
  91. GUI.enabled = false;
  92. _OldBindingPathsDisplay.DoLayoutList();
  93. GUI.enabled = true;
  94. GUILayout.EndVertical();
  95. GUILayout.BeginVertical();
  96. _NewBindingPathsDisplay.DoLayoutList();
  97. GUILayout.EndVertical();
  98. }
  99. GUILayout.EndHorizontal();
  100. GUI.enabled = Animation != null;
  101. GUILayout.BeginHorizontal();
  102. {
  103. GUILayout.FlexibleSpace();
  104. if (GUILayout.Button("Reset"))
  105. {
  106. AnimancerGUI.Deselect();
  107. AnimancerToolsWindow.RecordUndo();
  108. _NewBindingPaths.Clear();
  109. _OldBindingPathsAreDirty = true;
  110. }
  111. if (GUILayout.Button("Copy All"))
  112. {
  113. AnimancerGUI.Deselect();
  114. CopyAll();
  115. }
  116. if (GUILayout.Button("Paste All"))
  117. {
  118. AnimancerGUI.Deselect();
  119. PasteAll();
  120. }
  121. if (GUILayout.Button("Save As"))
  122. {
  123. if (SaveAs())
  124. {
  125. _OldBindingPathsAreDirty = true;
  126. }
  127. }
  128. }
  129. GUILayout.EndHorizontal();
  130. }
  131. /************************************************************************************************************************/
  132. /// <summary>Gathers the bindings from the <see cref="AnimationModifierTool.Animation"/>.</summary>
  133. private void GatherBindings()
  134. {
  135. if (!_OldBindingPathsAreDirty)
  136. return;
  137. _OldBindingPathsAreDirty = false;
  138. _BindingGroups.Clear();
  139. _OldBindingPaths.Clear();
  140. if (Animation == null)
  141. {
  142. _NewBindingPaths.Clear();
  143. return;
  144. }
  145. var isHumanoid = Animation.humanMotion;
  146. AnimationBindings.OnAnimationChanged(Animation);
  147. var bindings = AnimationBindings.GetBindings(Animation);
  148. Array.Sort(bindings, (a, b) =>
  149. {
  150. var result = EditorUtility.NaturalCompare(a.path, b.path);
  151. if (result != 0)
  152. return result;
  153. return EditorUtility.NaturalCompare(a.propertyName, b.propertyName);
  154. });
  155. string previousPath = null;
  156. List<EditorCurveBinding> previousGroup = null;
  157. for (int i = 0; i < bindings.Length; i++)
  158. {
  159. var binding = bindings[i];
  160. if (isHumanoid &&
  161. string.IsNullOrEmpty(binding.path) &&
  162. IsHumanoidBinding(binding.propertyName))
  163. continue;
  164. var path = binding.path;
  165. if (path == previousPath)
  166. {
  167. previousGroup.Add(binding);
  168. continue;
  169. }
  170. previousPath = path;
  171. previousGroup = new List<EditorCurveBinding> { binding };
  172. _BindingGroups.Add(previousGroup);
  173. _OldBindingPaths.Add(path);
  174. if (_NewBindingPaths.Count < _OldBindingPaths.Count)
  175. _NewBindingPaths.Add(path);
  176. }
  177. if (_NewBindingPaths.Count > _OldBindingPaths.Count)
  178. _NewBindingPaths.RemoveRange(_OldBindingPaths.Count, _NewBindingPaths.Count - _OldBindingPaths.Count);
  179. }
  180. /************************************************************************************************************************/
  181. private static HashSet<string> _HumanoidBindingNames;
  182. /// <summary>Is the `propertyName` one of the bindings used by Humanoid animations?</summary>
  183. private static bool IsHumanoidBinding(string propertyName)
  184. {
  185. if (_HumanoidBindingNames == null)
  186. {
  187. _HumanoidBindingNames = new HashSet<string>
  188. {
  189. "RootT.x", "RootT.y", "RootT.z",
  190. "RootQ.x", "RootQ.y", "RootQ.z", "RootQ.w",
  191. "LeftFootT.x", "LeftFootT.y", "LeftFootT.z",
  192. "LeftFootQ.x", "LeftFootQ.y", "LeftFootQ.z", "LeftFootQ.w",
  193. "RightFootT.x", "RightFootT.y", "RightFootT.z",
  194. "RightFootQ.x", "RightFootQ.y", "RightFootQ.z", "RightFootQ.w",
  195. "LeftHandT.x", "LeftHandT.y", "LeftHandT.z",
  196. "LeftHandQ.x", "LeftHandQ.y", "LeftHandQ.z", "LeftHandQ.w",
  197. "RightHandT.x", "RightHandT.y", "RightHandT.z",
  198. "RightHandQ.x", "RightHandQ.y", "RightHandQ.z", "RightHandQ.w",
  199. "Spine Front-Back", "Spine Left-Right", "Spine Twist Left-Right",
  200. "Chest Front-Back", "Chest Left-Right", "Chest Twist Left-Right",
  201. "UpperChest Front-Back", "UpperChest Left-Right", "UpperChest Twist Left-Right",
  202. "Neck Nod Down-Up", "Neck Tilt Left-Right", "Neck Turn Left-Right",
  203. "Head Nod Down-Up", "Head Tilt Left-Right", "Head Turn Left-Right",
  204. "Left Eye Down-Up", "Left Eye In-Out",
  205. "Right Eye Down-Up", "Right Eye In-Out",
  206. "Jaw Close", "Jaw Left-Right",
  207. "Left Upper Leg Front-Back", "Left Upper Leg In-Out", "Left Upper Leg Twist In-Out",
  208. "Left Lower Leg Stretch", "Left Lower Leg Twist In-Out",
  209. "Left Foot Up-Down", "Left Foot Twist In-Out",
  210. "Left Toes Up-Down",
  211. "Right Upper Leg Front-Back", "Right Upper Leg In-Out", "Right Upper Leg Twist In-Out",
  212. "Right Lower Leg Stretch", "Right Lower Leg Twist In-Out",
  213. "Right Foot Up-Down", "Right Foot Twist In-Out",
  214. "Right Toes Up-Down",
  215. "Left Shoulder Down-Up", "Left Shoulder Front-Back",
  216. "Left Arm Down-Up", "Left Arm Front-Back", "Left Arm Twist In-Out",
  217. "Left Forearm Stretch", "Left Forearm Twist In-Out",
  218. "Left Hand Down-Up", "Left Hand In-Out",
  219. "Right Shoulder Down-Up", "Right Shoulder Front-Back",
  220. "Right Arm Down-Up", "Right Arm Front-Back", "Right Arm Twist In-Out",
  221. "Right Forearm Stretch", "Right Forearm Twist In-Out",
  222. "Right Hand Down-Up", "Right Hand In-Out",
  223. "LeftHand.Thumb.Spread", "LeftHand.Thumb.1 Stretched", "LeftHand.Thumb.2 Stretched", "LeftHand.Thumb.3 Stretched",
  224. "LeftHand.Index.Spread", "LeftHand.Index.1 Stretched", "LeftHand.Index.2 Stretched", "LeftHand.Index.3 Stretched",
  225. "LeftHand.Middle.Spread", "LeftHand.Middle.1 Stretched", "LeftHand.Middle.2 Stretched", "LeftHand.Middle.3 Stretched",
  226. "LeftHand.Ring.Spread", "LeftHand.Ring.1 Stretched", "LeftHand.Ring.2 Stretched", "LeftHand.Ring.3 Stretched",
  227. "LeftHand.Little.Spread", "LeftHand.Little.1 Stretched", "LeftHand.Little.2 Stretched", "LeftHand.Little.3 Stretched",
  228. "RightHand.Thumb.Spread", "RightHand.Thumb.1 Stretched", "RightHand.Thumb.2 Stretched", "RightHand.Thumb.3 Stretched",
  229. "RightHand.Index.Spread", "RightHand.Index.1 Stretched", "RightHand.Index.2 Stretched", "RightHand.Index.3 Stretched",
  230. "RightHand.Middle.Spread", "RightHand.Middle.1 Stretched", "RightHand.Middle.2 Stretched", "RightHand.Middle.3 Stretched",
  231. "RightHand.Ring.Spread", "RightHand.Ring.1 Stretched", "RightHand.Ring.2 Stretched", "RightHand.Ring.3 Stretched",
  232. "RightHand.Little.Spread", "RightHand.Little.1 Stretched", "RightHand.Little.2 Stretched", "RightHand.Little.3 Stretched",
  233. };
  234. }
  235. return _HumanoidBindingNames.Contains(propertyName);
  236. }
  237. /************************************************************************************************************************/
  238. /// <summary>Copies all of the <see cref="_NewBindingPaths"/> to the system clipboard.</summary>
  239. private void CopyAll()
  240. {
  241. var text = ObjectPool.AcquireStringBuilder();
  242. for (int i = 0; i < _NewBindingPaths.Count; i++)
  243. {
  244. text.AppendLine(_NewBindingPaths[i]);
  245. }
  246. EditorGUIUtility.systemCopyBuffer = text.ReleaseToString();
  247. }
  248. /// <summary>Pastes the string from the system clipboard into the <see cref="_NewBindingPaths"/>.</summary>
  249. private void PasteAll()
  250. {
  251. using (var reader = new StringReader(EditorGUIUtility.systemCopyBuffer))
  252. {
  253. for (int i = 0; i < _NewBindingPaths.Count; i++)
  254. {
  255. var line = reader.ReadLine();
  256. if (line == null)
  257. return;
  258. _NewBindingPaths[i] = line;
  259. }
  260. }
  261. }
  262. /************************************************************************************************************************/
  263. /// <inheritdoc/>
  264. protected override void Modify(AnimationClip animation)
  265. {
  266. for (int iGroup = 0; iGroup < _BindingGroups.Count; iGroup++)
  267. {
  268. var oldPath = _OldBindingPaths[iGroup];
  269. var newPath = _NewBindingPaths[iGroup];
  270. if (oldPath == newPath)
  271. continue;
  272. var group = _BindingGroups[iGroup];
  273. for (int iBinding = 0; iBinding < group.Count; iBinding++)
  274. {
  275. var binding = group[iBinding];
  276. if (binding.isPPtrCurve)
  277. {
  278. var curve = AnimationUtility.GetObjectReferenceCurve(animation, binding);
  279. AnimationUtility.SetObjectReferenceCurve(animation, binding, null);
  280. binding.path = newPath;
  281. AnimationUtility.SetObjectReferenceCurve(animation, binding, curve);
  282. }
  283. else
  284. {
  285. var curve = AnimationUtility.GetEditorCurve(animation, binding);
  286. AnimationUtility.SetEditorCurve(animation, binding, null);
  287. binding.path = newPath;
  288. AnimationUtility.SetEditorCurve(animation, binding, curve);
  289. }
  290. }
  291. }
  292. }
  293. /************************************************************************************************************************/
  294. }
  295. }
  296. #endif