using System; using Unity.Profiling; using Unity.Collections; using Unity.Mathematics; using Unity.Collections.LowLevel.Unsafe; namespace UnityEngine.U2D.Common.UTess { // Constrained Delaunay Triangulation. struct Tessellator { // For Processing. NativeArray m_Edges; NativeArray m_Stars; Array m_Cells; int m_CellCount; // For Storage. NativeArray m_ILArray; NativeArray m_IUArray; NativeArray m_SPArray; int m_NumEdges; int m_NumHulls; int m_NumPoints; int m_StarCount; // Intermediates. NativeArray m_Flags; NativeArray m_Neighbors; NativeArray m_Constraints; Allocator m_Allocator; struct TestHullPointL : ICondition2 { public bool Test(UHull h, float2 p, ref float t) { t = ModuleHandle.OrientFast(h.a, h.b, p); return t < 0; } } struct TestHullPointU : ICondition2 { public bool Test(UHull h, float2 p, ref float t) { t = ModuleHandle.OrientFast(h.a, h.b, p); return t > 0; } } static float FindSplit(UHull hull, UEvent edge) { float d = 0; if (hull.a.x < edge.a.x) { d = ModuleHandle.OrientFast(hull.a, hull.b, edge.a); } else { d = ModuleHandle.OrientFast(edge.b, edge.a, hull.a); } if (0 != d) { return d; } if (edge.b.x < hull.b.x) { d = ModuleHandle.OrientFast(hull.a, hull.b, edge.b); } else { d = ModuleHandle.OrientFast(edge.b, edge.a, hull.b); } if (0 != d) { return d; } return hull.idx - edge.idx; } struct TestHullEventLe : ICondition2 { public bool Test(UHull h, UEvent p, ref float t) { t = FindSplit(h, p); return t <= 0; } } struct TestHullEventE : ICondition2 { public bool Test(UHull h, UEvent p, ref float t) { t = FindSplit(h, p); return t == 0; } } void SetAllocator(Allocator allocator) { m_Allocator = allocator; } bool AddPoint(NativeArray hulls, int hullCount, NativeArray points, float2 p, int idx) { int l = ModuleHandle.GetLower(hulls, hullCount, p, new TestHullPointL()); int u = ModuleHandle.GetUpper(hulls, hullCount, p, new TestHullPointU()); if (l < 0 || u < 0) return false; for (int i = l; i < u; ++i) { UHull hull = hulls[i]; int m = hull.ilcount; while (m > 1 && ModuleHandle.OrientFast(points[hull.ilarray[m - 2]], points[hull.ilarray[m - 1]], p) > 0) { int3 c = new int3(); c.x = hull.ilarray[m - 1]; c.y = hull.ilarray[m - 2]; c.z = idx; m_Cells[m_CellCount++] = c; m -= 1; } hull.ilcount = m + 1; if (hull.ilcount > hull.ilarray.Length) return false; hull.ilarray[m] = idx; m = hull.iucount; while (m > 1 && ModuleHandle.OrientFast(points[hull.iuarray[m - 2]], points[hull.iuarray[m - 1]], p) < 0) { int3 c = new int3(); c.x = hull.iuarray[m - 2]; c.y = hull.iuarray[m - 1]; c.z = idx; m_Cells[m_CellCount++] = c; m -= 1; } hull.iucount = m + 1; if (hull.iucount > hull.iuarray.Length) return false; hull.iuarray[m] = idx; hulls[i] = hull; } return true; } static void InsertHull(NativeArray Hulls, int Pos, ref int Count, UHull Value) { if (Count < Hulls.Length - 1) { for (int i = Count; i > Pos; --i) Hulls[i] = Hulls[i - 1]; Hulls[Pos] = Value; Count++; } } static void EraseHull(NativeArray Hulls, int Pos, ref int Count) { if (Count < Hulls.Length) { for (int i = Pos; i < Count - 1; ++i) Hulls[i] = Hulls[i + 1]; Count--; } } bool SplitHulls(NativeArray hulls, ref int hullCount, NativeArray points, UEvent evt) { int index = ModuleHandle.GetLower(hulls, hullCount, evt, new TestHullEventLe()); if (index < 0) return false; UHull hull = hulls[index]; UHull newHull; newHull.a = evt.a; newHull.b = evt.b; newHull.idx = evt.idx; int y = hull.iuarray[hull.iucount - 1]; newHull.iuarray = new ArraySlice(m_IUArray, newHull.idx * m_NumHulls, m_NumHulls); newHull.iucount = hull.iucount; for (int i = 0; i < newHull.iucount; ++i) newHull.iuarray[i] = hull.iuarray[i]; hull.iuarray[0] = y; hull.iucount = 1; hulls[index] = hull; newHull.ilarray = new ArraySlice(m_ILArray, newHull.idx * m_NumHulls, m_NumHulls); newHull.ilarray[0] = y; newHull.ilcount = 1; InsertHull(hulls, index + 1, ref hullCount, newHull); return true; } bool MergeHulls(NativeArray hulls, ref int hullCount, NativeArray points, UEvent evt) { float2 temp = evt.a; evt.a = evt.b; evt.b = temp; int index = ModuleHandle.GetEqual(hulls, hullCount, evt, new TestHullEventE()); if (index < 0) return false; UHull upper = hulls[index]; UHull lower = hulls[index - 1]; lower.iucount = upper.iucount; for (int i = 0; i < lower.iucount; ++i) lower.iuarray[i] = upper.iuarray[i]; hulls[index - 1] = lower; EraseHull(hulls, index, ref hullCount); return true; } static void InsertUniqueEdge(NativeArray edges, int2 e, ref int edgeCount) { TessEdgeCompare edgeComparer = new TessEdgeCompare(); var validEdge = true; for (int j = 0; validEdge && j < edgeCount; ++j) if (edgeComparer.Compare(e, edges[j]) == 0) validEdge = false; if (validEdge) edges[edgeCount++] = e; } void PrepareDelaunay(NativeArray edges, int edgeCount) { m_StarCount = m_CellCount * 3; m_Stars = new NativeArray(m_StarCount, m_Allocator); m_SPArray = new NativeArray(m_StarCount * m_StarCount, m_Allocator, NativeArrayOptions.UninitializedMemory); var UEdgeCount = 0; var UEdges = new NativeArray(m_StarCount, m_Allocator); // Input Edges. for (int i = 0; i < edgeCount; ++i) { int2 e = edges[i]; e.x = (edges[i].x < edges[i].y) ? edges[i].x : edges[i].y; e.y = (edges[i].x > edges[i].y) ? edges[i].x : edges[i].y; edges[i] = e; InsertUniqueEdge(UEdges, e, ref UEdgeCount); } m_Edges = new NativeArray(UEdgeCount, m_Allocator); for (int i = 0; i < UEdgeCount; ++i) m_Edges[i] = UEdges[i]; UEdges.Dispose(); unsafe { ModuleHandle.InsertionSort( NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_Edges), 0, m_Edges.Length - 1, new TessEdgeCompare()); } // Init Stars. for (int i = 0; i < m_StarCount; ++i) { UStar s = m_Stars[i]; s.points = new ArraySlice(m_SPArray, i * m_StarCount, m_StarCount); s.pointCount = 0; m_Stars[i] = s; } // Fill stars. for (int i = 0; i < m_CellCount; ++i) { int a = m_Cells[i].x; int b = m_Cells[i].y; int c = m_Cells[i].z; UStar sa = m_Stars[a]; UStar sb = m_Stars[b]; UStar sc = m_Stars[c]; sa.points[sa.pointCount++] = b; sa.points[sa.pointCount++] = c; sb.points[sb.pointCount++] = c; sb.points[sb.pointCount++] = a; sc.points[sc.pointCount++] = a; sc.points[sc.pointCount++] = b; m_Stars[a] = sa; m_Stars[b] = sb; m_Stars[c] = sc; } } int OppositeOf(int a, int b) { ArraySlice points = m_Stars[b].points; for (int k = 1, n = m_Stars[b].pointCount; k < n; k += 2) if (points[k] == a) return points[k - 1]; return -1; } struct TestEdgePointE : ICondition2 { public bool Test(int2 h, int2 p, ref float t) { TessEdgeCompare tc = new TessEdgeCompare(); t = tc.Compare(h, p); return t == 0; } } int FindConstraint(int a, int b) { int2 e; e.x = a < b ? a : b; e.y = a > b ? a : b; return ModuleHandle.GetEqual(m_Edges, m_Edges.Length, e, new TestEdgePointE()); } void AddTriangle(int i, int j, int k) { UStar si = m_Stars[i]; UStar sj = m_Stars[j]; UStar sk = m_Stars[k]; si.points[si.pointCount++] = j; si.points[si.pointCount++] = k; sj.points[sj.pointCount++] = k; sj.points[sj.pointCount++] = i; sk.points[sk.pointCount++] = i; sk.points[sk.pointCount++] = j; m_Stars[i] = si; m_Stars[j] = sj; m_Stars[k] = sk; } void RemovePair(int r, int j, int k) { UStar s = m_Stars[r]; ArraySlice points = s.points; for (int i = 1, n = s.pointCount; i < n; i += 2) { if (points[i - 1] == j && points[i] == k) { points[i - 1] = points[n - 2]; points[i] = points[n - 1]; s.points = points; s.pointCount = s.pointCount - 2; m_Stars[r] = s; return; } } } void RemoveTriangle(int i, int j, int k) { RemovePair(i, j, k); RemovePair(j, k, i); RemovePair(k, i, j); } void EdgeFlip(int i, int j) { int a = OppositeOf(i, j); int b = OppositeOf(j, i); RemoveTriangle(i, j, a); RemoveTriangle(j, i, b); AddTriangle(i, b, a); AddTriangle(j, a, b); } bool Flip(NativeArray points, ref Array stack, ref int stackCount, int a, int b, int x) { int y = OppositeOf(a, b); if (y < 0) { return true; } if (b < a) { int tmp = a; a = b; b = tmp; tmp = x; x = y; y = tmp; } if (FindConstraint(a, b) != -1) { return true; } if (ModuleHandle.IsInsideCircle(points[a], points[b], points[x], points[y])) { if ((2 + stackCount) >= stack.Length) return false; stack[stackCount++] = a; stack[stackCount++] = b; } return true; } Array GetCells(ref int count) { var cellsOut = new Array(m_NumPoints * 4, m_NumPoints * (m_NumPoints + 1), m_Allocator, NativeArrayOptions.UninitializedMemory); count = 0; for (int i = 0, n = m_Stars.Length; i < n; ++i) { ArraySlice points = m_Stars[i].points; for (int j = 0, m = m_Stars[i].pointCount; j < m; j += 2) { int s = points[j]; int t = points[j + 1]; if (i < math.min(s, t)) { int3 c = new int3(); c.x = i; c.y = s; c.z = t; cellsOut[count++] = c; } } } return cellsOut; } internal bool ApplyDelaunay(NativeArray points, NativeArray edges) { // Early out if cannot find any valid cells. if (0 == m_CellCount) return false; var stack = new Array(m_NumPoints * 4, m_NumPoints * (m_NumPoints + 1), m_Allocator, NativeArrayOptions.UninitializedMemory); int stackCount = 0; var valid = true; PrepareDelaunay(edges, m_NumEdges); for (int a = 0; valid && (a < m_NumPoints); ++a) { UStar star = m_Stars[a]; for (int j = 1; j < star.pointCount; j += 2) { int b = star.points[j]; if (b < a) { continue; } if (FindConstraint(a, b) >= 0) { continue; } int x = star.points[j - 1], y = -1; for (int k = 1; k < star.pointCount; k += 2) { if (star.points[k - 1] == b) { y = star.points[k]; break; } } if (y < 0) { continue; } if (ModuleHandle.IsInsideCircle(points[a], points[b], points[x], points[y])) { if ((2 + stackCount) >= stack.Length) { valid = false; break; } stack[stackCount++] = a; stack[stackCount++] = b; } } } var flipFlops = m_NumPoints * m_NumPoints; while (stackCount > 0 && valid) { int b = stack[stackCount - 1]; stackCount--; int a = stack[stackCount - 1]; stackCount--; int x = -1, y = -1; UStar star = m_Stars[a]; for (int i = 1; i < star.pointCount; i += 2) { int s = star.points[i - 1]; int t = star.points[i]; if (s == b) { y = t; } else if (t == b) { x = s; } } if (x < 0 || y < 0) { continue; } if (!ModuleHandle.IsInsideCircle(points[a], points[b], points[x], points[y])) { continue; } EdgeFlip(a, b); valid = Flip(points, ref stack, ref stackCount, x, a, y); valid = valid && Flip(points, ref stack, ref stackCount, a, y, x); valid = valid && Flip(points, ref stack, ref stackCount, y, b, x); valid = valid && Flip(points, ref stack, ref stackCount, b, x, y); valid = valid && (--flipFlops > 0); } stack.Dispose(); return valid; } struct TestCellE : ICondition2 { public bool Test(int3 h, int3 p, ref float t) { TessCellCompare tc = new TessCellCompare(); t = tc.Compare(h, p); return t == 0; } } int FindNeighbor(Array cells, int count, int a, int b, int c) { int x = a, y = b, z = c; if (b < c) { if (b < a) { x = b; y = c; z = a; } } else if (c < a) { x = c; y = a; z = b; } if (x < 0) { return -1; } int3 key; key.x = x; key.y = y; key.z = z; return ModuleHandle.GetEqual(cells, count, key, new TestCellE()); } Array Constrain(ref int count) { var cells = GetCells(ref count); int nc = count; for (int i = 0; i < nc; ++i) { int3 c = cells[i]; int x = c.x, y = c.y, z = c.z; if (y < z) { if (y < x) { c.x = y; c.y = z; c.z = x; } } else if (z < x) { c.x = z; c.y = x; c.z = y; } cells[i] = c; } unsafe { ModuleHandle.InsertionSort( cells.UnsafePtr, 0, m_CellCount - 1, new TessCellCompare()); } // Out m_Flags = new NativeArray(nc, m_Allocator); m_Neighbors = new NativeArray(nc * 3, m_Allocator); m_Constraints = new NativeArray(nc * 3, m_Allocator); var next = new NativeArray(nc * 3, m_Allocator); var active = new NativeArray(nc * 3, m_Allocator); int side = 1, nextCount = 0, activeCount = 0; for (int i = 0; i < nc; ++i) { int3 c = cells[i]; for (int j = 0; j < 3; ++j) { int x = j, y = (j + 1) % 3; x = (x == 0) ? c.x : (j == 1) ? c.y : c.z; y = (y == 0) ? c.x : (y == 1) ? c.y : c.z; int o = OppositeOf(y, x); int a = m_Neighbors[3 * i + j] = FindNeighbor(cells, count, y, x, o); int b = m_Constraints[3 * i + j] = (-1 != FindConstraint(x, y)) ? 1 : 0; if (a < 0) { if (0 != b) { next[nextCount++] = i; } else { active[activeCount++] = i; m_Flags[i] = 1; } } } } while (activeCount > 0 || nextCount > 0) { while (activeCount > 0) { int t = active[activeCount - 1]; activeCount--; if (m_Flags[t] == -side) { continue; } m_Flags[t] = side; int3 c = cells[t]; for (int j = 0; j < 3; ++j) { int f = m_Neighbors[3 * t + j]; if (f >= 0 && m_Flags[f] == 0) { if (0 != m_Constraints[3 * t + j]) { next[nextCount++] = f; } else { active[activeCount++] = f; m_Flags[f] = side; } } } } for (int e = 0; e < nextCount; e++) active[e] = next[e]; activeCount = nextCount; nextCount = 0; side = -side; } active.Dispose(); next.Dispose(); return cells; } internal NativeArray RemoveExterior(ref int cellCount) { int constrainedCount = 0; var constrained = Constrain(ref constrainedCount); var cellsOut = new NativeArray(constrainedCount, m_Allocator); cellCount = 0; for (int i = 0; i < constrainedCount; ++i) { if (m_Flags[i] == -1) { cellsOut[cellCount++] = constrained[i]; } } constrained.Dispose(); return cellsOut; } internal NativeArray RemoveInterior(int cellCount) { int constrainedCount = 0; var constrained = Constrain(ref constrainedCount); var cellsOut = new NativeArray(constrainedCount, m_Allocator); cellCount = 0; for (int i = 0; i < constrainedCount; ++i) { if (m_Flags[i] == 1) { cellsOut[cellCount++] = constrained[i]; } } constrained.Dispose(); return cellsOut; } internal bool Triangulate(NativeArray points, int pointCount, NativeArray edges, int edgeCount) { m_NumEdges = edgeCount; m_NumHulls = edgeCount * 2; m_NumPoints = pointCount; m_CellCount = 0; int allocSize = m_NumHulls * (m_NumHulls + 1); m_Cells = new Array(allocSize, ModuleHandle.kMaxTriangleCount, m_Allocator, NativeArrayOptions.UninitializedMemory); m_ILArray = new NativeArray(allocSize, m_Allocator); // Make room for -1 node. m_IUArray = new NativeArray(allocSize, m_Allocator); // Make room for -1 node. NativeArray hulls = new NativeArray(m_NumPoints * 8, m_Allocator); int hullCount = 0; NativeArray events = new NativeArray(m_NumPoints + (m_NumEdges * 2), m_Allocator); int eventCount = 0; for (int i = 0; i < m_NumPoints; ++i) { UEvent evt = new UEvent(); evt.a = points[i]; evt.b = new float2(); evt.idx = i; evt.type = (int)UEventType.EVENT_POINT; events[eventCount++] = evt; } for (int i = 0; i < m_NumEdges; ++i) { int2 e = edges[i]; float2 a = points[e.x]; float2 b = points[e.y]; if (a.x < b.x) { UEvent _s = new UEvent(); _s.a = a; _s.b = b; _s.idx = i; _s.type = (int)UEventType.EVENT_START; UEvent _e = new UEvent(); _e.a = b; _e.b = a; _e.idx = i; _e.type = (int)UEventType.EVENT_END; events[eventCount++] = _s; events[eventCount++] = _e; } else if (a.x > b.x) { UEvent _s = new UEvent(); _s.a = b; _s.b = a; _s.idx = i; _s.type = (int)UEventType.EVENT_START; UEvent _e = new UEvent(); _e.a = a; _e.b = b; _e.idx = i; _e.type = (int)UEventType.EVENT_END; events[eventCount++] = _s; events[eventCount++] = _e; } } unsafe { ModuleHandle.InsertionSort( NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(events), 0, eventCount - 1, new TessEventCompare()); ; } var hullOp = true; float minX = events[0].a.x - (1 + math.abs(events[0].a.x)) * math.pow(2.0f, -16.0f); UHull hull; hull.a.x = minX; hull.a.y = 1; hull.b.x = minX; hull.b.y = 0; hull.idx = -1; hull.ilarray = new ArraySlice(m_ILArray, m_NumHulls * m_NumHulls, m_NumHulls); // Last element hull.iuarray = new ArraySlice(m_IUArray, m_NumHulls * m_NumHulls, m_NumHulls); hull.ilcount = 0; hull.iucount = 0; hulls[hullCount++] = hull; for (int i = 0, numEvents = eventCount; i < numEvents; ++i) { switch (events[i].type) { case (int) UEventType.EVENT_POINT: { hullOp = AddPoint(hulls, hullCount, points, events[i].a, events[i].idx); } break; case (int) UEventType.EVENT_START: { hullOp = SplitHulls(hulls, ref hullCount, points, events[i]); } break; default: { hullOp = MergeHulls(hulls, ref hullCount, points, events[i]); } break; } if (!hullOp) break; } events.Dispose(); hulls.Dispose(); return hullOp; } internal static bool Tessellate(Allocator allocator, NativeArray pgPoints, int pgPointCount, NativeArray pgEdges, int pgEdgeCount, ref NativeArray outputVertices, ref int vertexCount, ref NativeArray outputIndices, ref int indexCount) { // Process. Tessellator tess = new Tessellator(); tess.SetAllocator(allocator); int maxCount = 0, triCount = 0; var valid = true; valid = tess.Triangulate(pgPoints, pgPointCount, pgEdges, pgEdgeCount); valid = valid && tess.ApplyDelaunay(pgPoints, pgEdges); if (valid) { // Output. NativeArray cells = tess.RemoveExterior(ref triCount); for (var i = 0; i < triCount; ++i) { var a = (UInt16)cells[i].x; var b = (UInt16)cells[i].y; var c = (UInt16)cells[i].z; if (a != b && b != c && a != c) { outputIndices[indexCount++] = a; outputIndices[indexCount++] = c; outputIndices[indexCount++] = b; } maxCount = math.max(math.max(math.max(cells[i].x, cells[i].y), cells[i].z), maxCount); } maxCount = (maxCount != 0) ? (maxCount + 1) : 0; for (var i = 0; i < maxCount; ++i) outputVertices[vertexCount++] = pgPoints[i]; cells.Dispose(); } tess.Cleanup(); return valid; } internal void Cleanup() { if (m_Edges.IsCreated) m_Edges.Dispose(); if (m_Stars.IsCreated) m_Stars.Dispose(); if (m_SPArray.IsCreated) m_SPArray.Dispose(); if (m_Cells.IsCreated) m_Cells.Dispose(); if (m_ILArray.IsCreated) m_ILArray.Dispose(); if (m_IUArray.IsCreated) m_IUArray.Dispose(); if (m_Flags.IsCreated) m_Flags.Dispose(); if (m_Neighbors.IsCreated) m_Neighbors.Dispose(); if (m_Constraints.IsCreated) m_Constraints.Dispose(); } } }