diff --git a/benchmark/CDT.Benchmarks/Benchmarks.cs b/benchmark/CDT.Benchmarks/Benchmarks.cs index 549fa0d..04c7f6e 100644 --- a/benchmark/CDT.Benchmarks/Benchmarks.cs +++ b/benchmark/CDT.Benchmarks/Benchmarks.cs @@ -19,7 +19,7 @@ internal static class BenchmarkInputReader /// Reads a CDT input file. /// Format: nVerts nEdges\n x y\n … v1 v2\n … /// - public static (List> Vertices, List Edges) Read(string fileName) + public static (V2d[] Vertices, Edge[] Edges) Read(string fileName) { var path = Path.Combine(AppContext.BaseDirectory, "inputs", fileName); @@ -31,20 +31,20 @@ public static (List> Vertices, List Edges) Read(string fileNam int nVerts = int.Parse(header[0]); int nEdges = int.Parse(header[1]); - var verts = new List>(nVerts); + var verts = new V2d[nVerts]; for (int i = 0; i < nVerts; i++) { var tok = sr.ReadLine()!.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries); - verts.Add(new V2d( + verts[i] = new V2d( double.Parse(tok[0], System.Globalization.CultureInfo.InvariantCulture), - double.Parse(tok[1], System.Globalization.CultureInfo.InvariantCulture))); + double.Parse(tok[1], System.Globalization.CultureInfo.InvariantCulture)); } - var edges = new List(nEdges); + var edges = new Edge[nEdges]; for (int i = 0; i < nEdges; i++) { var tok = sr.ReadLine()!.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries); - edges.Add(new Edge(int.Parse(tok[0]), int.Parse(tok[1]))); + edges[i] = new Edge(int.Parse(tok[0]), int.Parse(tok[1])); } return (verts, edges); @@ -66,8 +66,8 @@ public static (List> Vertices, List Edges) Read(string fileNam [ShortRunJob] public class ConstrainedSwedenBenchmarks { - private List> _vertices = null!; - private List _edges = null!; + private V2d[] _vertices = null!; + private Edge[] _edges = null!; [GlobalSetup] public void Setup() @@ -193,8 +193,8 @@ public Triangulation EraseOuterTriangles_Auto() [ShortRunJob] public class SmallDatasetBenchmarks { - private List> _vertices = null!; - private List _edges = null!; + private V2d[] _vertices = null!; + private Edge[] _edges = null!; [GlobalSetup] public void Setup() @@ -236,7 +236,7 @@ public Triangulation FloatVsDouble_Double() [BenchmarkCategory("FloatVsDouble")] public Triangulation FloatVsDouble_Float() { - var vf = _vertices.Select(v => new V2d((float)v.X, (float)v.Y)).ToList(); + var vf = Array.ConvertAll(_vertices, v => new V2d((float)v.X, (float)v.Y)); var ef = _edges; var cdt = new Triangulation(VertexInsertionOrder.Auto); cdt.InsertVertices(vf); diff --git a/src/CDT.Core/CdtUtils.cs b/src/CDT.Core/CdtUtils.cs index 17f306d..fb7cadf 100644 --- a/src/CDT.Core/CdtUtils.cs +++ b/src/CDT.Core/CdtUtils.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using CDT.Predicates; namespace CDT; @@ -24,9 +25,14 @@ public sealed class DuplicatesInfo internal DuplicatesInfo(int[] mapping, List duplicates) { + _mapping = mapping; + _duplicates = duplicates; Mapping = Array.AsReadOnly(mapping); Duplicates = duplicates.AsReadOnly(); } + + internal readonly int[] _mapping; + internal readonly List _duplicates; } /// @@ -50,10 +56,10 @@ public static class CdtUtils /// A containing a mapping from each original /// index to its canonical index, and the list of duplicate indices. /// - public static DuplicatesInfo FindDuplicates(IReadOnlyList> vertices) + public static DuplicatesInfo FindDuplicates(ReadOnlySpan> vertices) where T : IFloatingPoint { - int n = vertices.Count; + int n = vertices.Length; var mapping = new int[n]; var duplicates = new List(); var posToIndex = new Dictionary<(T, T), int>(n); @@ -84,11 +90,12 @@ public static DuplicatesInfo FindDuplicates(IReadOnlyList> vertices) /// The vertex list to modify in-place. /// The list of duplicate indices to remove. /// The list is mutated in-place. - public static void RemoveDuplicates(List> vertices, IReadOnlyList duplicates) + public static void RemoveDuplicates(List> vertices, ReadOnlySpan duplicates) where T : IFloatingPoint { - if (duplicates.Count == 0) return; - var toRemove = new HashSet(duplicates); + if (duplicates.Length == 0) return; + var toRemove = new HashSet(duplicates.Length); + foreach (int d in duplicates) toRemove.Add(d); int write = 0; for (int i = 0; i < vertices.Count; i++) { @@ -112,9 +119,9 @@ public static DuplicatesInfo RemoveDuplicatesAndRemapEdges( List edges) where T : IFloatingPoint { - var info = FindDuplicates(vertices); - RemoveDuplicates(vertices, info.Duplicates); - RemapEdges(edges, info.Mapping); + var info = FindDuplicates(CollectionsMarshal.AsSpan(vertices)); + RemoveDuplicates(vertices, CollectionsMarshal.AsSpan(info._duplicates)); + RemapEdges(edges, info._mapping); return info; } @@ -130,7 +137,7 @@ public static DuplicatesInfo RemoveDuplicatesAndRemapEdges( /// Vertex index mapping: for each old vertex index i, mapping[i] is the new index. /// /// The list is mutated in-place. - public static void RemapEdges(List edges, IReadOnlyList mapping) + public static void RemapEdges(List edges, ReadOnlySpan mapping) { for (int i = 0; i < edges.Count; i++) { @@ -145,9 +152,9 @@ public static void RemapEdges(List edges, IReadOnlyList mapping) /// Extracts all unique edges from a triangle list. /// The triangle list 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); + var edges = new HashSet(triangles.Length * 3); foreach (var t in triangles) { edges.Add(new Edge(t.V0, t.V1)); @@ -167,12 +174,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); @@ -605,11 +612,11 @@ public static V2d IntersectionPosition(V2d a, V2d b, V2d c, V2dTriangle indices adjacent to vertex A. /// Triangle indices adjacent to vertex B. /// true if any triangle is shared between the two lists. - internal static bool VerticesShareEdge(List aTris, List bTris) + internal static bool VerticesShareEdge(ReadOnlySpan aTris, ReadOnlySpan bTris) { foreach (int t in aTris) { - if (bTris.Contains(t)) return true; + if (MemoryExtensions.Contains(bTris, t)) return true; } return false; } diff --git a/src/CDT.Core/KdTree.cs b/src/CDT.Core/KdTree.cs index a124554..bba1f98 100644 --- a/src/CDT.Core/KdTree.cs +++ b/src/CDT.Core/KdTree.cs @@ -82,7 +82,7 @@ public KdTree(T minX, T minY, T maxX, T maxY) public int Size => _size; /// Inserts point at index from the external buffer. - public void Insert(int iPoint, IReadOnlyList> points) + public void Insert(int iPoint, ReadOnlySpan> points) { _size++; T px = points[iPoint].X, py = points[iPoint].Y; @@ -149,7 +149,7 @@ public void Insert(int iPoint, IReadOnlyList> points) } /// Finds the nearest point to in the external buffer. - public int Nearest(T qx, T qy, IReadOnlyList> points) + public int Nearest(T qx, T qy, ReadOnlySpan> points) { int resultIdx = 0; T minDistSq = T.MaxValue; @@ -311,7 +311,7 @@ private void ExtendTree(T px, T py) _root = newRoot; } - private void InitializeRootBox(IReadOnlyList> points) + private void InitializeRootBox(ReadOnlySpan> points) { Node rootNode = _nodes[_root]; T mxn = points[rootNode.Data![0]].X, myn = points[rootNode.Data[0]].Y; diff --git a/src/CDT.Core/Triangulation.cs b/src/CDT.Core/Triangulation.cs index 2ae393d..2203998 100644 --- a/src/CDT.Core/Triangulation.cs +++ b/src/CDT.Core/Triangulation.cs @@ -2,8 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +using System.Collections.Frozen; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using static CDT.CdtUtils; namespace CDT; @@ -110,6 +112,9 @@ public sealed class Triangulation // KD-tree for nearest-point location private KdTree? _kdTree; + // Frozen set for fast read-only Contains() on fixed edges + private FrozenSet? _frozenFixedEdges; + // ------------------------------------------------------------------------- // Construction // ------------------------------------------------------------------------- @@ -149,9 +154,9 @@ public Triangulation( // ------------------------------------------------------------------------- /// Inserts a list of vertices into the triangulation. - public void InsertVertices(IReadOnlyList> newVertices) + public void InsertVertices(ReadOnlySpan> newVertices) { - if (newVertices.Count == 0) return; + if (newVertices.Length == 0) return; bool isFirstInsertion = _kdTree == null && _vertices.Count == 0; @@ -159,7 +164,7 @@ public void InsertVertices(IReadOnlyList> newVertices) // Euler's formula: a planar triangulation of N points has ~2N triangles. if (isFirstInsertion) { - int n = newVertices.Count; + int n = newVertices.Length; _vertices.EnsureCapacity(n + Indices.SuperTriangleVertexCount); _vertTris.EnsureCapacity(n + Indices.SuperTriangleVertexCount); _triangles.EnsureCapacity(2 * n + 4); @@ -212,7 +217,7 @@ public void InsertVertices(IReadOnlyList> newVertices) // ------------------------------------------------------------------------- /// Inserts constraint edges (constrained Delaunay triangulation). - public void InsertEdges(IReadOnlyList edges) + public void InsertEdges(ReadOnlySpan edges) { var remaining = new List(4); var tppTasks = new List(8); @@ -220,6 +225,7 @@ public void InsertEdges(IReadOnlyList edges) var polyR = new List(8); var outerTris = new Dictionary(); var intersected = new List(8); + var edgeStack = new Stack(4); foreach (var e in edges) { remaining.Clear(); @@ -228,7 +234,7 @@ public void InsertEdges(IReadOnlyList edges) { Edge edge = remaining[^1]; remaining.RemoveAt(remaining.Count - 1); - InsertEdgeIteration(edge, new Edge(e.V1 + _nTargetVerts, e.V2 + _nTargetVerts), remaining, tppTasks, polyL, polyR, outerTris, intersected); + InsertEdgeIteration(edge, new Edge(e.V1 + _nTargetVerts, e.V2 + _nTargetVerts), remaining, tppTasks, polyL, polyR, outerTris, intersected, edgeStack); } } } @@ -241,7 +247,7 @@ public void InsertEdges(IReadOnlyList edges) /// Inserts constraint edges for a conforming Delaunay triangulation. /// May add new vertices (midpoints) until edges are represented. /// - public void ConformToEdges(IReadOnlyList edges) + public void ConformToEdges(ReadOnlySpan edges) { var remaining = new List(8); var flipStack = new Stack(4); @@ -309,6 +315,17 @@ public void EraseOuterTrianglesAndHoles() /// public bool IsFinalized => _vertTris.Count == 0 && _vertices.Count > 0; + /// + /// Freezes (snapshots) the current fixed-edge set for faster read-only Contains() queries. + /// Call this only after all / operations + /// and any other edge mutations are complete, or call it again after such mutations to + /// refresh the snapshot. The frozen set is automatically cleared whenever an edge is added, + /// removed, or split, but is not automatically refreshed after finalization + /// ( / / + /// ). + /// + public void FreezeFixedEdges() => _frozenFixedEdges = _fixedEdges.ToFrozenSet(); + // ------------------------------------------------------------------------- // Internal helpers – super-triangle setup // ------------------------------------------------------------------------- @@ -390,7 +407,7 @@ private void InsertVertex(int iVert, int walkStart) private void InsertVertex(int iVert, Stack stack) { int near = _kdTree != null - ? _kdTree.Nearest(_vertices[iVert].X, _vertices[iVert].Y, _vertices) + ? _kdTree.Nearest(_vertices[iVert].X, _vertices[iVert].Y, CollectionsMarshal.AsSpan(_vertices)) : 0; InsertVertex(iVert, near, stack); } @@ -399,7 +416,7 @@ private void InsertVertex(int iVert) { // Walk-start from KD-tree nearest point, or vertex 0 as fallback int near = _kdTree != null - ? _kdTree.Nearest(_vertices[iVert].X, _vertices[iVert].Y, _vertices) + ? _kdTree.Nearest(_vertices[iVert].X, _vertices[iVert].Y, CollectionsMarshal.AsSpan(_vertices)) : 0; InsertVertex(iVert, near); } @@ -464,16 +481,24 @@ private void InsertVertices_KDTreeBFS(int superGeomVertCount, Box2d box) private readonly struct VertexXComparer : IComparer { - private readonly IReadOnlyList> _vertices; - public VertexXComparer(IReadOnlyList> vertices) => _vertices = vertices; - public int Compare(int a, int b) => _vertices[a].X.CompareTo(_vertices[b].X); + private readonly List> _vertices; + public VertexXComparer(List> vertices) => _vertices = vertices; + public int Compare(int a, int b) + { + var span = CollectionsMarshal.AsSpan(_vertices); + return span[a].X.CompareTo(span[b].X); + } } private readonly struct VertexYComparer : IComparer { - private readonly IReadOnlyList> _vertices; - public VertexYComparer(IReadOnlyList> vertices) => _vertices = vertices; - public int Compare(int a, int b) => _vertices[a].Y.CompareTo(_vertices[b].Y); + private readonly List> _vertices; + public VertexYComparer(List> vertices) => _vertices = vertices; + public int Compare(int a, int b) + { + var span = CollectionsMarshal.AsSpan(_vertices); + return span[a].Y.CompareTo(span[b].Y); + } } /// @@ -510,7 +535,7 @@ private void InsertVertex_FlipFixedEdges(int iV, Stack stack, List fl flipped.Clear(); // Use KD-tree if available, otherwise fall back to vertex 0 (first super-triangle vertex) int near = _kdTree != null - ? _kdTree.Nearest(_vertices[iV].X, _vertices[iV].Y, _vertices) + ? _kdTree.Nearest(_vertices[iV].X, _vertices[iV].Y, CollectionsMarshal.AsSpan(_vertices)) : 0; var (iT, iTopo) = WalkingSearchTrianglesAt(iV, near); if (iTopo == Indices.NoNeighbor) @@ -608,10 +633,12 @@ private int FindTriangleLinear(V2d pos, out PtTriLocation loc) private int WalkTriangles(int startVertex, V2d pos) { + var tris = CollectionsMarshal.AsSpan(_triangles); + var verts = CollectionsMarshal.AsSpan(_vertices); int currTri = _vertTris[startVertex]; for (int guard = 0; guard < 1_000_000; guard++) { - var t = _triangles[currTri]; + var t = tris[currTri]; bool found = true; int offset = guard % 3; for (int i = 0; i < 3; i++) @@ -619,7 +646,7 @@ private int WalkTriangles(int startVertex, V2d pos) int idx = (i + offset) % 3; int vStart = t.GetVertex(idx); int vEnd = t.GetVertex(CdtUtils.Ccw(idx)); - var loc = LocatePointLine(pos, _vertices[vStart], _vertices[vEnd]); + var loc = LocatePointLine(pos, verts[vStart], verts[vEnd]); int iN = t.GetNeighbor(idx); if (loc == PtLineLocation.Right && iN != Indices.NoNeighbor) { @@ -643,13 +670,14 @@ private void InsertVertexInsideTriangle(int v, int iT, Stack stack) int iNewT1 = AddTriangle(); int iNewT2 = AddTriangle(); - var t = _triangles[iT]; + var tris = CollectionsMarshal.AsSpan(_triangles); + var t = tris[iT]; int v1 = t.V0, v2 = t.V1, v3 = t.V2; int n1 = t.N0, n2 = t.N1, n3 = t.N2; - _triangles[iNewT1] = new Triangle(v2, v3, v, n2, iNewT2, iT); - _triangles[iNewT2] = new Triangle(v3, v1, v, n3, iT, iNewT1); - _triangles[iT] = new Triangle(v1, v2, v, n1, iNewT1, iNewT2); + tris[iNewT1] = new Triangle(v2, v3, v, n2, iNewT2, iT); + tris[iNewT2] = new Triangle(v3, v1, v, n3, iT, iNewT1); + tris[iT] = new Triangle(v1, v2, v, n1, iNewT1, iNewT2); SetAdjacentTriangle(v, iT); SetAdjacentTriangle(v3, iNewT1); @@ -667,24 +695,25 @@ private void InsertVertexOnEdge(int v, int iT1, int iT2, bool handleFixedSplitEd int iTnew1 = AddTriangle(); int iTnew2 = AddTriangle(); - var t1 = _triangles[iT1]; + var tris = CollectionsMarshal.AsSpan(_triangles); + var t1 = tris[iT1]; int i1 = CdtUtils.NeighborIndex(t1, iT2); int v1 = t1.GetVertex(CdtUtils.OpposedVertexIndex(i1)); int v2 = t1.GetVertex(CdtUtils.Ccw(CdtUtils.OpposedVertexIndex(i1))); int n1 = t1.GetNeighbor(CdtUtils.OpposedVertexIndex(i1)); int n4 = t1.GetNeighbor(CdtUtils.Cw(CdtUtils.OpposedVertexIndex(i1))); - var t2 = _triangles[iT2]; + var t2 = tris[iT2]; int i2 = CdtUtils.NeighborIndex(t2, iT1); int v3 = t2.GetVertex(CdtUtils.OpposedVertexIndex(i2)); int v4 = t2.GetVertex(CdtUtils.Ccw(CdtUtils.OpposedVertexIndex(i2))); int n3 = t2.GetNeighbor(CdtUtils.OpposedVertexIndex(i2)); int n2 = t2.GetNeighbor(CdtUtils.Cw(CdtUtils.OpposedVertexIndex(i2))); - _triangles[iT1] = new Triangle(v, v1, v2, iTnew1, n1, iT2); - _triangles[iT2] = new Triangle(v, v2, v3, iT1, n2, iTnew2); - _triangles[iTnew1] = new Triangle(v, v4, v1, iTnew2, n4, iT1); - _triangles[iTnew2] = new Triangle(v, v3, v4, iT2, n3, iTnew1); + tris[iT1] = new Triangle(v, v1, v2, iTnew1, n1, iT2); + tris[iT2] = new Triangle(v, v2, v3, iT1, n2, iTnew2); + tris[iTnew1] = new Triangle(v, v4, v1, iTnew2, n4, iT1); + tris[iTnew2] = new Triangle(v, v3, v4, iT2, n3, iTnew1); SetAdjacentTriangle(v, iT1); SetAdjacentTriangle(v4, iTnew1); @@ -753,7 +782,12 @@ private void EdgeFlipInfo( private bool IsFlipNeeded(int iV1, int iV2, int iV3, int iV4) { // Skip HashSet lookup when there are no fixed edges (pure vertex-insertion path). - if (_fixedEdges.Count > 0 && _fixedEdges.Contains(new Edge(iV2, iV4))) return false; + if (_fixedEdges.Count > 0) + { + var e24 = new Edge(iV2, iV4); + if (_frozenFixedEdges != null ? _frozenFixedEdges.Contains(e24) : _fixedEdges.Contains(e24)) + return false; + } var v1 = _vertices[iV1]; var v2 = _vertices[iV2]; @@ -814,7 +848,8 @@ private void InsertEdgeIteration( List polyL, List polyR, Dictionary outerTris, - List intersected) + List intersected, + Stack stack) { int iA = edge.V1, iB = edge.V2; if (iA == iB) return; @@ -860,7 +895,7 @@ private void InsertEdgeIteration( var tOpo = _triangles[iTopo]; int iVopo = CdtUtils.OpposedVertex(tOpo, iT); - HandleIntersectingEdgeStrategy(iVL, iVR, iA, iB, iT, iTopo, originalEdge, a, b, distTol, remaining, tppIterations, out bool @return); + HandleIntersectingEdgeStrategy(iVL, iVR, iA, iB, iT, iTopo, originalEdge, a, b, distTol, remaining, tppIterations, stack, out bool @return); if (@return) return; var loc = LocatePointLine(_vertices[iVopo], a, b, distTol); @@ -907,8 +942,8 @@ private void InsertEdgeIteration( int iTL = intersected[^1]; intersected.RemoveAt(intersected.Count - 1); int iTR = intersected[^1]; intersected.RemoveAt(intersected.Count - 1); - TriangulatePseudoPolygon(polyL, outerTris, iTL, iTR, intersected, tppIterations); - TriangulatePseudoPolygon(polyR, outerTris, iTR, iTL, intersected, tppIterations); + TriangulatePseudoPolygon(CollectionsMarshal.AsSpan(polyL), outerTris, iTL, iTR, intersected, tppIterations); + TriangulatePseudoPolygon(CollectionsMarshal.AsSpan(polyR), outerTris, iTR, iTL, intersected, tppIterations); if (iB != edge.V2) { @@ -925,6 +960,7 @@ private void HandleIntersectingEdgeStrategy( int iVL, int iVR, int iA, int iB, int iT, int iTopo, Edge originalEdge, V2d a, V2d b, T distTol, List remaining, List tppIterations, + Stack stack, out bool @return) { @return = false; @@ -947,7 +983,7 @@ private void HandleIntersectingEdgeStrategy( if (_fixedEdges.Contains(edgeLR)) { var newV = IntersectionPosition(_vertices[iA], _vertices[iB], _vertices[iVL], _vertices[iVR]); - int iNewVert = SplitFixedEdgeAt(edgeLR, newV, iT, iTopo); + int iNewVert = SplitFixedEdgeAt(edgeLR, newV, iT, iTopo, stack); remaining.Add(new Edge(iA, iNewVert)); remaining.Add(new Edge(iNewVert, iB)); @return = true; @@ -1005,7 +1041,7 @@ private void ConformToEdgeIteration( var vOpo = _vertices[iVopo]; HandleConformIntersecting(iVleft, iVright, iA, iB, iT, iTopo, - originals, overlaps, remaining, out bool @return); + originals, overlaps, remaining, flipStack, out bool @return); if (@return) return; iT = iTopo; @@ -1034,6 +1070,7 @@ private void ConformToEdgeIteration( foreach (var fe in flippedFixed) { _fixedEdges.Remove(fe); + _frozenFixedEdges = null; ushort prevOv = _overlapCount.TryGetValue(fe, out var ov) ? ov : (ushort)0; _overlapCount.Remove(fe); var prevOrig = _pieceToOriginals.TryGetValue(fe, out var po) ? po : new List { fe }; @@ -1045,6 +1082,7 @@ private void HandleConformIntersecting( int iVleft, int iVright, int iA, int iB, int iT, int iTopo, List originals, ushort overlaps, List remaining, + Stack stack, out bool @return) { @return = false; @@ -1064,7 +1102,7 @@ private void HandleConformIntersecting( if (_fixedEdges.Contains(edgeLR)) { var newV = IntersectionPosition(_vertices[iA], _vertices[iB], _vertices[iVleft], _vertices[iVright]); - int iNewVert = SplitFixedEdgeAt(edgeLR, newV, iT, iTopo); + int iNewVert = SplitFixedEdgeAt(edgeLR, newV, iT, iTopo, stack); remaining.Add(new ConformToEdgeTask(new Edge(iNewVert, iB), originals, overlaps)); remaining.Add(new ConformToEdgeTask(new Edge(iA, iNewVert), originals, overlaps)); @return = true; @@ -1081,14 +1119,14 @@ private void HandleConformIntersecting( private readonly record struct TriangulatePseudoPolygonTask(int IA, int IB, int IT, int IParent, int IInParent); private void TriangulatePseudoPolygon( - List poly, + ReadOnlySpan poly, Dictionary outerTris, int iT, int iN, List trianglesToReuse, List iterations) { iterations.Clear(); - iterations.Add(new TriangulatePseudoPolygonTask(0, poly.Count - 1, iT, iN, 0)); + iterations.Add(new TriangulatePseudoPolygonTask(0, poly.Length - 1, iT, iN, 0)); while (iterations.Count > 0) { TriangulatePseudoPolygonIteration(poly, outerTris, trianglesToReuse, iterations); @@ -1096,7 +1134,7 @@ private void TriangulatePseudoPolygon( } private void TriangulatePseudoPolygonIteration( - List poly, + ReadOnlySpan poly, Dictionary outerTris, List trianglesToReuse, List iterations) @@ -1151,15 +1189,16 @@ private void TriangulatePseudoPolygonIteration( SetAdjacentTriangle(c, iT); } - private int FindDelaunayPoint(List poly, int iA, int iB) + private int FindDelaunayPoint(ReadOnlySpan poly, int iA, int iB) { - var a = _vertices[poly[iA]]; - var b = _vertices[poly[iB]]; + var verts = CollectionsMarshal.AsSpan(_vertices); + var a = verts[poly[iA]]; + var b = verts[poly[iB]]; int best = iA + 1; - var bestV = _vertices[poly[best]]; + var bestV = verts[poly[best]]; for (int i = iA + 1; i < iB; i++) { - var v = _vertices[poly[i]]; + var v = verts[poly[i]]; if (IsInCircumcircle(v, a, b, bestV)) { best = i; @@ -1257,6 +1296,7 @@ private void PivotVertexTriangleCW(int v) private void FixEdge(Edge edge) { + _frozenFixedEdges = null; if (!_fixedEdges.Add(edge)) { _overlapCount.TryGetValue(edge, out ushort cur); @@ -1276,6 +1316,7 @@ private void SplitFixedEdge(Edge edge, int iSplitVert) var half1 = new Edge(edge.V1, iSplitVert); var half2 = new Edge(iSplitVert, edge.V2); _fixedEdges.Remove(edge); + _frozenFixedEdges = null; FixEdge(half1); FixEdge(half2); if (_overlapCount.TryGetValue(edge, out ushort ov)) @@ -1294,11 +1335,11 @@ private void SplitFixedEdge(Edge edge, int iSplitVert) InsertUnique(_pieceToOriginals.GetOrAdd(half2), newOrig); } - private int SplitFixedEdgeAt(Edge edge, V2d splitVert, int iT, int iTopo) + private int SplitFixedEdgeAt(Edge edge, V2d splitVert, int iT, int iTopo, Stack stack) { int iSplit = _vertices.Count; AddNewVertex(splitVert, Indices.NoNeighbor); - var stack = new Stack(4); + stack.Clear(); InsertVertexOnEdge(iSplit, iT, iTopo, handleFixedSplitEdge: false, stack); TryAddVertexToLocator(iSplit); EnsureDelaunayByEdgeFlips(iSplit, stack); @@ -1329,18 +1370,20 @@ private bool HasEdge(int va, int vb) private HashSet GrowToBoundary(Stack seeds) { + var tris = CollectionsMarshal.AsSpan(_triangles); + var frozen = _frozenFixedEdges; var traversed = new HashSet(); while (seeds.Count > 0) { int iT = seeds.Pop(); traversed.Add(iT); - var t = _triangles[iT]; + var t = tris[iT]; for (int i = 0; i < 3; i++) { int va = t.GetVertex(CdtUtils.Ccw(i)); int vb = t.GetVertex(CdtUtils.Cw(i)); var opEdge = new Edge(va, vb); - if (_fixedEdges.Contains(opEdge)) continue; + if (frozen != null ? frozen.Contains(opEdge) : _fixedEdges.Contains(opEdge)) continue; int iN = t.GetNeighbor(CdtUtils.OpposedNeighborIndex(i)); if (iN != Indices.NoNeighbor && !traversed.Contains(iN)) seeds.Push(iN); @@ -1357,6 +1400,7 @@ private void FinalizeTriangulation(HashSet removedTriangles) { _vertices.RemoveRange(0, Indices.SuperTriangleVertexCount); RemapEdgesNoSuperTriangle(_fixedEdges); + _frozenFixedEdges = null; // fixed-edge keys shifted; frozen snapshot is now stale RemapEdgesNoSuperTriangle(_overlapCount); RemapEdgesNoSuperTriangle(_pieceToOriginals); } @@ -1444,36 +1488,42 @@ private void RemoveTriangles(HashSet removed) private ushort[] CalculateTriangleDepths() { var depths = new ushort[_triangles.Count]; - for (int i = 0; i < depths.Length; i++) depths[i] = ushort.MaxValue; + Array.Fill(depths, 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 = _vertTris.Count > 0 ? _vertTris[0] : (_triangles.Count > 0 ? 0 : Indices.NoNeighbor); + if (seedTri == Indices.NoNeighbor) return depths; - var layerSeeds = new Stack(); - layerSeeds.Push(seedTri); + var currentSeeds = new Stack(); + var nextSeeds = new Stack(); + var behindBoundary = new Dictionary(); + currentSeeds.Push(seedTri); ushort depth = 0; - while (layerSeeds.Count > 0) + while (currentSeeds.Count > 0) { - var nextLayer = PeelLayer(layerSeeds, depth, depths); - layerSeeds = new Stack(nextLayer.Keys); + behindBoundary.Clear(); + PeelLayer(currentSeeds, depth, depths, behindBoundary); + nextSeeds.Clear(); + foreach (var kv in behindBoundary) nextSeeds.Push(kv.Key); + (currentSeeds, nextSeeds) = (nextSeeds, currentSeeds); depth++; } return depths; } - private Dictionary PeelLayer( - Stack seeds, ushort layerDepth, ushort[] triDepths) + private void PeelLayer( + Stack seeds, ushort layerDepth, ushort[] triDepths, Dictionary behindBoundary) { - var behindBoundary = new Dictionary(); + var tris = CollectionsMarshal.AsSpan(_triangles); + var frozen = _frozenFixedEdges; while (seeds.Count > 0) { int iT = seeds.Pop(); triDepths[iT] = Math.Min(triDepths[iT], layerDepth); behindBoundary.Remove(iT); - var t = _triangles[iT]; + var t = tris[iT]; for (int i = 0; i < 3; i++) { int va = t.GetVertex(CdtUtils.Ccw(i)); @@ -1482,7 +1532,7 @@ private Dictionary PeelLayer( int iN = t.GetNeighbor(CdtUtils.OpposedNeighborIndex(i)); if (iN == Indices.NoNeighbor || triDepths[iN] <= layerDepth) continue; - if (_fixedEdges.Contains(opEdge)) + if (frozen != null ? frozen.Contains(opEdge) : _fixedEdges.Contains(opEdge)) { ushort nextDepth = layerDepth; if (_overlapCount.TryGetValue(opEdge, out ushort ov)) @@ -1497,7 +1547,6 @@ private Dictionary PeelLayer( } } } - return behindBoundary; } // ------------------------------------------------------------------------- @@ -1507,16 +1556,17 @@ private Dictionary PeelLayer( private void InitKdTree() { var box = new Box2d(); - box.Envelop(_vertices); + box.Envelop(CollectionsMarshal.AsSpan(_vertices)); _kdTree = new KdTree(box.Min.X, box.Min.Y, box.Max.X, box.Max.Y); + var verts = CollectionsMarshal.AsSpan(_vertices); for (int i = 0; i < _vertices.Count; i++) - _kdTree.Insert(i, _vertices); + _kdTree.Insert(i, verts); } private void TryAddVertexToLocator(int iV) { // Only add to the locator if it's already initialized - _kdTree?.Insert(iV, _vertices); + _kdTree?.Insert(iV, CollectionsMarshal.AsSpan(_vertices)); } // ------------------------------------------------------------------------- diff --git a/src/CDT.Core/Types.cs b/src/CDT.Core/Types.cs index 7f6475c..3bed61a 100644 --- a/src/CDT.Core/Types.cs +++ b/src/CDT.Core/Types.cs @@ -78,7 +78,7 @@ public void Envelop(T x, T y) public void Envelop(V2d p) => Envelop(p.X, p.Y); /// Expands the box to include all given points. - public void Envelop(IReadOnlyList> points) + public void Envelop(ReadOnlySpan> points) { foreach (var p in points) { @@ -87,7 +87,7 @@ public void Envelop(IReadOnlyList> points) } /// Creates a box containing all the given points. - public static Box2d Of(IReadOnlyList> points) + public static Box2d Of(ReadOnlySpan> points) { var box = new Box2d(); box.Envelop(points); diff --git a/test/CDT.Tests/GroundTruthTests.cs b/test/CDT.Tests/GroundTruthTests.cs index 18706ba..f2dc3a5 100644 --- a/test/CDT.Tests/GroundTruthTests.cs +++ b/test/CDT.Tests/GroundTruthTests.cs @@ -259,15 +259,15 @@ private void RunConstraint( ExpectedFileName(inputFile, isTyped, order, strategy, section)); var (verts, edges) = TestInputReader.ReadInput(inputPath); - Assert.Empty(CdtUtils.FindDuplicates(verts).Duplicates); + Assert.Empty(CdtUtils.FindDuplicates(verts.ToArray()).Duplicates); var cdt = new Triangulation( GroundTruthHelpers.ParseOrder(order), GroundTruthHelpers.ParseStrategy(strategy), T.Zero); - cdt.InsertVertices(verts); - cdt.InsertEdges(edges); + cdt.InsertVertices(verts.ToArray()); + cdt.InsertEdges(edges.ToArray()); postInsert(cdt); Assert.True(TopologyVerifier.VerifyTopology(cdt)); GroundTruthHelpers.AssertTopologyMatchesFile(cdt, expectedPath); @@ -361,8 +361,8 @@ public void GroundTruth_Conforming(string inputFile, bool isTyped) IntersectingConstraintEdges.NotAllowed, T.Zero); - cdt.InsertVertices(verts); - cdt.ConformToEdges(edges); + cdt.InsertVertices(verts.ToArray()); + cdt.ConformToEdges(edges.ToArray()); cdt.EraseOuterTrianglesAndHoles(); Assert.True(TopologyVerifier.VerifyTopology(cdt)); @@ -413,8 +413,8 @@ public void GroundTruth_CrossingEdges_Constraint(string inputFile) VertexInsertionOrder.Auto, IntersectingConstraintEdges.TryResolve, T.Zero); - cdt.InsertVertices(verts); - cdt.InsertEdges(edges); + cdt.InsertVertices(verts.ToArray()); + cdt.InsertEdges(edges.ToArray()); Assert.True(TopologyVerifier.VerifyTopology(cdt)); GroundTruthHelpers.AssertTopologyMatchesFile( @@ -435,8 +435,8 @@ public void GroundTruth_CrossingEdges_Conforming(string inputFile) VertexInsertionOrder.Auto, IntersectingConstraintEdges.TryResolve, T.Zero); - cdt.InsertVertices(verts); - cdt.ConformToEdges(edges); + cdt.InsertVertices(verts.ToArray()); + cdt.ConformToEdges(edges.ToArray()); Assert.True(TopologyVerifier.VerifyTopology(cdt)); GroundTruthHelpers.AssertTopologyMatchesFile( @@ -476,8 +476,8 @@ public void HangingIntersection_f64_Auto_Resolve_All() VertexInsertionOrder.Auto, IntersectingConstraintEdges.TryResolve, 0.0); - cdt.InsertVertices(verts); - cdt.InsertEdges(edges); + cdt.InsertVertices(verts.ToArray()); + cdt.InsertEdges(edges.ToArray()); Assert.True(TopologyVerifier.VerifyTopology(cdt)); GroundTruthHelpers.AssertTopologyMatchesFile( @@ -494,8 +494,8 @@ public void DontFlip_f64_AsProvided_Resolve_All() VertexInsertionOrder.AsProvided, IntersectingConstraintEdges.TryResolve, 0.0); - cdt.InsertVertices(verts); - cdt.InsertEdges(edges); + cdt.InsertVertices(verts.ToArray()); + cdt.InsertEdges(edges.ToArray()); Assert.True(TopologyVerifier.VerifyTopology(cdt)); GroundTruthHelpers.AssertTopologyMatchesFile( @@ -552,7 +552,7 @@ public void Issue154_2_LoopsInPseudoPoly() VertexInsertionOrder.Auto, IntersectingConstraintEdges.NotAllowed, 0.0); - cdt.InsertVertices(pts); + cdt.InsertVertices(pts.ToArray()); cdt.InsertEdges([new Edge(0, 1)]); Assert.True(TopologyVerifier.VerifyTopology(cdt)); } @@ -577,7 +577,7 @@ public void Issue154_3_LoopsInPseudoPoly2() VertexInsertionOrder.Auto, IntersectingConstraintEdges.NotAllowed, 0.0); - cdt.InsertVertices(pts); + cdt.InsertVertices(pts.ToArray()); cdt.InsertEdges([new Edge(0, 8)]); Assert.True(TopologyVerifier.VerifyTopology(cdt)); } @@ -633,8 +633,8 @@ public void Regression_Debug2_File() var (verts, edges) = TestInputReader.ReadInput( GroundTruthHelpers.InputPath("debug2.txt")); var cdt = new Triangulation(); - cdt.InsertVertices(verts); - cdt.InsertEdges(edges); + cdt.InsertVertices(verts.ToArray()); + cdt.InsertEdges(edges.ToArray()); Assert.True(TopologyVerifier.VerifyTopology(cdt)); } @@ -646,8 +646,8 @@ public void Regression_Hanging3_File() var (verts, edges) = TestInputReader.ReadInput( GroundTruthHelpers.InputPath("hanging3.txt")); var cdt = new Triangulation(); - cdt.InsertVertices(verts); - cdt.InsertEdges(edges); + cdt.InsertVertices(verts.ToArray()); + cdt.InsertEdges(edges.ToArray()); Assert.True(TopologyVerifier.VerifyTopology(cdt)); } @@ -666,8 +666,8 @@ public void TwoPartInsertionBatches() VertexInsertionOrder.Auto, IntersectingConstraintEdges.NotAllowed, 0.0); - cdtSingle.InsertVertices(verts); - cdtSingle.InsertEdges(edges); + cdtSingle.InsertVertices(verts.ToArray()); + cdtSingle.InsertEdges(edges.ToArray()); cdtSingle.EraseOuterTrianglesAndHoles(); // Two-batch: insert vertices in two halves, then edges @@ -676,9 +676,9 @@ public void TwoPartInsertionBatches() VertexInsertionOrder.Auto, IntersectingConstraintEdges.NotAllowed, 0.0); - cdtTwo.InsertVertices(verts.Take(half).ToList()); - cdtTwo.InsertVertices(verts.Skip(half).ToList()); - cdtTwo.InsertEdges(edges); + cdtTwo.InsertVertices(verts.Take(half).ToArray()); + cdtTwo.InsertVertices(verts.Skip(half).ToArray()); + cdtTwo.InsertEdges(edges.ToArray()); cdtTwo.EraseOuterTrianglesAndHoles(); // Both should be topologically valid @@ -711,13 +711,14 @@ private static List> MakeGrid() public void KdTree_NearestPoint_ExactMatch() { var pts = MakeGrid(); // 100 points on 10×10 grid + var ptsSpan = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(pts); var kd = new KdTree(0, 0, 9, 9); - for (int i = 0; i < pts.Count; i++) kd.Insert(i, pts); + for (int i = 0; i < pts.Count; i++) kd.Insert(i, ptsSpan); // Querying the exact location of each grid point returns that point for (int i = 0; i < pts.Count; i++) { - int nearest = kd.Nearest(pts[i].X, pts[i].Y, pts); + int nearest = kd.Nearest(pts[i].X, pts[i].Y, ptsSpan); Assert.Equal(i, nearest); } } @@ -730,12 +731,13 @@ public void KdTree_NearestPoint_NearestToMidpoint() { new(0, 0), new(1, 0), new(1, 1), new(0, 1), }; + var ptsSpan = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(pts); var kd = new KdTree(0, 0, 1, 1); - for (int i = 0; i < pts.Count; i++) kd.Insert(i, pts); + for (int i = 0; i < pts.Count; i++) kd.Insert(i, ptsSpan); // Query the centre (0.5, 0.5): all corners are equidistant, // but a specific one will be returned; just verify it's a corner. - int nearest = kd.Nearest(0.5, 0.5, pts); + int nearest = kd.Nearest(0.5, 0.5, ptsSpan); Assert.InRange(nearest, 0, 3); } @@ -744,9 +746,10 @@ public void KdTree_NearestPoint_EdgeCase() { // Single point var pts = new List> { new(3.0, 7.0) }; + var ptsSpan = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(pts); var kd = new KdTree(); - kd.Insert(0, pts); - Assert.Equal(0, kd.Nearest(100.0, 200.0, pts)); + kd.Insert(0, ptsSpan); + Assert.Equal(0, kd.Nearest(100.0, 200.0, ptsSpan)); } [Fact] @@ -757,15 +760,16 @@ public void KdTree_StressTest_100Points_1000Queries() for (int i = 0; i < 100; i++) pts.Add(new V2d(rng.NextDouble() * 100, rng.NextDouble() * 100)); + var ptsSpan = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(pts); var kd = new KdTree(0, 0, 100, 100); - for (int i = 0; i < pts.Count; i++) kd.Insert(i, pts); + for (int i = 0; i < pts.Count; i++) kd.Insert(i, ptsSpan); for (int q = 0; q < 1000; q++) { double qx = rng.NextDouble() * 100; double qy = rng.NextDouble() * 100; - int kdNearest = kd.Nearest(qx, qy, pts); + int kdNearest = kd.Nearest(qx, qy, ptsSpan); // Brute-force verification int bruteNearest = 0; @@ -792,12 +796,13 @@ public void KdTree_Regression200() new(10.0, 10.0), // 2 new(0.0, 10.0), // 3 }; + var ptsSpan = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(pts); var kd = new KdTree(0, 0, 10, 10); - for (int i = 0; i < pts.Count; i++) kd.Insert(i, pts); + for (int i = 0; i < pts.Count; i++) kd.Insert(i, ptsSpan); - Assert.Equal(0, kd.Nearest(1.0, 1.0, pts)); - Assert.Equal(1, kd.Nearest(9.0, 1.0, pts)); - Assert.Equal(2, kd.Nearest(9.0, 9.0, pts)); - Assert.Equal(3, kd.Nearest(1.0, 9.0, pts)); + Assert.Equal(0, kd.Nearest(1.0, 1.0, ptsSpan)); + Assert.Equal(1, kd.Nearest(9.0, 1.0, ptsSpan)); + Assert.Equal(2, kd.Nearest(9.0, 9.0, ptsSpan)); + Assert.Equal(3, kd.Nearest(1.0, 9.0, ptsSpan)); } } diff --git a/test/CDT.Tests/ReadmeExamplesTests.cs b/test/CDT.Tests/ReadmeExamplesTests.cs index 1c30c6a..6067789 100644 --- a/test/CDT.Tests/ReadmeExamplesTests.cs +++ b/test/CDT.Tests/ReadmeExamplesTests.cs @@ -20,7 +20,7 @@ public void Example_DelaunayConvexHull() }; var cdt = new Triangulation(); - cdt.InsertVertices(vertices); + cdt.InsertVertices(vertices.ToArray()); cdt.EraseSuperTriangle(); // produces convex hull Assert.True(TopologyVerifier.VerifyTopology(cdt)); @@ -46,8 +46,8 @@ public void Example_ConstrainedDelaunay_BoundedDomain() }; var cdt = new Triangulation(); - cdt.InsertVertices(vertices); - cdt.InsertEdges(edges); + cdt.InsertVertices(vertices.ToArray()); + cdt.InsertEdges(edges.ToArray()); cdt.EraseOuterTriangles(); // removes everything outside the boundary Assert.True(TopologyVerifier.VerifyTopology(cdt)); @@ -78,8 +78,8 @@ public void Example_AutoDetectBoundariesAndHoles() }; var cdt = new Triangulation(); - cdt.InsertVertices(vertices); - cdt.InsertEdges(edges); + cdt.InsertVertices(vertices.ToArray()); + cdt.InsertEdges(edges.ToArray()); cdt.EraseOuterTrianglesAndHoles(); // removes outer AND fills holes automatically Assert.True(TopologyVerifier.VerifyTopology(cdt)); @@ -104,8 +104,8 @@ public void Example_ConformingDelaunay() }; var cdt = new Triangulation(); - cdt.InsertVertices(vertices); - cdt.ConformToEdges(edges); // may split edges and add new points + cdt.InsertVertices(vertices.ToArray()); + cdt.ConformToEdges(edges.ToArray()); // may split edges and add new points cdt.EraseOuterTriangles(); Assert.True(TopologyVerifier.VerifyTopology(cdt)); @@ -142,8 +142,8 @@ public void Example_RemoveDuplicatesAndRemapEdges() Assert.Equal(new Edge(1, 2), edges[1]); var cdt = new Triangulation(); - cdt.InsertVertices(vertices); - cdt.InsertEdges(edges.Where(e => e.V1 != e.V2).ToList()); // skip degenerate self-edge + cdt.InsertVertices(vertices.ToArray()); + cdt.InsertEdges(edges.Where(e => e.V1 != e.V2).ToArray()); // skip degenerate self-edge cdt.EraseSuperTriangle(); Assert.True(TopologyVerifier.VerifyTopology(cdt)); @@ -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.ToArray()); // A single triangle has exactly 3 edges Assert.Equal(3, allEdges.Count); @@ -191,8 +191,8 @@ public void Example_ResolveIntersectingConstraints() IntersectingConstraintEdges.TryResolve, 0.0); - cdt.InsertVertices(vertices); - cdt.InsertEdges(edges); + cdt.InsertVertices(vertices.ToArray()); + cdt.InsertEdges(edges.ToArray()); cdt.EraseSuperTriangle(); Assert.True(TopologyVerifier.VerifyTopology(cdt)); diff --git a/test/CDT.Tests/TriangulationTests.cs b/test/CDT.Tests/TriangulationTests.cs index 09227b8..b1629a8 100644 --- a/test/CDT.Tests/TriangulationTests.cs +++ b/test/CDT.Tests/TriangulationTests.cs @@ -65,7 +65,7 @@ public void AddConstraintEdge_PresentAfterErase() Assert.Equal(2, cdt.Triangles.Count); Assert.Contains(constraint, cdt.FixedEdges); - var edges = CdtUtils.ExtractEdgesFromTriangles(cdt.Triangles); + var edges = CdtUtils.ExtractEdgesFromTriangles(cdt.Triangles.ToArray()); 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.ToArray()); Assert.Equal(3, edges.Count); // one triangle → 3 edges } @@ -145,7 +145,7 @@ public void FindDuplicates_DetectsExactMatches() { Pt(0, 0), Pt(1, 0), Pt(0, 0), Pt(2, 0), }; - var info = CdtUtils.FindDuplicates(pts); + var info = CdtUtils.FindDuplicates(pts.ToArray()); Assert.Single(info.Duplicates); Assert.Equal(2, info.Duplicates[0]); // mapping[2] should equal mapping[0] = 0 diff --git a/viz/CDT.Viz/MainWindow.xaml.cs b/viz/CDT.Viz/MainWindow.xaml.cs index 1f4ff42..9377fec 100644 --- a/viz/CDT.Viz/MainWindow.xaml.cs +++ b/viz/CDT.Viz/MainWindow.xaml.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -430,14 +431,14 @@ private void Rebuild() workEdges = edges; } - _cdt.InsertVertices(workPts); + _cdt.InsertVertices(CollectionsMarshal.AsSpan(workPts)); if (vLimit >= _loadedPoints.Count && workEdges.Count > 0) { if (conforming) - _cdt.ConformToEdges(workEdges); + _cdt.ConformToEdges(CollectionsMarshal.AsSpan(workEdges)); else - _cdt.InsertEdges(workEdges); + _cdt.InsertEdges(CollectionsMarshal.AsSpan(workEdges)); } } catch (Exception ex)