A game about forced loneliness, made by TACStudios
at master 1050 lines 40 kB view raw
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}