Skip to content

fix(Geometry): tier 2 robustness fixes for degenerate inputs - #1350

Merged
BotellaA merged 55 commits into
fix/geometry-robustness-tier1from
fix/geometry-robustness-tier2
Oct 14, 2026
Merged

BotellaA merged 55 commits into
fix/geometry-robustness-tier1from
fix/geometry-robustness-tier2

Conversation

@BotellaA

Copy link
Copy Markdown
Member

Tier 2 of the geometry robustness review: crashes, NaN, infinite loops and tolerance inconsistencies on degenerate or extreme inputs. It is independent of the tier 1 PR (#1349): both branch from next and edit different functions, so the only expected conflicts are in the test() call lists of shared test files. Each fix is its own commit with its own test. Unless a commit says otherwise, the test fails on next and passes here.

Throughout, the project convention applies: anything below GLOBAL_EPSILON (a length) counts as 0, so every tolerance is now compared against a length rather than an area, volume or unnormalized dot product.

Fixes

Commit Bug on next
Intersection: degenerated segments as points Segments of length 1e-6 or less threw from Segment::direction() in segment_triangle_distance and in segment/plane, sphere, cylinder, ellipse, segment and line intersections
Distance: degenerated edges in triangle_triangle_distance Threw on degenerate triangles; leftover DEBUG output removed
perf: each triangle edge once test_close_triangles tested every edge in both directions (1.85x faster)
Distance: NaN in point_triangle_distance Out-of-bounds read when no edge is selected
Distance: flat triangles in 2D point_triangle_distance Returned 0 for any point on the line of a collinear triangle
Triangle: is_degenerated by minimum height Disagreed with normal(); Tetrahedron::is_degenerated threw bad_optional_access
Polygon: farthest vertex in minimum_height Used an arbitrary vertex, so a T-junction polygon was reported degenerate
NNSearch: empty sets and k=0 UB (front() / [0] on an empty vector); closest_neighbor now returns NO_ID
SquareMatrix: 1x1 determinant and inverse CoordinateSystem1D::coordinates always threw
Rotation: huge angles rotate looped forever for |angle| >= 1e17 or inf; the axis check is now relative
Intersection: line/cylinder perpendicular case Division by zero and duplicated points; wrong correctness bookkeeping
Intersection: correctness in sync with results Segment sphere/cylinder/ellipse correctness lists did not match the filtered results; circle flags were overwritten instead of combined with AND
Intersection: stable line_sphere discriminant Cancellation when the line origin is far from the sphere; tangency tolerance was not in length units
Circle: bounding_box Returned an extent of pi*r instead of 0 for normals opposite to an axis
BoundingBox: scale-independent triangle epsilon_intersects Unnormalized axes made the tolerance eps/|n|; large triangles missed contacts within eps
BoundingBox: 1D ray and line A ray starting inside the box returned false; InfiniteLine1D never intersected
Quality: scale-invariant tetrahedron degeneracy Well-shaped tets with edge 0.01 were reported degenerate; coincident vertices gave NaN
IsPointInside: exact predicates Boundary result depended on polygon orientation; boundary points are now inside (documented)
Distance: point-line and point-segment by projection Heron's formula error reached 4e-6 (> eps); distance and projection now agree
Point: absl::Hash XOR hash gave hash(1,1) == hash(7,7) and hash(1,2) == hash(2,1)
Predicates: static expansion constants Exact predicates were wrong in about 7% of near-degenerate cases whenever OpenGeodeGeometryLibrary::initialize() had not run (e.g. geometry unit tests)
Predicates: -ffp-contract=off Guards the exact arithmetic against FMA contraction (no FMA is emitted on x86 today, so this test passes on both)

Plus a LOCAL benchmark (tests/geometry/benchmark-tier2-robustness.cpp).

Behavior changes to check downstream

  • Triangle / Tetrahedron::is_degenerated are now height-based and stricter on thin elements (Background, Explicit).
  • Tetrahedron quality: small valid tets are no longer reported degenerate, and degenerate tets give 0 instead of NaN (Background, Simplex, SLB, Fracsima, DHI).
  • NNSearch::closest_neighbor returns NO_ID on an empty set (was UB).
  • is_point_inside_polygon: boundary points are inside. The current callers query points known to be off the boundary.
  • rotate accepts any finite non-null axis.
  • std::hash<Point> values change; this only matters if hashes are stored somewhere.

Not done

  • line_ellipse / plane_circle have a discriminant issue similar to the sphere case, but the fix differs (scaled space), so it is left for a follow-up.
  • Triangle::minimum_height() throws out_of_range when all three vertices coincide (found while testing, not fixed here).

Benchmark (ns/call, median of 3, next built with the same flags)

The machine was shared with another build, so expect 5-10% noise. The only real slowdown is is_point_inside_polygon (0.60x), the cost of the exact orient_2d.

Full table
function input next ns branch ns speedup
point_segment_distance(3D) random 25.5 13.8 1.85x
point_segment_projection(3D) random 28.2 13.0 2.17x
point_line_distance(3D) random 40.3 32.8 1.23x
point_segment_distance(2D) random 25.6 13.8 1.86x
point_triangle_distance(2D) random 88.3 42.6 2.07x
point_triangle_distance(2D) flat triangle 69.8 20.9 3.34x
point_triangle_distance(3D) random 102.8 102.2 1.01x
point_triangle_distance(3D) degenerated triangle 124.4 76.1 1.63x
segment_triangle_distance random 236.8 214.8 1.10x
triangle_triangle_distance random 2289.8 1240.8 1.85x
line_sphere_intersection(3D) random 62.4 66.0 0.94x
segment_sphere_intersection(3D) random 101.0 98.1 1.03x
segment_plane_intersection random 108.4 103.4 1.05x
line_cylinder_intersection random 178.9 174.2 1.03x
segment_cylinder_intersection random 215.5 181.3 1.19x
segment_segment_intersection(2D) random 118.1 82.8 1.43x
segment_line_intersection(2D) random 112.1 79.5 1.41x
segment_ellipse_intersection(2D) random 148.5 109.7 1.35x
plane_circle_intersection random 131.4 116.1 1.13x
triangle_circle_intersection random 249.6 245.2 1.02x
Triangle3D::is_degenerated random 53.2 10.6 5.02x
Tetrahedron::is_degenerated random 105.3 14.3 7.37x
Polygon2D::minimum_height random 6 vertices 118.9 116.2 1.02x
is_point_inside_polygon 20-vertex star 98.9 165.6 0.60x
Circle::bounding_box random normals 64.0 64.6 0.99x
rotate random 50.1 42.9 1.17x
std::hash random 5.0 4.7 1.06x
Tetrahedron qualities (4) random 207.4 212.9 0.97x
BoundingBox3D::intersects(Triangle3D) random 102.2 61.1 1.67x
BoundingBox3D::epsilon_intersects(Triangle3D) random 122.8 68.6 1.79x
BoundingBox1D::intersects(Ray1D) random 25.5 15.3 1.67x
NNSearch3D::closest_neighbor 100k points 635.8 539.9 1.18x
NNSearch3D::neighbors(k=5) 100k points 1197.1 1221.1 0.98x
point_side_to_segment(2D) collinear +-1ulp 141.2 153.3 0.92x
point_side_to_triangle coplanar +-1ulp 1200.0 646.8 1.86x

Testing

ctest -L essential: 101/101.

🤖 Generated with Claude Code

BotellaA and others added 23 commits October 13, 2026 21:28
segment_triangle_distance and the segment_{plane,sphere,cylinder,ellipse}
intersections, as well as 2D segment_segment and segment_line
intersections, built an InfiniteLine from the segment, which throws from
Segment::direction() when the segment length is below GLOBAL_EPSILON.
Such segments are now treated as their barycenter: the result is the
point-to-object distance, or a single intersection point when the point
lies on the object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_close_triangles printed leftover DEBUG/SDEBUG output when two
vertices of a triangle were colocated, then still computed the distance
from that zero-length edge (which used to throw from
Segment::direction()). Degenerated edges are now skipped: their
vertices are covered by the other edges of the triangle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tance

test_close_triangles iterated over all ordered vertex pairs, computing
segment_triangle_distance twice per edge. Only unordered pairs are now
tested, halving the work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stance

For degenerated triangles, no_pivot_point_triangle_distance initialized
the selected edge to NO_LID and only updated it on a strictly smaller
distance. With NaN coordinates no comparison succeeds and the edge array
was read out of bounds. The selected edge now defaults to the first one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The in-triangle test relies on the signs of three signed areas, which
are all zero for any point on the supporting line of a collinear (or
point) triangle, so such points were reported at distance 0. Triangles
whose minimum height is below GLOBAL_EPSILON (compared with squared
quantities) now skip the inside test and use the edge distances.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Triangle::is_degenerated only measured the distance from the third
vertex to the line of the first edge, so a triangle whose minimum height
is below GLOBAL_EPSILON could be reported valid while normal() returns
nullopt. Tetrahedron::is_degenerated then built a Plane from that
triangle and threw bad_optional_access.

Both now compare their minimum height (2*area / longest edge for
triangles, 3*volume / largest facet area for tetrahedra) to
GLOBAL_EPSILON, using squared quantities to avoid square roots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
minimum_height measured the distance from the longest edge line to an
arbitrary vertex (the last one not on that edge), so a valid polygon
with one vertex near the longest edge line, e.g. (0,0),(10,0),(5,5),
(-1,0), was reported with height 0 and as degenerated. The height is now
the maximum distance over all vertices not on the longest edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
closest_neighbor called front() on an empty result when the search had
no point, and neighbors(point, 0) passed &vector[0] of empty vectors to
nanoflann, whose k-NN result set writes at index k-1. closest_neighbor
now returns NO_ID on an empty set (as AABBTree::closest_element_box
does), and neighbors returns an empty vector for k = 0 or an empty set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SquareMatrix<1> is explicitly instantiated but determinant() and
inverse() threw "Not implemented", so CoordinateSystem1D::coordinates
always threw. Both are now implemented for dimension 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rotate normalized the angle with while loops adding or removing 2*PI,
which never terminate for infinite angles or angles whose ulp exceeds
2*PI (e.g. 1e17). The normalization is useless since the rotation
matrix is quadratic in the quaternion, so it is removed. The axis check
compared a squared length to GLOBAL_EPSILON, rejecting valid axes
shorter than 1e-3; any non-null finite axis is now accepted. The
homogeneous coordinate is always 1, so its check and division are
dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the line was perpendicular to the cylinder axis, the computed wall
intersections fell through to the end-disk code, dividing by D.z == 0
and appending parameters past the 2-element array. The perpendicular
case is now handled on its own, using the line-to-axis distance with an
epsilon tolerance so tangent lines give a single point. Parameter
insertion is bounded.

Correctness bookkeeping is also fixed: the line correctness flag was
overwritten instead of AND-accumulated and both correctness lists
received extra points; each now holds exactly one entry per result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
segment_sphere, segment_cylinder and segment_ellipse intersections
filtered the line intersections lying on the segment but returned the
unfiltered correctness lists, so correctness points no longer matched
the result points. Correctness entries are now filtered in lockstep.

plane_circle_intersection overwrote its correctness flags for each
point instead of AND-accumulating them, and triangle_circle_intersection
ignored the circle correctness flag of the plane-circle result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The discriminant |P-C|^2 - (D.(P-C))^2 - r^2 suffered catastrophic
cancellation when the line origin was far from the sphere (no or wrong
intersections for an origin at 1e8), and compared an area to
GLOBAL_EPSILON, reporting tangency for lines 0.4e-4 away from a sphere
of radius 1e-4. The sphere center is now projected on the line, the
distance h is computed directly, the tangency test compares the length
r - h to GLOBAL_EPSILON, and the roots use (r-h)*(r+h) from the
projected point.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sine of the angle between the circle normal and an axis returned
M_PI instead of 0 when the normal was opposite to that axis, inflating
the bounding box along that axis by PI times the radius.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The separating axis tests of BoundingBox3D::intersects and
epsilon_intersects with a Triangle3D used unnormalized axes (triangle
normal, edge x box axis cross products), so GLOBAL_EPSILON was compared
to projections scaled by |axis| (up to edge length squared): large
triangles 0.5e-6 away from a box were not epsilon-intersecting. The
tolerance is now scaled by the axis length. The triangle normal test
projects all three vertices instead of only the first one, which is
wrong when the normal is noisy, and the box face tests, redundant with
the triangle bounding box test, are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 1D line test returned true only when the origin was before the box
in the line direction, so rays starting inside the box were reported as
not intersecting, and infinite lines (which cover the whole axis) as
well. 1D lines now always intersect; rays pointing away from the box are
rejected by the existing ray quick-skip test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tetrahedron_aspect_ratio and tetrahedron_collapse_aspect_ratio compared
a determinant / volume (cubic in length) to GLOBAL_EPSILON, so a regular
tetrahedron with edge 0.01 was reported as degenerated. The volume ratio
qualities returned NaN (0/0) when vertices were coincident. All four now
use Tetrahedron::is_degenerated (minimum height below GLOBAL_EPSILON):
degenerated tetrahedra get the worst value (max for aspect ratios, 0 for
volume ratios).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
is_point_inside_polygon used an inexact floating-point orientation test,
and boundary points were inside or outside depending on which edge they
lay on (e.g. bottom edge inside, top edge outside for a unit square).
The winding number now uses GEO::PCK::orient_2d, and points on the
polygon boundary are always considered inside, independently of the
polygon orientation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
point_line_distance and point_segment_distance used Heron's formula on
the three side lengths, whose cancellation gives errors above
GLOBAL_EPSILON (4e-6 measured) for points far along the line. Both now
measure the distance to the projection, so they are accurate and
consistent with point_line_projection / point_segment_projection.
point_segment_projection computes the clamped parameter directly
instead of normalizing the direction, and degenerated segments use
their barycenter for both distance and projection (distance used the
first vertex).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
std::hash<Point2D/3D> XOR-ed the per-coordinate hashes, so every point
with equal coordinates hashed to 0 and permuted coordinates collided
((1,2) and (2,1)). They now delegate to absl::Hash<Point>, which
combines the coordinate array through AbslHashValue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The exact arithmetic splitter and epsilon were zero until
GEO::PCK::initialize() ran (through OpenGeodeGeometryLibrary::initialize).
Without it, two_product produced wrong error terms and exact orient_2d /
orient_3d returned zero or wrong signs for near-degenerate inputs; this
was the case in geometry unit tests, which only initialize the basic
library. The constants are now statically initialized to their IEEE-754
values (2^27 + 1 and 2^-53).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The exact arithmetic of predicates.cpp (two_product, expansion sums)
relies on every operation being rounded; contracting a*b+c into fused
multiply-add breaks it. Only MSVC was protected by a pragma. The file is
now compiled with -ffp-contract=off on GCC and Clang (and excluded from
unity builds so the option applies to it alone).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BotellaA
BotellaA requested a review from panquez October 13, 2026 23:22
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Cpp-Linter Report ⚠️

Some files did not pass the configured checks!

clang-tidy (v21.1.8) reports: 2578 concern(s)
  • include/geode/geometry/basic_objects/tetrahedron.hpp:46:11: warning: [cppcoreguidelines-special-member-functions]

    class 'GenericTetrahedron' defines a copy constructor, a copy assignment operator, a move constructor and a move assignment operator but does not define a destructor

       46 |     class GenericTetrahedron
          |           ^
  • include/geode/geometry/basic_objects/tetrahedron.hpp:50:26: warning: [readability-redundant-inline-specifier]

    variable 'tetrahedron_facet_vertex' has inline specifier but is implicitly inlined

       50 |         static constexpr inline std::array< std::array< index_t, 3 >, 4 >
          |                          ^~~~~~
  • include/geode/geometry/basic_objects/tetrahedron.hpp:51:13: warning: [readability-identifier-naming]

    invalid case style for constexpr variable 'tetrahedron_facet_vertex'

       51 |             tetrahedron_facet_vertex{ { { { 1, 3, 2 } }, { { 0, 2, 3 } },
          |             ^~~~~~~~~~~~~~~~~~~~~~~~
          |             TETRAHEDRON_FACET_VERTEX
  • include/geode/geometry/basic_objects/tetrahedron.hpp:80:34: warning: [cppcoreguidelines-special-member-functions]

    class 'OwnerTetrahedron' defines a copy constructor, a copy assignment operator, a move constructor and a move assignment operator but does not define a destructor

       80 |     class opengeode_geometry_api OwnerTetrahedron
          |                                  ^
  • include/geode/geometry/basic_objects/tetrahedron.hpp:91:9: warning: [google-explicit-constructor]

    single-argument constructors must be marked explicit to avoid unintentional implicit conversions

       91 |         OwnerTetrahedron( const Tetrahedron& other ) noexcept;
          |         ^
          |         explicit 
  • include/geode/geometry/basic_objects/tetrahedron.hpp:98:34: warning: [cppcoreguidelines-special-member-functions]

    class 'Tetrahedron' defines a copy constructor, a copy assignment operator, a move constructor and a move assignment operator but does not define a destructor

       98 |     class opengeode_geometry_api Tetrahedron
          |                                  ^
  • include/geode/geometry/basic_objects/tetrahedron.hpp:110:9: warning: [google-explicit-constructor]

    single-argument constructors must be marked explicit to avoid unintentional implicit conversions

      110 |         Tetrahedron( const OwnerTetrahedron& other ) noexcept;
          |         ^
          |         explicit 
  • include/geode/geometry/basic_objects/triangle.hpp:59:11: warning: [cppcoreguidelines-special-member-functions]

    class 'GenericTriangle' defines a copy constructor, a copy assignment operator, a move constructor and a move assignment operator but does not define a destructor

       59 |     class GenericTriangle
          |           ^
  • include/geode/geometry/basic_objects/triangle.hpp:116:11: warning: [cppcoreguidelines-special-member-functions]

    class 'OwnerTriangle' defines a copy constructor, a copy assignment operator, a move constructor and a move assignment operator but does not define a destructor

      116 |     class OwnerTriangle
          |           ^
  • include/geode/geometry/basic_objects/triangle.hpp:135:11: warning: [cppcoreguidelines-special-member-functions]

    class 'Triangle' defines a copy constructor, a copy assignment operator, a move constructor and a move assignment operator but does not define a destructor

      135 |     class Triangle : public GenericTriangle< RefPoint< dimension >, dimension >
          |           ^
  • include/geode/geometry/basic_objects/triangle.hpp:145:9: warning: [google-explicit-constructor]

    single-argument constructors must be marked explicit to avoid unintentional implicit conversions

      145 |         Triangle( const OwnerTriangle< dimension >& other ) noexcept;
          |         ^
          |         explicit 
  • include/geode/geometry/detail/predicates.hpp:80:6: warning: [readability-use-concise-preprocessor-directives]

    preprocessor condition can be written more concisely using '#ifdef'

       80 | #    if defined( __clang__ )
          |      ^~ ~~~~~~~~~~~~~~~~~~~~
          |      ifdef __clang__
  • include/geode/geometry/detail/predicates.hpp:85:6: warning: [readability-use-concise-preprocessor-directives]

    preprocessor condition can be written more concisely using '#ifdef'

       85 | #    if defined( GEO_DYNAMIC_LIBS )
          |      ^~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |      ifdef GEO_DYNAMIC_LIBS
  • include/geode/geometry/detail/predicates.hpp:86:10: warning: [readability-use-concise-preprocessor-directives]

    preprocessor condition can be written more concisely using '#ifdef'

       86 | #        if defined( _MSC_VER )
          |          ^~ ~~~~~~~~~~~~~~~~~~~
          |          ifdef _MSC_VER
  • include/geode/geometry/detail/predicates.hpp:109:1: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      109 | typedef int GeoMesh;
          | ^~~~~~~~~~~~~~~~~~~
          | using GeoMesh = int
  • include/geode/geometry/detail/predicates.hpp:111:1: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      111 | typedef unsigned char geo_coord_index_t;
          | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          | using geo_coord_index_t = unsigned char
  • include/geode/geometry/detail/predicates.hpp:127:1: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      127 | typedef unsigned int geo_index_t;
          | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          | using geo_index_t = unsigned int
  • include/geode/geometry/detail/predicates.hpp:129:1: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      129 | typedef int geo_signed_index_t;
          | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          | using geo_signed_index_t = int
  • include/geode/geometry/detail/predicates.hpp:133:1: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      133 | typedef double geo_coord_t;
          | ^~~~~~~~~~~~~~~~~~~~~~~~~~
          | using geo_coord_t = double
  • include/geode/geometry/detail/predicates.hpp:135:1: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      135 | typedef int geo_boolean;
          | ^~~~~~~~~~~~~~~~~~~~~~~
          | using geo_boolean = int
  • include/geode/geometry/detail/predicates.hpp:137:1: warning: [cppcoreguidelines-use-enum-class]

    enum '(unnamed enum at /__w/OpenGeode/OpenGeode/include/geode/geometry/detail/predicates.hpp:137:1)' is unscoped, use 'enum class' instead

      137 | enum
          | ^
  • include/geode/geometry/detail/predicates.hpp:137:1: warning: [performance-enum-size]

    enum '(unnamed enum at /__w/OpenGeode/OpenGeode/include/geode/geometry/detail/predicates.hpp:137:1)' uses a larger base type ('unsigned int', size: 4 bytes) than necessary for its value set, consider using 'std::uint8_t' (1 byte) as the base type to reduce its size

  • include/geode/geometry/detail/predicates.hpp:139:5: warning: [readability-identifier-naming]

    invalid case style for enum constant 'GEO_FALSE'

      139 |     GEO_FALSE = 0,
          |     ^~~~~~~~~
          |     geo_false
  • include/geode/geometry/detail/predicates.hpp:140:5: warning: [readability-identifier-naming]

    invalid case style for enum constant 'GEO_TRUE'

      140 |     GEO_TRUE = 1
          |     ^~~~~~~~
          |     geo_true
  • include/geode/geometry/detail/predicates.hpp:165:6: warning: [readability-use-concise-preprocessor-directives]

    preprocessor condition can be written more concisely using '#ifdef'

      165 | #    if defined( __ANDROID__ )
          |      ^~ ~~~~~~~~~~~~~~~~~~~~~~
          |      ifdef __ANDROID__
  • include/geode/geometry/detail/predicates.hpp:169:6: warning: [readability-use-concise-preprocessor-directives]

    preprocessor condition can be written more concisely using '#ifdef'

      169 | #    if defined( __linux__ )
          |      ^~ ~~~~~~~~~~~~~~~~~~~~
          |      ifdef __linux__
  • include/geode/geometry/detail/predicates.hpp:178:10: warning: [readability-use-concise-preprocessor-directives]

    preprocessor condition can be written more concisely using '#ifdef'

      178 | #        if defined( _OPENMP )
          |          ^~ ~~~~~~~~~~~~~~~~~~
          |          ifdef _OPENMP
  • include/geode/geometry/detail/predicates.hpp:182:10: warning: [readability-use-concise-preprocessor-directives]

    preprocessor condition can be written more concisely using '#ifdef'

      182 | #        if defined( __INTEL_COMPILER )
          |          ^~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |          ifdef __INTEL_COMPILER
  • include/geode/geometry/detail/predicates.hpp:193:10: warning: [readability-use-concise-preprocessor-directives]

    preprocessor condition can be written more concisely using '#ifdef'

      193 | #        if defined( __x86_64 )
          |          ^~ ~~~~~~~~~~~~~~~~~~~
          |          ifdef __x86_64
  • include/geode/geometry/detail/predicates.hpp:276:13: warning: [readability-identifier-naming]

    invalid case style for macro definition 'CPP_CONCAT_'

      276 | #    define CPP_CONCAT_( A, B ) A##B
          |             ^~~~~~~~~~~
          |             CPP_CONCAT
  • include/geode/geometry/detail/predicates.hpp:278:13: warning: [cppcoreguidelines-macro-usage]

    function-like macro 'CPP_CONCAT' used; consider a 'constexpr' template function

      278 | #    define CPP_CONCAT( A, B ) CPP_CONCAT_( A, B )
          |             ^
  • include/geode/geometry/detail/predicates.hpp:280:6: warning: [readability-use-concise-preprocessor-directives]

    preprocessor condition can be written more concisely using '#ifdef'

      280 | #    if defined( GOMGEN )
          |      ^~ ~~~~~~~~~~~~~~~~~
          |      ifdef GOMGEN
  • include/geode/geometry/detail/predicates.hpp:288:6: warning: [readability-use-concise-preprocessor-directives]

    preprocessor condition can be written more concisely using '#ifdef'

      288 | #    if defined( GOMGEN )
          |      ^~ ~~~~~~~~~~~~~~~~~
          |      ifdef GOMGEN
  • include/geode/geometry/detail/predicates.hpp:306:13: warning: [cppcoreguidelines-macro-usage]

    function-like macro 'FOR' used; consider a 'constexpr' template function

      306 | #    define FOR( I, UPPERBND )                                                 \
          |             ^
  • include/geode/geometry/detail/predicates.hpp:307:29: warning: [bugprone-macro-parentheses]

    macro argument should be enclosed in parentheses

      307 |         for( index_t I = 0; I < index_t( UPPERBND ); ++I )
          |                             ^
          |                             ()
  • include/geode/geometry/detail/predicates.hpp:307:56: warning: [bugprone-macro-parentheses]

    macro argument should be enclosed in parentheses

      307 |         for( index_t I = 0; I < index_t( UPPERBND ); ++I )
          |                                                        ^
          |                                                        ()
  • include/geode/geometry/detail/predicates.hpp:316:11: warning: [readability-identifier-naming]

    invalid case style for namespace 'GEO'

      316 | namespace GEO
          |           ^
    note: this fix will not be applied because it overlaps with another fix
  • include/geode/geometry/detail/predicates.hpp:319:39: warning: [hicpp-named-parameter]

    all parameters should be named in a function

      319 |     inline void geo_argused( const T& )
          |                                       ^
          |                                        /*unused*/
  • include/geode/geometry/detail/predicates.hpp:332:13: warning: [bugprone-reserved-identifier]

    declaration uses identifier '_USE_MATH_DEFINES', which is a reserved identifier

      332 | #    define _USE_MATH_DEFINES
          |             ^
    note: this fix will not be applied because it overlaps with another fix
  • include/geode/geometry/detail/predicates.hpp:332:13: warning: [readability-identifier-naming]

    invalid case style for macro definition '_USE_MATH_DEFINES'

    note: this fix will not be applied because it overlaps with another fix
  • include/geode/geometry/detail/predicates.hpp:334:14: warning: [hicpp-deprecated-headers]

    inclusion of deprecated C++ header 'float.h'; consider using 'cfloat' instead

      334 | #    include <float.h>
          |              ^~~~~~~~~
          |              <cfloat>
  • include/geode/geometry/detail/predicates.hpp:335:14: warning: [hicpp-deprecated-headers]

    inclusion of deprecated C++ header 'limits.h'; consider using 'climits' instead

      335 | #    include <limits.h>
          |              ^~~~~~~~~~
          |              <climits>
  • include/geode/geometry/detail/predicates.hpp:341:18: warning: [hicpp-deprecated-headers]

    inclusion of deprecated C++ header 'stdint.h'; consider using 'cstdint' instead

      341 | #        include <stdint.h>
          |                  ^~~~~~~~~~
          |                  <cstdint>
  • include/geode/geometry/detail/predicates.hpp:348:15: warning: [readability-identifier-naming]

    invalid case style for namespace 'Numeric'

      348 |     namespace Numeric
          |               ^~~~~~~
          |               numeric
  • include/geode/geometry/detail/predicates.hpp:350:9: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      350 |         typedef void* pointer;
          |         ^~~~~~~~~~~~~~~~~~~~~
          |         using pointer = void*
  • include/geode/geometry/detail/predicates.hpp:352:9: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      352 |         typedef int8_t int8;
          |         ^~~~~~~~~~~~~~~~~~~
          |         using int8 = int8_t
  • include/geode/geometry/detail/predicates.hpp:354:9: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      354 |         typedef int16_t int16;
          |         ^~~~~~~~~~~~~~~~~~~~~
          |         using int16 = int16_t
  • include/geode/geometry/detail/predicates.hpp:356:9: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      356 |         typedef int32_t int32;
          |         ^~~~~~~~~~~~~~~~~~~~~
          |         using int32 = int32_t
  • include/geode/geometry/detail/predicates.hpp:358:9: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      358 |         typedef int64_t int64;
          |         ^~~~~~~~~~~~~~~~~~~~~
          |         using int64 = int64_t
  • include/geode/geometry/detail/predicates.hpp:360:9: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      360 |         typedef uint8_t uint8;
          |         ^~~~~~~~~~~~~~~~~~~~~
          |         using uint8 = uint8_t
  • include/geode/geometry/detail/predicates.hpp:362:9: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      362 |         typedef uint16_t uint16;
          |         ^~~~~~~~~~~~~~~~~~~~~~~
          |         using uint16 = uint16_t
  • include/geode/geometry/detail/predicates.hpp:364:9: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      364 |         typedef uint32_t uint32;
          |         ^~~~~~~~~~~~~~~~~~~~~~~
          |         using uint32 = uint32_t
  • include/geode/geometry/detail/predicates.hpp:366:9: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      366 |         typedef uint64_t uint64;
          |         ^~~~~~~~~~~~~~~~~~~~~~~
          |         using uint64 = uint64_t
  • include/geode/geometry/detail/predicates.hpp:368:9: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      368 |         typedef float float32;
          |         ^~~~~~~~~~~~~~~~~~~~~
          |         using float32 = float
  • include/geode/geometry/detail/predicates.hpp:370:9: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      370 |         typedef double float64;
          |         ^~~~~~~~~~~~~~~~~~~~~~
          |         using float64 = double
  • include/geode/geometry/detail/predicates.hpp:398:30: warning: [readability-identifier-length]

    parameter name 'x' is too short, expected at least 3 characters

      398 |         bool is_nan( float32 x );
          |                              ^
  • include/geode/geometry/detail/predicates.hpp:400:30: warning: [readability-identifier-length]

    parameter name 'x' is too short, expected at least 3 characters

      400 |         bool is_nan( float64 x );
          |                              ^
  • include/geode/geometry/detail/predicates.hpp:418:33: warning: [readability-identifier-naming]

    invalid case style for global constant 'size'

      418 |             static const size_t size = sizeof( T );
          |                                 ^~~~
          |                                 SIZE
  • include/geode/geometry/detail/predicates.hpp:420:33: warning: [readability-identifier-naming]

    invalid case style for global constant 'numbits'

      420 |             static const size_t numbits = 8 * sizeof( T );
          |                                 ^~~~~~~
          |                                 NUMBITS
  • include/geode/geometry/detail/predicates.hpp:430:10: warning: [cppcoreguidelines-use-enum-class]

    enum 'SIGN' is unscoped, use 'enum class' instead

      430 |     enum SIGN
          |          ^
  • include/geode/geometry/detail/predicates.hpp:430:10: warning: [performance-enum-size]

    enum 'SIGN' uses a larger base type ('int', size: 4 bytes) than necessary for its value set, consider using 'std::int8_t' (1 byte) as the base type to reduce its size

  • include/geode/geometry/detail/predicates.hpp:440:35: warning: [readability-identifier-length]

    parameter name 'x' is too short, expected at least 3 characters

      440 |     inline SIGN geo_sgn( const T& x )
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:442:41: warning: [readability-avoid-nested-conditional-operator]

    conditional operator is used as sub-expression of parent conditional operator, refrain from using nested conditional operators

      442 |         return ( x > 0 ) ? positive : ( ( x < 0 ) ? negative : zero );
          |                                         ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/detail/predicates.hpp:442:16: note: parent conditional operator here
      442 |         return ( x > 0 ) ? positive : ( ( x < 0 ) ? negative : zero );
          |                ^
  • include/geode/geometry/detail/predicates.hpp:446:25: warning: [readability-identifier-length]

    parameter name 'x' is too short, expected at least 3 characters

      446 |     inline T geo_sqr( T x )
          |                         ^
  • include/geode/geometry/detail/predicates.hpp:452:31: warning: [readability-identifier-length]

    parameter name 'x' is too short, expected at least 3 characters

      452 |     inline void geo_clamp( T& x, T min, T max )
          |                               ^
  • include/geode/geometry/detail/predicates.hpp:452:34: warning: [bugprone-easily-swappable-parameters]

    2 adjacent parameters of 'geo_clamp' of similar type ('T') are easily swapped by mistake

      452 |     inline void geo_clamp( T& x, T min, T max )
          |                                  ^~~~~~~~~~~~
    /__w/OpenGeode/OpenGeode/include/geode/geometry/detail/predicates.hpp:452:36: note: the first parameter in the range is 'min'
      452 |     inline void geo_clamp( T& x, T min, T max )
          |                                    ^~~
    /__w/OpenGeode/OpenGeode/include/geode/geometry/detail/predicates.hpp:452:43: note: the last parameter in the range is 'max'
      452 |     inline void geo_clamp( T& x, T min, T max )
          |                                           ^~~
  • include/geode/geometry/detail/predicates.hpp:464:5: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      464 |     typedef geo_index_t index_t;
          |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~
          |     using index_t = geo_index_t
  • include/geode/geometry/detail/predicates.hpp:471:5: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      471 |     typedef geo_signed_index_t signed_index_t;
          |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |     using signed_index_t = geo_signed_index_t
  • include/geode/geometry/detail/predicates.hpp:483:5: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

      483 |     typedef geo_coord_index_t coord_index_t;
          |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |     using coord_index_t = geo_coord_index_t
  • include/geode/geometry/detail/predicates.hpp:485:33: warning: [readability-identifier-length]

    parameter name 'x' is too short, expected at least 3 characters

      485 |     inline double round( double x )
          |                                 ^
  • include/geode/geometry/detail/predicates.hpp:487:39: warning: [cppcoreguidelines-avoid-magic-numbers]

    0.5 is a magic number; consider replacing it with a named constant

      487 |         return ( ( x - floor( x ) ) > 0.5 ? ceil( x ) : floor( x ) );
          |                                       ^
  • include/geode/geometry/detail/predicates.hpp:498:14: warning: [hicpp-deprecated-headers]

    inclusion of deprecated C++ header 'assert.h'; consider using 'cassert' instead

      498 | #    include <assert.h>
          |              ^~~~~~~~~~
          |              <cassert>
  • include/geode/geometry/detail/predicates.hpp:508:17: warning: [cppcoreguidelines-macro-usage]

    function-like macro 'geo_assert' used; consider a 'constexpr' template function

      508 | #        define geo_assert( x ) assert( x )
          |                 ^
  • include/geode/geometry/detail/predicates.hpp:508:17: warning: [readability-identifier-naming]

    invalid case style for macro definition 'geo_assert'

      508 | #        define geo_assert( x ) assert( x )
          |                 ^~~~~~~~~~
          |                 GEO_ASSERT
  • include/geode/geometry/detail/predicates.hpp:509:17: warning: [cppcoreguidelines-macro-usage]

    function-like macro 'geo_range_assert' used; consider a 'constexpr' template function

      509 | #        define geo_range_assert( x, min_val, max_val )                        \
          |                 ^
  • include/geode/geometry/detail/predicates.hpp:509:17: warning: [readability-identifier-naming]

    invalid case style for macro definition 'geo_range_assert'

      509 | #        define geo_range_assert( x, min_val, max_val )                        \
          |                 ^~~~~~~~~~~~~~~~
          |                 GEO_RANGE_ASSERT
  • include/geode/geometry/detail/predicates.hpp:511:17: warning: [readability-identifier-naming]

    invalid case style for macro definition 'geo_assert_not_reached'

      511 | #        define geo_assert_not_reached assert( 0 )
          |                 ^~~~~~~~~~~~~~~~~~~~~~
          |                 GEO_ASSERT_NOT_REACHED
  • include/geode/geometry/detail/predicates.hpp:518:21: warning: [readability-identifier-naming]

    invalid case style for macro definition 'geo_debug_assert'

      518 | #            define geo_debug_assert( x )
          |                     ^~~~~~~~~~~~~~~~
          |                     GEO_DEBUG_ASSERT
  • include/geode/geometry/detail/predicates.hpp:519:21: warning: [readability-identifier-naming]

    invalid case style for macro definition 'geo_debug_range_assert'

      519 | #            define geo_debug_range_assert( x, min_val, max_val )
          |                     ^~~~~~~~~~~~~~~~~~~~~~
          |                     GEO_DEBUG_RANGE_ASSERT
  • include/geode/geometry/detail/predicates.hpp:527:21: warning: [readability-identifier-naming]

    invalid case style for macro definition 'geo_parano_assert'

      527 | #            define geo_parano_assert( x )
          |                     ^~~~~~~~~~~~~~~~~
          |                     GEO_PARANO_ASSERT
  • include/geode/geometry/detail/predicates.hpp:528:21: warning: [readability-identifier-naming]

    invalid case style for macro definition 'geo_parano_range_assert'

      528 | #            define geo_parano_range_assert( x, min_val, max_val )
          |                     ^~~~~~~~~~~~~~~~~~~~~~~
          |                     GEO_PARANO_RANGE_ASSERT
  • include/geode/geometry/detail/predicates.hpp:534:17: warning: [readability-identifier-naming]

    invalid case style for macro definition 'geo_cite'

      534 | #        define geo_cite( x )
          |                 ^~~~~~~~
          |                 GEO_CITE
  • include/geode/geometry/detail/predicates.hpp:538:17: warning: [readability-identifier-naming]

    invalid case style for macro definition 'geo_cite_with_info'

      538 | #        define geo_cite_with_info( x, y )
          |                 ^~~~~~~~~~~~~~~~~~
          |                 GEO_CITE_WITH_INFO
  • include/geode/geometry/detail/predicates.hpp:542:17: warning: [cppcoreguidelines-macro-usage]

    macro 'FPG_UNCERTAIN_VALUE' used to declare a constant; consider using a 'constexpr' constant

      542 | #        define FPG_UNCERTAIN_VALUE 0
          |                 ^
  • include/geode/geometry/detail/predicates.hpp:560:1: warning: [modernize-concat-nested-namespaces]

    nested namespaces can be concatenated

      560 | namespace GEO
          | ^~~~~~~~~~~~~
      561 | {
          | ~
      562 |     namespace PCK
          |     ~~~~~~~~~~~~~
          |     namespace GEO::PCK
  • include/geode/geometry/detail/predicates.hpp:562:15: warning: [readability-identifier-naming]

    invalid case style for namespace 'PCK'

      562 |     namespace PCK
          |               ^
    note: this fix will not be applied because it overlaps with another fix
  • include/geode/geometry/detail/predicates.hpp:564:70: warning: [readability-identifier-length]

    parameter name 'p0' is too short, expected at least 3 characters

      564 |         opengeode_geometry_api SIGN orient_2d( const geode::Point2D& p0,
          |                                                                      ^
  • include/geode/geometry/detail/predicates.hpp:565:35: warning: [readability-identifier-length]

    parameter name 'p1' is too short, expected at least 3 characters

      565 |             const geode::Point2D& p1,
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:566:35: warning: [readability-identifier-length]

    parameter name 'p2' is too short, expected at least 3 characters

      566 |             const geode::Point2D& p2 );
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:568:70: warning: [readability-identifier-length]

    parameter name 'p0' is too short, expected at least 3 characters

      568 |         opengeode_geometry_api SIGN orient_3d( const geode::Point3D& p0,
          |                                                                      ^
  • include/geode/geometry/detail/predicates.hpp:569:35: warning: [readability-identifier-length]

    parameter name 'p1' is too short, expected at least 3 characters

      569 |             const geode::Point3D& p1,
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:570:35: warning: [readability-identifier-length]

    parameter name 'p2' is too short, expected at least 3 characters

      570 |             const geode::Point3D& p2,
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:571:35: warning: [readability-identifier-length]

    parameter name 'p3' is too short, expected at least 3 characters

      571 |             const geode::Point3D& p3 );
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:573:68: warning: [readability-identifier-length]

    parameter name 'p0' is too short, expected at least 3 characters

      573 |         opengeode_geometry_api SIGN det_3d( const geode::Vector3D& p0,
          |                                                                    ^
  • include/geode/geometry/detail/predicates.hpp:574:36: warning: [readability-identifier-length]

    parameter name 'p1' is too short, expected at least 3 characters

      574 |             const geode::Vector3D& p1,
          |                                    ^
  • include/geode/geometry/detail/predicates.hpp:575:36: warning: [readability-identifier-length]

    parameter name 'p2' is too short, expected at least 3 characters

      575 |             const geode::Vector3D& p2 );
          |                                    ^
  • include/geode/geometry/detail/predicates.hpp:577:71: warning: [readability-identifier-length]

    parameter name 'p0' is too short, expected at least 3 characters

      577 |         opengeode_geometry_api bool aligned_3d( const geode::Point3D& p0,
          |                                                                       ^
  • include/geode/geometry/detail/predicates.hpp:578:35: warning: [readability-identifier-length]

    parameter name 'p1' is too short, expected at least 3 characters

      578 |             const geode::Point3D& p1,
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:579:35: warning: [readability-identifier-length]

    parameter name 'p2' is too short, expected at least 3 characters

      579 |             const geode::Point3D& p2 );
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:581:67: warning: [readability-identifier-length]

    parameter name 'p0' is too short, expected at least 3 characters

      581 |         opengeode_geometry_api SIGN dot_3d( const geode::Point3D& p0,
          |                                                                   ^
  • include/geode/geometry/detail/predicates.hpp:582:35: warning: [readability-identifier-length]

    parameter name 'p1' is too short, expected at least 3 characters

      582 |             const geode::Point3D& p1,
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:583:35: warning: [readability-identifier-length]

    parameter name 'p2' is too short, expected at least 3 characters

      583 |             const geode::Point3D& p2 );
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:585:67: warning: [readability-identifier-length]

    parameter name 'p0' is too short, expected at least 3 characters

      585 |         opengeode_geometry_api SIGN dot_2d( const geode::Point2D& p0,
          |                                                                   ^
  • include/geode/geometry/detail/predicates.hpp:586:35: warning: [readability-identifier-length]

    parameter name 'p1' is too short, expected at least 3 characters

      586 |             const geode::Point2D& p1,
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:587:35: warning: [readability-identifier-length]

    parameter name 'p2' is too short, expected at least 3 characters

      587 |             const geode::Point2D& p2 );
          |                                   ^
  • include/geode/geometry/detail/predicates.hpp:593:1: warning: [modernize-concat-nested-namespaces]

    nested namespaces can be concatenated

      593 | namespace geode
          | ^~~~~~~~~~~~~~~
      594 | {
          | ~
      595 |     namespace internal
          |     ~~~~~~~~~~~~~~~~~~
          |     namespace geode::internal
  • include/geode/geometry/internal/intersection_from_sides.hpp:28:1: warning: [modernize-concat-nested-namespaces]

    nested namespaces can be concatenated

       28 | namespace geode
          | ^~~~~~~~~~~~~~~
       29 | {
          | ~
       30 |     namespace internal
          |     ~~~~~~~~~~~~~~~~~~
          |     namespace geode::internal
  • include/geode/geometry/internal/intersection_from_sides.hpp:33:25: warning: [readability-identifier-length]

    parameter name 's0' is too short, expected at least 3 characters

       33 |             const SIDE& s0, const SIDE& s1 )
          |                         ^
  • include/geode/geometry/internal/intersection_from_sides.hpp:33:41: warning: [readability-identifier-length]

    parameter name 's1' is too short, expected at least 3 characters

       33 |             const SIDE& s0, const SIDE& s1 )
          |                                         ^
  • include/geode/geometry/internal/intersection_from_sides.hpp:54:39: warning: [readability-function-cognitive-complexity]

    function 'triangle_intersection_detection' has cognitive complexity of 17 (threshold 10)

       54 |         [[nodiscard]] inline POSITION triangle_intersection_detection(
          |                                       ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/intersection_from_sides.hpp:57:13: note: +1, including nesting penalty of 0, nesting level increased to 1
       57 |             if( s0 == SIDE::zero )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/intersection_from_sides.hpp:59:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       59 |                 if( s1 == SIDE::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/intersection_from_sides.hpp:63:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       63 |                 if( s2 == SIDE::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/intersection_from_sides.hpp:67:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       67 |                 if( s1 == s2 )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/intersection_from_sides.hpp:73:13: note: +1, including nesting penalty of 0, nesting level increased to 1
       73 |             if( s1 == SIDE::zero )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/intersection_from_sides.hpp:75:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       75 |                 if( s2 == SIDE::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/intersection_from_sides.hpp:79:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       79 |                 if( s0 == s2 )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/intersection_from_sides.hpp:85:13: note: +1, including nesting penalty of 0, nesting level increased to 1
       85 |             if( s2 == SIDE::zero )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/intersection_from_sides.hpp:87:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       87 |                 if( s0 == s1 )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/intersection_from_sides.hpp:93:13: note: +1, including nesting penalty of 0, nesting level increased to 1
       93 |             if( s0 == s1 && s1 == s2 )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/intersection_from_sides.hpp:93:26: note: +1
       93 |             if( s0 == s1 && s1 == s2 )
          |                          ^
  • include/geode/geometry/internal/intersection_from_sides.hpp:55:24: warning: [readability-identifier-length]

    parameter name 's0' is too short, expected at least 3 characters

       55 |             const SIDE s0, const SIDE s1, const SIDE s2 )
          |                        ^
  • include/geode/geometry/internal/intersection_from_sides.hpp:55:39: warning: [readability-identifier-length]

    parameter name 's1' is too short, expected at least 3 characters

       55 |             const SIDE s0, const SIDE s1, const SIDE s2 )
          |                                       ^
  • include/geode/geometry/internal/intersection_from_sides.hpp:55:54: warning: [readability-identifier-length]

    parameter name 's2' is too short, expected at least 3 characters

       55 |             const SIDE s0, const SIDE s1, const SIDE s2 )
          |                                                      ^
  • include/geode/geometry/internal/position_from_sides.hpp:28:1: warning: [modernize-concat-nested-namespaces]

    nested namespaces can be concatenated

       28 | namespace geode
          | ^~~~~~~~~~~~~~~
       29 | {
          | ~
       30 |     namespace internal
          |     ~~~~~~~~~~~~~~~~~~
          |     namespace geode::internal
  • include/geode/geometry/internal/position_from_sides.hpp:33:25: warning: [readability-identifier-length]

    parameter name 's0' is too short, expected at least 3 characters

       33 |             const SIDE& s0, const SIDE& s1 )
          |                         ^
  • include/geode/geometry/internal/position_from_sides.hpp:33:41: warning: [readability-identifier-length]

    parameter name 's1' is too short, expected at least 3 characters

       33 |             const SIDE& s0, const SIDE& s1 )
          |                                         ^
  • include/geode/geometry/internal/position_from_sides.hpp:51:25: warning: [readability-identifier-length]

    parameter name 's0' is too short, expected at least 3 characters

       51 |             const SIDE& s0, const SIDE& s1, bool points_aligned )
          |                         ^
  • include/geode/geometry/internal/position_from_sides.hpp:51:41: warning: [readability-identifier-length]

    parameter name 's1' is too short, expected at least 3 characters

       51 |             const SIDE& s0, const SIDE& s1, bool points_aligned )
          |                                         ^
  • include/geode/geometry/internal/position_from_sides.hpp:60:39: warning: [readability-function-cognitive-complexity]

    function 'point_triangle_position' has cognitive complexity of 17 (threshold 10)

       60 |         [[nodiscard]] inline POSITION point_triangle_position(
          |                                       ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:63:13: note: +1, including nesting penalty of 0, nesting level increased to 1
       63 |             if( s0 == SIDE::zero )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:65:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       65 |                 if( s1 == SIDE::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:69:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       69 |                 if( s2 == SIDE::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:73:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       73 |                 if( s1 == s2 )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:79:13: note: +1, including nesting penalty of 0, nesting level increased to 1
       79 |             if( s1 == SIDE::zero )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:81:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       81 |                 if( s2 == SIDE::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:85:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       85 |                 if( s0 == s2 )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:91:13: note: +1, including nesting penalty of 0, nesting level increased to 1
       91 |             if( s2 == SIDE::zero )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:93:17: note: +2, including nesting penalty of 1, nesting level increased to 2
       93 |                 if( s0 == s1 )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:99:13: note: +1, including nesting penalty of 0, nesting level increased to 1
       99 |             if( s0 == s1 && s1 == s2 )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:99:26: note: +1
       99 |             if( s0 == s1 && s1 == s2 )
          |                          ^
  • include/geode/geometry/internal/position_from_sides.hpp:61:24: warning: [readability-identifier-length]

    parameter name 's0' is too short, expected at least 3 characters

       61 |             const SIDE s0, const SIDE s1, const SIDE s2 )
          |                        ^
  • include/geode/geometry/internal/position_from_sides.hpp:61:39: warning: [readability-identifier-length]

    parameter name 's1' is too short, expected at least 3 characters

       61 |             const SIDE s0, const SIDE s1, const SIDE s2 )
          |                                       ^
  • include/geode/geometry/internal/position_from_sides.hpp:61:54: warning: [readability-identifier-length]

    parameter name 's2' is too short, expected at least 3 characters

       61 |             const SIDE s0, const SIDE s1, const SIDE s2 )
          |                                                      ^
  • include/geode/geometry/internal/position_from_sides.hpp:107:24: warning: [readability-identifier-length]

    parameter name 's0' is too short, expected at least 3 characters

      107 |             const SIDE s0, const SIDE s1, const SIDE s2, bool point_on_plane )
          |                        ^
  • include/geode/geometry/internal/position_from_sides.hpp:107:39: warning: [readability-identifier-length]

    parameter name 's1' is too short, expected at least 3 characters

      107 |             const SIDE s0, const SIDE s1, const SIDE s2, bool point_on_plane )
          |                                       ^
  • include/geode/geometry/internal/position_from_sides.hpp:107:54: warning: [readability-identifier-length]

    parameter name 's2' is too short, expected at least 3 characters

      107 |             const SIDE s0, const SIDE s1, const SIDE s2, bool point_on_plane )
          |                                                      ^
  • include/geode/geometry/internal/position_from_sides.hpp:116:39: warning: [readability-function-cognitive-complexity]

    function 'point_tetrahedron_position' has cognitive complexity of 60 (threshold 10)

      116 |         [[nodiscard]] inline POSITION point_tetrahedron_position(
          |                                       ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:119:13: note: +1, including nesting penalty of 0, nesting level increased to 1
      119 |             if( signs[0] == GEO::zero )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:121:17: note: +2, including nesting penalty of 1, nesting level increased to 2
      121 |                 if( signs[1] == GEO::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:123:21: note: +3, including nesting penalty of 2, nesting level increased to 3
      123 |                     if( signs[2] == GEO::zero )
          |                     ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:127:21: note: +3, including nesting penalty of 2, nesting level increased to 3
      127 |                     if( signs[3] == GEO::zero )
          |                     ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:131:21: note: +3, including nesting penalty of 2, nesting level increased to 3
      131 |                     if( signs[2] == signs[3] )
          |                     ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:137:17: note: +2, including nesting penalty of 1, nesting level increased to 2
      137 |                 if( signs[2] == GEO::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:139:21: note: +3, including nesting penalty of 2, nesting level increased to 3
      139 |                     if( signs[3] == GEO::zero )
          |                     ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:143:21: note: +3, including nesting penalty of 2, nesting level increased to 3
      143 |                     if( signs[1] == signs[3] )
          |                     ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:149:17: note: +2, including nesting penalty of 1, nesting level increased to 2
      149 |                 if( signs[3] == GEO::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:151:21: note: +3, including nesting penalty of 2, nesting level increased to 3
      151 |                     if( signs[1] == signs[2] )
          |                     ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:157:17: note: +2, including nesting penalty of 1, nesting level increased to 2
      157 |                 if( signs[1] == signs[2] && signs[1] == signs[3] )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:157:42: note: +1
      157 |                 if( signs[1] == signs[2] && signs[1] == signs[3] )
          |                                          ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:163:13: note: +1, including nesting penalty of 0, nesting level increased to 1
      163 |             if( signs[1] == GEO::zero )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:165:17: note: +2, including nesting penalty of 1, nesting level increased to 2
      165 |                 if( signs[2] == GEO::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:167:21: note: +3, including nesting penalty of 2, nesting level increased to 3
      167 |                     if( signs[3] == GEO::zero )
          |                     ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:171:21: note: +3, including nesting penalty of 2, nesting level increased to 3
      171 |                     if( signs[0] == signs[3] )
          |                     ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:177:17: note: +2, including nesting penalty of 1, nesting level increased to 2
      177 |                 if( signs[3] == GEO::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:179:21: note: +3, including nesting penalty of 2, nesting level increased to 3
      179 |                     if( signs[0] == signs[2] )
          |                     ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:185:17: note: +2, including nesting penalty of 1, nesting level increased to 2
      185 |                 if( signs[0] == signs[2] && signs[0] == signs[3] )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:185:42: note: +1
      185 |                 if( signs[0] == signs[2] && signs[0] == signs[3] )
          |                                          ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:191:13: note: +1, including nesting penalty of 0, nesting level increased to 1
      191 |             if( signs[2] == GEO::zero )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:193:17: note: +2, including nesting penalty of 1, nesting level increased to 2
      193 |                 if( signs[3] == GEO::zero )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:195:21: note: +3, including nesting penalty of 2, nesting level increased to 3
      195 |                     if( signs[0] == signs[1] )
          |                     ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:201:17: note: +2, including nesting penalty of 1, nesting level increased to 2
      201 |                 if( signs[0] == signs[1] && signs[0] == signs[3] )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:201:42: note: +1
      201 |                 if( signs[0] == signs[1] && signs[0] == signs[3] )
          |                                          ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:207:13: note: +1, including nesting penalty of 0, nesting level increased to 1
      207 |             if( signs[3] == GEO::zero )
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:209:17: note: +2, including nesting penalty of 1, nesting level increased to 2
      209 |                 if( signs[0] == signs[1] && signs[0] == signs[2] )
          |                 ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:209:42: note: +1
      209 |                 if( signs[0] == signs[1] && signs[0] == signs[2] )
          |                                          ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:215:13: note: +1, including nesting penalty of 0, nesting level increased to 1
      215 |             if( signs[0] == signs[1] && signs[0] == signs[2]
          |             ^
    /__w/OpenGeode/OpenGeode/include/geode/geometry/internal/position_from_sides.hpp:216:17: note: +1
      216 |                 && signs[0] == signs[3] )
          |                 ^
  • include/geode/geometry/nn_search.hpp:41:11: warning: [cppcoreguidelines-special-member-functions]

    class 'NNSearch' defines a destructor, a copy constructor, a copy assignment operator and a move constructor but does not define a move assignment operator

       41 |     class NNSearch
          |           ^
  • src/geode/geometry/basic_objects/circle.cpp:62:24: warning: [readability-identifier-length]

    variable name 'dP' is too short, expected at least 3 characters

       62 |             const auto dP = plane_.normal().dot( direction );
          |                        ^
  • src/geode/geometry/basic_objects/circle.cpp:67:35: warning: [readability-math-missing-parentheses]

    '*' has higher precedence than '-'; add parentheses to explicitly specify the order of operations

       67 |             return std::sqrt( 1 - dP * dP );
          |                                   ^~~~~~~
          |                                   (      )
  • src/geode/geometry/detail/predicates.cpp:571:16: warning: [readability-identifier-length]

    variable name '_0' is too short, expected at least 3 characters

Have any feedback or feature suggestions? Share it here.

BotellaA and others added 5 commits October 14, 2026 10:00
…le_distance

is_triangle_flat duplicated the Triangle2D::is_degenerated formula.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…segments

point_cylinder_distance moves to the public distance API and returns the
closest point on the cylinder surface, so the degenerated segment/cylinder
correctness point lies on the cylinder like for the other objects.
degenerated_segment_intersection parameters are renamed to match all
call sites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All branches now compare lengths against GLOBAL_EPSILON (wall, end disks,
parallel case), and parameters closer than GLOBAL_EPSILON are merged: a
line through an end disk rim reported the rim point twice and lost its
second intersection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… point row

20-vertex star: 157 ns -> 74 ns per call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BotellaA and others added 3 commits October 14, 2026 13:39
test-predicates calls GEO::PCK functions directly: they need to be
exported from the geometry DLL on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GEO::PCK predicates are now exported: predicates.hpp becomes a
public header and predicates.cpp leaves the internal folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The exported GEO::PCK predicates embed Geogram definitions: they are
advanced API, not public API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@panquez

panquez commented Oct 14, 2026

Copy link
Copy Markdown
Member

Triangle / Tetrahedron::is_degenerated are now height-based and stricter on thin elements (Background, Explicit). -> is it maybe the good moment to check in all the repository that our definitions are always the same for degenerated elements. I know there are several definitions through all the repos

@panquez

panquez commented Oct 14, 2026

Copy link
Copy Markdown
Member

Triangle::minimum_height() throws out_of_range when all three vertices coincide (found while testing, not fixed here). -> i suggest to fix it in this PR

Comment thread src/geode/geometry/basic_objects/tetrahedron.cpp
const Vector< dimension > edge12{ point1, point2 };
const auto max_length2 = std::max(
{ edge01.length2(), edge02.length2(), edge12.length2() } );
return squared_double_area( edge01, edge02 )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same question than for tetrahedron. is it possible to have different response depending on which edges is given to squared_double_area. You need to check limit cases.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same answer as for the tetrahedron. |cross(e01, e02)| is the same for any pair of edges in exact arithmetic, and max_length2 is taken over all three edges. Rounding contributes about u·L² against a threshold of GLOBAL_EPSILON·L, so the result is the same whichever edges are used unless L ≳ 1e10.

test_degenerated_triangle_vertex_order (712b5a9) checks caps, obtuse triangles and needles with a minimum height of 0.5, 0.9, 1.1 and 2 × GLOBAL_EPSILON. They sit in a rotated frame offset by up to 1e6, and all 6 vertex permutations must give the expected answer.

Comment thread src/geode/geometry/distance.cpp Outdated
Comment thread src/geode/geometry/is_point_inside.cpp
Comment thread tests/geometry/test-position.cpp Outdated
== geode::SIDE::zero,
"Wrong side for collinear point" );
const auto side_up = geode::point_side_to_segment(
geode::Point2D{ { x2, std::nextafter( x2, 1e300 ) } }, segment );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Clarifies what does std::nextafter( x2, 1e300 ) in all usage of this formula

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It gives the next representable double above x2, i.e. x2 + 1 ulp. That way the test checks that the exact predicates still see a point one ulp off the line as off it. In 6586706 it is now wrapped in documented next_double_above / next_double_below helpers, which use std::numeric_limits< double >::infinity() instead of 1e300.

Comment thread tests/geometry/test-quality.cpp Outdated
Comment thread tests/geometry/test-distance.cpp Outdated
Comment thread tests/geometry/test-intersection.cpp

@panquez panquez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

some comments but LGTM

BotellaA and others added 5 commits October 14, 2026 18:02
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
is_point_on_edge only checked the edge bounding box and relied on its
caller to have checked that the point is on the edge line. It now takes
the exact orientation and checks both, so the function does what its name
says. Tests cover points inside the bounding box of a diagonal edge but
off it, and name the one-ulp moves in the exact predicate tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Triangles (cap, obtuse, needle) and tetrahedra (cap, wedge, sliver) of
thickness 0.5, 0.9, 1.1 and 2 GLOBAL_EPSILON, in a rotated frame offset by
up to 1e6, give the expected result for every vertex permutation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One function per intersected object, with lengths and offsets expressed
with GLOBAL_EPSILON. The degenerated segment is closer than GLOBAL_EPSILON
to the cylinder, so its correctness point is compared exactly to the
closest point on the cylinder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BotellaA
BotellaA requested a review from panquez October 14, 2026 16:03
…lter

perf(Geometry): faster exact predicates on degenerate inputs
Replaced static double initialization with constexpr for DOUBLE_EPSILON and updated expansion_splitter and expansion_epsilon to use these constants.
@BotellaA
BotellaA changed the base branch from next to fix/geometry-robustness-tier1 October 14, 2026 19:33
@BotellaA
BotellaA merged commit 961f682 into fix/geometry-robustness-tier1 Oct 14, 2026
23 checks passed
@BotellaA
BotellaA deleted the fix/geometry-robustness-tier2 branch October 14, 2026 21:07
@BotellaA

Copy link
Copy Markdown
Member Author

🎉 This PR is included in version 19.0.1-rc.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants