using Unity.Collections; using Unity.Mathematics; using Unity.Collections.LowLevel.Unsafe; namespace UnityEngine.U2D.Common.UTess { // Ensures that there are no duplicate Points, Overlapping Edges. struct PlanarGraph { private static readonly double kEpsilon = 0.00001; private static readonly int kMaxIntersectionTolerance = 4; // Maximum Intersection Tolerance per Intersection Loop Check. internal static void RemoveDuplicateEdges(ref Array edges, ref int edgeCount, Array duplicates, int duplicateCount) { if (duplicateCount == 0) { for (var i = 0; i < edgeCount; ++i) { var e = edges[i]; e.x = math.min(edges[i].x, edges[i].y); e.y = math.max(edges[i].x, edges[i].y); edges[i] = e; } } else { for (var i = 0; i < edgeCount; ++i) { var e = edges[i]; var a = duplicates[e.x]; var b = duplicates[e.y]; e.x = math.min(a, b); e.y = math.max(a, b); edges[i] = e; } } unsafe { ModuleHandle.InsertionSort(edges.UnsafePtr, 0, edgeCount - 1,new TessEdgeCompare()); } var n = 1; for (var i = 1; i < edgeCount; ++i) { var prev = edges[i - 1]; var next = edges[i]; if (next.x == prev.x && next.y == prev.y) continue; if (next.x == next.y) continue; edges[n++] = next; } edgeCount = n; } internal static bool CheckCollinear(double2 a0, double2 a1, double2 b0, double2 b1) { double2 a = a0; double2 b = a1; double2 c = b0; double2 d = b1; double x = (b.y - a.y) / (b.x - a.x); double y = (c.y - a.y) / (c.x - a.x); double z = (d.y - a.y) / (d.x - a.x); return ((!math.isinf(x) || !math.isinf(y) || !math.isinf(z)) && math.abs(x - y) > kEpsilon && math.abs(x - z) > kEpsilon); } internal static bool LineLineIntersection(double2 a0, double2 a1, double2 b0, double2 b1) { var x0 = ModuleHandle.OrientFastDouble(a0, b0, b1); var y0 = ModuleHandle.OrientFastDouble(a1, b0, b1); if ((x0 > kEpsilon && y0 > kEpsilon) || (x0 < -kEpsilon && y0 < -kEpsilon)) { return false; } var x1 = ModuleHandle.OrientFastDouble(b0, a0, a1); var y1 = ModuleHandle.OrientFastDouble(b1, a0, a1); if ((x1 > kEpsilon && y1 > kEpsilon) || (x1 < -kEpsilon && y1 < -kEpsilon)) { return false; } // Check for degenerate collinear case if (math.abs(x0) < kEpsilon && math.abs(y0) < kEpsilon && math.abs(x1) < kEpsilon && math.abs(y1) < kEpsilon) { return CheckCollinear(a0, a1, b0, b1); } return true; } internal static bool LineLineIntersection(double2 p1, double2 p2, double2 p3, double2 p4, ref double2 result) { double bx = p2.x - p1.x; double by = p2.y - p1.y; double dx = p4.x - p3.x; double dy = p4.y - p3.y; double bDotDPerp = bx * dy - by * dx; if (math.abs(bDotDPerp) < kEpsilon) { return false; } double cx = p3.x - p1.x; double cy = p3.y - p1.y; double t = (cx * dy - cy * dx) / bDotDPerp; if ((t >= -kEpsilon) && (t <= 1.0f + kEpsilon)) { result.x = p1.x + t * bx; result.y = p1.y + t * by; return true; } return false; } internal static bool CalculateEdgeIntersections(Array edges, int edgeCount, Array points, int pointCount, ref Array results, ref Array intersects, ref int resultCount) { resultCount = 0; for (int i = 0; i < edgeCount; ++i) { for (int j = i + 1; j < edgeCount; ++j) { var e = edges[i]; var f = edges[j]; if (e.x == f.x || e.x == f.y || e.y == f.x || e.y == f.y) continue; var a = points[e.x]; var b = points[e.y]; var c = points[f.x]; var d = points[f.y]; var g = double2.zero; if (LineLineIntersection(a, b, c, d)) { if (LineLineIntersection(a, b, c, d, ref g)) { // Until we ensure Outline is generated properly, we need this useless Check every correction. if (resultCount >= intersects.Length) return false; intersects[resultCount] = g; results[resultCount++] = new int2(i, j); } } } } // Basically we have self intersections all over (eg. gobo_tree_04). Better don't generate any Mesh as even though this will eventually succeed, error correction will take long time. if (resultCount > (edgeCount * kMaxIntersectionTolerance)) { return false; } var tjc = new IntersectionCompare(); tjc.edges = edges; tjc.points = points; unsafe { ModuleHandle.InsertionSort(results.UnsafePtr, 0, resultCount - 1, tjc); } return true; } internal static bool CalculateTJunctions(Array edges, int edgeCount, Array points, int pointCount, Array results, ref int resultCount) { resultCount = 0; for (int i = 0; i < edgeCount; ++i) { for (int j = 0; j < pointCount; ++j) { var e = edges[i]; if (e.x == j || e.y == j) continue; var a = points[e.x]; var b = points[e.y]; var c = points[j]; var d = points[j]; if (LineLineIntersection(a, b, c, d)) { // Until we ensure Outline is generated properly, we need this useless Check every correction. if (resultCount >= results.Length) return false; results[resultCount++] = new int2(i, j); } } } return true; } internal static bool CutEdges(ref Array points, ref int pointCount, ref Array edges, ref int edgeCount, ref Array tJunctions, ref int tJunctionCount, Array intersections, Array intersects, int intersectionCount) { for (int i = 0; i < intersectionCount; ++i) { var intr = intersections[i]; var e = intr.x; var f = intr.y; int2 j1 = int2.zero; j1.x = e; j1.y = pointCount; tJunctions[tJunctionCount++] = j1; int2 j2 = int2.zero; j2.x = f; j2.y = pointCount; tJunctions[tJunctionCount++] = j2; // Until we ensure Outline is generated properly, we need this useless Check every correction. if (pointCount >= points.Length) return false; points[pointCount++] = intersects[i]; } unsafe { ModuleHandle.InsertionSort( tJunctions.UnsafePtr, 0, tJunctionCount - 1, new TessJunctionCompare()); } // Split edges along junctions for (int i = tJunctionCount - 1; i >= 0; --i) { var tJunction = tJunctions[i]; var e = tJunction.x; var edge = edges[e]; var s = edge.x; var t = edge.y; // Check if edge is not lexicographically sorted var a = points[s]; var b = points[t]; if (((a.x - b.x) < 0) || (a.x == b.x && (a.y - b.y) < 0)) { var tmp = s; s = t; t = tmp; } // Split leading edge edge.x = s; var last = edge.y = tJunction.y; edges[e] = edge; // If we are grouping edges by color, remember to track data // Split other edges while (i > 0 && tJunctions[i - 1].x == e) { var next = tJunctions[--i].y; int2 te = new int2(); te.x = last; te.y = next; edges[edgeCount++] = te; last = next; } int2 le = new int2(); le.x = last; le.y = t; edges[edgeCount++] = le; } return true; } internal static void RemoveDuplicatePoints(ref Array points, ref int pointCount, ref Array duplicates, ref int duplicateCount, Allocator allocator) { TessLink link = TessLink.CreateLink(pointCount, allocator); for (int i = 0; i < pointCount; ++i) { for (int j = i + 1; j < pointCount; ++j) { if (math.distance(points[i], points[j]) < kEpsilon) { link.Link(i, j); } } } duplicateCount = 0; for (var i = 0; i < pointCount; ++i) { var j = link.Find(i); if (j != i) { duplicateCount++; points[j] = math.min(points[i], points[j]); } } // Find Duplicates. if (duplicateCount != 0) { var prevPointCount = pointCount; pointCount = 0; for (var i = 0; i < prevPointCount; ++i) { var j = link.Find(i); if (j == i) { duplicates[i] = pointCount; points[pointCount++] = points[i]; } else { duplicates[i] = -1; } } // Update Duplicates. for (int i = 0; i < prevPointCount; ++i) { if (duplicates[i] < 0) { duplicates[i] = duplicates[link.Find(i)]; } } } TessLink.DestroyLink(link); } // Validate the Input Points ane Edges. internal static bool Validate(Allocator allocator, in NativeArray inputPoints, int pointCount, in NativeArray inputEdges, int edgeCount, ref NativeArray outputPoints, out int outputPointCount, ref NativeArray outputEdges, out int outputEdgeCount) { outputPointCount = 0; outputEdgeCount = 0; // Outline generated inputs can have differences in the range of 0.00001f.. See TwoLayers.psb sample. // Since PlanarGraph operates on double, scaling up and down does not result in loss of data. var precisionFudge = 10000.0f; var protectLoop = edgeCount; var requiresFix = true; var validGraph = false; // Processing Arrays. int startEdgeCount = edgeCount; Array duplicates = new Array(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory); Array edges = new Array(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory); Array tJunctions = new Array(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory); Array edgeIntersections = new Array(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory); Array points = new Array(pointCount * 2, pointCount * 8, allocator, NativeArrayOptions.UninitializedMemory); Array intersects = new Array(pointCount * 2, pointCount * 8, allocator, NativeArrayOptions.UninitializedMemory); // Initialize. for (int i = 0; i < pointCount; ++i) points[i] = inputPoints[i] * precisionFudge; unsafe { UnsafeUtility.MemCpy(edges.UnsafeReadOnlyPtr, inputEdges.GetUnsafePtr(), edgeCount * sizeof(int2)); } // Pre-clear duplicates, otherwise the following will simply fail. RemoveDuplicateEdges(ref edges, ref edgeCount, duplicates, 0); // While PSG is clean. while (requiresFix && --protectLoop > 0) { // Edge Edge Intersection. int intersectionCount = 0; validGraph = CalculateEdgeIntersections(edges, edgeCount, points, pointCount, ref edgeIntersections, ref intersects, ref intersectionCount); if (!validGraph) break; // Edge Point Intersection. T-Junction. int tJunctionCount = 0; validGraph = CalculateTJunctions(edges, edgeCount, points, pointCount, tJunctions, ref tJunctionCount); if (!validGraph) break; // Cut Overlapping Edges. validGraph = CutEdges(ref points, ref pointCount, ref edges, ref edgeCount, ref tJunctions, ref tJunctionCount, edgeIntersections, intersects, intersectionCount); if (!validGraph) break; // Remove Duplicate Points. int duplicateCount = 0; RemoveDuplicatePoints(ref points, ref pointCount, ref duplicates, ref duplicateCount, allocator); RemoveDuplicateEdges(ref edges, ref edgeCount, duplicates, duplicateCount); requiresFix = intersectionCount != 0 || tJunctionCount != 0; } if (validGraph) { // Finalize Output. outputEdgeCount = edgeCount; outputPointCount = pointCount; unsafe { UnsafeUtility.MemCpy(outputEdges.GetUnsafePtr(), edges.UnsafeReadOnlyPtr, edgeCount * sizeof(int2)); } for (int i = 0; i < pointCount; ++i) outputPoints[i] = new float2((float)(points[i].x / precisionFudge), (float)(points[i].y / precisionFudge)); } edges.Dispose(); points.Dispose(); intersects.Dispose(); duplicates.Dispose(); tJunctions.Dispose(); edgeIntersections.Dispose(); return (validGraph && protectLoop > 0); } } }