Singularity/Library/PackageCache/com.unity.2d.common@6.0.6/Runtime/UTess2D/PlanarGraph.cs
2024-05-06 11:45:45 -07:00

422 lines
16 KiB
C#

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<int2> edges, ref int edgeCount, Array<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>(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<int2> edges, int edgeCount, Array<double2> points, int pointCount, ref Array<int2> results, ref Array<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;
}
var tjc = new IntersectionCompare();
tjc.edges = edges;
tjc.points = points;
unsafe
{
ModuleHandle.InsertionSort<int2, IntersectionCompare>(results.UnsafePtr, 0, resultCount - 1, tjc);
}
return true;
}
internal static bool CalculateTJunctions(Array<int2> edges, int edgeCount, Array<double2> points, int pointCount, Array<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 Array<double2> points, ref int pointCount, ref Array<int2> edges, ref int edgeCount, ref Array<int2> tJunctions, ref int tJunctionCount, Array<int2> intersections, Array<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>( 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<double2> points, ref int pointCount, ref Array<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, in NativeArray<float2> inputPoints, int pointCount, in NativeArray<int2> inputEdges, int edgeCount, ref NativeArray<float2> outputPoints, out int outputPointCount, ref NativeArray<int2> 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<int> duplicates = new Array<int>(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory);
Array<int2> edges = new Array<int2>(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory);
Array<int2> tJunctions = new Array<int2>(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory);
Array<int2> edgeIntersections = new Array<int2>(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory);
Array<double2> points = new Array<double2>(pointCount * 2, pointCount * 8, allocator, NativeArrayOptions.UninitializedMemory);
Array<double2> intersects = new Array<double2>(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);
}
}
}