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(); } } }