Skip to content

Rigid and Affine Body Dynamics - #184

Open
zfergus wants to merge 157 commits into
mainfrom
dynamics
Open

zfergus wants to merge 157 commits into
mainfrom
dynamics

Conversation

@zfergus

@zfergus zfergus commented Aug 18, 2025 •

Copy link
Copy Markdown
Member

Description

Adds rigid and affine body dynamics as reusable library components (src/ipc/dynamics/), glTF scene I/O (src/ipc/io/), and a demo simulator (demo/) that assembles them.

This PR used to carry the CCD, LBVH, math, logging, and docs changes that the simulator depends on. Those were split out and have all merged, so what remains here is the dynamics work itself:

  • #290: NonlinearCCD can delegate to any NarrowPhaseCCD (NarrowPhaseCCD::Mode).
  • #291: ipc::PI, sinc, and nearest_rotation.
  • #292: candidate OBJ writing moved into the io module.
  • #293: two-tree LBVH queries against another set's leaves.
  • #294: the duplicate logger name across shared libraries.
  • #295 and #296: docs table styling and the example notebook move.

Mechanism

  • ipc::rigid — Pose, RigidBody, and RigidBodies (a CollisionMesh of all bodies, built from per-body meshes). Each body is re-centered on its center of mass and expressed in its principal frame. This follows Rigid IPC (Ferguson et al. 2021).
  • ipc::rigid::RigidCandidates — finds body pairs with a bounding-sphere broad phase, then queries each pair with the two-tree LBVH queries from Query an LBVH against another set's leaves #293 in one body's rest frame. Narrow-phase CCD follows the curved rigid trajectories through NonlinearCCD (Let NonlinearCCD delegate to any NarrowPhaseCCD #290), with a conservative bound on how far each piece strays from its chord.
  • ipc::affine — the body terms are written once in affine coordinates y = [p; vec(A)]: InertialTerm, BodyForces, OrthogonalityPotential, an AugmentedLagrangian that drives kinematic bodies, and linear JointConstraints. This follows ABD (Lan et al. 2022), with joints as in Chen et al. 2022, §4.1.
  • ipc::dynamics — a ToAffine change of variables lets the same terms serve both models. RigidToAffine maps [p; θ] to A = R(θ); AffineToAffine is the identity. BodyPotentials sums the terms and applies the chain rule once, and BDF provides orders 1–6.
  • demo/ (ipc_toolkit_demo, not part of the library) — a polysolve-based Simulator with adaptive barrier stiffness, lagged friction, and static/kinematic bodies. It is exposed in Python as ipctk.demo.

Impact

  • Downstream simulators can build rigid or affine body dynamics from library parts. The core library still depends on no solver; only the demo links polysolve.
  • The demo uses AdditiveCCD as the per-piece linear CCD inside NonlinearCCD.
  • Scenes load from and animations save to glTF when IPC_TOOLKIT_WITH_GLTF=ON.
  • Top-level builds now build the demo by default, which fetches polysolve. Projects that include the toolkit as a subproject are unaffected.

API changes

  • New CMake options: IPC_TOOLKIT_BUILD_DEMO (ON at top level, OFF as a subproject) and IPC_TOOLKIT_WITH_GLTF (OFF).
  • CollisionMesh: the destructor and vertices(full_positions) are now virtual, so RigidBodies can map poses to vertices. This is source compatible, but CollisionMesh now has a vtable, so it is not binary compatible with earlier builds.
  • Python: bindings for RigidBodies, RigidBody, Pose/Poses, AffinePose, JointConstraints, and read_gltf/write_gltf, plus the demo simulator as ipctk.demo.

💥 Breaking changes

None. The breaking changes this work originally needed (the NarrowPhaseCCD mode parameter, the NonlinearCCD constructor, and the save_obj rename) shipped in #290 and #292.

New dependencies

  • polysolve (MIT), fetched with CPM at a pinned commit.
    • Linked only by ipc_toolkit_demo, never by ipc_toolkit.
    • Its optional backends (CHOLMOD, MKL, AMGCL, …) are disabled, so it uses only Eigen's solvers and adds no transitive system dependencies.
  • TinyGLTF 2.9.7 (MIT), fetched with CPM.
    • Compiled as one translation unit and linked PRIVATE.
    • Fetched only when IPC_TOOLKIT_WITH_GLTF=ON.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which improves existing functionality)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

  • ipc_toolkit_tests "~[!benchmark]": all 518 test cases pass (last full local run, before the split PRs landed; CI covers the merged state). This includes 86 new cases in tests/src/tests/{dynamics,demo,io}:
    • Finite-difference checks of every body term, the ToAffine chain rule, and rigid pose derivatives.
    • Analytic checks of mass properties, BDF exactness and derivatives, and external torques applied at arbitrary orientations.
    • Rigid candidates and CCD along curved trajectories.
    • glTF round-trips, including node hierarchies, multi-primitive meshes, and faceless bodies.
    • Simulator scenes: free flight, friction stick/slip and deceleration, static and kinematic bodies, joints, 2D, and codimensional contact.
  • python -m nose2 in python/tests: all 106 tests pass.

Test Configuration:

  • OS and Version: macOS 26 (Apple Silicon)
  • Compiler and Version: AppleClang 21.0, Release, Python 3.13

Benchmark: rotation vector → rotation matrix

The new analytic rotation_vector_to_matrix (plus _jacobian and _hessian) compared against the autodiff pose construction in Rigid IPC (dfff467b). Catch2 mean time per call, averaged over three runs of each benchmark:

Quantity Rigid IPC (autodiff) IPC Toolkit (analytic) Speedup
R 49.8 ns 19.3 ns 2.6×
∇R 1.16 µs 127 ns 9.1×
∇²R 16.1 µs 333 ns 48×

Every measurement stayed within about ±3% of its mean across the three runs.

These are not identical workloads, so read the Jacobian and Hessian rows as "what each simulator pays to get these derivatives" rather than a pure algorithmic comparison:

  • Rigid IPC differentiates with respect to all 12 pose DOFs ([p; θ]) using dynamically sized autodiff types. The new code differentiates only with respect to the 3 rotation parameters θ, since the rotation does not depend on the position.
  • Rigid IPC benchmarks the fixed rotation θ = (0, π, 0). The new benchmark uses a random θ.

To reproduce (Release, AppleClang 21, -O3, Apple Silicon):

ipc_toolkit_tests "Benchmark so(3) -> SO(3)"      # build/release/tests
rigid_ipc_tests "∇²(SE(3) ↦ SO(3))"               # rigid-ipc/build/tests

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 17 commits May 31, 2024 00:41
- Added InertialTerm class for computing energy, gradient, and Hessian of rigid bodies.
- Implemented mass properties computation for 2D and 3D meshes, including point clouds.
- Created pose representation with rotation vector and matrix conversions.
- Developed RigidBodies class to manage multiple rigid bodies from mesh data.
- Implemented ImplicitEuler time integrator for updating positions and velocities.
- Added unit tests for mass properties and pose transformations using Catch2 framework.
- Updated finite-diff package from version 1.0.1 to 1.0.3 in CMake configuration.
- Remove template from Pose.
- Modified RigidBody and related classes to use the updated Pose structure.
- Added unit tests for RigidBody construction and transformations.
- Update CPM to 0.42.0
- Add TinyGLTF dependency
- Rename save_obj to write_candidates_obj
- Add write_gltf
@zfergus zfergus added the enhancement New feature or request label Aug 18, 2025
@zfergus zfergus added this to the v1.5.0 milestone Aug 18, 2025
@codecov

codecov Bot commented Aug 18, 2025 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.74680% with 311 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.09%. Comparing base (3dd025f) to head (e947d37).

Files with missing lines Patch % Lines
src/ipc/io/write_gltf.cpp 65.84% 97 Missing ⚠️
src/ipc/io/read_gltf.cpp 72.52% 61 Missing ⚠️
demo/src/simulator.cpp 94.35% 35 Missing ⚠️
src/ipc/dynamics/rigid/rigid_candidates.cpp 87.67% 35 Missing ⚠️
.../ipc/dynamics/time_integration/time_integrator.cpp 77.94% 15 Missing ⚠️
demo/src/kinematics.cpp 85.86% 13 Missing ⚠️
src/ipc/dynamics/affine/joints.cpp 93.71% 11 Missing ⚠️
src/ipc/dynamics/rigid/mass.cpp 93.67% 10 Missing ⚠️
src/ipc/dynamics/affine/affine_dof.cpp 82.92% 7 Missing ⚠️
src/ipc/dynamics/affine/augmented_lagrangian.cpp 95.95% 7 Missing ⚠️
... and 7 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #184      +/-   ##
==========================================
- Coverage   96.86%   96.09%   -0.78%     
==========================================
  Files         229      270      +41     
  Lines       23047    26412    +3365     
  Branches     1619     2039     +420     
==========================================
+ Hits        22325    25381    +3056     
- Misses        722     1031     +309     
Flag Coverage Δ
unittests 96.09% <90.74%> (-0.78%) ⬇️

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.

- Introduce new overloads for build methods across various classes, allowing for the use of precomputed AABBs.
- Update related documentation and ensure consistent handling of vertex boxes and edges.
- Improve voxel size suggestion logic based on bounding box dimensions.
zfergus and others added 29 commits November 3, 2026 00:42
# Conflicts:
#	python/src/candidates/plane_vertex.cpp
#	src/ipc/broad_phase/lbvh.hpp
#	src/ipc/candidates/plane_vertex.hpp
#	src/ipc/utils/save_obj.hpp

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ipc_toolkit defines it publicly when SIMD is on, and the demo Simulator
passes Eigen objects across the polysolve boundary. Match the setting
on polysolve and polysolve_linear, as done for finite-diff and
MeshFEMSparse, so both sides agree on alignment and layout.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
# Conflicts:
#	.gitignore
v3 is a rewrite that ships tiny_gltf_v3.h/.c and no tiny_gltf.h or
tiny_gltf.cc, so the recipe failed at configure on every platform.
read_gltf and write_gltf also use the v2 API, so moving to v3 needs a
port first.

This reverts commit 5bf9f24.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GENERATE expands to a lambda with no captures, and MSVC cannot capture
a local constexpr implicitly.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
# Conflicts:
#	.gitattributes
#	src/ipc/broad_phase/lbvh.cpp
#	src/ipc/broad_phase/lbvh.hpp
# Conflicts:
#	docs/source/cpp-api/ccd.rst
#	docs/source/python-api/ccd.rst
#	python/src/ccd/additive_ccd.cpp
#	python/src/ccd/narrow_phase_ccd.cpp
#	python/src/ccd/nonlinear_ccd.cpp
#	python/tests/test_ccd.py
#	src/ipc/ccd/additive_ccd.cpp
#	src/ipc/ccd/additive_ccd.hpp
#	src/ipc/ccd/inexact_ccd.cpp
#	src/ipc/ccd/inexact_ccd.hpp
#	src/ipc/ccd/narrow_phase_ccd.hpp
#	src/ipc/ccd/nonlinear_ccd.cpp
#	src/ipc/ccd/nonlinear_ccd.hpp
#	src/ipc/ccd/tight_inclusion_ccd.cpp
#	src/ipc/ccd/tight_inclusion_ccd.hpp
#	src/ipc/io/CMakeLists.txt
#	tests/src/tests/ccd/test_nonlinear_ccd.cpp
# Conflicts:
#	src/ipc/math/math.cpp
#	src/ipc/math/math.hpp
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The demo simulator tests now share the runner with the slow barrier
potential tests, which can push one past ctest's default 1500 s.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Add C++ pages for rigid bodies, affine bodies, dynamics, and I/O
- Add Python pages for rigid bodies, affine bodies, I/O, and the demo
- Build the docs with glTF enabled so the I/O pages render
- Drop explicit entries for nested enums that their class already lists
- Walk through dropping a cube with the demo simulator in both models
- Pull the C++ and Python snippets from new tests so they stay working
PolySolve now inherits it from the Eigen target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
These files had no trailing newline on main, so adding one only
creates diff noise.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Wrap the compiled bindings, now ipctk._ipctk, in a Python package
- Move rigid.py to ipctk.demo.rigid (python -m or ipctk-rigid)
- Report the installed package version in __version__

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zfergus zfergus mentioned this pull request Nov 10, 2026
5 of 7 tasks

This branch has not been deployed

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant