diff --git a/src/CDT.Core/CdtUtils.cs b/src/CDT.Core/CdtUtils.cs index 17f306d..8525ca8 100644 --- a/src/CDT.Core/CdtUtils.cs +++ b/src/CDT.Core/CdtUtils.cs @@ -143,12 +143,12 @@ public static void RemapEdges(List edges, IReadOnlyList mapping) // ------------------------------------------------------------------------- /// Extracts all unique edges from a triangle list. - /// The triangle list to extract edges from. + /// The triangles to extract edges from. /// A set containing all unique edges in the triangulation. - public static HashSet ExtractEdgesFromTriangles(IReadOnlyList triangles) + public static HashSet ExtractEdgesFromTriangles(ReadOnlySpan triangles) { - var edges = new HashSet(triangles.Count * 3); - foreach (var t in triangles) + var edges = new HashSet(triangles.Length * 3); + foreach (ref readonly var t in triangles) { edges.Add(new Edge(t.V0, t.V1)); edges.Add(new Edge(t.V1, t.V2)); @@ -167,12 +167,12 @@ public static HashSet ExtractEdgesFromTriangles(IReadOnlyList tr /// A read-only list of read-only lists: for each vertex index, the list of adjacent triangle indices. /// public static IReadOnlyList> CalculateTrianglesByVertex( - IReadOnlyList triangles, + ReadOnlySpan triangles, int verticesCount) { var result = new List[verticesCount]; for (int i = 0; i < verticesCount; i++) result[i] = new List(); - for (int i = 0; i < triangles.Count; i++) + for (int i = 0; i < triangles.Length; i++) { var t = triangles[i]; result[t.V0].Add(i); diff --git a/src/CDT.Core/TopologyVerifier.cs b/src/CDT.Core/TopologyVerifier.cs index 508aa86..355dcc7 100644 --- a/src/CDT.Core/TopologyVerifier.cs +++ b/src/CDT.Core/TopologyVerifier.cs @@ -19,16 +19,16 @@ public static class TopologyVerifier public static bool VerifyTopology(Triangulation cdt) where T : unmanaged, IFloatingPoint, IMinMaxValue, IRootFunctions { - var triangles = cdt.Triangles; - var vertices = cdt.Vertices; + var triangles = cdt.Triangles.Span; + var vertices = cdt.Vertices.Span; - for (int iT = 0; iT < triangles.Count; iT++) + for (int iT = 0; iT < triangles.Length; iT++) { var t = triangles[iT]; // Verify non-invalid vertices if (t.V0 == Indices.NoVertex || t.V1 == Indices.NoVertex || t.V2 == Indices.NoVertex) return false; - if (t.V0 >= vertices.Count || t.V1 >= vertices.Count || t.V2 >= vertices.Count) + if (t.V0 >= vertices.Length || t.V1 >= vertices.Length || t.V2 >= vertices.Length) return false; // No degenerate (same-vertex) triangles if (t.V0 == t.V1 || t.V1 == t.V2 || t.V0 == t.V2) @@ -39,7 +39,7 @@ public static bool VerifyTopology(Triangulation cdt) { int iN = t.GetNeighbor(i); if (iN == Indices.NoNeighbor) continue; - if (iN >= triangles.Count) return false; + if (iN >= triangles.Length) return false; var tN = triangles[iN]; // Neighbor must reference us back if (tN.N0 != iT && tN.N1 != iT && tN.N2 != iT) diff --git a/src/CDT.Core/Triangulation.cs b/src/CDT.Core/Triangulation.cs index 2ae393d..ae1ca29 100644 --- a/src/CDT.Core/Triangulation.cs +++ b/src/CDT.Core/Triangulation.cs @@ -66,10 +66,10 @@ public sealed class Triangulation // ------------------------------------------------------------------------- /// All vertices in the triangulation (including super-triangle vertices while not finalized). - public IReadOnlyList> Vertices => _vertices; + public ReadOnlyMemory> Vertices => new(_vertices, 0, _verticesCount); /// All triangles in the triangulation. - public IReadOnlyList Triangles => _triangles; + public ReadOnlyMemory Triangles => new(_triangles, 0, _trianglesCount); /// Set of constraint (fixed) edges. public IReadOnlySet FixedEdges => _fixedEdges; @@ -90,8 +90,10 @@ public sealed class Triangulation // Private fields // ------------------------------------------------------------------------- - private readonly List> _vertices = new(); - private readonly List _triangles = new(); + private V2d[] _vertices = []; + private int _verticesCount; + private Triangle[] _triangles = []; + private int _trianglesCount; private readonly HashSet _fixedEdges = new(); private readonly Dictionary _overlapCount = new(); private readonly Dictionary> _pieceToOriginals = new(); @@ -105,7 +107,8 @@ public sealed class Triangulation private int _nTargetVerts; // For each vertex: one adjacent triangle index - private readonly List _vertTris = new(); + private int[] _vertTris = []; + private int _vertTrisCount; // KD-tree for nearest-point location private KdTree? _kdTree; @@ -153,16 +156,16 @@ public void InsertVertices(IReadOnlyList> newVertices) { if (newVertices.Count == 0) return; - bool isFirstInsertion = _kdTree == null && _vertices.Count == 0; + bool isFirstInsertion = _kdTree == null && _verticesCount == 0; // Pre-allocate backing arrays once we know the incoming vertex count. // Euler's formula: a planar triangulation of N points has ~2N triangles. if (isFirstInsertion) { int n = newVertices.Count; - _vertices.EnsureCapacity(n + Indices.SuperTriangleVertexCount); - _vertTris.EnsureCapacity(n + Indices.SuperTriangleVertexCount); - _triangles.EnsureCapacity(2 * n + 4); + ArrayEnsureCapacity(ref _vertices, n + Indices.SuperTriangleVertexCount); + ArrayEnsureCapacity(ref _vertTris, n + Indices.SuperTriangleVertexCount); + ArrayEnsureCapacity(ref _triangles, 2 * n + 4); } // Build bounding box of new vertices @@ -179,7 +182,7 @@ public void InsertVertices(IReadOnlyList> newVertices) InitKdTree(); } - int insertStart = _vertices.Count; + int insertStart = _verticesCount; foreach (var v in newVertices) { AddNewVertex(v, Indices.NoNeighbor); @@ -200,7 +203,7 @@ public void InsertVertices(IReadOnlyList> newVertices) { // AsProvided: sequential order, KD-tree walk-start var stack = new Stack(4); - for (int iV = insertStart; iV < _vertices.Count; iV++) + for (int iV = insertStart; iV < _verticesCount; iV++) { InsertVertex(iV, stack); } @@ -269,7 +272,7 @@ public void EraseSuperTriangle() { if (_superGeomType != SuperGeometryType.SuperTriangle) return; var toErase = new HashSet(); - for (int i = 0; i < _triangles.Count; i++) + for (int i = 0; i < _trianglesCount; i++) { if (CdtUtils.TouchesSuperTriangle(_triangles[i])) toErase.Add(i); @@ -296,7 +299,7 @@ public void EraseOuterTrianglesAndHoles() { var depths = CalculateTriangleDepths(); var toErase = new HashSet(); - for (int i = 0; i < _triangles.Count; i++) + for (int i = 0; i < _trianglesCount; i++) { if (depths[i] % 2 == 0) toErase.Add(i); } @@ -307,7 +310,7 @@ public void EraseOuterTrianglesAndHoles() /// Indicates whether the triangulation has been finalized (i.e., one of the /// Erase methods was called). Further modification is not possible. /// - public bool IsFinalized => _vertTris.Count == 0 && _vertices.Count > 0; + public bool IsFinalized => _vertTrisCount == 0 && _verticesCount > 0; // ------------------------------------------------------------------------- // Internal helpers – super-triangle setup @@ -351,8 +354,8 @@ private void AddSuperTriangle(Box2d box) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void AddNewVertex(V2d pos, int iTri) { - _vertices.Add(pos); - _vertTris.Add(iTri); + ArrayAdd(ref _vertices, ref _verticesCount, pos); + ArrayAdd(ref _vertTris, ref _vertTrisCount, iTri); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -361,11 +364,33 @@ private void AddNewVertex(V2d pos, int iTri) [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddTriangle(Triangle t) { - int idx = _triangles.Count; - _triangles.Add(t); + int idx = _trianglesCount; + ArrayAdd(ref _triangles, ref _trianglesCount, t); return idx; } + // ------------------------------------------------------------------------- + // Low-level array-growth helpers + // ------------------------------------------------------------------------- + + // Maximum element count for which a temporary int[] is stackalloc'd instead of heap-allocated. + private const int StackAllocThreshold = 512; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ArrayAdd(ref TItem[] arr, ref int count, TItem item) + { + if (count == arr.Length) + Array.Resize(ref arr, arr.Length == 0 ? 4 : arr.Length * 2); + arr[count++] = item; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ArrayEnsureCapacity(ref TItem[] arr, int capacity) + { + if (arr.Length < capacity) + Array.Resize(ref arr, capacity); + } + // ------------------------------------------------------------------------- // Internal helpers – vertex insertion // ------------------------------------------------------------------------- @@ -406,8 +431,8 @@ private void InsertVertex(int iVert) private void InsertVertices_Randomized(int superGeomVertCount) { - int count = _vertices.Count - superGeomVertCount; - var indices = new int[count]; + int count = _verticesCount - superGeomVertCount; + Span indices = count <= StackAllocThreshold ? stackalloc int[count] : new int[count]; for (int i = 0; i < count; i++) { indices[i] = superGeomVertCount + i; } for (int i = count - 1; i > 0; i--) { @@ -420,10 +445,10 @@ private void InsertVertices_Randomized(int superGeomVertCount) private void InsertVertices_KDTreeBFS(int superGeomVertCount, Box2d box) { - int vertexCount = _vertices.Count - superGeomVertCount; + int vertexCount = _verticesCount - superGeomVertCount; if (vertexCount <= 0) { return; } - var indices = new int[vertexCount]; + Span indices = vertexCount <= StackAllocThreshold ? stackalloc int[vertexCount] : new int[vertexCount]; for (int i = 0; i < vertexCount; i++) { indices[i] = superGeomVertCount + i; } var queue = new Queue<(int lo, int hi, T boxMinX, T boxMinY, T boxMaxX, T boxMaxY, int parent)>(); @@ -464,15 +489,15 @@ private void InsertVertices_KDTreeBFS(int superGeomVertCount, Box2d box) private readonly struct VertexXComparer : IComparer { - private readonly IReadOnlyList> _vertices; - public VertexXComparer(IReadOnlyList> vertices) => _vertices = vertices; + private readonly V2d[] _vertices; + public VertexXComparer(V2d[] vertices) => _vertices = vertices; public int Compare(int a, int b) => _vertices[a].X.CompareTo(_vertices[b].X); } private readonly struct VertexYComparer : IComparer { - private readonly IReadOnlyList> _vertices; - public VertexYComparer(IReadOnlyList> vertices) => _vertices = vertices; + private readonly V2d[] _vertices; + public VertexYComparer(V2d[] vertices) => _vertices = vertices; public int Compare(int a, int b) => _vertices[a].Y.CompareTo(_vertices[b].Y); } @@ -481,7 +506,7 @@ private void InsertVertices_KDTreeBFS(int superGeomVertCount, Box2d box) /// so the element at position is the one that would be there /// after a full sort; elements before it are ≤ it and elements after are ≥ it. /// - private static void NthElement(int[] arr, int lo, int nth, int hi, TComparer cmp) + private static void NthElement(Span arr, int lo, int nth, int hi, TComparer cmp) where TComparer : struct, IComparer { while (lo < hi - 1) @@ -594,7 +619,7 @@ private void EnsureDelaunayByEdgeFlips(int iV1, Stack triStack) /// Brute-force O(n) fallback: scan all triangles to find the one containing . private int FindTriangleLinear(V2d pos, out PtTriLocation loc) { - for (int i = 0; i < _triangles.Count; i++) + for (int i = 0; i < _trianglesCount; i++) { var t = _triangles[i]; loc = LocatePointTriangle(pos, _vertices[t.V0], _vertices[t.V1], _vertices[t.V2]); @@ -1020,7 +1045,7 @@ private void ConformToEdgeIteration( remaining.Add(new ConformToEdgeTask(new Edge(iB, edge.V2), originals, overlaps)); // Insert midpoint and recurse - int iMid = _vertices.Count; + int iMid = _verticesCount; var start = _vertices[iA]; var end = _vertices[iB]; AddNewVertex(new V2d((start.X + end.X) / _two, (start.Y + end.Y) / _two), Indices.NoNeighbor); @@ -1117,10 +1142,11 @@ private void TriangulatePseudoPolygonIteration( { var outerEdge = new Edge(b, c); int outerTri = outerTris[outerEdge]; - var tri = _triangles[iT]; tri.N1 = Indices.NoNeighbor; _triangles[iT] = tri; + ref var tri = ref _triangles[iT]; + tri.N1 = Indices.NoNeighbor; if (outerTri != Indices.NoNeighbor) { - tri = _triangles[iT]; tri.N1 = outerTri; _triangles[iT] = tri; + tri.N1 = outerTri; ChangeNeighbor(outerTri, c, b, iT); } else outerTris[outerEdge] = iT; @@ -1136,18 +1162,19 @@ private void TriangulatePseudoPolygonIteration( { var outerEdge = new Edge(c, a); int outerTri = outerTris[outerEdge]; - var tri = _triangles[iT]; tri.N2 = Indices.NoNeighbor; _triangles[iT] = tri; + ref var tri = ref _triangles[iT]; + tri.N2 = Indices.NoNeighbor; if (outerTri != Indices.NoNeighbor) { - tri = _triangles[iT]; tri.N2 = outerTri; _triangles[iT] = tri; + tri.N2 = outerTri; ChangeNeighbor(outerTri, c, a, iT); } else outerTris[outerEdge] = iT; } // Finalize triangle - var parentTri = _triangles[iParent]; parentTri.SetNeighbor(iInParent, iT); _triangles[iParent] = parentTri; - var tFinal = _triangles[iT]; tFinal.N0 = iParent; tFinal.V0 = a; tFinal.V1 = b; tFinal.V2 = c; _triangles[iT] = tFinal; + ref var parentTri = ref _triangles[iParent]; parentTri.SetNeighbor(iInParent, iT); + ref var tFinal = ref _triangles[iT]; tFinal.N0 = iParent; tFinal.V0 = a; tFinal.V1 = b; tFinal.V2 = c; SetAdjacentTriangle(c, iT); } @@ -1228,20 +1255,18 @@ private void SetAdjacentTriangle(int v, int iT) private void ChangeNeighbor(int iT, int oldN, int newN) { if (iT == Indices.NoNeighbor) return; - var t = _triangles[iT]; + ref var t = ref _triangles[iT]; if (t.N0 == oldN) t.N0 = newN; else if (t.N1 == oldN) t.N1 = newN; else t.N2 = newN; - _triangles[iT] = t; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ChangeNeighbor(int iT, int va, int vb, int newN) { if (iT == Indices.NoNeighbor) return; - var t = _triangles[iT]; + ref var t = ref _triangles[iT]; t.SetNeighbor(CdtUtils.EdgeNeighborIndex(t, va, vb), newN); - _triangles[iT] = t; } private void PivotVertexTriangleCW(int v) @@ -1296,7 +1321,7 @@ private void SplitFixedEdge(Edge edge, int iSplitVert) private int SplitFixedEdgeAt(Edge edge, V2d splitVert, int iT, int iTopo) { - int iSplit = _vertices.Count; + int iSplit = _verticesCount; AddNewVertex(splitVert, Indices.NoNeighbor); var stack = new Stack(4); InsertVertexOnEdge(iSplit, iT, iTopo, handleFixedSplitEdge: false, stack); @@ -1351,11 +1376,13 @@ private HashSet GrowToBoundary(Stack seeds) private void FinalizeTriangulation(HashSet removedTriangles) { - _vertTris.Clear(); + _vertTrisCount = 0; if (_superGeomType == SuperGeometryType.SuperTriangle) { - _vertices.RemoveRange(0, Indices.SuperTriangleVertexCount); + int shift = Indices.SuperTriangleVertexCount; + _vertices.AsSpan(shift, _verticesCount - shift).CopyTo(_vertices); + _verticesCount -= shift; RemapEdgesNoSuperTriangle(_fixedEdges); RemapEdgesNoSuperTriangle(_overlapCount); RemapEdgesNoSuperTriangle(_pieceToOriginals); @@ -1366,11 +1393,10 @@ private void FinalizeTriangulation(HashSet removedTriangles) if (_superGeomType == SuperGeometryType.SuperTriangle) { int offset = Indices.SuperTriangleVertexCount; - for (int i = 0; i < _triangles.Count; i++) + for (int i = 0; i < _trianglesCount; i++) { - var t = _triangles[i]; + ref var t = ref _triangles[i]; t.V0 -= offset; t.V1 -= offset; t.V2 -= offset; - _triangles[i] = t; } } } @@ -1411,29 +1437,28 @@ private void RemoveTriangles(HashSet removed) { if (removed.Count == 0) return; // Build compact mapping: old index → new index - var mapping = new int[_triangles.Count]; + var mapping = new int[_trianglesCount]; int newIdx = 0; - for (int i = 0; i < _triangles.Count; i++) + for (int i = 0; i < _trianglesCount; i++) { if (removed.Contains(i)) { mapping[i] = Indices.NoNeighbor; continue; } mapping[i] = newIdx++; } // Compact triangle list int write = 0; - for (int i = 0; i < _triangles.Count; i++) + for (int i = 0; i < _trianglesCount; i++) { if (removed.Contains(i)) continue; _triangles[write++] = _triangles[i]; } - _triangles.RemoveRange(write, _triangles.Count - write); + _trianglesCount = write; // Re-map neighbor indices - for (int i = 0; i < _triangles.Count; i++) + for (int i = 0; i < _trianglesCount; i++) { - var t = _triangles[i]; + ref var t = ref _triangles[i]; t.N0 = t.N0 == Indices.NoNeighbor ? Indices.NoNeighbor : (removed.Contains(t.N0) ? Indices.NoNeighbor : mapping[t.N0]); t.N1 = t.N1 == Indices.NoNeighbor ? Indices.NoNeighbor : (removed.Contains(t.N1) ? Indices.NoNeighbor : mapping[t.N1]); t.N2 = t.N2 == Indices.NoNeighbor ? Indices.NoNeighbor : (removed.Contains(t.N2) ? Indices.NoNeighbor : mapping[t.N2]); - _triangles[i] = t; } } @@ -1443,12 +1468,12 @@ private void RemoveTriangles(HashSet removed) private ushort[] CalculateTriangleDepths() { - var depths = new ushort[_triangles.Count]; + var depths = new ushort[_trianglesCount]; for (int i = 0; i < depths.Length; i++) depths[i] = ushort.MaxValue; // Find a triangle touching the super-triangle vertex 0 - int seedTri = _vertTris.Count > 0 ? _vertTris[0] : Indices.NoNeighbor; - if (seedTri == Indices.NoNeighbor && _triangles.Count > 0) seedTri = 0; + int seedTri = _vertTrisCount > 0 ? _vertTris[0] : Indices.NoNeighbor; + if (seedTri == Indices.NoNeighbor && _trianglesCount > 0) seedTri = 0; var layerSeeds = new Stack(); layerSeeds.Push(seedTri); @@ -1507,9 +1532,9 @@ private Dictionary PeelLayer( private void InitKdTree() { var box = new Box2d(); - box.Envelop(_vertices); + box.Envelop(_vertices.AsSpan(0, _verticesCount)); _kdTree = new KdTree(box.Min.X, box.Min.Y, box.Max.X, box.Max.Y); - for (int i = 0; i < _vertices.Count; i++) + for (int i = 0; i < _verticesCount; i++) _kdTree.Insert(i, _vertices); } diff --git a/src/CDT.Core/Types.cs b/src/CDT.Core/Types.cs index 7f6475c..ba5119d 100644 --- a/src/CDT.Core/Types.cs +++ b/src/CDT.Core/Types.cs @@ -86,6 +86,15 @@ public void Envelop(IReadOnlyList> points) } } + /// Expands the box to include all given points. + public void Envelop(ReadOnlySpan> points) + { + foreach (ref readonly var p in points) + { + Envelop(p.X, p.Y); + } + } + /// Creates a box containing all the given points. public static Box2d Of(IReadOnlyList> points) { diff --git a/test/CDT.Tests/GroundTruthTests.cs b/test/CDT.Tests/GroundTruthTests.cs index 18706ba..1879a44 100644 --- a/test/CDT.Tests/GroundTruthTests.cs +++ b/test/CDT.Tests/GroundTruthTests.cs @@ -68,8 +68,8 @@ public static class TriangulationTopo public static string ToString(Triangulation cdt) where T : unmanaged, IFloatingPoint, IMinMaxValue, IRootFunctions { - var triangles = cdt.Triangles; - int n = triangles.Count; + var triangles = cdt.Triangles.Span; + int n = triangles.Length; // Step 1: rotate each triangle so smallest vertex is first var canonical = new (int v0, int v1, int v2, int n0, int n1, int n2)[n]; @@ -527,7 +527,7 @@ public void Issue154_1_LargeCoordinates() new V2d(0.0, -1e38), ]); Assert.True(TopologyVerifier.VerifyTopology(cdt)); - Assert.Equal(5, cdt.Triangles.Count); // 2 user verts + 3 super = 5 triangles + Assert.Equal(5, cdt.Triangles.Length); // 2 user verts + 3 super = 5 triangles } // ---- Issue 154: Loops in PseudoPoly ---- @@ -597,7 +597,7 @@ public void Issue174_TinyBoundingBox() ]); Assert.True(TopologyVerifier.VerifyTopology(cdt)); cdt.EraseSuperTriangle(); - Assert.Single(cdt.Triangles); + Assert.Equal(1, cdt.Triangles.Length); } // ---- Issue 204: Insert Vertex on Fixed Edge ---- @@ -686,8 +686,8 @@ public void TwoPartInsertionBatches() Assert.True(TopologyVerifier.VerifyTopology(cdtTwo)); // Triangle count must match - Assert.Equal(cdtSingle.Triangles.Count, cdtTwo.Triangles.Count); - Assert.Equal(cdtSingle.Vertices.Count, cdtTwo.Vertices.Count); + Assert.Equal(cdtSingle.Triangles.Length, cdtTwo.Triangles.Length); + Assert.Equal(cdtSingle.Vertices.Length, cdtTwo.Vertices.Length); Assert.Equal(cdtSingle.FixedEdges.Count, cdtTwo.FixedEdges.Count); } } diff --git a/test/CDT.Tests/ReadmeExamplesTests.cs b/test/CDT.Tests/ReadmeExamplesTests.cs index 1c30c6a..27e5260 100644 --- a/test/CDT.Tests/ReadmeExamplesTests.cs +++ b/test/CDT.Tests/ReadmeExamplesTests.cs @@ -24,8 +24,8 @@ public void Example_DelaunayConvexHull() cdt.EraseSuperTriangle(); // produces convex hull Assert.True(TopologyVerifier.VerifyTopology(cdt)); - Assert.Equal(5, cdt.Vertices.Count); - Assert.True(cdt.Triangles.Count > 0); + Assert.Equal(5, cdt.Vertices.Length); + Assert.True(cdt.Triangles.Length > 0); Assert.Empty(cdt.FixedEdges); } @@ -51,8 +51,8 @@ public void Example_ConstrainedDelaunay_BoundedDomain() cdt.EraseOuterTriangles(); // removes everything outside the boundary Assert.True(TopologyVerifier.VerifyTopology(cdt)); - Assert.Equal(4, cdt.Vertices.Count); - Assert.Equal(2, cdt.Triangles.Count); + Assert.Equal(4, cdt.Vertices.Length); + Assert.Equal(2, cdt.Triangles.Length); Assert.Equal(4, cdt.FixedEdges.Count); } @@ -83,8 +83,8 @@ public void Example_AutoDetectBoundariesAndHoles() cdt.EraseOuterTrianglesAndHoles(); // removes outer AND fills holes automatically Assert.True(TopologyVerifier.VerifyTopology(cdt)); - Assert.Equal(8, cdt.Vertices.Count); - Assert.True(cdt.Triangles.Count > 0); + Assert.Equal(8, cdt.Vertices.Length); + Assert.True(cdt.Triangles.Length > 0); } // ------------------------------------------------------------------------- @@ -109,9 +109,9 @@ public void Example_ConformingDelaunay() cdt.EraseOuterTriangles(); Assert.True(TopologyVerifier.VerifyTopology(cdt)); - Assert.True(cdt.Triangles.Count > 0); + Assert.True(cdt.Triangles.Length > 0); // ConformToEdges may have added midpoints, so vertex count >= 4 - Assert.True(cdt.Vertices.Count >= 4); + Assert.True(cdt.Vertices.Length >= 4); } // ------------------------------------------------------------------------- @@ -161,7 +161,7 @@ public void Example_ExtractEdgesFromTriangles() cdt.EraseSuperTriangle(); // Extract all unique edges from every triangle - HashSet allEdges = CdtUtils.ExtractEdgesFromTriangles(cdt.Triangles); + HashSet allEdges = CdtUtils.ExtractEdgesFromTriangles(cdt.Triangles.Span); // A single triangle has exactly 3 edges Assert.Equal(3, allEdges.Count); @@ -197,6 +197,6 @@ public void Example_ResolveIntersectingConstraints() Assert.True(TopologyVerifier.VerifyTopology(cdt)); // An extra vertex is added at the intersection - Assert.True(cdt.Vertices.Count > 4); + Assert.True(cdt.Vertices.Length > 4); } } diff --git a/test/CDT.Tests/TriangulationTests.cs b/test/CDT.Tests/TriangulationTests.cs index 09227b8..10b5056 100644 --- a/test/CDT.Tests/TriangulationTests.cs +++ b/test/CDT.Tests/TriangulationTests.cs @@ -31,10 +31,10 @@ public void InsertFourPoints_TopologicallyValid() Assert.True(TopologyVerifier.VerifyTopology(cdt)); // 4 user vertices + 3 super-triangle vertices - Assert.Equal(7, cdt.Vertices.Count); + Assert.Equal(7, cdt.Vertices.Length); Assert.Empty(cdt.FixedEdges); // 4 vertices inside a super-triangle → 9 triangles - Assert.Equal(9, cdt.Triangles.Count); + Assert.Equal(9, cdt.Triangles.Length); } [Fact] @@ -45,8 +45,8 @@ public void EraseSuperTriangle_LeavesConvexHull() cdt.EraseSuperTriangle(); Assert.True(TopologyVerifier.VerifyTopology(cdt)); - Assert.Equal(4, cdt.Vertices.Count); - Assert.Equal(2, cdt.Triangles.Count); + Assert.Equal(4, cdt.Vertices.Length); + Assert.Equal(2, cdt.Triangles.Length); } [Fact] @@ -60,12 +60,12 @@ public void AddConstraintEdge_PresentAfterErase() cdt.EraseSuperTriangle(); Assert.True(TopologyVerifier.VerifyTopology(cdt)); - Assert.Equal(4, cdt.Vertices.Count); + Assert.Equal(4, cdt.Vertices.Length); Assert.Single(cdt.FixedEdges); - Assert.Equal(2, cdt.Triangles.Count); + Assert.Equal(2, cdt.Triangles.Length); Assert.Contains(constraint, cdt.FixedEdges); - var edges = CdtUtils.ExtractEdgesFromTriangles(cdt.Triangles); + var edges = CdtUtils.ExtractEdgesFromTriangles(cdt.Triangles.Span); Assert.Contains(constraint, edges); } @@ -92,7 +92,7 @@ public void ExtractEdges_ReturnsAllEdges() cdt.InsertVertices([Pt(0, 0), Pt(1, 0), Pt(0.5, 1)]); cdt.EraseSuperTriangle(); - var edges = CdtUtils.ExtractEdgesFromTriangles(cdt.Triangles); + var edges = CdtUtils.ExtractEdgesFromTriangles(cdt.Triangles.Span); Assert.Equal(3, edges.Count); // one triangle → 3 edges } @@ -109,7 +109,7 @@ public void Square_WithDiagonalConstraint() cdt.EraseSuperTriangle(); Assert.True(TopologyVerifier.VerifyTopology(cdt)); - Assert.Equal(2, cdt.Triangles.Count); + Assert.Equal(2, cdt.Triangles.Length); Assert.Single(cdt.FixedEdges); } @@ -130,8 +130,8 @@ public void InsertionOrder_AsProvided_SameResult() cdtProvided.InsertVertices(pts); cdtProvided.EraseSuperTriangle(); - Assert.Equal(cdtAuto.Triangles.Count, cdtProvided.Triangles.Count); - Assert.Equal(cdtAuto.Vertices.Count, cdtProvided.Vertices.Count); + Assert.Equal(cdtAuto.Triangles.Length, cdtProvided.Triangles.Length); + Assert.Equal(cdtAuto.Vertices.Length, cdtProvided.Vertices.Length); } // ------------------------------------------------------------------------- @@ -186,7 +186,7 @@ public void EraseOuterTriangles_RemovesOutside() Assert.True(TopologyVerifier.VerifyTopology(cdt)); // Square → 2 triangles - Assert.Equal(2, cdt.Triangles.Count); + Assert.Equal(2, cdt.Triangles.Length); } // ------------------------------------------------------------------------- @@ -201,8 +201,8 @@ public void ThreePoints_SingleTriangle() cdt.EraseSuperTriangle(); Assert.True(TopologyVerifier.VerifyTopology(cdt)); - Assert.Equal(3, cdt.Vertices.Count); - Assert.Single(cdt.Triangles); + Assert.Equal(3, cdt.Vertices.Length); + Assert.Equal(1, cdt.Triangles.Length); } // ------------------------------------------------------------------------- @@ -220,7 +220,7 @@ public void FivePoints_ValidTopology() cdt.EraseSuperTriangle(); Assert.True(TopologyVerifier.VerifyTopology(cdt)); - Assert.Equal(5, cdt.Vertices.Count); + Assert.Equal(5, cdt.Vertices.Length); } } diff --git a/viz/CDT.Viz/MainWindow.xaml.cs b/viz/CDT.Viz/MainWindow.xaml.cs index 1f4ff42..a021330 100644 --- a/viz/CDT.Viz/MainWindow.xaml.cs +++ b/viz/CDT.Viz/MainWindow.xaml.cs @@ -292,10 +292,10 @@ private void SaveOff_Click(object sender, RoutedEventArgs e) using var sw = new StreamWriter(dlg.FileName); sw.WriteLine("OFF"); - sw.WriteLine($"{_cdt.Vertices.Count} {_cdt.Triangles.Count} 0"); - foreach (var v in _cdt.Vertices) + sw.WriteLine($"{_cdt.Vertices.Length} {_cdt.Triangles.Length} 0"); + foreach (var v in _cdt.Vertices.Span) sw.WriteLine(FormattableString.Invariant($"{v.X} {v.Y} 0")); - foreach (var t in _cdt.Triangles) + foreach (var t in _cdt.Triangles.Span) sw.WriteLine($"3 {t.V0} {t.V1} {t.V2}"); StatusText.Text = $"Saved {dlg.FileName}"; } @@ -456,16 +456,16 @@ private void Rebuild() } // Push to visual - _visual.Vertices = _cdt.Vertices.ToList(); - _visual.Triangles = _cdt.Triangles.ToList(); + _visual.Vertices = [.. _cdt.Vertices.Span]; + _visual.Triangles = [.. _cdt.Triangles.Span]; _visual.FixedEdges = new HashSet(_cdt.FixedEdges); _visual.ShowSuperTriangle = FinalizeMode.SelectedIndex == 0; _visual.ShowPoints = ShowPoints.IsChecked == true; _visual.ShowIndices = ShowIndices.IsChecked == true; _visual.InvalidateVisual(); - StatsLabel.Text = $"Vertices: {_cdt.Vertices.Count}\n" + - $"Triangles: {_cdt.Triangles.Count}\n" + + StatsLabel.Text = $"Vertices: {_cdt.Vertices.Length}\n" + + $"Triangles: {_cdt.Triangles.Length}\n" + $"Fixed edges: {_cdt.FixedEdges.Count}"; if (!TopologyVerifier.VerifyTopology(_cdt))