using Unity.Collections; using Unity.Mathematics; using Unity.Collections.LowLevel.Unsafe; namespace UnityEngine.Rendering.Universal.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 NativeArray edges, ref int edgeCount, NativeArray 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( NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(edges), 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(NativeArray edges, int edgeCount, NativeArray points, int pointCount, ref NativeArray results, ref NativeArray 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; } unsafe { var tjc = new IntersectionCompare(); tjc.edges = edges; tjc.points = points; ModuleHandle.InsertionSort( NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(results), 0, resultCount - 1, tjc); } return true; } internal static bool CalculateTJunctions(NativeArray edges, int edgeCount, NativeArray points, int pointCount, NativeArray 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 NativeArray points, ref int pointCount, ref NativeArray edges, ref int edgeCount, ref NativeArray tJunctions, ref int tJunctionCount, NativeArray intersections, NativeArray 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( NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(tJunctions), 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 NativeArray points, ref int pointCount, ref NativeArray 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, NativeArray inputPoints, int pointCount, NativeArray inputEdges, int edgeCount, ref NativeArray outputPoints, ref int outputPointCount, ref NativeArray outputEdges, ref int outputEdgeCount) { // 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. NativeArray duplicates = new NativeArray(ModuleHandle.kMaxEdgeCount, allocator); NativeArray edges = new NativeArray(ModuleHandle.kMaxEdgeCount, allocator); NativeArray tJunctions = new NativeArray(ModuleHandle.kMaxEdgeCount, allocator); NativeArray edgeIntersections = new NativeArray(ModuleHandle.kMaxEdgeCount, allocator); NativeArray points = new NativeArray(pointCount * 8, allocator); NativeArray intersects = new NativeArray(pointCount * 8, allocator); // Initialize. for (int i = 0; i < pointCount; ++i) points[i] = inputPoints[i] * precisionFudge; ModuleHandle.Copy(inputEdges, edges, edgeCount); // 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; ModuleHandle.Copy(edges, outputEdges, edgeCount); 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); } } }