From 8e889defa73643e268b1c3188e677f76788e12ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:47:14 +0000 Subject: [PATCH 1/2] Initial plan From a99bb2d7efa9ef4d309557b880f9d8892278f340 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:51:25 +0000 Subject: [PATCH 2/2] Add IsFinalized guards to public API + TriangulationFinalizedException + unit tests Co-authored-by: MichaCo <5837539+MichaCo@users.noreply.github.com> --- src/CDT.Core/Triangulation.cs | 18 +++++++ test/CDT.Tests/TriangulationTests.cs | 75 ++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/src/CDT.Core/Triangulation.cs b/src/CDT.Core/Triangulation.cs index a6f3039..43afca1 100644 --- a/src/CDT.Core/Triangulation.cs +++ b/src/CDT.Core/Triangulation.cs @@ -35,6 +35,18 @@ public DuplicateVertexException(int v1, int v2) } } +/// +/// Thrown when a mutating method is called after the triangulation has been finalized +/// (i.e., after any Erase* method has been called). +/// +public sealed class TriangulationFinalizedException : TriangulationException +{ + /// + public TriangulationFinalizedException() + : base("The triangulation has already been finalized. No further modifications are allowed after calling an Erase method.") + { } +} + /// Thrown when intersecting constraint edges are detected and is set. public sealed class IntersectingConstraintsException : TriangulationException { @@ -154,6 +166,7 @@ public Triangulation( /// Inserts a list of vertices into the triangulation. public void InsertVertices(IReadOnlyList> newVertices) { + if (IsFinalized) throw new TriangulationFinalizedException(); if (newVertices.Count == 0) return; bool isFirstInsertion = _kdTree == null && _verticesCount == 0; @@ -217,6 +230,7 @@ public void InsertVertices(IReadOnlyList> newVertices) /// Inserts constraint edges (constrained Delaunay triangulation). public void InsertEdges(IReadOnlyList edges) { + if (IsFinalized) throw new TriangulationFinalizedException(); var remaining = new List(4); var tppTasks = new List(8); var polyL = new List(8); @@ -246,6 +260,7 @@ public void InsertEdges(IReadOnlyList edges) /// public void ConformToEdges(IReadOnlyList edges) { + if (IsFinalized) throw new TriangulationFinalizedException(); var remaining = new List(8); var flipStack = new Stack(4); var flippedFixed = new List(8); @@ -270,6 +285,7 @@ public void ConformToEdges(IReadOnlyList edges) /// Removes the super-triangle to produce a convex hull triangulation. public void EraseSuperTriangle() { + if (IsFinalized) throw new TriangulationFinalizedException(); if (_superGeomType != SuperGeometryType.SuperTriangle) return; var toErase = new HashSet(); for (int i = 0; i < _trianglesCount; i++) @@ -283,6 +299,7 @@ public void EraseSuperTriangle() /// Removes all outer triangles (flood-fill from super-triangle vertex until a constraint edge). public void EraseOuterTriangles() { + if (IsFinalized) throw new TriangulationFinalizedException(); if (_vertTris[0] == Indices.NoNeighbor) throw new TriangulationException("No vertex triangle data – already finalized?"); var seeds = new Stack(); @@ -297,6 +314,7 @@ public void EraseOuterTriangles() /// public void EraseOuterTrianglesAndHoles() { + if (IsFinalized) throw new TriangulationFinalizedException(); var depths = CalculateTriangleDepths(); var toErase = new HashSet(); for (int i = 0; i < _trianglesCount; i++) diff --git a/test/CDT.Tests/TriangulationTests.cs b/test/CDT.Tests/TriangulationTests.cs index 10b5056..d3a6a41 100644 --- a/test/CDT.Tests/TriangulationTests.cs +++ b/test/CDT.Tests/TriangulationTests.cs @@ -222,6 +222,81 @@ public void FivePoints_ValidTopology() Assert.True(TopologyVerifier.VerifyTopology(cdt)); Assert.Equal(5, cdt.Vertices.Length); } + + // ------------------------------------------------------------------------- + // IsFinalized guard checks + // ------------------------------------------------------------------------- + + [Fact] + public void InsertVertices_AfterFinalized_ThrowsTriangulationFinalizedException() + { + var cdt = CreateCdt(); + cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(0.5, 1)]); + cdt.EraseSuperTriangle(); + + Assert.True(cdt.IsFinalized); + Assert.Throws(() => + cdt.InsertVertices([Pt(2, 0)])); + } + + [Fact] + public void InsertEdges_AfterFinalized_ThrowsTriangulationFinalizedException() + { + var cdt = CreateCdt(); + cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(1, 1), Pt(0, 1)]); + cdt.EraseSuperTriangle(); + + Assert.True(cdt.IsFinalized); + Assert.Throws(() => + cdt.InsertEdges([new Edge(0, 2)])); + } + + [Fact] + public void ConformToEdges_AfterFinalized_ThrowsTriangulationFinalizedException() + { + var cdt = CreateCdt(); + cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(1, 1), Pt(0, 1)]); + cdt.EraseSuperTriangle(); + + Assert.True(cdt.IsFinalized); + Assert.Throws(() => + cdt.ConformToEdges([new Edge(0, 2)])); + } + + [Fact] + public void EraseSuperTriangle_AfterFinalized_ThrowsTriangulationFinalizedException() + { + var cdt = CreateCdt(); + cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(0.5, 1)]); + cdt.EraseSuperTriangle(); + + Assert.True(cdt.IsFinalized); + Assert.Throws(() => cdt.EraseSuperTriangle()); + } + + [Fact] + public void EraseOuterTriangles_AfterFinalized_ThrowsTriangulationFinalizedException() + { + var cdt = CreateCdt(); + cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(1, 1), Pt(0, 1)]); + cdt.InsertEdges([new Edge(0, 1), new Edge(1, 2), new Edge(2, 3), new Edge(3, 0)]); + cdt.EraseOuterTriangles(); + + Assert.True(cdt.IsFinalized); + Assert.Throws(() => cdt.EraseOuterTriangles()); + } + + [Fact] + public void EraseOuterTrianglesAndHoles_AfterFinalized_ThrowsTriangulationFinalizedException() + { + var cdt = CreateCdt(); + cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(1, 1), Pt(0, 1)]); + cdt.InsertEdges([new Edge(0, 1), new Edge(1, 2), new Edge(2, 3), new Edge(3, 0)]); + cdt.EraseOuterTrianglesAndHoles(); + + Assert.True(cdt.IsFinalized); + Assert.Throws(() => cdt.EraseOuterTrianglesAndHoles()); + } } /// Triangulation tests for .