A game about forced loneliness, made by TACStudios
1using System; 2using System.IO; 3using UnityEngine; 4using UnityEditorInternal; 5using System.Collections.Generic; 6using System.Text; 7using UnityTexture2D = UnityEngine.Texture2D; 8using UnityEditor.ShortcutManagement; 9using Object = UnityEngine.Object; 10 11namespace UnityEditor.U2D.Sprites 12{ 13 [RequireSpriteDataProvider(typeof(ITextureDataProvider))] 14 internal partial class SpriteFrameModule : SpriteFrameModuleBase 15 { 16 public enum AutoSlicingMethod 17 { 18 DeleteAll = 0, 19 Smart = 1, 20 Safe = 2 21 } 22 23 private bool[] m_AlphaPixelCache; 24 SpriteFrameModuleContext m_SpriteFrameModuleContext; 25 public event Action onModuleDeactivated = () => { }; 26 SpriteEditorModeBase m_CurrentMode = null; 27 28 private const float kOverlapTolerance = 0.00001f; 29 private StringBuilder m_SpriteNameStringBuilder; 30 31 private List<Rect> m_PotentialRects; 32 Texture2D m_TextureToSlice; 33 public List<Rect> potentialRects 34 { 35 set => m_PotentialRects = value; 36 } 37 38 public SpriteFrameModule(ISpriteEditor sw, IEventSystem es, IUndoSystem us, IAssetDatabase ad) : 39 base("Sprite Editor", sw, es, us, ad) 40 {} 41 42 public override void SetModuleModes(IEnumerable<Type> modes) 43 { 44 base.SetModuleModes(modes); 45 foreach (var mode in this.modes) 46 { 47 mode.RegisterOnModeRequestActivate(OnModuleExtensionActivate); 48 } 49 } 50 51 void OnModuleExtensionActivate(SpriteEditorModeBase activatingMode) 52 { 53 m_CurrentMode?.DeactivateMode(); 54 m_CurrentMode = activatingMode; 55 var activated = m_CurrentMode?.ActivateMode(); 56 // Mode did not activate 57 if(activated.HasValue && !activated.Value) 58 { 59 m_CurrentMode = null; 60 } 61 62 bool modeNull = m_CurrentMode == null; 63 if (modeNull) 64 { 65 spriteEditor.spriteRects = m_RectsCache.GetSpriteRects(); 66 } 67 EnableInspector(modeNull); 68 } 69 70 public override bool ApplyRevert(bool apply) 71 { 72 var returnValue = base.ApplyRevert(apply); 73 var dataProviderApplied = new HashSet<Type>(); 74 dataProviderApplied.Add(typeof(ISpriteEditorDataProvider)); 75 foreach(var mode in modes) 76 { 77 returnValue |= mode.ApplyModeData(apply, dataProviderApplied); 78 } 79 if(apply) 80 m_SourceOverrideCallback?.Invoke(spriteAssetPath); 81 return returnValue; 82 } 83 84 class SpriteFrameModuleContext : IShortcutContext 85 { 86 SpriteFrameModule m_SpriteFrameModule; 87 88 public SpriteFrameModuleContext(SpriteFrameModule spriteFrame) 89 { 90 m_SpriteFrameModule = spriteFrame; 91 } 92 93 public bool active 94 { 95 get { return true; } 96 } 97 public SpriteFrameModule spriteFrameModule 98 { 99 get { return m_SpriteFrameModule; } 100 } 101 } 102 103 [FormerlyPrefKeyAs("Sprite Editor/Trim", "#t")] 104 [Shortcut("Sprite Editor/Trim", typeof(SpriteFrameModuleContext), KeyCode.T, ShortcutModifiers.Shift)] 105 static void ShortcutTrim(ShortcutArguments args) 106 { 107 if (!string.IsNullOrEmpty(GUI.GetNameOfFocusedControl())) 108 return; 109 var spriteFrameContext = (SpriteFrameModuleContext)args.context; 110 spriteFrameContext.spriteFrameModule.TrimAlpha(); 111 spriteFrameContext.spriteFrameModule.spriteEditor.RequestRepaint(); 112 } 113 114 public override void OnModuleActivate() 115 { 116 base.OnModuleActivate(); 117 spriteEditor.enableMouseMoveEvent = true; 118 m_SpriteFrameModuleContext = new SpriteFrameModuleContext(this); 119 ShortcutIntegration.instance.contextManager.RegisterToolContext(m_SpriteFrameModuleContext); 120 m_SpriteNameStringBuilder = new StringBuilder(GetSpriteNamePrefix() + "_"); 121 m_PotentialRects = null; 122 RegisterDataChangeCallback(OnTextureDataProviderDataChanged); 123 SignalModuleActivate(); 124 } 125 126 public override void OnModuleDeactivate() 127 { 128 base.OnModuleDeactivate(); 129 130 ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_SpriteFrameModuleContext); 131 m_PotentialRects = null; 132 m_AlphaPixelCache = null; 133 m_CurrentMode?.DeactivateMode(); 134 m_CurrentMode = null; 135 UnregisterDataChangeCallback(OnTextureDataProviderDataChanged); 136 CleanUpDataDataProviderOverride(); 137 foreach (var mode in modes) 138 { 139 mode.UnregisterOnModeRequestActivate(OnModuleExtensionActivate); 140 } 141 if(m_TextureToSlice != null) 142 Object.DestroyImmediate(m_TextureToSlice); 143 modes.Clear(); 144 onModuleDeactivated(); 145 } 146 147 void OnTextureDataProviderDataChanged(ISpriteEditorDataProvider obj) 148 { 149 if(m_TextureToSlice != null) 150 Object.DestroyImmediate(m_TextureToSlice); 151 m_TextureToSlice = null; 152 } 153 154 public static SpriteImportMode GetSpriteImportMode(ISpriteEditorDataProvider dataProvider) 155 { 156 return dataProvider == null ? SpriteImportMode.None : dataProvider.spriteImportMode; 157 } 158 159 public override bool CanBeActivated() 160 { 161 return GetSpriteImportMode(spriteEditor.GetDataProvider<ISpriteEditorDataProvider>()) != SpriteImportMode.Polygon; 162 } 163 164 private string GenerateSpriteNameWithIndex(int startIndex) 165 { 166 int originalLength = m_SpriteNameStringBuilder.Length; 167 m_SpriteNameStringBuilder.Append(startIndex); 168 var name = m_SpriteNameStringBuilder.ToString(); 169 m_SpriteNameStringBuilder.Length = originalLength; 170 return name; 171 } 172 173 private int AddSprite(Rect frame, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, int originalCount, ref int nameIndex) 174 { 175 int outSprite = -1; 176 switch (slicingMethod) 177 { 178 case AutoSlicingMethod.DeleteAll: 179 { 180 while (outSprite == -1) 181 { 182 outSprite = AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(nameIndex++), Vector4.zero); 183 } 184 } 185 break; 186 case AutoSlicingMethod.Smart: 187 { 188 outSprite = GetExistingOverlappingSprite(frame, originalCount, true); 189 if (outSprite != -1) 190 { 191 var existingRect = m_RectsCache.spriteRects[outSprite]; 192 existingRect.rect = frame; 193 existingRect.alignment = (SpriteAlignment)alignment; 194 existingRect.pivot = pivot; 195 } 196 else 197 { 198 while (outSprite == -1) 199 { 200 outSprite = AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(nameIndex++), Vector4.zero); 201 } 202 } 203 } 204 break; 205 case AutoSlicingMethod.Safe: 206 { 207 outSprite = GetExistingOverlappingSprite(frame, originalCount); 208 while (outSprite == -1) 209 { 210 outSprite = AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(nameIndex++), Vector4.zero); 211 } 212 } 213 break; 214 } 215 return outSprite; 216 } 217 218 private int GetExistingOverlappingSprite(Rect rect, int originalCount, bool bestFit = false) 219 { 220 var count = Math.Min(originalCount, m_RectsCache.spriteRects.Count); 221 int bestRect = -1; 222 float rectArea = rect.width * rect.height; 223 if (rectArea < kOverlapTolerance) 224 return bestRect; 225 226 float bestRatio = float.MaxValue; 227 float bestArea = float.MaxValue; 228 for (int i = 0; i < count; i++) 229 { 230 Rect existingRect = m_RectsCache.spriteRects[i].rect; 231 if (existingRect.Overlaps(rect)) 232 { 233 if (bestFit) 234 { 235 float dx = Math.Min(rect.xMax, existingRect.xMax) - Math.Max(rect.xMin, existingRect.xMin); 236 float dy = Math.Min(rect.yMax, existingRect.yMax) - Math.Max(rect.yMin, existingRect.yMin); 237 float overlapArea = dx * dy; 238 float overlapRatio = Math.Abs((overlapArea / rectArea) - 1.0f); 239 float existingArea = existingRect.width * existingRect.height; 240 if (overlapRatio < bestRatio || (overlapRatio < kOverlapTolerance && existingArea < bestArea)) 241 { 242 bestRatio = overlapRatio; 243 if (overlapRatio < kOverlapTolerance) 244 bestArea = existingArea; 245 bestRect = i; 246 } 247 } 248 else 249 { 250 bestRect = i; 251 break; 252 } 253 } 254 } 255 return bestRect; 256 } 257 258 private bool PixelHasAlpha(int x, int y, UnityTexture2D texture) 259 { 260 if (m_AlphaPixelCache == null) 261 { 262 m_AlphaPixelCache = new bool[texture.width * texture.height]; 263 Color32[] pixels = texture.GetPixels32(); 264 265 for (int i = 0; i < pixels.Length; i++) 266 m_AlphaPixelCache[i] = pixels[i].a != 0; 267 } 268 int index = y * (int)texture.width + x; 269 return m_AlphaPixelCache[index]; 270 } 271 272 private int AddSprite(Rect rect, int alignment, Vector2 pivot, string name, Vector4 border) 273 { 274 if (m_RectsCache.IsNameUsed(name)) 275 return -1; 276 277 SpriteRect spriteRect = new SpriteRect(); 278 spriteRect.rect = rect; 279 spriteRect.alignment = (SpriteAlignment)alignment; 280 spriteRect.pivot = pivot; 281 spriteRect.name = name; 282 spriteRect.originalName = spriteRect.name; 283 spriteRect.border = border; 284 285 spriteRect.spriteID = GUID.Generate(); 286 287 if (!m_RectsCache.Add(spriteRect)) 288 return -1; 289 spriteEditor.SetDataModified(); 290 291 return m_RectsCache.spriteRects.Count - 1; 292 } 293 294 private string GetSpriteNamePrefix() 295 { 296 return Path.GetFileNameWithoutExtension(spriteAssetPath); 297 } 298 299 public void DoAutomaticSlicing(int minimumSpriteSize, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod) 300 { 301 m_RectsCache.RegisterUndo(undoSystem, "Automatic Slicing"); 302 303 if (slicingMethod == AutoSlicingMethod.DeleteAll) 304 m_RectsCache.Clear(); 305 306 var textureToUse = GetTextureToSlice(); 307 List<Rect> frames = new List<Rect>(InternalSpriteUtility.GenerateAutomaticSpriteRectangles((UnityTexture2D)textureToUse, minimumSpriteSize, 0)); 308 if (frames.Count == 0) 309 frames.Add(new Rect(0, 0, textureToUse.width, textureToUse.height)); 310 311 int index = 0; 312 int originalCount = m_RectsCache.spriteRects.Count; 313 314 foreach (Rect frame in frames) 315 AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index); 316 317 if (slicingMethod == AutoSlicingMethod.DeleteAll) 318 m_RectsCache.ClearUnusedFileID(); 319 selected = null; 320 NotifyOnSpriteRectChanged(); 321 spriteEditor.SetDataModified(); 322 Repaint(); 323 } 324 325 UnityTexture2D GetTextureToSlice() 326 { 327 int width, height; 328 GetTextureActualWidthAndHeight(out width, out height); 329 var readableTexture = GetReadableTexture2D(); 330 if (readableTexture == null || (readableTexture.width == width && readableTexture.height == height)) 331 return readableTexture; 332 333 if (m_TextureToSlice == null) 334 { 335 // we want to slice based on the original texture slice. Upscale the imported texture 336 m_TextureToSlice = UnityEditor.SpriteUtility.CreateTemporaryDuplicate(readableTexture, width, height); 337 m_TextureToSlice.hideFlags = HideFlags.HideAndDontSave; 338 } 339 340 return m_TextureToSlice; 341 } 342 343 public IEnumerable<Rect> GetGridRects(Vector2 size, Vector2 offset, Vector2 padding, bool keepEmptyRects) 344 { 345 var textureToUse = GetTextureToSlice(); 346 return InternalSpriteUtility.GenerateGridSpriteRectangles((UnityTexture2D)textureToUse, offset, size, padding, keepEmptyRects); 347 } 348 349 public void DoGridSlicing(Vector2 size, Vector2 offset, Vector2 padding, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, bool keepEmptyRects = false) 350 { 351 var frames = GetGridRects(size, offset, padding, keepEmptyRects); 352 353 m_RectsCache.RegisterUndo(undoSystem, "Grid Slicing"); 354 if (slicingMethod == AutoSlicingMethod.DeleteAll) 355 m_RectsCache.Clear(); 356 357 int index = 0; 358 int originalCount = m_RectsCache.spriteRects.Count; 359 foreach (Rect frame in frames) 360 AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index); 361 362 if (slicingMethod == AutoSlicingMethod.DeleteAll) 363 m_RectsCache.ClearUnusedFileID(); 364 selected = null; 365 NotifyOnSpriteRectChanged(); 366 spriteEditor.SetDataModified(); 367 Repaint(); 368 } 369 370 public IEnumerable<Rect> GetIsometricRects(Vector2 size, Vector2 offset, bool isAlternate, bool keepEmptyRects) 371 { 372 var textureToUse = GetTextureToSlice(); 373 var gradient = (size.x / 2) / (size.y / 2); 374 bool isAlt = isAlternate; 375 float x = offset.x; 376 if (isAlt) 377 x += size.x / 2; 378 float y = textureToUse.height - offset.y; 379 while (y - size.y >= 0) 380 { 381 while (x + size.x <= textureToUse.width) 382 { 383 var rect = new Rect(x, y - size.y, size.x, size.y); 384 if (!keepEmptyRects) 385 { 386 int sx = (int)rect.x; 387 int sy = (int)rect.y; 388 int width = (int)size.x; 389 int odd = ((int)size.y) % 2; 390 int topY = ((int)size.y / 2) - 1; 391 int bottomY = topY + odd; 392 int totalPixels = 0; 393 int alphaPixels = 0; 394 { 395 for (int ry = 0; ry <= topY; ry++) 396 { 397 var pixelOffset = Mathf.CeilToInt(gradient * ry); 398 for (int rx = pixelOffset; rx < width - pixelOffset; ++rx) 399 { 400 if (PixelHasAlpha(sx + rx, sy + topY - ry, textureToUse)) 401 alphaPixels++; 402 if (PixelHasAlpha(sx + rx, sy + bottomY + ry, textureToUse)) 403 alphaPixels++; 404 totalPixels += 2; 405 } 406 } 407 } 408 if (odd > 0) 409 { 410 int ry = topY + 1; 411 for (int rx = 0; rx < size.x; ++rx) 412 { 413 if (PixelHasAlpha(sx + rx, sy + ry, textureToUse)) 414 alphaPixels++; 415 totalPixels++; 416 } 417 } 418 if (totalPixels > 0 && ((float)alphaPixels) / totalPixels > 0.01f) 419 yield return rect; 420 } 421 else 422 yield return rect; 423 x += size.x; 424 } 425 isAlt = !isAlt; 426 x = offset.x; 427 if (isAlt) 428 x += size.x / 2; 429 y -= size.y / 2; 430 } 431 } 432 433 public void DoIsometricGridSlicing(Vector2 size, Vector2 offset, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, bool keepEmptyRects = false, bool isAlternate = false) 434 { 435 var frames = GetIsometricRects(size, offset, isAlternate, keepEmptyRects); 436 437 List<Vector2[]> outlines = new List<Vector2[]>(4); 438 outlines.Add(new[] { new Vector2(0.0f, -size.y / 2) 439 , new Vector2(size.x / 2, 0.0f) 440 , new Vector2(0.0f, size.y / 2) 441 , new Vector2(-size.x / 2, 0.0f)}); 442 443 m_RectsCache.RegisterUndo(undoSystem, "Isometric Grid Slicing"); 444 if (slicingMethod == AutoSlicingMethod.DeleteAll) 445 m_RectsCache.Clear(); 446 447 int index = 0; 448 var spriteRects = m_RectsCache.GetSpriteRects(); 449 int originalCount = spriteRects.Count; 450 foreach (var frame in frames) 451 { 452 var spriteIndex = AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index); 453 var outlineRect = new OutlineSpriteRect(spriteRects[spriteIndex]); 454 outlineRect.outlines = outlines; 455 spriteRects[spriteIndex] = outlineRect; 456 } 457 if (slicingMethod == AutoSlicingMethod.DeleteAll) 458 m_RectsCache.ClearUnusedFileID(); 459 selected = null; 460 NotifyOnSpriteRectChanged(); 461 spriteEditor.SetDataModified(); 462 Repaint(); 463 } 464 465 public void ScaleSpriteRect(Rect r) 466 { 467 if (selected != null) 468 { 469 m_RectsCache.RegisterUndo(undoSystem, "Scale sprite"); 470 selected.rect = ClampSpriteRect(r, textureActualWidth, textureActualHeight); 471 selected.border = ClampSpriteBorderToRect(selected.border, selected.rect); 472 NotifyOnSpriteRectChanged(); 473 spriteEditor.SetDataModified(); 474 } 475 } 476 477 public void TrimAlpha() 478 { 479 var texture = GetTextureToSlice(); 480 if (texture == null) 481 return; 482 483 Rect rect = selected.rect; 484 485 int xMin = (int)rect.xMax; 486 int xMax = (int)rect.xMin; 487 int yMin = (int)rect.yMax; 488 int yMax = (int)rect.yMin; 489 490 for (int y = (int)rect.yMin; y < (int)rect.yMax; y++) 491 { 492 for (int x = (int)rect.xMin; x < (int)rect.xMax; x++) 493 { 494 if (PixelHasAlpha(x, y, texture)) 495 { 496 xMin = Mathf.Min(xMin, x); 497 xMax = Mathf.Max(xMax, x); 498 yMin = Mathf.Min(yMin, y); 499 yMax = Mathf.Max(yMax, y); 500 } 501 } 502 } 503 // Case 582309: Return an empty rectangle if no pixel has an alpha 504 if (xMin > xMax || yMin > yMax) 505 rect = new Rect(0, 0, 0, 0); 506 else 507 rect = new Rect(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1); 508 509 if (rect.width <= 0 && rect.height <= 0) 510 { 511 m_RectsCache.Remove(selected); 512 spriteEditor.SetDataModified(); 513 selected = null; 514 } 515 else 516 { 517 rect = ClampSpriteRect(rect, texture.width, texture.height); 518 if (selected.rect != rect) 519 spriteEditor.SetDataModified(); 520 521 selected.rect = rect; 522 PopulateSpriteFrameInspectorField(); 523 } 524 } 525 526 public void DuplicateSprite() 527 { 528 if (selected != null) 529 { 530 m_RectsCache.RegisterUndo(undoSystem, "Duplicate sprite"); 531 var index = 0; 532 var createdIndex = -1; 533 while (createdIndex == -1) 534 { 535 createdIndex = AddSprite(selected.rect, (int)selected.alignment, selected.pivot, GenerateSpriteNameWithIndex(index++), selected.border); 536 } 537 selected = m_RectsCache.spriteRects[createdIndex]; 538 NotifyOnSpriteRectChanged(); 539 } 540 } 541 542 public void CreateSprite(Rect rect) 543 { 544 rect = ClampSpriteRect(rect, textureActualWidth, textureActualHeight); 545 m_RectsCache.RegisterUndo(undoSystem, "Create sprite"); 546 var index = 0; 547 var createdIndex = -1; 548 while (createdIndex == -1) 549 { 550 createdIndex = AddSprite(rect, 0, Vector2.zero, GenerateSpriteNameWithIndex(index++), Vector4.zero); 551 } 552 selected = m_RectsCache.spriteRects[createdIndex]; 553 NotifyOnSpriteRectChanged(); 554 } 555 556 public void DeleteSprite() 557 { 558 if (selected != null) 559 { 560 m_RectsCache.RegisterUndo(undoSystem, "Delete sprite"); 561 m_RectsCache.Remove(selected); 562 selected = null; 563 NotifyOnSpriteRectChanged(); 564 spriteEditor.SetDataModified(); 565 } 566 } 567 568 public bool IsOnlyUsingDefaultNamedSpriteRects() 569 { 570 var onlyDefaultNames = true; 571 var names = m_RectsCache.spriteNames; 572 var defaultName = m_SpriteNameStringBuilder.ToString(); 573 574 foreach (var name in names) 575 { 576 if (!name.Contains(defaultName)) 577 { 578 onlyDefaultNames = false; 579 break; 580 } 581 } 582 583 return onlyDefaultNames; 584 } 585 } 586}