using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Unity.Collections; using System.Collections.Generic; using UnityEngine.U2D; using UnityEngine.Rendering.Universal.UTess; using Unity.Collections.LowLevel.Unsafe; using Unity.Mathematics; using Unity.Burst; namespace UnityEngine.Rendering.Universal { [BurstCompile] internal 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, } [StructLayout(LayoutKind.Sequential)] internal struct ShadowMeshVertex { internal Vector3 position; // stores: xy: position z: projection type w: soft shadow value (0 is fully shadowed) internal Vector4 tangent; // stores: xy: contraction dir zw: other edge position internal ShadowMeshVertex(ProjectionType inProjectionType, Vector2 inEdgePosition0, Vector2 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), }; unsafe static int GetNextShapeStart(int currentShape, int* inShapeStartingEdgePtr, int inShapeStartingEdgeLength, int maxValue) { // Make sure we are in the bounds of the shapes we have. Also make sure our starting edge isn't negative return ((currentShape + 1 < inShapeStartingEdgeLength) && (inShapeStartingEdgePtr[currentShape + 1] >= 0)) ? inShapeStartingEdgePtr[currentShape + 1] : maxValue; } [BurstCompile] static internal void CalculateProjectionInfo(ref NativeArray inVertices, ref NativeArray inEdges, ref NativeArray inShapeStartingEdge, ref NativeArray inShapeIsClosedArray, ref NativeArray outProjectionInfo) { unsafe { Vector3* inVerticesPtr = (Vector3*)inVertices.m_Buffer; ShadowEdge* inEdgesPtr = (ShadowEdge*)inEdges.m_Buffer; int* inShapeStartingEdgePtr = (int *)inShapeStartingEdge.m_Buffer; bool* inShapeIsClosedArrayPtr = (bool*)inShapeIsClosedArray.m_Buffer; Vector2* outProjectionInfoPtr = (Vector2*)outProjectionInfo.m_Buffer; Vector2 tmpVec2 = new Vector2(); // So we don't call the constructor 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++) { if (i == nextShapeStart) { 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; tmpVec2.x = inVerticesPtr[v0].x; tmpVec2.y = inVerticesPtr[v0].y; Vector2 startPt = tmpVec2; tmpVec2.x = inVerticesPtr[v1].x; tmpVec2.y = inVerticesPtr[v1].y; Vector2 endPt = tmpVec2; tmpVec2.x = inVerticesPtr[prev1].x; tmpVec2.y = inVerticesPtr[prev1].y; Vector2 prevPt = tmpVec2; tmpVec2.x = inVerticesPtr[next0].x; tmpVec2.y = inVerticesPtr[next0].y; Vector2 nextPt = tmpVec2; // 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] = endPt; outProjectionInfoPtr[additionalVerticesStart + 3] = endPt; } } } [BurstCompile] static internal void CalculateVertices(ref NativeArray inVertices, ref NativeArray inEdges, ref NativeArray inEdgeOtherPoints, ref NativeArray outMeshVertices) { unsafe { Vector3* inVerticesPtr = (Vector3*)inVertices.m_Buffer; ShadowEdge* inEdgesPtr = (ShadowEdge*)inEdges.m_Buffer; Vector2* inEdgeOtherPointsPtr = (Vector2*)inEdgeOtherPoints.m_Buffer; ShadowMeshVertex* outMeshVerticesPtr = (ShadowMeshVertex*)outMeshVertices.m_Buffer; Vector2 tmpVec2 = new Vector2(); // So we don't call the constructor int inEdgesLength = inEdges.Length; int inVerticesLength = inVertices.Length; for (int i = 0; i < inVerticesLength; i++) { tmpVec2.x = inVerticesPtr[i].x; tmpVec2.y = inVerticesPtr[i].y; ShadowMeshVertex originalShadowMesh = new ShadowMeshVertex(ProjectionType.ProjectionNone, tmpVec2, inEdgeOtherPointsPtr[i]); outMeshVerticesPtr[i] = originalShadowMesh; } for (int i = 0; i < inEdgesLength; i++) { int v0 = inEdgesPtr[i].v0; int v1 = inEdgesPtr[i].v1; tmpVec2.x = inVerticesPtr[v0].x; tmpVec2.y = inVerticesPtr[v0].y; Vector2 pt0 = tmpVec2; tmpVec2.x = inVerticesPtr[v1].x; tmpVec2.y = inVerticesPtr[v1].y; Vector2 pt1 = tmpVec2; int additionalVerticesStart = k_AdditionalVerticesPerEdge * i + inVerticesLength; ShadowMeshVertex additionalVertex0 = new ShadowMeshVertex(ProjectionType.ProjectionHard, pt0, inEdgeOtherPointsPtr[additionalVerticesStart]); ShadowMeshVertex additionalVertex1 = new ShadowMeshVertex(ProjectionType.ProjectionHard, pt1, inEdgeOtherPointsPtr[additionalVerticesStart + 1]); ShadowMeshVertex additionalVertex2 = new ShadowMeshVertex(ProjectionType.ProjectionSoftLeft, pt0, inEdgeOtherPointsPtr[additionalVerticesStart + 2]); ShadowMeshVertex additionalVertex3 = new ShadowMeshVertex(ProjectionType.ProjectionSoftRight, pt0, inEdgeOtherPointsPtr[additionalVerticesStart + 3]); outMeshVerticesPtr[additionalVerticesStart] = additionalVertex0; outMeshVerticesPtr[additionalVerticesStart + 1] = additionalVertex1; outMeshVerticesPtr[additionalVerticesStart + 2] = additionalVertex2; outMeshVerticesPtr[additionalVerticesStart + 3] = additionalVertex3; } } } [BurstCompile] static internal void CalculateTriangles(ref NativeArray inVertices, ref NativeArray inEdges, ref NativeArray inShapeStartingEdge, ref NativeArray inShapeIsClosedArray, ref NativeArray outMeshIndices) { unsafe { ShadowEdge* inEdgesPtr = (ShadowEdge*)inEdges.m_Buffer; int* inShapeStartingEdgePtr = (int*)inShapeStartingEdge.m_Buffer; int* outMeshIndicesPtr = (int*)outMeshIndices.m_Buffer; 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 int prevEdge = endIndex - 1; for (int i = startingIndex; i < endIndex; i++) { int v0 = inEdgesPtr[i].v0; int v1 = inEdgesPtr[i].v1; int additionalVerticesStart = k_AdditionalVerticesPerEdge * i + inVerticesLength; // Add a degenerate rectangle outMeshIndicesPtr[meshIndex++] = (ushort)v0; outMeshIndicesPtr[meshIndex++] = (ushort)additionalVerticesStart; outMeshIndicesPtr[meshIndex++] = (ushort)(additionalVerticesStart + 1); outMeshIndicesPtr[meshIndex++] = (ushort)(additionalVerticesStart + 1); outMeshIndicesPtr[meshIndex++] = (ushort)v1; outMeshIndicesPtr[meshIndex++] = (ushort)v0; prevEdge = i; } // Soft Shadow Geometry for (int i = startingIndex; i < endIndex; i++) //int i = 0; { int v0 = inEdgesPtr[i].v0; int v1 = inEdgesPtr[i].v1; int additionalVerticesStart = k_AdditionalVerticesPerEdge * i + inVerticesLength; // We also need 1 more triangles for soft shadows (3 indices) outMeshIndicesPtr[meshIndex++] = (ushort)v0; outMeshIndicesPtr[meshIndex++] = (ushort)additionalVerticesStart + 2; outMeshIndicesPtr[meshIndex++] = (ushort)additionalVerticesStart + 3; } } } } [BurstCompile] static internal void CalculateLocalBounds(ref NativeArray inVertices, out Bounds retBounds) { if (inVertices.Length <= 0) { retBounds = new Bounds(Vector3.zero, Vector3.zero); } else { Vector2 minVec = Vector2.positiveInfinity; Vector2 maxVec = Vector2.negativeInfinity; unsafe { Vector3* inVerticesPtr = (Vector3*)inVertices.m_Buffer; int inVerticesLength = inVertices.Length; // Add outline vertices for (int i = 0; i < inVerticesLength; i++) { Vector2 vertex = new Vector2(inVerticesPtr[i].x, inVerticesPtr[i].y); minVec = Vector2.Min(minVec, vertex); maxVec = Vector2.Max(maxVec, vertex); } } retBounds = new Bounds { max = maxVec, min = minVec }; } } [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; int tessOutEdgeCount = 0; UTess.ModuleHandle.Tessellate(Allocator.Persistent, tessInVertices, tessInEdges, ref tessOutVertices, ref tessOutVertexCount, ref tessOutIndices, ref tessOutIndexCount, ref tessOutEdges, ref tessOutEdgeCount); 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.ClearMemory); outIndices = new NativeArray(totalOutIndices, Allocator.Persistent, NativeArrayOptions.ClearMemory); // Copy vertices for (int i = 0; i < inVertices.Length; i++) outVertices[i] = inVertices[i]; for (int i = 0; i < tessOutVertexCount; i++) { float2 tessVertex = tessOutVertices[i]; ShadowMeshVertex vertex = new ShadowMeshVertex(ProjectionType.ProjectionNone, tessVertex, Vector2.zero); outVertices[i + vertexOffset] = vertex; } // Copy indices for (int i = 0; i < inIndices.Length; i++) outIndices[i] = inIndices[i]; // 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(); } //inEdges is expected to be contiguous static public Bounds GenerateShadowMesh(Mesh mesh, NativeArray inVertices, NativeArray inEdges, NativeArray inShapeStartingEdge, NativeArray inShapeIsClosedArray, bool allowContraction, bool fill, ShadowShape2D.OutlineTopology topology) { // Setup our buffers int meshVertexCount = inVertices.Length + k_AdditionalVerticesPerEdge * inEdges.Length; // Each vertex will have a duplicate that can be extruded. int meshIndexCount = inEdges.Length * k_VerticesPerTriangle * k_TrianglesPerEdge; // There are two triangles per edge making a degenerate rectangle (0 area) NativeArray meshProjectionInfo = new NativeArray(meshVertexCount, Allocator.Persistent); NativeArray meshIndices = new NativeArray(meshIndexCount, Allocator.Persistent); NativeArray meshVertices = new NativeArray(meshVertexCount, Allocator.Persistent); CalculateProjectionInfo(ref inVertices, ref inEdges, ref inShapeStartingEdge, ref inShapeIsClosedArray, ref meshProjectionInfo); CalculateVertices(ref inVertices, ref inEdges, ref meshProjectionInfo, ref meshVertices); CalculateTriangles(ref inVertices, ref inEdges, ref inShapeStartingEdge, ref inShapeIsClosedArray, ref meshIndices); NativeArray finalVertices; NativeArray finalIndices; int fillSubmeshStartIndex = 0; int fillSubmeshIndexCount = 0; if (fill) // This has limited utility at the moment as contraction is not calculated. More work will need to be done to generalize this { GenerateInteriorMesh(ref meshVertices, ref meshIndices, ref inEdges, out finalVertices, out finalIndices, out fillSubmeshStartIndex, out fillSubmeshIndexCount); meshVertices.Dispose(); meshIndices.Dispose(); } else { finalVertices = meshVertices; finalIndices = meshIndices; } // Set the mesh data mesh.SetVertexBufferParams(finalVertices.Length, m_VertexLayout); mesh.SetVertexBufferData(finalVertices, 0, 0, finalVertices.Length); mesh.SetIndexBufferParams(finalIndices.Length, IndexFormat.UInt32); mesh.SetIndexBufferData(finalIndices, 0, 0, finalIndices.Length); mesh.SetSubMesh(0, new SubMeshDescriptor(0, finalIndices.Length)); mesh.subMeshCount = 1; meshProjectionInfo.Dispose(); finalVertices.Dispose(); finalIndices.Dispose(); CalculateLocalBounds(ref inVertices, out Bounds retLocalBound); 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.Persistent); NativeArray tempShapeIsClosedArray = new NativeArray(numOfEdges, Allocator.Persistent); int* indicesPtr = (int*)indices.m_Buffer; int* tempShapeStartIndicesPtr = (int*)tempShapeStartIndices.m_Buffer; bool* tempShapeIsClosedArrayPtr = (bool*)tempShapeIsClosedArray.m_Buffer; 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); outShapeIsClosedArray = new NativeArray(shapeCount, Allocator.Persistent); int* outShapeStartingEdgePtr = (int*)outShapeStartingEdge.m_Buffer; bool* outShapeIsClosedArrayPtr = (bool*)outShapeIsClosedArray.m_Buffer; for (int i = 0; i < shapeCount; i++) { outShapeStartingEdgePtr[i] = tempShapeStartIndicesPtr[i]; outShapeIsClosedArrayPtr[i] = tempShapeIsClosedArrayPtr[i]; } tempShapeStartIndices.Dispose(); tempShapeIsClosedArray.Dispose(); // Add edges outEdges = new NativeArray(numOfEdges, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); ShadowEdge* outEdgesPtr = (ShadowEdge*)outEdges.m_Buffer; 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) // This is an open shape { remappingInfoPtr[v1].count = 1; newVertexCount++; } } // Find the starts of the new indices.. int startPos = 0; for (int i=0;i 0) { remappingInfoPtr[i].index = startPos; startPos += remappingInfoPtr[i].count; } } } } static public bool IsTriangleReversed(NativeArray vertices, int idx0, int idx1, int idx2) { Vector3 v0 = vertices[idx0]; Vector3 v1 = vertices[idx1]; Vector3 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 Mathf.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); NativeArray pathSizes = new NativeArray(triangleCount, Allocator.Persistent); NativeArray pathArguments = new NativeArray(triangleCount, Allocator.Persistent); // Pointers to our native arrays for performance in editor Vector2* pointsPtr = (Vector2*)points.GetUnsafePtr(); int* pathSizesPtr = (int*)pathSizes.GetUnsafePtr(); Clipper2D.PathArguments* pathArgumentsPtr = (Clipper2D.PathArguments*)pathArguments.GetUnsafePtr(); Vector3* verticesPtr = (Vector3*)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; pointsPtr[pointOffset] = verticesPtr[indices[pointOffset]]; pointsPtr[pointOffset + 1] = verticesPtr[indices[pointOffset + 1]]; pointsPtr[pointOffset + 2] = verticesPtr[indices[pointOffset + 2]]; } 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); outEdges = new NativeArray(pointLen, Allocator.Persistent); outShapeStartingEdge = new NativeArray(shapeCount, Allocator.Persistent); outShapeIsClosedArray = new NativeArray(shapeCount, Allocator.Persistent); // More pointers for edtor time perfomance int* solutionPathSizesPtr = (int*)solution.pathSizes.GetUnsafePtr(); Vector2* solutionPointsPtr = (Vector2*)solution.points.GetUnsafePtr(); Vector3* newVerticesPtr = (Vector3*)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++) { // Copy shape info to outputs int curStart = nextStart; int curPathSize = solutionPathSizesPtr[shapeIndex]; outShapeStartingEdgePtr[shapeIndex] = nextStart; nextStart += curPathSize; // Copy vertices and edges to outputs; int previousVertex = nextStart - 1; for (int pointIndex = curStart; pointIndex < nextStart; pointIndex++) { newVerticesPtr[pointIndex] = solutionPointsPtr[pointIndex]; outEdgesPtr[pointIndex] = new ShadowEdge(previousVertex, pointIndex); previousVertex = pointIndex; } // All shapes are closed since they are created from triangles outShapeIsClosedArrayPtr[shapeIndex] = true; } // Cleanup solution 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); for (int i = 0; i < (count >> 1); 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; } bool isOdd = (count & 1) == 1; if (isOdd) { int edgeAIndex = startingIndex + (count >> 1); ShadowEdge edgeA = inOutSortedEdges[edgeAIndex]; edgeA.Reverse(); inOutSortedEdges[edgeAIndex] = edgeA; } } } static int GetClosedPathCount(ref NativeArray inShapeStartingEdge, ref NativeArray inShapeIsClosedArray) { int count = 0; for(int i=0;i inEdges, NativeArray inShapeStartingEdge, 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(inEdges, inShapeStartingEdge, 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); NativeArray closedPath = new NativeArray(closedPathArrayCount, k_ClippingAllocator); NativeArray openPathSizes = new NativeArray(openPathCount, k_ClippingAllocator); NativeArray openPath = new NativeArray(openPathArrayCount, k_ClippingAllocator); Clipper2D.PathArguments* clipperPathArgumentsPtr = (Clipper2D.PathArguments*)clipperPathArguments.m_Buffer; int* closedPathSizesPtr = (int*)closedPathSizes.m_Buffer; Vector2* closedPathPtr = (Vector2*)closedPath.m_Buffer; int* openPathSizesPtr = (int*)openPathSizes.m_Buffer; Vector2* openPathPtr = (Vector2*)openPath.m_Buffer; int* inShapeStartingEdgePtr = (int*)inShapeStartingEdge.m_Buffer; bool* inShapeIsClosedArrayPtr = (bool*)inShapeIsClosedArray.m_Buffer; Vector3* inVerticesPtr = (Vector3*)inVertices.m_Buffer; ShadowEdge* inEdgesPtr = (ShadowEdge*)inEdges.m_Buffer; int inEdgesLength = inEdges.Length; Vector2 tmpVec2 = new Vector2(); // So we don't call the constructor 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 we have a closed shape then add it to our path and path sizes. if (inShapeIsClosedArrayPtr[shapeStartIndex]) { closedPathSizesPtr[closedPathSizesIndex] = numberOfEdges + 1; clipperPathArgumentsPtr[closedPathSizesIndex] = new Clipper2D.PathArguments(Clipper2D.PolyType.ptSubject, true); closedPathSizesIndex++; for (int i = 0; i < numberOfEdges; i++) { Vector3 vec3 = inVerticesPtr[inEdgesPtr[i + currentShapeStart].v0]; tmpVec2.x = vec3.x; tmpVec2.y = vec3.y; closedPathPtr[closedPathArrayIndex++] = tmpVec2; } closedPathPtr[closedPathArrayIndex++] = inVerticesPtr[inEdgesPtr[numberOfEdges + currentShapeStart - 1].v1]; } else { openPathSizesPtr[openPathSizesIndex++] = numberOfEdges + 1; for (int i = 0; i < numberOfEdges; i++) { Vector3 vec3 = inVerticesPtr[inEdgesPtr[i + currentShapeStart].v0]; tmpVec2.x = vec3.x; tmpVec2.y = vec3.y; openPathPtr[openPathArrayIndex++] = tmpVec2; } openPathPtr[openPathArrayIndex++] = inVerticesPtr[inEdgesPtr[numberOfEdges + currentShapeStart - 1].v1]; } } NativeArray clipperOffsetPath = closedPath; NativeArray clipperOffsetPathSizes = closedPathSizes; Clipper2D.Solution clipperSolution = new Clipper2D.Solution(); // Run this to try to merge outlines if there is more than one 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; // Combine the solutions from clipper and our open paths int solutionPathLens = offsetSolution.pathSizes.Length + openPathCount; outVertices = new NativeArray(offsetSolution.points.Length + openPathArrayCount, k_ClippingAllocator); outEdges = new NativeArray(offsetSolution.points.Length + openPathArrayCount, k_ClippingAllocator); outShapeStartingEdge = new NativeArray(solutionPathLens, k_ClippingAllocator); Vector3* outVerticesPtr = (Vector3*)outVertices.m_Buffer; ShadowEdge* outEdgesPtr = (ShadowEdge*)outEdges.m_Buffer; int* outShapeStartingEdgePtr = (int*)outShapeStartingEdge.m_Buffer; Vector2* offsetSolutionPointsPtr = (Vector2*)offsetSolution.points.m_Buffer; int offsetSolutionPointsLength = offsetSolution.points.Length; int* offsetSolutionPathSizesPtr = (int*)offsetSolution.pathSizes.m_Buffer; int offsetSolutionPathSizesLength = offsetSolution.pathSizes.Length; // Copy out the solution first.. for (int i = 0; i < offsetSolutionPointsLength; i++) { tmpVec3.x = offsetSolutionPointsPtr[i].x; tmpVec3.y = offsetSolutionPointsPtr[i].y; outVerticesPtr[vertexPos++] = tmpVec3; } 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; } // Copy out the open vertices int pathStartIndex = offsetSolutionPathSizesLength; start = vertexPos; // We need to remap our vertices; for (int i = 0; i < openPath.Length; i++) { tmpVec3.x = openPathPtr[i].x; tmpVec3.y = openPathPtr[i].y; outVerticesPtr[vertexPos++] = tmpVec3; } 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); 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(); } } } }