diff --git a/Runtime/Triangulator.cs b/Runtime/Triangulator.cs index a0e8f01..d9b1625 100644 --- a/Runtime/Triangulator.cs +++ b/Runtime/Triangulator.cs @@ -763,12 +763,9 @@ public static class Utilities /// The allocator to use for temporary data. public static void GenerateHalfedges(Span halfedges, ReadOnlySpan triangles, Allocator allocator) { - CheckAndThrowIfLengthNotEqual(halfedges, triangles); + ThrowCheckGenerateHalfedges(halfedges, triangles); - for (int i = 0; i < halfedges.Length; i++) - { - halfedges[i] = -1; - } + halfedges.Fill(-1); using var tmp = new NativeHashMap(triangles.Length, allocator); for (int he = 0; he < halfedges.Length; he++) @@ -790,6 +787,56 @@ public static void GenerateHalfedges(Span halfedges, ReadOnlySpan tria } } + /// + /// Generates triangle using the provided . + /// Triangles that share a common edge are assigned the same color index. + /// The resulting contains values in the range [0, ). + /// Check the documentation for further details. + /// + /// A buffer that will be populated with triangle colors. Its length must be three times smaller than . + /// The halfedge data used for generating colors. + /// The total number of unique colors assigned. + /// The allocator to use for temporary data. + /// + public static void GenerateTriangleColors(Span colors, ReadOnlySpan halfedges, out int colorsCount, Allocator allocator) + { + ThrowCheckGenerateTriangleColors(colors, halfedges); + + colorsCount = 0; + colors.Fill(-1); + + using var heQueue = new NativeQueueList(allocator); + for (int t = 0; t < colors.Length; t++) + { + if (colors[t] == -1) + { + heQueue.Enqueue(3 * t + 0); + heQueue.Enqueue(3 * t + 1); + heQueue.Enqueue(3 * t + 2); + colors[t] = colorsCount; + BFS(colorsCount++, colors, heQueue, halfedges); + } + } + + static void BFS(int color, Span colors, NativeQueueList heQueue, ReadOnlySpan halfedges) + { + while (heQueue.TryDequeue(out var he)) + { + var ohe = halfedges[he]; + var t = ohe / 3; + if (ohe == -1 || colors[t] != -1) + { + continue; + } + + heQueue.Enqueue(3 * t + 0); + heQueue.Enqueue(3 * t + 1); + heQueue.Enqueue(3 * t + 2); + colors[t] = color; + } + } + } + /// /// Inserts a sub-mesh, defined by (, ), into the main mesh /// represented by (, ). @@ -837,7 +884,7 @@ public static unsafe void InsertSubMesh(NativeList positions, NativeList he % 3 == 2 ? he - 2 : he + 1; [System.Diagnostics.Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] - private static void CheckAndThrowIfLengthNotEqual(ReadOnlySpan halfedges, ReadOnlySpan triangles) + private static void ThrowCheckGenerateHalfedges(ReadOnlySpan halfedges, ReadOnlySpan triangles) { if (halfedges.Length != triangles.Length) { @@ -846,6 +893,17 @@ private static void CheckAndThrowIfLengthNotEqual(ReadOnlySpan halfedges, R ); } } + + [System.Diagnostics.Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void ThrowCheckGenerateTriangleColors(ReadOnlySpan colors, ReadOnlySpan halfedges) + { + if (3 * colors.Length != halfedges.Length) + { + throw new ArgumentException( + $"The provided colors[{colors.Length}] must be one-third of the length of halfedges [{halfedges.Length}]." + ); + } + } } } diff --git a/Tests/UtilitiesTests.cs b/Tests/UtilitiesTests.cs index 8d00a6d..81b9508 100644 --- a/Tests/UtilitiesTests.cs +++ b/Tests/UtilitiesTests.cs @@ -47,10 +47,9 @@ public int[] GenerateHalfedgesTest(int[] triangles) } [Test] - public void GenerateHalfedgesThrowIfDifferentLengthTest() - { - Assert.Throws(() => Utilities.GenerateHalfedges(new int[4], new int[7], Allocator.Persistent)); - } + public void GenerateHalfedgesThrowTest() => Assert.Throws(() => + Utilities.GenerateHalfedges(halfedges: new int[4], triangles: new int[7], Allocator.Persistent) + ); private static readonly TestCaseData[] nextHalfedgeTestData = { @@ -116,5 +115,68 @@ public int[] InsertSubMeshTest((float2[] p, int[] t) mesh, (float2[] p, int[] t) Assert.That(positions.AsArray(), Is.EqualTo(mesh.p.Concat(subMesh.p))); return triangles.AsReadOnly().ToArray(); } + + private static readonly TestCaseData[] generateTriangleColorsTestData = + { + new(new int[]{ }, 0) + { + ExpectedResult = new int[]{ }, + TestName = "Test case 0 - 0 triangles 0 colors (GenerateTriangleColorsTest)" + }, + new(new int[]{ -1, -1, -1 }, 1) + { + ExpectedResult = new int[]{ 0 }, + TestName = "Test case 1 - 1 triangle 1 color (GenerateTriangleColorsTest)" + }, + new(new int[]{ -1, -1, -1, -1, -1, -1 }, 2) + { + ExpectedResult = new int[]{ 0, 1 }, + TestName = "Test case 2a - 2 triangles 2 colors (GenerateTriangleColorsTest)" + }, + new(new int[]{ 3, -1, -1, 0, -1, -1 }, 1) + { + ExpectedResult = new int[]{ 0, 0 }, + TestName = "Test case 2b - 2 triangles 1 colors (GenerateTriangleColorsTest)" + }, + new(new int[]{ -1, -1, -1, -1, -1, -1, -1, -1, -1 }, 3) + { + ExpectedResult = new int[]{ 0, 1, 2 }, + TestName = "Test case 3a - 3 triangles 3 colors (GenerateTriangleColorsTest)" + }, + new(new int[]{ 3, -1, -1, 0, -1, -1, -1, -1, -1 }, 2) + { + ExpectedResult = new int[]{ 0, 0, 1 }, + TestName = "Test case 3b - 3 triangles 2 colors (GenerateTriangleColorsTest)" + }, + new(new int[]{ 6, -1, -1, -1, -1, -1, 0, -1, -1 }, 2) + { + ExpectedResult = new int[]{ 0, 1, 0 }, + TestName = "Test case 3c - 3 triangles 2 colors (GenerateTriangleColorsTest)" + }, + new(new int[]{ -1, -1, -1, 6, -1, -1, 3, -1, -1 }, 2) + { + ExpectedResult = new int[]{ 0, 1, 1 }, + TestName = "Test case 3d - 3 triangles 2 colors (GenerateTriangleColorsTest)" + }, + new(new int[]{ 3, -1, -1, 0, 6, -1, 4, -1, -1 }, 1) + { + ExpectedResult = new int[]{ 0, 0, 0 }, + TestName = "Test case 3e - 3 triangles 1 color (GenerateTriangleColorsTest)" + }, + }; + + [Test, TestCaseSource(nameof(generateTriangleColorsTestData))] + public int[] GenerateTriangleColorsTest(int[] halfedges, int expectedCount) + { + var colors = new int[halfedges.Length / 3]; + Utilities.GenerateTriangleColors(colors, halfedges, out var count, Allocator.Persistent); + Assert.That(count, Is.EqualTo(expectedCount)); + return colors; + } + + [Test] + public void GenerateTriangleColorsThrowTest() => Assert.Throws(() => + Utilities.GenerateTriangleColors(colors: new int[1], halfedges: new int[30], out _, Allocator.Persistent) + ); } }