using System; using System.Collections.Generic; using System.Reflection; using Unity.Profiling; using Unity.Collections; using Unity.Mathematics; using Unity.Collections.LowLevel.Unsafe; namespace UnityEngine.Rendering.Universal.UTess { enum UEventType { EVENT_POINT = 0, EVENT_END = 1, EVENT_START = 2, }; struct UEvent { public float2 a; public float2 b; public int idx; public int type; }; struct UHull { public float2 a; public float2 b; public int idx; public ArraySlice ilarray; public int ilcount; public ArraySlice iuarray; public int iucount; }; struct UStar { public ArraySlice points; public int pointCount; }; struct UBounds { public double2 min; public double2 max; }; struct UCircle { public float2 center; public float radius; }; struct UTriangle { public float2 va; public float2 vb; public float2 vc; public UCircle c; public float area; public int3 indices; }; struct UEncroachingSegment { public float2 a; public float2 b; public int index; } internal interface ICondition2 { bool Test(T x, U y, ref float t); } struct XCompare : IComparer { public int Compare(double a, double b) { return (a < b) ? -1 : 1; } } unsafe struct IntersectionCompare : IComparer { public NativeArray points; public NativeArray edges; public fixed double xvasort[4]; public fixed double xvbsort[4]; public int Compare(int2 a, int2 b) { var e1a = edges[a.x]; var e1b = edges[a.y]; var e2a = edges[b.x]; var e2b = edges[b.y]; xvasort[0] = points[e1a.x].x; xvasort[1] = points[e1a.y].x; xvasort[2] = points[e1b.x].x; xvasort[3] = points[e1b.y].x; xvbsort[0] = points[e2a.x].x; xvbsort[1] = points[e2a.y].x; xvbsort[2] = points[e2b.x].x; xvbsort[3] = points[e2b.y].x; fixed(double* xvasortPtr = xvasort) { ModuleHandle.InsertionSort(xvasortPtr, 0, 3, new XCompare()); } fixed(double* xvbsortPtr = xvbsort) { ModuleHandle.InsertionSort(xvbsortPtr, 0, 3, new XCompare()); } for (int i = 0; i < 4; ++i) if (xvasort[i] - xvbsort[i] != 0) return xvasort[i] < xvbsort[i] ? -1 : 1; return points[e1a.x].y < points[e1a.x].y ? -1 : 1; } } struct TessEventCompare : IComparer { public int Compare(UEvent a, UEvent b) { float f = (a.a.x - b.a.x); if (0 != f) return (f > 0) ? 1 : -1; f = (a.a.y - b.a.y); if (0 != f) return (f > 0) ? 1 : -1; int i = a.type - b.type; if (0 != i) return i; if (a.type != (int)UEventType.EVENT_POINT) { float o = ModuleHandle.OrientFast(a.a, a.b, b.b); if (0 != o) { return (o > 0) ? 1 : -1; } } return a.idx - b.idx; } } struct TessEdgeCompare : IComparer { public int Compare(int2 a, int2 b) { int i = a.x - b.x; if (0 != i) return i; i = a.y - b.y; return i; } } struct TessCellCompare : IComparer { public int Compare(int3 a, int3 b) { int i = a.x - b.x; if (0 != i) return i; i = a.y - b.y; if (0 != i) return i; i = a.z - b.z; return i; } } struct TessJunctionCompare : IComparer { public int Compare(int2 a, int2 b) { int i = a.x - b.x; if (0 != i) return i; i = a.y - b.y; return i; } } struct DelaEdgeCompare : IComparer { public int Compare(int4 a, int4 b) { int i = a.x - b.x; if (0 != i) return i; i = a.y - b.y; if (0 != i) return i; i = a.z - b.z; if (0 != i) return i; i = a.w - b.w; return i; } } struct TessLink { internal NativeArray roots; internal NativeArray ranks; internal static TessLink CreateLink(int count, Allocator allocator) { TessLink link = new TessLink(); link.roots = new NativeArray(count, allocator); link.ranks = new NativeArray(count, allocator); for (int i = 0; i < count; ++i) { link.roots[i] = i; link.ranks[i] = 0; } return link; } internal static void DestroyLink(TessLink link) { link.ranks.Dispose(); link.roots.Dispose(); } internal int Find(int x) { var x0 = x; while (roots[x] != x) { x = roots[x]; } while (roots[x0] != x) { var y = roots[x0]; roots[x0] = x; x0 = y; } return x; } internal void Link(int x, int y) { var xr = Find(x); var yr = Find(y); if (xr == yr) { return; } var xd = ranks[xr]; var yd = ranks[yr]; if (xd < yd) { roots[xr] = yr; } else if (yd < xd) { roots[yr] = xr; } else { roots[yr] = xr; ++ranks[xr]; } } }; internal struct ModuleHandle { // Max Edge Count with Subdivision allowed. This is already a very relaxed limit // and anything beyond are basically littered with numerous paths. internal static readonly int kMaxArea = 65536; internal static readonly int kMaxEdgeCount = 65536; internal static readonly int kMaxIndexCount = 65536; internal static readonly int kMaxVertexCount = 65536; internal static readonly int kMaxTriangleCount = kMaxIndexCount / 3; internal static readonly int kMaxRefineIterations = 48; internal static readonly int kMaxSmoothenIterations = 256; internal static readonly float kIncrementAreaFactor = 1.2f; internal static void Copy(NativeArray src, int srcIndex, NativeArray dst, int dstIndex, int length) where T : struct { NativeArray.Copy(src, srcIndex, dst, dstIndex, length); } internal static void Copy(NativeArray src, NativeArray dst, int length) where T : struct { Copy(src, 0, dst, 0, length); } internal static unsafe void InsertionSort(void* array, int lo, int hi, U comp) where T : struct where U : IComparer { int i, j; T t; for (i = lo; i < hi; i++) { j = i; t = UnsafeUtility.ReadArrayElement(array, i + 1); while (j >= lo && comp.Compare(t, UnsafeUtility.ReadArrayElement(array, j)) < 0) { UnsafeUtility.WriteArrayElement(array, j + 1, UnsafeUtility.ReadArrayElement(array, j)); j--; } UnsafeUtility.WriteArrayElement(array, j + 1, t); } } // Search Lower Bounds internal static int GetLower(NativeArray values, int count, U check, X condition) where T : struct where U : struct where X : ICondition2 { int l = 0; int h = count - 1; int i = l - 1; while (l <= h) { int m = ((int)(l + h)) >> 1; float t = 0; if (condition.Test(values[m], check, ref t)) { i = m; l = m + 1; } else { h = m - 1; } } return i; } // Search Upper Bounds internal static int GetUpper(NativeArray values, int count, U check, X condition) where T : struct where U : struct where X : ICondition2 { int l = 0; int h = count - 1; int i = h + 1; while (l <= h) { int m = ((int)(l + h)) >> 1; float t = 0; if (condition.Test(values[m], check, ref t)) { i = m; h = m - 1; } else { l = m + 1; } } return i; } // Search for Equal internal static int GetEqual(NativeArray values, int count, U check, X condition) where T : struct where U : struct where X : ICondition2 { int l = 0; int h = count - 1; while (l <= h) { int m = ((int)(l + h)) >> 1; float t = 0; condition.Test(values[m], check, ref t); if (t == 0) { return m; } else if (t <= 0) { l = m + 1; } else { h = m - 1; } } return -1; } // Simple Orientation test. internal static float OrientFast(float2 a, float2 b, float2 c) { float epsilon = 1.1102230246251565e-16f; float det = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y); if (math.abs(det) < epsilon) return 0; return det; } // This is needed when doing PlanarGraph as it requires high precision separation of points. internal static double OrientFastDouble(double2 a, double2 b, double2 c) { double epsilon = 1.1102230246251565e-16f; double det = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y); if (math.abs(det) < epsilon) return 0; return det; } internal static UCircle CircumCircle(UTriangle tri) { float xa = tri.va.x * tri.va.x; float xb = tri.vb.x * tri.vb.x; float xc = tri.vc.x * tri.vc.x; float ya = tri.va.y * tri.va.y; float yb = tri.vb.y * tri.vb.y; float yc = tri.vc.y * tri.vc.y; float c = 2f * ((tri.vb.x - tri.va.x) * (tri.vc.y - tri.va.y) - (tri.vb.y - tri.va.y) * (tri.vc.x - tri.va.x)); float x = ((tri.vc.y - tri.va.y) * (xb - xa + yb - ya) + (tri.va.y - tri.vb.y) * (xc - xa + yc - ya)) / c; float y = ((tri.va.x - tri.vc.x) * (xb - xa + yb - ya) + (tri.vb.x - tri.va.x) * (xc - xa + yc - ya)) / c; float vx = (tri.va.x - x); float vy = (tri.va.y - y); return new UCircle { center = new float2(x, y), radius = math.sqrt((vx * vx) + (vy * vy)) }; } internal static bool IsInsideCircle(UCircle c, float2 v) { return math.distance(v, c.center) < c.radius; } internal static float TriangleArea(float2 va, float2 vb, float2 vc) { float3 a = new float3(va.x, va.y, 0); float3 b = new float3(vb.x, vb.y, 0); float3 c = new float3(vc.x, vc.y, 0); float3 v = math.cross(a - b, a - c); return math.abs(v.z) * 0.5f; } internal static float Sign(float2 p1, float2 p2, float2 p3) { return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); } internal static bool IsInsideTriangle(float2 pt, float2 v1, float2 v2, float2 v3) { float d1, d2, d3; bool has_neg, has_pos; d1 = Sign(pt, v1, v2); d2 = Sign(pt, v2, v3); d3 = Sign(pt, v3, v1); has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0); has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0); return !(has_neg && has_pos); } internal static bool IsInsideTriangleApproximate(float2 pt, float2 v1, float2 v2, float2 v3) { float d0, d1, d2, d3; d0 = TriangleArea(v1, v2, v3); d1 = TriangleArea(pt, v1, v2); d2 = TriangleArea(pt, v2, v3); d3 = TriangleArea(pt, v3, v1); float epsilon = 1.1102230246251565e-16f; return Mathf.Abs(d0 - (d1 + d2 + d3)) < epsilon; } internal static bool IsInsideCircle(float2 a, float2 b, float2 c, float2 p) { float ab = math.dot(a, a); float cd = math.dot(b, b); float ef = math.dot(c, c); float ax = a.x; float ay = a.y; float bx = b.x; float by = b.y; float cx = c.x; float cy = c.y; float circum_x = (ab * (cy - by) + cd * (ay - cy) + ef * (by - ay)) / (ax * (cy - by) + bx * (ay - cy) + cx * (by - ay)); float circum_y = (ab * (cx - bx) + cd * (ax - cx) + ef * (bx - ax)) / (ay * (cx - bx) + by * (ax - cx) + cy * (bx - ax)); float2 circum = new float2(); circum.x = circum_x / 2; circum.y = circum_y / 2; float circum_radius = math.distance(a, circum); float dist = math.distance(p, circum); return circum_radius - dist > 0.00001f; } internal static void BuildTriangles(NativeArray vertices, int vertexCount, NativeArray indices, int indexCount, ref NativeArray triangles, ref int triangleCount, ref float maxArea, ref float avgArea, ref float minArea) { // Check if there are invalid triangles or segments. for (int i = 0; i < indexCount; i += 3) { UTriangle tri = new UTriangle(); var i0 = indices[i + 0]; var i1 = indices[i + 1]; var i2 = indices[i + 2]; tri.va = vertices[i0]; tri.vb = vertices[i1]; tri.vc = vertices[i2]; tri.c = CircumCircle(tri); tri.area = TriangleArea(tri.va, tri.vb, tri.vc); maxArea = math.max(tri.area, maxArea); minArea = math.min(tri.area, minArea); avgArea = avgArea + tri.area; triangles[triangleCount++] = tri; } avgArea = avgArea / triangleCount; } internal static void BuildTriangles(NativeArray vertices, int vertexCount, NativeArray indices, int indexCount, ref NativeArray triangles, ref int triangleCount, ref float maxArea, ref float avgArea, ref float minArea, ref float maxEdge, ref float avgEdge, ref float minEdge) { // Check if there are invalid triangles or segments. for (int i = 0; i < indexCount; i += 3) { UTriangle tri = new UTriangle(); var i0 = indices[i + 0]; var i1 = indices[i + 1]; var i2 = indices[i + 2]; tri.va = vertices[i0]; tri.vb = vertices[i1]; tri.vc = vertices[i2]; tri.c = CircumCircle(tri); tri.area = TriangleArea(tri.va, tri.vb, tri.vc); maxArea = math.max(tri.area, maxArea); minArea = math.min(tri.area, minArea); avgArea = avgArea + tri.area; var e1 = math.distance(tri.va, tri.vb); var e2 = math.distance(tri.vb, tri.vc); var e3 = math.distance(tri.vc, tri.va); maxEdge = math.max(e1, maxEdge); maxEdge = math.max(e2, maxEdge); maxEdge = math.max(e3, maxEdge); minEdge = math.min(e1, minEdge); minEdge = math.min(e2, minEdge); minEdge = math.min(e3, minEdge); avgEdge = avgEdge + e1; avgEdge = avgEdge + e2; avgEdge = avgEdge + e3; triangles[triangleCount++] = tri; } avgArea = avgArea / triangleCount; avgEdge = avgEdge / indexCount; } internal static void BuildTrianglesAndEdges(NativeArray vertices, int vertexCount, NativeArray indices, int indexCount, ref NativeArray triangles, ref int triangleCount, ref NativeArray delaEdges, ref int delaEdgeCount, ref float maxArea, ref float avgArea, ref float minArea) { // Check if there are invalid triangles or segments. for (int i = 0; i < indexCount; i += 3) { UTriangle tri = new UTriangle(); var i0 = indices[i + 0]; var i1 = indices[i + 1]; var i2 = indices[i + 2]; tri.va = vertices[i0]; tri.vb = vertices[i1]; tri.vc = vertices[i2]; tri.c = CircumCircle(tri); tri.area = TriangleArea(tri.va, tri.vb, tri.vc); maxArea = math.max(tri.area, maxArea); minArea = math.min(tri.area, minArea); avgArea = avgArea + tri.area; tri.indices = new int3(i0, i1, i2); // Outputs. delaEdges[delaEdgeCount++] = new int4(math.min(i0, i1), math.max(i0, i1), triangleCount, -1); delaEdges[delaEdgeCount++] = new int4(math.min(i1, i2), math.max(i1, i2), triangleCount, -1); delaEdges[delaEdgeCount++] = new int4(math.min(i2, i0), math.max(i2, i0), triangleCount, -1); triangles[triangleCount++] = tri; } avgArea = avgArea / triangleCount; } static void CopyGraph(NativeArray srcPoints, int srcPointCount, ref NativeArray dstPoints, ref int dstPointCount, NativeArray srcEdges, int srcEdgeCount, ref NativeArray dstEdges, ref int dstEdgeCount) { dstEdgeCount = srcEdgeCount; dstPointCount = srcPointCount; Copy(srcEdges, dstEdges, srcEdgeCount); Copy(srcPoints, dstPoints, srcPointCount); } static void CopyGeometry(NativeArray srcIndices, int srcIndexCount, ref NativeArray dstIndices, ref int dstIndexCount, NativeArray srcVertices, int srcVertexCount, ref NativeArray dstVertices, ref int dstVertexCount) { dstIndexCount = srcIndexCount; dstVertexCount = srcVertexCount; Copy(srcIndices, dstIndices, srcIndexCount); Copy(srcVertices, dstVertices, srcVertexCount); } static void TransferOutput(NativeArray srcEdges, int srcEdgeCount, ref NativeArray dstEdges, ref int dstEdgeCount, NativeArray srcIndices, int srcIndexCount, ref NativeArray dstIndices, ref int dstIndexCount, NativeArray srcVertices, int srcVertexCount, ref NativeArray dstVertices, ref int dstVertexCount) { dstEdgeCount = srcEdgeCount; dstIndexCount = srcIndexCount; dstVertexCount = srcVertexCount; Copy(srcEdges, dstEdges, srcEdgeCount); Copy(srcIndices, dstIndices, srcIndexCount); Copy(srcVertices, dstVertices, srcVertexCount); } static void GraphConditioner(NativeArray points, ref NativeArray pgPoints, ref int pgPointCount, ref NativeArray pgEdges, ref int pgEdgeCount, bool resetTopology) { var min = new float2(math.INFINITY, math.INFINITY); var max = float2.zero; for (int i = 0; i < points.Length; ++i) { min = math.min(points[i], min); max = math.max(points[i], max); } var ext = (max - min); var mid = ext * 0.5f; var kNonRect = 0.0001f; // Construct a simple convex hull rect!. pgPointCount = resetTopology ? 0 : pgPointCount; var pc = pgPointCount; pgPoints[pgPointCount++] = new float2(min.x, min.y); pgPoints[pgPointCount++] = new float2(min.x - kNonRect, min.y + mid.y); pgPoints[pgPointCount++] = new float2(min.x, max.y); pgPoints[pgPointCount++] = new float2(min.x + mid.x, max.y + kNonRect); pgPoints[pgPointCount++] = new float2(max.x, max.y); pgPoints[pgPointCount++] = new float2(max.x + kNonRect, min.y + mid.y); pgPoints[pgPointCount++] = new float2(max.x, min.y); pgPoints[pgPointCount++] = new float2(min.x + mid.x, min.y - kNonRect); pgEdgeCount = 8; pgEdges[0] = new int2(pc + 0, pc + 1); pgEdges[1] = new int2(pc + 1, pc + 2); pgEdges[2] = new int2(pc + 2, pc + 3); pgEdges[3] = new int2(pc + 3, pc + 4); pgEdges[4] = new int2(pc + 4, pc + 5); pgEdges[5] = new int2(pc + 5, pc + 6); pgEdges[6] = new int2(pc + 6, pc + 7); pgEdges[7] = new int2(pc + 7, pc + 0); } // Reorder vertices. static void Reorder(int startVertexCount, int index, ref NativeArray indices, ref int indexCount, ref NativeArray vertices, ref int vertexCount) { var found = false; for (var i = 0; i < indexCount; ++i) { if (indices[i] != index) continue; found = true; break; } if (!found) { vertexCount--; vertices[index] = vertices[vertexCount]; for (var i = 0; i < indexCount; ++i) if (indices[i] == vertexCount) indices[i] = index; } } // Perform Sanitization. internal static void VertexCleanupConditioner(int startVertexCount, ref NativeArray indices, ref int indexCount, ref NativeArray vertices, ref int vertexCount) { for (int i = startVertexCount; i < vertexCount; ++i) { Reorder(startVertexCount, i, ref indices, ref indexCount, ref vertices, ref vertexCount); } } public static float4 ConvexQuad(Allocator allocator, NativeArray points, NativeArray edges, ref NativeArray outVertices, ref int outVertexCount, ref NativeArray outIndices, ref int outIndexCount, ref NativeArray outEdges, ref int outEdgeCount) { // Inputs are garbage, just early out. float4 ret = float4.zero; outEdgeCount = 0; outIndexCount = 0; outVertexCount = 0; if (points.Length < 3 || points.Length >= kMaxVertexCount) return ret; // Ensure inputs form a proper PlanarGraph. int pgEdgeCount = 0, pgPointCount = 0; NativeArray pgEdges = new NativeArray(kMaxEdgeCount, allocator); NativeArray pgPoints = new NativeArray(kMaxVertexCount, allocator); // Valid Edges and Paths, correct the Planar Graph. If invalid create a simple convex hull rect. GraphConditioner(points, ref pgPoints, ref pgPointCount, ref pgEdges, ref pgEdgeCount, true); Tessellator.Tessellate(allocator, pgPoints, pgPointCount, pgEdges, pgEdgeCount, ref outVertices, ref outVertexCount, ref outIndices, ref outIndexCount); // Dispose Temp Memory. pgPoints.Dispose(); pgEdges.Dispose(); return ret; } public static float4 Tessellate(Allocator allocator, NativeArray points, NativeArray edges, ref NativeArray outVertices, ref int outVertexCount, ref NativeArray outIndices, ref int outIndexCount, ref NativeArray outEdges, ref int outEdgeCount) { // Inputs are garbage, just early out. float4 ret = float4.zero; outEdgeCount = 0; outIndexCount = 0; outVertexCount = 0; if (points.Length < 3 || points.Length >= kMaxVertexCount) return ret; // Ensure inputs form a proper PlanarGraph. bool validGraph = false, handleEdgeCase = false; int pgEdgeCount = 0, pgPointCount = 0; NativeArray pgEdges = new NativeArray(edges.Length * 8, allocator); NativeArray pgPoints = new NativeArray(points.Length * 4, allocator); // Valid Edges and Paths, correct the Planar Graph. If invalid create a simple convex hull rect. if (0 != edges.Length) { validGraph = PlanarGraph.Validate(allocator, points, points.Length, edges, edges.Length, ref pgPoints, ref pgPointCount, ref pgEdges, ref pgEdgeCount); } // Fallbacks are now handled by the Higher level packages. Enable if UTess needs to handle it. // #if UTESS_QUAD_FALLBACK // if (!validGraph) // { // pgPointCount = 0; // handleEdgeCase = true; // ModuleHandle.Copy(points, pgPoints, points.Length); // GraphConditioner(points, ref pgPoints, ref pgPointCount, ref pgEdges, ref pgEdgeCount, false); // } // #else // If its not a valid Graph simply return back input Data without triangulation instead of going through UTess (pointless wasted cpu cycles). if (!validGraph) { outEdgeCount = edges.Length; outVertexCount = points.Length; ModuleHandle.Copy(edges, outEdges, edges.Length); ModuleHandle.Copy(points, outVertices, points.Length); } // Do a proper Delaunay Triangulation if Inputs are valid. if (pgPointCount > 2 && pgEdgeCount > 2) { // Tessellate does not add new points, only PG and SD does. Assuming each point creates a degenerate triangle, * 4 is more than enough. NativeArray tsIndices = new NativeArray(pgPointCount * 8, allocator); NativeArray tsVertices = new NativeArray(pgPointCount * 4, allocator); int tsIndexCount = 0, tsVertexCount = 0; validGraph = Tessellator.Tessellate(allocator, pgPoints, pgPointCount, pgEdges, pgEdgeCount, ref tsVertices, ref tsVertexCount, ref tsIndices, ref tsIndexCount); if (validGraph) { // Copy Out TransferOutput(pgEdges, pgEdgeCount, ref outEdges, ref outEdgeCount, tsIndices, tsIndexCount, ref outIndices, ref outIndexCount, tsVertices, tsVertexCount, ref outVertices, ref outVertexCount); if (handleEdgeCase == true) outEdgeCount = 0; } tsVertices.Dispose(); tsIndices.Dispose(); } // Dispose Temp Memory. pgPoints.Dispose(); pgEdges.Dispose(); return ret; } } }