UnityGame/Library/PackageCache/com.unity.render-pipelines.universal/Runtime/2D/Shadows/ShadowProvider/ShadowUtility.cs

938 lines
44 KiB
C#
Raw Permalink Normal View History

2024-10-27 10:53:47 +03:00
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<Vector3> inVertices, ref NativeArray<ShadowEdge> inEdges, ref NativeArray<int> inShapeStartingEdge, ref NativeArray<bool> inShapeIsClosedArray, ref NativeArray<Vector2> 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<Vector3> inVertices, ref NativeArray<ShadowEdge> inEdges, ref NativeArray<Vector2> inEdgeOtherPoints, ref NativeArray<ShadowMeshVertex> 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<Vector3> inVertices, ref NativeArray<ShadowEdge> inEdges, ref NativeArray<int> inShapeStartingEdge, ref NativeArray<bool> inShapeIsClosedArray, ref NativeArray<int> 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<Vector3> 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<ShadowMeshVertex> inVertices, ref NativeArray<int> inIndices, ref NativeArray<ShadowEdge> inEdges, out NativeArray<ShadowMeshVertex> outVertices, out NativeArray<int> outIndices, out int outStartIndex, out int outIndexCount)
{
int inEdgeCount = inEdges.Length;
// Do tessellation
NativeArray<int2> tessInEdges = new NativeArray<int2>(inEdgeCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
NativeArray<float2> tessInVertices = new NativeArray<float2>(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<int> tessOutIndices = new NativeArray<int>(tessInVertices.Length * 8, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
NativeArray<float2> tessOutVertices = new NativeArray<float2>(tessInVertices.Length * 4, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
NativeArray<int2> tessOutEdges = new NativeArray<int2>(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<ShadowMeshVertex>(totalOutVertices, Allocator.Persistent, NativeArrayOptions.ClearMemory);
outIndices = new NativeArray<int>(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<Vector3> inVertices, NativeArray<ShadowEdge> inEdges, NativeArray<int> inShapeStartingEdge, NativeArray<bool> 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<Vector2> meshProjectionInfo = new NativeArray<Vector2>(meshVertexCount, Allocator.Persistent);
NativeArray<int> meshIndices = new NativeArray<int>(meshIndexCount, Allocator.Persistent);
NativeArray<ShadowMeshVertex> meshVertices = new NativeArray<ShadowMeshVertex>(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<ShadowMeshVertex> finalVertices;
NativeArray<int> 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<ShadowMeshVertex>(finalVertices, 0, 0, finalVertices.Length);
mesh.SetIndexBufferParams(finalIndices.Length, IndexFormat.UInt32);
mesh.SetIndexBufferData<int>(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<int> indices, out NativeArray<ShadowEdge> outEdges, out NativeArray<int> outShapeStartingEdge, out NativeArray<bool> outShapeIsClosedArray)
{
unsafe
{
int numOfEdges = indices.Length >> 1;
NativeArray<int> tempShapeStartIndices = new NativeArray<int>(numOfEdges, Allocator.Persistent);
NativeArray<bool> tempShapeIsClosedArray = new NativeArray<bool>(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<int>(shapeCount, Allocator.Persistent);
outShapeIsClosedArray = new NativeArray<bool>(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<ShadowEdge>(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<Vector3> vertices, ref NativeArray<ShadowEdge> edges, int vertexCount, out bool hasReusedVertices, out int newVertexCount, out NativeArray<RemappingInfo> remappingInfo)
{
unsafe
{
int edgeCount = edges.Length;
newVertexCount = 0;
hasReusedVertices = false;
remappingInfo = new NativeArray<RemappingInfo>(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<vertexCount;i++)
{
// Leave the other indices -1 for easier validation testing
if (remappingInfoPtr[i].count > 0)
{
remappingInfoPtr[i].index = startPos;
startPos += remappingInfoPtr[i].count;
}
}
}
}
static public bool IsTriangleReversed(NativeArray<Vector3> 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<Vector3> vertices, ref NativeArray<int> indices, bool duplicatesVertices, out NativeArray<Vector3> newVertices, out NativeArray<ShadowEdge> outEdges, out NativeArray<int> outShapeStartingEdge, out NativeArray<bool> 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<Vector2> points = new NativeArray<Vector2>(indices.Length, Allocator.Persistent);
NativeArray<int> pathSizes = new NativeArray<int>(triangleCount, Allocator.Persistent);
NativeArray<Clipper2D.PathArguments> pathArguments = new NativeArray<Clipper2D.PathArguments>(triangleCount, Allocator.Persistent);
// Pointers to our native arrays for performance in editor
Vector2* pointsPtr = (Vector2*)points.GetUnsafePtr<Vector2>();
int* pathSizesPtr = (int*)pathSizes.GetUnsafePtr<int>();
Clipper2D.PathArguments* pathArgumentsPtr = (Clipper2D.PathArguments*)pathArguments.GetUnsafePtr<Clipper2D.PathArguments>();
Vector3* verticesPtr = (Vector3*)vertices.GetUnsafePtr<Vector3>();
// 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<Vector3>(pointLen, Allocator.Persistent);
outEdges = new NativeArray<ShadowEdge>(pointLen, Allocator.Persistent);
outShapeStartingEdge = new NativeArray<int>(shapeCount, Allocator.Persistent);
outShapeIsClosedArray = new NativeArray<bool>(shapeCount, Allocator.Persistent);
// More pointers for edtor time perfomance
int* solutionPathSizesPtr = (int*)solution.pathSizes.GetUnsafePtr<int>();
Vector2* solutionPointsPtr = (Vector2*)solution.points.GetUnsafePtr<Vector2>();
Vector3* newVerticesPtr = (Vector3*)newVertices.GetUnsafePtr<Vector3>();
ShadowEdge* outEdgesPtr = (ShadowEdge*)outEdges.GetUnsafePtr<ShadowEdge>();
int* outShapeStartingEdgePtr = (int*)outShapeStartingEdge.GetUnsafePtr<int>();
bool* outShapeIsClosedArrayPtr = (bool*)outShapeIsClosedArray.GetUnsafePtr<bool>();
// 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<int> inShapeStartingEdge, ref NativeArray<ShadowEdge> 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<int> inShapeStartingEdge, ref NativeArray<bool> inShapeIsClosedArray)
{
int count = 0;
for(int i=0;i<inShapeStartingEdge.Length;i++)
{
if (inShapeStartingEdge[i] < 0)
break;
count++;
}
return count;
}
static void GetPathInfo(NativeArray<ShadowEdge> inEdges, NativeArray<int> inShapeStartingEdge, NativeArray<bool> 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<Vector3> inVertices, ref NativeArray<ShadowEdge> inEdges, ref NativeArray<int> inShapeStartingEdge, ref NativeArray<bool> inShapeIsClosedArray, float contractEdge, out NativeArray<Vector3> outVertices, out NativeArray<ShadowEdge> outEdges, out NativeArray<int> 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<Clipper2D.PathArguments> clipperPathArguments = new NativeArray<Clipper2D.PathArguments>(closedPathCount, k_ClippingAllocator, NativeArrayOptions.ClearMemory);
NativeArray<int> closedPathSizes = new NativeArray<int>(closedPathCount, k_ClippingAllocator);
NativeArray<Vector2> closedPath = new NativeArray<Vector2>(closedPathArrayCount, k_ClippingAllocator);
NativeArray<int> openPathSizes = new NativeArray<int>(openPathCount, k_ClippingAllocator);
NativeArray<Vector2> openPath = new NativeArray<Vector2>(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<Vector2> clipperOffsetPath = closedPath;
NativeArray<int> 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<ClipperOffset2D.PathArguments> offsetPathArguments = new NativeArray<ClipperOffset2D.PathArguments>(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<Vector3>(offsetSolution.points.Length + openPathArrayCount, k_ClippingAllocator);
outEdges = new NativeArray<ShadowEdge>(offsetSolution.points.Length + openPathArrayCount, k_ClippingAllocator);
outShapeStartingEdge = new NativeArray<int>(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<Vector3>(0, k_ClippingAllocator);
outEdges = new NativeArray<ShadowEdge>(0, k_ClippingAllocator);
outShapeStartingEdge = new NativeArray<int>(0, k_ClippingAllocator);
}
closedPathSizes.Dispose();
closedPath.Dispose();
openPathSizes.Dispose();
openPath.Dispose();
clipperPathArguments.Dispose();
offsetPathArguments.Dispose();
clipperSolution.Dispose();
offsetSolution.Dispose();
}
}
}
}