EditablePathView.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEditor;
  6. using UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework;
  7. namespace UnityEditor.Experimental.Rendering.Universal.Path2D
  8. {
  9. internal class EditablePathView : IEditablePathView
  10. {
  11. const float kSnappingDistance = 15f;
  12. const string kDeleteCommandName = "Delete";
  13. const string kSoftDeleteCommandName = "SoftDelete";
  14. public IEditablePathController controller { get; set; }
  15. private Control m_PointControl;
  16. private Control m_EdgeControl;
  17. private Control m_LeftTangentControl;
  18. private Control m_RightTangentControl;
  19. private GUIAction m_MovePointAction;
  20. private GUIAction m_MoveEdgeAction;
  21. private GUIAction m_CreatePointAction;
  22. private GUIAction m_RemovePointAction1;
  23. private GUIAction m_RemovePointAction2;
  24. private GUIAction m_MoveLeftTangentAction;
  25. private GUIAction m_MoveRightTangentAction;
  26. private IDrawer m_Drawer;
  27. public EditablePathView() : this(new Drawer()) { }
  28. public EditablePathView(IDrawer drawer)
  29. {
  30. m_Drawer = drawer;
  31. m_PointControl = new GenericControl("Point")
  32. {
  33. count = GetPointCount,
  34. distance = (guiState, i) =>
  35. {
  36. var position = GetPoint(i).position;
  37. return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
  38. },
  39. position = (i) => { return GetPoint(i).position; },
  40. forward = (i) => { return GetForward(); },
  41. up = (i) => { return GetUp(); },
  42. right = (i) => { return GetRight(); },
  43. onRepaint = DrawPoint
  44. };
  45. m_EdgeControl = new GenericControl("Edge")
  46. {
  47. onEndLayout = (guiState) => { controller.AddClosestPath(m_EdgeControl.layoutData.distance); },
  48. count = GetEdgeCount,
  49. distance = DistanceToEdge,
  50. position = (i) => { return GetPoint(i).position; },
  51. forward = (i) => { return GetForward(); },
  52. up = (i) => { return GetUp(); },
  53. right = (i) => { return GetRight(); },
  54. onRepaint = DrawEdge
  55. };
  56. m_LeftTangentControl = new GenericControl("LeftTangent")
  57. {
  58. count = () =>
  59. {
  60. if (GetShapeType() != ShapeType.Spline)
  61. return 0;
  62. return GetPointCount();
  63. },
  64. distance = (guiState, i) =>
  65. {
  66. if (!IsSelected(i) || IsOpenEnded() && i == 0)
  67. return float.MaxValue;
  68. var position = GetLeftTangent(i);
  69. return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
  70. },
  71. position = (i) => { return GetLeftTangent(i); },
  72. forward = (i) => { return GetForward(); },
  73. up = (i) => { return GetUp(); },
  74. right = (i) => { return GetRight(); },
  75. onRepaint = (guiState, control, i) =>
  76. {
  77. if (!IsSelected(i) || IsOpenEnded() && i == 0)
  78. return;
  79. var position = GetPoint(i).position;
  80. var leftTangent = GetLeftTangent(i);
  81. m_Drawer.DrawTangent(position, leftTangent);
  82. }
  83. };
  84. m_RightTangentControl = new GenericControl("RightTangent")
  85. {
  86. count = () =>
  87. {
  88. if (GetShapeType() != ShapeType.Spline)
  89. return 0;
  90. return GetPointCount();
  91. },
  92. distance = (guiState, i) =>
  93. {
  94. if (!IsSelected(i) || IsOpenEnded() && i == GetPointCount()-1)
  95. return float.MaxValue;
  96. var position = GetRightTangent(i);
  97. return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
  98. },
  99. position = (i) => { return GetRightTangent(i); },
  100. forward = (i) => { return GetForward(); },
  101. up = (i) => { return GetUp(); },
  102. right = (i) => { return GetRight(); },
  103. onRepaint = (guiState, control, i) =>
  104. {
  105. if (!IsSelected(i) || IsOpenEnded() && i == GetPointCount()-1)
  106. return;
  107. var position = GetPoint(i).position;
  108. var rightTangent = GetRightTangent(i);
  109. m_Drawer.DrawTangent(position, rightTangent);
  110. }
  111. };
  112. m_CreatePointAction = new CreatePointAction(m_PointControl, m_EdgeControl)
  113. {
  114. enable = (guiState, action) => { return !guiState.isShiftDown && controller.closestEditablePath == controller.editablePath; },
  115. enableRepaint = EnableCreatePointRepaint,
  116. repaintOnMouseMove = (guiState, action) => { return true; },
  117. guiToWorld = GUIToWorld,
  118. onCreatePoint = (index, position) =>
  119. {
  120. controller.RegisterUndo("Create Point");
  121. controller.CreatePoint(index, position);
  122. },
  123. onPreRepaint = (guiState, action) =>
  124. {
  125. if (GetPointCount() > 0)
  126. {
  127. var position = ClosestPointInEdge(guiState, guiState.mousePosition, m_EdgeControl.layoutData.index);
  128. m_Drawer.DrawCreatePointPreview(position);
  129. }
  130. }
  131. };
  132. Action<IGUIState> removePoints = (guiState) =>
  133. {
  134. controller.RegisterUndo("Remove Point");
  135. controller.RemoveSelectedPoints();
  136. guiState.changed = true;
  137. };
  138. m_RemovePointAction1 = new CommandAction(kDeleteCommandName)
  139. {
  140. enable = (guiState, action) => { return GetSelectedPointCount() > 0; },
  141. onCommand = removePoints
  142. };
  143. m_RemovePointAction2 = new CommandAction(kSoftDeleteCommandName)
  144. {
  145. enable = (guiState, action) => { return GetSelectedPointCount() > 0; },
  146. onCommand = removePoints
  147. };
  148. m_MovePointAction = new SliderAction(m_PointControl)
  149. {
  150. onClick = (guiState, control) =>
  151. {
  152. var index = control.layoutData.index;
  153. if (!guiState.isActionKeyDown && !IsSelected(index))
  154. controller.ClearSelection();
  155. controller.SelectPoint(index, true);
  156. guiState.changed = true;
  157. },
  158. onSliderBegin = (guiState, control, position) =>
  159. {
  160. controller.RegisterUndo("Move Point");
  161. },
  162. onSliderChanged = (guiState, control, position) =>
  163. {
  164. var index = control.hotLayoutData.index;
  165. var delta = SnapIfNeeded(position) - GetPoint(index).position;
  166. controller.MoveSelectedPoints(delta);
  167. }
  168. };
  169. m_MoveEdgeAction = new SliderAction(m_EdgeControl)
  170. {
  171. enable = (guiState, action) => { return guiState.isShiftDown; },
  172. onSliderBegin = (guiState, control, position) =>
  173. {
  174. controller.RegisterUndo("Move Edge");
  175. },
  176. onSliderChanged = (guiState, control, position) =>
  177. {
  178. var index = control.hotLayoutData.index;
  179. var delta = position - GetPoint(index).position;
  180. controller.MoveEdge(index, delta);
  181. }
  182. };
  183. var cachedRightTangent = Vector3.zero;
  184. var cachedLeftTangent = Vector3.zero;
  185. m_MoveLeftTangentAction = new SliderAction(m_LeftTangentControl)
  186. {
  187. onSliderBegin = (guiState, control, position) =>
  188. {
  189. controller.RegisterUndo("Move Tangent");
  190. cachedRightTangent = GetPoint(control.hotLayoutData.index).rightTangent;
  191. },
  192. onSliderChanged = (guiState, control, position) =>
  193. {
  194. var index = control.hotLayoutData.index;
  195. var setToLinear = guiState.nearestControl == m_PointControl.ID && m_PointControl.layoutData.index == index;
  196. controller.SetLeftTangent(index, position, setToLinear, guiState.isShiftDown, cachedRightTangent);
  197. },
  198. onSliderEnd = (guiState, control, position) =>
  199. {
  200. controller.editablePath.UpdateTangentMode(control.hotLayoutData.index);
  201. guiState.changed = true;
  202. }
  203. };
  204. m_MoveRightTangentAction = new SliderAction(m_RightTangentControl)
  205. {
  206. onSliderBegin = (guiState, control, position) =>
  207. {
  208. controller.RegisterUndo("Move Tangent");
  209. cachedLeftTangent = GetPoint(control.hotLayoutData.index).leftTangent;
  210. },
  211. onSliderChanged = (guiState, control, position) =>
  212. {
  213. var index = control.hotLayoutData.index;
  214. var setToLinear = guiState.nearestControl == m_PointControl.ID && m_PointControl.layoutData.index == index;
  215. controller.SetRightTangent(index, position, setToLinear, guiState.isShiftDown, cachedLeftTangent);
  216. },
  217. onSliderEnd = (guiState, control, position) =>
  218. {
  219. controller.editablePath.UpdateTangentMode(control.hotLayoutData.index);
  220. guiState.changed = true;
  221. }
  222. };
  223. }
  224. public void Install(GUISystem guiSystem)
  225. {
  226. guiSystem.AddControl(m_EdgeControl);
  227. guiSystem.AddControl(m_PointControl);
  228. guiSystem.AddControl(m_LeftTangentControl);
  229. guiSystem.AddControl(m_RightTangentControl);
  230. guiSystem.AddAction(m_CreatePointAction);
  231. guiSystem.AddAction(m_RemovePointAction1);
  232. guiSystem.AddAction(m_RemovePointAction2);
  233. guiSystem.AddAction(m_MovePointAction);
  234. guiSystem.AddAction(m_MoveEdgeAction);
  235. guiSystem.AddAction(m_MoveLeftTangentAction);
  236. guiSystem.AddAction(m_MoveRightTangentAction);
  237. }
  238. public void Uninstall(GUISystem guiSystem)
  239. {
  240. guiSystem.RemoveControl(m_EdgeControl);
  241. guiSystem.RemoveControl(m_PointControl);
  242. guiSystem.RemoveControl(m_LeftTangentControl);
  243. guiSystem.RemoveControl(m_RightTangentControl);
  244. guiSystem.RemoveAction(m_CreatePointAction);
  245. guiSystem.RemoveAction(m_RemovePointAction1);
  246. guiSystem.RemoveAction(m_RemovePointAction2);
  247. guiSystem.RemoveAction(m_MovePointAction);
  248. guiSystem.RemoveAction(m_MoveEdgeAction);
  249. guiSystem.RemoveAction(m_MoveLeftTangentAction);
  250. guiSystem.RemoveAction(m_MoveRightTangentAction);
  251. }
  252. private ControlPoint GetPoint(int index)
  253. {
  254. return controller.editablePath.GetPoint(index);
  255. }
  256. private int GetPointCount()
  257. {
  258. return controller.editablePath.pointCount;
  259. }
  260. private int GetEdgeCount()
  261. {
  262. if (controller.editablePath.isOpenEnded)
  263. return controller.editablePath.pointCount - 1;
  264. return controller.editablePath.pointCount;
  265. }
  266. private int GetSelectedPointCount()
  267. {
  268. return controller.editablePath.selection.Count;
  269. }
  270. private bool IsSelected(int index)
  271. {
  272. return controller.editablePath.selection.Contains(index);
  273. }
  274. private Vector3 GetForward()
  275. {
  276. return controller.editablePath.forward;
  277. }
  278. private Vector3 GetUp()
  279. {
  280. return controller.editablePath.up;
  281. }
  282. private Vector3 GetRight()
  283. {
  284. return controller.editablePath.right;
  285. }
  286. private Matrix4x4 GetLocalToWorldMatrix()
  287. {
  288. return controller.editablePath.localToWorldMatrix;
  289. }
  290. private ShapeType GetShapeType()
  291. {
  292. return controller.editablePath.shapeType;
  293. }
  294. private bool IsOpenEnded()
  295. {
  296. return controller.editablePath.isOpenEnded;
  297. }
  298. private Vector3 GetLeftTangent(int index)
  299. {
  300. return controller.editablePath.CalculateLeftTangent(index);
  301. }
  302. private Vector3 GetRightTangent(int index)
  303. {
  304. return controller.editablePath.CalculateRightTangent(index);
  305. }
  306. private int NextIndex(int index)
  307. {
  308. return EditablePathUtility.Mod(index + 1, GetPointCount());
  309. }
  310. private ControlPoint NextControlPoint(int index)
  311. {
  312. return GetPoint(NextIndex(index));
  313. }
  314. private int PrevIndex(int index)
  315. {
  316. return EditablePathUtility.Mod(index - 1, GetPointCount());
  317. }
  318. private ControlPoint PrevControlPoint(int index)
  319. {
  320. return GetPoint(PrevIndex(index));
  321. }
  322. private Vector3 ClosestPointInEdge(IGUIState guiState, Vector2 mousePosition, int index)
  323. {
  324. if (GetShapeType() == ShapeType.Polygon)
  325. {
  326. var p0 = GetPoint(index).position;
  327. var p1 = NextControlPoint(index).position;
  328. var mouseWorldPosition = GUIToWorld(guiState, mousePosition);
  329. var dir1 = (mouseWorldPosition - p0);
  330. var dir2 = (p1 - p0);
  331. return Mathf.Clamp01(Vector3.Dot(dir1, dir2.normalized) / dir2.magnitude) * dir2 + p0;
  332. }
  333. else if (GetShapeType() == ShapeType.Spline)
  334. {
  335. var nextIndex = NextIndex(index);
  336. float t;
  337. return BezierUtility.ClosestPointOnCurve(
  338. GUIToWorld(guiState, mousePosition),
  339. GetPoint(index).position,
  340. GetPoint(nextIndex).position,
  341. GetRightTangent(index),
  342. GetLeftTangent(nextIndex),
  343. out t);
  344. }
  345. return Vector3.zero;
  346. }
  347. private float DistanceToEdge(IGUIState guiState, int index)
  348. {
  349. if (GetShapeType() == ShapeType.Polygon)
  350. {
  351. return guiState.DistanceToSegment(GetPoint(index).position, NextControlPoint(index).position);
  352. }
  353. else if (GetShapeType() == ShapeType.Spline)
  354. {
  355. var closestPoint = ClosestPointInEdge(guiState, guiState.mousePosition, index);
  356. var closestPoint2 = HandleUtility.WorldToGUIPoint(closestPoint);
  357. return (closestPoint2 - guiState.mousePosition).magnitude;
  358. }
  359. return float.MaxValue;
  360. }
  361. private Vector3 GUIToWorld(IGUIState guiState, Vector2 position)
  362. {
  363. return guiState.GUIToWorld(position, GetForward(), GetLocalToWorldMatrix().MultiplyPoint3x4(Vector3.zero));
  364. }
  365. private void DrawPoint(IGUIState guiState, Control control, int index)
  366. {
  367. var position = GetPoint(index).position;
  368. if (guiState.hotControl == control.actionID && control.hotLayoutData.index == index || IsSelected(index))
  369. m_Drawer.DrawPointSelected(position);
  370. else if (guiState.hotControl == 0 && guiState.nearestControl == control.ID && control.layoutData.index == index)
  371. m_Drawer.DrawPointHovered(position);
  372. else
  373. m_Drawer.DrawPoint(position);
  374. }
  375. private void DrawEdge(IGUIState guiState, Control control, int index)
  376. {
  377. if (GetShapeType() == ShapeType.Polygon)
  378. {
  379. var nextIndex = NextIndex(index);
  380. var color = Color.white;
  381. if(guiState.nearestControl == control.ID && control.layoutData.index == index && guiState.hotControl == 0)
  382. color = Color.yellow;
  383. m_Drawer.DrawLine(GetPoint(index).position, GetPoint(nextIndex).position, 5f, color);
  384. }
  385. else if (GetShapeType() == ShapeType.Spline)
  386. {
  387. var nextIndex = NextIndex(index);
  388. var color = Color.white;
  389. if(guiState.nearestControl == control.ID && control.layoutData.index == index && guiState.hotControl == 0)
  390. color = Color.yellow;
  391. m_Drawer.DrawBezier(
  392. GetPoint(index).position,
  393. GetRightTangent(index),
  394. GetLeftTangent(nextIndex),
  395. GetPoint(nextIndex).position,
  396. 5f,
  397. color);
  398. }
  399. }
  400. private bool EnableCreatePointRepaint(IGUIState guiState, GUIAction action)
  401. {
  402. return guiState.nearestControl != m_PointControl.ID &&
  403. guiState.hotControl == 0 &&
  404. (guiState.nearestControl != m_LeftTangentControl.ID) &&
  405. (guiState.nearestControl != m_RightTangentControl.ID);
  406. }
  407. private Vector3 SnapIfNeeded(Vector3 position)
  408. {
  409. if (!controller.enableSnapping || controller.snapping == null)
  410. return position;
  411. var guiPosition = HandleUtility.WorldToGUIPoint(position);
  412. var snappedGuiPosition = HandleUtility.WorldToGUIPoint(controller.snapping.Snap(position));
  413. var sqrDistance = (guiPosition - snappedGuiPosition).sqrMagnitude;
  414. if (sqrDistance < kSnappingDistance * kSnappingDistance)
  415. position = controller.snapping.Snap(position);
  416. return position;
  417. }
  418. }
  419. }