No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SpriteOutlineModule.cs 39KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System;
  4. using System.Linq;
  5. using Unity.Collections;
  6. using UnityEditor.U2D.Sprites.SpriteEditorTool;
  7. using UnityEngine.U2D;
  8. using UnityEngine.UIElements;
  9. using Object = UnityEngine.Object;
  10. namespace UnityEditor.U2D.Sprites
  11. {
  12. // We need this so that undo/redo works
  13. [Serializable]
  14. internal class SpriteOutline
  15. {
  16. [SerializeField]
  17. public List<Vector2> m_Path = new List<Vector2>();
  18. public void Add(Vector2 point)
  19. {
  20. m_Path.Add(point);
  21. }
  22. public void Insert(int index, Vector2 point)
  23. {
  24. m_Path.Insert(index, point);
  25. }
  26. public void RemoveAt(int index)
  27. {
  28. m_Path.RemoveAt(index);
  29. }
  30. public Vector2 this[int index]
  31. {
  32. get { return m_Path[index]; }
  33. set { m_Path[index] = value; }
  34. }
  35. public int Count
  36. {
  37. get { return m_Path.Count; }
  38. }
  39. public void AddRange(IEnumerable<Vector2> addRange)
  40. {
  41. m_Path.AddRange(addRange);
  42. }
  43. }
  44. // Collection of outlines for a single Sprite
  45. [Serializable]
  46. internal class SpriteOutlineList
  47. {
  48. [SerializeField]
  49. List<SpriteOutline> m_SpriteOutlines;
  50. [SerializeField]
  51. float m_TessellationDetail = 0;
  52. public List<SpriteOutline> spriteOutlines { get { return m_SpriteOutlines; } set { m_SpriteOutlines = value; } }
  53. public GUID spriteID { get; private set; }
  54. public float tessellationDetail
  55. {
  56. get { return m_TessellationDetail; }
  57. set
  58. {
  59. m_TessellationDetail = value;
  60. m_TessellationDetail = Mathf.Min(1, m_TessellationDetail);
  61. m_TessellationDetail = Mathf.Max(0, m_TessellationDetail);
  62. }
  63. }
  64. public SpriteOutlineList(GUID guid)
  65. {
  66. this.spriteID = guid;
  67. m_SpriteOutlines = new List<SpriteOutline>();
  68. }
  69. public SpriteOutlineList(GUID guid, List<Vector2[]> list)
  70. {
  71. this.spriteID = guid;
  72. m_SpriteOutlines = new List<SpriteOutline>(list.Count);
  73. for (int i = 0; i < list.Count; ++i)
  74. {
  75. var newList = new SpriteOutline();
  76. newList.m_Path.AddRange(list[i]);
  77. m_SpriteOutlines.Add(newList);
  78. }
  79. }
  80. public SpriteOutlineList(GUID guid, List<SpriteOutline> list)
  81. {
  82. this.spriteID = guid;
  83. m_SpriteOutlines = list;
  84. }
  85. public List<Vector2[]> ToListVector()
  86. {
  87. var value = new List<Vector2[]>(m_SpriteOutlines.Count);
  88. foreach (var s in m_SpriteOutlines)
  89. {
  90. value.Add(s.m_Path.ToArray());
  91. }
  92. return value;
  93. }
  94. public List<Vector2[]> ToListVectorCapped(Rect rect)
  95. {
  96. var value = ToListVector();
  97. rect.center = Vector2.zero;
  98. foreach (var path in value)
  99. {
  100. for (int i = 0; i < path.Length; ++i)
  101. {
  102. var point = path[i];
  103. path[i] = SpriteOutlineModule.CapPointToRect(point, rect);
  104. }
  105. }
  106. return value;
  107. }
  108. public SpriteOutline this[int index]
  109. {
  110. get { return IsValidIndex(index) ? m_SpriteOutlines[index] : null; }
  111. set
  112. {
  113. if (IsValidIndex(index))
  114. m_SpriteOutlines[index] = value;
  115. }
  116. }
  117. public static implicit operator List<SpriteOutline>(SpriteOutlineList list)
  118. {
  119. return list != null ? list.m_SpriteOutlines : null;
  120. }
  121. public int Count { get { return m_SpriteOutlines.Count; } }
  122. bool IsValidIndex(int index)
  123. {
  124. return index >= 0 && index < Count;
  125. }
  126. }
  127. // Collection of Sprites' outlines
  128. internal class SpriteOutlineModel : ScriptableObject
  129. {
  130. [SerializeField]
  131. List<SpriteOutlineList> m_SpriteOutlineList = new List<SpriteOutlineList>();
  132. private SpriteOutlineModel()
  133. {}
  134. public SpriteOutlineList this[int index]
  135. {
  136. get { return IsValidIndex(index) ? m_SpriteOutlineList[index] : null; }
  137. set
  138. {
  139. if (IsValidIndex(index))
  140. m_SpriteOutlineList[index] = value;
  141. }
  142. }
  143. public SpriteOutlineList this[GUID guid]
  144. {
  145. get { return m_SpriteOutlineList.FirstOrDefault(x => x.spriteID == guid); }
  146. set
  147. {
  148. var index = m_SpriteOutlineList.FindIndex(x => x.spriteID == guid);
  149. if (index != -1)
  150. m_SpriteOutlineList[index] = value;
  151. }
  152. }
  153. public void AddListVector2(GUID guid, List<Vector2[]> outline)
  154. {
  155. m_SpriteOutlineList.Add(new SpriteOutlineList(guid, outline));
  156. }
  157. public int Count { get { return m_SpriteOutlineList.Count; } }
  158. bool IsValidIndex(int index)
  159. {
  160. return index >= 0 && index < Count;
  161. }
  162. }
  163. [RequireSpriteDataProvider(typeof(ISpriteOutlineDataProvider), typeof(ITextureDataProvider))]
  164. internal class SpriteOutlineModule : SpriteEditorModuleBase
  165. {
  166. class Styles
  167. {
  168. public GUIContent generatingOutlineDialogTitle = EditorGUIUtility.TrTextContent("Outline");
  169. public GUIContent generatingOutlineDialogContent = EditorGUIUtility.TrTextContent("Generating outline {0}/{1}");
  170. public Color spriteBorderColor = new Color(0.25f, 0.5f, 1f, 0.75f);
  171. }
  172. protected SpriteRect m_Selected;
  173. private const float k_HandleSize = 5f;
  174. private readonly string k_DeleteCommandName = EventCommandNames.Delete;
  175. private readonly string k_SoftDeleteCommandName = EventCommandNames.SoftDelete;
  176. private ShapeEditor[] m_ShapeEditors;
  177. private bool m_RequestRepaint;
  178. private Matrix4x4 m_HandleMatrix;
  179. private Vector2 m_MousePosition;
  180. private ShapeEditorRectSelectionTool m_ShapeSelectionUI;
  181. private bool m_WasRectSelecting = false;
  182. private Rect? m_SelectionRect;
  183. private ITexture2D m_OutlineTexture;
  184. private Styles m_Styles;
  185. protected SpriteOutlineModel m_Outline;
  186. protected ITextureDataProvider m_TextureDataProvider;
  187. protected SpriteOutlineToolOverlayPanel m_SpriteOutlineToolElement;
  188. [SerializeReference]
  189. private static SpriteOutlineList s_CopyOutline = null;
  190. public SpriteOutlineModule(ISpriteEditor sem, IEventSystem es, IUndoSystem us, IAssetDatabase ad, IGUIUtility gu, IShapeEditorFactory sef, ITexture2D outlineTexture)
  191. {
  192. spriteEditorWindow = sem;
  193. undoSystem = us;
  194. eventSystem = es;
  195. assetDatabase = ad;
  196. guiUtility = gu;
  197. shapeEditorFactory = sef;
  198. m_OutlineTexture = outlineTexture;
  199. m_ShapeSelectionUI = new ShapeEditorRectSelectionTool(gu);
  200. m_ShapeSelectionUI.RectSelect += RectSelect;
  201. m_ShapeSelectionUI.ClearSelection += ClearSelection;
  202. }
  203. public override string moduleName
  204. {
  205. get { return "Custom Outline"; }
  206. }
  207. public override bool ApplyRevert(bool apply)
  208. {
  209. if (m_Outline != null)
  210. {
  211. if (apply)
  212. {
  213. var outlineDataProvider = spriteEditorWindow.GetDataProvider<ISpriteOutlineDataProvider>();
  214. for (int i = 0; i < m_Outline.Count; ++i)
  215. {
  216. outlineDataProvider.SetOutlines(m_Outline[i].spriteID, m_Outline[i].ToListVector());
  217. outlineDataProvider.SetTessellationDetail(m_Outline[i].spriteID, m_Outline[i].tessellationDetail);
  218. }
  219. }
  220. Object.DestroyImmediate(m_Outline);
  221. m_Outline = null;
  222. }
  223. return true;
  224. }
  225. private Styles styles
  226. {
  227. get
  228. {
  229. if (m_Styles == null)
  230. m_Styles = new Styles();
  231. return m_Styles;
  232. }
  233. }
  234. protected virtual List<SpriteOutline> selectedShapeOutline
  235. {
  236. get
  237. {
  238. return m_Outline[m_Selected.spriteID].spriteOutlines;
  239. }
  240. set
  241. {
  242. m_Outline[m_Selected.spriteID].spriteOutlines = value;
  243. }
  244. }
  245. protected virtual string alterateLabelText => L10n.Tr("From Physics Shape");
  246. private bool shapeEditorDirty
  247. {
  248. get; set;
  249. }
  250. private bool editingDisabled
  251. {
  252. get { return spriteEditorWindow.editingDisabled; }
  253. }
  254. private ISpriteEditor spriteEditorWindow
  255. {
  256. get; set;
  257. }
  258. private IUndoSystem undoSystem
  259. {
  260. get; set;
  261. }
  262. private IEventSystem eventSystem
  263. {
  264. get; set;
  265. }
  266. private IAssetDatabase assetDatabase
  267. {
  268. get; set;
  269. }
  270. private IGUIUtility guiUtility
  271. {
  272. get; set;
  273. }
  274. private IShapeEditorFactory shapeEditorFactory
  275. {
  276. get; set;
  277. }
  278. private void RectSelect(Rect r, ShapeEditor.SelectionType selectionType)
  279. {
  280. var localRect = EditorGUIExt.FromToRect(ScreenToLocal(r.min), ScreenToLocal(r.max));
  281. m_SelectionRect = localRect;
  282. }
  283. private void ClearSelection()
  284. {
  285. m_RequestRepaint = true;
  286. }
  287. protected virtual void LoadOutline()
  288. {
  289. m_Outline = ScriptableObject.CreateInstance<SpriteOutlineModel>();
  290. m_Outline.hideFlags = HideFlags.HideAndDontSave;
  291. var spriteDataProvider = spriteEditorWindow.GetDataProvider<ISpriteEditorDataProvider>();
  292. var outlineDataProvider = spriteEditorWindow.GetDataProvider<ISpriteOutlineDataProvider>();
  293. foreach (var rect in spriteDataProvider.GetSpriteRects())
  294. {
  295. var outlines = outlineDataProvider.GetOutlines(rect.spriteID);
  296. m_Outline.AddListVector2(rect.spriteID, outlines);
  297. m_Outline[m_Outline.Count - 1].tessellationDetail = outlineDataProvider.GetTessellationDetail(rect.spriteID);
  298. }
  299. }
  300. protected virtual List<Vector2[]> GetAlternateOutlines(GUID spriteID)
  301. {
  302. var alternateOutlineProvider = spriteEditorWindow.GetDataProvider<ISpritePhysicsOutlineDataProvider>();
  303. return alternateOutlineProvider.GetOutlines(spriteID);
  304. }
  305. public override void OnModuleActivate()
  306. {
  307. m_TextureDataProvider = spriteEditorWindow.GetDataProvider<ITextureDataProvider>();
  308. LoadOutline();
  309. GenerateOutlineIfNotExist();
  310. undoSystem.RegisterUndoCallback(UndoRedoPerformed);
  311. shapeEditorDirty = true;
  312. SetupShapeEditor();
  313. spriteEditorWindow.enableMouseMoveEvent = true;
  314. AddMainUI(spriteEditorWindow.GetMainVisualContainer());
  315. }
  316. void GenerateOutlineIfNotExist()
  317. {
  318. var rectCache = spriteEditorWindow.GetDataProvider<ISpriteEditorDataProvider>().GetSpriteRects();
  319. if (rectCache != null)
  320. {
  321. bool needApply = false;
  322. for (int i = 0; i < rectCache.Length; ++i)
  323. {
  324. var rect = rectCache[i];
  325. if (!HasShapeOutline(rect))
  326. {
  327. EditorUtility.DisplayProgressBar(styles.generatingOutlineDialogTitle.text,
  328. string.Format(styles.generatingOutlineDialogContent.text, i + 1 , rectCache.Length),
  329. (float)(i) / rectCache.Length);
  330. SetupShapeEditorOutline(rect);
  331. needApply = true;
  332. }
  333. }
  334. if (needApply)
  335. {
  336. EditorUtility.ClearProgressBar();
  337. spriteEditorWindow.ApplyOrRevertModification(true);
  338. LoadOutline();
  339. }
  340. }
  341. }
  342. public override void OnModuleDeactivate()
  343. {
  344. undoSystem.UnregisterUndoCallback(UndoRedoPerformed);
  345. CleanupShapeEditors();
  346. m_Selected = null;
  347. spriteEditorWindow.enableMouseMoveEvent = false;
  348. if (m_Outline != null)
  349. {
  350. undoSystem.ClearUndo(m_Outline);
  351. Object.DestroyImmediate(m_Outline);
  352. m_Outline = null;
  353. }
  354. RemoveMainUI(spriteEditorWindow.GetMainVisualContainer());
  355. }
  356. public override void DoMainGUI()
  357. {
  358. IEvent evt = eventSystem.current;
  359. m_RequestRepaint = false;
  360. m_HandleMatrix = Handles.matrix;
  361. m_MousePosition = Handles.inverseMatrix.MultiplyPoint(eventSystem.current.mousePosition);
  362. if (m_Selected == null || !m_Selected.rect.Contains(m_MousePosition) && !IsMouseOverOutlinePoints() && evt.shift == false)
  363. spriteEditorWindow.HandleSpriteSelection();
  364. HandleCreateNewOutline();
  365. m_WasRectSelecting = m_ShapeSelectionUI.isSelecting;
  366. UpdateShapeEditors();
  367. m_ShapeSelectionUI.OnGUI();
  368. DrawGizmos();
  369. if (m_RequestRepaint || evt.type == EventType.MouseMove)
  370. spriteEditorWindow.RequestRepaint();
  371. }
  372. protected virtual int alphaTolerance
  373. {
  374. get => SpriteOutlineModulePreference.alphaTolerance;
  375. set => SpriteOutlineModulePreference.alphaTolerance = value;
  376. }
  377. internal SpriteOutlineList selectedOutline => m_Outline[m_Selected.spriteID];
  378. public override void DoToolbarGUI(Rect drawArea)
  379. {}
  380. public override void DoPostGUI()
  381. {}
  382. public override bool CanBeActivated()
  383. {
  384. return SpriteFrameModule.GetSpriteImportMode(spriteEditorWindow.GetDataProvider<ISpriteEditorDataProvider>()) != SpriteImportMode.None;
  385. }
  386. private void RecordUndo()
  387. {
  388. undoSystem.RegisterCompleteObjectUndo(m_Outline, "Outline changed");
  389. }
  390. public void CreateNewOutline(Rect rectOutline)
  391. {
  392. Rect rect = m_Selected.rect;
  393. if (rect.Contains(rectOutline.min) && rect.Contains(rectOutline.max))
  394. {
  395. RecordUndo();
  396. SpriteOutline so = new SpriteOutline();
  397. Vector2 outlineOffset = new Vector2(0.5f * rect.width + rect.x, 0.5f * rect.height + rect.y);
  398. Rect selectionRect = new Rect(rectOutline);
  399. selectionRect.min = SnapPoint(rectOutline.min);
  400. selectionRect.max = SnapPoint(rectOutline.max);
  401. so.Add(CapPointToRect(new Vector2(selectionRect.xMin, selectionRect.yMin), rect) - outlineOffset);
  402. so.Add(CapPointToRect(new Vector2(selectionRect.xMax, selectionRect.yMin), rect) - outlineOffset);
  403. so.Add(CapPointToRect(new Vector2(selectionRect.xMax, selectionRect.yMax), rect) - outlineOffset);
  404. so.Add(CapPointToRect(new Vector2(selectionRect.xMin, selectionRect.yMax), rect) - outlineOffset);
  405. selectedShapeOutline.Add(so);
  406. spriteEditorWindow.SetDataModified();
  407. shapeEditorDirty = true;
  408. }
  409. }
  410. private void HandleCreateNewOutline()
  411. {
  412. if (m_WasRectSelecting && m_ShapeSelectionUI.isSelecting == false && m_SelectionRect != null && m_Selected != null)
  413. {
  414. bool createNewOutline = true;
  415. foreach (var se in m_ShapeEditors)
  416. {
  417. if (se.selectedPoints.Count != 0)
  418. {
  419. createNewOutline = false;
  420. break;
  421. }
  422. }
  423. if (createNewOutline)
  424. CreateNewOutline(m_SelectionRect.Value);
  425. }
  426. m_SelectionRect = null;
  427. }
  428. public void UpdateShapeEditors()
  429. {
  430. SetupShapeEditor();
  431. if (m_Selected != null)
  432. {
  433. IEvent currentEvent = eventSystem.current;
  434. var wantsDelete = currentEvent.type == EventType.ExecuteCommand && (currentEvent.commandName == k_SoftDeleteCommandName || currentEvent.commandName == k_DeleteCommandName);
  435. for (int i = 0; i < m_ShapeEditors.Length; ++i)
  436. {
  437. if (m_ShapeEditors[i].GetPointsCount() == 0)
  438. continue;
  439. m_ShapeEditors[i].inEditMode = true;
  440. m_ShapeEditors[i].OnGUI();
  441. if (shapeEditorDirty)
  442. break;
  443. }
  444. if (wantsDelete)
  445. {
  446. // remove outline which have lesser than 3 points
  447. for (int i = selectedShapeOutline.Count - 1; i >= 0; --i)
  448. {
  449. if (selectedShapeOutline[i].Count < 3)
  450. {
  451. selectedShapeOutline.RemoveAt(i);
  452. shapeEditorDirty = true;
  453. }
  454. }
  455. }
  456. }
  457. }
  458. private bool IsMouseOverOutlinePoints()
  459. {
  460. if (m_Selected == null)
  461. return false;
  462. Vector2 outlineOffset = new Vector2(0.5f * m_Selected.rect.width + m_Selected.rect.x, 0.5f * m_Selected.rect.height + m_Selected.rect.y);
  463. float handleSize = GetHandleSize();
  464. Rect r = new Rect(0, 0, handleSize * 2, handleSize * 2);
  465. for (int i = 0; i < selectedShapeOutline.Count; ++i)
  466. {
  467. var outline = selectedShapeOutline[i];
  468. for (int j = 0; j < outline.Count; ++j)
  469. {
  470. r.center = outline[j] + outlineOffset;
  471. if (r.Contains(m_MousePosition))
  472. return true;
  473. }
  474. }
  475. return false;
  476. }
  477. private float GetHandleSize()
  478. {
  479. return k_HandleSize / m_HandleMatrix.m00;
  480. }
  481. private void CleanupShapeEditors()
  482. {
  483. if (m_ShapeEditors != null)
  484. {
  485. for (int i = 0; i < m_ShapeEditors.Length; ++i)
  486. {
  487. for (int j = 0; j < m_ShapeEditors.Length; ++j)
  488. {
  489. if (i != j)
  490. m_ShapeEditors[j].UnregisterFromShapeEditor(m_ShapeEditors[i]);
  491. }
  492. m_ShapeEditors[i].OnDisable();
  493. }
  494. }
  495. m_ShapeEditors = null;
  496. }
  497. public void SetupShapeEditor()
  498. {
  499. if (shapeEditorDirty || m_Selected != spriteEditorWindow.selectedSpriteRect)
  500. {
  501. m_Selected = spriteEditorWindow.selectedSpriteRect;
  502. CleanupShapeEditors();
  503. if (m_Selected != null)
  504. {
  505. if (!HasShapeOutline(m_Selected))
  506. SetupShapeEditorOutline(m_Selected);
  507. m_ShapeEditors = new ShapeEditor[selectedShapeOutline.Count];
  508. for (int i = 0; i < selectedShapeOutline.Count; ++i)
  509. {
  510. int outlineIndex = i;
  511. m_ShapeEditors[i] = shapeEditorFactory.CreateShapeEditor();
  512. m_ShapeEditors[i].SetRectSelectionTool(m_ShapeSelectionUI);
  513. m_ShapeEditors[i].LocalToWorldMatrix = () => m_HandleMatrix;
  514. m_ShapeEditors[i].LocalToScreen = (point) => Handles.matrix.MultiplyPoint(point);
  515. m_ShapeEditors[i].ScreenToLocal = ScreenToLocal;
  516. m_ShapeEditors[i].RecordUndo = RecordUndo;
  517. m_ShapeEditors[i].GetHandleSize = GetHandleSize;
  518. m_ShapeEditors[i].lineTexture = m_OutlineTexture;
  519. m_ShapeEditors[i].Snap = SnapPoint;
  520. m_ShapeEditors[i].GetPointPosition = (index) => GetPointPosition(outlineIndex, index);
  521. m_ShapeEditors[i].SetPointPosition = (index, position) => SetPointPosition(outlineIndex, index, position);
  522. m_ShapeEditors[i].InsertPointAt = (index, position) => InsertPointAt(outlineIndex, index, position);
  523. m_ShapeEditors[i].RemovePointAt = (index) => RemovePointAt(outlineIndex, index);
  524. m_ShapeEditors[i].GetPointsCount = () => GetPointsCount(outlineIndex);
  525. }
  526. for (int i = 0; i < selectedShapeOutline.Count; ++i)
  527. {
  528. for (int j = 0; j < selectedShapeOutline.Count; ++j)
  529. {
  530. if (i != j)
  531. m_ShapeEditors[j].RegisterToShapeEditor(m_ShapeEditors[i]);
  532. }
  533. }
  534. }
  535. else
  536. {
  537. m_ShapeEditors = new ShapeEditor[0];
  538. }
  539. }
  540. shapeEditorDirty = false;
  541. }
  542. protected virtual bool HasShapeOutline(SpriteRect spriteRect)
  543. {
  544. var outline = m_Outline[spriteRect.spriteID] != null ? m_Outline[spriteRect.spriteID].spriteOutlines : null;
  545. return outline != null;
  546. }
  547. private void AddMainUI(VisualElement mainView)
  548. {
  549. m_SpriteOutlineToolElement = SpriteOutlineToolOverlayPanel.GenerateFromUXML(alterateLabelText);
  550. m_SpriteOutlineToolElement.AddStyleSheetPath("Packages/com.unity.2d.sprite/Editor/UI/SpriteEditor/SpriteEditor.uss");
  551. m_SpriteOutlineToolElement.AddToClassList("moduleWindow");
  552. m_SpriteOutlineToolElement.AddToClassList("bottomRightFloating");
  553. mainView.Add(m_SpriteOutlineToolElement);
  554. m_SpriteOutlineToolElement.onGenerateOutline += OnGenerateOutline;
  555. m_SpriteOutlineToolElement.onCopy += Copy;
  556. m_SpriteOutlineToolElement.onPaste += Paste;
  557. m_SpriteOutlineToolElement.onPasteAll += PasteAll;
  558. m_SpriteOutlineToolElement.onPasteAlternate += PasteFromAlternate;
  559. m_SpriteOutlineToolElement.onPasteAlternateAll += PasteAllFromAlternate;
  560. m_SpriteOutlineToolElement.onAlphaToleranceChanged += OnAlphaToleranceChanged;
  561. m_SpriteOutlineToolElement.onOutlineDetailChanged += OnOutlineDetailChanged;
  562. mainView.RegisterCallback<SpriteSelectionChangeEvent>(SpriteSelectionChange);
  563. SetupUIPanel();
  564. }
  565. private void RemoveMainUI(VisualElement mainView)
  566. {
  567. if (m_SpriteOutlineToolElement != null)
  568. {
  569. if (mainView.Contains(m_SpriteOutlineToolElement))
  570. mainView.Remove(m_SpriteOutlineToolElement);
  571. mainView.UnregisterCallback<SpriteSelectionChangeEvent>(SpriteSelectionChange);
  572. m_SpriteOutlineToolElement.onGenerateOutline -= OnGenerateOutline;
  573. m_SpriteOutlineToolElement.onCopy -= Copy;
  574. m_SpriteOutlineToolElement.onPaste -= Paste;
  575. m_SpriteOutlineToolElement.onPasteAll -= PasteAll;
  576. m_SpriteOutlineToolElement.onPasteAlternate -= PasteFromAlternate;
  577. m_SpriteOutlineToolElement.onPasteAlternateAll -= PasteAllFromAlternate;
  578. m_SpriteOutlineToolElement.onAlphaToleranceChanged -= OnAlphaToleranceChanged;
  579. m_SpriteOutlineToolElement.onOutlineDetailChanged -= OnOutlineDetailChanged;
  580. }
  581. }
  582. void SetupUIPanel()
  583. {
  584. m_Selected = spriteEditorWindow.selectedSpriteRect;
  585. m_SpriteOutlineToolElement.SetPanelMode(m_Selected != null);
  586. if (m_Selected != null)
  587. {
  588. m_SpriteOutlineToolElement.outlineDetail = m_Outline[m_Selected.spriteID].tessellationDetail;
  589. }
  590. else
  591. {
  592. m_SpriteOutlineToolElement.outlineDetail = 0;
  593. }
  594. m_SpriteOutlineToolElement.alphaTolerance = alphaTolerance;
  595. }
  596. void OnAlphaToleranceChanged(int value)
  597. {
  598. alphaTolerance = value;
  599. }
  600. internal void OnOutlineDetailChanged(float value)
  601. {
  602. if(m_Selected != null)
  603. m_Outline[m_Selected.spriteID].tessellationDetail = value;
  604. }
  605. void OnGenerateOutline(bool forceGenerate)
  606. {
  607. RecordUndo();
  608. if (m_Selected != null)
  609. {
  610. selectedShapeOutline.Clear();
  611. SetupShapeEditorOutline(m_Selected);
  612. }
  613. else
  614. {
  615. var rectCache = spriteEditorWindow.GetDataProvider<ISpriteEditorDataProvider>().GetSpriteRects();
  616. if (rectCache != null)
  617. {
  618. bool showedProgressBar = false;
  619. for (int i = 0; i < rectCache.Length; ++i)
  620. {
  621. var rect = rectCache[i];
  622. var outline = m_Outline[rect.spriteID] != null ? m_Outline[rect.spriteID].spriteOutlines : null;
  623. if (forceGenerate || outline == null || outline.Count == 0)
  624. {
  625. EditorUtility.DisplayProgressBar(styles.generatingOutlineDialogTitle.text,
  626. string.Format(styles.generatingOutlineDialogContent.text, i + 1, rectCache.Length),
  627. (float)(i) / rectCache.Length);
  628. showedProgressBar = true;
  629. m_Outline[rect.spriteID].tessellationDetail = m_SpriteOutlineToolElement.outlineDetail;
  630. SetupShapeEditorOutline(rect);
  631. }
  632. }
  633. if(showedProgressBar)
  634. EditorUtility.ClearProgressBar();
  635. }
  636. }
  637. spriteEditorWindow.SetDataModified();
  638. shapeEditorDirty = true;
  639. }
  640. void SpriteSelectionChange(SpriteSelectionChangeEvent evt)
  641. {
  642. var spriteRect = spriteEditorWindow.selectedSpriteRect;
  643. m_SpriteOutlineToolElement.SetPanelMode(spriteRect != null);
  644. if (spriteRect != null)
  645. {
  646. var data = m_Outline[spriteRect.spriteID];
  647. if(data != null)
  648. m_SpriteOutlineToolElement.outlineDetail = m_Outline[spriteRect.spriteID].tessellationDetail;
  649. }
  650. }
  651. protected virtual void SetupShapeEditorOutline(SpriteRect spriteRect)
  652. {
  653. var outline = m_Outline[spriteRect.spriteID];
  654. var outlines = GenerateSpriteRectOutline(spriteRect.rect,
  655. Math.Abs(outline.tessellationDetail - (-1f)) < Mathf.Epsilon ? 0 : outline.tessellationDetail,
  656. (byte)(alphaTolerance), m_TextureDataProvider, m_SpriteOutlineToolElement.optimizeOutline);
  657. if (outlines.Count == 0)
  658. {
  659. Vector2 halfSize = spriteRect.rect.size * 0.5f;
  660. outlines = new List<SpriteOutline>()
  661. {
  662. new SpriteOutline()
  663. {
  664. m_Path = new List<Vector2>()
  665. {
  666. new Vector2(-halfSize.x, -halfSize.y),
  667. new Vector2(-halfSize.x, halfSize.y),
  668. new Vector2(halfSize.x, halfSize.y),
  669. new Vector2(halfSize.x, -halfSize.y),
  670. }
  671. }
  672. };
  673. }
  674. m_Outline[spriteRect.spriteID].spriteOutlines = outlines;
  675. }
  676. public Vector3 SnapPoint(Vector3 position)
  677. {
  678. if (m_SpriteOutlineToolElement.snapOn)
  679. {
  680. position.x = Mathf.RoundToInt(position.x);
  681. position.y = Mathf.RoundToInt(position.y);
  682. }
  683. return position;
  684. }
  685. public Vector3 GetPointPosition(int outlineIndex, int pointIndex)
  686. {
  687. if (outlineIndex >= 0 && outlineIndex < selectedShapeOutline.Count)
  688. {
  689. var outline = selectedShapeOutline[outlineIndex];
  690. if (pointIndex >= 0 && pointIndex < outline.Count)
  691. {
  692. return ConvertSpriteRectSpaceToTextureSpace(outline[pointIndex]);
  693. }
  694. }
  695. return new Vector3(float.NaN, float.NaN, float.NaN);
  696. }
  697. public void SetPointPosition(int outlineIndex, int pointIndex, Vector3 position)
  698. {
  699. selectedShapeOutline[outlineIndex][pointIndex] = ConvertTextureSpaceToSpriteRectSpace(CapPointToRect(position, m_Selected.rect));
  700. spriteEditorWindow.SetDataModified();
  701. }
  702. public void InsertPointAt(int outlineIndex, int pointIndex, Vector3 position)
  703. {
  704. selectedShapeOutline[outlineIndex].Insert(pointIndex, ConvertTextureSpaceToSpriteRectSpace(CapPointToRect(position, m_Selected.rect)));
  705. spriteEditorWindow.SetDataModified();
  706. }
  707. public void RemovePointAt(int outlineIndex, int i)
  708. {
  709. selectedShapeOutline[outlineIndex].RemoveAt(i);
  710. spriteEditorWindow.SetDataModified();
  711. }
  712. public int GetPointsCount(int outlineIndex)
  713. {
  714. return selectedShapeOutline[outlineIndex].Count;
  715. }
  716. private Vector2 ConvertSpriteRectSpaceToTextureSpace(Vector2 value)
  717. {
  718. Vector2 outlineOffset = new Vector2(0.5f * m_Selected.rect.width + m_Selected.rect.x, 0.5f * m_Selected.rect.height + m_Selected.rect.y);
  719. value += outlineOffset;
  720. return value;
  721. }
  722. private Vector2 ConvertTextureSpaceToSpriteRectSpace(Vector2 value)
  723. {
  724. Vector2 outlineOffset = new Vector2(0.5f * m_Selected.rect.width + m_Selected.rect.x, 0.5f * m_Selected.rect.height + m_Selected.rect.y);
  725. value -= outlineOffset;
  726. return value;
  727. }
  728. private Vector3 ScreenToLocal(Vector2 point)
  729. {
  730. return Handles.inverseMatrix.MultiplyPoint(point);
  731. }
  732. private void UndoRedoPerformed()
  733. {
  734. shapeEditorDirty = true;
  735. }
  736. private void DrawGizmos()
  737. {
  738. if (eventSystem.current.type == EventType.Repaint)
  739. {
  740. var selected = spriteEditorWindow.selectedSpriteRect;
  741. if (selected != null)
  742. {
  743. SpriteEditorUtility.BeginLines(styles.spriteBorderColor);
  744. SpriteEditorUtility.DrawBox(selected.rect);
  745. SpriteEditorUtility.EndLines();
  746. }
  747. }
  748. }
  749. protected static List<SpriteOutline> GenerateSpriteRectOutline(Rect rect, float detail, byte alphaTolerance, ITextureDataProvider textureProvider, bool useClipper)
  750. {
  751. List<SpriteOutline> outline = new List<SpriteOutline>();
  752. var texture = textureProvider.GetReadableTexture2D();
  753. if (texture != null)
  754. {
  755. Vector2[][] paths;
  756. // we might have a texture that is capped because of max size or NPOT.
  757. // in that case, we need to convert values from capped space to actual texture space and back.
  758. int actualWidth = 0, actualHeight = 0;
  759. int cappedWidth, cappedHeight;
  760. textureProvider.GetTextureActualWidthAndHeight(out actualWidth, out actualHeight);
  761. cappedWidth = texture.width;
  762. cappedHeight = texture.height;
  763. Vector2 scale = new Vector2(cappedWidth / (float)actualWidth, cappedHeight / (float)actualHeight);
  764. Rect spriteRect = rect;
  765. spriteRect.xMin *= scale.x;
  766. spriteRect.xMax *= scale.x;
  767. spriteRect.yMin *= scale.y;
  768. spriteRect.yMax *= scale.y;
  769. UnityEditor.Sprites.SpriteUtility.GenerateOutline(texture, spriteRect, detail, alphaTolerance, true, out paths);
  770. if (useClipper)
  771. {
  772. Clipper2D.Solution clipperSolution = new Clipper2D.Solution();
  773. var pathSize = new NativeArray<int>(paths.Length, Allocator.Temp);
  774. var pathArguments = new NativeArray<Clipper2D.PathArguments>(paths.Length, Allocator.Temp);
  775. var totalPoints = 0;
  776. for (int j = 0; j < paths.Length; ++j)
  777. {
  778. pathSize[j] = paths[j].Length;
  779. totalPoints += paths[j].Length;
  780. pathArguments[j] = new Clipper2D.PathArguments(Clipper2D.PolyType.ptSubject, true);
  781. }
  782. var pathPoints= new NativeArray<Vector2>(totalPoints, Allocator.Temp);
  783. int pathPointsCounter = 0;
  784. for (int j = 0; j < paths.Length; ++j)
  785. {
  786. NativeArray<Vector2>.Copy(paths[j], 0, pathPoints, pathPointsCounter, paths[j].Length);
  787. pathPointsCounter += paths[j].Length;
  788. }
  789. var executeArgument = new Clipper2D.ExecuteArguments()
  790. {
  791. initOption = Clipper2D.InitOptions.ioStrictlySimple,
  792. clipType = Clipper2D.ClipType.ctUnion,
  793. subjFillType = Clipper2D.PolyFillType.pftPositive,
  794. clipFillType = Clipper2D.PolyFillType.pftPositive
  795. };
  796. Clipper2D.Execute(ref clipperSolution, pathPoints, pathSize, pathArguments, executeArgument, Allocator.Temp);
  797. paths = new Vector2[clipperSolution.pathSizes.Length][];
  798. pathPointsCounter = 0;
  799. for (int i = 0; i < paths.Length; ++i)
  800. {
  801. paths[i] = new Vector2[clipperSolution.pathSizes[i]];
  802. NativeArray<Vector2>.Copy(clipperSolution.points, pathPointsCounter, paths[i], 0, paths[i].Length);
  803. pathPointsCounter += paths[i].Length;
  804. }
  805. pathSize.Dispose();
  806. pathArguments.Dispose();
  807. pathPoints.Dispose();
  808. clipperSolution.Dispose();
  809. }
  810. Rect capRect = new Rect();
  811. capRect.size = rect.size;
  812. capRect.center = Vector2.zero;
  813. for (int j = 0; j < paths.Length; ++j)
  814. {
  815. SpriteOutline points = new SpriteOutline();
  816. foreach (Vector2 v in paths[j])
  817. points.Add(CapPointToRect(new Vector2(v.x / scale.x, v.y / scale.y), capRect));
  818. outline.Add(points);
  819. }
  820. }
  821. return outline;
  822. }
  823. public void Copy()
  824. {
  825. if (m_Selected == null || !HasShapeOutline(m_Selected))
  826. return;
  827. s_CopyOutline = new SpriteOutlineList(m_Selected.spriteID, m_Outline[m_Selected.spriteID].ToListVectorCapped(m_Selected.rect));
  828. }
  829. private void ReplaceOutline(GUID spriteID, List<Vector2[]> newOutline)
  830. {
  831. var oldOutline = m_Outline[spriteID];
  832. m_Outline[spriteID] = new SpriteOutlineList(spriteID, newOutline);
  833. if (oldOutline != null)
  834. m_Outline[spriteID].tessellationDetail = oldOutline.tessellationDetail;
  835. }
  836. public void Paste()
  837. {
  838. if (m_Selected == null || s_CopyOutline == null)
  839. return;
  840. RecordUndo();
  841. ReplaceOutline(m_Selected.spriteID, s_CopyOutline.ToListVectorCapped(m_Selected.rect));
  842. spriteEditorWindow.SetDataModified();
  843. shapeEditorDirty = true;
  844. }
  845. public void PasteAll()
  846. {
  847. if (s_CopyOutline == null)
  848. return;
  849. RecordUndo();
  850. var rectCache = spriteEditorWindow.GetDataProvider<ISpriteEditorDataProvider>().GetSpriteRects();
  851. if (rectCache != null)
  852. {
  853. foreach (var spriteRect in rectCache)
  854. {
  855. var outlines = s_CopyOutline.ToListVectorCapped(spriteRect.rect);
  856. ReplaceOutline(m_Selected.spriteID, outlines);
  857. }
  858. }
  859. spriteEditorWindow.SetDataModified();
  860. shapeEditorDirty = true;
  861. }
  862. public void PasteFromAlternate()
  863. {
  864. if (m_Selected == null)
  865. return;
  866. var alternateOutline = GetAlternateOutlines(m_Selected.spriteID);
  867. RecordUndo();
  868. ReplaceOutline(m_Selected.spriteID, alternateOutline);
  869. spriteEditorWindow.SetDataModified();
  870. shapeEditorDirty = true;
  871. }
  872. public void PasteAllFromAlternate()
  873. {
  874. RecordUndo();
  875. var rectCache = spriteEditorWindow.GetDataProvider<ISpriteEditorDataProvider>().GetSpriteRects();
  876. if (rectCache != null)
  877. {
  878. foreach (var spriteRect in rectCache)
  879. {
  880. var alternateOutline = GetAlternateOutlines(spriteRect.spriteID);
  881. ReplaceOutline(spriteRect.spriteID, alternateOutline);
  882. }
  883. }
  884. spriteEditorWindow.SetDataModified();
  885. shapeEditorDirty = true;
  886. }
  887. internal static Vector2 CapPointToRect(Vector2 so, Rect r)
  888. {
  889. so.x = Mathf.Min(r.xMax, so.x);
  890. so.x = Mathf.Max(r.xMin, so.x);
  891. so.y = Mathf.Min(r.yMax, so.y);
  892. so.y = Mathf.Max(r.yMin, so.y);
  893. return so;
  894. }
  895. }
  896. internal class SpriteOutlineModulePreference
  897. {
  898. public const string kSettingsUniqueKey = "UnityEditor.U2D.Sprites/SpriteOutlineModule";
  899. public const string kUseClipper = kSettingsUniqueKey + "kUseClipper";
  900. public const string kAlphaTolerance = kSettingsUniqueKey + "kAlphaTolerance";
  901. public const string kPhysicsAlphaTolerance = kSettingsUniqueKey + "kPhysicsAlphaTolerance";
  902. public static bool useClipper
  903. {
  904. get { return EditorPrefs.GetBool(kUseClipper, true); }
  905. set { EditorPrefs.SetBool(kUseClipper, value); }
  906. }
  907. public static int alphaTolerance
  908. {
  909. get { return EditorPrefs.GetInt(kAlphaTolerance, 0); }
  910. set { EditorPrefs.SetInt(kAlphaTolerance, value); }
  911. }
  912. public static int physicsAlphaTolerance
  913. {
  914. get { return EditorPrefs.GetInt(kPhysicsAlphaTolerance, 200); }
  915. set { EditorPrefs.SetInt(kPhysicsAlphaTolerance, value); }
  916. }
  917. }
  918. }