using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Linq; using Unity.Collections; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.U2D; #if !(UNITY_2019_3_OR_NEWER) using UnityEngine.Experimental.U2D; #endif using UnityEngine.Experimental.Rendering; namespace ToolBuddy.ThirdParty.VectorGraphics { public static partial class VectorUtils { /// The alignement of the sprite, to determine the location of the pivot. public enum Alignment { /// Center alignment. Center = 0, /// Top-left alignment. TopLeft = 1, /// Top-center alignment. TopCenter = 2, /// Top-right alignment. TopRight = 3, /// Left-center alignment. LeftCenter = 4, /// Right-center alignment. RightCenter = 5, /// Bottom-left alignment. BottomLeft = 6, /// Bottom-center alignment. BottomCenter = 7, /// Bottom-right alignment. BottomRight = 8, /// Custom alignment. /// /// Uses a custom alignment that will be used when building the sprite using the method. /// Custom = 9, /// SVG origin alignment. /// /// This will use the origin of the SVG document as the origin of the sprite. /// SVGOrigin = 10 } private static void FlipYAxis(IList vertices) { var bbox = Bounds(vertices); var h = bbox.height; for (int i = 0; i < vertices.Count; ++i) { var v = vertices[i]; v.y -= bbox.position.y; v.y = h - v.y; v.y += bbox.position.y; vertices[i] = v; } } internal enum WindingDir { CW, CCW } internal static void AdjustWinding(Vector2[] vertices, UInt16[] indices, WindingDir dir) { bool shouldFlip = false; var length = indices.Length; for (int i = 0; i < (length - 2); i += 3) { var v0 = (Vector3)vertices[indices[i]]; var v1 = (Vector3)vertices[indices[i+1]]; var v2 = (Vector3)vertices[indices[i+2]]; var s = (v1 - v0).normalized; var t = (v2 - v0).normalized; float dot = Vector3.Dot(s, t); if (s == Vector3.zero || t == Vector3.zero || dot > 0.9999f || dot < -0.9999f) continue; var n = Vector3.Cross(s, t); if (n.sqrMagnitude < 0.0001f) continue; shouldFlip = dir == WindingDir.CCW ? n.z < 0.0f : n.z > 0.0f; break; } if (shouldFlip) { for (int i = 0; i < (length - 2); i += 3) { var tmp = indices[i]; indices[i] = indices[i+1]; indices[i+1] = tmp; } } } private static void FlipRangeIfNecessary(List vertices, List indices, int indexStart, int indexEnd, bool flipYAxis) { // For the range, find the first valid triangle and check its winding order. If that triangle needs flipping, then flip the whole range. bool shouldFlip = false; for (int i = indexStart; i < (indexEnd - 2); i += 3) { var v0 = (Vector3)vertices[indices[i]]; var v1 = (Vector3)vertices[indices[i + 1]]; var v2 = (Vector3)vertices[indices[i + 2]]; var s = (v1 - v0).normalized; var t = (v2 - v0).normalized; float dot = Vector3.Dot(s, t); if (s == Vector3.zero || t == Vector3.zero || dot > 0.99f || dot < -0.99f) continue; var n = Vector3.Cross(s, t); if (n.sqrMagnitude < 0.001f) continue; shouldFlip = flipYAxis ? n.z < 0.0f : n.z > 0.0f; break; } if (shouldFlip) { for (int i = indexStart; i < (indexEnd - 2); i += 3) { var tmp = indices[i + 1]; indices[i + 1] = indices[i + 2]; indices[i + 2] = tmp; } } } internal static void RenderFromArrays(Vector2[] vertices, UInt16[] indices, Vector2[] uvs, Color[] colors, Vector2[] settings, Texture2D texture, Material mat, bool clear = true) { mat.SetTexture("_MainTex", texture); mat.SetPass(0); if (clear) GL.Clear(true, true, Color.clear); GL.PushMatrix(); GL.LoadOrtho(); GL.Color(new Color(1, 1, 1, 1)); GL.Begin(GL.TRIANGLES); for (int i = 0; i < indices.Length; ++i) { ushort index = indices[i]; Vector2 vertex = vertices[index]; Vector2 uv = uvs[index]; GL.TexCoord2(uv.x, uv.y); if (settings != null) { var setting = settings[index]; GL.MultiTexCoord2(2, setting.x, setting.y); } if (colors != null) GL.Color(colors[index]); GL.Vertex3(vertex.x, vertex.y, 0); } GL.End(); GL.PopMatrix(); mat.SetTexture("_MainTex", null); } /// Draws a vector sprite using the provided material. /// The sprite to render /// The material used for rendering /// If true, clear the render target before rendering public static void RenderSprite(Sprite sprite, Material mat, bool clear = true) { float spriteWidth = sprite.rect.width; float spriteHeight = sprite.rect.height; float pixelsToUnits = sprite.rect.width / sprite.bounds.size.x; var uvs = sprite.uv; var triangles = sprite.triangles; var pivot = sprite.pivot; var vertices = sprite.vertices.Select(v => new Vector2((v.x * pixelsToUnits + pivot.x)/spriteWidth, (v.y * pixelsToUnits + pivot.y)/spriteHeight) ).ToArray(); Color[] colors = null; if (sprite.HasVertexAttribute(VertexAttribute.Color)) colors = sprite.GetVertexAttribute(VertexAttribute.Color).Select(c => (Color)c).ToArray(); Vector2[] settings = null; if (sprite.HasVertexAttribute(VertexAttribute.TexCoord2)) settings = sprite.GetVertexAttribute(VertexAttribute.TexCoord2).ToArray(); RenderFromArrays(vertices, sprite.triangles, sprite.uv, colors, settings, sprite.texture, mat, clear); } private static Material s_ExpandEdgesMat; /// Renders a vector sprite to Texture2D. /// The sprite to render /// The desired width of the resulting texture /// The desired height of the resulting texture /// The material used to render the sprite /// The number of samples per pixel for anti-aliasing /// When true, expand the edges to avoid a dark banding effect caused by filtering. This is slower to render and uses more graphics memory. /// A Texture2D object containing the rendered vector sprite public static Texture2D RenderSpriteToTexture2D(Sprite sprite, int width, int height, Material mat, int antiAliasing = 1, bool expandEdges = false) { if (width <= 0 || height <= 0) return null; RenderTexture tex = null; var oldActive = RenderTexture.active; var desc = new RenderTextureDescriptor(width, height, RenderTextureFormat.ARGB32, 0) { msaaSamples = 1, sRGB = QualitySettings.activeColorSpace == ColorSpace.Linear }; if (expandEdges) { // Draw the sprite normally to be used as a background, no-antialiasing var normalTex = RenderTexture.GetTemporary(desc); RenderTexture.active = normalTex; RenderSprite(sprite, mat); // Expand the edges and make completely transparent if (s_ExpandEdgesMat == null) { var shader = Shader.Find("Hidden/VectorExpandEdges"); if (shader == null) { #if UNITY_EDITOR // Workaround for case 1167309. // Shader.Find() seems to fail on the package shader when doing a fresh import with a clean Library folder, // but AssetDatabase.LoadAssetAtPath() works fine though. shader = UnityEditor.AssetDatabase.LoadAssetAtPath("Packages/com.unity.vectorgraphics/Runtime/Shaders/VectorExpandEdges.shader"); #else return null; #endif } s_ExpandEdgesMat = new Material(shader); } var expandTex = RenderTexture.GetTemporary(desc); RenderTexture.active = expandTex; GL.Clear(false, true, Color.clear); Graphics.Blit(normalTex, expandTex, s_ExpandEdgesMat, 0); RenderTexture.ReleaseTemporary(normalTex); // Draw the sprite again, but clear with the texture rendered in the previous step, // this will make the bilinear filter to interpolate the colors with values different // than "transparent black", which causes black-ish outlines around the shape. desc.msaaSamples = antiAliasing; tex = RenderTexture.GetTemporary(desc); RenderTexture.active = tex; Graphics.Blit(expandTex, tex); RenderTexture.ReleaseTemporary(expandTex); // Use the expanded texture to clear the buffer RenderTexture.active = tex; RenderSprite(sprite, mat, false); } else { desc.msaaSamples = antiAliasing; tex = RenderTexture.GetTemporary(desc); RenderTexture.active = tex; RenderSprite(sprite, mat); } Texture2D copy = new Texture2D(width, height, TextureFormat.RGBA32, false); copy.hideFlags = HideFlags.HideAndDontSave; copy.ReadPixels(new Rect(0, 0, width, height), 0, 0); copy.Apply(); RenderTexture.active = oldActive; RenderTexture.ReleaseTemporary(tex); return copy; } internal static Vector2 GetPivot(Alignment alignment, Vector2 customPivot, Rect bbox, bool flipYAxis) { switch (alignment) { case Alignment.Center: return new Vector2(0.5f, 0.5f); case Alignment.TopLeft: return new Vector2(0.0f, 1.0f); case Alignment.TopCenter: return new Vector2(0.5f, 1.0f); case Alignment.TopRight: return new Vector2(1.0f, 1.0f); case Alignment.LeftCenter: return new Vector2(0.0f, 0.5f); case Alignment.RightCenter: return new Vector2(1.0f, 0.5f); case Alignment.BottomLeft: return new Vector2(0.0f, 0.0f); case Alignment.BottomCenter: return new Vector2(0.5f, 0.0f); case Alignment.BottomRight: return new Vector2(1.0f, 0.0f); case Alignment.SVGOrigin: { var p = -bbox.position / bbox.size; if (flipYAxis) p.y = 1.0f - p.y; return p; } case Alignment.Custom: return customPivot; } return Vector2.zero; } } }