From 4c5d5339dafdb455e17ce8925450f68e6d9c9a3f Mon Sep 17 00:00:00 2001 From: andywiecko Date: Sun, 8 Sep 2024 22:29:05 +0200 Subject: [PATCH] feat: ignore constraint for planting seeds --- ...l-ignore-constraint-for-planting-seeds.svg | 473 ++++++++++++++++++ .../manual/examples/holes-and-boundaries.md | 32 ++ Runtime/Triangulator.cs | 95 +++- Tests/TriangulatorGenericsEditorTests.cs | 61 +++ Tests/UnsafeTriangulatorEditorTests.cs | 75 +++ 5 files changed, 720 insertions(+), 16 deletions(-) create mode 100644 Documentation~/images/manual-ignore-constraint-for-planting-seeds.svg diff --git a/Documentation~/images/manual-ignore-constraint-for-planting-seeds.svg b/Documentation~/images/manual-ignore-constraint-for-planting-seeds.svg new file mode 100644 index 0000000..7dc8ece --- /dev/null +++ b/Documentation~/images/manual-ignore-constraint-for-planting-seeds.svg @@ -0,0 +1,473 @@ + + + + diff --git a/Documentation~/manual/examples/holes-and-boundaries.md b/Documentation~/manual/examples/holes-and-boundaries.md index 8cdaa02..798be84 100644 --- a/Documentation~/manual/examples/holes-and-boundaries.md +++ b/Documentation~/manual/examples/holes-and-boundaries.md @@ -80,7 +80,39 @@ var triangles = triangulator.Output.Triangles; > The current implementation of [`AutoHolesAndBoundary`][auto-holes-property] detects only *1-level islands*. > It will not detect holes in *solid* meshes inside other holes. +## Ignore constraints for planting seeds + +As described in the introduction, the algorithm for triangle removal (and automatic hole detection) is based on a "spreading virus" mechanism, where constrained edges block the propagation. This behavior can be overridden by setting [`IgnoreConstraintForPlantingSeeds`][ignore-constraint] to `true` for a given constraint + +```csharp +using var positions = new NativeArray(..., Allocator.Persistent); +using var constraintEdges = new NativeArray(..., Allocator.Persistent); +using var ignoreConstraint = new NativeArray(..., Allocator.Persistent); +using var triangulator = new Triangulator(Allocator.Persistent) +{ + Input = { + Positions = positions, + ConstraintEdges = constraintEdges, + IgnoreConstraintForPlantingSeeds = ignoreConstraint, + }, + Settings = { AutoHolesAndBoundary = true, }, +}; + +triangulator.Run(); + +var triangles = triangulator.Output.Triangles; +``` + +This feature is especially useful when the user wants to include a constraint but does not wish to enable hole detection for that edge. Consider the following example input: + +
+

+
+ +In this example, the red constraint is set to `true` in [`IgnoreConstraintForPlantingSeeds`][ignore-constraint]. As a result, a hole is not generated from red constraint, and the edge remains part of the final triangulation. + [restore-boundary-property]: xref:andywiecko.BurstTriangulator.TriangulationSettings.RestoreBoundary [input-constraint-edges]: xref:andywiecko.BurstTriangulator.InputData`1.ConstraintEdges [input-positions]: xref:andywiecko.BurstTriangulator.InputData`1.Positions [auto-holes-property]: xref:andywiecko.BurstTriangulator.TriangulationSettings.AutoHolesAndBoundary +[ignore-constraint]: xref:andywiecko.BurstTriangulator.InputData`1.IgnoreConstraintForPlantingSeeds diff --git a/Runtime/Triangulator.cs b/Runtime/Triangulator.cs index 5deafd0..352b624 100644 --- a/Runtime/Triangulator.cs +++ b/Runtime/Triangulator.cs @@ -171,6 +171,11 @@ public class InputData where T2 : unmanaged /// For more information, refer to the documentation. /// public NativeArray HoleSeeds { get; set; } + /// + /// Optional buffer used to mark constraints that should be ignored during the seed planting step. + /// The buffer length should be half the length of . + /// + public NativeArray IgnoreConstraintForPlantingSeeds { get; set; } } /// @@ -218,7 +223,17 @@ public class OutputData where T2 : unmanaged /// /// Buffer corresponding to . indicates that the halfedge is constrained, otherwise. /// + /// public NativeList ConstrainedHalfedges => owner.constrainedHalfedges; + /// + /// Buffer corresponding to . indicates that the halfedge was ignored during planting seed step, otherwise. Constraint edges to ignore can be set in input using . + /// + /// + /// This buffer is particularly useful when using . + /// + /// + public NativeList IgnoredHalfedgesForPlantingSeeds => owner.ignoredHalfedgesForPlantingSeeds; + private readonly Triangulator owner; [Obsolete("This will be converted into internal ctor.")] public OutputData(Triangulator owner) => this.owner = owner; @@ -293,6 +308,7 @@ public class Triangulator : IDisposable where T2 : unmanaged internal NativeList triangles; internal NativeList halfedges; internal NativeList constrainedHalfedges; + internal NativeList ignoredHalfedgesForPlantingSeeds; internal NativeReference status; public Triangulator(int capacity, Allocator allocator) @@ -302,6 +318,7 @@ public Triangulator(int capacity, Allocator allocator) status = new(Status.OK, allocator); halfedges = new(6 * capacity, allocator); constrainedHalfedges = new(6 * capacity, allocator); + ignoredHalfedgesForPlantingSeeds = new(6 * capacity, allocator); #pragma warning disable CS0618 Output = new(this); #pragma warning restore CS0618 @@ -393,8 +410,8 @@ public static JobHandle Schedule(this Triangulator @this, JobHandle depe /// public static void Run(this Triangulator @this) => new TriangulationJob( - input: new() { Positions = @this.Input.Positions.Reinterpret(), ConstraintEdges = @this.Input.ConstraintEdges, HoleSeeds = @this.Input.HoleSeeds.Reinterpret() }, - output: new() { Triangles = @this.triangles, Halfedges = @this.halfedges, Positions = UnsafeUtility.As, NativeList>(ref @this.outputPositions), Status = @this.status, ConstrainedHalfedges = @this.constrainedHalfedges }, + input: new() { Positions = @this.Input.Positions.Reinterpret(), ConstraintEdges = @this.Input.ConstraintEdges, HoleSeeds = @this.Input.HoleSeeds.Reinterpret(), IgnoreConstraintForPlantingSeeds = @this.Input.IgnoreConstraintForPlantingSeeds }, + output: new() { Triangles = @this.triangles, Halfedges = @this.halfedges, Positions = UnsafeUtility.As, NativeList>(ref @this.outputPositions), Status = @this.status, ConstrainedHalfedges = @this.constrainedHalfedges, IgnoredHalfedgesForPlantingSeeds = @this.ignoredHalfedgesForPlantingSeeds }, args: @this.Settings ).Run(); /// @@ -409,8 +426,8 @@ public static void Run(this Triangulator @this) => /// public static JobHandle Schedule(this Triangulator @this, JobHandle dependencies = default) => new TriangulationJob( - input: new() { Positions = @this.Input.Positions.Reinterpret(), ConstraintEdges = @this.Input.ConstraintEdges, HoleSeeds = @this.Input.HoleSeeds.Reinterpret() }, - output: new() { Triangles = @this.triangles, Halfedges = @this.halfedges, Positions = UnsafeUtility.As, NativeList>(ref @this.outputPositions), Status = @this.status, ConstrainedHalfedges = @this.constrainedHalfedges }, + input: new() { Positions = @this.Input.Positions.Reinterpret(), ConstraintEdges = @this.Input.ConstraintEdges, HoleSeeds = @this.Input.HoleSeeds.Reinterpret(), IgnoreConstraintForPlantingSeeds = @this.Input.IgnoreConstraintForPlantingSeeds }, + output: new() { Triangles = @this.triangles, Halfedges = @this.halfedges, Positions = UnsafeUtility.As, NativeList>(ref @this.outputPositions), Status = @this.status, ConstrainedHalfedges = @this.constrainedHalfedges, IgnoredHalfedgesForPlantingSeeds = @this.ignoredHalfedgesForPlantingSeeds }, args: @this.Settings ).Schedule(dependencies); @@ -499,6 +516,11 @@ public struct InputData where T2 : unmanaged /// For more information, refer to the documentation. /// public NativeArray HoleSeeds; + /// + /// Optional buffer used to mark constraints that should be ignored during the seed planting step. + /// The buffer length should be half the length of . + /// + public NativeArray IgnoreConstraintForPlantingSeeds; } /// @@ -531,7 +553,13 @@ public struct OutputData where T2 : unmanaged /// /// Buffer corresponding to . indicates that the halfedge is constrained, otherwise. /// + /// public NativeList ConstrainedHalfedges; + /// + /// Buffer corresponding to . indicates that the halfedge was ignored during planting seed step, otherwise. Constraint edges to ignore can be set in input using . + /// + /// + public NativeList IgnoredHalfedgesForPlantingSeeds; } /// @@ -868,11 +896,14 @@ internal struct TriangulationJob : IJob private NativeArray constraints; [NativeDisableContainerSafetyRestriction] private NativeArray holeSeeds; + [NativeDisableContainerSafetyRestriction] + private NativeArray ignoreConstraintForPlantingSeeds; private NativeList outputPositions; private NativeList triangles; private NativeList halfedges; private NativeList constrainedHalfedges; + private NativeList ignoredHalfedgesForPlantingSeeds; private NativeReference status; private readonly Args args; @@ -882,11 +913,13 @@ public TriangulationJob(InputData input, OutputData output, Args args) inputPositions = input.Positions; constraints = input.ConstraintEdges; holeSeeds = input.HoleSeeds; + ignoreConstraintForPlantingSeeds = input.IgnoreConstraintForPlantingSeeds; outputPositions = output.Positions; triangles = output.Triangles; halfedges = output.Halfedges; constrainedHalfedges = output.ConstrainedHalfedges; + ignoredHalfedgesForPlantingSeeds = output.IgnoredHalfedgesForPlantingSeeds; status = output.Status; this.args = args; @@ -897,11 +930,13 @@ public TriangulationJob(Triangulator @this) inputPositions = @this.Input.Positions; constraints = @this.Input.ConstraintEdges; holeSeeds = @this.Input.HoleSeeds; + ignoreConstraintForPlantingSeeds = @this.Input.IgnoreConstraintForPlantingSeeds; outputPositions = @this.Output.Positions; triangles = @this.Output.Triangles; halfedges = @this.Output.Halfedges; constrainedHalfedges = @this.Output.ConstrainedHalfedges; + ignoredHalfedgesForPlantingSeeds = @this.Output.IgnoredHalfedgesForPlantingSeeds; status = @this.Output.Status; args = @this.Settings; @@ -915,6 +950,7 @@ public void Execute() Positions = inputPositions, ConstraintEdges = constraints, HoleSeeds = holeSeeds, + IgnoreConstraintForPlantingSeeds = ignoreConstraintForPlantingSeeds, }, output: new() { @@ -923,6 +959,7 @@ public void Execute() Halfedges = halfedges, ConstrainedHalfedges = constrainedHalfedges, Status = status, + IgnoredHalfedgesForPlantingSeeds = ignoredHalfedgesForPlantingSeeds }, args, Allocator.Temp); } } @@ -954,16 +991,19 @@ public void Triangulate(InputData input, OutputData output, Args args, A var tmpPositions = default(NativeList); var tmpHalfedges = default(NativeList); var tmpConstrainedHalfedges = default(NativeList); + var tmpIgnoredHalfedgesForPlantingSeeds = default(NativeList); output.Status = output.Status.IsCreated ? output.Status : tmpStatus = new(allocator); output.Positions = output.Positions.IsCreated ? output.Positions : tmpPositions = new(16 * 1024, allocator); output.Halfedges = output.Halfedges.IsCreated ? output.Halfedges : tmpHalfedges = new(6 * 16 * 1024, allocator); output.ConstrainedHalfedges = output.ConstrainedHalfedges.IsCreated ? output.ConstrainedHalfedges : tmpConstrainedHalfedges = new(6 * 16 * 1024, allocator); + output.IgnoredHalfedgesForPlantingSeeds = output.IgnoredHalfedgesForPlantingSeeds.IsCreated ? output.IgnoredHalfedgesForPlantingSeeds : tmpIgnoredHalfedgesForPlantingSeeds = new(6 * 16 * 1024, allocator); output.Status.Value = Status.OK; output.Triangles.Clear(); output.Positions.Clear(); output.Halfedges.Clear(); output.ConstrainedHalfedges.Clear(); + output.IgnoredHalfedgesForPlantingSeeds.Clear(); PreProcessInputStep(input, output, args, out var localHoles, out var lt, allocator); new ValidateInputStep(input, output, args).Execute(); @@ -978,6 +1018,7 @@ public void Triangulate(InputData input, OutputData output, Args args, A if (tmpPositions.IsCreated) tmpPositions.Dispose(); if (tmpHalfedges.IsCreated) tmpHalfedges.Dispose(); if (tmpConstrainedHalfedges.IsCreated) tmpConstrainedHalfedges.Dispose(); + if (tmpIgnoredHalfedgesForPlantingSeeds.IsCreated) tmpIgnoredHalfedgesForPlantingSeeds.Dispose(); } public void PlantHoleSeeds(InputData input, OutputData output, Args args, Allocator allocator) @@ -1626,6 +1667,8 @@ private struct ConstrainEdgesStep // See the `UsingTempAllocatorInJobTest` to learn more. private NativeList halfedges; private NativeList constrainedHalfedges; + private NativeList ignoredHalfedgesForPlantingSeeds; + private NativeArray ignoreConstraintForPlantingSeeds; private readonly Args args; private NativeList intersections; @@ -1638,8 +1681,10 @@ public ConstrainEdgesStep(InputData input, OutputData output, Args args) positions = output.Positions.AsReadOnly(); triangles = output.Triangles.AsArray(); inputConstraintEdges = input.ConstraintEdges.AsReadOnly(); + ignoreConstraintForPlantingSeeds = input.IgnoreConstraintForPlantingSeeds; halfedges = output.Halfedges; constrainedHalfedges = output.ConstrainedHalfedges; + ignoredHalfedgesForPlantingSeeds = output.IgnoredHalfedgesForPlantingSeeds; this.args = args; intersections = default; @@ -1659,6 +1704,7 @@ public void Execute(Allocator allocator) using var _intersections = intersections = new NativeList(allocator); using var _unresolvedIntersections = unresolvedIntersections = new NativeList(allocator); using var _pointToHalfedge = pointToHalfedge = new NativeArray(positions.Length, allocator); + ignoredHalfedgesForPlantingSeeds.Length = halfedges.Length; // build point to halfedge for (int i = 0; i < triangles.Length; i++) @@ -1673,16 +1719,16 @@ public void Execute(Allocator allocator) inputConstraintEdges[2 * index + 1] ); c = c.x < c.y ? c.xy : c.yx; // Backward compatibility. To remove in the future. - TryApplyConstraint(c); + TryApplyConstraint(c, index); } } - private void TryApplyConstraint(int2 c) + private void TryApplyConstraint(int2 c, int index) { intersections.Clear(); unresolvedIntersections.Clear(); - CollectIntersections(c); + CollectIntersections(c, index); var iter = 0; do @@ -1693,11 +1739,11 @@ private void TryApplyConstraint(int2 c) } (intersections, unresolvedIntersections) = (unresolvedIntersections, intersections); - TryResolveIntersections(c, ref iter); + TryResolveIntersections(c, index, ref iter); } while (!unresolvedIntersections.IsEmpty); } - private void TryResolveIntersections(int2 c, ref int iter) + private void TryResolveIntersections(int2 c, int index, ref int iter) { for (int i = 0; i < intersections.Length; i++) { @@ -1776,6 +1822,8 @@ private void TryResolveIntersections(int2 c, ref int iter) halfedges[h5] = h2; constrainedHalfedges[h2] = false; constrainedHalfedges[h5] = false; + ignoredHalfedgesForPlantingSeeds[h2] = false; + ignoredHalfedgesForPlantingSeeds[h5] = false; // Fix intersections for (int j = i + 1; j < intersections.Length; j++) @@ -1794,6 +1842,8 @@ private void TryResolveIntersections(int2 c, ref int iter) { constrainedHalfedges[h2] = true; constrainedHalfedges[h5] = true; + ignoredHalfedgesForPlantingSeeds[h2] = IsConstraintIgnoredForPlanting(index); + ignoredHalfedgesForPlantingSeeds[h5] = IsConstraintIgnoredForPlanting(index); } if (EdgeEdgeIntersection(c, swapped)) { @@ -1804,6 +1854,8 @@ private void TryResolveIntersections(int2 c, ref int iter) intersections.Clear(); } + private bool IsConstraintIgnoredForPlanting(int index) => ignoreConstraintForPlantingSeeds.IsCreated && ignoreConstraintForPlantingSeeds[index]; + /// /// Replaces with . /// @@ -1812,10 +1864,13 @@ private void ReplaceHalfedge(int h0, int h1) var h0p = halfedges[h0]; halfedges[h1] = h0p; constrainedHalfedges[h1] = constrainedHalfedges[h0]; + ignoredHalfedgesForPlantingSeeds[h1] = ignoredHalfedgesForPlantingSeeds[h0]; + if (h0p != -1) { halfedges[h0p] = h1; constrainedHalfedges[h0p] = constrainedHalfedges[h0]; + ignoredHalfedgesForPlantingSeeds[h0p] = ignoredHalfedgesForPlantingSeeds[h0]; } } @@ -1826,7 +1881,7 @@ private bool EdgeEdgeIntersection(int2 e1, int2 e2) return !(math.any(e1.xy == e2.xy | e1.xy == e2.yx)) && UnsafeTriangulator.EdgeEdgeIntersection(a0, a1, b0, b1); } - private void CollectIntersections(int2 edge) + private void CollectIntersections(int2 edge, int index) { // 1. Check if h1 is cj // 2. Check if h1-h2 intersects with ci-cj @@ -1852,10 +1907,12 @@ private void CollectIntersections(int2 edge) if (triangles[h1] == cj) { constrainedHalfedges[h0] = true; + ignoredHalfedgesForPlantingSeeds[h0] = IsConstraintIgnoredForPlanting(index); var oh0 = halfedges[h0]; if (oh0 != -1) { constrainedHalfedges[oh0] = true; + ignoredHalfedgesForPlantingSeeds[oh0] = IsConstraintIgnoredForPlanting(index); } break; } @@ -1875,6 +1932,7 @@ private void CollectIntersections(int2 edge) if (triangles[h2] == cj) { constrainedHalfedges[h2] = true; + ignoredHalfedgesForPlantingSeeds[h2] = IsConstraintIgnoredForPlanting(index); } // possible that triangles[h2] == cj, not need to check @@ -1893,10 +1951,12 @@ private void CollectIntersections(int2 edge) if (triangles[h1] == cj) { constrainedHalfedges[h0] = true; + ignoredHalfedgesForPlantingSeeds[h0] = IsConstraintIgnoredForPlanting(index); var oh0 = halfedges[h0]; if (oh0 != -1) { constrainedHalfedges[oh0] = true; + ignoredHalfedgesForPlantingSeeds[oh0] = IsConstraintIgnoredForPlanting(index); } break; } @@ -1985,7 +2045,7 @@ private struct PlantingSeedStep private NativeList positions; private NativeList constrainedHalfedges; private NativeList halfedges; - + private NativeList ignoredHalfedges; private NativeArray visitedTriangles; private NativeQueue trianglesQueue; private NativeArray holes; @@ -2002,6 +2062,7 @@ public PlantingSeedStep(OutputData output, Args args, NativeArray localH positions = output.Positions; constrainedHalfedges = output.ConstrainedHalfedges; halfedges = output.Halfedges; + ignoredHalfedges = output.IgnoredHalfedgesForPlantingSeeds; holes = localHoles; this.args = args; @@ -2030,13 +2091,15 @@ public void Execute(Allocator allocator, bool constraintsIsCreated) RemoveVisitedTriangles(allocator); } + private bool HalfedgeIsIgnored(int he) => ignoredHalfedges.IsCreated && ignoredHalfedges[he]; + private void PlantBoundarySeeds() { for (int he = 0; he < halfedges.Length; he++) { if (halfedges[he] == -1 && !visitedTriangles[he / 3] && - !constrainedHalfedges[he]) + (!constrainedHalfedges[he] || HalfedgeIsIgnored(he))) { PlantSeed(he / 3); } @@ -2124,7 +2187,7 @@ private void PlantSeed(int tId) { var he = 3 * tId + i; var ohe = halfedges[he]; - if (constrainedHalfedges[he] || ohe == -1) + if (constrainedHalfedges[he] && !HalfedgeIsIgnored(he) || ohe == -1) { continue; } @@ -2169,7 +2232,7 @@ private void PlantAuto(Allocator allocator) } heVisited[he] = true; - if (constrainedHalfedges[he]) + if (constrainedHalfedges[he] && !HalfedgeIsIgnored(he)) { loop.Add(he); } @@ -2200,7 +2263,7 @@ private void PlantAuto(Allocator allocator) while (heQueue.TryDequeue(out var he)) { var ohe = halfedges[he]; // valid `ohe` should always exist, -1 are eliminated in the 1st sweep! - if (constrainedHalfedges[ohe]) + if (constrainedHalfedges[ohe] && !HalfedgeIsIgnored(ohe)) { heVisited[ohe] = true; loop.Add(ohe); @@ -2243,7 +2306,7 @@ private void PlantAuto(Allocator allocator) while (heQueue.TryDequeue(out var he)) { var ohe = halfedges[he]; - if (constrainedHalfedges[ohe]) + if (constrainedHalfedges[ohe] && !HalfedgeIsIgnored(ohe)) { heVisited[ohe] = true; PlantSeed(ohe / 3); diff --git a/Tests/TriangulatorGenericsEditorTests.cs b/Tests/TriangulatorGenericsEditorTests.cs index c06e83e..b2914ac 100644 --- a/Tests/TriangulatorGenericsEditorTests.cs +++ b/Tests/TriangulatorGenericsEditorTests.cs @@ -1426,6 +1426,67 @@ public void AutoHolesTest() var manualResult = triangulator.Output.Triangles.AsArray().ToArray(); Assert.That(autoResult, Is.EqualTo(manualResult)); } + + [Test] + public void AutoHolesWithIgnoredConstraintsTest() + { + // 3 ------------------------ 2 + // | | + // | 5 | + // | | | + // | | | + // | | | + // | | | + // | | | + // | | 9 ---- 8 | + // | | | | | + // | | | | | + // | 4 6 ---- 7 | + // | | + // 0 ------------------------ 1 + using var positions = new NativeArray(new float2[] + { + math.float2(0, 0), + math.float2(10, 0), + math.float2(10, 10), + math.float2(0, 10), + + math.float2(1, 1), + math.float2(1, 9), + + math.float2(8, 1), + math.float2(9, 1), + math.float2(9, 2), + math.float2(8, 2), + }.DynamicCast(), Allocator.Persistent); + using var constraintEdges = new NativeArray(new int[] + { + 0, 1, 1, 2, 2, 3, 3, 0, + 4, 5, + 6, 7, 7, 8, 8, 9, 9, 6, + }, Allocator.Persistent); + using var ignoreConstraints = new NativeArray(new bool[] + { + false, false, false, false, + true, + false, false, false, false, + }, Allocator.Persistent); + using var triangulator = new Triangulator(Allocator.Persistent) + { + Input = { + Positions = positions, + ConstraintEdges = constraintEdges, + IgnoreConstraintForPlantingSeeds = ignoreConstraints + }, + Settings = { AutoHolesAndBoundary = true, }, + }; + + triangulator.Run(); + + Assert.That(triangulator.Output.Triangles.AsArray(), Has.Length.EqualTo(3 * 12)); + Assert.That(triangulator.Output.ConstrainedHalfedges.AsArray().Count(i => i), Is.EqualTo(10)); + Assert.That(triangulator.Output.IgnoredHalfedgesForPlantingSeeds.AsArray().Count(i => i), Is.EqualTo(2)); + } } [TestFixture(typeof(float2))] diff --git a/Tests/UnsafeTriangulatorEditorTests.cs b/Tests/UnsafeTriangulatorEditorTests.cs index 498c4e1..55f0868 100644 --- a/Tests/UnsafeTriangulatorEditorTests.cs +++ b/Tests/UnsafeTriangulatorEditorTests.cs @@ -298,6 +298,81 @@ public void UnsafeTriangulatorPlantHoleSeedsHolesTest() h3.Free(); Assert.That(triangles1.AsArray(), Is.EqualTo(triangles2.AsArray().ToArray())); } + + [Test] + public void UnsafeTriangulatorAutoHolesWithIgnoredConstraintsTest() + { + // 3 ------------------------ 2 + // | | + // | 5 | + // | | | + // | | | + // | | | + // | | | + // | | | + // | | 9 ---- 8 | + // | | | | | + // | | | | | + // | 4 6 ---- 7 | + // | | + // 0 ------------------------ 1 + using var positions = new NativeArray(new float2[] + { + math.float2(0, 0), + math.float2(10, 0), + math.float2(10, 10), + math.float2(0, 10), + + math.float2(1, 1), + math.float2(1, 9), + + math.float2(8, 1), + math.float2(9, 1), + math.float2(9, 2), + math.float2(8, 2), + }.DynamicCast(), Allocator.Persistent); + using var constraintEdges = new NativeArray(new int[] + { + 0, 1, 1, 2, 2, 3, 3, 0, + 4, 5, + 6, 7, 7, 8, 8, 9, 9, 6, + }, Allocator.Persistent); + using var ignoreConstraints = new NativeArray(new bool[] + { + false, false, false, false, + true, + false, false, false, false, + }, Allocator.Persistent); + + + var t = new UnsafeTriangulator(); + + using var triangles1 = new NativeList(Allocator.Persistent); + t.Triangulate( + input: new() { Positions = positions, ConstraintEdges = constraintEdges, IgnoreConstraintForPlantingSeeds = ignoreConstraints }, + output: new() { Triangles = triangles1 }, + args: Args.Default().With(autoHolesAndBoundary: true), Allocator.Persistent + ); + + using var triangles2 = new NativeList(Allocator.Persistent); + using var outputPositions = new NativeList(Allocator.Persistent); + using var halfedges = new NativeList(Allocator.Persistent); + using var constrainedHalfedges = new NativeList(Allocator.Persistent); + using var ignoredHalfedges = new NativeList(Allocator.Persistent); + t.Triangulate( + input: new() { Positions = positions, ConstraintEdges = constraintEdges, IgnoreConstraintForPlantingSeeds = ignoreConstraints }, + output: new() { Triangles = triangles2, Halfedges = halfedges, ConstrainedHalfedges = constrainedHalfedges, Positions = outputPositions, IgnoredHalfedgesForPlantingSeeds = ignoredHalfedges }, + args: Args.Default(), Allocator.Persistent + ); + t.PlantHoleSeeds( + input: default, + output: new() { Triangles = triangles2, Halfedges = halfedges, ConstrainedHalfedges = constrainedHalfedges, Positions = outputPositions, IgnoredHalfedgesForPlantingSeeds = ignoredHalfedges }, + args: Args.Default().With(autoHolesAndBoundary: true), Allocator.Persistent + ); + + Assert.That(triangles1.AsArray(), Has.Length.EqualTo(3 * 12)); + Assert.That(triangles1.AsArray(), Is.EqualTo(triangles2.AsArray()).Using(TrianglesComparer.Instance)); + } } [TestFixture(typeof(float2))]