Expose the uv/t coordinates of an edge–triangle intersection - #245
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the geometry intersection utilities by adding an edge–triangle intersection API that returns the intersection location (triangle barycentric coordinates and edge parameter), while keeping the existing boolean predicate behavior via a wrapper. It also adds a CollisionMesh::face_normals() helper to compute per-face unit normals in parallel, plus targeted unit tests for the new intersection API.
Changes:
- Add
edge_triangle_intersection()returning(u, v, t)and refactoris_edge_intersecting_triangle()into a wrapper. - Add
CollisionMesh::face_normals()(TBB-parallel) to compute per-face unit normals for provided vertex positions. - Add a new Catch2 test file and wire it into the geometry test CMake target.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/src/tests/geometry/test_intersection.cpp | Adds unit tests validating edge_triangle_intersection() outputs on a hit and a clear miss case. |
| tests/src/tests/geometry/CMakeLists.txt | Registers the new geometry intersection test source. |
| src/ipc/geometry/intersection.hpp | Declares the new intersection API and documents its parameters. |
| src/ipc/geometry/intersection.cpp | Implements edge_triangle_intersection(), routes the old predicate through it, and extends the rational path to output coordinates. |
| src/ipc/collision_mesh.hpp | Declares CollisionMesh::face_normals() public helper. |
| src/ipc/collision_mesh.cpp | Implements face_normals() using tbb::parallel_for and triangle_normal(). |
Comments suppressed due to low confidence (1)
src/ipc/geometry/intersection.cpp:146
edge_triangle_intersection()can returnfalsefrom the plane-side gate without writingu/v/t, leaving out-params indeterminate. Initializing them at the top avoids accidental use of uninitialized outputs (and makes behavior consistent across early-exit paths).
{
// Robust plane-side gate (same as is_edge_intersecting_triangle): both edge
// endpoints strictly on one side of the triangle's plane ⇒ no crossing.
igl::predicates::exactinit();
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #245 +/- ##
=======================================
Coverage 96.57% 96.58%
=======================================
Files 163 163
Lines 16656 16673 +17
Branches 922 922
=======================================
+ Hits 16086 16104 +18
+ Misses 570 569 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Fix the Doxygen comment: (a, b) -> (u, v), and state that boundary hits count as intersections (the comparisons are inclusive). - Write the out-params only once an intersection is confirmed, in both the rational and floating-point paths, so a false return never leaves them partially populated. They are seeded to NaN on entry, so every return path is deterministic. - The degenerate rational case (d.sign() == 0) still conservatively returns true, but now documents that the coordinates stay NaN because they are not uniquely defined there. - Fix typo: "completly" -> "completely". - Extend the tests: hit-point round-trip through both parameterizations, a plane-crossing miss outside the triangle, NaN checks on misses, and agreement with is_edge_intersecting_triangle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Bind edge_triangle_intersection(), returning a (intersects, u, v, t) tuple since Python has no out-params. Documents the NaN contract. - Bind CollisionMesh.face_normals(), returning an (#F × 3) array rather than a list of vectors, to match the other per-element accessors. The C++ side only asserts the 3D requirement, which is a no-op in release builds, so the binding raises ValueError on a 2D mesh instead of invoking UB. - Add tests for both, mirroring the C++ tests. Also add a C++ test for CollisionMesh::face_normals (rest positions, deformed positions, and flipped winding), which had no coverage — this is what the codecov patch check was failing on. Drive-by: test_faces_to_edges used the nose-style `yield` form, which modern pytest rejects at collection, taking the whole test_collision_mesh.py file down with it. Converted to @pytest.mark.parametrize plus a pytest.raises case so the file collects. CI does not run pytest, which is why this went unnoticed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit converted test_faces_to_edges from the nose-style `yield` form to @pytest.mark.parametrize. That was wrong: python.yml runs the suite with nose2, and pytest is not in python/tests/requirements.txt, so `import pytest` would have failed and taken every test in the file with it. nose2 supports the yield form natively, so there was nothing broken to fix. Restores the original test_faces_to_edges and rewrites the new test_face_normals_2d_raises to use try/except, matching the surrounding style, so the file has no pytest dependency. Verified with `nose2 -v -s python/tests`, as CI does: 13 tests, all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Description
Adds a variant of the edge–triangle intersection test that reports where the
intersection happens, not just whether it happens.
is_edge_intersecting_triangle()already computes the barycentric coordinates(u, v)on the triangle and the parametertalong the edge internally, thenthrows them away. Downstream code that wants the intersection point had to
recompute them (and, in the rational build, redo the exact arithmetic).
New API:
is_edge_intersecting_triangle()is now a thin wrapper over it, so both sharethe same robust
orient3dplane-side gate and the same solve — no behavioralchange to the existing predicate. The rational path
(
IPC_TOOLKIT_WITH_RATIONAL_INTERSECTION) writes the coordinates out as well.Also adds
CollisionMesh::face_normals(), a TBB-parallel helper returning theper-face unit normals for a given set of vertex positions — the companion piece
needed to orient/interpret an intersection once you have its location.
Type of change
How Has This Been Tested?
New
tests/src/tests/geometry/test_intersection.cpp:t == 0.5and that(u, v)is a valid barycentric coordinate.Also confirmed the refactor of
is_edge_intersecting_triangle()isbehavior-preserving by running the existing suite:
Test Configuration:
Checklist
Notes for review
edge_triangle_intersection()is boundreturning an
(intersects, u, v, t)tuple (Python has no out-params), andCollisionMesh.face_normals()returns an(#F × 3)array rather than a listof vectors, to match the other per-element accessors. Since the C++ side only
asserts the 3D requirement — a no-op in release builds — the binding raisesValueErroron a 2D mesh instead of invoking UB. Both have tests mirroringthe C++ ones, verified with
nose2 -v -s python/testsas CI runs them.CollisionMesh::face_normals(rest positions, deformedpositions, flipped winding). This was the codecov patch failure — the
function had no coverage at all.
documented: the outputs are seeded to NaN and written only on a confirmed
intersection, in both the rational and floating-point paths. The float
path had the same bug (it wrote
u/v/tbefore its range check), so thetwo build configurations now agree.
-DIPC_TOOLKIT_WITH_RATIONAL_INTERSECTION=ONbuild aswell as the default; the rational path had never been compiled before this.