RotationLimitSplineInspector.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. using UnityEditor;
  2. using UnityEngine;
  3. using System.Collections;
  4. using System;
  5. namespace RootMotion.FinalIK
  6. {
  7. /*
  8. * Custom inspector for RotationLimitSpline
  9. * */
  10. [CustomEditor(typeof(RotationLimitSpline))]
  11. [CanEditMultipleObjects]
  12. public class RotationLimitSplineInspector : RotationLimitInspector
  13. {
  14. // Determines if we are dragging the handle's limits or angle
  15. public enum ScaleMode
  16. {
  17. Limit,
  18. Angle
  19. }
  20. // In Smooth TangentMode, in and out tangents will always be the same, Independent allowes for difference
  21. public enum TangentMode
  22. {
  23. Smooth,
  24. Independent
  25. }
  26. private RotationLimitSpline script { get { return target as RotationLimitSpline; } }
  27. private RotationLimitSpline clone;
  28. private ScaleMode scaleMode;
  29. private TangentMode tangentMode;
  30. private int selectedHandle = -1, deleteHandle = -1, addHandle = -1;
  31. #region Inspector
  32. public void OnEnable()
  33. {
  34. // Check if RotationLimitspline is properly set up, if not, reset to defaults
  35. if (script.spline == null)
  36. {
  37. script.spline = new AnimationCurve(defaultKeys);
  38. EditorUtility.SetDirty(script);
  39. }
  40. else if (script.spline.keys.Length < 4)
  41. {
  42. script.spline.keys = defaultKeys;
  43. EditorUtility.SetDirty(script);
  44. }
  45. }
  46. /*
  47. * Returns the default keyframes for the RotationLimitspline
  48. * */
  49. private static Keyframe[] defaultKeys
  50. {
  51. get
  52. {
  53. Keyframe[] k = new Keyframe[5];
  54. // Values for a simple elliptic spline
  55. k[0].time = 0.01f;
  56. k[0].value = 30;
  57. k[0].inTangent = 0.01f;
  58. k[0].outTangent = 0.01f;
  59. k[1].time = 90;
  60. k[1].value = 45;
  61. k[1].inTangent = 0.01f;
  62. k[1].outTangent = 0.01f;
  63. k[2].time = 180;
  64. k[2].value = 30;
  65. k[2].inTangent = 0.01f;
  66. k[2].outTangent = 0.01f;
  67. k[3].time = 270;
  68. k[3].value = 45;
  69. k[3].inTangent = 0.01f;
  70. k[3].outTangent = 0.01f;
  71. k[4].time = 360;
  72. k[4].value = 30;
  73. k[4].inTangent = 0.01f;
  74. k[4].outTangent = 0.01f;
  75. return k;
  76. }
  77. }
  78. public override void OnInspectorGUI()
  79. {
  80. GUI.changed = false;
  81. DrawDefaultInspector();
  82. script.twistLimit = Mathf.Clamp(script.twistLimit, 0, 180);
  83. if (GUI.changed) EditorUtility.SetDirty(script);
  84. }
  85. /*
  86. * Make sure the keyframes and tangents are valid
  87. * */
  88. private void ValidateKeyframes(Keyframe[] keys)
  89. {
  90. keys[keys.Length - 1].value = keys[0].value;
  91. keys[keys.Length - 1].time = keys[0].time + 360;
  92. keys[keys.Length - 1].inTangent = keys[0].inTangent;
  93. }
  94. #endregion Inspector
  95. #region Scene
  96. void OnSceneGUI()
  97. {
  98. GUI.changed = false;
  99. // Get the keyframes of the AnimationCurve to be manipulated
  100. Keyframe[] keys = script.spline.keys;
  101. // Set defaultLocalRotation so that the initial local rotation will be the zero point for the rotation limit
  102. if (!Application.isPlaying && !script.defaultLocalRotationOverride) script.defaultLocalRotation = script.transform.localRotation;
  103. if (script.axis == Vector3.zero) return;
  104. // Make the curve loop
  105. script.spline.postWrapMode = WrapMode.Loop;
  106. script.spline.preWrapMode = WrapMode.Loop;
  107. DrawRotationSphere(script.transform.position);
  108. // Display the main axis
  109. DrawArrow(script.transform.position, Direction(script.axis), colorDefault, "Axis", 0.02f);
  110. Vector3 swing = script.axis.normalized;
  111. // Editing tools GUI
  112. Handles.BeginGUI();
  113. GUILayout.BeginArea(new Rect(10, 10, 440, 100), "Rotation Limit Spline", "Window");
  114. // Scale Mode and Tangent Mode
  115. GUILayout.BeginHorizontal();
  116. scaleMode = (ScaleMode)EditorGUILayout.EnumPopup("Drag Handle", scaleMode);
  117. tangentMode = (TangentMode)EditorGUILayout.EnumPopup("Drag Tangents", tangentMode);
  118. GUILayout.EndHorizontal();
  119. EditorGUILayout.Space();
  120. if (Inspector.Button("Rotate 90 degrees", "Rotate rotation limit around axis.", script, GUILayout.Width(220)))
  121. {
  122. if (!Application.isPlaying) Undo.RecordObject(script, "Handle Value");
  123. for (int i = 0; i < keys.Length; i++) keys[i].time += 90;
  124. }
  125. // Cloning values from another RotationLimitSpline
  126. EditorGUILayout.BeginHorizontal();
  127. if (Inspector.Button("Clone From", "Make this rotation limit identical to another", script, GUILayout.Width(220)))
  128. {
  129. CloneLimit();
  130. keys = script.spline.keys;
  131. }
  132. clone = (RotationLimitSpline)EditorGUILayout.ObjectField("", clone, typeof(RotationLimitSpline), true);
  133. EditorGUILayout.EndHorizontal();
  134. GUILayout.EndArea();
  135. Handles.EndGUI();
  136. // Draw keyframes
  137. for (int i = 0; i < keys.Length - 1; i++)
  138. {
  139. float angle = keys[i].time;
  140. // Start drawing handles
  141. Quaternion offset = Quaternion.AngleAxis(angle, swing);
  142. Quaternion rotation = Quaternion.AngleAxis(keys[i].value, offset * script.crossAxis);
  143. Vector3 position = script.transform.position + Direction(rotation * swing);
  144. Handles.Label(position, " " + i.ToString());
  145. // Dragging Values
  146. if (selectedHandle == i)
  147. {
  148. Handles.color = colorHandles;
  149. switch (scaleMode)
  150. {
  151. case ScaleMode.Limit:
  152. float inputValue = keys[i].value;
  153. inputValue = Mathf.Clamp(Inspector.ScaleValueHandleSphere(inputValue, position, Quaternion.identity, 0.5f, 0), 0.01f, 180);
  154. if (keys[i].value != inputValue)
  155. {
  156. if (!Application.isPlaying) Undo.RecordObject(script, "Handle Value");
  157. keys[i].value = inputValue;
  158. }
  159. break;
  160. case ScaleMode.Angle:
  161. float inputTime = keys[i].time;
  162. inputTime = Inspector.ScaleValueHandleSphere(inputTime, position, Quaternion.identity, 0.5f, 0);
  163. if (keys[i].time != inputTime)
  164. {
  165. if (!Application.isPlaying) Undo.RecordObject(script, "Handle Angle");
  166. keys[i].time = inputTime;
  167. }
  168. break;
  169. }
  170. }
  171. // Handle select button
  172. if (selectedHandle != i)
  173. {
  174. Handles.color = Color.blue;
  175. if (Inspector.SphereButton(position, script.transform.rotation, 0.05f, 0.05f))
  176. {
  177. selectedHandle = i;
  178. }
  179. }
  180. // Tangents
  181. if (selectedHandle == i)
  182. {
  183. // Evaluate positions before and after the key to get the tangent positions
  184. Vector3 prevPosition = GetAnglePosition(keys[i].time - 1);
  185. Vector3 nextPosition = GetAnglePosition(keys[i].time + 1);
  186. // Draw handles for the tangents
  187. Handles.color = Color.white;
  188. Vector3 toNext = (nextPosition - position).normalized * 0.3f;
  189. float outTangent = keys[i].outTangent;
  190. outTangent = Inspector.ScaleValueHandleSphere(outTangent, position + toNext, Quaternion.identity, 0.2f, 0);
  191. Vector3 toPrev = (prevPosition - position).normalized * 0.3f;
  192. float inTangent = keys[i].inTangent;
  193. inTangent = Inspector.ScaleValueHandleSphere(inTangent, position + toPrev, Quaternion.identity, 0.2f, 0);
  194. if (outTangent != keys[i].outTangent || inTangent != keys[i].inTangent) selectedHandle = i;
  195. // Make the other tangent match the dragged tangent (if in "Smooth" TangentMode)
  196. switch (tangentMode)
  197. {
  198. case TangentMode.Smooth:
  199. if (outTangent != keys[i].outTangent)
  200. {
  201. if (!Application.isPlaying) Undo.RecordObject(script, "Tangents");
  202. keys[i].outTangent = outTangent;
  203. keys[i].inTangent = outTangent;
  204. }
  205. else if (inTangent != keys[i].inTangent)
  206. {
  207. if (!Application.isPlaying) Undo.RecordObject(script, "Tangents");
  208. keys[i].outTangent = inTangent;
  209. keys[i].inTangent = inTangent;
  210. }
  211. break;
  212. case TangentMode.Independent:
  213. if (outTangent != keys[i].outTangent)
  214. {
  215. if (!Application.isPlaying) Undo.RecordObject(script, "Tangents");
  216. keys[i].outTangent = outTangent;
  217. }
  218. else if (inTangent != keys[i].inTangent)
  219. {
  220. if (!Application.isPlaying) Undo.RecordObject(script, "Tangents");
  221. keys[i].inTangent = inTangent;
  222. }
  223. break;
  224. }
  225. // Draw lines and labels to tangent handles
  226. Handles.color = Color.white;
  227. GUI.color = Color.white;
  228. Handles.DrawLine(position, position + toNext);
  229. Handles.Label(position + toNext, " Out");
  230. Handles.DrawLine(position, position + toPrev);
  231. Handles.Label(position + toPrev, " In");
  232. }
  233. }
  234. // Selected Point GUI
  235. if (selectedHandle != -1)
  236. {
  237. Handles.BeginGUI();
  238. GUILayout.BeginArea(new Rect(Screen.width - 240, Screen.height - 200, 230, 150), "Handle " + selectedHandle.ToString(), "Window");
  239. if (Inspector.Button("Delete", "Delete this handle", script))
  240. {
  241. if (keys.Length > 4)
  242. {
  243. deleteHandle = selectedHandle;
  244. }
  245. else if (!Warning.logged) script.LogWarning("Spline Rotation Limit should have at least 3 handles");
  246. }
  247. if (Inspector.Button("Add Handle", "Add a new handle next to this one", script))
  248. {
  249. addHandle = selectedHandle;
  250. }
  251. // Clamp the key angles to previous and next handle angles
  252. float prevTime = 0, nextTime = 0;
  253. if (selectedHandle < keys.Length - 2) nextTime = keys[selectedHandle + 1].time;
  254. else nextTime = keys[0].time + 360;
  255. if (selectedHandle == 0) prevTime = keys[keys.Length - 2].time - 360;
  256. else prevTime = keys[selectedHandle - 1].time;
  257. // Angles
  258. float inputTime = keys[selectedHandle].time;
  259. inputTime = Mathf.Clamp(EditorGUILayout.FloatField(new GUIContent("Angle", "Angle of the point (0-360)."), inputTime), prevTime, nextTime);
  260. if (keys[selectedHandle].time != inputTime)
  261. {
  262. if (!Application.isPlaying) Undo.RecordObject(script, "Handle Angle");
  263. keys[selectedHandle].time = inputTime;
  264. }
  265. // Limits
  266. float inputValue = keys[selectedHandle].value;
  267. inputValue = Mathf.Clamp(EditorGUILayout.FloatField(new GUIContent("Limit", "Max angular limit from Axis at this angle"), inputValue), 0, 180);
  268. if (keys[selectedHandle].value != inputValue)
  269. {
  270. if (!Application.isPlaying) Undo.RecordObject(script, "Handle Limit");
  271. keys[selectedHandle].value = inputValue;
  272. }
  273. // In Tangents
  274. float inputInTangent = keys[selectedHandle].inTangent;
  275. inputInTangent = EditorGUILayout.FloatField(new GUIContent("In Tangent", "In tangent of the handle point on the spline"), inputInTangent);
  276. if (keys[selectedHandle].inTangent != inputInTangent)
  277. {
  278. if (!Application.isPlaying) Undo.RecordObject(script, "Handle In Tangent");
  279. keys[selectedHandle].inTangent = inputInTangent;
  280. }
  281. // Out tangents
  282. float inputOutTangent = keys[selectedHandle].outTangent;
  283. inputOutTangent = EditorGUILayout.FloatField(new GUIContent("Out Tangent", "Out tangent of the handle point on the spline"), inputOutTangent);
  284. if (keys[selectedHandle].outTangent != inputOutTangent)
  285. {
  286. if (!Application.isPlaying) Undo.RecordObject(script, "Handle Out Tangent");
  287. keys[selectedHandle].outTangent = inputOutTangent;
  288. }
  289. GUILayout.EndArea();
  290. Handles.EndGUI();
  291. }
  292. // Make sure the keyframes are valid;
  293. ValidateKeyframes(keys);
  294. // Replace the AnimationCurve keyframes with the manipulated keyframes
  295. script.spline.keys = keys;
  296. // Display limits
  297. for (int i = 0; i < 360; i += 2)
  298. {
  299. float evaluatedLimit = script.spline.Evaluate((float)i);
  300. Quaternion offset = Quaternion.AngleAxis(i, swing);
  301. Quaternion evaluatedRotation = Quaternion.AngleAxis(evaluatedLimit, offset * script.crossAxis);
  302. Quaternion testRotation = Quaternion.AngleAxis(179.9f, offset * script.crossAxis);
  303. Quaternion limitedRotation = script.LimitSwing(testRotation);
  304. Vector3 evaluatedDirection = evaluatedRotation * swing;
  305. Vector3 limitedDirection = limitedRotation * swing;
  306. // Display the limit points in red if they are out of range
  307. bool isValid = Vector3.Distance(evaluatedDirection, limitedDirection) < 0.01f && evaluatedLimit >= 0;
  308. Color color = isValid ? colorDefaultTransparent : colorInvalid;
  309. Vector3 limitPoint = script.transform.position + Direction(evaluatedDirection);
  310. Handles.color = color;
  311. if (i == 0) zeroPoint = limitPoint;
  312. Handles.DrawLine(script.transform.position, limitPoint);
  313. if (i > 0)
  314. {
  315. Handles.color = isValid ? colorDefault : colorInvalid;
  316. Handles.DrawLine(limitPoint, lastPoint);
  317. if (i == 358) Handles.DrawLine(limitPoint, zeroPoint);
  318. }
  319. lastPoint = limitPoint;
  320. }
  321. // Deleting points
  322. if (deleteHandle != -1)
  323. {
  324. DeleteHandle(deleteHandle);
  325. selectedHandle = -1;
  326. deleteHandle = -1;
  327. }
  328. // Adding points
  329. if (addHandle != -1)
  330. {
  331. AddHandle(addHandle);
  332. addHandle = -1;
  333. }
  334. Handles.color = Color.white;
  335. if (GUI.changed) EditorUtility.SetDirty(script);
  336. }
  337. private Vector3 lastPoint, zeroPoint;
  338. /*
  339. * Return the evaluated position for the specified angle
  340. * */
  341. private Vector3 GetAnglePosition(float angle)
  342. {
  343. Vector3 swing = script.axis.normalized;
  344. Quaternion offset = Quaternion.AngleAxis(angle, swing);
  345. Quaternion rotation = Quaternion.AngleAxis(script.spline.Evaluate(angle), offset * script.crossAxis);
  346. return script.transform.position + Direction(rotation * swing);
  347. }
  348. /*
  349. * Converting directions from local space to world space
  350. * */
  351. private Vector3 Direction(Vector3 v)
  352. {
  353. if (script.transform.parent == null) return script.defaultLocalRotation * v;
  354. return script.transform.parent.rotation * (script.defaultLocalRotation * v);
  355. }
  356. /*
  357. * Removing Handles
  358. * */
  359. private void DeleteHandle(int p)
  360. {
  361. Keyframe[] keys = script.spline.keys;
  362. Keyframe[] newKeys = new Keyframe[0];
  363. for (int i = 0; i < keys.Length; i++)
  364. {
  365. if (i != p)
  366. {
  367. Array.Resize(ref newKeys, newKeys.Length + 1);
  368. newKeys[newKeys.Length - 1] = keys[i];
  369. }
  370. }
  371. script.spline.keys = newKeys;
  372. }
  373. /*
  374. * Creating new Handles
  375. * */
  376. private void AddHandle(int p)
  377. {
  378. Keyframe[] keys = script.spline.keys;
  379. Keyframe[] newKeys = new Keyframe[keys.Length + 1];
  380. for (int i = 0; i < p + 1; i++) newKeys[i] = keys[i];
  381. float nextTime = 0;
  382. if (p < keys.Length - 1) nextTime = keys[p + 1].time;
  383. else nextTime = keys[0].time;
  384. float newTime = Mathf.Lerp(keys[p].time, nextTime, 0.5f);
  385. float newValue = script.spline.Evaluate(newTime);
  386. newKeys[p + 1] = new Keyframe(newTime, newValue);
  387. for (int i = p + 2; i < newKeys.Length; i++) newKeys[i] = keys[i - 1];
  388. script.spline.keys = newKeys;
  389. }
  390. /*
  391. * Clone properties from another RotationLimitSpline
  392. * */
  393. private void CloneLimit()
  394. {
  395. if (clone == null) return;
  396. if (clone == script)
  397. {
  398. script.LogWarning("Can't clone from self.");
  399. return;
  400. }
  401. script.axis = clone.axis;
  402. script.twistLimit = clone.twistLimit;
  403. script.spline.keys = clone.spline.keys;
  404. }
  405. #endregion Scene
  406. }
  407. }