using Unity.Mathematics; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; namespace UnityEngine.Rendering.Universal.UTess { struct Smoothen { // This is an arbitrary value less than 2 to ensure points are not moved too far away from the source polygon. private static readonly float kMaxAreaTolerance = 1.842f; private static readonly float kMaxEdgeTolerance = 2.482f; // Trim Edges static void RefineEdges(ref NativeArray refinedEdges, ref NativeArray delaEdges, ref int delaEdgeCount, ref NativeArray voronoiEdges) { int origEdgeCount = delaEdgeCount; delaEdgeCount = 0; // Check Neighbour Triangles. for (int i = 0; i < origEdgeCount - 1; ++i) { var edge = delaEdges[i]; var neighbor = delaEdges[i + 1]; if (edge.x == neighbor.x && edge.y == neighbor.y) { // Found the Opposite Edge. i.e Nearby Triangle. edge.w = neighbor.z; ++i; } // Update new list. refinedEdges[delaEdgeCount++] = edge; } // Generate Voronoi Edges. for (int i = 0; i < delaEdgeCount; ++i) { var ti1 = refinedEdges[i].z; var ti2 = refinedEdges[i].w; // We only really care about Bounded Edges. This is simplification. Hoping this garbage works. if (ti1 != -1 && ti2 != -1) { // Get Triangles int4 e = new int4(ti2, ti1, i, 0); voronoiEdges[i] = e; } } ModuleHandle.Copy(refinedEdges, delaEdges, delaEdgeCount); } // Get all the Edges that has this Point. static void GetAffectingEdges(int pointIndex, NativeArray edges, int edgeCount, ref NativeArray resultSet, ref NativeArray checkSet, ref int resultCount) { resultCount = 0; for (int i = 0; i < edgeCount; ++i) { if (pointIndex == edges[i].x || pointIndex == edges[i].y) resultSet[resultCount++] = i; checkSet[i] = 0; } } // Add to Centroids Triangles. static void CentroidByPoints(int triIndex, NativeArray triangles, ref NativeArray centroidTris, ref int centroidCount, ref float2 aggregate, ref float2 point) { for (int i = 0; i < centroidCount; ++i) if (triIndex == centroidTris[i]) return; centroidTris[centroidCount++] = triIndex; aggregate += triangles[triIndex].c.center; point = aggregate / centroidCount; } static void CentroidByPolygon(int4 e, NativeArray triangles, ref float2 centroid, ref float area, ref float distance) { var es = triangles[e.x].c.center; var ee = triangles[e.y].c.center; var d = es.x * ee.y - ee.x * es.y; distance = distance + math.distance(es, ee); area = area + d; centroid.x += (ee.x + es.x) * d; centroid.y += (ee.y + es.y) * d; } // Connect Triangles static bool ConnectTriangles(ref NativeArray connectedTri, ref NativeArray affectEdges, ref NativeArray checkSet, NativeArray voronoiEdges, int triangleCount) { var ei = affectEdges[0]; var ni = affectEdges[0]; connectedTri[0] = new int4(voronoiEdges[ei].x, voronoiEdges[ei].y, 0, 0); checkSet[ni] = 1; for (int i = 1; i < triangleCount; ++i) { ni = affectEdges[i]; if (checkSet[ni] == 0) { if (voronoiEdges[ni].x == connectedTri[i - 1].y) { connectedTri[i] = new int4(voronoiEdges[ni].x, voronoiEdges[ni].y, 0, 0); checkSet[ni] = 1; continue; } else { if (voronoiEdges[ni].y == connectedTri[i - 1].y) { connectedTri[i] = new int4(voronoiEdges[ni].y, voronoiEdges[ni].x, 0, 0); checkSet[ni] = 1; continue; } } } var connected = false; for (int j = 0; j < triangleCount; ++j) { ni = affectEdges[j]; if (checkSet[ni] == 1) continue; if (voronoiEdges[ni].x == connectedTri[i - 1].y) { connectedTri[i] = new int4(voronoiEdges[ni].x, voronoiEdges[ni].y, 0, 0); checkSet[ni] = 1; connected = true; break; } else if (voronoiEdges[ni].y == connectedTri[i - 1].y) { connectedTri[i] = new int4(voronoiEdges[ni].y, voronoiEdges[ni].x, 0, 0); checkSet[ni] = 1; connected = true; break; } } if (!connected) return false; } return true; } // Perform Voronoi based Smoothing. Does not add/remove points but merely relocates internal vertices so they are uniform distributed. internal static bool Condition(Allocator allocator, ref NativeArray pgPoints, int pgPointCount, NativeArray pgEdges, int pgEdgeCount, ref NativeArray vertices, ref int vertexCount, ref NativeArray indices, ref int indexCount) { // Build Triangles and Edges. float maxArea = 0, cmxArea = 0, minArea = 0, cmnArea = 0, avgArea = 0, minEdge = 0, maxEdge = 0, avgEdge = 0; bool polygonCentroid = true, validGraph = true; int triangleCount = 0, delaEdgeCount = 0, affectingEdgeCount = 0; var triangles = new NativeArray(indexCount, allocator); // Intentionally added more room than actual Triangles needed here. var delaEdges = new NativeArray(indexCount, allocator); var voronoiEdges = new NativeArray(indexCount, allocator); var connectedTri = new NativeArray(vertexCount, allocator); var voronoiCheck = new NativeArray(indexCount, allocator); var affectsEdges = new NativeArray(indexCount, allocator); var triCentroids = new NativeArray(vertexCount, allocator); ModuleHandle.BuildTrianglesAndEdges(vertices, vertexCount, indices, indexCount, ref triangles, ref triangleCount, ref delaEdges, ref delaEdgeCount, ref maxArea, ref avgArea, ref minArea); var refinedEdges = new NativeArray(delaEdgeCount, allocator); // Sort the Delaunay Edges. unsafe { ModuleHandle.InsertionSort( NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(delaEdges), 0, delaEdgeCount - 1, new DelaEdgeCompare()); } // TrimEdges. Update Triangle Info for Shared Edges and remove Duplicates. RefineEdges(ref refinedEdges, ref delaEdges, ref delaEdgeCount, ref voronoiEdges); // Now for each point, generate Voronoi diagram. for (int i = 0; i < vertexCount; ++i) { // Try moving this to Centroid of the Voronoi Polygon. GetAffectingEdges(i, delaEdges, delaEdgeCount, ref affectsEdges, ref voronoiCheck, ref affectingEdgeCount); var bounded = affectingEdgeCount != 0; // Check for Boundedness for (int j = 0; j < affectingEdgeCount; ++j) { // Edge Index. var ei = affectsEdges[j]; if (delaEdges[ei].z == -1 || delaEdges[ei].w == -1) { bounded = false; break; } } // If this is bounded point, relocate to Voronoi Diagram's Centroid if (bounded) { polygonCentroid = ConnectTriangles(ref connectedTri, ref affectsEdges, ref voronoiCheck, voronoiEdges, affectingEdgeCount); if (!polygonCentroid) { break; } float2 point = float2.zero; float area = 0, distance = 0; for (int k = 0; k < affectingEdgeCount; ++k) { CentroidByPolygon(connectedTri[k], triangles, ref point, ref area, ref distance); } point /= (3 * area); pgPoints[i] = point; } } // Do Delaunay Again. int srcIndexCount = indexCount, srcVertexCount = vertexCount; indexCount = 0; vertexCount = 0; triangleCount = 0; if (polygonCentroid) { validGraph = Tessellator.Tessellate(allocator, pgPoints, pgPointCount, pgEdges, pgEdgeCount, ref vertices, ref vertexCount, ref indices, ref indexCount); if (validGraph) ModuleHandle.BuildTriangles(vertices, vertexCount, indices, indexCount, ref triangles, ref triangleCount, ref cmxArea, ref avgArea, ref cmnArea, ref maxEdge, ref avgEdge, ref minEdge); // This Edge validation prevents artifacts by forcing a fallback. todo: Fix the actual bug in Outline generation. validGraph = validGraph && (cmxArea < maxArea * kMaxAreaTolerance) && (maxEdge < avgEdge * kMaxEdgeTolerance); } // Cleanup. triangles.Dispose(); delaEdges.Dispose(); refinedEdges.Dispose(); voronoiCheck.Dispose(); voronoiEdges.Dispose(); affectsEdges.Dispose(); triCentroids.Dispose(); connectedTri.Dispose(); return (validGraph && srcIndexCount == indexCount && srcVertexCount == vertexCount); } } }