Skip to content

Template the per-collision geometry functions on scalar and dimension - #249

Merged
zfergus merged 24 commits into
mainfrom
feature/template-distances
Sep 3, 2026
Merged

Template the per-collision geometry functions on scalar and dimension#249
zfergus merged 24 commits into
mainfrom
feature/template-distances

Conversation

@zfergus

@zfergus zfergus commented Sep 1, 2026

Copy link
Copy Markdown
Member

Description

This PR restructures the distance, tangent-basis, closest-point, relative-velocity, mollifier, and area families into two layers.

  1. Concrete functions move into ipc::detail, templated on scalar and dimension (template <typename T, int dim>), taking Eigen::ConstRef<Eigen::Vector<T, dim>>.
  2. The public names in ipc become thin front ends templated on the argument expression types — they deduce T, dispatch on the compile-time dimension when the arguments know it, and fall back to one runtime branch on size() otherwise.

This design enables:

  • Templating on the scalar: float, double, and autodiff scalars for the value functions.
  • Fixed-size optimization of shared 2D and 3D code.
    • E.g., point_edge_closest_point at 8.7 ns on a matrix row vs 4.7 ns on a Vector3d — the same arithmetic, 1.9× apart, purely from losing the compile-time size.
  • Rows of a column-major vertex matrix are now copied to local stack for cache efficiency.

⚡ Performance

Fixed-size arguments (Vector2d / Vector3d)

plot1_fixed_size
  • Geometric mean 1.21× over 46 functions, max 4.69×, 8 functions at ≥1.5×.
  • For fixed-size inputs, size-dispatched functions gain the most.

V.row(i) arguments

plot2_matrix_rows
  • Geometric mean 1.36×, max 5.42×, 12 functions at ≥1.5×.
  • Implementing a split template design allows copying x,y,z entries to the local stack.
  • Dimension-agnostic functions benefit the most.

float vs double

plot3_float_vs_double
  • Median 1.01×.
  • float provides interoperability and memory footprint here, not speed.
  • Scalar f32 and f64 add/mul/FMA cost the same on this CPU; only division is cheaper (1.17×), but these functions are not division-bound.
  • EIGEN_DONT_VECTORIZE=1 is set PUBLIC, which forces PacketAccess=NO on every Eigen type and removes the twice-as-many-lanes advantage entirely.
    • Lifting that flag results in a wash: 1.5–3.8× on the wide autogen Hessians against 0.77–0.90× on the small per-candidate functions and a reproducible 0.62× on point_line_signed_distance_hessian.

Other Improvements

  • Replace three 2×2 LDLT solves in point_triangle_distance_type with a closed form.
    • Root cause of the cost: each edge lies in the triangle's plane, so the 2×2 Gram matrix is diagonal and the solve collapses to two guarded divisions.
    • 10–13× standalone (104 → 8.6 ns), 7.3× on the full AUTO query (93 → 12.8 ns).
    • Error study over 10.8M configurations (uniform, needle, cap, near-collinear, degenerate, in-plane; coordinate scales 1e-50 to 1e+50): zero classification changes outside triangles collinear to within 1e-11 of their own edge length.

💥 Breaking Changes

  • ipc::Barrier is now an alias for ipc::BarrierBase<T> (defaulting to double).
    • The concrete barriers need explicit template arguments: ipc::ClampedLogBarrier<>, ipc::NormalizedClampedLogBarrier<>, ipc::ClampedLogSqBarrier<>, ipc::CubicBarrier<>, ipc::TwoStageBarrier<>.
  • Gradients/Hessians of the point-point, point-line, and point-edge distances and the normalization_* family return fixed-size Eigen types when the argument knows its dimension, and the previous VectorMax/MatrixMax types otherwise.
    • Assignments into VectorMax9d-style storage compile unchanged; code binding with auto may now hold a fixed-size type.
  • Mixed-precision calls no longer deduce: barrier(float_d, 0.001) must become barrier(float_d, 0.001f).

Type of change

  • Enhancement (non-breaking change which improves existing functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

How Has This Been Tested?

  • Full C++ suite: 4,112,420 assertions in 281 test cases, all passing.
  • Python suite: nose2 -v --pretty-assert -s python/tests against a rebuilt ipctk.
  • test_distance_type.cpp extended for the closed-form point_triangle_distance_type, including the degenerate and extreme-coordinate-scale cases that broke the LDLT.
  • 20k exactly-parallel edge pairs comparing float and double edge_edge_distance_type before and after the PARALLEL_THRESHOLD change.
  • tests/src/tests/benchmark_eigen.cpp for the per-kernel timings.
  • Not tested: float end-to-end through a full simulation loop — only the kernels are exercised at single precision.

Test Configuration:

  • OS and Version: macOS 26.5.2 (arm64, Apple Silicon)
  • Compiler and Version: Apple clang 21.0.0 (clang-2100.1.1.101), Release, C++17

Checklist

  • I have followed the project style guide
  • My code follows the clang-format style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

zfergus and others added 16 commits April 23, 2026 12:10
- Refactored all geometric distance, gradient, and hessian functions to
  be
  templated on scalar type (e.g., double, float).
- Updated Python bindings to explicitly instantiate with double.
- Removed point_point.cpp, moved implementation to header as inline
  template.
- Updated all usages and tests to use templated versions.
- Added Eigen expression overloads for point-point distance functions.
- Updated normal and normalization utilities to be templated.
- Set minimum C++ standard to C++20 in CMakeLists.txt.
- Remove redundant point/edge/triangle distance implementations from
  smooth_contact/distance
- Replace calls to local *_sqr_distance with core distance functions
- Update includes to use ipc/distance/point_line.hpp and related headers
- Simplify mollifier and primitive_distance to use unified API
- Minor cleanup in tangential_potential and point_plane distance math
- Convert barrier functions and barrier class hierarchy to use templates
- Update all usages to specify template parameters where needed
- Update Python bindings and tests to construct template barrier types
- Extend benchmarks to compare float and double barrier performance
- Improves support for mixed-precision and SIMD optimizations
- Set default CMAKE_CXX_STANDARD to 17 for top-level projects
- Comment out EigenExpression concept in utils/eigen_ext.hpp
- Update Eigen-expression wrapper templates to use typename
  parameters and add enable_if where appropriate
…ce API

Scale PARALLEL_THRESHOLD in edge_edge_distance_type with the precision of T.

u x v cancels for nearly parallel edges, leaving an absolute error of about
eps*|u|*|v| per component, so sin^2(theta) cannot be resolved below ~eps^2.
The threshold was left as a hard-coded 2.5e-16, which is ~1.13*eps for double
but sits ~57x *below* float's noise floor, making the near-parallel branch
unreachable in single precision. Over 20k exactly-parallel edge pairs the
float distance type disagreed with the double one 44% of the time, and
edge_edge_distance<float> differed from the double result by >0.1% relative
in 14.8% of cases (worst case 24x). Both drop to 0% after the fix.

The threshold is now derived from the double-tuned value by the ratio of
epsilons, so the double threshold is bit-for-bit unchanged (asserted); only
the newly added float instantiation changes. parallel_tolerance is typed T
rather than double to match.

Guard the *_distance_type EigenExpression wrappers with std::is_class_v.

The other wrappers are rejected by SFINAE when the first template argument is
given explicitly as a scalar, because their trailing return type mentions
typename DerivedX::Scalar. These four return a non-dependent enum, so nothing
rejected the candidate and substitution went on to form Eigen::MatrixBase<T>,
a hard error inside Eigen rather than a substitution failure. As a result
edge_edge_distance_type<double>(a, b, c, d) -- the natural way to pick the
scalar in the new templated API -- failed to compile with 20 errors pointing
into Eigen internals. The guard matches the one already used on the
single-argument wrappers in geometry/normal.hpp.

Drop the commented-out EigenExpression concept and its now-unused <concepts>
include. The doc comment left above it described a dispatch contract the
shipped code does not follow ("Layer 1 always calls Layer 2 with explicit
<T>"), which is exactly the gap that produced the compile failure above.

Fix the sizes in the MatrixMax alias doc comments: MatrixMax2f/MatrixMax2d
were documented as 3x3 and MatrixMax9f/MatrixMax9d as 12x12.

Add a v2.0.0 (alpha) section to the release notes covering the scalar
templatization of the distance, barrier, and normal APIs, along with every
commit since v1.6.0: the Tight Inclusion 1.1.0 update (#248), the tutorial
and Python binding work (#247), the edge-triangle intersection coordinates
and CollisionMesh::face_normals (#245), the mollified m == 0 Hessian PSD
projection fix (#244), and the MeshFEM gallery entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The distance functions now have two layers. The concrete kernels moved into
ipc::detail, templated on <typename T, int dim> (or just <typename T> for the
3D-only line_line, edge_edge, and point_triangle) and taking
Eigen::ConstRef<Eigen::Vector<T, dim>>. The public names in ipc are thin front
ends templated on the argument expression types: they deduce the scalar,
resolve the dimension at compile time when the caller's type knows it, and
otherwise take a single runtime branch on size(). Existing calls are
unaffected.

The win is the dimension, not the parameter passing. Erasing a Vector3d into a
VectorMax3d cost 2.4x, so the front end branches on size() before it
materializes anything and the fixed-size path never forms a dynamically sized
temporary. Measured on the dominant in-tree call site, a row of a column-major
MatrixXd:

  point_line_distance                8.2 -> 2.3 ns   3.5x
  point_edge_distance, AUTO         18.1 -> 5.2 ns   3.5x
  point_triangle_distance, AUTO     93.3 -> 12.8 ns  7.3x
  point_point_distance_hessian                       5x
  normalization_and_jacobian                         3.4x

point_triangle_distance_type is the largest single piece of that: its three
2x2 LDLT solves are replaced by a closed form, since each edge lies in the
triangle's plane and the Gram matrix is therefore diagonal. An error study
over 10.8M configurations found no classification changes outside triangles
collinear to within 1e-11 of their own edge length, a regime where the old
code's own answer flips under a one-ulp input perturbation. It also fixes two
real failures: above coordinate scale ~1e+51 the LDLT returned an infinite
plane distance on every query, and in float near 1e-6 Eigen's tolerance
discarded denormal Gram entries (2771 misclassifications per 400k, now 0).

Two smaller levers, both measured: moving the cold throw bodies out of line
behind [[noreturn]] helpers (worth up to 2x on its own, since constructing a
std::invalid_argument inline consumes the caller's inlining budget), and
branching once on the dimension in EdgeVertexCandidate (3.1x on the gradient).
edge_edge_distance and point_triangle_distance keep their out-of-line switch
dispatch; inlining a 9- or 7-case switch measured as a 10% regression with
runtime distance types.

line_line_distance_gradient and line_line_distance_hessian are single
MatrixBase templates rather than two layers. They read three coefficients per
argument and hand them to generated code, so Eigen::ConstRef's guaranteed
single evaluation buys nothing and its materialization of an expression
argument costs 1.16x.

Also:

- Guard the *_distance_type Eigen-expression overloads with std::is_class_v.
  Their return type is a non-dependent enum, so an explicit scalar argument
  was not rejected by SFINAE and instead formed Eigen::MatrixBase<double>, a
  hard error inside Eigen.
- Scale PARALLEL_THRESHOLD in edge_edge_distance_type with the precision of
  the scalar type. The value tuned for double sits ~57x below the cancellation
  noise floor of u x v in single precision, making the near-parallel branch
  unreachable for float: across 20k exactly-parallel edge pairs the float
  distance type disagreed with the double one 44% of the time. The double
  threshold is bit-for-bit unchanged.
- Taking the address of these functions is no longer possible, so the Python
  bindings wrap them in lambdas.
- Rewrite tests/src/tests/benchmark_eigen.cpp around the shipped design:
  every row now compares the library against a same-TU reference
  implementation that acts as a noise-floor control, and the accumulated
  findings and measurement traps are consolidated into one header block. This
  drops a benchmark helper that asked for block<3,1> from a 1x3 row
  expression, reading out of bounds under NDEBUG.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…antiations

Two cleanups in the templated distance API.

`edge_edge` and `point_triangle` guarded their AUTO resolution with
`is_same_v<T, double> || is_same_v<T, float>` and then let AUTO fall through
to the switch, where the default case threw the generic "invalid distance
type". `point_edge` instead used `is_floating_point_v<T>` with an explicit
`throw_auto_requires_explicit_dtype`. Adopt the latter everywhere, naming the
function in the message, so a scalar type that cannot resolve a distance type
now reports why rather than claiming its distance type is invalid:

    edge_edge_distance: an explicit distance type is required for
    non-floating-point scalars; ...

This is a user-visible improvement for autodiff scalars, which reach these
paths today. It also covers the gradient and Hessian kernels, whose AUTO
resolution was previously unguarded, so they can now be instantiated for
scalar types that have no ordering.

The generated `autogen` instantiations in `line_line`, `point_line`, and
`point_plane` spelled out every signature once per scalar type -- eight
hand-written lines in `point_line`, whose four functions take 6, 9, 6 and 9
arguments and fill arrays of 6, 9, 36 and 81. Fold each file's list into an
`IPC_INSTANTIATE_*_AUTOGEN(T)` macro, matching the convention already used for
the distance kernels themselves. `nm` confirms the same sixteen `float` and
`double` symbols are still emitted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The message claimed "the distance type cannot be determined from an autodiff
scalar", which predates the other scalar types that reach this path. It is now
also hit by SIMD batches and by filib::Interval, for which the old wording was
simply wrong. Say what the actual obstacle is -- resolving AUTO means comparing
single ordered values -- and name all three families.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rnels

Convert every remaining per-collision kernel family to the shape the distance
functions established: fixed-size inner kernels in ipc::detail, deducing
expression-templated front ends in ipc, and float alongside double.

Families that already had scalar-templated kernels but no front end (so a
matrix-row argument forced a Ref materialization per call, and an explicit
scalar produced an error wall inside Eigen): edge_edge_mollifier, point_plane,
and the three signed distances (line_line, point_line -- a 2D family -- and
point_plane). Families that were double-only with dimension-erased
VectorMax/MatrixMax signatures: closest_point, tangent_basis,
relative_velocity, and geometry/area. Their autogen kernels are templated with
the same instantiation-macro pattern as line_line; the only edits to generated
expressions are T() literal wrappers, verified byte-identical to the previous
text modulo the scalar substitution.

relative_velocity's runtime-dim entry points keep their exact public
signatures (template <typename T = double> over dim-templated kernels), so
call sites passing int dim compile unchanged. No caller anywhere needed
editing; the deducing front ends accept every argument type used in-tree.
relative_velocity ends up fully header-inline, so its TU is deleted.

Small hand-written kernels are header-inline; wide autogen bodies stay in
their TUs. That split is load-bearing, and the new "Converted families"
benchmark (tests/src/tests/benchmark_eigen.cpp) measured it both ways: with
the tangent kernels TU-defined, the dimension-erased path paid the Ref copies
and MatrixMax wrap at an opaque call boundary and point_edge_tangent_basis
REGRESSED 2x on matrix rows; header-inlining the value kernels turned that
into the wins below. Measured old-vs-new by interleaved A/B of two binaries
(baseline built from a worktree at the previous commit; controls 0.99-1.00x):

  point_edge_closest_point, rows           8.7  -> 1.8 ns   4.8x
  point_edge_closest_point, Vector3d       4.7  -> 1.7 ns   2.8x
  point_edge_closest_point_jacobian, rows 14.9  -> 6.2 ns   2.4x
  point_point_relative_velocity, rows      5.5  -> 1.4 ns   3.9x
  pp_relative_velocity_jacobian(dim)       6.8  -> 1.8 ns   3.9x
  edge_length, rows                        4.1  -> 1.3 ns   3.1x
  point_edge_tangent_basis, rows           7.1  -> 4.2 ns   1.7x
  point_triangle_tangent_basis, rows       7.7  -> 5.0 ns   1.5x

The already-fixed-size 3D functions (edge_edge/point_triangle closest point,
triangle_area, edge_edge_cross_squarednorm, point_plane_distance) measured
neutral, as expected: the win was always recovering the compile-time
dimension, not the templates themselves. One residual: point_point_tangent
basis on matrix rows reads 0.87-0.90x (its fixed-dim path improved 1.11x);
suspected branch/layout effects in its axis-picking body, unresolved.

Also: move the relative-velocity Γ layout derivations from stranded TU
comments into the doxygen of their public front ends, exempt NOTE/TODO/
WARNING/FIXME comments from clang-format reflow, and swap a leftover
fmt include for spdlog in the CCD benchmark test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The public front ends deduce their scalar and dimension from the argument
expressions, so their address cannot be taken. Rather than wrap each one in a
lambda, bind the ipc::detail kernel with its scalar named explicitly --
&detail::f<double> -- which is a concrete function and needs no wrapper.

Two cases keep something else:

- point_plane_distance, _gradient and _hessian each have two arities (a plane
  given as origin+normal, or as three triangle vertices). Both remain bound
  under one Python name, disambiguated with py::overload_cast on the parameter
  list.
- edge_length_gradient, point_point_relative_velocity and
  point_edge_relative_velocity keep their lambdas. Their kernels are templated
  on the dimension, so any address-of would pin one; the lambdas take
  VectorMax3d and preserve the runtime 2D/3D dispatch these functions have
  always offered from Python.

Docstrings, argument names and Python-visible signatures are unchanged.

Verified against a freshly built ipctk (the first Python build of this work):
nose2 -s python/tests passes 95/95, and a targeted probe exercises all 40
converted entry points. The repo suite covers none of these functions, so the
probe is what actually checks them: both point_plane arities return the right
shapes (3-vector/3x3 versus 12-vector/12x12), the three retained lambdas still
accept 2D and 3D (shape (4,) versus (6,) from edge_length_gradient), the
runtime-dim jacobians work at dim 2 and 3, and values are correct -- signed
distance -1 where the unsigned distance is +1, edge-edge cross squared norm 16
for perpendicular length-2 edges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Applies the &detail::f<double> treatment to the bindings converted to lambdas
earlier on this branch: line_line, edge_edge and point_triangle. Their kernels
take Eigen::Vector3<T>, so naming the scalar is enough to get a concrete
function and the wrapper is unnecessary. The dtype parameters keep their
py::arg defaults, which bind fine against the kernel's required parameter.

Three groups deliberately keep their lambdas:

- line_line_distance_gradient and line_line_distance_hessian have no detail
  kernel to address. They are single-layer Eigen::MatrixBase templates, which
  is correct for them: they read three coefficients per argument and hand them
  to generated code, so there is no whole-vector operation whose size a second
  layer would need to recover.
- point_point, point_line, point_edge and point_edge_distance_type take
  VectorMax3d and dispatch on the dimension at runtime.
- The normalization_* family, likewise VectorMax3d.

Pinning a dimension in any of those would silently drop 2D from the Python API.

Verified against a rebuilt ipctk: nose2 95/95, the previous 40-point probe
still green, and a new 21-point probe covering the newly converted functions
and the retained lambdas. Both dtype spellings (defaulted and explicit) return
the same value, and every VectorMax3d entry point still answers in 2D and 3D.
Docstrings, argument names and signatures unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Covers the three preceding commits: the two-layer conversion of the tangent,
closest-point, relative-velocity, area, mollifier, point-plane and signed
distance families; the measured speedups and the one unresolved regression;
the header-inline versus translation-unit split and why it is load-bearing;
and the Python bindings moving from lambdas to &detail::f<double>.

Also corrects two claims that the intervening work invalidated: the Highlights
line named only the distance, barrier and normal APIs, and the API Changes
section still advised wrapping in a lambda as the only way to take a function's
address.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.98805% with 64 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.41%. Comparing base (fe12fb1) to head (17aa8e8).

Files with missing lines Patch % Lines
src/ipc/geometry/normal.hpp 86.66% 10 Missing ⚠️
src/ipc/tangent/tangent_basis.hpp 81.48% 10 Missing ⚠️
src/ipc/distance/distance_type.cpp 80.85% 9 Missing ⚠️
src/ipc/tangent/closest_point.hpp 92.22% 7 Missing ⚠️
src/ipc/tangent/relative_velocity.hpp 93.75% 6 Missing ⚠️
src/ipc/distance/edge_edge.cpp 94.62% 5 Missing ⚠️
src/ipc/distance/point_edge.hpp 90.47% 4 Missing ⚠️
src/ipc/distance/point_triangle.cpp 94.93% 4 Missing ⚠️
src/ipc/barrier/barrier.hpp 62.50% 3 Missing ⚠️
src/ipc/distance/distance_type.hpp 84.21% 3 Missing ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #249      +/-   ##
==========================================
- Coverage   96.60%   96.41%   -0.19%     
==========================================
  Files         169      179      +10     
  Lines       16894    16808      -86     
  Branches      963      963              
==========================================
- Hits        16320    16206     -114     
- Misses        574      602      +28     
Flag Coverage Δ
unittests 96.41% <97.98%> (-0.19%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The private 'using T = typename BarrierT::value_type' shadowed the
template parameter of any derived class that also names its parameter T.
MSVC resolved 'NormalizedClampedLogBarrier<T>' inside PhysicalBarrier to
the inherited private typedef and rejected it as inaccessible, while
Clang and GCC resolved it to the derived class's own parameter.
is_eigen_expression_v, are_eigen_expressions_v, and dim_v follow the
standard library's convention for variable templates, which the
UPPER_CASE GlobalConstantCase rule rejects.
.pre-commit-config.yaml pins mirrors-clang-format v21.1.2, but the
format check ran clang-format 20. The two disagree on how to pack the
MatrixMax3 initializers in normal.hpp, so a file formatted by the hook
a contributor actually runs failed CI. The whole tracked tree is
already clean under 21.
@zfergus zfergus changed the title Template the per-collision geometry kernels on scalar and dimension Template the per-collision geometry functions on scalar and dimension Sep 2, 2026
FindSIMD compiles with -march=native, so every cached object file carries
the building runner's ISA. The cache key was runner.os + config with no CPU
component, so objects built on one runner model were restored onto another
and the test step died with SIGILL across unrelated suites (friction,
candidates, CFL, plane-vertex collisions).

The run that populated the cache compiled from scratch in 4m58s and passed;
the next run restored 313 MB, built in 1m22s, and failed in 29s.
@zfergus
zfergus marked this pull request as ready for review September 2, 2026 03:23
@zfergus
zfergus requested a lite review from Copilot September 2, 2026 03:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

There are confirmed build/link hazards from missing/fragile includes and from unconstrained templated wrappers that can instantiate autogen derivatives for unsupported scalar types (leading to link errors).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR refactors IPC’s per-collision geometry kernels (distance / closest-point / tangent-basis / relative-velocity / mollifier / area) into a two-layer design: scalar+dimension-templated implementations under ipc::detail, and public front-ends that deduce scalar/dimension from Eigen expressions (with a runtime size() fallback).

Changes:

  • Introduces scalar- and dimension-templated geometry/distance APIs and updates call sites (C++ + Python bindings) to the new entry points.
  • Refactors barrier types into BarrierBase<T> / Barrier alias and updates barrier usage across code and tests.
  • Updates formatting/tooling config (clang-format version, comment pragmas, clang-tidy naming ignore for _v).
File summaries
File Description
tests/src/tests/distance/test_distance_type.cpp Adds degenerate point-triangle distance-type regression cases.
tests/src/tests/ccd/test_ccd_benchmark.cpp Adjusts includes for logging/formatting in CCD benchmark test.
tests/src/tests/candidates/test_normals.cpp Updates templated normal helper invocation.
tests/src/tests/barrier/test_barrier.cpp Updates barriers to templated barrier types and instantiation syntax.
src/ipc/utils/eigen_ext.hpp Adds float fixed-size aliases and Eigen-expression detection helpers/macros.
src/ipc/tangent/relative_velocity.cpp Removes old implementation source (migrated elsewhere).
src/ipc/tangent/CMakeLists.txt Drops removed relative_velocity.cpp from build sources.
src/ipc/smooth_contact/distance/primitive_distance.tpp Switches smooth-contact primitive distance calls to new distance APIs.
src/ipc/smooth_contact/distance/primitive_distance.cpp Removes obsolete include after distance API reshuffle.
src/ipc/smooth_contact/distance/point_face.hpp Removes obsolete declarations for squared-distance helpers.
src/ipc/smooth_contact/distance/point_face.cpp Removes squared-distance implementations/explicit instantiations now provided elsewhere.
src/ipc/smooth_contact/distance/point_edge.hpp Removes duplicate point-point/point-line sqr-distance helpers; updates includes.
src/ipc/smooth_contact/distance/point_edge.cpp Routes smooth-contact point-edge distance to the new shared distance functions.
src/ipc/smooth_contact/distance/mollifier.tpp Uses new point-line distance for mollifier computations.
src/ipc/smooth_contact/distance/edge_edge.hpp Removes obsolete squared-distance declarations.
src/ipc/smooth_contact/distance/edge_edge.cpp Removes squared-distance implementations and relies on new shared distance functions.
src/ipc/potentials/barrier_potential.hpp Updates default barrier instantiation to templated barrier type.
src/ipc/potentials/barrier_potential.cpp Updates barrier construction to templated barrier type.
src/ipc/geometry/area.hpp Introduces templated detail area/edge-length helpers + expression-deducing front-ends.
src/ipc/geometry/area.cpp Moves triangle-area gradient autogen to templated function + float/double instantiations.
src/ipc/distance/signed/point_plane.hpp Splits into detail templated implementation + expression wrappers.
src/ipc/distance/signed/point_plane.cpp Implements templated signed-distance hessian in detail + float/double instantiations.
src/ipc/distance/signed/point_line.hpp Splits into detail templated implementation + expression wrappers.
src/ipc/distance/signed/point_line.cpp Implements templated signed-distance hessian in detail + float/double instantiations.
src/ipc/distance/signed/line_line.hpp Splits into detail templated implementation + expression wrappers.
src/ipc/distance/signed/line_line.cpp Implements templated signed-distance hessian in detail + float/double instantiations.
src/ipc/distance/point_triangle.hpp Introduces detail templated API + expression wrappers.
src/ipc/distance/point_triangle.cpp Implements templated distance/derivatives with AUTO restrictions and explicit instantiations.
src/ipc/distance/point_point.hpp Inlines point-point distance/derivatives with scalar+dim templated detail layer.
src/ipc/distance/point_point.cpp Removes legacy .cpp implementation (now header-only).
src/ipc/distance/point_plane.hpp Adds templated/autogen-based plane distance/derivatives + wrappers.
src/ipc/distance/point_line.hpp Adds templated/autogen-based line distance/derivatives + wrappers.
src/ipc/distance/point_edge.hpp Adds templated point-edge distance/derivatives + wrappers.
src/ipc/distance/point_edge.cpp Keeps templated hessian implementation and explicit instantiations.
src/ipc/distance/line_line.hpp Adds templated line-line distance + autogen derivative wrappers.
src/ipc/distance/edge_edge.hpp Introduces detail templated API + expression wrappers.
src/ipc/distance/distance_type.hpp Adds detail helpers, wrappers, and error/reporting helpers; templates distance-type APIs.
src/ipc/distance/distance_type.cpp Implements error helpers + templated distance-type kernels and explicit instantiations.
src/ipc/distance/CMakeLists.txt Drops removed point_point.cpp from build sources.
src/ipc/candidates/edge_vertex.cpp Optimizes by branching on dimension and using fixed-size slices.
src/ipc/barrier/barrier.hpp Converts barriers to BarrierBase<T> with templated implementations.
src/ipc/barrier/barrier.cpp Templates barrier functions and adds explicit instantiations for float/double.
python/src/tangent/relative_velocity.cpp Updates bindings to new templated/detail functions and adds lambdas where needed.
python/src/geometry/normal.cpp Updates bindings to templated normal helpers and adds lambdas for deduction cases.
python/src/geometry/area.cpp Updates bindings to new area APIs (detail instantiations / lambdas).
python/src/distance/signed_distance.cpp Updates signed-distance bindings to templated detail functions.
python/src/distance/point_triangle.cpp Updates bindings to templated detail point-triangle distance APIs.
python/src/distance/point_point.cpp Wraps calls in lambdas to keep dynamic-size entry points stable.
python/src/distance/point_plane.cpp Switches overload bindings to templated detail implementations.
python/src/distance/point_line.cpp Wraps calls in lambdas for dynamic-size entry points.
python/src/distance/point_edge.cpp Wraps calls in lambdas for dynamic-size entry points.
python/src/distance/line_line.cpp Updates bindings (detail for value; lambdas for derivative wrappers).
python/src/distance/edge_edge.cpp Updates bindings to templated detail edge-edge distance APIs.
python/src/distance/edge_edge_mollifier.cpp Updates bindings to templated detail mollifier APIs and overload casts.
python/src/distance/distance_type.cpp Updates bindings to templated distance-type functions and lambdas.
python/src/barrier/barrier.cpp Updates bindings for templated barrier classes and free functions.
.github/workflows/clang-format-check.yml Bumps clang-format version used in CI.
.clang-tidy Exempts _v variable templates from global-constant naming rule.
.clang-format Expands comment pragma matching for formatting exceptions.
Review details

Suppressed comments (3)

src/ipc/distance/point_line.hpp:180

  • point_line_distance_hessian() dispatches to autogen::point_line_distance_hessian_*(), which is only explicitly instantiated for float/double in point_line.cpp. Without constraining T, unsupported scalar types will lead to link errors.
    src/ipc/distance/line_line.hpp:96
  • line_line_distance_hessian() relies on autogen::line_line_distance_hessian(), which is only instantiated for float/double in line_line.cpp. Constrain the wrapper to avoid link errors for other scalar types.
    IPC_ASSERT_EIGEN_ARGS(DerivedEA0, DerivedEA1, DerivedEB0, DerivedEB1);
    Eigen::Matrix<typename DerivedEA0::Scalar, 12, 12> hess;
    autogen::line_line_distance_hessian(
        ea0[0], ea0[1], ea0[2], ea1[0], ea1[1], ea1[2], eb0[0], eb0[1], eb0[2],
        eb1[0], eb1[1], eb1[2], hess.data());
    return hess;

src/ipc/distance/point_plane.hpp:206

  • The triangle overload of point_plane_distance_hessian() calls autogen::point_plane_distance_hessian(), which is only explicitly instantiated for float/double in point_plane.cpp. Add a scalar constraint to prevent link errors for other scalar types.
  • Files reviewed: 73/74 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/ipc/distance/line_line.hpp Outdated
Comment thread src/ipc/distance/point_line.hpp Outdated
Comment thread src/ipc/distance/point_plane.hpp
Comment thread src/ipc/geometry/area.hpp
Comment thread tests/src/tests/ccd/test_ccd_benchmark.cpp
zfergus and others added 3 commits September 2, 2026 11:53
…AE-friendly front ends

Push the fixed-size, per-scalar implementations (point-line/triangle/line-line
normals, point/edge/triangle/plane distances, tangent bases, relative
velocities) into ipc::detail, and give each a thin ipc:: front end that takes
Eigen::MatrixBase<Derived> so overload resolution SFINAEs away cleanly instead
of hard-erroring on non-Eigen arguments. Drops the IPC_ASSERT_EIGEN_ARGS macro
and its is_eigen_expression_v/are_eigen_expressions_v traits now that the
MatrixBase parameter does that job directly. Copies the detail-side Doxygen
comments down to the public wrappers so the documented API carries its own
docs instead of pointing into an internal namespace.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Explicitly specify the global templated version of distance functions
@zfergus
zfergus merged commit 5e5ac39 into main Sep 3, 2026
21 checks passed
@zfergus
zfergus deleted the feature/template-distances branch September 3, 2026 00:57
@zfergus zfergus added this to the v2.0.0 milestone Sep 8, 2026
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.

2 participants