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 m_Path = new List(); 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 addRange) { m_Path.AddRange(addRange); } } // Collection of outlines for a single Sprite [Serializable] internal class SpriteOutlineList { [SerializeField] List m_SpriteOutlines = new List(); [SerializeField] float m_TessellationDetail = 0; public List 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(); } public SpriteOutlineList(GUID guid, List list) { this.spriteID = guid; m_SpriteOutlines = new List(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 list) { this.spriteID = guid; m_SpriteOutlines = list; } public List ToListVector() { var value = new List(m_SpriteOutlines.Count); foreach (var s in m_SpriteOutlines) { value.Add(s.m_Path.ToArray()); } return value; } public List 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(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 m_SpriteOutlineList = new List(); 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 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(); 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 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(); m_Outline.hideFlags = HideFlags.HideAndDontSave; var spriteDataProvider = spriteEditorWindow.GetDataProvider(); var outlineDataProvider = spriteEditorWindow.GetDataProvider(); 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(); LoadOutline(); GenerateOutlineIfNotExist(); undoSystem.RegisterUndoCallback(UndoRedoPerformed); shapeEditorDirty = true; SetupShapeEditor(); spriteEditorWindow.enableMouseMoveEvent = true; } void GenerateOutlineIfNotExist() { var rectCache = spriteEditorWindow.GetDataProvider().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()) != 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() { new SpriteOutline() { m_Path = new List() { 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 GenerateSpriteRectOutline(Rect rect, float detail, byte alphaTolerance, ITextureDataProvider textureProvider) { List outline = new List(); 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().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; } } }