using System; using System.Runtime.InteropServices; using Unity.Collections; using UnityEngine.U2D; using Unity.Collections.LowLevel.Unsafe; using Unity.Mathematics; using Unity.Burst; using Unity.Jobs; #if USING_2DCOMMON using UnityEngine.U2D.Common; #endif namespace UnityEngine.Rendering.Universal { [BurstCompile] internal static class ShadowUtility { internal const int k_AdditionalVerticesPerEdge = 4; internal const int k_VerticesPerTriangle = 3; internal const int k_TrianglesPerEdge = 3; internal const int k_MinimumEdges = 3; internal const int k_SafeSize = 40; public enum ProjectionType { ProjectionNone = -1, ProjectionHard = 0, ProjectionSoftLeft = 1, ProjectionSoftRight = 3, } [Serializable] [StructLayout(LayoutKind.Sequential)] internal struct ShadowMeshVertex { [SerializeField] internal Vector3 position; [SerializeField] internal Vector4 tangent; internal ShadowMeshVertex(ProjectionType inProjectionType, float2 inEdgePosition0, float2 inEdgePosition1) { position.x = inEdgePosition0.x; position.y = inEdgePosition0.y; position.z = 0; tangent.x = (int)inProjectionType; tangent.y = 0; tangent.z = inEdgePosition1.x; tangent.w = inEdgePosition1.y; } } [StructLayout(LayoutKind.Sequential)] internal struct RemappingInfo { public int count; public int index; public int v0Offset; public int v1Offset; public void Initialize() { count = 0; index = -1; v0Offset = 0; v1Offset = 0; } } static VertexAttributeDescriptor[] m_VertexLayout = new VertexAttributeDescriptor[] { new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3), new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4), }; #if UNITY_EDITOR [RuntimeInitializeOnLoadMethod] static void ResetStaticsOnLoad() { m_VertexLayout = new VertexAttributeDescriptor[] { new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3), new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4), }; } #endif [BurstCompile] unsafe static int GetNextShapeStart(int currentShape, int* inShapeStartingEdgePtr, int inShapeStartingEdgeLength, int maxValue) { return ((currentShape + 1 < inShapeStartingEdgeLength) && (inShapeStartingEdgePtr[currentShape + 1] >= 0)) ? inShapeStartingEdgePtr[currentShape + 1] : maxValue; } [BurstCompile] static internal void CalculateProjectionInfo_FromTriangles(ref NativeArray inVertices, ref NativeArray inEdges, ref NativeArray inShapeStartingEdge, ref NativeArray outProjectionInfo) { unsafe { float3* inVerticesPtr = (float3*)inVertices.GetUnsafePtr(); ShadowEdge* inEdgesPtr = (ShadowEdge*)inEdges.GetUnsafePtr(); int* inShapeStartingEdgePtr = (int*)inShapeStartingEdge.GetUnsafePtr(); float2* outProjectionInfoPtr = (float2*)outProjectionInfo.GetUnsafePtr(); int inEdgesLength = inEdges.Length; int inShapeStartingEdgeLength = inShapeStartingEdge.Length; int inVerticesLength = inVertices.Length; int currentShape = 0; int shapeStart = 0; int nextShapeStart = GetNextShapeStart(currentShape, inShapeStartingEdgePtr, inShapeStartingEdgeLength, inEdgesLength); int shapeSize = nextShapeStart; for (int i = 0; i < inEdgesLength; i++) { while (i >= nextShapeStart && currentShape < inShapeStartingEdgeLength - 1) { currentShape++; shapeStart = nextShapeStart; nextShapeStart = GetNextShapeStart(currentShape, inShapeStartingEdgePtr, inShapeStartingEdgeLength, inEdgesLength); shapeSize = nextShapeStart - shapeStart; } int nextEdgeIndex = (i - shapeStart + 1) % shapeSize + shapeStart; int prevEdgeIndex = (i - shapeStart + shapeSize - 1) % shapeSize + shapeStart; int v0 = inEdgesPtr[i].v0; int v1 = inEdgesPtr[i].v1; int prev1 = inEdgesPtr[prevEdgeIndex].v0; int next0 = inEdgesPtr[nextEdgeIndex].v1; float2 startPt = inVerticesPtr[v0].xy; float2 endPt = inVerticesPtr[v1].xy; float2 prevPt = inVerticesPtr[prev1].xy; float2 nextPt = inVerticesPtr[next0].xy; // Original Vertex outProjectionInfoPtr[v0] = endPt; // Hard Shadows int additionalVerticesStart = k_AdditionalVerticesPerEdge * i + inVerticesLength; outProjectionInfoPtr[additionalVerticesStart] = endPt; outProjectionInfoPtr[additionalVerticesStart + 1] = startPt; // Soft Triangles outProjectionInfoPtr[additionalVerticesStart + 2] = prevPt; outProjectionInfoPtr[additionalVerticesStart + 3] = nextPt; } } } [BurstCompile] static internal void CalculateProjectionInfo_FromLines(ref NativeArray inVertices, ref NativeArray inEdges, ref NativeArray inShapeStartingEdge, ref NativeArray outProjectionInfo) { unsafe { float3* inVerticesPtr = (float3*)inVertices.GetUnsafePtr(); ShadowEdge* inEdgesPtr = (ShadowEdge*)inEdges.GetUnsafePtr(); int* inShapeStartingEdgePtr = (int*)inShapeStartingEdge.GetUnsafePtr(); float2* outProjectionInfoPtr = (float2*)outProjectionInfo.GetUnsafePtr(); int inEdgesLength = inEdges.Length; int inShapeStartingEdgeLength = inShapeStartingEdge.Length; int inVerticesLength = inVertices.Length; int currentShape = 0; int shapeStart = 0; int nextShapeStart = GetNextShapeStart(currentShape, inShapeStartingEdgePtr, inShapeStartingEdgeLength, inEdgesLength); int shapeSize = nextShapeStart; for (int i = 0; i < inEdgesLength; i++) { while (i >= nextShapeStart && currentShape < inShapeStartingEdgeLength - 1) { currentShape++; shapeStart = nextShapeStart; nextShapeStart = GetNextShapeStart(currentShape, inShapeStartingEdgePtr, inShapeStartingEdgeLength, inEdgesLength); shapeSize = nextShapeStart - shapeStart; } int nextEdgeIndex = (i - shapeStart + 1) % shapeSize + shapeStart; int prevEdgeIndex = (i - shapeStart + shapeSize - 1) % shapeSize + shapeStart; int v0 = inEdgesPtr[i].v0; int v1 = inEdgesPtr[i].v1; int prev1 = inEdgesPtr[prevEdgeIndex].v0; int next0 = inEdgesPtr[nextEdgeIndex].v1; float2 startPt = inVerticesPtr[v0].xy; float2 endPt = inVerticesPtr[v1].xy; float2 prevPt = inVerticesPtr[prev1].xy; float2 nextPt = inVerticesPtr[next0].xy; // Original Vertex outProjectionInfoPtr[v0] = endPt; // Hard Shadows int additionalVerticesStart = k_AdditionalVerticesPerEdge * i + inVerticesLength; outProjectionInfoPtr[additionalVerticesStart] = endPt; outProjectionInfoPtr[additionalVerticesStart + 1] = startPt; // Soft Triangles outProjectionInfoPtr[additionalVerticesStart + 2] = prevPt; outProjectionInfoPtr[additionalVerticesStart + 3] = nextPt; } } } [BurstCompile] static internal void CalculateVertices(ref NativeArray inVertices, ref NativeArray inEdges, ref NativeArray inEdgeOtherPoints, ref NativeArray outMeshVertices) { unsafe { float3* inVerticesPtr = (float3*)inVertices.GetUnsafePtr(); ShadowEdge* inEdgesPtr = (ShadowEdge*)inEdges.GetUnsafePtr(); float2* inEdgeOtherPointsPtr = (float2*)inEdgeOtherPoints.GetUnsafePtr(); ShadowMeshVertex* outMeshVerticesPtr = (ShadowMeshVertex*)outMeshVertices.GetUnsafePtr(); int inEdgesLength = inEdges.Length; int inVerticesLength = inVertices.Length; for (int i = 0; i < inVerticesLength; i++) { float2 pt = inVerticesPtr[i].xy; outMeshVerticesPtr[i] = new ShadowMeshVertex(ProjectionType.ProjectionNone, pt, inEdgeOtherPointsPtr[i]); } for (int i = 0; i < inEdgesLength; i++) { int v0 = inEdgesPtr[i].v0; int v1 = inEdgesPtr[i].v1; float2 pt0 = inVerticesPtr[v0].xy; float2 pt1 = inVerticesPtr[v1].xy; int additionalVerticesStart = k_AdditionalVerticesPerEdge * i + inVerticesLength; outMeshVerticesPtr[additionalVerticesStart] = new ShadowMeshVertex(ProjectionType.ProjectionHard, pt0, inEdgeOtherPointsPtr[additionalVerticesStart]); outMeshVerticesPtr[additionalVerticesStart + 1] = new ShadowMeshVertex(ProjectionType.ProjectionHard, pt1, inEdgeOtherPointsPtr[additionalVerticesStart + 1]); outMeshVerticesPtr[additionalVerticesStart + 2] = new ShadowMeshVertex(ProjectionType.ProjectionSoftLeft, pt0, inEdgeOtherPointsPtr[additionalVerticesStart + 2]); outMeshVerticesPtr[additionalVerticesStart + 3] = new ShadowMeshVertex(ProjectionType.ProjectionSoftRight, pt0, inEdgeOtherPointsPtr[additionalVerticesStart + 3]); } } } [BurstCompile] static internal void CalculateShadowTriangles_FromTriangles(ref NativeArray inVertices, ref NativeArray inEdges, ref NativeArray inShapeStartingEdge, ref NativeArray outMeshIndices) { unsafe { ShadowEdge* inEdgesPtr = (ShadowEdge*)inEdges.GetUnsafePtr(); int* inShapeStartingEdgePtr = (int*)inShapeStartingEdge.GetUnsafePtr(); int* outMeshIndicesPtr = (int*)outMeshIndices.GetUnsafePtr(); int inEdgesLength = inEdges.Length; int inShapeStartingEdgeLength = inShapeStartingEdge.Length; int inVerticesLength = inVertices.Length; int meshIndex = 0; for (int shapeIndex = 0; shapeIndex < inShapeStartingEdgeLength; shapeIndex++) { int startingIndex = inShapeStartingEdgePtr[shapeIndex]; if (startingIndex < 0) return; int endIndex = inEdgesLength; if ((shapeIndex + 1) < inShapeStartingEdgeLength && inShapeStartingEdgePtr[shapeIndex + 1] > -1) endIndex = inShapeStartingEdgePtr[shapeIndex + 1]; // Hard Shadow Geometry - optimized and unrolled for (int i = startingIndex; i < endIndex; i++) { int v0 = inEdgesPtr[i].v0; int v1 = inEdgesPtr[i].v1; int additionalVerticesStart = (k_AdditionalVerticesPerEdge * i) + inVerticesLength; int av1 = additionalVerticesStart + 1; // Unroll the degenerate rectangle writes outMeshIndicesPtr[meshIndex] = v0; outMeshIndicesPtr[meshIndex + 1] = additionalVerticesStart; outMeshIndicesPtr[meshIndex + 2] = av1; outMeshIndicesPtr[meshIndex + 3] = av1; outMeshIndicesPtr[meshIndex + 4] = v1; outMeshIndicesPtr[meshIndex + 5] = v0; meshIndex += 6; } // Soft Shadow Geometry - unrolled for (int i = startingIndex; i < endIndex; i++) { int v0 = inEdgesPtr[i].v0; int additionalVerticesStart = (k_AdditionalVerticesPerEdge * i) + inVerticesLength; outMeshIndicesPtr[meshIndex] = v0; outMeshIndicesPtr[meshIndex + 1] = additionalVerticesStart + 2; outMeshIndicesPtr[meshIndex + 2] = additionalVerticesStart + 3; meshIndex += 3; } } } } [BurstCompile] static internal void CalculateShadowTriangles_FromLines(ref NativeArray inVertices, ref NativeArray inEdges, ref NativeArray outMeshIndices) { unsafe { ShadowEdge* inEdgesPtr = (ShadowEdge*)inEdges.GetUnsafePtr(); int* outMeshIndicesPtr = (int*)outMeshIndices.GetUnsafePtr(); int inEdgesLength = inEdges.Length; int inVerticesLength = inVertices.Length; int meshIndex = 0; // Hard Shadow Geometry - optimized and unrolled for (int i = 0; i < inEdgesLength; i++) { int v0 = inEdgesPtr[i].v0; int v1 = inEdgesPtr[i].v1; int additionalVerticesStart = (k_AdditionalVerticesPerEdge * i) + inVerticesLength; int av1 = additionalVerticesStart + 1; // Unroll the degenerate rectangle writes outMeshIndicesPtr[meshIndex] = v0; outMeshIndicesPtr[meshIndex + 1] = additionalVerticesStart; outMeshIndicesPtr[meshIndex + 2] = av1; outMeshIndicesPtr[meshIndex + 3] = av1; outMeshIndicesPtr[meshIndex + 4] = v1; outMeshIndicesPtr[meshIndex + 5] = v0; meshIndex += 6; } // Soft Shadow Geometry - unrolled for (int i = 0; i < inEdgesLength; i++) { int v0 = inEdgesPtr[i].v0; int additionalVerticesStart = (k_AdditionalVerticesPerEdge * i) + inVerticesLength; outMeshIndicesPtr[meshIndex] = v0; outMeshIndicesPtr[meshIndex + 1] = additionalVerticesStart + 2; outMeshIndicesPtr[meshIndex + 2] = additionalVerticesStart + 3; meshIndex += 3; } } } [BurstCompile] static internal void CalculateLocalBounds(ref NativeArray inVertices, out Bounds retBounds) { if (inVertices.Length <= 0) { retBounds = default; retBounds.center = Vector3.zero; retBounds.size = Vector3.zero; return; } unsafe { float3* inVerticesPtr = (float3*)inVertices.GetUnsafePtr(); int inVerticesLength = inVertices.Length; // Initialize with first vertex instead of infinity float3 first = inVerticesPtr[0]; float minX = first.x; float minY = first.y; float maxX = first.x; float maxY = first.y; // Start from 1 since we used 0 for initialization for (int i = 1; i < inVerticesLength; i++) { float3 v = inVerticesPtr[i]; minX = math.min(minX, v.x); minY = math.min(minY, v.y); maxX = math.max(maxX, v.x); maxY = math.max(maxY, v.y); } retBounds = default; retBounds.SetMinMax( new Vector3(minX, minY, 0), new Vector3(maxX, maxY, 0)); } } [BurstCompile] static void GenerateInteriorMesh(ref NativeArray inVertices, ref NativeArray inIndices, ref NativeArray inEdges, out NativeArray outVertices, out NativeArray outIndices, out int outStartIndex, out int outIndexCount) { int inEdgeCount = inEdges.Length; // Do tessellation NativeArray tessInEdges = new NativeArray(inEdgeCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); NativeArray tessInVertices = new NativeArray(inEdgeCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); for (int i = 0; i < inEdgeCount; i++) { int2 edge = new int2(inEdges[i].v0, inEdges[i].v1); tessInEdges[i] = edge; int index = edge.x; tessInVertices[index] = new float2(inVertices[index].position.x, inVertices[index].position.y); } NativeArray tessOutIndices = new NativeArray(tessInVertices.Length * 8, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); NativeArray tessOutVertices = new NativeArray(tessInVertices.Length * 4, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); NativeArray tessOutEdges = new NativeArray(tessInEdges.Length * 4, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); int tessOutVertexCount = 0; int tessOutIndexCount = 0; #if USING_2DCOMMON int tessOutEdgeCount = 0; UnityEngine.U2D.Common.UTess.ModuleHandle.Tessellate(Allocator.Temp, tessInVertices, tessInEdges, ref tessOutVertices, out tessOutVertexCount, ref tessOutIndices, out tessOutIndexCount, ref tessOutEdges, out tessOutEdgeCount, false); #endif int indexOffset = inIndices.Length; int vertexOffset = inVertices.Length; int totalOutVertices = tessOutVertexCount + inVertices.Length; int totalOutIndices = tessOutIndexCount + inIndices.Length; outVertices = new NativeArray(totalOutVertices, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); outIndices = new NativeArray(totalOutIndices, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); // Copy vertices using MemCpy for better performance unsafe { UnsafeUtility.MemCpy( outVertices.GetUnsafePtr(), inVertices.GetUnsafePtr(), inVertices.Length * UnsafeUtility.SizeOf()); } // Generate new vertices from tessellation for (int i = 0; i < tessOutVertexCount; i++) { float2 tessVertex = tessOutVertices[i]; ShadowMeshVertex vertex = new ShadowMeshVertex(ProjectionType.ProjectionNone, tessVertex, float2.zero); outVertices[i + vertexOffset] = vertex; } // Copy indices using MemCpy unsafe { UnsafeUtility.MemCpy( outIndices.GetUnsafePtr(), inIndices.GetUnsafePtr(), inIndices.Length * UnsafeUtility.SizeOf()); } // Copy and remap indices for (int i = 0; i < tessOutIndexCount; i++) { outIndices[i + indexOffset] = tessOutIndices[i] + vertexOffset; } outStartIndex = indexOffset; outIndexCount = tessOutIndexCount; tessInEdges.Dispose(); tessInVertices.Dispose(); tessOutIndices.Dispose(); tessOutVertices.Dispose(); tessOutEdges.Dispose(); } // NON-BURST WRAPPER - Mesh operations are not Burst compatible static public void GenerateShadowMesh(ref Mesh mesh, NativeArray inVertices, NativeArray inIndices) { if (mesh == null) mesh = new Mesh(); if (inVertices.IsCreated && inIndices.IsCreated) { // Set the mesh data mesh.SetVertexBufferParams(inVertices.Length, m_VertexLayout); mesh.SetVertexBufferData(inVertices, 0, 0, inVertices.Length); mesh.SetIndexBufferParams(inIndices.Length, IndexFormat.UInt32); mesh.SetIndexBufferData(inIndices, 0, 0, inIndices.Length); mesh.SetSubMesh(0, new SubMeshDescriptor(0, inIndices.Length)); mesh.subMeshCount = 1; } else { mesh.Clear(); } } // BURST-COMPATIBLE internal function - Triangle version [BurstCompile] static void GenerateShadowGeometry_Internal_FromTriangles( ref NativeArray inVertices, ref NativeArray inEdges, ref NativeArray inShapeStartingEdge, bool fill, out NativeArray newOutVertices, out NativeArray newOutIndices, out Bounds retLocalBound) { // Setup our buffers int meshVertexCount = inVertices.Length + k_AdditionalVerticesPerEdge * inEdges.Length; int meshIndexCount = inEdges.Length * k_VerticesPerTriangle * k_TrianglesPerEdge; NativeArray meshProjectionInfo = new NativeArray(meshVertexCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); NativeArray meshIndices = new NativeArray(meshIndexCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); NativeArray meshVertices = new NativeArray(meshVertexCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); CalculateProjectionInfo_FromTriangles(ref inVertices, ref inEdges, ref inShapeStartingEdge, ref meshProjectionInfo); CalculateVertices(ref inVertices, ref inEdges, ref meshProjectionInfo, ref meshVertices); CalculateShadowTriangles_FromTriangles(ref inVertices, ref inEdges, ref inShapeStartingEdge, ref meshIndices); NativeArray finalVertices; NativeArray finalIndices; int fillSubmeshStartIndex = 0; int fillSubmeshIndexCount = 0; if (fill) { GenerateInteriorMesh(ref meshVertices, ref meshIndices, ref inEdges, out finalVertices, out finalIndices, out fillSubmeshStartIndex, out fillSubmeshIndexCount); meshVertices.Dispose(); meshIndices.Dispose(); } else { finalVertices = meshVertices; finalIndices = meshIndices; } newOutVertices = finalVertices; newOutIndices = finalIndices; meshProjectionInfo.Dispose(); CalculateLocalBounds(ref inVertices, out retLocalBound); } // BURST-COMPATIBLE internal function - Line version [BurstCompile] static void GenerateShadowGeometry_Internal_FromLines( ref NativeArray inVertices, ref NativeArray inEdges, ref NativeArray inShapeStartingEdge, bool fill, out NativeArray newOutVertices, out NativeArray newOutIndices, out Bounds retLocalBound) { // Setup our buffers int meshVertexCount = inVertices.Length + k_AdditionalVerticesPerEdge * inEdges.Length; int meshIndexCount = inEdges.Length * k_VerticesPerTriangle * k_TrianglesPerEdge; NativeArray meshProjectionInfo = new NativeArray(meshVertexCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); NativeArray meshIndices = new NativeArray(meshIndexCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); NativeArray meshVertices = new NativeArray(meshVertexCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); CalculateProjectionInfo_FromLines(ref inVertices, ref inEdges, ref inShapeStartingEdge, ref meshProjectionInfo); CalculateVertices(ref inVertices, ref inEdges, ref meshProjectionInfo, ref meshVertices); CalculateShadowTriangles_FromLines(ref inVertices, ref inEdges, ref meshIndices); NativeArray finalVertices; NativeArray finalIndices; int fillSubmeshStartIndex = 0; int fillSubmeshIndexCount = 0; if (fill) { GenerateInteriorMesh(ref meshVertices, ref meshIndices, ref inEdges, out finalVertices, out finalIndices, out fillSubmeshStartIndex, out fillSubmeshIndexCount); meshVertices.Dispose(); meshIndices.Dispose(); } else { finalVertices = meshVertices; finalIndices = meshIndices; } newOutVertices = finalVertices; newOutIndices = finalIndices; meshProjectionInfo.Dispose(); CalculateLocalBounds(ref inVertices, out retLocalBound); } // NON-BURST WRAPPER - Handles disposal of old arrays - Triangle version static public Bounds GenerateShadowGeometry( ref NativeArray outVertices, ref NativeArray outIndices, NativeArray inVertices, NativeArray inEdges, NativeArray inShapeStartingEdge, bool fill, ShadowShape2D.OutlineTopology topology) { // Dispose old arrays if they exist if (outIndices.IsCreated) outIndices.Dispose(); if (outVertices.IsCreated) outVertices.Dispose(); // Reinterpret Vector3 array as float3 (same memory layout) NativeArray inVerticesFloat3 = inVertices.Reinterpret(); // Call Burst-compatible function NativeArray newOutVertices; NativeArray newOutIndices; Bounds retLocalBound; if (topology == ShadowShape2D.OutlineTopology.Triangles) { GenerateShadowGeometry_Internal_FromTriangles( ref inVerticesFloat3, ref inEdges, ref inShapeStartingEdge, fill, out newOutVertices, out newOutIndices, out retLocalBound); } else { GenerateShadowGeometry_Internal_FromLines( ref inVerticesFloat3, ref inEdges, ref inShapeStartingEdge, fill, out newOutVertices, out newOutIndices, out retLocalBound); } // Assign new arrays to ref parameters outVertices = newOutVertices; outIndices = newOutIndices; return retLocalBound; } [BurstCompile] static public void CalculateEdgesFromLines(ref NativeArray indices, out NativeArray outEdges, out NativeArray outShapeStartingEdge, out NativeArray outShapeIsClosedArray) { unsafe { int numOfEdges = indices.Length >> 1; NativeArray tempShapeStartIndices = new NativeArray(numOfEdges, Allocator.Temp, NativeArrayOptions.UninitializedMemory); NativeArray tempShapeIsClosedArray = new NativeArray(numOfEdges, Allocator.Temp, NativeArrayOptions.UninitializedMemory); int* indicesPtr = (int*)indices.GetUnsafePtr(); int* tempShapeStartIndicesPtr = (int*)tempShapeStartIndices.GetUnsafePtr(); bool* tempShapeIsClosedArrayPtr = (bool*)tempShapeIsClosedArray.GetUnsafePtr(); int indicesLength = indices.Length; // Find the shape starting indices and allow contraction int shapeCount = 0; int shapeStart = indicesPtr[0]; int lastIndex = indicesPtr[0]; bool closedShapeFound = false; tempShapeStartIndicesPtr[0] = 0; for (int i = 0; i < indicesLength; i += 2) { if (closedShapeFound) { shapeStart = indicesPtr[i]; tempShapeIsClosedArrayPtr[shapeCount] = true; tempShapeStartIndicesPtr[++shapeCount] = i >> 1; closedShapeFound = false; } else if (indicesPtr[i] != lastIndex) { tempShapeIsClosedArrayPtr[shapeCount] = false; tempShapeStartIndicesPtr[++shapeCount] = i >> 1; shapeStart = indicesPtr[i]; } if (shapeStart == indicesPtr[i + 1]) closedShapeFound = true; lastIndex = indicesPtr[i + 1]; } tempShapeIsClosedArrayPtr[shapeCount++] = closedShapeFound; // Copy the our data to a smaller array outShapeStartingEdge = new NativeArray(shapeCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); outShapeIsClosedArray = new NativeArray(shapeCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); // Use MemCpy for faster copy UnsafeUtility.MemCpy( outShapeStartingEdge.GetUnsafePtr(), tempShapeStartIndices.GetUnsafePtr(), shapeCount * UnsafeUtility.SizeOf()); UnsafeUtility.MemCpy( outShapeIsClosedArray.GetUnsafePtr(), tempShapeIsClosedArray.GetUnsafePtr(), shapeCount * UnsafeUtility.SizeOf()); tempShapeStartIndices.Dispose(); tempShapeIsClosedArray.Dispose(); // Add edges outEdges = new NativeArray(numOfEdges, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); ShadowEdge* outEdgesPtr = (ShadowEdge*)outEdges.GetUnsafePtr(); for (int i = 0; i < numOfEdges; i++) { int indicesIndex = i << 1; int v0Index = indicesPtr[indicesIndex]; int v1Index = indicesPtr[indicesIndex + 1]; outEdgesPtr[i] = new ShadowEdge(v0Index, v1Index); } } } [BurstCompile] static internal void GetVertexReferenceStats(ref NativeArray vertices, ref NativeArray edges, int vertexCount, out bool hasReusedVertices, out int newVertexCount, out NativeArray remappingInfo) { unsafe { int edgeCount = edges.Length; newVertexCount = 0; hasReusedVertices = false; remappingInfo = new NativeArray(vertexCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); RemappingInfo* remappingInfoPtr = (RemappingInfo*)remappingInfo.GetUnsafePtr(); ShadowEdge* edgesPtr = (ShadowEdge*)edges.GetUnsafePtr(); // Clear the remapping info for (int i = 0; i < vertexCount; i++) remappingInfoPtr[i].Initialize(); // Process v0 for (int i = 0; i < edgeCount; i++) { int v0 = edgesPtr[i].v0; remappingInfoPtr[v0].count = remappingInfoPtr[v0].count + 1; if (remappingInfoPtr[v0].count > 1) hasReusedVertices = true; newVertexCount++; } // Process v1 for (int i = 0; i < edgeCount; i++) { int v1 = edgesPtr[i].v1; if (remappingInfoPtr[v1].count == 0) { remappingInfoPtr[v1].count = 1; newVertexCount++; } } // Find the starts of the new indices int startPos = 0; for (int i = 0; i < vertexCount; i++) { if (remappingInfoPtr[i].count > 0) { remappingInfoPtr[i].index = startPos; startPos += remappingInfoPtr[i].count; } } } } [BurstCompile] static public bool IsTriangleReversed(ref NativeArray vertices, int idx0, int idx1, int idx2) { float3 v0 = vertices[idx0]; float3 v1 = vertices[idx1]; float3 v2 = vertices[idx2]; float twiceArea = (v0.x * v1.y + v1.x * v2.y + v2.x * v0.y) - (v0.y * v1.x + v1.y * v2.x + v2.y * v0.x); return math.sign(twiceArea) >= 0; } [BurstCompile] static public void CalculateEdgesFromTriangles(ref NativeArray vertices, ref NativeArray indices, bool duplicatesVertices, out NativeArray newVertices, out NativeArray outEdges, out NativeArray outShapeStartingEdge, out NativeArray outShapeIsClosedArray) { unsafe { // Run clipper to calculate edges Clipper2D.Solution solution = new Clipper2D.Solution(); Clipper2D.ExecuteArguments executeArguments = new Clipper2D.ExecuteArguments(Clipper2D.InitOptions.ioDefault, Clipper2D.ClipType.ctUnion); int triangleCount = indices.Length / 3; NativeArray points = new NativeArray(indices.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); NativeArray pathSizes = new NativeArray(triangleCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); NativeArray pathArguments = new NativeArray(triangleCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); // Pointers - cast between Vector3/float3 and Vector2/float2 since they have same layout float2* pointsPtr = (float2*)points.GetUnsafePtr(); int* pathSizesPtr = (int*)pathSizes.GetUnsafePtr(); Clipper2D.PathArguments* pathArgumentsPtr = (Clipper2D.PathArguments*)pathArguments.GetUnsafePtr(); float3* verticesPtr = (float3*)vertices.GetUnsafePtr(); // Copy input data for Clipper2D.Execute Clipper2D.PathArguments sharedPathArg = new Clipper2D.PathArguments(Clipper2D.PolyType.ptSubject, true); for (int i = 0; i < triangleCount; i++) { pathSizesPtr[i] = 3; pathArgumentsPtr[i] = sharedPathArg; int pointOffset = 3 * i; // Use .xy swizzle to extract float2 from float3 pointsPtr[pointOffset] = verticesPtr[indices[pointOffset]].xy; pointsPtr[pointOffset + 1] = verticesPtr[indices[pointOffset + 1]].xy; pointsPtr[pointOffset + 2] = verticesPtr[indices[pointOffset + 2]].xy; } Clipper2D.Execute(ref solution, points, pathSizes, pathArguments, executeArguments, Allocator.Persistent); // Cleanup execute inputs because we have necessary data in our solution points.Dispose(); pathSizes.Dispose(); pathArguments.Dispose(); // Copy solution to outputs int pointLen = solution.points.Length; int shapeCount = solution.pathSizes.Length; newVertices = new NativeArray(pointLen, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); outEdges = new NativeArray(pointLen, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); outShapeStartingEdge = new NativeArray(shapeCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); outShapeIsClosedArray = new NativeArray(shapeCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); int* solutionPathSizesPtr = (int*)solution.pathSizes.GetUnsafePtr(); float2* solutionPointsPtr = (float2*)solution.points.GetUnsafePtr(); float3* newVerticesPtr = (float3*)newVertices.GetUnsafePtr(); ShadowEdge* outEdgesPtr = (ShadowEdge*)outEdges.GetUnsafePtr(); int* outShapeStartingEdgePtr = (int*)outShapeStartingEdge.GetUnsafePtr(); bool* outShapeIsClosedArrayPtr = (bool*)outShapeIsClosedArray.GetUnsafePtr(); // Copy output data from the solution int nextStart = 0; for (int shapeIndex = 0; shapeIndex < shapeCount; shapeIndex++) { int curStart = nextStart; int curPathSize = solutionPathSizesPtr[shapeIndex]; outShapeStartingEdgePtr[shapeIndex] = nextStart; nextStart += curPathSize; int previousVertex = nextStart - 1; for (int pointIndex = curStart; pointIndex < nextStart; pointIndex++) { // Convert float2 to float3 with z=0 newVerticesPtr[pointIndex] = new float3(solutionPointsPtr[pointIndex], 0); outEdgesPtr[pointIndex] = new ShadowEdge(previousVertex, pointIndex); previousVertex = pointIndex; } outShapeIsClosedArrayPtr[shapeIndex] = true; } solution.Dispose(); } } [BurstCompile] static public void ReverseWindingOrder(ref NativeArray inShapeStartingEdge, ref NativeArray inOutSortedEdges) { for (int shapeIndex = 0; shapeIndex < inShapeStartingEdge.Length; shapeIndex++) { int startingIndex = inShapeStartingEdge[shapeIndex]; if (startingIndex < 0) return; int endIndex = inOutSortedEdges.Length; if ((shapeIndex + 1) < inShapeStartingEdge.Length && inShapeStartingEdge[shapeIndex + 1] > -1) endIndex = inShapeStartingEdge[shapeIndex + 1]; // Reverse the winding order int count = (endIndex - startingIndex); int halfCount = count >> 1; for (int i = 0; i < halfCount; i++) { int edgeAIndex = startingIndex + i; int edgeBIndex = startingIndex + count - 1 - i; ShadowEdge edgeA = inOutSortedEdges[edgeAIndex]; ShadowEdge edgeB = inOutSortedEdges[edgeBIndex]; edgeA.Reverse(); edgeB.Reverse(); inOutSortedEdges[edgeAIndex] = edgeB; inOutSortedEdges[edgeBIndex] = edgeA; } // Handle odd count if ((count & 1) == 1) { int edgeAIndex = startingIndex + halfCount; ShadowEdge edgeA = inOutSortedEdges[edgeAIndex]; edgeA.Reverse(); inOutSortedEdges[edgeAIndex] = edgeA; } } } [BurstCompile] static int GetClosedPathCount(ref NativeArray inShapeStartingEdge, ref NativeArray inShapeIsClosedArray) { int count = 0; for (int i = 0; i < inShapeStartingEdge.Length; i++) { if (inShapeStartingEdge[i] < 0) break; count++; } return count; } [BurstCompile] static void GetPathInfo(ref NativeArray inEdges, ref NativeArray inShapeStartingEdge, ref NativeArray inShapeIsClosedArray, out int closedPathArrayCount, out int closedPathsCount, out int openPathArrayCount, out int openPathsCount) { closedPathArrayCount = 0; openPathArrayCount = 0; closedPathsCount = 0; openPathsCount = 0; for (int i = 0; i < inShapeStartingEdge.Length; i++) { // If this shape starting edge is invalid stop if (inShapeStartingEdge[i] < 0) break; int start = inShapeStartingEdge[i]; int end = (i < (inShapeStartingEdge.Length - 1)) && (inShapeStartingEdge[i + 1] != -1) ? inShapeStartingEdge[i + 1] : inEdges.Length; int edges = end - start; if (inShapeIsClosedArray[i]) { closedPathArrayCount += edges + 1; closedPathsCount++; } else { openPathArrayCount += edges + 1; openPathsCount++; } } } [BurstCompile] static public void ClipEdges(ref NativeArray inVertices, ref NativeArray inEdges, ref NativeArray inShapeStartingEdge, ref NativeArray inShapeIsClosedArray, float contractEdge, out NativeArray outVertices, out NativeArray outEdges, out NativeArray outShapeStartingEdge) { unsafe { Allocator k_ClippingAllocator = Allocator.Persistent; int k_Precision = 65536; int closedPathCount; int closedPathArrayCount; int openPathCount; int openPathArrayCount; GetPathInfo(ref inEdges, ref inShapeStartingEdge, ref inShapeIsClosedArray, out closedPathArrayCount, out closedPathCount, out openPathArrayCount, out openPathCount); NativeArray clipperPathArguments = new NativeArray(closedPathCount, k_ClippingAllocator, NativeArrayOptions.ClearMemory); NativeArray closedPathSizes = new NativeArray(closedPathCount, k_ClippingAllocator, NativeArrayOptions.UninitializedMemory); NativeArray closedPath = new NativeArray(closedPathArrayCount, k_ClippingAllocator, NativeArrayOptions.UninitializedMemory); NativeArray openPathSizes = new NativeArray(openPathCount, k_ClippingAllocator, NativeArrayOptions.UninitializedMemory); NativeArray openPath = new NativeArray(openPathArrayCount, k_ClippingAllocator, NativeArrayOptions.UninitializedMemory); Clipper2D.PathArguments* clipperPathArgumentsPtr = (Clipper2D.PathArguments*)clipperPathArguments.GetUnsafePtr(); int* closedPathSizesPtr = (int*)closedPathSizes.GetUnsafePtr(); float2* closedPathPtr = (float2*)closedPath.GetUnsafePtr(); int* openPathSizesPtr = (int*)openPathSizes.GetUnsafePtr(); float2* openPathPtr = (float2*)openPath.GetUnsafePtr(); int* inShapeStartingEdgePtr = (int*)inShapeStartingEdge.GetUnsafePtr(); bool* inShapeIsClosedArrayPtr = (bool*)inShapeIsClosedArray.GetUnsafePtr(); float3* inVerticesPtr = (float3*)inVertices.GetUnsafePtr(); ShadowEdge* inEdgesPtr = (ShadowEdge*)inEdges.GetUnsafePtr(); int inEdgesLength = inEdges.Length; Vector3 tmpVec3 = Vector3.zero; // Seperate out our closed and open shapes. Closed shapes will go through clipper. Open shapes will just be copied. int closedPathArrayIndex = 0; int closedPathSizesIndex = 0; int openPathArrayIndex = 0; int openPathSizesIndex = 0; int totalPathCount = closedPathCount + openPathCount; for (int shapeStartIndex = 0; (shapeStartIndex < totalPathCount); shapeStartIndex++) { int currentShapeStart = inShapeStartingEdgePtr[shapeStartIndex]; int nextShapeStart = (shapeStartIndex + 1) < (totalPathCount) ? inShapeStartingEdgePtr[shapeStartIndex + 1] : inEdgesLength; int numberOfEdges = nextShapeStart - currentShapeStart; if (inShapeIsClosedArrayPtr[shapeStartIndex]) { closedPathSizesPtr[closedPathSizesIndex] = numberOfEdges + 1; clipperPathArgumentsPtr[closedPathSizesIndex] = new Clipper2D.PathArguments(Clipper2D.PolyType.ptSubject, true); closedPathSizesIndex++; for (int i = 0; i < numberOfEdges; i++) { // Use .xy swizzle on float3 closedPathPtr[closedPathArrayIndex++] = inVerticesPtr[inEdgesPtr[i + currentShapeStart].v0].xy; } closedPathPtr[closedPathArrayIndex++] = inVerticesPtr[inEdgesPtr[numberOfEdges + currentShapeStart - 1].v1].xy; } else { openPathSizesPtr[openPathSizesIndex++] = numberOfEdges + 1; for (int i = 0; i < numberOfEdges; i++) { openPathPtr[openPathArrayIndex++] = inVerticesPtr[inEdgesPtr[i + currentShapeStart].v0].xy; } openPathPtr[openPathArrayIndex++] = inVerticesPtr[inEdgesPtr[numberOfEdges + currentShapeStart - 1].v1].xy; } } NativeArray clipperOffsetPath = closedPath; NativeArray clipperOffsetPathSizes = closedPathSizes; Clipper2D.Solution clipperSolution = new Clipper2D.Solution(); if (closedPathSizes.Length > 1) { Clipper2D.ExecuteArguments executeArguments = new Clipper2D.ExecuteArguments(); executeArguments.clipType = Clipper2D.ClipType.ctUnion; executeArguments.clipFillType = Clipper2D.PolyFillType.pftEvenOdd; executeArguments.subjFillType = Clipper2D.PolyFillType.pftEvenOdd; executeArguments.strictlySimple = false; executeArguments.preserveColinear = false; Clipper2D.Execute(ref clipperSolution, closedPath, closedPathSizes, clipperPathArguments, executeArguments, k_ClippingAllocator, inIntScale: k_Precision, useRounding: true); clipperOffsetPath = clipperSolution.points; clipperOffsetPathSizes = clipperSolution.pathSizes; } ClipperOffset2D.Solution offsetSolution = new ClipperOffset2D.Solution(); NativeArray offsetPathArguments = new NativeArray(clipperOffsetPathSizes.Length, k_ClippingAllocator, NativeArrayOptions.ClearMemory); ClipperOffset2D.Execute(ref offsetSolution, clipperOffsetPath, clipperOffsetPathSizes, offsetPathArguments, k_ClippingAllocator, -contractEdge, inIntScale: k_Precision); if (offsetSolution.pathSizes.Length > 0 || openPathCount > 0) { int vertexPos = 0; int solutionPathLens = offsetSolution.pathSizes.Length + openPathCount; outVertices = new NativeArray(offsetSolution.points.Length + openPathArrayCount, k_ClippingAllocator, NativeArrayOptions.UninitializedMemory); // Fix: openPathArrayCount is vertex count for open paths, but open paths have (vertices - 1) edges // Closed paths from offsetSolution have edges == vertices, but open paths have edges = vertices - 1 outEdges = new NativeArray(offsetSolution.points.Length + openPathArrayCount - openPathCount, k_ClippingAllocator, NativeArrayOptions.UninitializedMemory); outShapeStartingEdge = new NativeArray(solutionPathLens, k_ClippingAllocator, NativeArrayOptions.UninitializedMemory); float3* outVerticesPtr = (float3*)outVertices.GetUnsafePtr(); ShadowEdge* outEdgesPtr = (ShadowEdge*)outEdges.GetUnsafePtr(); int* outShapeStartingEdgePtr = (int*)outShapeStartingEdge.GetUnsafePtr(); float2* offsetSolutionPointsPtr = (float2*)offsetSolution.points.GetUnsafePtr(); int offsetSolutionPointsLength = offsetSolution.points.Length; int* offsetSolutionPathSizesPtr = (int*)offsetSolution.pathSizes.GetUnsafePtr(); int offsetSolutionPathSizesLength = offsetSolution.pathSizes.Length; // Copy with float2 to float3 conversion for (int i = 0; i < offsetSolutionPointsLength; i++) { outVerticesPtr[vertexPos++] = new float3(offsetSolutionPointsPtr[i], 0); } int start = 0; for (int pathSizeIndex = 0; pathSizeIndex < offsetSolutionPathSizesLength; pathSizeIndex++) { int pathSize = offsetSolutionPathSizesPtr[pathSizeIndex]; int end = start + pathSize; outShapeStartingEdgePtr[pathSizeIndex] = start; for (int shapeIndex = 0; shapeIndex < pathSize; shapeIndex++) { ShadowEdge edge = new ShadowEdge(shapeIndex + start, (shapeIndex + 1) % pathSize + start); outEdgesPtr[shapeIndex + start] = edge; } start = end; } int pathStartIndex = offsetSolutionPathSizesLength; start = vertexPos; for (int i = 0; i < openPath.Length; i++) { outVerticesPtr[vertexPos++] = new float3(openPathPtr[i], 0); } for (int openPathIndex = 0; openPathIndex < openPathCount; openPathIndex++) { int pathSize = openPathSizesPtr[openPathIndex]; int end = start + pathSize; outShapeStartingEdgePtr[pathStartIndex + openPathIndex] = start; for (int shapeIndex = 0; shapeIndex < pathSize - 1; shapeIndex++) { ShadowEdge edge = new ShadowEdge(shapeIndex + start, shapeIndex + 1 + start); outEdgesPtr[shapeIndex + start] = edge; } start = end; } } else { outVertices = new NativeArray(0, k_ClippingAllocator); outEdges = new NativeArray(0, k_ClippingAllocator); outShapeStartingEdge = new NativeArray(0, k_ClippingAllocator); } closedPathSizes.Dispose(); closedPath.Dispose(); openPathSizes.Dispose(); openPath.Dispose(); clipperPathArguments.Dispose(); offsetPathArguments.Dispose(); clipperSolution.Dispose(); offsetSolution.Dispose(); } } } }