SplineEditor.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEditor;
  4. using UnityEngine;
  5. namespace SplineMesh {
  6. [CustomEditor(typeof(Spline))]
  7. public class SplineEditor : Editor {
  8. private const int QUAD_SIZE = 12;
  9. private static Color CURVE_COLOR = new Color(0.8f, 0.8f, 0.8f);
  10. private static Color CURVE_BUTTON_COLOR = new Color(0.8f, 0.8f, 0.8f);
  11. private static Color DIRECTION_COLOR = Color.red;
  12. private static Color DIRECTION_BUTTON_COLOR = Color.red;
  13. private static Color UP_BUTTON_COLOR = Color.green;
  14. private static bool showUpVector = false;
  15. private enum SelectionType {
  16. Node,
  17. Direction,
  18. InverseDirection,
  19. Up
  20. }
  21. private SplineNode selection;
  22. private SelectionType selectionType;
  23. private bool mustCreateNewNode = false;
  24. private SerializedProperty nodesProp { get { return serializedObject.FindProperty("nodes"); } }
  25. private Spline spline { get { return (Spline)serializedObject.targetObject; } }
  26. private GUIStyle nodeButtonStyle, directionButtonStyle, upButtonStyle;
  27. private void OnEnable() {
  28. Texture2D t = new Texture2D(1, 1);
  29. t.SetPixel(0, 0, CURVE_BUTTON_COLOR);
  30. t.Apply();
  31. nodeButtonStyle = new GUIStyle();
  32. nodeButtonStyle.normal.background = t;
  33. t = new Texture2D(1, 1);
  34. t.SetPixel(0, 0, DIRECTION_BUTTON_COLOR);
  35. t.Apply();
  36. directionButtonStyle = new GUIStyle();
  37. directionButtonStyle.normal.background = t;
  38. t = new Texture2D(1, 1);
  39. t.SetPixel(0, 0, UP_BUTTON_COLOR);
  40. t.Apply();
  41. upButtonStyle = new GUIStyle();
  42. upButtonStyle.normal.background = t;
  43. selection = null;
  44. Undo.undoRedoPerformed -= spline.RefreshCurves;
  45. Undo.undoRedoPerformed += spline.RefreshCurves;
  46. }
  47. SplineNode AddClonedNode(SplineNode node) {
  48. int index = spline.nodes.IndexOf(node);
  49. SplineNode res = new SplineNode(node.Position, node.Direction);
  50. if (index == spline.nodes.Count - 1) {
  51. spline.AddNode(res);
  52. } else {
  53. spline.InsertNode(index + 1, res);
  54. }
  55. return res;
  56. }
  57. void OnSceneGUI() {
  58. // disable game object transform gyzmo
  59. // if the spline script is active
  60. if (Selection.activeGameObject == spline.gameObject) {
  61. if (!spline.enabled) {
  62. Tools.current = Tool.Move;
  63. } else {
  64. Tools.current = Tool.None;
  65. if (selection == null && spline.nodes.Count > 0)
  66. selection = spline.nodes[0];
  67. }
  68. }
  69. // draw a bezier curve for each curve in the spline
  70. foreach (CubicBezierCurve curve in spline.GetCurves()) {
  71. Handles.DrawBezier(spline.transform.TransformPoint(curve.n1.Position),
  72. spline.transform.TransformPoint(curve.n2.Position),
  73. spline.transform.TransformPoint(curve.n1.Direction),
  74. spline.transform.TransformPoint(curve.GetInverseDirection()),
  75. CURVE_COLOR,
  76. null,
  77. 3);
  78. }
  79. if (!spline.enabled)
  80. return;
  81. // draw the selection handles
  82. switch (selectionType) {
  83. case SelectionType.Node:
  84. // place a handle on the node and manage position change
  85. // TODO place the handle depending on user params (local or world)
  86. Vector3 newPosition = spline.transform.InverseTransformPoint(Handles.PositionHandle(spline.transform.TransformPoint(selection.Position), spline.transform.rotation));
  87. if (newPosition != selection.Position) {
  88. // position handle has been moved
  89. if (mustCreateNewNode) {
  90. mustCreateNewNode = false;
  91. selection = AddClonedNode(selection);
  92. selection.Direction += newPosition - selection.Position;
  93. selection.Position = newPosition;
  94. } else {
  95. selection.Direction += newPosition - selection.Position;
  96. selection.Position = newPosition;
  97. }
  98. }
  99. break;
  100. case SelectionType.Direction:
  101. var result = Handles.PositionHandle(spline.transform.TransformPoint(selection.Direction), Quaternion.identity);
  102. selection.Direction = spline.transform.InverseTransformPoint(result);
  103. break;
  104. case SelectionType.InverseDirection:
  105. result = Handles.PositionHandle(2 * spline.transform.TransformPoint(selection.Position) - spline.transform.TransformPoint(selection.Direction), Quaternion.identity);
  106. selection.Direction = 2 * selection.Position - spline.transform.InverseTransformPoint(result);
  107. break;
  108. case SelectionType.Up:
  109. result = Handles.PositionHandle(spline.transform.TransformPoint(selection.Position + selection.Up), Quaternion.LookRotation(selection.Direction - selection.Position));
  110. selection.Up = (spline.transform.InverseTransformPoint(result) - selection.Position).normalized;
  111. break;
  112. }
  113. // draw the handles of all nodes, and manage selection motion
  114. Handles.BeginGUI();
  115. foreach (SplineNode n in spline.nodes) {
  116. var dir = spline.transform.TransformPoint(n.Direction);
  117. var pos = spline.transform.TransformPoint(n.Position);
  118. var invDir = spline.transform.TransformPoint(2 * n.Position - n.Direction);
  119. var up = spline.transform.TransformPoint(n.Position + n.Up);
  120. // first we check if at least one thing is in the camera field of view
  121. if (!(CameraUtility.IsOnScreen(pos) ||
  122. CameraUtility.IsOnScreen(dir) ||
  123. CameraUtility.IsOnScreen(invDir) ||
  124. (showUpVector && CameraUtility.IsOnScreen(up)))) {
  125. continue;
  126. }
  127. Vector3 guiPos = HandleUtility.WorldToGUIPoint(pos);
  128. if (n == selection) {
  129. Vector3 guiDir = HandleUtility.WorldToGUIPoint(dir);
  130. Vector3 guiInvDir = HandleUtility.WorldToGUIPoint(invDir);
  131. Vector3 guiUp = HandleUtility.WorldToGUIPoint(up);
  132. // for the selected node, we also draw a line and place two buttons for directions
  133. Handles.color = DIRECTION_COLOR;
  134. Handles.DrawLine(guiDir, guiInvDir);
  135. // draw quads direction and inverse direction if they are not selected
  136. if (selectionType != SelectionType.Node) {
  137. if (Button(guiPos, directionButtonStyle)) {
  138. selectionType = SelectionType.Node;
  139. }
  140. }
  141. if (selectionType != SelectionType.Direction) {
  142. if (Button(guiDir, directionButtonStyle)) {
  143. selectionType = SelectionType.Direction;
  144. }
  145. }
  146. if (selectionType != SelectionType.InverseDirection) {
  147. if (Button(guiInvDir, directionButtonStyle)) {
  148. selectionType = SelectionType.InverseDirection;
  149. }
  150. }
  151. if (showUpVector) {
  152. Handles.color = Color.green;
  153. Handles.DrawLine(guiPos, guiUp);
  154. if (selectionType != SelectionType.Up) {
  155. if (Button(guiUp, upButtonStyle)) {
  156. selectionType = SelectionType.Up;
  157. }
  158. }
  159. }
  160. } else {
  161. if (Button(guiPos, nodeButtonStyle)) {
  162. selection = n;
  163. selectionType = SelectionType.Node;
  164. }
  165. }
  166. }
  167. Handles.EndGUI();
  168. if (GUI.changed)
  169. EditorUtility.SetDirty(target);
  170. }
  171. bool Button(Vector2 position, GUIStyle style) {
  172. return GUI.Button(new Rect(position - new Vector2(QUAD_SIZE / 2, QUAD_SIZE / 2), new Vector2(QUAD_SIZE, QUAD_SIZE)), GUIContent.none, style);
  173. }
  174. public override void OnInspectorGUI() {
  175. serializedObject.Update();
  176. if(spline.nodes.IndexOf(selection) < 0) {
  177. selection = null;
  178. }
  179. // add button
  180. if (selection == null) {
  181. GUI.enabled = false;
  182. }
  183. if (GUILayout.Button("Add node after selected")) {
  184. Undo.RecordObject(spline, "add spline node");
  185. SplineNode newNode = new SplineNode(selection.Direction, selection.Direction + selection.Direction - selection.Position);
  186. var index = spline.nodes.IndexOf(selection);
  187. if(index == spline.nodes.Count - 1) {
  188. spline.AddNode(newNode);
  189. } else {
  190. spline.InsertNode(index + 1, newNode);
  191. }
  192. selection = newNode;
  193. serializedObject.Update();
  194. }
  195. GUI.enabled = true;
  196. // delete button
  197. if (selection == null || spline.nodes.Count <= 2) {
  198. GUI.enabled = false;
  199. }
  200. if (GUILayout.Button("Delete selected node")) {
  201. Undo.RecordObject(spline, "delete spline node");
  202. spline.RemoveNode(selection);
  203. selection = null;
  204. serializedObject.Update();
  205. }
  206. GUI.enabled = true;
  207. showUpVector = GUILayout.Toggle(showUpVector, "Show up vector");
  208. spline.IsLoop = GUILayout.Toggle(spline.IsLoop, "Is loop");
  209. // nodes
  210. GUI.enabled = false;
  211. EditorGUILayout.PropertyField(nodesProp);
  212. GUI.enabled = true;
  213. if (selection != null) {
  214. int index = spline.nodes.IndexOf(selection);
  215. SerializedProperty nodeProp = nodesProp.GetArrayElementAtIndex(index);
  216. EditorGUILayout.LabelField("Selected node (node " + index + ")");
  217. EditorGUI.indentLevel++;
  218. DrawNodeData(nodeProp, selection);
  219. EditorGUI.indentLevel--;
  220. } else {
  221. EditorGUILayout.LabelField("No selected node");
  222. }
  223. }
  224. private void DrawNodeData(SerializedProperty nodeProperty, SplineNode node) {
  225. var positionProp = nodeProperty.FindPropertyRelative("position");
  226. var directionProp = nodeProperty.FindPropertyRelative("direction");
  227. var upProp = nodeProperty.FindPropertyRelative("up");
  228. var scaleProp = nodeProperty.FindPropertyRelative("scale");
  229. var rollProp = nodeProperty.FindPropertyRelative("roll");
  230. EditorGUI.BeginChangeCheck();
  231. EditorGUILayout.PropertyField(positionProp, new GUIContent("Position"));
  232. EditorGUILayout.PropertyField(directionProp, new GUIContent("Direction"));
  233. EditorGUILayout.PropertyField(upProp, new GUIContent("Up"));
  234. EditorGUILayout.PropertyField(scaleProp, new GUIContent("Scale"));
  235. EditorGUILayout.PropertyField(rollProp, new GUIContent("Roll"));
  236. if (EditorGUI.EndChangeCheck()) {
  237. node.Position = positionProp.vector3Value;
  238. node.Direction = directionProp.vector3Value;
  239. node.Up = upProp.vector3Value;
  240. node.Scale = scaleProp.vector2Value;
  241. node.Roll = rollProp.floatValue;
  242. serializedObject.Update();
  243. }
  244. }
  245. [MenuItem("GameObject/3D Object/Spline")]
  246. public static void CreateSpline() {
  247. new GameObject("Spline", typeof(Spline));
  248. }
  249. [DrawGizmo(GizmoType.InSelectionHierarchy)]
  250. static void DisplayUnselected(Spline spline, GizmoType gizmoType) {
  251. foreach (CubicBezierCurve curve in spline.GetCurves()) {
  252. Handles.DrawBezier(spline.transform.TransformPoint(curve.n1.Position),
  253. spline.transform.TransformPoint(curve.n2.Position),
  254. spline.transform.TransformPoint(curve.n1.Direction),
  255. spline.transform.TransformPoint(curve.GetInverseDirection()),
  256. CURVE_COLOR,
  257. null,
  258. 3);
  259. }
  260. }
  261. }
  262. }