318 lines
13 KiB
C#
Raw Permalink Normal View History

2022-09-06 13:47:45 +08:00
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
{
/// <summary>The alignement of the sprite, to determine the location of the pivot.</summary>
public enum Alignment
{
/// <summary>Center alignment.</summary>
Center = 0,
/// <summary>Top-left alignment.</summary>
TopLeft = 1,
/// <summary>Top-center alignment.</summary>
TopCenter = 2,
/// <summary>Top-right alignment.</summary>
TopRight = 3,
/// <summary>Left-center alignment.</summary>
LeftCenter = 4,
/// <summary>Right-center alignment.</summary>
RightCenter = 5,
/// <summary>Bottom-left alignment.</summary>
BottomLeft = 6,
/// <summary>Bottom-center alignment.</summary>
BottomCenter = 7,
/// <summary>Bottom-right alignment.</summary>
BottomRight = 8,
/// <summary>Custom alignment.</summary>
/// <remarks>
/// Uses a custom alignment that will be used when building the sprite using the <see cref="BuildSprite"/> method.
/// </remarks>
Custom = 9,
/// <summary>SVG origin alignment.</summary>
/// <remarks>
/// This will use the origin of the SVG document as the origin of the sprite.
/// </remarks>
SVGOrigin = 10
}
private static void FlipYAxis(IList<Vector2> 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<Vector2> vertices, List<UInt16> 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);
}
/// <summary>Draws a vector sprite using the provided material.</summary>
/// <param name="sprite">The sprite to render</param>
/// <param name="mat">The material used for rendering</param>
/// <param name="clear">If true, clear the render target before rendering</param>
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<Color32>(VertexAttribute.Color).Select(c => (Color)c).ToArray();
Vector2[] settings = null;
if (sprite.HasVertexAttribute(VertexAttribute.TexCoord2))
settings = sprite.GetVertexAttribute<Vector2>(VertexAttribute.TexCoord2).ToArray();
RenderFromArrays(vertices, sprite.triangles, sprite.uv, colors, settings, sprite.texture, mat, clear);
}
private static Material s_ExpandEdgesMat;
/// <summary>Renders a vector sprite to Texture2D.</summary>
/// <param name="sprite">The sprite to render</param>
/// <param name="width">The desired width of the resulting texture</param>
/// <param name="height">The desired height of the resulting texture</param>
/// <param name="mat">The material used to render the sprite</param>
/// <param name="antiAliasing">The number of samples per pixel for anti-aliasing</param>
/// <param name="expandEdges">When true, expand the edges to avoid a dark banding effect caused by filtering. This is slower to render and uses more graphics memory.</param>
/// <returns>A Texture2D object containing the rendered vector sprite</returns>
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<Shader>("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;
}
}
}