forked from BilalY/Rasagar
508 lines
20 KiB
C#
508 lines
20 KiB
C#
|
using System;
|
||
|
using System.IO;
|
||
|
using UnityEngine;
|
||
|
using UnityEditorInternal;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Text;
|
||
|
using UnityTexture2D = UnityEngine.Texture2D;
|
||
|
using UnityEditor.ShortcutManagement;
|
||
|
|
||
|
namespace UnityEditor.U2D.Sprites
|
||
|
{
|
||
|
[RequireSpriteDataProvider(typeof(ITextureDataProvider))]
|
||
|
internal partial class SpriteFrameModule : SpriteFrameModuleBase
|
||
|
{
|
||
|
public enum AutoSlicingMethod
|
||
|
{
|
||
|
DeleteAll = 0,
|
||
|
Smart = 1,
|
||
|
Safe = 2
|
||
|
}
|
||
|
|
||
|
private bool[] m_AlphaPixelCache;
|
||
|
SpriteFrameModuleContext m_SpriteFrameModuleContext;
|
||
|
public event Action onModuleDeactivated = () => { };
|
||
|
|
||
|
private const float kOverlapTolerance = 0.00001f;
|
||
|
private StringBuilder m_SpriteNameStringBuilder;
|
||
|
|
||
|
private List<Rect> m_PotentialRects;
|
||
|
public List<Rect> potentialRects
|
||
|
{
|
||
|
set => m_PotentialRects = value;
|
||
|
}
|
||
|
|
||
|
public SpriteFrameModule(ISpriteEditor sw, IEventSystem es, IUndoSystem us, IAssetDatabase ad) :
|
||
|
base("Sprite Editor", sw, es, us, ad)
|
||
|
{}
|
||
|
|
||
|
class SpriteFrameModuleContext : IShortcutContext
|
||
|
{
|
||
|
SpriteFrameModule m_SpriteFrameModule;
|
||
|
|
||
|
public SpriteFrameModuleContext(SpriteFrameModule spriteFrame)
|
||
|
{
|
||
|
m_SpriteFrameModule = spriteFrame;
|
||
|
}
|
||
|
|
||
|
public bool active
|
||
|
{
|
||
|
get { return true; }
|
||
|
}
|
||
|
public SpriteFrameModule spriteFrameModule
|
||
|
{
|
||
|
get { return m_SpriteFrameModule; }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[FormerlyPrefKeyAs("Sprite Editor/Trim", "#t")]
|
||
|
[Shortcut("Sprite Editor/Trim", typeof(SpriteFrameModuleContext), KeyCode.T, ShortcutModifiers.Shift)]
|
||
|
static void ShortcutTrim(ShortcutArguments args)
|
||
|
{
|
||
|
if (!string.IsNullOrEmpty(GUI.GetNameOfFocusedControl()))
|
||
|
return;
|
||
|
var spriteFrameContext = (SpriteFrameModuleContext)args.context;
|
||
|
spriteFrameContext.spriteFrameModule.TrimAlpha();
|
||
|
spriteFrameContext.spriteFrameModule.spriteEditor.RequestRepaint();
|
||
|
}
|
||
|
|
||
|
public override void OnModuleActivate()
|
||
|
{
|
||
|
base.OnModuleActivate();
|
||
|
spriteEditor.enableMouseMoveEvent = true;
|
||
|
m_SpriteFrameModuleContext = new SpriteFrameModuleContext(this);
|
||
|
ShortcutIntegration.instance.contextManager.RegisterToolContext(m_SpriteFrameModuleContext);
|
||
|
m_SpriteNameStringBuilder = new StringBuilder(GetSpriteNamePrefix() + "_");
|
||
|
m_PotentialRects = null;
|
||
|
}
|
||
|
|
||
|
public override void OnModuleDeactivate()
|
||
|
{
|
||
|
base.OnModuleDeactivate();
|
||
|
ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_SpriteFrameModuleContext);
|
||
|
m_PotentialRects = null;
|
||
|
m_AlphaPixelCache = null;
|
||
|
onModuleDeactivated();
|
||
|
}
|
||
|
|
||
|
public static SpriteImportMode GetSpriteImportMode(ISpriteEditorDataProvider dataProvider)
|
||
|
{
|
||
|
return dataProvider == null ? SpriteImportMode.None : dataProvider.spriteImportMode;
|
||
|
}
|
||
|
|
||
|
public override bool CanBeActivated()
|
||
|
{
|
||
|
return GetSpriteImportMode(spriteEditor.GetDataProvider<ISpriteEditorDataProvider>()) != SpriteImportMode.Polygon;
|
||
|
}
|
||
|
|
||
|
private string GenerateSpriteNameWithIndex(int startIndex)
|
||
|
{
|
||
|
int originalLength = m_SpriteNameStringBuilder.Length;
|
||
|
m_SpriteNameStringBuilder.Append(startIndex);
|
||
|
var name = m_SpriteNameStringBuilder.ToString();
|
||
|
m_SpriteNameStringBuilder.Length = originalLength;
|
||
|
return name;
|
||
|
}
|
||
|
|
||
|
private int AddSprite(Rect frame, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, int originalCount, ref int nameIndex)
|
||
|
{
|
||
|
int outSprite = -1;
|
||
|
switch (slicingMethod)
|
||
|
{
|
||
|
case AutoSlicingMethod.DeleteAll:
|
||
|
{
|
||
|
while (outSprite == -1)
|
||
|
{
|
||
|
outSprite = AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(nameIndex++), Vector4.zero);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case AutoSlicingMethod.Smart:
|
||
|
{
|
||
|
outSprite = GetExistingOverlappingSprite(frame, originalCount, true);
|
||
|
if (outSprite != -1)
|
||
|
{
|
||
|
var existingRect = m_RectsCache.spriteRects[outSprite];
|
||
|
existingRect.rect = frame;
|
||
|
existingRect.alignment = (SpriteAlignment)alignment;
|
||
|
existingRect.pivot = pivot;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
while (outSprite == -1)
|
||
|
{
|
||
|
outSprite = AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(nameIndex++), Vector4.zero);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case AutoSlicingMethod.Safe:
|
||
|
{
|
||
|
outSprite = GetExistingOverlappingSprite(frame, originalCount);
|
||
|
while (outSprite == -1)
|
||
|
{
|
||
|
outSprite = AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(nameIndex++), Vector4.zero);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
return outSprite;
|
||
|
}
|
||
|
|
||
|
private int GetExistingOverlappingSprite(Rect rect, int originalCount, bool bestFit = false)
|
||
|
{
|
||
|
var count = Math.Min(originalCount, m_RectsCache.spriteRects.Count);
|
||
|
int bestRect = -1;
|
||
|
float rectArea = rect.width * rect.height;
|
||
|
if (rectArea < kOverlapTolerance)
|
||
|
return bestRect;
|
||
|
|
||
|
float bestRatio = float.MaxValue;
|
||
|
float bestArea = float.MaxValue;
|
||
|
for (int i = 0; i < count; i++)
|
||
|
{
|
||
|
Rect existingRect = m_RectsCache.spriteRects[i].rect;
|
||
|
if (existingRect.Overlaps(rect))
|
||
|
{
|
||
|
if (bestFit)
|
||
|
{
|
||
|
float dx = Math.Min(rect.xMax, existingRect.xMax) - Math.Max(rect.xMin, existingRect.xMin);
|
||
|
float dy = Math.Min(rect.yMax, existingRect.yMax) - Math.Max(rect.yMin, existingRect.yMin);
|
||
|
float overlapArea = dx * dy;
|
||
|
float overlapRatio = Math.Abs((overlapArea / rectArea) - 1.0f);
|
||
|
float existingArea = existingRect.width * existingRect.height;
|
||
|
if (overlapRatio < bestRatio || (overlapRatio < kOverlapTolerance && existingArea < bestArea))
|
||
|
{
|
||
|
bestRatio = overlapRatio;
|
||
|
if (overlapRatio < kOverlapTolerance)
|
||
|
bestArea = existingArea;
|
||
|
bestRect = i;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bestRect = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return bestRect;
|
||
|
}
|
||
|
|
||
|
private bool PixelHasAlpha(int x, int y, UnityTexture2D texture)
|
||
|
{
|
||
|
if (m_AlphaPixelCache == null)
|
||
|
{
|
||
|
m_AlphaPixelCache = new bool[texture.width * texture.height];
|
||
|
Color32[] pixels = texture.GetPixels32();
|
||
|
|
||
|
for (int i = 0; i < pixels.Length; i++)
|
||
|
m_AlphaPixelCache[i] = pixels[i].a != 0;
|
||
|
}
|
||
|
int index = y * (int)texture.width + x;
|
||
|
return m_AlphaPixelCache[index];
|
||
|
}
|
||
|
|
||
|
private int AddSprite(Rect rect, int alignment, Vector2 pivot, string name, Vector4 border)
|
||
|
{
|
||
|
if (m_RectsCache.IsNameUsed(name))
|
||
|
return -1;
|
||
|
|
||
|
SpriteRect spriteRect = new SpriteRect();
|
||
|
spriteRect.rect = rect;
|
||
|
spriteRect.alignment = (SpriteAlignment)alignment;
|
||
|
spriteRect.pivot = pivot;
|
||
|
spriteRect.name = name;
|
||
|
spriteRect.originalName = spriteRect.name;
|
||
|
spriteRect.border = border;
|
||
|
|
||
|
spriteRect.spriteID = GUID.Generate();
|
||
|
|
||
|
if (!m_RectsCache.Add(spriteRect))
|
||
|
return -1;
|
||
|
spriteEditor.SetDataModified();
|
||
|
|
||
|
return m_RectsCache.spriteRects.Count - 1;
|
||
|
}
|
||
|
|
||
|
private string GetSpriteNamePrefix()
|
||
|
{
|
||
|
return Path.GetFileNameWithoutExtension(spriteAssetPath);
|
||
|
}
|
||
|
|
||
|
public void DoAutomaticSlicing(int minimumSpriteSize, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod)
|
||
|
{
|
||
|
undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Automatic Slicing");
|
||
|
|
||
|
if (slicingMethod == AutoSlicingMethod.DeleteAll)
|
||
|
m_RectsCache.Clear();
|
||
|
|
||
|
var textureToUse = GetTextureToSlice();
|
||
|
List<Rect> frames = new List<Rect>(InternalSpriteUtility.GenerateAutomaticSpriteRectangles((UnityTexture2D)textureToUse, minimumSpriteSize, 0));
|
||
|
if (frames.Count == 0)
|
||
|
frames.Add(new Rect(0, 0, textureToUse.width, textureToUse.height));
|
||
|
|
||
|
int index = 0;
|
||
|
int originalCount = m_RectsCache.spriteRects.Count;
|
||
|
|
||
|
foreach (Rect frame in frames)
|
||
|
AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index);
|
||
|
|
||
|
if (slicingMethod == AutoSlicingMethod.DeleteAll)
|
||
|
m_RectsCache.ClearUnusedFileID();
|
||
|
selected = null;
|
||
|
spriteEditor.SetDataModified();
|
||
|
Repaint();
|
||
|
}
|
||
|
|
||
|
UnityTexture2D GetTextureToSlice()
|
||
|
{
|
||
|
int width, height;
|
||
|
m_TextureDataProvider.GetTextureActualWidthAndHeight(out width, out height);
|
||
|
var readableTexture = m_TextureDataProvider.GetReadableTexture2D();
|
||
|
if (readableTexture == null || (readableTexture.width == width && readableTexture.height == height))
|
||
|
return readableTexture;
|
||
|
// we want to slice based on the original texture slice. Upscale the imported texture
|
||
|
var texture = UnityEditor.SpriteUtility.CreateTemporaryDuplicate(readableTexture, width, height);
|
||
|
return texture;
|
||
|
}
|
||
|
|
||
|
public IEnumerable<Rect> GetGridRects(Vector2 size, Vector2 offset, Vector2 padding, bool keepEmptyRects)
|
||
|
{
|
||
|
var textureToUse = GetTextureToSlice();
|
||
|
return InternalSpriteUtility.GenerateGridSpriteRectangles((UnityTexture2D)textureToUse, offset, size, padding, keepEmptyRects);
|
||
|
}
|
||
|
|
||
|
public void DoGridSlicing(Vector2 size, Vector2 offset, Vector2 padding, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, bool keepEmptyRects = false)
|
||
|
{
|
||
|
var frames = GetGridRects(size, offset, padding, keepEmptyRects);
|
||
|
|
||
|
undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Grid Slicing");
|
||
|
if (slicingMethod == AutoSlicingMethod.DeleteAll)
|
||
|
m_RectsCache.Clear();
|
||
|
|
||
|
int index = 0;
|
||
|
int originalCount = m_RectsCache.spriteRects.Count;
|
||
|
foreach (Rect frame in frames)
|
||
|
AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index);
|
||
|
|
||
|
if (slicingMethod == AutoSlicingMethod.DeleteAll)
|
||
|
m_RectsCache.ClearUnusedFileID();
|
||
|
selected = null;
|
||
|
spriteEditor.SetDataModified();
|
||
|
Repaint();
|
||
|
}
|
||
|
|
||
|
public IEnumerable<Rect> GetIsometricRects(Vector2 size, Vector2 offset, bool isAlternate, bool keepEmptyRects)
|
||
|
{
|
||
|
var textureToUse = GetTextureToSlice();
|
||
|
var gradient = (size.x / 2) / (size.y / 2);
|
||
|
bool isAlt = isAlternate;
|
||
|
float x = offset.x;
|
||
|
if (isAlt)
|
||
|
x += size.x / 2;
|
||
|
float y = textureToUse.height - offset.y;
|
||
|
while (y - size.y >= 0)
|
||
|
{
|
||
|
while (x + size.x <= textureToUse.width)
|
||
|
{
|
||
|
var rect = new Rect(x, y - size.y, size.x, size.y);
|
||
|
if (!keepEmptyRects)
|
||
|
{
|
||
|
int sx = (int)rect.x;
|
||
|
int sy = (int)rect.y;
|
||
|
int width = (int)size.x;
|
||
|
int odd = ((int)size.y) % 2;
|
||
|
int topY = ((int)size.y / 2) - 1;
|
||
|
int bottomY = topY + odd;
|
||
|
int totalPixels = 0;
|
||
|
int alphaPixels = 0;
|
||
|
{
|
||
|
for (int ry = 0; ry <= topY; ry++)
|
||
|
{
|
||
|
var pixelOffset = Mathf.CeilToInt(gradient * ry);
|
||
|
for (int rx = pixelOffset; rx < width - pixelOffset; ++rx)
|
||
|
{
|
||
|
if (PixelHasAlpha(sx + rx, sy + topY - ry, textureToUse))
|
||
|
alphaPixels++;
|
||
|
if (PixelHasAlpha(sx + rx, sy + bottomY + ry, textureToUse))
|
||
|
alphaPixels++;
|
||
|
totalPixels += 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (odd > 0)
|
||
|
{
|
||
|
int ry = topY + 1;
|
||
|
for (int rx = 0; rx < size.x; ++rx)
|
||
|
{
|
||
|
if (PixelHasAlpha(sx + rx, sy + ry, textureToUse))
|
||
|
alphaPixels++;
|
||
|
totalPixels++;
|
||
|
}
|
||
|
}
|
||
|
if (totalPixels > 0 && ((float)alphaPixels) / totalPixels > 0.01f)
|
||
|
yield return rect;
|
||
|
}
|
||
|
else
|
||
|
yield return rect;
|
||
|
x += size.x;
|
||
|
}
|
||
|
isAlt = !isAlt;
|
||
|
x = offset.x;
|
||
|
if (isAlt)
|
||
|
x += size.x / 2;
|
||
|
y -= size.y / 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void DoIsometricGridSlicing(Vector2 size, Vector2 offset, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, bool keepEmptyRects = false, bool isAlternate = false)
|
||
|
{
|
||
|
var frames = GetIsometricRects(size, offset, isAlternate, keepEmptyRects);
|
||
|
|
||
|
List<Vector2[]> outlines = new List<Vector2[]>(4);
|
||
|
outlines.Add(new[] { new Vector2(0.0f, -size.y / 2)
|
||
|
, new Vector2(size.x / 2, 0.0f)
|
||
|
, new Vector2(0.0f, size.y / 2)
|
||
|
, new Vector2(-size.x / 2, 0.0f)});
|
||
|
|
||
|
undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Isometric Grid Slicing");
|
||
|
if (slicingMethod == AutoSlicingMethod.DeleteAll)
|
||
|
m_RectsCache.Clear();
|
||
|
|
||
|
int index = 0;
|
||
|
var spriteRects = m_RectsCache.GetSpriteRects();
|
||
|
int originalCount = spriteRects.Count;
|
||
|
foreach (var frame in frames)
|
||
|
{
|
||
|
var spriteIndex = AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index);
|
||
|
var outlineRect = new OutlineSpriteRect(spriteRects[spriteIndex]);
|
||
|
outlineRect.outlines = outlines;
|
||
|
spriteRects[spriteIndex] = outlineRect;
|
||
|
}
|
||
|
if (slicingMethod == AutoSlicingMethod.DeleteAll)
|
||
|
m_RectsCache.ClearUnusedFileID();
|
||
|
selected = null;
|
||
|
spriteEditor.SetDataModified();
|
||
|
Repaint();
|
||
|
}
|
||
|
|
||
|
public void ScaleSpriteRect(Rect r)
|
||
|
{
|
||
|
if (selected != null)
|
||
|
{
|
||
|
undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Scale sprite");
|
||
|
selected.rect = ClampSpriteRect(r, textureActualWidth, textureActualHeight);
|
||
|
selected.border = ClampSpriteBorderToRect(selected.border, selected.rect);
|
||
|
spriteEditor.SetDataModified();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void TrimAlpha()
|
||
|
{
|
||
|
var texture = GetTextureToSlice();
|
||
|
if (texture == null)
|
||
|
return;
|
||
|
|
||
|
Rect rect = selected.rect;
|
||
|
|
||
|
int xMin = (int)rect.xMax;
|
||
|
int xMax = (int)rect.xMin;
|
||
|
int yMin = (int)rect.yMax;
|
||
|
int yMax = (int)rect.yMin;
|
||
|
|
||
|
for (int y = (int)rect.yMin; y < (int)rect.yMax; y++)
|
||
|
{
|
||
|
for (int x = (int)rect.xMin; x < (int)rect.xMax; x++)
|
||
|
{
|
||
|
if (PixelHasAlpha(x, y, texture))
|
||
|
{
|
||
|
xMin = Mathf.Min(xMin, x);
|
||
|
xMax = Mathf.Max(xMax, x);
|
||
|
yMin = Mathf.Min(yMin, y);
|
||
|
yMax = Mathf.Max(yMax, y);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Case 582309: Return an empty rectangle if no pixel has an alpha
|
||
|
if (xMin > xMax || yMin > yMax)
|
||
|
rect = new Rect(0, 0, 0, 0);
|
||
|
else
|
||
|
rect = new Rect(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1);
|
||
|
|
||
|
if (rect.width <= 0 && rect.height <= 0)
|
||
|
{
|
||
|
m_RectsCache.Remove(selected);
|
||
|
spriteEditor.SetDataModified();
|
||
|
selected = null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
rect = ClampSpriteRect(rect, texture.width, texture.height);
|
||
|
if (selected.rect != rect)
|
||
|
spriteEditor.SetDataModified();
|
||
|
|
||
|
selected.rect = rect;
|
||
|
PopulateSpriteFrameInspectorField();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void DuplicateSprite()
|
||
|
{
|
||
|
if (selected != null)
|
||
|
{
|
||
|
undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Duplicate sprite");
|
||
|
var index = 0;
|
||
|
var createdIndex = -1;
|
||
|
while (createdIndex == -1)
|
||
|
{
|
||
|
createdIndex = AddSprite(selected.rect, (int)selected.alignment, selected.pivot, GenerateSpriteNameWithIndex(index++), selected.border);
|
||
|
}
|
||
|
selected = m_RectsCache.spriteRects[createdIndex];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void CreateSprite(Rect rect)
|
||
|
{
|
||
|
rect = ClampSpriteRect(rect, textureActualWidth, textureActualHeight);
|
||
|
undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Create sprite");
|
||
|
var index = 0;
|
||
|
var createdIndex = -1;
|
||
|
while (createdIndex == -1)
|
||
|
{
|
||
|
createdIndex = AddSprite(rect, 0, Vector2.zero, GenerateSpriteNameWithIndex(index++), Vector4.zero);
|
||
|
}
|
||
|
selected = m_RectsCache.spriteRects[createdIndex];
|
||
|
}
|
||
|
|
||
|
public void DeleteSprite()
|
||
|
{
|
||
|
if (selected != null)
|
||
|
{
|
||
|
undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Delete sprite");
|
||
|
m_RectsCache.Remove(selected);
|
||
|
selected = null;
|
||
|
spriteEditor.SetDataModified();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool IsOnlyUsingDefaultNamedSpriteRects()
|
||
|
{
|
||
|
var onlyDefaultNames = true;
|
||
|
var names = m_RectsCache.spriteNames;
|
||
|
var defaultName = m_SpriteNameStringBuilder.ToString();
|
||
|
|
||
|
foreach (var name in names)
|
||
|
{
|
||
|
if (!name.Contains(defaultName))
|
||
|
{
|
||
|
onlyDefaultNames = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return onlyDefaultNames;
|
||
|
}
|
||
|
}
|
||
|
}
|