Singularity/Library/PackageCache/com.unity.2d.animation@7.0.10/Editor/SpriteOutlineRenderer/SpriteOutlineRenderer.cs
2024-05-06 11:45:45 -07:00

305 lines
12 KiB
C#

using System.Collections.Generic;
using Unity.Mathematics;
using UnityEditor.U2D.Sprites;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class SpriteOutlineRenderer
{
class OutlineRenderTexture
{
public Texture outlineTexture;
public bool dirty;
}
Material m_OutlineMaterial;
Material m_BitMaskMaterial;
Material m_EdgeOutlineMaterial;
Mesh m_CircleMesh;
Dictionary<string, OutlineRenderTexture> m_OutlineTextureCache = new ();
SkinningEvents m_EventSystem;
static readonly int k_OutlineColorProperty = Shader.PropertyToID("_OutlineColor");
static readonly int k_OutlineSizeProperty = Shader.PropertyToID("_OutlineSize");
static readonly int k_AdjustLinearForGammaProperty = Shader.PropertyToID("_AdjustLinearForGamma");
const int k_ReferenceTextureSize = 1024;
public SpriteOutlineRenderer(SkinningEvents eventSystem)
{
m_EdgeOutlineMaterial = new Material(Shader.Find("Hidden/2D-Animation-SpriteEdgeOutline")) { hideFlags = HideFlags.HideAndDontSave };
m_BitMaskMaterial = new Material(Shader.Find("Hidden/2D-Animation-SpriteBitmask")) { hideFlags = HideFlags.HideAndDontSave };
m_OutlineMaterial = new Material(Shader.Find("Hidden/2D-Animation-SpriteOutline")) { hideFlags = HideFlags.HideAndDontSave };
m_EventSystem = eventSystem;
m_EventSystem.meshPreviewChanged.AddListener(OnMeshPreviewChanged);
m_EventSystem.selectedSpriteChanged.AddListener(OnSelectionChanged);
m_CircleMesh = GenerateCircleMesh();
}
public void Dispose()
{
DestroyMaterialsAndMeshes();
DestroyTextures();
m_EventSystem.meshPreviewChanged.RemoveListener(OnMeshPreviewChanged);
m_EventSystem.selectedSpriteChanged.RemoveListener(OnSelectionChanged);
}
internal void RenderSpriteOutline(ISpriteEditor spriteEditor, SpriteCache sprite)
{
if (spriteEditor == null || sprite == null)
return;
if (SelectionOutlineSettings.selectedSpriteOutlineSize < 0.01f || SelectionOutlineSettings.outlineColor.a < 0.01f)
return;
var mesh = GetMesh(sprite);
if (mesh == null)
return;
UnityEngine.Profiling.Profiler.BeginSample("SpriteOutlineRenderer::RenderSpriteOutline");
var vertices = mesh.vertices;
var edges = sprite.GetMesh().edges;
var multMatrix = Handles.matrix * sprite.GetLocalToWorldMatrixFromMode();
var texture = spriteEditor.GetDataProvider<ITextureDataProvider>().texture;
var outlineSize = SelectionOutlineSettings.selectedSpriteOutlineSize;
var outlineColor = SelectionOutlineSettings.outlineColor;
var adjustForGamma = PlayerSettings.colorSpace == ColorSpace.Linear ? 1.0f : 0.0f;
if (edges != null && edges.Length > 0 && vertices.Length > 0)
{
var finalOutlineSize = outlineSize / spriteEditor.zoomLevel;
DrawEdgeOutline(edges, vertices, multMatrix, finalOutlineSize, outlineColor, adjustForGamma);
}
else // Fallback: Draw using the Sobel filter.
{
var finalOutlineSize = Mathf.Max(texture.width, texture.height) * outlineSize / k_ReferenceTextureSize;
DrawMeshOutline(mesh, sprite, multMatrix, finalOutlineSize, outlineColor, adjustForGamma);
}
UnityEngine.Profiling.Profiler.EndSample();
}
void DrawEdgeOutline(int2[] edges, Vector3[] vertices, Matrix4x4 multMatrix, float outlineSize, Color outlineColor, float adjustForGamma)
{
m_EdgeOutlineMaterial.SetColor(k_OutlineColorProperty, outlineColor);
m_EdgeOutlineMaterial.SetFloat(k_AdjustLinearForGammaProperty, adjustForGamma);
m_EdgeOutlineMaterial.SetPass(0);
var edgeCount = edges.Length;
var vertexCount = vertices.Length;
GL.PushMatrix();
GL.MultMatrix(multMatrix);
GL.Begin(GL.QUADS);
for (var i = 0; i < edgeCount; i++)
{
var currentEdge = edges[i];
if (currentEdge.x < 0 || currentEdge.y < 0 || currentEdge.x >= vertexCount || currentEdge.y >= vertexCount)
continue;
var start = vertices[edges[i].x];
var end = vertices[edges[i].y];
var direction = (end - start).normalized;
var right = Vector3.Cross(Vector3.forward, direction) * outlineSize;
GL.Vertex(start - right);
GL.Vertex(start + right);
GL.Vertex(end + right);
GL.Vertex(end - right);
}
GL.End();
GL.PopMatrix();
for (var i = 0; i < edgeCount; i++)
{
var currentEdge = edges[i];
if (currentEdge.x < 0 || currentEdge.y < 0 || currentEdge.x >= vertexCount || currentEdge.y >= vertexCount)
continue;
var start = vertices[edges[i].x];
var end = vertices[edges[i].y];
Graphics.DrawMeshNow(m_CircleMesh, multMatrix * Matrix4x4.TRS(start, Quaternion.identity, Vector3.one * outlineSize));
Graphics.DrawMeshNow(m_CircleMesh, multMatrix * Matrix4x4.TRS(end, Quaternion.identity, Vector3.one * outlineSize));
}
}
void DrawMeshOutline(Mesh mesh, SpriteCache spriteCache, Matrix4x4 multMatrix, float outlineSize, Color outlineColor, float adjustForGamma)
{
TryRegenerateMaskTexture(spriteCache);
m_OutlineMaterial.SetColor(k_OutlineColorProperty, outlineColor);
m_OutlineMaterial.SetFloat(k_AdjustLinearForGammaProperty, adjustForGamma);
m_OutlineMaterial.SetFloat(k_OutlineSizeProperty, outlineSize);
m_OutlineMaterial.SetPass(0);
GL.PushMatrix();
GL.MultMatrix(multMatrix);
var meshBoundsRect = new Rect(mesh.bounds.min.x, mesh.bounds.min.y, mesh.bounds.size.x, mesh.bounds.size.y);
GL.Begin(GL.QUADS);
GL.Color(Color.white);
GL.TexCoord(new Vector3(0, 0, 0));
GL.Vertex3(meshBoundsRect.xMin, meshBoundsRect.yMin, 0);
GL.TexCoord(new Vector3(1, 0, 0));
GL.Vertex3(meshBoundsRect.xMax, meshBoundsRect.yMin, 0);
GL.TexCoord(new Vector3(1, 1, 0));
GL.Vertex3(meshBoundsRect.xMax, meshBoundsRect.yMax, 0);
GL.TexCoord(new Vector3(0, 1, 0));
GL.Vertex3(meshBoundsRect.xMin, meshBoundsRect.yMax, 0);
GL.End();
GL.PopMatrix();
}
Texture GenerateOutlineTexture(SpriteCache spriteCache, RenderTexture reuseRT)
{
if (spriteCache == null)
return null;
var mesh = GetMesh(spriteCache);
if (mesh == null || (int)mesh.bounds.size.x == 0 || (int)mesh.bounds.size.y == 0)
return null;
var bounds = mesh.bounds;
UnityEngine.Profiling.Profiler.BeginSample("SpriteOutlineRenderer::GenerateOutlineTexture");
if (reuseRT == null || reuseRT.width != (int)bounds.size.x || reuseRT.height != (int)bounds.size.y)
{
UnityEngine.Profiling.Profiler.BeginSample("SpriteOutlineRenderer::CreateRT");
if(reuseRT != null)
Object.DestroyImmediate(reuseRT);
reuseRT = new RenderTexture((int)bounds.size.x, (int)bounds.size.y, 24, RenderTextureFormat.ARGBHalf) { filterMode = FilterMode.Bilinear };
UnityEngine.Profiling.Profiler.EndSample();
}
var oldRT = RenderTexture.active;
Graphics.SetRenderTarget(reuseRT);
m_BitMaskMaterial.SetPass(0);
UnityEngine.Profiling.Profiler.BeginSample("SpriteOutlineRenderer::DrawMesh");
GL.Clear(false, true, new Color(0, 0, 0, 0));
GL.PushMatrix();
GL.LoadOrtho();
var h = bounds.size.y * 0.5f;
var w = h * (bounds.size.x / bounds.size.y);
GL.LoadProjectionMatrix(Matrix4x4.Ortho(-w, w, -h, h, -1, 1));
GL.Begin(GL.QUADS);
GL.Color(Color.white);
Graphics.DrawMeshNow(mesh, Matrix4x4.Translate(-bounds.center));
GL.End();
GL.PopMatrix();
Graphics.SetRenderTarget(oldRT);
UnityEngine.Profiling.Profiler.EndSample();
UnityEngine.Profiling.Profiler.EndSample();
return reuseRT;
}
static Mesh GetMesh(SpriteCache sprite)
{
var meshPreview = sprite.GetMeshPreview();
if (meshPreview == null)
return null;
return meshPreview.mesh.vertexCount > 0 ? meshPreview.mesh : meshPreview.defaultMesh;
}
static Mesh GenerateCircleMesh()
{
const int triangleVerts = 9;
var verts = new Vector3[triangleVerts];
for (var i = 1; i < verts.Length; i++)
{
verts[i] = Quaternion.Euler(0, 0, i * 360f / (verts.Length - 1)) * Vector3.up;
}
var indices = new int[(verts.Length - 1) * 3];
var index = 0;
for (var i = 1; i < triangleVerts; i++)
{
indices[index++] = 0;
indices[index++] = i;
indices[index++] = i + 1 < triangleVerts ? i + 1 : 1;
}
return new Mesh { vertices = verts, triangles = indices, hideFlags = HideFlags.HideAndDontSave };
}
void OnMeshPreviewChanged(MeshPreviewCache mesh)
{
AddOrUpdateMaskTexture(mesh.sprite, true);
}
void OnSelectionChanged(SpriteCache spriteCache)
{
AddOrUpdateMaskTexture(spriteCache, false);
}
void DestroyMaterialsAndMeshes()
{
if(m_EdgeOutlineMaterial != null)
Object.DestroyImmediate(m_EdgeOutlineMaterial);
if (m_BitMaskMaterial != null)
Object.DestroyImmediate(m_BitMaskMaterial);
if (m_OutlineMaterial != null)
Object.DestroyImmediate(m_OutlineMaterial);
if(m_CircleMesh != null)
Object.DestroyImmediate(m_CircleMesh);
}
void DestroyTextures()
{
if (m_OutlineTextureCache != null)
{
foreach (var value in m_OutlineTextureCache.Values)
{
if (value != null && value.outlineTexture != null)
Object.DestroyImmediate(value.outlineTexture);
}
m_OutlineTextureCache.Clear();
}
}
void AddOrUpdateMaskTexture(SpriteCache sprite, bool regenerate)
{
if (m_OutlineTextureCache != null && sprite != null)
{
if (!m_OutlineTextureCache.ContainsKey(sprite.id))
m_OutlineTextureCache.Add(sprite.id, new OutlineRenderTexture() {dirty = true});
var outlineTextureCache = m_OutlineTextureCache[sprite.id];
outlineTextureCache.dirty |= regenerate;
}
}
void TryRegenerateMaskTexture(SpriteCache sprite)
{
var selectedSprite = sprite.skinningCache.selectedSprite;
var outlineTextureCache = m_OutlineTextureCache[sprite.id];
if (sprite == selectedSprite)
{
if (outlineTextureCache.dirty || outlineTextureCache.outlineTexture == null)
{
outlineTextureCache.outlineTexture = GenerateOutlineTexture(sprite, (RenderTexture)outlineTextureCache.outlineTexture);
if (outlineTextureCache.outlineTexture != null)
{
outlineTextureCache.outlineTexture.hideFlags = HideFlags.HideAndDontSave;
outlineTextureCache.dirty = false;
}
}
m_OutlineMaterial.mainTexture = outlineTextureCache.outlineTexture;
}
}
}
}