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