Skip to content

Add Triangulate_Planar_Contour_Connectivity for bridging offset planar contours#9

Open
Copilot wants to merge 7 commits intomasterfrom
copilot/determine-connectivity-contours
Open

Add Triangulate_Planar_Contour_Connectivity for bridging offset planar contours#9
Copilot wants to merge 7 commits intomasterfrom
copilot/determine-connectivity-contours

Conversation

Copy link

Copilot AI commented Jan 31, 2026

Implements triangulation of the gap between two contour_collections on parallel, offset planes, producing an fv_surface_mesh that bridges them.

Changes

  • New header: src/YgorMathContourConnectivity.h — function declaration with documentation
  • New implementation: src/YgorMathContourConnectivity.cc — greedy nearest-neighbor triangulation algorithm with edge-intersection validation
  • Unit tests: tests2/YgorMath/contour_connectivity.cc — 5 test cases covering basic shapes, multiple contours, holes, oblique planes, empty vertex collections, and edge cases

Algorithm

  1. Compute average points using contour_collection::Average_Point() API
  2. Project all 3D vertices onto a 2D plane perpendicular to the separation direction
  3. For each contour edge, find nearest vertices on the opposite plane
  4. Create triangles connecting edges to counterpart vertices
  5. Validate triangles: reject degenerate triangles (zero 3D area) and triangles whose edges intersect existing face edges in 2D projection
  6. Deduplicate triangles before adding to mesh

Usage

#include <YgorMathContourConnectivity.h>

contour_collection<double> top_cc, bottom_cc;
// ... populate contours on z=1 and z=0 planes ...

auto mesh = Triangulate_Planar_Contour_Connectivity(top_cc, bottom_cc);
// mesh.faces contains triangles bridging the gap
// mesh.vertices contains all vertices from both collections

Notes

  • Handles multiple contours per collection (for example, disjoint polygons)
  • Treats all input contours uniformly; callers should not rely on orientation to denote holes
  • Non-watertight output mesh representing surface patches
  • Template instantiated for float and double
  • Uses O(1) map lookups for vertex-to-2D-position mapping during edge intersection checks

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits January 31, 2026 07:46
…g gap between planar contours

Co-authored-by: hdclark <934858+hdclark@users.noreply.github.com>
…safety check

Co-authored-by: hdclark <934858+hdclark@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement function to determine connectivity between contour collections Add Triangulate_Planar_Contour_Connectivity for bridging offset planar contours Jan 31, 2026
Copilot AI requested a review from hdclark January 31, 2026 07:51
@hdclark hdclark marked this pull request as ready for review January 31, 2026 07:54
Copilot AI review requested due to automatic review settings January 31, 2026 07:54
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a new triangulation utility to build an fv_surface_mesh that bridges two planar contour_collections on parallel, offset planes, plus unit tests and small test runner ignore updates.

Changes:

  • Added Triangulate_Planar_Contour_Connectivity template declaration in YgorMathContourConnectivity.h, documenting the new API and its expected behavior.
  • Implemented a projected-plane, greedy nearest-neighbor triangulation in YgorMathContourConnectivity.cc, including explicit float/double instantiations.
  • Added doctest-based unit tests in tests2/YgorMath/contour_connectivity.cc to validate basic shapes, multiple contours (including a hole pattern), oblique planes, float instantiation, and mesh integrity, and updated .gitignore to ignore local test runners.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 6 comments.

File Description
src/YgorMathContourConnectivity.h Declares the Triangulate_Planar_Contour_Connectivity API and documents inputs, outputs, and exceptions (note: documentation for contour orientation handling and distance_eps semantics should be aligned with the actual implementation).
src/YgorMathContourConnectivity.cc Implements the triangulation algorithm: computes an approximate separation direction, projects vertices to 2D, connects top/bottom contour edges via nearest neighbors, filters degenerate triangles, and populates an fv_surface_mesh (contains some dead code and duplicated Average_Point logic that can be simplified).
tests2/YgorMath/contour_connectivity.cc Adds tests covering simple and multiple contours, a hole configuration, oblique planes, float support, and mesh index/area validity (additional coverage is suggested for the "no vertices in collections" invalid-argument branch).
.gitignore Ignores run_tests and run_connectivity_tests binaries/scripts to keep the repo clean from local test artifacts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +234 to +252
// Helper to check if triangle is valid (proper orientation and no edge crossings)
// Use 3D area calculation since the triangles span between the two planes
auto is_valid_triangle = [&](size_t v0, size_t v1, size_t v2) -> bool {
const auto &p0_3d = projected_verts[v0].pos_3d;
const auto &p1_3d = projected_verts[v1].pos_3d;
const auto &p2_3d = projected_verts[v2].pos_3d;

// Calculate 3D triangle area using cross product
const vec3<T> edge1 = p1_3d - p0_3d;
const vec3<T> edge2 = p2_3d - p0_3d;
const vec3<T> cross = edge1.Cross(edge2);
const T area = cross.length() / static_cast<T>(2);

if (area < distance_eps * distance_eps) {
return false;
}

return true;
};
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on is_valid_triangle says it checks "proper orientation and no edge crossings", but the implementation only computes a 3D area and rejects nearly-degenerate triangles. To avoid misleading readers, either update the comment/name to reflect that it is purely an area-based degeneracy check, or extend the implementation to actually enforce orientation and perform edge-intersection checks against existing faces.

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extend the implementation to actually enforce orientation and perform edge-intersection checks against existing faces.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extended is_valid_triangle to:

  1. Check for degenerate triangles (non-zero 3D area)
  2. Perform edge-intersection checks against all existing face edges in 2D projection
  3. Use O(1) map lookups for vertex_index to pos_2d mapping

The 2D segment intersection test uses cross-product orientation to detect edge crossings. Commit f143399.

Comment on lines +83 to +109
SUBCASE("empty top collection throws"){
contour_collection<double> top_cc;

contour_collection<double> bottom_cc;
bottom_cc.contours.emplace_back();
bottom_cc.contours.back().closed = true;
bottom_cc.contours.back().points.emplace_back(vec3<double>(0.0, 0.0, 0.0));

REQUIRE_THROWS_AS(
Triangulate_Planar_Contour_Connectivity(top_cc, bottom_cc),
std::invalid_argument
);
}

SUBCASE("empty bottom collection throws"){
contour_collection<double> top_cc;
top_cc.contours.emplace_back();
top_cc.contours.back().closed = true;
top_cc.contours.back().points.emplace_back(vec3<double>(0.0, 0.0, 1.0));

contour_collection<double> bottom_cc;

REQUIRE_THROWS_AS(
Triangulate_Planar_Contour_Connectivity(top_cc, bottom_cc),
std::invalid_argument
);
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests cover empty contour collections via top_cc.contours.empty() / bottom_cc.contours.empty(), but they do not exercise the case where the collections are non-empty yet contain no points, which triggers the separate "Contour collections contain no vertices." invalid_argument path in the implementation. To keep the new API well-tested, consider adding a small test that constructs contour_collections with one or more empty contour_of_points entries and verifies that Triangulate_Planar_Contour_Connectivity throws std::invalid_argument in that scenario.

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a small test that constructs contour_collections with one or more empty contour_of_points entries and verifies that Triangulate_Planar_Contour_Connectivity throws std::invalid_argument in that scenario.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added two test cases:

  • "collections with empty contour_of_points entries throw" - top collection has empty contour, bottom has points
  • "multiple contours with some empty entries throw" - top collection has multiple empty contours

Both verify std::invalid_argument is thrown. Commit f143399.

Comment on lines +64 to +80
vec3<T> avg_top_point(static_cast<T>(0), static_cast<T>(0), static_cast<T>(0));
vec3<T> avg_bottom_point(static_cast<T>(0), static_cast<T>(0), static_cast<T>(0));
size_t top_count = 0;
size_t bottom_count = 0;

for (const auto &contour : top_cc.contours) {
for (const auto &pt : contour.points) {
avg_top_point += pt;
++top_count;
}
}
for (const auto &contour : bottom_cc.contours) {
for (const auto &pt : contour.points) {
avg_bottom_point += pt;
++bottom_count;
}
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The computation of avg_top_point and avg_bottom_point manually iterates all contours and points, duplicating the contour_collection<T>::Average_Point() logic that already exists in YgorMath (see src/YgorMath.cc:5095). For consistency with the rest of the codebase and to reduce duplication, consider replacing this block with calls to top_cc.Average_Point() and bottom_cc.Average_Point(), letting that API handle the empty/NaN cases.

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace this block with calls to top_cc.Average_Point() and bottom_cc.Average_Point().

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced with calls to top_cc.Average_Point() and bottom_cc.Average_Point(). The implementation now uses isfinite() check on returned values to detect empty vertex collections. Commit f143399.

hdclark and others added 3 commits February 26, 2026 22:03
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@hdclark
Copy link
Owner

hdclark commented Feb 27, 2026

@copilot apply changes based on the comments in this thread

…e, add empty vertex tests

Co-authored-by: hdclark <934858+hdclark@users.noreply.github.com>
Copy link
Author

Copilot AI commented Feb 27, 2026

@copilot apply changes based on the comments in this thread

All review comments have been addressed in commit f143399:

  • Replaced manual average point computation with Average_Point() API calls
  • Extended is_valid_triangle to perform edge-intersection checks against existing faces
  • Added tests for empty contour_of_points entries

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants