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.U2D.Common.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<int> ilarray; public int ilcount; public ArraySlice<int> iuarray; public int iucount; }; struct UStar { public ArraySlice<int> 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<in T, in U> { bool Test(T x, U y, ref float t); } struct XCompare : IComparer<double> { public int Compare(double a, double b) { return (a < b) ? -1 : 1; } } unsafe struct IntersectionCompare : IComparer<int2> { public Array<double2> points; public Array<int2> 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<double, XCompare>(xvasortPtr, 0, 3, new XCompare()); } fixed (double* xvbsortPtr = xvbsort) { ModuleHandle.InsertionSort<double, XCompare>(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<UEvent> { 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<int2> { 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<int3> { 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<int2> { 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<int4> { 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<int> roots; internal NativeArray<int> ranks; internal static TessLink CreateLink(int count, Allocator allocator) { TessLink link = new TessLink(); link.roots = new NativeArray<int>(count, allocator); link.ranks = new NativeArray<int>(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<T>(NativeArray<T> src, int srcIndex, NativeArray<T> dst, int dstIndex, int length) where T : struct { NativeArray<T>.Copy(src, srcIndex, dst, dstIndex, length); } internal static void Copy<T>(NativeArray<T> src, NativeArray<T> dst, int length) where T : struct { Copy(src, 0, dst, 0, length); } internal static unsafe void InsertionSort<T, U>(void* array, int lo, int hi, U comp) where T : struct where U : IComparer<T> { int i, j; T t; for (i = lo; i < hi; i++) { j = i; t = UnsafeUtility.ReadArrayElement<T>(array, i + 1); while (j >= lo && comp.Compare(t, UnsafeUtility.ReadArrayElement<T>(array, j)) < 0) { UnsafeUtility.WriteArrayElement<T>(array, j + 1, UnsafeUtility.ReadArrayElement<T>(array, j)); j--; } UnsafeUtility.WriteArrayElement<T>(array, j + 1, t); } } // Search Lower Bounds internal static int GetLower<T, U, X>(NativeArray<T> values, int count, U check, X condition) where T : struct where U : struct where X : ICondition2<T, U> { 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<T, U, X>(NativeArray<T> values, int count, U check, X condition) where T : struct where U : struct where X : ICondition2<T, U> { 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<T, U, X>(Array<T> values, int count, U check, X condition) where T : struct where U : struct where X : ICondition2<T, U> { 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; } // Search for Equal internal static int GetEqual<T, U, X>(NativeArray<T> values, int count, U check, X condition) where T : struct where U : struct where X : ICondition2<T, U> { 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; } // From https://www.cs.cmu.edu/afs/cs/project/quake/public/code/predicates.c and is public domain. Can't find one within Unity. internal static float OrientFast(float2 a, float2 b, float2 c) { float epsilon = 1.1102230246251565e-16f; float errbound3 = (3.0f + 16.0f * epsilon) * epsilon; float l = (a.y - c.y) * (b.x - c.x); float r = (a.x - c.x) * (b.y - c.y); float det = l - r; float s = 0; if (l > 0) { if (r <= 0) { return det; } else { s = l + r; } } else if (l < 0) { if (r >= 0) { return det; } else { s = -(l + r); } } else { return det; } float tol = errbound3 * s; if (det >= tol || det <= -tol) { return det; } return epsilon; } // 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 errbound3 = (3.0 + 16.0 * epsilon) * epsilon; double l = (a.y - c.y) * (b.x - c.x); double r = (a.x - c.x) * (b.y - c.y); double det = l - r; double s = 0; if (l > 0) { if (r <= 0) { return det; } else { s = l + r; } } else if (l < 0) { if (r >= 0) { return det; } else { s = -(l + r); } } else { return det; } double tol = errbound3 * s; if (det >= tol || det <= -tol) { return det; } return epsilon; } 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<float2> vertices, int vertexCount, NativeArray<int> indices, int indexCount, ref NativeArray<UTriangle> 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<float2> vertices, int vertexCount, NativeArray<int> indices, int indexCount, ref Array<UTriangle> 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<float2> vertices, int vertexCount, NativeArray<int> indices, int indexCount, ref NativeArray<UTriangle> 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<float2> vertices, int vertexCount, NativeArray<int> indices, int indexCount, ref NativeArray<UTriangle> triangles, ref int triangleCount, ref NativeArray<int4> 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<float2> srcPoints, int srcPointCount, ref NativeArray<float2> dstPoints, ref int dstPointCount, NativeArray<int2> srcEdges, int srcEdgeCount, ref NativeArray<int2> dstEdges, ref int dstEdgeCount) { dstEdgeCount = srcEdgeCount; dstPointCount = srcPointCount; Copy(srcEdges, dstEdges, srcEdgeCount); Copy(srcPoints, dstPoints, srcPointCount); } static void CopyGeometry(NativeArray<int> srcIndices, int srcIndexCount, ref NativeArray<int> dstIndices, ref int dstIndexCount, NativeArray<float2> srcVertices, int srcVertexCount, ref NativeArray<float2> dstVertices, ref int dstVertexCount) { dstIndexCount = srcIndexCount; dstVertexCount = srcVertexCount; Copy(srcIndices, dstIndices, srcIndexCount); Copy(srcVertices, dstVertices, srcVertexCount); } static void TransferOutput(NativeArray<int2> srcEdges, int srcEdgeCount, ref NativeArray<int2> dstEdges, ref int dstEdgeCount, NativeArray<int> srcIndices, int srcIndexCount, ref NativeArray<int> dstIndices, ref int dstIndexCount, NativeArray<float2> srcVertices, int srcVertexCount, ref NativeArray<float2> 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<float2> points, ref NativeArray<float2> pgPoints, ref int pgPointCount, ref NativeArray<int2> 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<int> indices, ref int indexCount, ref NativeArray<float2> 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<int> indices, ref int indexCount, ref NativeArray<float2> 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<float2> points, NativeArray<int2> edges, ref NativeArray<float2> outVertices, ref int outVertexCount, ref NativeArray<int> outIndices, ref int outIndexCount, ref NativeArray<int2> 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<int2> pgEdges = new NativeArray<int2>(kMaxEdgeCount, allocator); NativeArray<float2> pgPoints = new NativeArray<float2>(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, in NativeArray<float2> points, in NativeArray<int2> edges, ref NativeArray<float2> outVertices, out int outVertexCount, ref NativeArray<int> outIndices, out int outIndexCount, ref NativeArray<int2> outEdges, out 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<int2> pgEdges = new NativeArray<int2>(edges.Length * 8, allocator); NativeArray<float2> pgPoints = new NativeArray<float2>(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, in points, points.Length, in edges, edges.Length, ref pgPoints,out pgPointCount, ref pgEdges, out 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<int> tsIndices = new NativeArray<int>(pgPointCount * 8, allocator); NativeArray<float2> tsVertices = new NativeArray<float2>(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; } public static float4 Subdivide(Allocator allocator, NativeArray<float2> points, NativeArray<int2> edges, ref NativeArray<float2> outVertices, ref int outVertexCount, ref NativeArray<int> outIndices, ref int outIndexCount, ref NativeArray<int2> outEdges, ref int outEdgeCount, float areaFactor, float targetArea, int refineIterations, int smoothenIterations) { // Inputs are garbage, just early out. float4 ret = float4.zero; outEdgeCount = 0; outIndexCount = 0; outVertexCount = 0; if (points.Length < 3 || points.Length >= kMaxVertexCount || 0 == edges.Length) return ret; // Do a proper Delaunay Triangulation. int tsIndexCount = 0, tsVertexCount = 0; NativeArray<int> tsIndices = new NativeArray<int>(kMaxIndexCount, allocator); NativeArray<float2> tsVertices = new NativeArray<float2>(kMaxVertexCount, allocator); var validGraph = Tessellator.Tessellate(allocator, points, points.Length, edges, edges.Length, ref tsVertices, ref tsVertexCount, ref tsIndices, ref tsIndexCount); // Refinement and Smoothing. bool refined = false; bool refinementRequired = (targetArea != 0 || areaFactor != 0); if (validGraph && refinementRequired) { // Do Refinement until success. float maxArea = 0; float incArea = 0; int rfEdgeCount = 0, rfPointCount = 0, rfIndexCount = 0, rfVertexCount = 0; NativeArray<int2> rfEdges = new NativeArray<int2>(kMaxEdgeCount, allocator); NativeArray<float2> rfPoints = new NativeArray<float2>(kMaxVertexCount, allocator); NativeArray<int> rfIndices = new NativeArray<int>(kMaxIndexCount, allocator); NativeArray<float2> rfVertices = new NativeArray<float2>(kMaxVertexCount, allocator); ret.x = 0; refineIterations = Math.Min(refineIterations, kMaxRefineIterations); if (targetArea != 0) { // Increment for Iterations. incArea = (targetArea / 10); while (targetArea < kMaxArea && refineIterations > 0) { // Do Mesh Refinement. CopyGraph(points, points.Length, ref rfPoints, ref rfPointCount, edges, edges.Length, ref rfEdges, ref rfEdgeCount); CopyGeometry(tsIndices, tsIndexCount, ref rfIndices, ref rfIndexCount, tsVertices, tsVertexCount, ref rfVertices, ref rfVertexCount); refined = Refinery.Condition(allocator, areaFactor, targetArea, ref rfPoints, ref rfPointCount, ref rfEdges, ref rfEdgeCount, ref rfVertices, ref rfVertexCount, ref rfIndices, ref rfIndexCount, ref maxArea); if (refined && rfIndexCount > rfPointCount) { // Copy Out ret.x = areaFactor; TransferOutput(rfEdges, rfEdgeCount, ref outEdges, ref outEdgeCount, rfIndices, rfIndexCount, ref outIndices, ref outIndexCount, rfVertices, rfVertexCount, ref outVertices, ref outVertexCount); break; } refined = false; targetArea = targetArea + incArea; refineIterations--; } } else if (areaFactor != 0) { // Increment for Iterations. areaFactor = math.lerp(0.1f, 0.54f, (areaFactor - 0.05f) / 0.45f); // Specific to Animation. incArea = (areaFactor / 10); while (areaFactor < 0.8f && refineIterations > 0) { // Do Mesh Refinement. CopyGraph(points, points.Length, ref rfPoints, ref rfPointCount, edges, edges.Length, ref rfEdges, ref rfEdgeCount); CopyGeometry(tsIndices, tsIndexCount, ref rfIndices, ref rfIndexCount, tsVertices, tsVertexCount, ref rfVertices, ref rfVertexCount); refined = Refinery.Condition(allocator, areaFactor, targetArea, ref rfPoints, ref rfPointCount, ref rfEdges, ref rfEdgeCount, ref rfVertices, ref rfVertexCount, ref rfIndices, ref rfIndexCount, ref maxArea); if (refined && rfIndexCount > rfPointCount) { // Copy Out ret.x = areaFactor; TransferOutput(rfEdges, rfEdgeCount, ref outEdges, ref outEdgeCount, rfIndices, rfIndexCount, ref outIndices, ref outIndexCount, rfVertices, rfVertexCount, ref outVertices, ref outVertexCount); break; } refined = false; areaFactor = areaFactor + incArea; refineIterations--; } } if (refined) { // Sanitize generated geometry data. var preSmoothen = outVertexCount; if (ret.x != 0) VertexCleanupConditioner(tsVertexCount, ref rfIndices, ref rfIndexCount, ref rfVertices, ref rfVertexCount); // Smoothen. At this point only vertex relocation is allowed, not vertex addition/removal. // Note: Only refined mesh contains Steiner points and we only smoothen these points. ret.y = 0; smoothenIterations = math.clamp(smoothenIterations, 0, kMaxSmoothenIterations); while (smoothenIterations > 0) { var smoothen = Smoothen.Condition(allocator, ref rfPoints, rfPointCount, rfEdges, rfEdgeCount, ref rfVertices, ref rfVertexCount, ref rfIndices, ref rfIndexCount); if (!smoothen) break; // Copy Out ret.y = (float)(smoothenIterations); TransferOutput(rfEdges, rfEdgeCount, ref outEdges, ref outEdgeCount, rfIndices, rfIndexCount, ref outIndices, ref outIndexCount, rfVertices, rfVertexCount, ref outVertices, ref outVertexCount); smoothenIterations--; } // Sanitize generated geometry data. var postSmoothen = outVertexCount; if (ret.y != 0) VertexCleanupConditioner(tsVertexCount, ref outIndices, ref outIndexCount, ref outVertices, ref outVertexCount); } rfVertices.Dispose(); rfIndices.Dispose(); rfPoints.Dispose(); rfEdges.Dispose(); } // Refinement failed but Graph succeeded. if (validGraph && !refined) { // Copy Out TransferOutput(edges, edges.Length, ref outEdges, ref outEdgeCount, tsIndices, tsIndexCount, ref outIndices, ref outIndexCount, tsVertices, tsVertexCount, ref outVertices, ref outVertexCount); } // Dispose Temp Memory. tsVertices.Dispose(); tsIndices.Dispose(); return ret; } } }