From f848f5f2510fc2caad30317ce9d1b2f4a16e71da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 09:47:34 +0000 Subject: [PATCH 1/9] Initial plan From a634d5e5fbb77fb14d641b220d90972b5369763e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 09:51:35 +0000 Subject: [PATCH 2/9] feat: add CI/CD pipeline, NuGet package setup, upstream sync, and README Co-authored-by: MichaCo <5837539+MichaCo@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 83 ++++++++++++++++++++ .github/workflows/upstream-sync.yml | 114 ++++++++++++++++++++++++++++ .last-sync-commit | 1 + README.md | 82 +++++++++++++++++++- src/CDT.Core/CDT.Core.csproj | 26 ++++++- 5 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci-cd.yml create mode 100644 .github/workflows/upstream-sync.yml create mode 100644 .last-sync-commit diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..6fab4b1 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,83 @@ +name: CI/CD + +on: + push: + branches: [ main ] + tags: [ 'v*.*.*' ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + build-and-test: + name: Build and Test + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for SourceLink + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.0.x + 10.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore -c Release + + - name: Test + run: dotnet test --no-build -c Release --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage + + - name: Generate coverage report + uses: danielpalme/ReportGenerator-GitHub-Action@5 + with: + reports: ./coverage/**/coverage.cobertura.xml + targetdir: coveragereport + reporttypes: MarkdownSummaryGithub + + - name: Write coverage to job summary + run: cat coveragereport/SummaryGithub.md >> $GITHUB_STEP_SUMMARY + + publish: + name: Publish to NuGet + needs: build-and-test + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for SourceLink + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.0.x + 10.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore -c Release + + - name: Pack + run: dotnet pack src/CDT.Core/CDT.Core.csproj --no-build -c Release -o ./artifacts + + - name: Push to NuGet.org + # Requires a NUGET_API_KEY secret configured in: + # GitHub → Repository Settings → Secrets and variables → Actions → New repository secret + run: dotnet nuget push ./artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/.github/workflows/upstream-sync.yml b/.github/workflows/upstream-sync.yml new file mode 100644 index 0000000..8561612 --- /dev/null +++ b/.github/workflows/upstream-sync.yml @@ -0,0 +1,114 @@ +name: Upstream Sync + +on: + schedule: + # Run daily at 06:00 UTC + - cron: '0 6 * * *' + workflow_dispatch: + +jobs: + check-upstream: + name: Check for upstream changes + runs-on: ubuntu-latest + permissions: + contents: write # needed to update .last-sync-commit on main + issues: write # needed to create the issue that triggers Copilot + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: main + + - name: Get last synced commit + id: last-sync + run: echo "sha=$(cat .last-sync-commit | tr -d '[:space:]')" >> "$GITHUB_OUTPUT" + + - name: Get latest upstream commit + id: upstream + run: | + # git ls-remote reads public repos without any auth or API calls + LATEST=$(git ls-remote https://github.com/artem-ogre/CDT.git refs/heads/master | cut -f1) + if [ -z "$LATEST" ]; then + echo "::error::Could not resolve latest upstream commit SHA" + exit 1 + fi + echo "sha=$LATEST" >> "$GITHUB_OUTPUT" + echo "Latest upstream commit: $LATEST" + + - name: Create Copilot porting issue + if: steps.last-sync.outputs.sha != steps.upstream.outputs.sha + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + LAST="${{ steps.last-sync.outputs.sha }}" + LATEST="${{ steps.upstream.outputs.sha }}" + DATE=$(date -u +"%Y-%m-%d") + + echo "Upstream has new commits since ${LAST:0:7} (latest: ${LATEST:0:7})" + + # Shallow-clone the upstream history (metadata only) to build a commit list + git clone --depth 50 --no-checkout https://github.com/artem-ogre/CDT.git /tmp/CDT-upstream + COMMITS=$(git -C /tmp/CDT-upstream log \ + --pretty=format:"- [%h](https://github.com/artem-ogre/CDT/commit/%H) %s" \ + "${LAST}..${LATEST}" 2>/dev/null || echo "- (see compare link below)") + [ -z "$COMMITS" ] && COMMITS="- (see compare link below)" + + # Update .last-sync-commit on main so we don't re-open the issue next run + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + echo "$LATEST" > .last-sync-commit + git add .last-sync-commit + git commit -m "chore: advance upstream sync marker to ${LATEST:0:7}" + git push origin main + + # Ensure the auto-sync label exists + gh label create "auto-sync" \ + --description "Automatically generated upstream sync issue" \ + --color "0075ca" 2>/dev/null || true + + # Create an issue assigned to the Copilot coding agent. + # Assigning to @Copilot triggers the coding agent to pick it up + # and create a PR with the ported C# changes. + ISSUE_BODY=$(cat <<'EOF' + ## Upstream Sync Task + + @Copilot please port the upstream changes listed below to C# and open a pull request. + + New commits have been pushed to [artem-ogre/CDT](https://github.com/artem-ogre/CDT) since the last sync. + + **Previous synced commit:** [`LAST_SHA`](https://github.com/artem-ogre/CDT/commit/LAST_FULL) + **New upstream commit:** [`LATEST_SHA`](https://github.com/artem-ogre/CDT/commit/LATEST_FULL) + **Full diff:** https://github.com/artem-ogre/CDT/compare/LAST_FULL...LATEST_FULL + + ### Upstream commits to port + + COMMIT_LIST + + ### What to port + + Please port all upstream C++ changes to C# and open a pull request. + + Key files to check in the upstream diff: + - `CDT/include/CDT.h` and `CDT/include/CDT.hpp` → port any algorithm changes to `src/CDT.Core/` + - `CDT/include/Predicates.h` → port to `src/CDT.Core/Predicates.cs` + - `CDT/include/KDTree.h` → port to `src/CDT.Core/KdTree.cs` + - Any new types or utilities → port to `src/CDT.Core/Types.cs` or new files as appropriate + + After porting: + 1. Run `dotnet test` and ensure all tests pass + 2. Update test inputs/expected outputs if needed + EOF + ) + # Replace placeholders with actual values + ISSUE_BODY="${ISSUE_BODY//LAST_SHA/${LAST:0:7}}" + ISSUE_BODY="${ISSUE_BODY//LAST_FULL/${LAST}}" + ISSUE_BODY="${ISSUE_BODY//LATEST_SHA/${LATEST:0:7}}" + ISSUE_BODY="${ISSUE_BODY//LATEST_FULL/${LATEST}}" + ISSUE_BODY="${ISSUE_BODY//COMMIT_LIST/$COMMITS}" + + gh issue create \ + --title "Port upstream CDT changes to C# (${LATEST:0:7}) — ${DATE}" \ + --label "auto-sync" \ + --assignee "Copilot" \ + --body "$ISSUE_BODY" diff --git a/.last-sync-commit b/.last-sync-commit new file mode 100644 index 0000000..1f2034d --- /dev/null +++ b/.last-sync-commit @@ -0,0 +1 @@ +7bd85e41a7b2e6e6e3bf82f36bcbc2bcec6441c5 diff --git a/README.md b/README.md index 6e514ee..ebd4264 100644 --- a/README.md +++ b/README.md @@ -1 +1,81 @@ -"# CDT.NET" +# CDT.NET + +[![Build](https://github.com/MichaCo/CDT.NET/actions/workflows/ci-cd.yml/badge.svg?branch=main)](https://github.com/MichaCo/CDT.NET/actions/workflows/ci-cd.yml) +[![NuGet](https://img.shields.io/nuget/v/CDT.NET.svg)](https://www.nuget.org/packages/CDT.NET) + +A C# port of the [artem-ogre/CDT](https://github.com/artem-ogre/CDT) Constrained Delaunay Triangulation library. + +Fast and robust Constrained Delaunay Triangulation for .NET 8+. + +## Features + +- Constrained Delaunay Triangulation (CDT) +- Support for constrained edges and holes +- Multiple vertex insertion strategies (auto-optimized or as-provided) +- KD-tree spatial indexing for fast nearest-neighbor lookups +- Robust geometric predicates +- Multi-target: .NET 8 and .NET 10 + +## Installation + +``` +dotnet add package CDT.NET +``` + +## Usage + +```csharp +using CDT; + +// Create a triangulation +var cdt = new Triangulation(); + +// Insert vertices +var vertices = new List> +{ + new(0, 0), + new(1, 0), + new(1, 1), + new(0, 1), +}; +cdt.InsertVertices(vertices); + +// Optionally insert constrained edges +var edges = new List +{ + new(0, 1), + new(1, 2), + new(2, 3), + new(3, 0), +}; +cdt.InsertEdges(edges); + +// Erase triangles outside the domain +cdt.EraseOuterTriangles(); + +// Access the result +IReadOnlyList triangles = cdt.Triangles; +IReadOnlyList> points = cdt.Vertices; +``` + +## Building + +```bash +dotnet build +``` + +## Testing + +```bash +dotnet test +``` + +## Benchmarking + +```bash +dotnet run -c Release --project benchmark/CDT.Benchmarks +``` + +## License + +[MIT License](LICENSE) diff --git a/src/CDT.Core/CDT.Core.csproj b/src/CDT.Core/CDT.Core.csproj index ec4f631..c217f01 100644 --- a/src/CDT.Core/CDT.Core.csproj +++ b/src/CDT.Core/CDT.Core.csproj @@ -1,7 +1,7 @@ - net10.0 + net8.0;net10.0 enable enable true @@ -12,4 +12,28 @@ CDT + + + CDT.NET + 1.0.0 + Michael Conrad + Copyright © 2026 Michael Conrad + A fast and robust C# port of the artem-ogre/CDT Constrained Delaunay Triangulation library. Supports constrained edges, holes, and modern .NET 8+ optimizations. + CDT;constrained-delaunay;triangulation;geometry;mesh + https://github.com/MichaCo/CDT.NET + https://github.com/MichaCo/CDT.NET + git + MIT + README.md + true + true + true + snupkg + portable + + + + + + From 8c8b0846eea9d09905b5c552f9c9b35c47e8b225 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 09:51:44 +0000 Subject: [PATCH 3/9] fix: change license from MIT to MPL-2.0 in csproj and README Co-authored-by: MichaCo <5837539+MichaCo@users.noreply.github.com> --- README.md | 2 +- src/CDT.Core/CDT.Core.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ebd4264..680c247 100644 --- a/README.md +++ b/README.md @@ -78,4 +78,4 @@ dotnet run -c Release --project benchmark/CDT.Benchmarks ## License -[MIT License](LICENSE) +[Mozilla Public License Version 2.0](LICENSE) diff --git a/src/CDT.Core/CDT.Core.csproj b/src/CDT.Core/CDT.Core.csproj index c217f01..efa7112 100644 --- a/src/CDT.Core/CDT.Core.csproj +++ b/src/CDT.Core/CDT.Core.csproj @@ -23,7 +23,7 @@ https://github.com/MichaCo/CDT.NET https://github.com/MichaCo/CDT.NET git - MIT + MPL-2.0 README.md true true From da1e0d46d79d0592d43a8189b2611fbc766544d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 10:03:08 +0000 Subject: [PATCH 4/9] docs: improve README with sophisticated examples, credits, and noteworthy info from upstream Co-authored-by: MichaCo <5837539+MichaCo@users.noreply.github.com> --- README.md | 182 ++++++++++++++++++++--- test/CDT.Tests/ReadmeExamplesTests.cs | 202 ++++++++++++++++++++++++++ 2 files changed, 361 insertions(+), 23 deletions(-) create mode 100644 test/CDT.Tests/ReadmeExamplesTests.cs diff --git a/README.md b/README.md index 680c247..988a1c9 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,31 @@ A C# port of the [artem-ogre/CDT](https://github.com/artem-ogre/CDT) Constrained Delaunay Triangulation library. -Fast and robust Constrained Delaunay Triangulation for .NET 8+. +> **Credits:** This library is a C# port of the excellent [CDT C++ library](https://github.com/artem-ogre/CDT) by Artem Amirkhanov and contributors, licensed under MPL 2.0. +> For full algorithm documentation, research references, and in-depth API documentation please refer to the [original C++ repository](https://github.com/artem-ogre/CDT) and its [online documentation](https://artem-ogre.github.io/CDT/). + +## What is CDT? + +CDT is a library for generating **Constrained** and **Conforming** Delaunay Triangulations. It produces triangulations from a set of points and optional boundary/constraint edges. Unlike a plain Delaunay triangulation, CDT guarantees that the constraint edges you specify will appear in the final mesh. ## Features -- Constrained Delaunay Triangulation (CDT) -- Support for constrained edges and holes -- Multiple vertex insertion strategies (auto-optimized or as-provided) -- KD-tree spatial indexing for fast nearest-neighbor lookups -- Robust geometric predicates -- Multi-target: .NET 8 and .NET 10 +- **Constrained Delaunay Triangulation** — force specific edges into the triangulation +- **Conforming Delaunay Triangulation** — split edges and add Steiner points until constraint edges are present in the triangulation +- **Convex-hull** triangulation — triangulate all points without any constraints +- **Automatic hole detection** — use `EraseOuterTrianglesAndHoles` to remove outer regions and holes based on even–odd winding depth +- **Robust geometric predicates** — numerically stable orientation and in-circle tests +- **KD-tree spatial indexing** — fast nearest-neighbor lookup during vertex insertion +- **Duplicate handling** — utilities to remove duplicate vertices and remap edges before triangulation +- **Intersecting constraint edges** — optionally resolve by splitting edges at the intersection point +- Multi-target: **.NET 8** and **.NET 10** + +**Pre-conditions** (same as the C++ original): +- No duplicate vertices (use `CdtUtils.RemoveDuplicatesAndRemapEdges` to clean input) +- No two constraint edges may intersect (or pass `IntersectingConstraintEdges.TryResolve`) + +**Post-conditions:** +- All triangles have **counter-clockwise (CCW) winding** in a coordinate system where X points right and Y points up. ## Installation @@ -24,38 +39,154 @@ dotnet add package CDT.NET ## Usage +### Delaunay triangulation (convex hull, no constraints) + +Insert vertices and call `EraseSuperTriangle` to get the convex-hull triangulation. + ```csharp using CDT; -// Create a triangulation +var vertices = new List> +{ + new(0, 0), new(4, 0), new(4, 4), new(0, 4), new(2, 2), +}; + var cdt = new Triangulation(); +cdt.InsertVertices(vertices); +cdt.EraseSuperTriangle(); // produces convex hull + +IReadOnlyList triangles = cdt.Triangles; +IReadOnlyList> points = cdt.Vertices; +HashSet allEdges = CdtUtils.ExtractEdgesFromTriangles(triangles); +``` + +### Constrained Delaunay triangulation (bounded domain) + +Insert boundary edges, then call `EraseOuterTriangles` to keep only the region inside the boundary. + +```csharp +using CDT; -// Insert vertices var vertices = new List> { - new(0, 0), - new(1, 0), - new(1, 1), - new(0, 1), + new(0, 0), new(4, 0), new(4, 4), new(0, 4), }; +var edges = new List +{ + new(0, 1), new(1, 2), new(2, 3), new(3, 0), // square boundary +}; + +var cdt = new Triangulation(); cdt.InsertVertices(vertices); +cdt.InsertEdges(edges); +cdt.EraseOuterTriangles(); // removes everything outside the boundary + +IReadOnlyList triangles = cdt.Triangles; +IReadOnlySet fixedEdges = cdt.FixedEdges; // the constraint edges +``` + +### Auto-detect boundaries and holes + +Use `EraseOuterTrianglesAndHoles` to automatically remove the outer region **and** fill holes. The algorithm uses an even–odd depth rule: depth 0 = outside, depth 1 = inside, depth 2 = hole, etc. -// Optionally insert constrained edges +```csharp +using CDT; + +// Outer square (vertices 0-3) + inner square hole (vertices 4-7) +var vertices = new List> +{ + new(0, 0), new(6, 0), new(6, 6), new(0, 6), // outer square + new(2, 2), new(4, 2), new(4, 4), new(2, 4), // inner hole +}; var edges = new List { - new(0, 1), - new(1, 2), - new(2, 3), - new(3, 0), + new(0, 1), new(1, 2), new(2, 3), new(3, 0), // outer boundary (CCW) + new(4, 7), new(7, 6), new(6, 5), new(5, 4), // inner hole (CW — opposite winding) }; + +var cdt = new Triangulation(); +cdt.InsertVertices(vertices); cdt.InsertEdges(edges); +cdt.EraseOuterTrianglesAndHoles(); -// Erase triangles outside the domain +IReadOnlyList triangles = cdt.Triangles; +``` + +### Conforming Delaunay triangulation + +Use `ConformToEdges` instead of `InsertEdges`. The algorithm may split constraint edges and insert Steiner points (midpoints) until the constraint is represented in the triangulation. + +```csharp +using CDT; + +var vertices = new List> +{ + new(0, 0), new(4, 0), new(4, 4), new(0, 4), +}; +var edges = new List +{ + new(0, 1), new(1, 2), new(2, 3), new(3, 0), +}; + +var cdt = new Triangulation(); +cdt.InsertVertices(vertices); +cdt.ConformToEdges(edges); // may add Steiner points cdt.EraseOuterTriangles(); +``` -// Access the result -IReadOnlyList triangles = cdt.Triangles; -IReadOnlyList> points = cdt.Vertices; +### Removing duplicate vertices and remapping edges + +Input data often contains duplicate vertices (e.g., from shared polygon boundaries). Use `CdtUtils.RemoveDuplicatesAndRemapEdges` to clean up before triangulation. + +```csharp +using CDT; + +var vertices = new List> +{ + new(0, 0), new(4, 0), new(4, 4), new(0, 4), + new(0, 0), // duplicate of vertex 0 +}; +var edges = new List +{ + new(0, 4), // will be remapped since vertex 4 is a duplicate of vertex 0 + new(1, 2), +}; + +CdtUtils.RemoveDuplicatesAndRemapEdges(vertices, edges); +// vertices now has 4 entries; degenerate self-edges like (0,0) can be dropped + +var cdt = new Triangulation(); +cdt.InsertVertices(vertices); +cdt.InsertEdges(edges.Where(e => e.V1 != e.V2).ToList()); +cdt.EraseSuperTriangle(); +``` + +### Resolving intersecting constraint edges + +By default, intersecting constraint edges throw an exception. Pass `IntersectingConstraintEdges.TryResolve` to split them at their intersection point instead. + +```csharp +using CDT; + +// Two diagonals of a unit square that cross each other +var vertices = new List> +{ + new(0, 0), new(1, 0), new(1, 1), new(0, 1), +}; +var edges = new List +{ + new(0, 2), // diagonal ↗ + new(1, 3), // diagonal ↖ — intersects (0,2) +}; + +var cdt = new Triangulation( + VertexInsertionOrder.Auto, + IntersectingConstraintEdges.TryResolve, + minDistToConstraintEdge: 0.0); + +cdt.InsertVertices(vertices); +cdt.InsertEdges(edges); // intersection is resolved by inserting a new vertex +cdt.EraseSuperTriangle(); ``` ## Building @@ -67,7 +198,7 @@ dotnet build ## Testing ```bash -dotnet test +dotnet run --project test/CDT.Tests ``` ## Benchmarking @@ -79,3 +210,8 @@ dotnet run -c Release --project benchmark/CDT.Benchmarks ## License [Mozilla Public License Version 2.0](LICENSE) + +This software is based in part on [CDT — C++ library for constrained Delaunay triangulation](https://github.com/artem-ogre/CDT): +Copyright © 2019 Leica Geosystems Technology AB +Copyright © The CDT Contributors +Licensed under the MPL-2.0 license. diff --git a/test/CDT.Tests/ReadmeExamplesTests.cs b/test/CDT.Tests/ReadmeExamplesTests.cs new file mode 100644 index 0000000..1c30c6a --- /dev/null +++ b/test/CDT.Tests/ReadmeExamplesTests.cs @@ -0,0 +1,202 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/. + +namespace CDT.Tests; + +/// Validates that the README code examples compile and produce the expected results. +public sealed class ReadmeExamplesTests +{ + // ------------------------------------------------------------------------- + // Example 1 – Delaunay triangulation without constraints (convex hull) + // ------------------------------------------------------------------------- + + [Fact] + public void Example_DelaunayConvexHull() + { + var vertices = new List> + { + new(0, 0), new(4, 0), new(4, 4), new(0, 4), new(2, 2), // inner point + }; + + var cdt = new Triangulation(); + cdt.InsertVertices(vertices); + cdt.EraseSuperTriangle(); // produces convex hull + + Assert.True(TopologyVerifier.VerifyTopology(cdt)); + Assert.Equal(5, cdt.Vertices.Count); + Assert.True(cdt.Triangles.Count > 0); + Assert.Empty(cdt.FixedEdges); + } + + // ------------------------------------------------------------------------- + // Example 2 – Constrained Delaunay triangulation (bounded domain) + // ------------------------------------------------------------------------- + + [Fact] + public void Example_ConstrainedDelaunay_BoundedDomain() + { + var vertices = new List> + { + new(0, 0), new(4, 0), new(4, 4), new(0, 4), + }; + var edges = new List + { + new(0, 1), new(1, 2), new(2, 3), new(3, 0), // square boundary + }; + + var cdt = new Triangulation(); + cdt.InsertVertices(vertices); + cdt.InsertEdges(edges); + 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.FixedEdges.Count); + } + + // ------------------------------------------------------------------------- + // Example 3 – Auto-detect outer triangles and holes + // ------------------------------------------------------------------------- + + [Fact] + public void Example_AutoDetectBoundariesAndHoles() + { + // Outer square (vertices 0-3) + inner square hole (vertices 4-7) + var vertices = new List> + { + new(0, 0), new(6, 0), new(6, 6), new(0, 6), // outer square + new(2, 2), new(4, 2), new(4, 4), new(2, 4), // inner hole + }; + var edges = new List + { + // outer boundary (CCW) + new(0, 1), new(1, 2), new(2, 3), new(3, 0), + // inner hole boundary (CW — opposite winding marks it as a hole) + new(4, 7), new(7, 6), new(6, 5), new(5, 4), + }; + + var cdt = new Triangulation(); + cdt.InsertVertices(vertices); + cdt.InsertEdges(edges); + 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); + } + + // ------------------------------------------------------------------------- + // Example 4 – Conforming Delaunay triangulation + // ------------------------------------------------------------------------- + + [Fact] + public void Example_ConformingDelaunay() + { + var vertices = new List> + { + new(0, 0), new(4, 0), new(4, 4), new(0, 4), + }; + var edges = new List + { + new(0, 1), new(1, 2), new(2, 3), new(3, 0), + }; + + var cdt = new Triangulation(); + cdt.InsertVertices(vertices); + cdt.ConformToEdges(edges); // may split edges and add new points + cdt.EraseOuterTriangles(); + + Assert.True(TopologyVerifier.VerifyTopology(cdt)); + Assert.True(cdt.Triangles.Count > 0); + // ConformToEdges may have added midpoints, so vertex count >= 4 + Assert.True(cdt.Vertices.Count >= 4); + } + + // ------------------------------------------------------------------------- + // Example 5 – Removing duplicates and remapping edges + // ------------------------------------------------------------------------- + + [Fact] + public void Example_RemoveDuplicatesAndRemapEdges() + { + var vertices = new List> + { + new(0, 0), new(4, 0), new(4, 4), new(0, 4), + new(0, 0), // duplicate of vertex 0 + }; + var edges = new List + { + new(0, 4), // references duplicate; will be remapped to (0, 0) + new(1, 2), + }; + + CdtUtils.RemoveDuplicatesAndRemapEdges(vertices, edges); + + // Duplicate removed → 4 unique vertices + Assert.Equal(4, vertices.Count); + // Edge (0,4) remapped: both map to index 0 → self-edge (0,0) + Assert.Equal(new Edge(0, 0), edges[0]); + // Edge (1,2) unchanged + 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.EraseSuperTriangle(); + + Assert.True(TopologyVerifier.VerifyTopology(cdt)); + } + + // ------------------------------------------------------------------------- + // Example 6 – Extract all edges from a triangulation + // ------------------------------------------------------------------------- + + [Fact] + public void Example_ExtractEdgesFromTriangles() + { + var cdt = new Triangulation(); + cdt.InsertVertices([new(0, 0), new(2, 0), new(1, 2)]); + cdt.EraseSuperTriangle(); + + // Extract all unique edges from every triangle + HashSet allEdges = CdtUtils.ExtractEdgesFromTriangles(cdt.Triangles); + + // A single triangle has exactly 3 edges + Assert.Equal(3, allEdges.Count); + } + + // ------------------------------------------------------------------------- + // Example 7 – Resolve intersecting constraint edges + // ------------------------------------------------------------------------- + + [Fact] + public void Example_ResolveIntersectingConstraints() + { + // Two edges that cross each other: (0,2) and (1,3) on a unit square + var vertices = new List> + { + new(0, 0), new(1, 0), new(1, 1), new(0, 1), + }; + var edges = new List + { + new(0, 2), // diagonal + new(1, 3), // other diagonal — intersects (0,2) + }; + + // TryResolve splits intersecting edges by inserting the intersection point + var cdt = new Triangulation( + VertexInsertionOrder.Auto, + IntersectingConstraintEdges.TryResolve, + 0.0); + + cdt.InsertVertices(vertices); + cdt.InsertEdges(edges); + cdt.EraseSuperTriangle(); + + Assert.True(TopologyVerifier.VerifyTopology(cdt)); + // An extra vertex is added at the intersection + Assert.True(cdt.Vertices.Count > 4); + } +} From 463036801d5f77d04f89ba520df675417dfef18a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 10:22:46 +0000 Subject: [PATCH 5/9] ci: switch pipeline to windows-latest to support Windows-only VIZ project Co-authored-by: MichaCo <5837539+MichaCo@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 6fab4b1..22c8d48 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -11,7 +11,7 @@ on: jobs: build-and-test: name: Build and Test - runs-on: ubuntu-latest + runs-on: windows-latest permissions: contents: read @@ -45,12 +45,13 @@ jobs: reporttypes: MarkdownSummaryGithub - name: Write coverage to job summary - run: cat coveragereport/SummaryGithub.md >> $GITHUB_STEP_SUMMARY + run: Get-Content coveragereport/SummaryGithub.md >> $env:GITHUB_STEP_SUMMARY + shell: pwsh publish: name: Publish to NuGet needs: build-and-test - runs-on: ubuntu-latest + runs-on: windows-latest if: startsWith(github.ref, 'refs/tags/v') permissions: contents: read From bca4a8410f537628ae5cbe804fa5ab435ce389c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 10:25:07 +0000 Subject: [PATCH 6/9] ci: add matrix build for both windows-latest and ubuntu-latest Co-authored-by: MichaCo <5837539+MichaCo@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 40 ++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 22c8d48..3e07aad 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -10,8 +10,12 @@ on: jobs: build-and-test: - name: Build and Test - runs-on: windows-latest + name: Build and Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest] permissions: contents: read @@ -28,15 +32,40 @@ jobs: 8.0.x 10.0.x + # Windows: restore full solution (includes Windows-only CDT.Viz) - name: Restore dependencies + if: runner.os == 'Windows' run: dotnet restore + # Linux: restore only cross-platform projects (CDT.Viz targets Windows only) + - name: Restore dependencies + if: runner.os == 'Linux' + run: | + dotnet restore src/CDT.Core/CDT.Core.csproj + dotnet restore test/CDT.Tests/CDT.Tests.csproj + + # Windows: build full solution - name: Build + if: runner.os == 'Windows' run: dotnet build --no-restore -c Release + # Linux: build only cross-platform projects + - name: Build + if: runner.os == 'Linux' + run: | + dotnet build --no-restore -c Release src/CDT.Core/CDT.Core.csproj + dotnet build --no-restore -c Release test/CDT.Tests/CDT.Tests.csproj + + # Windows: test full solution - name: Test + if: runner.os == 'Windows' run: dotnet test --no-build -c Release --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage + # Linux: test only CDT.Tests (CDT.Viz has no tests; benchmark is not a test project) + - name: Test + if: runner.os == 'Linux' + run: dotnet test --no-build -c Release --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage test/CDT.Tests/CDT.Tests.csproj + - name: Generate coverage report uses: danielpalme/ReportGenerator-GitHub-Action@5 with: @@ -44,10 +73,15 @@ jobs: targetdir: coveragereport reporttypes: MarkdownSummaryGithub - - name: Write coverage to job summary + - name: Write coverage to job summary (Windows) + if: runner.os == 'Windows' run: Get-Content coveragereport/SummaryGithub.md >> $env:GITHUB_STEP_SUMMARY shell: pwsh + - name: Write coverage to job summary (Linux) + if: runner.os == 'Linux' + run: cat coveragereport/SummaryGithub.md >> $GITHUB_STEP_SUMMARY + publish: name: Publish to NuGet needs: build-and-test From 05502c9ed6d0ed420e2899c593e22dad6386c4a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 10:39:52 +0000 Subject: [PATCH 7/9] ci: add coverage PR comment from Windows build using sticky-pull-request-comment Co-authored-by: MichaCo <5837539+MichaCo@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 3e07aad..015a962 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -18,6 +18,7 @@ jobs: os: [windows-latest, ubuntu-latest] permissions: contents: read + pull-requests: write # needed for sticky-pull-request-comment steps: - name: Checkout @@ -78,6 +79,13 @@ jobs: run: Get-Content coveragereport/SummaryGithub.md >> $env:GITHUB_STEP_SUMMARY shell: pwsh + - name: Add coverage PR comment + uses: marocchino/sticky-pull-request-comment@v2 + if: runner.os == 'Windows' && github.event_name == 'pull_request' + with: + recreate: true + path: coveragereport/SummaryGithub.md + - name: Write coverage to job summary (Linux) if: runner.os == 'Linux' run: cat coveragereport/SummaryGithub.md >> $GITHUB_STEP_SUMMARY From 6fc732944472813d0a5cb7f8b561726236fc5ab6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 10:45:21 +0000 Subject: [PATCH 8/9] fix: enable MTP runner for dotnet test to fix xunit.v3 test discovery in CI Co-authored-by: MichaCo <5837539+MichaCo@users.noreply.github.com> --- test/CDT.Tests/CDT.Tests.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/CDT.Tests/CDT.Tests.csproj b/test/CDT.Tests/CDT.Tests.csproj index 8fc5c13..fe1bb49 100644 --- a/test/CDT.Tests/CDT.Tests.csproj +++ b/test/CDT.Tests/CDT.Tests.csproj @@ -5,6 +5,9 @@ enable enable false + + true + true From d5b7afeacc9c12d36a6ade5692e9cca93c91e109 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 10:52:26 +0000 Subject: [PATCH 9/9] fix: switch to coverlet.msbuild for coverage collection with MTP/xunit.v3 runner Co-authored-by: MichaCo <5837539+MichaCo@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 6 +++--- test/CDT.Tests/CDT.Tests.csproj | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 015a962..2158b31 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -60,17 +60,17 @@ jobs: # Windows: test full solution - name: Test if: runner.os == 'Windows' - run: dotnet test --no-build -c Release --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage + run: dotnet test --no-build -c Release --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=${{ github.workspace }}/coverage/coverage.cobertura.xml # Linux: test only CDT.Tests (CDT.Viz has no tests; benchmark is not a test project) - name: Test if: runner.os == 'Linux' - run: dotnet test --no-build -c Release --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage test/CDT.Tests/CDT.Tests.csproj + run: dotnet test --no-build -c Release --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=${{ github.workspace }}/coverage/coverage.cobertura.xml test/CDT.Tests/CDT.Tests.csproj - name: Generate coverage report uses: danielpalme/ReportGenerator-GitHub-Action@5 with: - reports: ./coverage/**/coverage.cobertura.xml + reports: ${{ github.workspace }}/coverage/coverage.cobertura.xml targetdir: coveragereport reporttypes: MarkdownSummaryGithub diff --git a/test/CDT.Tests/CDT.Tests.csproj b/test/CDT.Tests/CDT.Tests.csproj index fe1bb49..193f180 100644 --- a/test/CDT.Tests/CDT.Tests.csproj +++ b/test/CDT.Tests/CDT.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive