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)