using System.Runtime.CompilerServices;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using Unity.Profiling;
using UnityEngine.U2D.Common.UTess;
namespace UnityEngine.U2D.Animation
{
[BurstCompile]
internal static class MeshUtilities
{
static readonly ProfilerMarker k_OldOutline = new ProfilerMarker("MeshUtilities.OldOutline");
static readonly ProfilerMarker k_newOutline = new ProfilerMarker("MeshUtilities.NewOutline");
///
/// Get the outline edges from a set of indices.
/// This method expects the index array to be laid out with one triangle for every 3 indices.
/// E.g. triangle 0: index 0 - 2, triangle 1: index 3 - 5, etc.
///
/// Returns a NativeArray of sorted edges. It is up to the caller to dispose this array.
public static NativeArray GetOutlineEdges(in NativeArray indices)
{
k_OldOutline.Begin();
NativeArray sortedEdges;
GetOutlineEdgesFallback(indices, out sortedEdges);
k_OldOutline.End();
return sortedEdges;
}
public static NativeArray GetOutlineEdgesUTess(in NativeArray indices)
{
k_newOutline.Begin();
NativeArray uTessOutput = new NativeArray(indices.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
int uTessLength = GenerateUTessOutline(indices, ref uTessOutput);
NativeArray output = new NativeArray(uTessLength, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
unsafe
{
UnsafeUtility.MemCpy(output.GetUnsafePtr(), uTessOutput.GetUnsafePtr(), uTessLength * UnsafeUtility.SizeOf());
}
k_newOutline.End();
return output;
}
[BurstCompile]
static int GenerateUTessOutline(in NativeArray indices, ref NativeArray outline)
{
// To ensure this function is Burst compiled GenerateOutlineFromTriangleIndices is wrapped within GenerateUTessOutline
return ModuleHandle.GenerateOutlineFromTriangleIndices(indices, ref outline);
}
[BurstCompile]
public static void GetOutlineEdgesFallback(in NativeArray indices, out NativeArray output)
{
UnsafeHashMap edges = new UnsafeHashMap(indices.Length, Allocator.Temp);
for (int i = 0; i < indices.Length; i += 3)
{
ushort i0 = indices[i];
ushort i1 = indices[i + 1];
ushort i2 = indices[i + 2];
AddToEdgeMap(i0, i1, ref edges);
AddToEdgeMap(i1, i2, ref edges);
AddToEdgeMap(i2, i0, ref edges);
}
NativeArray values = edges.GetValueArray(Allocator.Temp);
SortEdges(values, out output);
values.Dispose();
edges.Dispose();
}
[BurstCompile]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void AddToEdgeMap(int x, int y, ref UnsafeHashMap edgeMap)
{
// Use ulong as edge key for hash map (min,max vertex) to avoid struct hash overhead and redundant hash calculations.
int minV = math.min(x, y);
int maxV = math.max(x, y);
ulong key = ((ulong)minV << 32) | (uint)maxV;
if (!edgeMap.Remove(key))
edgeMap[key] = new int2(x, y);
}
[BurstCompile]
static void SortEdges(in NativeArray unsortedEdges, out NativeArray sortedEdges)
{
NativeArray tmpEdges = new NativeArray(unsortedEdges.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
NativeList shapeStartingEdge = new NativeList(1, Allocator.Temp);
UnsafeHashMap edgeMap = new UnsafeHashMap(unsortedEdges.Length, Allocator.Temp);
NativeBitArray usedEdges = new NativeBitArray(unsortedEdges.Length, Allocator.Temp);
int searchStartPosition = 0;
for (int i = 0; i < unsortedEdges.Length; i++)
edgeMap[unsortedEdges[i].x] = i;
bool findStartingEdge = true;
int edgeIndex = -1;
int startingEdge = 0;
for (int i = 0; i < unsortedEdges.Length; i++)
{
if (findStartingEdge)
{
for (int pos = searchStartPosition; pos < unsortedEdges.Length; pos += 64)
{
ulong bits = ~usedEdges.GetBits(pos, math.min(64, unsortedEdges.Length - pos));
if (bits != 0)
{
int bitPosition = math.tzcnt(bits);
edgeIndex = pos + bitPosition;
searchStartPosition = edgeIndex;
break;
}
}
startingEdge = edgeIndex;
findStartingEdge = false;
shapeStartingEdge.Add(i);
}
usedEdges.Set(edgeIndex, true);
tmpEdges[i] = unsortedEdges[edgeIndex];
int nextVertex = unsortedEdges[edgeIndex].y;
edgeIndex = edgeMap[nextVertex];
if (edgeIndex == startingEdge)
findStartingEdge = true;
}
int finalEdgeArrLength = unsortedEdges.Length;
sortedEdges = new NativeArray(finalEdgeArrLength, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
int count = 0;
for (int i = 0; i < shapeStartingEdge.Length; ++i)
{
int edgeStart = shapeStartingEdge[i];
int edgeEnd = (i + 1) == shapeStartingEdge.Length ? tmpEdges.Length : shapeStartingEdge[i + 1];
for (int m = edgeStart; m < edgeEnd; ++m)
sortedEdges[count++] = tmpEdges[m];
}
usedEdges.Dispose();
edgeMap.Dispose();
shapeStartingEdge.Dispose();
tmpEdges.Dispose();
}
}
}