ShapeEditor.cs 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. using UnityEditorInternal;
  6. using UnityEngine.U2D.Sprites;
  7. namespace UnityEditor.U2D.Sprites
  8. {
  9. internal interface IShapeEditorFactory
  10. {
  11. ShapeEditor CreateShapeEditor();
  12. }
  13. internal class ShapeEditorFactory : IShapeEditorFactory
  14. {
  15. public ShapeEditor CreateShapeEditor()
  16. {
  17. return new ShapeEditor(new GUIUtilitySystem(), new EventSystem());
  18. }
  19. }
  20. internal class ShapeEditor
  21. {
  22. public delegate float DistanceToControl(Vector3 pos, Quaternion rotation, float handleSize);
  23. internal enum SelectionType { Normal, Additive, Subtractive }
  24. internal enum Tool { Edit, Create, Break }
  25. internal enum TangentMode { Linear, Continuous, Broken }
  26. // --- To use this class in your editor, you need to implement these: (see ShapeEditorTests for example)
  27. // --- Data
  28. public Func<int, Vector3> GetPointPosition = i => Vector3.zero;
  29. public Action<int, Vector3> SetPointPosition = (i, p) => {};
  30. public Func<int, Vector3> GetPointLTangent = i => Vector3.zero;
  31. public Action<int, Vector3> SetPointLTangent = (i, p) => {};
  32. public Func<int, Vector3> GetPointRTangent = i => Vector3.zero;
  33. public Action<int, Vector3> SetPointRTangent = (i, p) => {};
  34. public Func<int, TangentMode> GetTangentMode = i => TangentMode.Linear;
  35. public Action<int, TangentMode> SetTangentMode = (i, m) => {};
  36. public Action<int, Vector3> InsertPointAt = (i, p) => {};
  37. public Action<int> RemovePointAt = i => {};
  38. public Func<int> GetPointsCount = () => 0;
  39. // --- Transforms
  40. public Func<Vector2, Vector3> ScreenToLocal = i => i;
  41. public Func<Vector3, Vector2> LocalToScreen = i => i;
  42. public Func<Matrix4x4> LocalToWorldMatrix = () => Matrix4x4.identity;
  43. // --- Distance functions
  44. public Func<DistanceToControl> DistanceToRectangle = () => HandleUtility.DistanceToRectangle;
  45. public Func<DistanceToControl> DistanceToDiamond = () => HandleUtility.DistanceToDiamond;
  46. public Func<DistanceToControl> DistanceToCircle = () => DistanceToCircleInternal;
  47. // --- Other
  48. public Action Repaint = () => {};
  49. public Action RecordUndo = () => {};
  50. public Func<Vector3, Vector3> Snap = i => i;
  51. public Action<Bounds> Frame = b => {};
  52. public Action<int> OnPointClick = i => {};
  53. public Func<bool> OpenEnded = () => false;
  54. public Func<float> GetHandleSize = () => 5f;
  55. // --- END
  56. public ITexture2D lineTexture { get; set; }
  57. public int activePoint { get; set; }
  58. public HashSet<int> selectedPoints { get { return m_Selection.indices; } }
  59. public bool inEditMode { get; set; }
  60. public int activeEdge { get { return m_ActiveEdge; } set { m_ActiveEdge = value; } }
  61. // Shape editor needs to reset its state on next OnSceneGUI. Reset can't be called immediately elsewhere, because we need to also reset local sceneview state like GUIUtility.keyboardControl
  62. public bool delayedReset { set { m_DelayedReset = value; } }
  63. private ShapeEditorSelection m_Selection;
  64. private Vector2 m_MousePositionLastMouseDown;
  65. private int m_ActivePointOnLastMouseDown = -1;
  66. private int m_NewPointIndex = -1;
  67. private Vector3 m_EdgeDragStartMousePosition;
  68. private Vector3 m_EdgeDragStartP0;
  69. private Vector3 m_EdgeDragStartP1;
  70. private bool m_NewPointDragFinished;
  71. private int m_ActiveEdge = -1;
  72. private bool m_DelayedReset = false;
  73. private HashSet<ShapeEditor> m_ShapeEditorListeners = new HashSet<ShapeEditor>();
  74. private ShapeEditorRectSelectionTool m_RectSelectionTool;
  75. private int m_MouseClosestEdge = -1;
  76. private float m_MouseClosestEdgeDist = float.MaxValue;
  77. private int m_ShapeEditorRegisteredTo = 0;
  78. private int m_ShapeEditorUpdateDone = 0;
  79. private static Color handleOutlineColor { get; set; }
  80. private static Color handleFillColor { get; set; }
  81. private Quaternion handleMatrixrotation { get { return Quaternion.LookRotation(handles.matrix.GetColumn(2), handles.matrix.GetColumn(1)); } }
  82. private IGUIUtility guiUtility { get; set; }
  83. private IEventSystem eventSystem { get; set; }
  84. private IEvent currentEvent { get; set; }
  85. private IGL glSystem { get; set; }
  86. private IHandles handles { get; set; }
  87. private Dictionary<DrawBatchDataKey, List<Vector3>> m_DrawBatch;
  88. private Vector3[][] m_EdgePoints;
  89. enum ColorEnum
  90. {
  91. EUnselected,
  92. EUnselectedHovered,
  93. ESelected,
  94. ESelectedHovered
  95. }
  96. private static readonly Color[] k_OutlineColor = new[]
  97. {
  98. Color.gray,
  99. Color.white,
  100. new Color(34f / 255f, 171f / 255f, 1f), // #22abff
  101. Color.white
  102. };
  103. static readonly Color[] k_FillColor = new[]
  104. {
  105. Color.white,
  106. new Color(131f / 255f, 220f / 255f, 1f), // #83dcff
  107. new Color(34f / 255f, 171f / 255f, 1f), // #22abff
  108. new Color(34f / 255f, 171f / 255f, 1f) // #22abff
  109. };
  110. private static readonly Color k_TangentColor = new Color(34f / 255f, 171f / 255f, 1f); // #22abff
  111. private static readonly Color k_TangentColorAlternative = new Color(131f / 255f, 220f / 255f, 1f); // #83dcff
  112. private const float k_EdgeHoverDistance = 9f;
  113. private const float k_EdgeWidth = 2f;
  114. private const float k_ActiveEdgeWidth = 6f;
  115. private const float k_MinExistingPointDistanceForInsert = 20f;
  116. private readonly int k_CreatorID;
  117. private readonly int k_EdgeID;
  118. private readonly int k_RightTangentID;
  119. private readonly int k_LeftTangentID;
  120. private const int k_BezierPatch = 40;
  121. class DrawBatchDataKey
  122. {
  123. public Color color { get; private set; }
  124. public int glMode { get; private set; }
  125. private int m_Hash;
  126. public DrawBatchDataKey(Color c , int mode)
  127. {
  128. color = c;
  129. glMode = mode;
  130. m_Hash = glMode ^ (color.GetHashCode() << 2);
  131. }
  132. public override int GetHashCode()
  133. {
  134. return m_Hash;
  135. }
  136. public override bool Equals(object obj)
  137. {
  138. var key = obj as DrawBatchDataKey;
  139. if (ReferenceEquals(key, null))
  140. return false;
  141. return m_Hash == obj.GetHashCode();
  142. }
  143. }
  144. public ShapeEditor(IGUIUtility gu, IEventSystem es)
  145. {
  146. m_Selection = new ShapeEditorSelection(this);
  147. guiUtility = gu;
  148. eventSystem = es;
  149. k_CreatorID = guiUtility.GetPermanentControlID();
  150. k_EdgeID = guiUtility.GetPermanentControlID();
  151. k_RightTangentID = guiUtility.GetPermanentControlID();
  152. k_LeftTangentID = guiUtility.GetPermanentControlID();
  153. glSystem = GLSystem.GetSystem();
  154. handles = HandlesSystem.GetSystem();
  155. }
  156. public void SetRectSelectionTool(ShapeEditorRectSelectionTool sers)
  157. {
  158. if (m_RectSelectionTool != null)
  159. {
  160. m_RectSelectionTool.RectSelect -= SelectPointsInRect;
  161. m_RectSelectionTool.ClearSelection -= ClearSelectedPoints;
  162. }
  163. m_RectSelectionTool = sers;
  164. m_RectSelectionTool.RectSelect += SelectPointsInRect;
  165. m_RectSelectionTool.ClearSelection += ClearSelectedPoints;
  166. }
  167. public void OnDisable()
  168. {
  169. m_RectSelectionTool.RectSelect -= SelectPointsInRect;
  170. m_RectSelectionTool.ClearSelection -= ClearSelectedPoints;
  171. m_RectSelectionTool = null;
  172. }
  173. private void PrepareDrawBatch()
  174. {
  175. if (currentEvent.type == EventType.Repaint)
  176. {
  177. m_DrawBatch = new Dictionary<DrawBatchDataKey, List<Vector3>>();
  178. }
  179. }
  180. private void DrawBatch()
  181. {
  182. if (currentEvent.type == EventType.Repaint)
  183. {
  184. HandleUtility.ApplyWireMaterial();
  185. glSystem.PushMatrix();
  186. glSystem.MultMatrix(handles.matrix);
  187. foreach (var drawBatch in m_DrawBatch)
  188. {
  189. glSystem.Begin(drawBatch.Key.glMode);
  190. glSystem.Color(drawBatch.Key.color);
  191. foreach (var t in drawBatch.Value)
  192. {
  193. glSystem.Vertex(t);
  194. }
  195. glSystem.End();
  196. }
  197. glSystem.PopMatrix();
  198. }
  199. }
  200. List<Vector3> GetDrawBatchList(DrawBatchDataKey key)
  201. {
  202. List<Vector3> data = null;
  203. if (!m_DrawBatch.ContainsKey(key))
  204. {
  205. m_DrawBatch.Add(key, new List<Vector3>());
  206. }
  207. data = m_DrawBatch[key];
  208. return data;
  209. }
  210. public void OnGUI()
  211. {
  212. DelayedResetIfNecessary();
  213. currentEvent = eventSystem.current;
  214. if (currentEvent.type == EventType.MouseDown)
  215. StoreMouseDownState();
  216. var oldColor = handles.color;
  217. var oldMatrix = handles.matrix;
  218. handles.matrix = LocalToWorldMatrix();
  219. PrepareDrawBatch();
  220. Edges();
  221. if (inEditMode)
  222. {
  223. Framing();
  224. Tangents();
  225. Points();
  226. }
  227. DrawBatch();
  228. handles.color = oldColor;
  229. handles.matrix = oldMatrix;
  230. OnShapeEditorUpdateDone();
  231. foreach (var se in m_ShapeEditorListeners)
  232. se.OnShapeEditorUpdateDone();
  233. }
  234. private void Framing()
  235. {
  236. if (currentEvent.commandName == EventCommandNames.FrameSelected && m_Selection.Count > 0)
  237. {
  238. switch (currentEvent.type)
  239. {
  240. case EventType.ExecuteCommand:
  241. Bounds bounds = new Bounds(GetPointPosition(selectedPoints.First()), Vector3.zero);
  242. foreach (var index in selectedPoints)
  243. bounds.Encapsulate(GetPointPosition(index));
  244. Frame(bounds);
  245. goto case EventType.ValidateCommand;
  246. case EventType.ValidateCommand:
  247. currentEvent.Use();
  248. break;
  249. }
  250. }
  251. }
  252. void PrepareEdgePointList()
  253. {
  254. if (m_EdgePoints == null)
  255. {
  256. var total = this.GetPointsCount();
  257. int loopCount = OpenEnded() ? total - 1 : total;
  258. m_EdgePoints = new Vector3[loopCount][];
  259. int index = mod(total - 1, loopCount);
  260. for (int loop = mod(index + 1, total); loop < total; index = loop, ++loop)
  261. {
  262. var position0 = this.GetPointPosition(index);
  263. var position1 = this.GetPointPosition(loop);
  264. if (GetTangentMode(index) == TangentMode.Linear && GetTangentMode(loop) == TangentMode.Linear)
  265. {
  266. m_EdgePoints[index] = new[] { position0, position1 };
  267. }
  268. else
  269. {
  270. var tangent0 = GetPointRTangent(index) + position0;
  271. var tangent1 = GetPointLTangent(loop) + position1;
  272. m_EdgePoints[index] = handles.MakeBezierPoints(position0, position1, tangent0, tangent1, k_BezierPatch);
  273. }
  274. }
  275. }
  276. }
  277. float DistancePointEdge(Vector3 point, Vector3[] edge)
  278. {
  279. float result = float.MaxValue;
  280. int index = edge.Length - 1;
  281. for (int nextIndex = 0; nextIndex < edge.Length; index = nextIndex, nextIndex++)
  282. {
  283. float dist = HandleUtility.DistancePointLine(point, edge[index], edge[nextIndex]);
  284. if (dist < result)
  285. result = dist;
  286. }
  287. return result;
  288. }
  289. private float GetMouseClosestEdgeDistance()
  290. {
  291. var mousePosition = ScreenToLocal(eventSystem.current.mousePosition);
  292. var total = this.GetPointsCount();
  293. if (m_MouseClosestEdge == -1 && total > 0)
  294. {
  295. PrepareEdgePointList();
  296. m_MouseClosestEdgeDist = float.MaxValue;
  297. int loopCount = OpenEnded() ? total - 1 : total;
  298. for (int loop = 0; loop < loopCount; loop++)
  299. {
  300. var dist = DistancePointEdge(mousePosition, m_EdgePoints[loop]);
  301. if (dist < m_MouseClosestEdgeDist)
  302. {
  303. m_MouseClosestEdge = loop;
  304. m_MouseClosestEdgeDist = dist;
  305. }
  306. }
  307. }
  308. if (guiUtility.hotControl == k_CreatorID || guiUtility.hotControl == k_EdgeID)
  309. return float.MinValue;
  310. return m_MouseClosestEdgeDist;
  311. }
  312. public void Edges()
  313. {
  314. var otherClosestEdgeDistance = float.MaxValue;
  315. if (m_ShapeEditorListeners.Count > 0)
  316. otherClosestEdgeDistance = (from se in m_ShapeEditorListeners select se.GetMouseClosestEdgeDistance()).Max();
  317. float edgeDistance = GetMouseClosestEdgeDistance();
  318. bool closestEdgeHighlight = EdgeDragModifiersActive() && edgeDistance < k_EdgeHoverDistance && edgeDistance < otherClosestEdgeDistance;
  319. if (currentEvent.type == EventType.Repaint)
  320. {
  321. Color handlesOldColor = handles.color;
  322. PrepareEdgePointList();
  323. var total = this.GetPointsCount();
  324. int loopCount = OpenEnded() ? total - 1 : total;
  325. for (int loop = 0; loop < loopCount; loop++)
  326. {
  327. Color edgeColor = loop == m_ActiveEdge ? Color.yellow : Color.white;
  328. float edgeWidth = loop == m_ActiveEdge || (m_MouseClosestEdge == loop && closestEdgeHighlight) ? k_ActiveEdgeWidth : k_EdgeWidth;
  329. handles.color = edgeColor;
  330. handles.DrawAAPolyLine(lineTexture, edgeWidth, m_EdgePoints[loop]);
  331. }
  332. handles.color = handlesOldColor;
  333. }
  334. if (inEditMode)
  335. {
  336. if (otherClosestEdgeDistance > edgeDistance)
  337. {
  338. var farEnoughFromExistingPoints = MouseDistanceToPoint(FindClosestPointToMouse()) > k_MinExistingPointDistanceForInsert;
  339. var farEnoughtFromActiveTangents = MouseDistanceToClosestTangent() > k_MinExistingPointDistanceForInsert;
  340. var farEnoughFromExisting = farEnoughFromExistingPoints && farEnoughtFromActiveTangents;
  341. var hoveringEdge = m_MouseClosestEdgeDist < k_EdgeHoverDistance;
  342. var handleEvent = hoveringEdge && farEnoughFromExisting && !m_RectSelectionTool.isSelecting;
  343. if (GUIUtility.hotControl == k_EdgeID || EdgeDragModifiersActive() && handleEvent)
  344. HandleEdgeDragging(m_MouseClosestEdge);
  345. else if (GUIUtility.hotControl == k_CreatorID || (currentEvent.modifiers == EventModifiers.None && handleEvent))
  346. HandlePointInsertToEdge(m_MouseClosestEdge, m_MouseClosestEdgeDist);
  347. }
  348. }
  349. if (guiUtility.hotControl != k_CreatorID && m_NewPointIndex != -1)
  350. {
  351. m_NewPointDragFinished = true;
  352. guiUtility.keyboardControl = 0;
  353. m_NewPointIndex = -1;
  354. }
  355. if (guiUtility.hotControl != k_EdgeID && m_ActiveEdge != -1)
  356. {
  357. m_ActiveEdge = -1;
  358. }
  359. }
  360. public void Tangents()
  361. {
  362. if (activePoint < 0 || m_Selection.Count > 1 || GetTangentMode(activePoint) == TangentMode.Linear)
  363. return;
  364. var evt = eventSystem.current;
  365. var p = this.GetPointPosition(activePoint);
  366. var lt = this.GetPointLTangent(activePoint);
  367. var rt = this.GetPointRTangent(activePoint);
  368. var isHot = (guiUtility.hotControl == k_RightTangentID || guiUtility.hotControl == k_LeftTangentID);
  369. var allZero = lt.sqrMagnitude == 0 && rt.sqrMagnitude == 0;
  370. if (isHot || !allZero)
  371. {
  372. var m = this.GetTangentMode(activePoint);
  373. var mouseDown = evt.GetTypeForControl(k_RightTangentID) == EventType.MouseDown || evt.GetTypeForControl(k_LeftTangentID) == EventType.MouseDown;
  374. var mouseUp = evt.GetTypeForControl(k_RightTangentID) == EventType.MouseUp || evt.GetTypeForControl(k_LeftTangentID) == EventType.MouseUp;
  375. var nlt = DoTangent(p, p + lt, k_LeftTangentID, activePoint, k_TangentColor);
  376. var nrt = DoTangent(p, p + rt, k_RightTangentID, activePoint, GetTangentMode(activePoint) == TangentMode.Broken ? k_TangentColorAlternative : k_TangentColor);
  377. var changed = (nlt != lt || nrt != rt);
  378. allZero = nlt.sqrMagnitude == 0 && nrt.sqrMagnitude == 0;
  379. // if indeed we are dragging one of the handles and we just shift+mousedown, toggle the point. this happens even without change!
  380. if (isHot && mouseDown)
  381. {
  382. var nm = ((int)m + 1) % 3;
  383. m = (TangentMode)nm;
  384. //m = m == PointMode.Continuous ? PointMode.Broken : PointMode.Continuous;
  385. this.SetTangentMode(activePoint, m);
  386. }
  387. // make it broken when both tangents are zero
  388. if (mouseUp && allZero)
  389. {
  390. this.SetTangentMode(activePoint, TangentMode.Linear);
  391. changed = true;
  392. }
  393. // only do something when it's changed
  394. if (changed)
  395. {
  396. RecordUndo();
  397. this.SetPointLTangent(activePoint, nlt);
  398. this.SetPointRTangent(activePoint, nrt);
  399. RefreshTangents(activePoint, guiUtility.hotControl == k_RightTangentID);
  400. Repaint();
  401. }
  402. }
  403. }
  404. public void Points()
  405. {
  406. var wantsDelete =
  407. (UnityEngine.Event.current.type == EventType.ExecuteCommand || UnityEngine.Event.current.type == EventType.ValidateCommand)
  408. && (UnityEngine.Event.current.commandName == EventCommandNames.SoftDelete || UnityEngine.Event.current.commandName == EventCommandNames.Delete);
  409. for (int i = 0; i < this.GetPointsCount(); i++)
  410. {
  411. if (i == m_NewPointIndex)
  412. continue;
  413. var p0 = this.GetPointPosition(i);
  414. var id = guiUtility.GetControlID(5353, FocusType.Keyboard);
  415. var mouseDown = currentEvent.GetTypeForControl(id) == EventType.MouseDown;
  416. var mouseUp = currentEvent.GetTypeForControl(id) == EventType.MouseUp;
  417. EditorGUI.BeginChangeCheck();
  418. if (currentEvent.type == EventType.Repaint)
  419. {
  420. ColorEnum c = GetColorForPoint(i, id);
  421. handleOutlineColor = k_OutlineColor[(int)c];
  422. handleFillColor = k_FillColor[(int)c];
  423. }
  424. var np = p0;
  425. var hotControlBefore = guiUtility.hotControl;
  426. if (!currentEvent.alt || guiUtility.hotControl == id)
  427. np = DoSlider(id, p0, Vector3.up, Vector3.right, GetHandleSizeForPoint(i), GetCapForPoint(i));
  428. else if (currentEvent.type == EventType.Repaint)
  429. GetCapForPoint(i)(id, p0, Quaternion.LookRotation(Vector3.forward, Vector3.up), GetHandleSizeForPoint(i), currentEvent.type);
  430. var hotcontrolAfter = guiUtility.hotControl;
  431. if (mouseUp && hotControlBefore == id && hotcontrolAfter == 0 && (currentEvent.mousePosition == m_MousePositionLastMouseDown) && !currentEvent.shift)
  432. HandlePointClick(i);
  433. if (EditorGUI.EndChangeCheck())
  434. {
  435. RecordUndo();
  436. np = Snap(np);
  437. MoveSelections(np - p0);
  438. }
  439. if (guiUtility.hotControl == id && mouseDown && !m_Selection.Contains(i))
  440. {
  441. SelectPoint(i, currentEvent.shift ? SelectionType.Additive : SelectionType.Normal);
  442. Repaint();
  443. }
  444. if (m_NewPointDragFinished && activePoint == i && id != -1)
  445. {
  446. guiUtility.keyboardControl = id;
  447. m_NewPointDragFinished = false;
  448. }
  449. }
  450. if (wantsDelete)
  451. {
  452. if (currentEvent.type == EventType.ValidateCommand)
  453. {
  454. currentEvent.Use();
  455. }
  456. else if (currentEvent.type == EventType.ExecuteCommand)
  457. {
  458. RecordUndo();
  459. DeleteSelections();
  460. currentEvent.Use();
  461. }
  462. }
  463. }
  464. public void HandlePointInsertToEdge(int closestEdge, float closestEdgeDist)
  465. {
  466. var pointCreatorIsHot = GUIUtility.hotControl == k_CreatorID;
  467. Vector3 position = pointCreatorIsHot ? GetPointPosition(m_NewPointIndex) : FindClosestPointOnEdge(closestEdge, ScreenToLocal(currentEvent.mousePosition), 100);
  468. EditorGUI.BeginChangeCheck();
  469. handleFillColor = k_FillColor[(int)ColorEnum.ESelectedHovered];
  470. handleOutlineColor = k_OutlineColor[(int)ColorEnum.ESelectedHovered];
  471. if (!pointCreatorIsHot)
  472. {
  473. handleFillColor = handleFillColor.AlphaMultiplied(0.5f);
  474. handleOutlineColor = handleOutlineColor.AlphaMultiplied(0.5f);
  475. }
  476. int hotControlBefore = GUIUtility.hotControl;
  477. var newPosition = DoSlider(k_CreatorID, position, Vector3.up, Vector3.right, GetHandleSizeForPoint(closestEdge), RectCap);
  478. if (hotControlBefore != k_CreatorID && GUIUtility.hotControl == k_CreatorID)
  479. {
  480. RecordUndo();
  481. m_NewPointIndex = NextIndex(closestEdge, GetPointsCount());
  482. InsertPointAt(m_NewPointIndex, newPosition);
  483. SelectPoint(m_NewPointIndex, SelectionType.Normal);
  484. }
  485. else if (EditorGUI.EndChangeCheck())
  486. {
  487. RecordUndo();
  488. newPosition = Snap(newPosition);
  489. MoveSelections(newPosition - position);
  490. }
  491. }
  492. private void HandleEdgeDragging(int closestEdge)
  493. {
  494. switch (currentEvent.type)
  495. {
  496. case EventType.MouseDown:
  497. m_ActiveEdge = closestEdge;
  498. m_EdgeDragStartP0 = GetPointPosition(m_ActiveEdge);
  499. m_EdgeDragStartP1 = GetPointPosition(NextIndex(m_ActiveEdge, GetPointsCount()));
  500. if (currentEvent.shift)
  501. {
  502. RecordUndo();
  503. InsertPointAt(m_ActiveEdge + 1, m_EdgeDragStartP0);
  504. InsertPointAt(m_ActiveEdge + 2, m_EdgeDragStartP1);
  505. m_ActiveEdge++;
  506. }
  507. m_EdgeDragStartMousePosition = ScreenToLocal(currentEvent.mousePosition);
  508. GUIUtility.hotControl = k_EdgeID;
  509. currentEvent.Use();
  510. break;
  511. case EventType.MouseDrag:
  512. // This can happen when MouseDown event happen when dragging a point instead of line
  513. if (GUIUtility.hotControl == k_EdgeID)
  514. {
  515. RecordUndo();
  516. Vector3 mousePos = ScreenToLocal(currentEvent.mousePosition);
  517. Vector3 delta = mousePos - m_EdgeDragStartMousePosition;
  518. Vector3 position = GetPointPosition(m_ActiveEdge);
  519. Vector3 newPosition = m_EdgeDragStartP0 + delta;
  520. newPosition = Snap(newPosition);
  521. Vector3 snappedDelta = newPosition - position;
  522. var i0 = m_ActiveEdge;
  523. var i1 = NextIndex(m_ActiveEdge, GetPointsCount());
  524. SetPointPosition(m_ActiveEdge, GetPointPosition(i0) + snappedDelta);
  525. SetPointPosition(i1, GetPointPosition(i1) + snappedDelta);
  526. currentEvent.Use();
  527. }
  528. break;
  529. case EventType.MouseUp:
  530. if (GUIUtility.hotControl == k_EdgeID)
  531. {
  532. m_ActiveEdge = -1;
  533. GUIUtility.hotControl = 0;
  534. currentEvent.Use();
  535. }
  536. break;
  537. }
  538. }
  539. Vector3 DoTangent(Vector3 p0, Vector3 t0, int cid, int pointIndex, Color color)
  540. {
  541. var handleSize = GetHandleSizeForPoint(pointIndex);
  542. var tangentSize = GetTangentSizeForPoint(pointIndex);
  543. handles.color = color;
  544. float distance = HandleUtility.DistanceToCircle(t0, tangentSize);
  545. if (lineTexture != null)
  546. handles.DrawAAPolyLine(lineTexture, new Vector3[] {p0, t0});
  547. else
  548. handles.DrawLine(p0, t0);
  549. handleOutlineColor = distance > 0f ? color : k_OutlineColor[(int)ColorEnum.ESelectedHovered];
  550. handleFillColor = color;
  551. var delta = DoSlider(cid, t0, Vector3.up, Vector3.right, tangentSize, GetCapForTangent(pointIndex)) - p0;
  552. return delta.magnitude < handleSize ? Vector3.zero : delta;
  553. }
  554. public void HandlePointClick(int pointIndex)
  555. {
  556. if (m_Selection.Count > 1)
  557. {
  558. m_Selection.SelectPoint(pointIndex, SelectionType.Normal);
  559. }
  560. else if (!currentEvent.control && !currentEvent.shift && m_ActivePointOnLastMouseDown == activePoint)
  561. {
  562. OnPointClick(pointIndex);
  563. }
  564. }
  565. public void CycleTangentMode()
  566. {
  567. TangentMode oldMode = GetTangentMode(activePoint);
  568. TangentMode newMode = GetNextTangentMode(oldMode);
  569. SetTangentMode(activePoint, newMode);
  570. RefreshTangentsAfterModeChange(activePoint, oldMode, newMode);
  571. }
  572. public static TangentMode GetNextTangentMode(TangentMode current)
  573. {
  574. return (TangentMode)((((int)current) + 1) % Enum.GetValues(typeof(TangentMode)).Length);
  575. }
  576. public void RefreshTangentsAfterModeChange(int pointIndex, TangentMode oldMode, TangentMode newMode)
  577. {
  578. if (oldMode != TangentMode.Linear && newMode == TangentMode.Linear)
  579. {
  580. SetPointLTangent(pointIndex, Vector3.zero);
  581. SetPointRTangent(pointIndex, Vector3.zero);
  582. }
  583. if (newMode == TangentMode.Continuous)
  584. {
  585. if (oldMode == TangentMode.Broken)
  586. {
  587. SetPointRTangent(pointIndex, GetPointLTangent(pointIndex) * -1f);
  588. }
  589. if (oldMode == TangentMode.Linear)
  590. {
  591. FromAllZeroToTangents(pointIndex);
  592. }
  593. }
  594. }
  595. private ColorEnum GetColorForPoint(int pointIndex, int handleID)
  596. {
  597. bool hovered = MouseDistanceToPoint(pointIndex) <= 0f;
  598. bool selected = m_Selection.Contains(pointIndex);
  599. if (hovered && selected || GUIUtility.hotControl == handleID)
  600. return ColorEnum.ESelectedHovered;
  601. if (hovered)
  602. return ColorEnum.EUnselectedHovered;
  603. if (selected)
  604. return ColorEnum.ESelected;
  605. return ColorEnum.EUnselected;
  606. }
  607. private void FromAllZeroToTangents(int pointIndex)
  608. {
  609. Vector3 p0 = GetPointPosition(pointIndex);
  610. int prevPointIndex = pointIndex > 0 ? pointIndex - 1 : GetPointsCount() - 1;
  611. Vector3 lt = (GetPointPosition(prevPointIndex) - p0) * .33f;
  612. Vector3 rt = -lt;
  613. const float maxScreenLen = 100f;
  614. float ltScreenLen = (LocalToScreen(p0) - LocalToScreen(p0 + lt)).magnitude;
  615. float rtScreenLen = (LocalToScreen(p0) - LocalToScreen(p0 + rt)).magnitude;
  616. lt *= Mathf.Min(maxScreenLen / ltScreenLen, 1f);
  617. rt *= Mathf.Min(maxScreenLen / rtScreenLen, 1f);
  618. this.SetPointLTangent(pointIndex, lt);
  619. this.SetPointRTangent(pointIndex, rt);
  620. }
  621. private Handles.CapFunction GetCapForTangent(int index)
  622. {
  623. if (GetTangentMode(index) == TangentMode.Continuous)
  624. return CircleCap;
  625. return DiamondCap;
  626. }
  627. private DistanceToControl GetDistanceFuncForTangent(int index)
  628. {
  629. if (GetTangentMode(index) == TangentMode.Continuous)
  630. return DistanceToCircle();
  631. return HandleUtility.DistanceToDiamond;
  632. }
  633. private Handles.CapFunction GetCapForPoint(int index)
  634. {
  635. switch (GetTangentMode(index))
  636. {
  637. case TangentMode.Broken:
  638. return DiamondCap;
  639. case TangentMode.Continuous:
  640. return CircleCap;
  641. case TangentMode.Linear:
  642. return RectCap;
  643. }
  644. return DiamondCap;
  645. }
  646. private static float DistanceToCircleInternal(Vector3 position, Quaternion rotation, float size)
  647. {
  648. return HandleUtility.DistanceToCircle(position, size);
  649. }
  650. private float GetHandleSizeForPoint(int index)
  651. {
  652. return Camera.current != null ? HandleUtility.GetHandleSize(GetPointPosition(index)) * 0.075f : GetHandleSize();
  653. }
  654. private float GetTangentSizeForPoint(int index)
  655. {
  656. return GetHandleSizeForPoint(index) * 0.8f;
  657. }
  658. private void RefreshTangents(int index, bool rightIsActive)
  659. {
  660. var m = GetTangentMode(index);
  661. var lt = GetPointLTangent(index);
  662. var rt = GetPointRTangent(index);
  663. // mirror the change on the other tangent for continuous
  664. if (m == TangentMode.Continuous)
  665. {
  666. if (rightIsActive)
  667. {
  668. lt = -rt;
  669. var mag = lt.magnitude;
  670. lt = lt.normalized * mag;
  671. }
  672. else
  673. {
  674. rt = -lt;
  675. var mag = rt.magnitude;
  676. rt = rt.normalized * mag;
  677. }
  678. }
  679. this.SetPointLTangent(activePoint, lt);
  680. this.SetPointRTangent(activePoint, rt);
  681. }
  682. private void StoreMouseDownState()
  683. {
  684. m_MousePositionLastMouseDown = currentEvent.mousePosition;
  685. m_ActivePointOnLastMouseDown = activePoint;
  686. }
  687. private void DelayedResetIfNecessary()
  688. {
  689. if (m_DelayedReset)
  690. {
  691. guiUtility.hotControl = 0;
  692. guiUtility.keyboardControl = 0;
  693. m_Selection.Clear();
  694. activePoint = -1;
  695. m_DelayedReset = false;
  696. }
  697. }
  698. public Vector3 FindClosestPointOnEdge(int edgeIndex, Vector3 position, int iterations)
  699. {
  700. float step = 1f / iterations;
  701. float closestDistance = float.MaxValue;
  702. float closestDistanceIndex = edgeIndex;
  703. for (float a = 0f; a <= 1f; a += step)
  704. {
  705. Vector3 pos = GetPositionByIndex(edgeIndex + a);
  706. float distance = (position - pos).sqrMagnitude;
  707. if (distance < closestDistance)
  708. {
  709. closestDistance = distance;
  710. closestDistanceIndex = edgeIndex + a;
  711. }
  712. }
  713. return GetPositionByIndex(closestDistanceIndex);
  714. }
  715. private Vector3 GetPositionByIndex(float index)
  716. {
  717. int indexA = Mathf.FloorToInt(index);
  718. int indexB = NextIndex(indexA, GetPointsCount());
  719. float subPosition = index - indexA;
  720. return GetPoint(GetPointPosition(indexA), GetPointPosition(indexB), GetPointRTangent(indexA), GetPointLTangent(indexB), subPosition);
  721. }
  722. private static Vector3 GetPoint(Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, float t)
  723. {
  724. t = Mathf.Clamp01(t);
  725. float a = 1f - t;
  726. return a * a * a * startPosition + 3f * a * a * t * (startPosition + startTangent) + 3f * a * t * t * (endPosition + endTangent) + t * t * t * endPosition;
  727. }
  728. private int FindClosestPointToMouse()
  729. {
  730. Vector3 mousePos = ScreenToLocal(currentEvent.mousePosition);
  731. return FindClosestPointIndex(mousePos);
  732. }
  733. private float MouseDistanceToClosestTangent()
  734. {
  735. if (activePoint < 0)
  736. return float.MaxValue;
  737. var lt = GetPointLTangent(activePoint);
  738. var rt = GetPointRTangent(activePoint);
  739. if (lt.sqrMagnitude == 0f && rt.sqrMagnitude == 0f)
  740. return float.MaxValue;
  741. var p = GetPointPosition(activePoint);
  742. var tangentSize = GetTangentSizeForPoint(activePoint);
  743. return Mathf.Min(
  744. HandleUtility.DistanceToRectangle(p + lt, Quaternion.identity, tangentSize),
  745. HandleUtility.DistanceToRectangle(p + rt, Quaternion.identity, tangentSize)
  746. );
  747. }
  748. private int FindClosestPointIndex(Vector3 position)
  749. {
  750. float closestDistance = float.MaxValue;
  751. int closestIndex = -1;
  752. for (int i = 0; i < this.GetPointsCount(); i++)
  753. {
  754. var p0 = this.GetPointPosition(i);
  755. var distance = (p0 - position).sqrMagnitude;
  756. if (distance < closestDistance)
  757. {
  758. closestIndex = i;
  759. closestDistance = distance;
  760. }
  761. }
  762. return closestIndex;
  763. }
  764. private DistanceToControl GetDistanceFuncForPoint(int index)
  765. {
  766. switch (GetTangentMode(index))
  767. {
  768. case TangentMode.Broken:
  769. return DistanceToDiamond();
  770. case TangentMode.Continuous:
  771. return DistanceToCircle();
  772. case TangentMode.Linear:
  773. return DistanceToRectangle();
  774. }
  775. return DistanceToRectangle();
  776. }
  777. private float MouseDistanceToPoint(int index)
  778. {
  779. switch (GetTangentMode(index))
  780. {
  781. case TangentMode.Broken:
  782. return HandleUtility.DistanceToDiamond(GetPointPosition(index), Quaternion.identity, GetHandleSizeForPoint(index));
  783. case TangentMode.Linear:
  784. return HandleUtility.DistanceToRectangle(GetPointPosition(index), Quaternion.identity, GetHandleSizeForPoint(index));
  785. case TangentMode.Continuous:
  786. return HandleUtility.DistanceToCircle(GetPointPosition(index), GetHandleSizeForPoint(index));
  787. }
  788. return float.MaxValue;
  789. }
  790. private bool EdgeDragModifiersActive()
  791. {
  792. return currentEvent.modifiers == EventModifiers.Control;
  793. }
  794. private static Vector3 DoSlider(int id, Vector3 position, Vector3 slide1, Vector3 slide2, float s, Handles.CapFunction cap)
  795. {
  796. return Slider2D.Do(id, position, Vector3.zero, Vector3.Cross(slide1, slide2), slide1, slide2, s, cap, Vector2.zero, false);
  797. }
  798. public void RectCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
  799. {
  800. if (eventType == EventType.Layout || eventType == EventType.MouseMove)
  801. {
  802. HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(position, size * 0.5f));
  803. }
  804. else if (eventType == EventType.Repaint)
  805. {
  806. Vector3 normal = handles.matrix.GetColumn(2);
  807. Vector3 projectedUp = (ProjectPointOnPlane(normal, position, position + Vector3.up) - position).normalized;
  808. Quaternion localRotation = Quaternion.LookRotation(handles.matrix.GetColumn(2), projectedUp);
  809. Vector3 sideways = localRotation * Vector3.right * size;
  810. Vector3 up = localRotation * Vector3.up * size;
  811. List<Vector3> list = GetDrawBatchList(new DrawBatchDataKey(handleFillColor, GL.TRIANGLES));
  812. list.Add(position + sideways + up);
  813. list.Add(position + sideways - up);
  814. list.Add(position - sideways - up);
  815. list.Add(position - sideways - up);
  816. list.Add(position - sideways + up);
  817. list.Add(position + sideways + up);
  818. list = GetDrawBatchList(new DrawBatchDataKey(handleOutlineColor, GL.LINES));
  819. list.Add(position + sideways + up);
  820. list.Add(position + sideways - up);
  821. list.Add(position + sideways - up);
  822. list.Add(position - sideways - up);
  823. list.Add(position - sideways - up);
  824. list.Add(position - sideways + up);
  825. list.Add(position - sideways + up);
  826. list.Add(position + sideways + up);
  827. }
  828. }
  829. public void CircleCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
  830. {
  831. if (eventType == EventType.Layout || eventType == EventType.MouseMove)
  832. {
  833. HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(position, size * 0.5f));
  834. }
  835. else if (eventType == EventType.Repaint)
  836. {
  837. Vector3 forward = handleMatrixrotation * rotation * Vector3.forward;
  838. Vector3 tangent = Vector3.Cross(forward, Vector3.up);
  839. if (tangent.sqrMagnitude < .001f)
  840. tangent = Vector3.Cross(forward, Vector3.right);
  841. Vector3[] points = new Vector3[60];
  842. handles.SetDiscSectionPoints(points, position, forward, tangent, 360f, size);
  843. List<Vector3> list = GetDrawBatchList(new DrawBatchDataKey(handleFillColor, GL.TRIANGLES));
  844. for (int i = 1; i < points.Length; i++)
  845. {
  846. list.Add(position);
  847. list.Add(points[i]);
  848. list.Add(points[i - 1]);
  849. }
  850. list = GetDrawBatchList(new DrawBatchDataKey(handleOutlineColor, GL.LINES));
  851. for (int i = 0; i < points.Length - 1; i++)
  852. {
  853. list.Add(points[i]);
  854. list.Add(points[i + 1]);
  855. }
  856. }
  857. }
  858. public void DiamondCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
  859. {
  860. if (eventType == EventType.Layout || eventType == EventType.MouseMove)
  861. {
  862. HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(position, size * 0.5f));
  863. }
  864. else if (eventType == EventType.Repaint)
  865. {
  866. Vector3 normal = handles.matrix.GetColumn(2);
  867. Vector3 projectedUp = (ProjectPointOnPlane(normal, position, position + Vector3.up) - position).normalized;
  868. Quaternion localRotation = Quaternion.LookRotation(handles.matrix.GetColumn(2), projectedUp);
  869. Vector3 sideways = localRotation * Vector3.right * size * 1.25f;
  870. Vector3 up = localRotation * Vector3.up * size * 1.25f;
  871. List<Vector3> list = GetDrawBatchList(new DrawBatchDataKey(handleFillColor, GL.TRIANGLES));
  872. list.Add(position - up);
  873. list.Add(position + sideways);
  874. list.Add(position - sideways);
  875. list.Add(position - sideways);
  876. list.Add(position + up);
  877. list.Add(position + sideways);
  878. list = GetDrawBatchList(new DrawBatchDataKey(handleOutlineColor, GL.LINES));
  879. list.Add(position + sideways);
  880. list.Add(position - up);
  881. list.Add(position - up);
  882. list.Add(position - sideways);
  883. list.Add(position - sideways);
  884. list.Add(position + up);
  885. list.Add(position + up);
  886. list.Add(position + sideways);
  887. }
  888. }
  889. private static int NextIndex(int index, int total)
  890. {
  891. return mod(index + 1, total);
  892. }
  893. private static int PreviousIndex(int index, int total)
  894. {
  895. return mod(index - 1, total);
  896. }
  897. private static int mod(int x, int m)
  898. {
  899. int r = x % m;
  900. return r < 0 ? r + m : r;
  901. }
  902. private static Vector3 ProjectPointOnPlane(Vector3 planeNormal, Vector3 planePoint, Vector3 point)
  903. {
  904. planeNormal.Normalize();
  905. var distance = -Vector3.Dot(planeNormal.normalized, (point - planePoint));
  906. return point + planeNormal * distance;
  907. }
  908. public void RegisterToShapeEditor(ShapeEditor se)
  909. {
  910. ++m_ShapeEditorRegisteredTo;
  911. se.m_ShapeEditorListeners.Add(this);
  912. }
  913. public void UnregisterFromShapeEditor(ShapeEditor se)
  914. {
  915. --m_ShapeEditorRegisteredTo;
  916. se.m_ShapeEditorListeners.Remove(this);
  917. }
  918. private void OnShapeEditorUpdateDone()
  919. {
  920. // When all the ShapeEditor we are interested in
  921. // has completed their update, we reset our internal values
  922. ++m_ShapeEditorUpdateDone;
  923. if (m_ShapeEditorUpdateDone >= m_ShapeEditorRegisteredTo)
  924. {
  925. m_ShapeEditorUpdateDone = 0;
  926. m_MouseClosestEdge = -1;
  927. m_MouseClosestEdgeDist = float.MaxValue;
  928. m_EdgePoints = null;
  929. }
  930. }
  931. private void ClearSelectedPoints()
  932. {
  933. selectedPoints.Clear();
  934. activePoint = -1;
  935. }
  936. private void SelectPointsInRect(Rect r, SelectionType st)
  937. {
  938. var localRect = EditorGUIExt.FromToRect(ScreenToLocal(r.min), ScreenToLocal(r.max));
  939. m_Selection.RectSelect(localRect, st);
  940. }
  941. private void DeleteSelections()
  942. {
  943. foreach (var se in m_ShapeEditorListeners)
  944. se.m_Selection.DeleteSelection();
  945. m_Selection.DeleteSelection();
  946. }
  947. private void MoveSelections(Vector2 distance)
  948. {
  949. foreach (var se in m_ShapeEditorListeners)
  950. se.m_Selection.MoveSelection(distance);
  951. m_Selection.MoveSelection(distance);
  952. }
  953. private void SelectPoint(int index, SelectionType st)
  954. {
  955. if (st == SelectionType.Normal)
  956. {
  957. foreach (var se in m_ShapeEditorListeners)
  958. se.ClearSelectedPoints();
  959. }
  960. m_Selection.SelectPoint(index, st);
  961. }
  962. }
  963. }