You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
915 lines
34 KiB
915 lines
34 KiB
using UnityEngine; |
|
using System.Collections.Generic; |
|
using System; |
|
using System.Linq; |
|
|
|
namespace UnityEditor.U2D.Sprites |
|
{ |
|
// We need this so that undo/redo works |
|
[Serializable] |
|
internal class SpriteOutline |
|
{ |
|
[SerializeField] |
|
public List<Vector2> m_Path = new List<Vector2>(); |
|
|
|
public void Add(Vector2 point) |
|
{ |
|
m_Path.Add(point); |
|
} |
|
|
|
public void Insert(int index, Vector2 point) |
|
{ |
|
m_Path.Insert(index, point); |
|
} |
|
|
|
public void RemoveAt(int index) |
|
{ |
|
m_Path.RemoveAt(index); |
|
} |
|
|
|
public Vector2 this[int index] |
|
{ |
|
get { return m_Path[index]; } |
|
set { m_Path[index] = value; } |
|
} |
|
|
|
public int Count |
|
{ |
|
get { return m_Path.Count; } |
|
} |
|
|
|
public void AddRange(IEnumerable<Vector2> addRange) |
|
{ |
|
m_Path.AddRange(addRange); |
|
} |
|
} |
|
|
|
// Collection of outlines for a single Sprite |
|
[Serializable] |
|
internal class SpriteOutlineList |
|
{ |
|
[SerializeField] |
|
List<SpriteOutline> m_SpriteOutlines = new List<SpriteOutline>(); |
|
[SerializeField] |
|
float m_TessellationDetail = 0; |
|
|
|
public List<SpriteOutline> spriteOutlines { get { return m_SpriteOutlines; } set { m_SpriteOutlines = value; } } |
|
public GUID spriteID { get; private set; } |
|
|
|
public float tessellationDetail |
|
{ |
|
get { return m_TessellationDetail; } |
|
set |
|
{ |
|
m_TessellationDetail = value; |
|
m_TessellationDetail = Mathf.Min(1, m_TessellationDetail); |
|
m_TessellationDetail = Mathf.Max(0, m_TessellationDetail); |
|
} |
|
} |
|
|
|
public SpriteOutlineList(GUID guid) |
|
{ |
|
this.spriteID = guid; |
|
m_SpriteOutlines = new List<SpriteOutline>(); |
|
} |
|
|
|
public SpriteOutlineList(GUID guid, List<Vector2[]> list) |
|
{ |
|
this.spriteID = guid; |
|
|
|
m_SpriteOutlines = new List<SpriteOutline>(list.Count); |
|
for (int i = 0; i < list.Count; ++i) |
|
{ |
|
var newList = new SpriteOutline(); |
|
newList.m_Path.AddRange(list[i]); |
|
m_SpriteOutlines.Add(newList); |
|
} |
|
} |
|
|
|
public SpriteOutlineList(GUID guid, List<SpriteOutline> list) |
|
{ |
|
this.spriteID = guid; |
|
|
|
m_SpriteOutlines = list; |
|
} |
|
|
|
public List<Vector2[]> ToListVector() |
|
{ |
|
var value = new List<Vector2[]>(m_SpriteOutlines.Count); |
|
foreach (var s in m_SpriteOutlines) |
|
{ |
|
value.Add(s.m_Path.ToArray()); |
|
} |
|
return value; |
|
} |
|
|
|
public List<Vector2[]> ToListVectorCapped(Rect rect) |
|
{ |
|
var value = ToListVector(); |
|
rect.center = Vector2.zero; |
|
foreach (var path in value) |
|
{ |
|
for (int i = 0; i < path.Length; ++i) |
|
{ |
|
var point = path[i]; |
|
path[i] = SpriteOutlineModule.CapPointToRect(point, rect);; |
|
} |
|
} |
|
return value; |
|
} |
|
|
|
public SpriteOutline this[int index] |
|
{ |
|
get { return IsValidIndex(index) ? m_SpriteOutlines[index] : null; } |
|
set |
|
{ |
|
if (IsValidIndex(index)) |
|
m_SpriteOutlines[index] = value; |
|
} |
|
} |
|
|
|
public static implicit operator List<SpriteOutline>(SpriteOutlineList list) |
|
{ |
|
return list != null ? list.m_SpriteOutlines : null; |
|
} |
|
|
|
public int Count { get { return m_SpriteOutlines.Count; } } |
|
|
|
bool IsValidIndex(int index) |
|
{ |
|
return index >= 0 && index < Count; |
|
} |
|
} |
|
|
|
// Collection of Sprites' outlines |
|
internal class SpriteOutlineModel : ScriptableObject |
|
{ |
|
[SerializeField] |
|
List<SpriteOutlineList> m_SpriteOutlineList = new List<SpriteOutlineList>(); |
|
|
|
private SpriteOutlineModel() |
|
{} |
|
|
|
public SpriteOutlineList this[int index] |
|
{ |
|
get { return IsValidIndex(index) ? m_SpriteOutlineList[index] : null; } |
|
set |
|
{ |
|
if (IsValidIndex(index)) |
|
m_SpriteOutlineList[index] = value; |
|
} |
|
} |
|
|
|
public SpriteOutlineList this[GUID guid] |
|
{ |
|
get { return m_SpriteOutlineList.FirstOrDefault(x => x.spriteID == guid); } |
|
set |
|
{ |
|
var index = m_SpriteOutlineList.FindIndex(x => x.spriteID == guid); |
|
if (index != -1) |
|
m_SpriteOutlineList[index] = value; |
|
} |
|
} |
|
|
|
public void AddListVector2(GUID guid, List<Vector2[]> outline) |
|
{ |
|
m_SpriteOutlineList.Add(new SpriteOutlineList(guid, outline)); |
|
} |
|
|
|
public int Count { get { return m_SpriteOutlineList.Count; } } |
|
|
|
bool IsValidIndex(int index) |
|
{ |
|
return index >= 0 && index < Count; |
|
} |
|
} |
|
|
|
[RequireSpriteDataProvider(typeof(ISpriteOutlineDataProvider), typeof(ITextureDataProvider))] |
|
internal class SpriteOutlineModule : SpriteEditorModuleBase |
|
{ |
|
class Styles |
|
{ |
|
public GUIContent generateOutlineLabel = EditorGUIUtility.TrTextContent("Generate", "Generate new outline based on mesh detail value."); |
|
public GUIContent outlineTolerance = EditorGUIUtility.TrTextContent("Outline Tolerance", "Sets how tight the outline should be from the sprite."); |
|
public GUIContent snapButtonLabel = EditorGUIUtility.TrTextContent("Snap", "Snap points to nearest pixel"); |
|
|
|
public GUIContent copyButtonLabel = EditorGUIUtility.TrTextContent("Copy", "Copy outline from Sprite"); |
|
public GUIContent pasteButtonLabel = EditorGUIUtility.TrTextContent("Paste", "Paste outline to Sprite"); |
|
public GUIContent pasteAllButtonLabel = EditorGUIUtility.TrTextContent("Paste All", "Paste outline to all Sprites"); |
|
|
|
public GUIContent generatingOutlineDialogTitle = EditorGUIUtility.TrTextContent("Outline"); |
|
public GUIContent generatingOutlineDialogContent = EditorGUIUtility.TrTextContent("Generating outline {0}/{1}"); |
|
public Color spriteBorderColor = new Color(0.25f, 0.5f, 1f, 0.75f); |
|
} |
|
|
|
protected SpriteRect m_Selected; |
|
|
|
private const float k_HandleSize = 5f; |
|
private readonly string k_DeleteCommandName = EventCommandNames.Delete; |
|
private readonly string k_SoftDeleteCommandName = EventCommandNames.SoftDelete; |
|
|
|
private ShapeEditor[] m_ShapeEditors; |
|
private bool m_RequestRepaint; |
|
private Matrix4x4 m_HandleMatrix; |
|
private Vector2 m_MousePosition; |
|
private bool m_Snap = true; |
|
private ShapeEditorRectSelectionTool m_ShapeSelectionUI; |
|
private bool m_WasRectSelecting = false; |
|
private Rect? m_SelectionRect; |
|
private ITexture2D m_OutlineTexture; |
|
private Styles m_Styles; |
|
protected SpriteOutlineModel m_Outline; |
|
private SpriteOutlineList m_CopyOutline = null; |
|
protected ITextureDataProvider m_TextureDataProvider; |
|
|
|
public SpriteOutlineModule(ISpriteEditor sem, IEventSystem es, IUndoSystem us, IAssetDatabase ad, IGUIUtility gu, IShapeEditorFactory sef, ITexture2D outlineTexture) |
|
{ |
|
spriteEditorWindow = sem; |
|
undoSystem = us; |
|
eventSystem = es; |
|
assetDatabase = ad; |
|
guiUtility = gu; |
|
shapeEditorFactory = sef; |
|
m_OutlineTexture = outlineTexture; |
|
|
|
m_ShapeSelectionUI = new ShapeEditorRectSelectionTool(gu); |
|
|
|
m_ShapeSelectionUI.RectSelect += RectSelect; |
|
m_ShapeSelectionUI.ClearSelection += ClearSelection; |
|
} |
|
|
|
public override string moduleName |
|
{ |
|
get { return "Custom Outline"; } |
|
} |
|
|
|
public override bool ApplyRevert(bool apply) |
|
{ |
|
if (m_Outline != null) |
|
{ |
|
if (apply) |
|
{ |
|
var outlineDataProvider = spriteEditorWindow.GetDataProvider<ISpriteOutlineDataProvider>(); |
|
for (int i = 0; i < m_Outline.Count; ++i) |
|
{ |
|
outlineDataProvider.SetOutlines(m_Outline[i].spriteID, m_Outline[i].ToListVector()); |
|
outlineDataProvider.SetTessellationDetail(m_Outline[i].spriteID, m_Outline[i].tessellationDetail); |
|
} |
|
} |
|
|
|
ScriptableObject.DestroyImmediate(m_Outline); |
|
m_Outline = null; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
private Styles styles |
|
{ |
|
get |
|
{ |
|
if (m_Styles == null) |
|
m_Styles = new Styles(); |
|
return m_Styles; |
|
} |
|
} |
|
|
|
protected virtual List<SpriteOutline> selectedShapeOutline |
|
{ |
|
get |
|
{ |
|
return m_Outline[m_Selected.spriteID].spriteOutlines; |
|
} |
|
set |
|
{ |
|
m_Outline[m_Selected.spriteID].spriteOutlines = value; |
|
} |
|
} |
|
|
|
private bool shapeEditorDirty |
|
{ |
|
get; set; |
|
} |
|
|
|
private bool editingDisabled |
|
{ |
|
get { return spriteEditorWindow.editingDisabled; } |
|
} |
|
|
|
private ISpriteEditor spriteEditorWindow |
|
{ |
|
get; set; |
|
} |
|
|
|
private IUndoSystem undoSystem |
|
{ |
|
get; set; |
|
} |
|
|
|
private IEventSystem eventSystem |
|
{ |
|
get; set; |
|
} |
|
|
|
private IAssetDatabase assetDatabase |
|
{ |
|
get; set; |
|
} |
|
|
|
private IGUIUtility guiUtility |
|
{ |
|
get; set; |
|
} |
|
|
|
private IShapeEditorFactory shapeEditorFactory |
|
{ |
|
get; set; |
|
} |
|
|
|
private void RectSelect(Rect r, ShapeEditor.SelectionType selectionType) |
|
{ |
|
var localRect = EditorGUIExt.FromToRect(ScreenToLocal(r.min), ScreenToLocal(r.max)); |
|
m_SelectionRect = localRect; |
|
} |
|
|
|
private void ClearSelection() |
|
{ |
|
m_RequestRepaint = true; |
|
} |
|
|
|
protected virtual void LoadOutline() |
|
{ |
|
m_Outline = ScriptableObject.CreateInstance<SpriteOutlineModel>(); |
|
m_Outline.hideFlags = HideFlags.HideAndDontSave; |
|
var spriteDataProvider = spriteEditorWindow.GetDataProvider<ISpriteEditorDataProvider>(); |
|
var outlineDataProvider = spriteEditorWindow.GetDataProvider<ISpriteOutlineDataProvider>(); |
|
foreach (var rect in spriteDataProvider.GetSpriteRects()) |
|
{ |
|
var outlines = outlineDataProvider.GetOutlines(rect.spriteID); |
|
m_Outline.AddListVector2(rect.spriteID, outlines); |
|
m_Outline[m_Outline.Count - 1].tessellationDetail = outlineDataProvider.GetTessellationDetail(rect.spriteID); |
|
} |
|
} |
|
|
|
public override void OnModuleActivate() |
|
{ |
|
m_TextureDataProvider = spriteEditorWindow.GetDataProvider<ITextureDataProvider>(); |
|
LoadOutline(); |
|
GenerateOutlineIfNotExist(); |
|
undoSystem.RegisterUndoCallback(UndoRedoPerformed); |
|
shapeEditorDirty = true; |
|
SetupShapeEditor(); |
|
spriteEditorWindow.enableMouseMoveEvent = true; |
|
} |
|
|
|
void GenerateOutlineIfNotExist() |
|
{ |
|
var rectCache = spriteEditorWindow.GetDataProvider<ISpriteEditorDataProvider>().GetSpriteRects(); |
|
if (rectCache != null) |
|
{ |
|
bool needApply = false; |
|
for (int i = 0; i < rectCache.Length; ++i) |
|
{ |
|
var rect = rectCache[i]; |
|
if (!HasShapeOutline(rect)) |
|
{ |
|
EditorUtility.DisplayProgressBar(styles.generatingOutlineDialogTitle.text, |
|
string.Format(styles.generatingOutlineDialogContent.text, i + 1 , rectCache.Length), |
|
(float)(i) / rectCache.Length); |
|
|
|
SetupShapeEditorOutline(rect); |
|
needApply = true; |
|
} |
|
} |
|
if (needApply) |
|
{ |
|
EditorUtility.ClearProgressBar(); |
|
spriteEditorWindow.ApplyOrRevertModification(true); |
|
LoadOutline(); |
|
} |
|
} |
|
} |
|
|
|
public override void OnModuleDeactivate() |
|
{ |
|
undoSystem.UnregisterUndoCallback(UndoRedoPerformed); |
|
CleanupShapeEditors(); |
|
m_Selected = null; |
|
spriteEditorWindow.enableMouseMoveEvent = false; |
|
if (m_Outline != null) |
|
{ |
|
undoSystem.ClearUndo(m_Outline); |
|
ScriptableObject.DestroyImmediate(m_Outline); |
|
m_Outline = null; |
|
} |
|
} |
|
|
|
public override void DoMainGUI() |
|
{ |
|
IEvent evt = eventSystem.current; |
|
|
|
m_RequestRepaint = false; |
|
m_HandleMatrix = Handles.matrix; |
|
|
|
m_MousePosition = Handles.inverseMatrix.MultiplyPoint(eventSystem.current.mousePosition); |
|
if (m_Selected == null || !m_Selected.rect.Contains(m_MousePosition) && !IsMouseOverOutlinePoints() && evt.shift == false) |
|
spriteEditorWindow.HandleSpriteSelection(); |
|
|
|
HandleCreateNewOutline(); |
|
|
|
m_WasRectSelecting = m_ShapeSelectionUI.isSelecting; |
|
|
|
UpdateShapeEditors(); |
|
|
|
m_ShapeSelectionUI.OnGUI(); |
|
|
|
DrawGizmos(); |
|
|
|
if (m_RequestRepaint || evt.type == EventType.MouseMove) |
|
spriteEditorWindow.RequestRepaint(); |
|
} |
|
|
|
public override void DoToolbarGUI(Rect drawArea) |
|
{ |
|
var style = styles; |
|
|
|
Rect snapDrawArea = new Rect(drawArea.x, drawArea.y, EditorStyles.toolbarButton.CalcSize(style.snapButtonLabel).x, drawArea.height); |
|
m_Snap = GUI.Toggle(snapDrawArea, m_Snap, style.snapButtonLabel, EditorStyles.toolbarButton); |
|
|
|
using (new EditorGUI.DisabledScope(editingDisabled || m_Selected == null)) |
|
{ |
|
float totalWidth = drawArea.width - snapDrawArea.width; |
|
drawArea.x = snapDrawArea.xMax; |
|
drawArea.width = EditorStyles.toolbarButton.CalcSize(style.outlineTolerance).x; |
|
totalWidth -= drawArea.width; |
|
if (totalWidth < 0) |
|
drawArea.width += totalWidth; |
|
if (drawArea.width > 0) |
|
GUI.Label(drawArea, style.outlineTolerance, EditorStyles.miniLabel); |
|
drawArea.x += drawArea.width; |
|
|
|
drawArea.width = 100; |
|
totalWidth -= drawArea.width; |
|
if (totalWidth < 0) |
|
drawArea.width += totalWidth; |
|
|
|
if (drawArea.width > 0) |
|
{ |
|
float tesselationValue = m_Selected != null ? m_Outline[m_Selected.spriteID].tessellationDetail : 0; |
|
EditorGUI.BeginChangeCheck(); |
|
float oldFieldWidth = EditorGUIUtility.fieldWidth; |
|
float oldLabelWidth = EditorGUIUtility.labelWidth; |
|
EditorGUIUtility.fieldWidth = 35; |
|
EditorGUIUtility.labelWidth = 1; |
|
tesselationValue = EditorGUI.Slider(drawArea, Mathf.Clamp01(tesselationValue), 0, 1); |
|
if (EditorGUI.EndChangeCheck()) |
|
{ |
|
RecordUndo(); |
|
m_Outline[m_Selected.spriteID].tessellationDetail = tesselationValue; |
|
} |
|
EditorGUIUtility.fieldWidth = oldFieldWidth; |
|
EditorGUIUtility.labelWidth = oldLabelWidth; |
|
} |
|
|
|
drawArea.x += drawArea.width + 2; |
|
drawArea.width = EditorStyles.toolbarButton.CalcSize(style.generateOutlineLabel).x; |
|
totalWidth -= drawArea.width; |
|
if (totalWidth < 0) |
|
drawArea.width += totalWidth; |
|
|
|
if (drawArea.width > 0 && GUI.Button(drawArea, style.generateOutlineLabel, EditorStyles.toolbarButton)) |
|
{ |
|
RecordUndo(); |
|
selectedShapeOutline.Clear(); |
|
SetupShapeEditorOutline(m_Selected); |
|
spriteEditorWindow.SetDataModified(); |
|
shapeEditorDirty = true; |
|
} |
|
|
|
using (new EditorGUI.DisabledScope(m_Selected == null || !HasShapeOutline(m_Selected))) |
|
{ |
|
drawArea.x += drawArea.width + 2; |
|
drawArea.width = EditorStyles.toolbarButton.CalcSize(style.copyButtonLabel).x; |
|
totalWidth -= drawArea.width; |
|
if (totalWidth < 0) |
|
drawArea.width += totalWidth; |
|
|
|
if (drawArea.width > 0 && GUI.Button(drawArea, style.copyButtonLabel, EditorStyles.toolbarButton)) |
|
{ |
|
Copy(); |
|
} |
|
} |
|
|
|
using (new EditorGUI.DisabledScope(m_Selected == null || m_CopyOutline == null)) |
|
{ |
|
drawArea.x += drawArea.width; |
|
drawArea.width = EditorStyles.toolbarButton.CalcSize(style.pasteButtonLabel).x; |
|
totalWidth -= drawArea.width; |
|
if (totalWidth < 0) |
|
drawArea.width += totalWidth; |
|
|
|
if (drawArea.width > 0 && GUI.Button(drawArea, style.pasteButtonLabel, EditorStyles.toolbarButton)) |
|
{ |
|
Paste(); |
|
} |
|
} |
|
|
|
using (new EditorGUI.DisabledScope(m_CopyOutline == null)) |
|
{ |
|
drawArea.x += drawArea.width; |
|
drawArea.width = EditorStyles.toolbarButton.CalcSize(style.pasteAllButtonLabel).x; |
|
totalWidth -= drawArea.width; |
|
if (totalWidth < 0) |
|
drawArea.width += totalWidth; |
|
|
|
if (drawArea.width > 0 && GUI.Button(drawArea, style.pasteAllButtonLabel, EditorStyles.toolbarButton)) |
|
{ |
|
PasteAll(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
public override void DoPostGUI() |
|
{} |
|
|
|
public override bool CanBeActivated() |
|
{ |
|
return SpriteFrameModule.GetSpriteImportMode(spriteEditorWindow.GetDataProvider<ISpriteEditorDataProvider>()) != SpriteImportMode.None; |
|
} |
|
|
|
private void RecordUndo() |
|
{ |
|
undoSystem.RegisterCompleteObjectUndo(m_Outline, "Outline changed"); |
|
} |
|
|
|
public void CreateNewOutline(Rect rectOutline) |
|
{ |
|
Rect rect = m_Selected.rect; |
|
if (rect.Contains(rectOutline.min) && rect.Contains(rectOutline.max)) |
|
{ |
|
RecordUndo(); |
|
SpriteOutline so = new SpriteOutline(); |
|
Vector2 outlineOffset = new Vector2(0.5f * rect.width + rect.x, 0.5f * rect.height + rect.y); |
|
Rect selectionRect = new Rect(rectOutline); |
|
selectionRect.min = SnapPoint(rectOutline.min); |
|
selectionRect.max = SnapPoint(rectOutline.max); |
|
so.Add(CapPointToRect(new Vector2(selectionRect.xMin, selectionRect.yMin), rect) - outlineOffset); |
|
so.Add(CapPointToRect(new Vector2(selectionRect.xMin, selectionRect.yMax), rect) - outlineOffset); |
|
so.Add(CapPointToRect(new Vector2(selectionRect.xMax, selectionRect.yMax), rect) - outlineOffset); |
|
so.Add(CapPointToRect(new Vector2(selectionRect.xMax, selectionRect.yMin), rect) - outlineOffset); |
|
selectedShapeOutline.Add(so); |
|
spriteEditorWindow.SetDataModified(); |
|
shapeEditorDirty = true; |
|
} |
|
} |
|
|
|
private void HandleCreateNewOutline() |
|
{ |
|
if (m_WasRectSelecting && m_ShapeSelectionUI.isSelecting == false && m_SelectionRect != null && m_Selected != null) |
|
{ |
|
bool createNewOutline = true; |
|
foreach (var se in m_ShapeEditors) |
|
{ |
|
if (se.selectedPoints.Count != 0) |
|
{ |
|
createNewOutline = false; |
|
break; |
|
} |
|
} |
|
|
|
if (createNewOutline) |
|
CreateNewOutline(m_SelectionRect.Value); |
|
} |
|
m_SelectionRect = null; |
|
} |
|
|
|
public void UpdateShapeEditors() |
|
{ |
|
SetupShapeEditor(); |
|
|
|
if (m_Selected != null) |
|
{ |
|
IEvent currentEvent = eventSystem.current; |
|
var wantsDelete = currentEvent.type == EventType.ExecuteCommand && (currentEvent.commandName == k_SoftDeleteCommandName || currentEvent.commandName == k_DeleteCommandName); |
|
|
|
for (int i = 0; i < m_ShapeEditors.Length; ++i) |
|
{ |
|
if (m_ShapeEditors[i].GetPointsCount() == 0) |
|
continue; |
|
|
|
m_ShapeEditors[i].inEditMode = true; |
|
m_ShapeEditors[i].OnGUI(); |
|
if (shapeEditorDirty) |
|
break; |
|
} |
|
|
|
if (wantsDelete) |
|
{ |
|
// remove outline which have lesser than 3 points |
|
for (int i = selectedShapeOutline.Count - 1; i >= 0; --i) |
|
{ |
|
if (selectedShapeOutline[i].Count < 3) |
|
{ |
|
selectedShapeOutline.RemoveAt(i); |
|
shapeEditorDirty = true; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
private bool IsMouseOverOutlinePoints() |
|
{ |
|
if (m_Selected == null) |
|
return false; |
|
Vector2 outlineOffset = new Vector2(0.5f * m_Selected.rect.width + m_Selected.rect.x, 0.5f * m_Selected.rect.height + m_Selected.rect.y); |
|
float handleSize = GetHandleSize(); |
|
Rect r = new Rect(0, 0, handleSize * 2, handleSize * 2); |
|
for (int i = 0; i < selectedShapeOutline.Count; ++i) |
|
{ |
|
var outline = selectedShapeOutline[i]; |
|
for (int j = 0; j < outline.Count; ++j) |
|
{ |
|
r.center = outline[j] + outlineOffset; |
|
if (r.Contains(m_MousePosition)) |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
private float GetHandleSize() |
|
{ |
|
return k_HandleSize / m_HandleMatrix.m00; |
|
} |
|
|
|
private void CleanupShapeEditors() |
|
{ |
|
if (m_ShapeEditors != null) |
|
{ |
|
for (int i = 0; i < m_ShapeEditors.Length; ++i) |
|
{ |
|
for (int j = 0; j < m_ShapeEditors.Length; ++j) |
|
{ |
|
if (i != j) |
|
m_ShapeEditors[j].UnregisterFromShapeEditor(m_ShapeEditors[i]); |
|
} |
|
m_ShapeEditors[i].OnDisable(); |
|
} |
|
} |
|
m_ShapeEditors = null; |
|
} |
|
|
|
public void SetupShapeEditor() |
|
{ |
|
if (shapeEditorDirty || m_Selected != spriteEditorWindow.selectedSpriteRect) |
|
{ |
|
m_Selected = spriteEditorWindow.selectedSpriteRect; |
|
CleanupShapeEditors(); |
|
|
|
if (m_Selected != null) |
|
{ |
|
if (!HasShapeOutline(m_Selected)) |
|
SetupShapeEditorOutline(m_Selected); |
|
m_ShapeEditors = new ShapeEditor[selectedShapeOutline.Count]; |
|
|
|
for (int i = 0; i < selectedShapeOutline.Count; ++i) |
|
{ |
|
int outlineIndex = i; |
|
m_ShapeEditors[i] = shapeEditorFactory.CreateShapeEditor(); |
|
m_ShapeEditors[i].SetRectSelectionTool(m_ShapeSelectionUI); |
|
m_ShapeEditors[i].LocalToWorldMatrix = () => m_HandleMatrix; |
|
m_ShapeEditors[i].LocalToScreen = (point) => Handles.matrix.MultiplyPoint(point); |
|
m_ShapeEditors[i].ScreenToLocal = ScreenToLocal; |
|
m_ShapeEditors[i].RecordUndo = RecordUndo; |
|
m_ShapeEditors[i].GetHandleSize = GetHandleSize; |
|
m_ShapeEditors[i].lineTexture = m_OutlineTexture; |
|
m_ShapeEditors[i].Snap = SnapPoint; |
|
m_ShapeEditors[i].GetPointPosition = (index) => GetPointPosition(outlineIndex, index); |
|
m_ShapeEditors[i].SetPointPosition = (index, position) => SetPointPosition(outlineIndex, index, position); |
|
m_ShapeEditors[i].InsertPointAt = (index, position) => InsertPointAt(outlineIndex, index, position); |
|
m_ShapeEditors[i].RemovePointAt = (index) => RemovePointAt(outlineIndex, index); |
|
m_ShapeEditors[i].GetPointsCount = () => GetPointsCount(outlineIndex); |
|
} |
|
for (int i = 0; i < selectedShapeOutline.Count; ++i) |
|
{ |
|
for (int j = 0; j < selectedShapeOutline.Count; ++j) |
|
{ |
|
if (i != j) |
|
m_ShapeEditors[j].RegisterToShapeEditor(m_ShapeEditors[i]); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
m_ShapeEditors = new ShapeEditor[0]; |
|
} |
|
} |
|
shapeEditorDirty = false; |
|
} |
|
|
|
protected virtual bool HasShapeOutline(SpriteRect spriteRect) |
|
{ |
|
var outline = m_Outline[spriteRect.spriteID] != null ? m_Outline[spriteRect.spriteID].spriteOutlines : null; |
|
return outline != null; |
|
} |
|
|
|
protected virtual void SetupShapeEditorOutline(SpriteRect spriteRect) |
|
{ |
|
var outline = m_Outline[spriteRect.spriteID]; |
|
var outlines = GenerateSpriteRectOutline(spriteRect.rect, |
|
Math.Abs(outline.tessellationDetail - (-1f)) < Mathf.Epsilon ? 0 : outline.tessellationDetail, |
|
0, m_TextureDataProvider); |
|
if (outlines.Count == 0) |
|
{ |
|
Vector2 halfSize = spriteRect.rect.size * 0.5f; |
|
outlines = new List<SpriteOutline>() |
|
{ |
|
new SpriteOutline() |
|
{ |
|
m_Path = new List<Vector2>() |
|
{ |
|
new Vector2(-halfSize.x, -halfSize.y), |
|
new Vector2(-halfSize.x, halfSize.y), |
|
new Vector2(halfSize.x, halfSize.y), |
|
new Vector2(halfSize.x, -halfSize.y), |
|
} |
|
} |
|
}; |
|
} |
|
m_Outline[spriteRect.spriteID].spriteOutlines = outlines; |
|
} |
|
|
|
public Vector3 SnapPoint(Vector3 position) |
|
{ |
|
if (m_Snap) |
|
{ |
|
position.x = Mathf.RoundToInt(position.x); |
|
position.y = Mathf.RoundToInt(position.y); |
|
} |
|
return position; |
|
} |
|
|
|
public Vector3 GetPointPosition(int outlineIndex, int pointIndex) |
|
{ |
|
if (outlineIndex >= 0 && outlineIndex < selectedShapeOutline.Count) |
|
{ |
|
var outline = selectedShapeOutline[outlineIndex]; |
|
if (pointIndex >= 0 && pointIndex < outline.Count) |
|
{ |
|
return ConvertSpriteRectSpaceToTextureSpace(outline[pointIndex]); |
|
} |
|
} |
|
return new Vector3(float.NaN, float.NaN, float.NaN); |
|
} |
|
|
|
public void SetPointPosition(int outlineIndex, int pointIndex, Vector3 position) |
|
{ |
|
selectedShapeOutline[outlineIndex][pointIndex] = ConvertTextureSpaceToSpriteRectSpace(CapPointToRect(position, m_Selected.rect)); |
|
spriteEditorWindow.SetDataModified(); |
|
} |
|
|
|
public void InsertPointAt(int outlineIndex, int pointIndex, Vector3 position) |
|
{ |
|
selectedShapeOutline[outlineIndex].Insert(pointIndex, ConvertTextureSpaceToSpriteRectSpace(CapPointToRect(position, m_Selected.rect))); |
|
spriteEditorWindow.SetDataModified(); |
|
} |
|
|
|
public void RemovePointAt(int outlineIndex, int i) |
|
{ |
|
selectedShapeOutline[outlineIndex].RemoveAt(i); |
|
spriteEditorWindow.SetDataModified(); |
|
} |
|
|
|
public int GetPointsCount(int outlineIndex) |
|
{ |
|
return selectedShapeOutline[outlineIndex].Count; |
|
} |
|
|
|
private Vector2 ConvertSpriteRectSpaceToTextureSpace(Vector2 value) |
|
{ |
|
Vector2 outlineOffset = new Vector2(0.5f * m_Selected.rect.width + m_Selected.rect.x, 0.5f * m_Selected.rect.height + m_Selected.rect.y); |
|
value += outlineOffset; |
|
return value; |
|
} |
|
|
|
private Vector2 ConvertTextureSpaceToSpriteRectSpace(Vector2 value) |
|
{ |
|
Vector2 outlineOffset = new Vector2(0.5f * m_Selected.rect.width + m_Selected.rect.x, 0.5f * m_Selected.rect.height + m_Selected.rect.y); |
|
value -= outlineOffset; |
|
return value; |
|
} |
|
|
|
private Vector3 ScreenToLocal(Vector2 point) |
|
{ |
|
return Handles.inverseMatrix.MultiplyPoint(point); |
|
} |
|
|
|
private void UndoRedoPerformed() |
|
{ |
|
shapeEditorDirty = true; |
|
} |
|
|
|
private void DrawGizmos() |
|
{ |
|
if (eventSystem.current.type == EventType.Repaint) |
|
{ |
|
var selected = spriteEditorWindow.selectedSpriteRect; |
|
if (selected != null) |
|
{ |
|
SpriteEditorUtility.BeginLines(styles.spriteBorderColor); |
|
SpriteEditorUtility.DrawBox(selected.rect); |
|
SpriteEditorUtility.EndLines(); |
|
} |
|
} |
|
} |
|
|
|
protected static List<SpriteOutline> GenerateSpriteRectOutline(Rect rect, float detail, byte alphaTolerance, ITextureDataProvider textureProvider) |
|
{ |
|
List<SpriteOutline> outline = new List<SpriteOutline>(); |
|
var texture = textureProvider.GetReadableTexture2D(); |
|
if (texture != null) |
|
{ |
|
Vector2[][] paths; |
|
|
|
// we might have a texture that is capped because of max size or NPOT. |
|
// in that case, we need to convert values from capped space to actual texture space and back. |
|
int actualWidth = 0, actualHeight = 0; |
|
int cappedWidth, cappedHeight; |
|
textureProvider.GetTextureActualWidthAndHeight(out actualWidth, out actualHeight); |
|
cappedWidth = texture.width; |
|
cappedHeight = texture.height; |
|
|
|
Vector2 scale = new Vector2(cappedWidth / (float)actualWidth, cappedHeight / (float)actualHeight); |
|
Rect spriteRect = rect; |
|
spriteRect.xMin *= scale.x; |
|
spriteRect.xMax *= scale.x; |
|
spriteRect.yMin *= scale.y; |
|
spriteRect.yMax *= scale.y; |
|
|
|
UnityEditor.Sprites.SpriteUtility.GenerateOutline(texture, spriteRect, detail, alphaTolerance, true, out paths); |
|
|
|
Rect capRect = new Rect(); |
|
capRect.size = rect.size; |
|
capRect.center = Vector2.zero; |
|
for (int j = 0; j < paths.Length; ++j) |
|
{ |
|
SpriteOutline points = new SpriteOutline(); |
|
foreach (Vector2 v in paths[j]) |
|
points.Add(CapPointToRect(new Vector2(v.x / scale.x, v.y / scale.y), capRect)); |
|
|
|
outline.Add(points); |
|
} |
|
} |
|
return outline; |
|
} |
|
|
|
public void Copy() |
|
{ |
|
if (m_Selected == null || !HasShapeOutline(m_Selected)) |
|
return; |
|
|
|
m_CopyOutline = new SpriteOutlineList(m_Selected.spriteID, m_Outline[m_Selected.spriteID].ToListVectorCapped(m_Selected.rect)); |
|
} |
|
|
|
public void Paste() |
|
{ |
|
if (m_Selected == null || m_CopyOutline == null) |
|
return; |
|
|
|
RecordUndo(); |
|
m_Outline[m_Selected.spriteID] = new SpriteOutlineList(m_Selected.spriteID, m_CopyOutline.ToListVectorCapped(m_Selected.rect)); |
|
spriteEditorWindow.SetDataModified(); |
|
shapeEditorDirty = true; |
|
} |
|
|
|
public void PasteAll() |
|
{ |
|
if (m_CopyOutline == null) |
|
return; |
|
|
|
RecordUndo(); |
|
var rectCache = spriteEditorWindow.GetDataProvider<ISpriteEditorDataProvider>().GetSpriteRects(); |
|
if (rectCache != null) |
|
{ |
|
foreach (var spriteRect in rectCache) |
|
{ |
|
var outlines = m_CopyOutline.ToListVectorCapped(spriteRect.rect); |
|
m_Outline[spriteRect.spriteID] = new SpriteOutlineList(spriteRect.spriteID, outlines); |
|
} |
|
} |
|
spriteEditorWindow.SetDataModified(); |
|
shapeEditorDirty = true; |
|
} |
|
|
|
internal static Vector2 CapPointToRect(Vector2 so, Rect r) |
|
{ |
|
so.x = Mathf.Min(r.xMax, so.x); |
|
so.x = Mathf.Max(r.xMin, so.x); |
|
so.y = Mathf.Min(r.yMax, so.y); |
|
so.y = Mathf.Max(r.yMin, so.y); |
|
return so; |
|
} |
|
} |
|
}
|
|
|