410 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			410 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | 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<int2> edges, ref int edgeCount, NativeArray<int> 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<int2, TessEdgeCompare>( | ||
|  |                     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<int2> edges, int edgeCount, NativeArray<double2> points, int pointCount, ref NativeArray<int2> results, ref NativeArray<double2> 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<int2, IntersectionCompare>( | ||
|  |                     NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(results), 0, resultCount - 1, tjc); | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static bool CalculateTJunctions(NativeArray<int2> edges, int edgeCount, NativeArray<double2> points, int pointCount, NativeArray<int2> 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<double2> points, ref int pointCount, ref NativeArray<int2> edges, ref int edgeCount, ref NativeArray<int2> tJunctions, ref int tJunctionCount, NativeArray<int2> intersections, NativeArray<double2> 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<int2, TessJunctionCompare>( | ||
|  |                     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<double2> points, ref int pointCount, ref NativeArray<int> 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<float2> inputPoints, int pointCount, NativeArray<int2> inputEdges, int edgeCount, ref NativeArray<float2> outputPoints, ref int outputPointCount, ref NativeArray<int2> 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<int> duplicates = new NativeArray<int>(ModuleHandle.kMaxEdgeCount, allocator); | ||
|  |             NativeArray<int2> edges = new NativeArray<int2>(ModuleHandle.kMaxEdgeCount, allocator); | ||
|  |             NativeArray<int2> tJunctions = new NativeArray<int2>(ModuleHandle.kMaxEdgeCount, allocator); | ||
|  |             NativeArray<int2> edgeIntersections = new NativeArray<int2>(ModuleHandle.kMaxEdgeCount, allocator); | ||
|  |             NativeArray<double2> points = new NativeArray<double2>(pointCount * 8, allocator); | ||
|  |             NativeArray<double2> intersects = new NativeArray<double2>(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); | ||
|  |         } | ||
|  |     } | ||
|  | } |