RotationLimitPolygonalInspector.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. using UnityEditor;
  2. using UnityEngine;
  3. using System.Collections;
  4. using System;
  5. using System.Reflection;
  6. namespace RootMotion.FinalIK
  7. {
  8. /*
  9. * Custom inspector for RotationLimitPolygonal
  10. * */
  11. [CustomEditor(typeof(RotationLimitPolygonal))]
  12. [CanEditMultipleObjects]
  13. public class RotationLimitPolygonalInspector : RotationLimitInspector
  14. {
  15. /*
  16. * Used for quick symmetric editing in the scene
  17. * */
  18. public enum Symmetry
  19. {
  20. Off,
  21. X,
  22. Y,
  23. Z
  24. }
  25. private RotationLimitPolygonal script { get { return target as RotationLimitPolygonal; } }
  26. private RotationLimitPolygonal clone;
  27. private int selectedPoint = -1, deletePoint = -1, addPoint = -1;
  28. private float degrees = 90;
  29. private Symmetry symmetry;
  30. #region Inspector
  31. public void OnEnable()
  32. {
  33. // If initialized, set up the default polygon
  34. if (script.points == null || (script.points != null && script.points.Length < 3))
  35. {
  36. script.ResetToDefault();
  37. EditorUtility.SetDirty(script);
  38. }
  39. }
  40. public override void OnInspectorGUI()
  41. {
  42. GUI.changed = false;
  43. // Clamping values
  44. script.twistLimit = Mathf.Clamp(script.twistLimit, 0, 180);
  45. script.smoothIterations = Mathf.Clamp(script.smoothIterations, 0, 3);
  46. DrawDefaultInspector();
  47. if (GUI.changed) EditorUtility.SetDirty(script);
  48. }
  49. #endregion Inspector
  50. #region Scene
  51. public void OnSceneGUI()
  52. {
  53. GUI.changed = false;
  54. // Set defaultLocalRotation so that the initial local rotation will be the zero point for the rotation limit
  55. if (!Application.isPlaying && !script.defaultLocalRotationOverride) script.defaultLocalRotation = script.transform.localRotation;
  56. if (script.axis == Vector3.zero) return;
  57. // Quick Editing Tools
  58. Handles.BeginGUI();
  59. GUILayout.BeginArea(new Rect(10, 10, 550, 140), "Rotation Limit Polygonal", "Window");
  60. // Cloning values from another RotationLimitPolygonal
  61. EditorGUILayout.BeginHorizontal();
  62. if (Inspector.Button("Clone From", "Make this rotation limit identical to another", script, GUILayout.Width(220))) CloneLimit();
  63. clone = (RotationLimitPolygonal)EditorGUILayout.ObjectField("", clone, typeof(RotationLimitPolygonal), true);
  64. EditorGUILayout.EndHorizontal();
  65. // Symmetry
  66. symmetry = (Symmetry)EditorGUILayout.EnumPopup("Symmetry", symmetry, GUILayout.Width(220));
  67. // Flipping
  68. EditorGUILayout.BeginHorizontal();
  69. if (Inspector.Button("Flip X", "Flip points along local X axis", script, GUILayout.Width(100))) FlipLimit(0);
  70. if (Inspector.Button("Flip Y", "Flip points along local Y axis", script, GUILayout.Width(100))) FlipLimit(1);
  71. if (Inspector.Button("Flip Z", "Flip points along local Z axis", script, GUILayout.Width(100))) FlipLimit(2);
  72. GUILayout.Label("Flip everything along axis");
  73. EditorGUILayout.EndHorizontal();
  74. // Rotating
  75. EditorGUILayout.BeginHorizontal();
  76. if (Inspector.Button("Rotate X", "Rotate points along X axis by Degrees", script, GUILayout.Width(100))) RotatePoints(degrees, Vector3.right);
  77. if (Inspector.Button("Rotate Y", "Rotate points along Y axis by Degrees", script, GUILayout.Width(100))) RotatePoints(degrees, Vector3.up);
  78. if (Inspector.Button("Rotate Z", "Rotate points along Z axis by Degrees", script, GUILayout.Width(100))) RotatePoints(degrees, Vector3.forward);
  79. degrees = EditorGUILayout.FloatField("Degrees", degrees, GUILayout.Width(200));
  80. EditorGUILayout.EndHorizontal();
  81. // Smooth/Optimize
  82. EditorGUILayout.BeginHorizontal();
  83. if (Inspector.Button("Smooth", "Double the points", script)) Smooth();
  84. if (Inspector.Button("Optimize", "Delete every second point", script)) Optimize();
  85. EditorGUILayout.EndHorizontal();
  86. GUILayout.EndArea();
  87. Handles.EndGUI();
  88. // Rebuild reach cones
  89. script.BuildReachCones();
  90. // Draw a white transparent sphere
  91. DrawRotationSphere(script.transform.position);
  92. // Draw Axis
  93. DrawArrow(script.transform.position, Direction(script.axis), colorDefault, "Axis", 0.02f);
  94. // Display limit points
  95. for (int i = 0; i < script.points.Length; i++)
  96. {
  97. Color color = GetColor(i); // Paint the point in green or red if it belongs to an invalid reach cone
  98. Handles.color = color;
  99. GUI.color = color;
  100. // Line from the center to the point and the label
  101. Handles.DrawLine(script.transform.position, script.transform.position + Direction(script.points[i].point));
  102. Handles.Label(script.transform.position + Direction(script.points[i].point + new Vector3(-0.02f, 0, 0)), " " + i.ToString());
  103. // Selecting points
  104. Handles.color = colorHandles;
  105. if (Inspector.DotButton(script.transform.position + Direction(script.points[i].point), script.transform.rotation, 0.02f, 0.02f))
  106. {
  107. selectedPoint = i;
  108. }
  109. Handles.color = Color.white;
  110. GUI.color = Color.white;
  111. // Limit point GUI
  112. if (i == selectedPoint)
  113. {
  114. Handles.BeginGUI();
  115. GUILayout.BeginArea(new Rect(Screen.width - 240, Screen.height - 180, 230, 130), "Limit Point " + i.ToString(), "Window");
  116. if (Inspector.Button("Delete", "Delete this point", script))
  117. {
  118. if (script.points.Length > 3)
  119. {
  120. // Using the deletePoint index here because we dont want to delete points from the array that we are iterating
  121. deletePoint = i;
  122. }
  123. else if (!Warning.logged) script.LogWarning("Polygonal Rotation Limit should have at least 3 limit points");
  124. }
  125. if (Inspector.Button("Add Point", "Add a new point next to this one", script))
  126. {
  127. addPoint = i;
  128. }
  129. // Store point for undo
  130. Vector3 oldPoint = script.points[i].point;
  131. // Manual input for the point position
  132. Inspector.AddVector3(ref script.points[i].point, "Point", script, GUILayout.Width(210));
  133. EditorGUILayout.Space();
  134. // Tangent weight
  135. Inspector.AddFloat(ref script.points[i].tangentWeight, "Tangent Weight", "Weight of this point's tangent. Used in smoothing.", script, -Mathf.Infinity, Mathf.Infinity, GUILayout.Width(150));
  136. GUILayout.EndArea();
  137. Handles.EndGUI();
  138. // Moving Points
  139. Vector3 pointWorld = Handles.PositionHandle(script.transform.position + Direction(script.points[i].point), Quaternion.identity);
  140. Vector3 newPoint = InverseDirection(pointWorld - script.transform.position);
  141. if (newPoint != script.points[i].point)
  142. {
  143. if (!Application.isPlaying) Undo.RecordObject(script, "Move Limit Point");
  144. script.points[i].point = newPoint;
  145. }
  146. // Symmetry
  147. if (symmetry != Symmetry.Off && script.points.Length > 3 && oldPoint != script.points[i].point)
  148. {
  149. RotationLimitPolygonal.LimitPoint symmetryPoint = GetClosestPoint(Symmetrize(oldPoint, symmetry));
  150. if (symmetryPoint != script.points[i])
  151. {
  152. symmetryPoint.point = Symmetrize(script.points[i].point, symmetry);
  153. }
  154. }
  155. }
  156. // Normalize the point
  157. script.points[i].point = script.points[i].point.normalized;
  158. }
  159. // Display smoothed polygon
  160. for (int i = 0; i < script.P.Length; i++)
  161. {
  162. Color color = GetColor(i);
  163. // Smoothed triangles are transparent
  164. Handles.color = new Color(color.r, color.g, color.b, 0.25f);
  165. Handles.DrawLine(script.transform.position, script.transform.position + Direction(script.P[i]));
  166. Handles.color = color;
  167. if (i < script.P.Length - 1) Handles.DrawLine(script.transform.position + Direction(script.P[i]), script.transform.position + Direction(script.P[i + 1]));
  168. else Handles.DrawLine(script.transform.position + Direction(script.P[i]), script.transform.position + Direction(script.P[0]));
  169. Handles.color = Color.white;
  170. }
  171. // Deleting points
  172. if (deletePoint != -1)
  173. {
  174. DeletePoint(deletePoint);
  175. selectedPoint = -1;
  176. deletePoint = -1;
  177. }
  178. // Adding points
  179. if (addPoint != -1)
  180. {
  181. AddPoint(addPoint);
  182. addPoint = -1;
  183. }
  184. if (GUI.changed) EditorUtility.SetDirty(script);
  185. }
  186. private Color GetColor(int i)
  187. {
  188. // Paint the polygon in red if the reach cone is invalid
  189. return script.reachCones[i].isValid ? colorDefault : colorInvalid;
  190. }
  191. /*
  192. * Doubles the number of Limit Points
  193. * */
  194. private void Smooth()
  195. {
  196. int length = script.points.Length;
  197. for (int i = 0; i < length; i++)
  198. {
  199. AddPoint(i + i);
  200. }
  201. }
  202. /*
  203. * Reduces the number of Limit Points
  204. * */
  205. private void Optimize()
  206. {
  207. for (int i = 1; i < script.points.Length; i++)
  208. {
  209. if (script.points.Length > 3) DeletePoint(i);
  210. }
  211. }
  212. /*
  213. * Flips the rotation limit along the axis
  214. * */
  215. private void FlipLimit(int axis)
  216. {
  217. script.axis[axis] = -script.axis[axis];
  218. foreach (RotationLimitPolygonal.LimitPoint limitPoint in script.points) limitPoint.point[axis] = -limitPoint.point[axis];
  219. Array.Reverse(script.points);
  220. script.BuildReachCones();
  221. }
  222. private void RotatePoints(float degrees, Vector3 axis)
  223. {
  224. foreach (RotationLimitPolygonal.LimitPoint limitPoint in script.points) limitPoint.point = Quaternion.AngleAxis(degrees, axis) * limitPoint.point;
  225. script.BuildReachCones();
  226. }
  227. /*
  228. * Converting directions from local space to world space
  229. * */
  230. private Vector3 Direction(Vector3 v)
  231. {
  232. if (script.transform.parent == null) return script.defaultLocalRotation * v;
  233. return script.transform.parent.rotation * (script.defaultLocalRotation * v);
  234. }
  235. /*
  236. * Inverse of Direction(Vector3 v)
  237. * */
  238. private Vector3 InverseDirection(Vector3 v)
  239. {
  240. if (script.transform.parent == null) return Quaternion.Inverse(script.defaultLocalRotation) * v;
  241. return Quaternion.Inverse(script.defaultLocalRotation) * Quaternion.Inverse(script.transform.parent.rotation) * v;
  242. }
  243. /*
  244. * Removing Limit Points
  245. * */
  246. private void DeletePoint(int p)
  247. {
  248. RotationLimitPolygonal.LimitPoint[] newPoints = new RotationLimitPolygonal.LimitPoint[0];
  249. for (int i = 0; i < script.points.Length; i++)
  250. {
  251. if (i != p)
  252. {
  253. Array.Resize(ref newPoints, newPoints.Length + 1);
  254. newPoints[newPoints.Length - 1] = script.points[i];
  255. }
  256. }
  257. script.points = newPoints;
  258. script.BuildReachCones();
  259. }
  260. /*
  261. * Creating new Limit Points
  262. * */
  263. private void AddPoint(int p)
  264. {
  265. RotationLimitPolygonal.LimitPoint[] newPoints = new RotationLimitPolygonal.LimitPoint[script.points.Length + 1];
  266. for (int i = 0; i < p + 1; i++) newPoints[i] = script.points[i];
  267. newPoints[p + 1] = new RotationLimitPolygonal.LimitPoint();
  268. Vector3 nextPoint = Vector3.forward;
  269. if (p < script.points.Length - 1) nextPoint = script.points[p + 1].point;
  270. else nextPoint = script.points[0].point;
  271. newPoints[p + 1].point = Vector3.Lerp(script.points[p].point, nextPoint, 0.5f);
  272. for (int i = p + 2; i < newPoints.Length; i++) newPoints[i] = script.points[i - 1];
  273. script.points = newPoints;
  274. script.BuildReachCones();
  275. }
  276. /*
  277. * Clone properties from another RotationLimitPolygonal
  278. * */
  279. private void CloneLimit()
  280. {
  281. if (clone == null) return;
  282. if (clone == script)
  283. {
  284. script.LogWarning("Can't clone from self.");
  285. return;
  286. }
  287. script.axis = clone.axis;
  288. script.twistLimit = clone.twistLimit;
  289. script.smoothIterations = clone.smoothIterations;
  290. script.points = new RotationLimitPolygonal.LimitPoint[clone.points.Length];
  291. for (int i = 0; i < script.points.Length; i++)
  292. {
  293. script.points[i] = (RotationLimitPolygonal.LimitPoint)CloneObject(clone.points[i]);
  294. }
  295. script.BuildReachCones();
  296. }
  297. private static object CloneObject(object o)
  298. {
  299. Type t = o.GetType();
  300. object clone = Activator.CreateInstance(t);
  301. foreach (FieldInfo fi in t.GetFields())
  302. {
  303. fi.SetValue(clone, fi.GetValue(o));
  304. }
  305. return clone;
  306. }
  307. /*
  308. * Flipping vectors for symmetry
  309. * */
  310. private static Vector3 Symmetrize(Vector3 v, Symmetry symmetry)
  311. {
  312. switch (symmetry)
  313. {
  314. case Symmetry.X: return new Vector3(-v.x, v.y, v.z);
  315. case Symmetry.Y: return new Vector3(v.x, -v.y, v.z);
  316. case Symmetry.Z: return new Vector3(v.x, v.y, -v.z);
  317. default: return v;
  318. }
  319. }
  320. /*
  321. * Returns closest point to a position. Used for symmetric editing
  322. * */
  323. private RotationLimitPolygonal.LimitPoint GetClosestPoint(Vector3 v)
  324. {
  325. float closestDistace = Mathf.Infinity;
  326. RotationLimitPolygonal.LimitPoint closestPoint = null;
  327. foreach (RotationLimitPolygonal.LimitPoint limitPoint in script.points)
  328. {
  329. if (limitPoint.point == v) return limitPoint;
  330. float d = Vector3.Distance(limitPoint.point, v);
  331. if (d < closestDistace)
  332. {
  333. closestPoint = limitPoint;
  334. closestDistace = d;
  335. }
  336. }
  337. return closestPoint;
  338. }
  339. #endregion Scene
  340. }
  341. }