ro-webgl/Assets/Src/Utils/PolyImage.cs
2021-12-21 09:40:39 +08:00

318 lines
12 KiB
C#

using System;
using System.Collections.Generic;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/PolyImage", 51)]
[DisallowMultipleComponent]
public class PolyImage : Image
{
#region Image.cs
/// Image's dimensions used for drawing. X = left, Y = bottom, Z = right, W = top.
private Vector4 _GetDrawingDimensions(bool shouldPreserveAspect)
{
var padding = overrideSprite == null ? Vector4.zero : Sprites.DataUtility.GetPadding(overrideSprite);
var size = overrideSprite == null ? Vector2.zero : new Vector2(overrideSprite.rect.width, overrideSprite.rect.height);
Rect r = GetPixelAdjustedRect();
// Debug.Log(string.Format("r:{2}, size:{0}, padding:{1}", size, padding, r));
int spriteW = Mathf.RoundToInt(size.x);
int spriteH = Mathf.RoundToInt(size.y);
var v = new Vector4(
padding.x / spriteW,
padding.y / spriteH,
(spriteW - padding.z) / spriteW,
(spriteH - padding.w) / spriteH);
if (shouldPreserveAspect && size.sqrMagnitude > 0.0f)
{
var spriteRatio = size.x / size.y;
var rectRatio = r.width / r.height;
if (spriteRatio > rectRatio)
{
var oldHeight = r.height;
r.height = r.width * (1.0f / spriteRatio);
r.y += (oldHeight - r.height) * rectTransform.pivot.y;
}
else
{
var oldWidth = r.width;
r.width = r.height * spriteRatio;
r.x += (oldWidth - r.width) * rectTransform.pivot.x;
}
}
v = new Vector4(
r.x + r.width * v.x,
r.y + r.height * v.y,
r.x + r.width * v.z,
r.y + r.height * v.w
);
return v;
}
Vector4 _GetAdjustedBorders(Vector4 border, Rect rect)
{
for (int axis = 0; axis <= 1; axis++)
{
// If the rect is smaller than the combined borders, then there's not room for the borders at their normal size.
// In order to avoid artefacts with overlapping borders, we scale the borders down to fit.
float combinedBorders = border[axis] + border[axis + 2];
if (rect.size[axis] < combinedBorders && combinedBorders != 0)
{
float borderScaleRatio = rect.size[axis] / combinedBorders;
border[axis] *= borderScaleRatio;
border[axis + 2] *= borderScaleRatio;
}
}
return border;
}
#endregion
#if UNITY_5_0 || UNITY_5_1
static VertexHelper toFill = new VertexHelper();
protected override void OnFillVBO(List<UIVertex> vbo)
#elif UNITY_5_2
protected override void OnPopulateMesh(Mesh mesh)
#else
protected override void OnPopulateMesh(VertexHelper toFill)
#endif
{
// Tiled & Filled not supported yet
if (overrideSprite == null || type == Type.Tiled || type == Type.Filled)
{
#if UNITY_5_0 || UNITY_5_1
base.OnFillVBO(vbo);
#elif UNITY_5_2
base.OnPopulateMesh(mesh);
#else
base.OnPopulateMesh(toFill);
#endif
return;
}
toFill.Clear();
switch (type)
{
case Type.Simple:
GenerateSimplePolySprite(toFill, preserveAspect);
break;
case Type.Sliced:
GenerateSlicedPolySprite(toFill);
break;
}
#if UNITY_5_0 || UNITY_5_1
toFill.FillVBO(vbo);
#elif UNITY_5_2
toFill.FillMesh(mesh);
#endif
}
void GenerateSimplePolySprite(VertexHelper vh, bool lPreserveAspect)
{
Vector4 v = _GetDrawingDimensions(lPreserveAspect);
Vector4 uv = Sprites.DataUtility.GetOuterUV(overrideSprite);
Color color32 = color;
Vector2[] uvs = overrideSprite.uv;
float invU = 1 / (uv.z - uv.x);
float invV = 1 / (uv.w - uv.y);
for (int i = 0; i < uvs.Length; i++)
{
float u2 = invU * (uvs[i].x - uv.x);
float v2 = invV * (uvs[i].y - uv.y);
float x = u2 * (v.z - v.x) + v.x;
float y = v2 * (v.w - v.y) + v.y;
vh.AddVert(new Vector2(x, y), color32, uvs[i]);
}
ushort[] triangles = overrideSprite.triangles;
for (int i = 0; i < triangles.Length; i += 3)
{
vh.AddTriangle(triangles[i], triangles[i + 1], triangles[i + 2]);
}
}
public static float Cross(Vector2 lhs, Vector2 rhs)
{
return lhs.x * rhs.y - lhs.y * rhs.x;
}
// idea comes from RadialCut
private static List<ushort> LineCut(
List<Vector2> uvs, List<ushort> triangles,
Vector2 start, float angle, Func<Vector2, bool> predict = null)
{
List<ushort> splitTriangles = new List<ushort>();
List<ushort> splitTriCache1 = new List<ushort>();
List<ushort> splitTriCache2 = new List<ushort>();
var offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
for (int i = 0; i < triangles.Count; i += 3)
{
splitTriCache1.Clear();
splitTriCache2.Clear();
for (int j = 0; j < 3; j++)
{
ushort tri1 = triangles[i + j], tri2 = triangles[i + (j + 1) % 3];
Vector2 uv1 = uvs[tri1], uv2 = uvs[tri2];
float sign1 = Cross(offset, uv1 - start), sign2 = Cross(offset, uv2 - start);
if (sign1 <= 0)
splitTriCache1.Add(tri1);
if (sign1 >= 0)
splitTriCache2.Add(tri1);
if (sign1 * sign2 < 0)
{
// Line Intersects!
var diff = uv2 - uv1;
float t1 = -sign1 / Cross(offset, uv2 - uv1);
var p = uv1 + diff * t1;
ushort idx = (ushort)uvs.Count;
uvs.Add(p);
splitTriCache1.Add(idx);
splitTriCache2.Add(idx);
}
}
if (splitTriCache1.Count >= 3)
{
for (int j = 2; j < splitTriCache1.Count; j++)
{
if(predict != null)
{
Vector2 center = (uvs[splitTriCache1[0]] + uvs[splitTriCache1[j - 1]] + uvs[splitTriCache1[j]]) / 3;
if (!predict(center)) continue;
}
splitTriangles.Add(splitTriCache1[0]);
splitTriangles.Add(splitTriCache1[j - 1]);
splitTriangles.Add(splitTriCache1[j]);
}
}
if (splitTriCache2.Count >= 3)
{
for (int j = 2; j < splitTriCache2.Count; j++)
{
if (predict != null)
{
Vector2 center = (uvs[splitTriCache2[0]] + uvs[splitTriCache2[j - 1]] + uvs[splitTriCache2[j]]) / 3;
if (!predict(center)) continue;
}
splitTriangles.Add(splitTriCache2[0]);
splitTriangles.Add(splitTriCache2[j - 1]);
splitTriangles.Add(splitTriCache2[j]);
}
}
}
return splitTriangles;
}
static readonly Vector2[] s_VertScratch = new Vector2[4];
static readonly Vector2[] s_UVScratch = new Vector2[4];
private static int XSlot(float x)
{
for (int idx = 0; idx < 3; idx++)
{
if (s_UVScratch[idx].x < s_UVScratch[idx + 1].x && x <= s_UVScratch[idx + 1].x)
return idx;
}
return 2;
}
private static int YSlot(float y)
{
for (int idx = 0; idx < 3; idx++)
{
if (s_UVScratch[idx].y < s_UVScratch[idx + 1].y && y <= s_UVScratch[idx + 1].y)
return idx;
}
return 2;
}
private void GenerateSlicedPolySprite(VertexHelper toFill)
{
if (!hasBorder)
{
GenerateSimplePolySprite(toFill, false);
return;
}
Vector4 outer = Sprites.DataUtility.GetOuterUV(overrideSprite);
Vector4 inner = Sprites.DataUtility.GetInnerUV(overrideSprite);
Vector4 padding = Sprites.DataUtility.GetPadding(overrideSprite);
Vector4 border = overrideSprite.border;
Rect rect = GetPixelAdjustedRect();
border = _GetAdjustedBorders(border / pixelsPerUnit, rect);
padding = padding / pixelsPerUnit;
s_VertScratch[0] = new Vector2(padding.x, padding.y);
s_VertScratch[3] = new Vector2(rect.width - padding.z, rect.height - padding.w);
s_VertScratch[1].x = border.x;
s_VertScratch[1].y = border.y;
s_VertScratch[2].x = rect.width - border.z;
s_VertScratch[2].y = rect.height - border.w;
for (int i = 0; i < 4; ++i)
{
s_VertScratch[i].x += rect.x;
s_VertScratch[i].y += rect.y;
}
s_UVScratch[0] = new Vector2(outer.x, outer.y);
s_UVScratch[1] = new Vector2(inner.x, inner.y);
s_UVScratch[2] = new Vector2(inner.z, inner.w);
s_UVScratch[3] = new Vector2(outer.z, outer.w);
List<Vector2> uvs = new List<Vector2>(overrideSprite.uv);
List<ushort> triangles = new List<ushort>(overrideSprite.triangles);
var splitedTriangles = LineCut(uvs, triangles, new Vector2(inner.x, inner.y), Mathf.PI / 2);
splitedTriangles = LineCut(uvs, triangles, new Vector2(inner.z, inner.w), 0);
splitedTriangles = LineCut(uvs, triangles, new Vector2(inner.x, inner.y), Mathf.PI / 2);
splitedTriangles = LineCut(uvs, triangles, new Vector2(inner.z, inner.w), 0);
splitedTriangles = LineCut(uvs, triangles, new Vector2(inner.x, inner.y), Mathf.PI / 2);
splitedTriangles = LineCut(uvs, triangles, new Vector2(inner.z, inner.w), 0);
splitedTriangles = LineCut(uvs, triangles, new Vector2(inner.x, inner.y), Mathf.PI / 2);
splitedTriangles = LineCut(uvs, triangles, new Vector2(inner.z, inner.w), 0);
for (int i = 0; i < uvs.Count; i++)
{
int x = XSlot(uvs[i].x);
int y = YSlot(uvs[i].y);
float kX = (uvs[i].x - s_UVScratch[x].x) / (s_UVScratch[x + 1].x - s_UVScratch[x].x);
float kY = (uvs[i].y - s_UVScratch[y].y) / (s_UVScratch[y + 1].y - s_UVScratch[y].y);
Vector2 pos = new Vector2(kX * (s_VertScratch[x + 1].x - s_VertScratch[x].x) + s_VertScratch[x].x,
kY * (s_VertScratch[y + 1].y - s_VertScratch[y].y) + s_VertScratch[y].y);
toFill.AddVert(pos, color, uvs[i]);
}
for (int i = 0; i < splitedTriangles.Count; i += 3)
{
int x = XSlot(uvs[splitedTriangles[i + 0]].x);
int y = YSlot(uvs[splitedTriangles[i + 0]].y);
if (x == 1 && y == 1 && !fillCenter) continue;
toFill.AddTriangle(splitedTriangles[i + 0], splitedTriangles[i + 1], splitedTriangles[i + 2]);
}
}
}
}