Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: generate triangle colors utility #335

Merged
merged 1 commit into from
Mar 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 64 additions & 6 deletions Runtime/Triangulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -763,12 +763,9 @@ public static class Utilities
/// <param name="allocator">The allocator to use for temporary data.</param>
public static void GenerateHalfedges(Span<int> halfedges, ReadOnlySpan<int> 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<int2, int>(triangles.Length, allocator);
for (int he = 0; he < halfedges.Length; he++)
Expand All @@ -790,6 +787,56 @@ public static void GenerateHalfedges(Span<int> halfedges, ReadOnlySpan<int> tria
}
}

/// <summary>
/// Generates triangle <paramref name="colors"/> using the provided <paramref name="halfedges"/>.
/// Triangles that share a common edge are assigned the same color index.
/// The resulting <paramref name="colors"/> contains values in the range <tt>[0, <paramref name="colorsCount"/>)</tt>.
/// Check the documentation for further details.
/// </summary>
/// <param name="colors">A buffer that will be populated with triangle colors. Its length must be three times smaller than <paramref name="halfedges"/>.</param>
/// <param name="halfedges">The halfedge data used for generating colors.</param>
/// <param name="colorsCount">The total number of unique colors assigned.</param>
/// <param name="allocator">The allocator to use for temporary data.</param>
/// <seealso cref="GenerateHalfedges(Span{int}, ReadOnlySpan{int}, Allocator)"/>
public static void GenerateTriangleColors(Span<int> colors, ReadOnlySpan<int> halfedges, out int colorsCount, Allocator allocator)
{
ThrowCheckGenerateTriangleColors(colors, halfedges);

colorsCount = 0;
colors.Fill(-1);

using var heQueue = new NativeQueueList<int>(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<int> colors, NativeQueueList<int> heQueue, ReadOnlySpan<int> 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;
}
}
}

/// <summary>
/// Inserts a sub-mesh, defined by (<paramref name="subpositions"/>, <paramref name="subtriangles"/>), into the main mesh
/// represented by (<paramref name="positions"/>, <paramref name="triangles"/>).
Expand Down Expand Up @@ -837,7 +884,7 @@ public static unsafe void InsertSubMesh<T>(NativeList<T> positions, NativeList<i
public static int NextHalfedge(int he) => he % 3 == 2 ? he - 2 : he + 1;

[System.Diagnostics.Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
private static void CheckAndThrowIfLengthNotEqual(ReadOnlySpan<int> halfedges, ReadOnlySpan<int> triangles)
private static void ThrowCheckGenerateHalfedges(ReadOnlySpan<int> halfedges, ReadOnlySpan<int> triangles)
{
if (halfedges.Length != triangles.Length)
{
Expand All @@ -846,6 +893,17 @@ private static void CheckAndThrowIfLengthNotEqual(ReadOnlySpan<int> halfedges, R
);
}
}

[System.Diagnostics.Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
private static void ThrowCheckGenerateTriangleColors(ReadOnlySpan<int> colors, ReadOnlySpan<int> 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}]."
);
}
}
}
}

Expand Down
70 changes: 66 additions & 4 deletions Tests/UtilitiesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,9 @@ public int[] GenerateHalfedgesTest(int[] triangles)
}

[Test]
public void GenerateHalfedgesThrowIfDifferentLengthTest()
{
Assert.Throws<ArgumentException>(() => Utilities.GenerateHalfedges(new int[4], new int[7], Allocator.Persistent));
}
public void GenerateHalfedgesThrowTest() => Assert.Throws<ArgumentException>(() =>
Utilities.GenerateHalfedges(halfedges: new int[4], triangles: new int[7], Allocator.Persistent)
);

private static readonly TestCaseData[] nextHalfedgeTestData =
{
Expand Down Expand Up @@ -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<ArgumentException>(() =>
Utilities.GenerateTriangleColors(colors: new int[1], halfedges: new int[30], out _, Allocator.Persistent)
);
}
}