Skip to content

Rigid and Affine Body Dynamics - #184

Draft
zfergus wants to merge 103 commits into
mainfrom
dynamics
Draft

zfergus wants to merge 103 commits into
mainfrom
dynamics

Conversation

@zfergus

@zfergus zfergus commented Aug 18, 2025

Copy link
Copy Markdown
Member

Description

This pull request introduces support for rigid body dynamics simulation.

Rigid body dynamics support in Python bindings

  • Added a new dynamics module with subfolder rigid, including Simulator, RigidBodies, Pose, and Poses classes, and exposed them via pybind11 in the Python bindings

Improvements to CMake dependency management

  • Updated CPM versions and refactored third-party recipes to use the OPTIONS argument for configuration, improving clarity and maintainability.
  • Added a new CMake recipe for TinyGLTF and integrated it into the build.

Python usability improvements and new examples

  • Added a new example script demonstrating rigid body simulation and visualization using ipctk, meshio, and polyscope (python/examples/rigid.py).

Minor Python binding improvements

  • Changed save_obj method to write_obj in Candidates Python bindings for clarity.
  • Made CollisionMesh Python class use std::shared_ptr for better memory management.

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

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.30425% with 290 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.73%. Comparing base (1396e7c) to head (f98c7c5).
⚠️ Report is 14 commits behind head on main.

Files with missing lines Patch % Lines
src/ipc/io/write_gltf.cpp 64.90% 93 Missing ⚠️
demo/src/simulator.cpp 94.23% 31 Missing ⚠️
src/ipc/dynamics/rigid/rigid_candidates.cpp 88.43% 31 Missing ⚠️
src/ipc/io/read_gltf.cpp 81.48% 20 Missing ⚠️
src/ipc/dynamics/rigid/rigid_bodies.hpp 69.64% 17 Missing ⚠️
.../ipc/dynamics/time_integration/time_integrator.cpp 77.94% 15 Missing ⚠️
src/ipc/dynamics/rigid/pose.hpp 83.07% 11 Missing ⚠️
demo/src/kinematics.cpp 90.10% 9 Missing ⚠️
src/ipc/candidates/candidates.cpp 0.00% 9 Missing ⚠️
src/ipc/dynamics/affine/joints.cpp 93.89% 8 Missing ⚠️
... and 13 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #184      +/-   ##
==========================================
- Coverage   96.51%   95.73%   -0.78%     
==========================================
  Files         163      207      +44     
  Lines       16651    19649    +2998     
  Branches      920     1265     +345     
==========================================
+ Hits        16070    18811    +2741     
- Misses        581      838     +257     
Flag Coverage Δ
unittests 95.73% <90.30%> (-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 30 commits July 7, 2026 00:42
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Rename JointConstraints::m_U/m_V to m_u/m_v to satisfy the
  readability-identifier-naming lower_case member rule.
- Force cl.exe in the Windows continuous job: the windows-latest image
  now ships MinGW GCC 15.2, which CMake's Ninja generator picks up ahead
  of MSVC and fails to compile oneTBB's ITT profiling.h. VsDevCmd already
  puts cl on PATH; this restores the MSVC toolchain the build expects.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The coverage job built neither the demo simulator nor the glTF I/O, so
their sources reported 0% patch coverage despite having tests
(tests/src/tests/demo and tests/src/tests/io, gated on
IPC_TOOLKIT_BUILD_DEMO / IPC_TOOLKIT_WITH_GLTF). Enable both in the
coverage configure step, instrument the demo target with the coverage
flags, and add demo/ to the workflow's path filter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Finish the Rigid IPC / ABD port on the `dynamics` branch with the remaining
algorithmic features, modernize the incremental potential, and extract the
kinematic driver into its own class.

Objective units:
- Keep the objective uniformly in kg·m²: inertia is unscaled and every other
  term (body forces, orthogonality, barrier, friction, augmented Lagrangian)
  is multiplied by the integrator's (βΔt)² acceleration scaling at summation.
  The library energy terms are now pure, unscaled potentials; the demo
  Simulator applies the scaling. Barrier stiffness κ lives only in the
  BarrierPotential.

Friction (velocity-based, lagged) [Ferguson et al. 2021]:
- Wire TangentialCollisions + FrictionPotential into the demo Simulator.
  Friction is evaluated on velocities recovered from the BDF history (correct
  at order ≥ 2), lagged per solve, with a single outer loop and
  momentum-balance convergence. New settings: friction_coefficient,
  static_friction_speed_bound, friction_iterations.

Static / kinematic bodies (augmented Lagrangian + DOF masking):
- RigidBody gains Type (STATIC/KINEMATIC/DYNAMIC) and per-DOF is_dof_fixed.
- New rigid/affine AugmentedLagrangian energy terms (manual derivatives) drive
  kinematic bodies to per-step targets; DOF masking pins static/fixed/AL-
  satisfied DOFs, composed with the joint reduction.
- Kinematic driving is encapsulated in a demo KinematicDriver owned by the
  Simulator (set_kinematic_driver); RigidBody keeps only its classification.

2D simulation (rigid + affine):
- Dimension-generic mass properties (2×2 second moment; edge/point-cloud
  lumped masses), explicit time-integrator layout, dim-aware kinematics and
  simulator, 2D affine terms and joints (add_hinge throws in 2D).

Codimensional contact:
- Rigid body-pair broad phase emits vertex-vertex (and 3D codim edge-vertex)
  candidates via a new two-tree LBVH query; point clouds and segment nets get
  valid lumped mass and BVHs.

Tests: friction (FD + stick/slip + deceleration), static/kinematic (both
modes), 2D, codim, and augmented-Lagrangian finite-difference checks. Full
suite green (358 cases) + hidden bunny (115 assertions). Python bindings and
demo/README.md updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prep for merging the RBD/ABD inertial and body-force terms behind a single
change of variables.

Stage 0: extract rotation_to_matrix_jacobian/_hessian into pose.{hpp,cpp},
dispatching 2D (so(2) exp map, 4x1) vs 3D (Rodrigues, 9x3 / 9x9), and refactor
Pose::transform_vertices_jacobian/_hessian to use them (behavior-preserving).

Stage 1: move rigid::AffinePose into a new ipc::affine::Pose (affine/pose.{hpp,cpp})
as the shared affine-coordinate representation. Update all references across the
integrator, affine_dof, body_forces, inertial_term, demo kinematics/simulator,
Python bindings (Python-facing name kept as "AffinePose"), and tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stage 2 of unifying the RBD/ABD body terms behind one to-affine map. Nothing is
wired into the simulator yet; the existing terms remain in use.

- ipc::dynamics::ToAffine (RigidToAffine / AffineToAffine): maps the optimization
  DOFs to affine pose coordinates y=[p; vec(A)] and pulls affine-space
  gradients/Hessians back to the DOFs (block-diagonal per body; rotation block
  PSD-projected once). RigidToAffine uses A=Q(θ); AffineToAffine is the identity.

- ipc::dynamics::BodyPotentials: owns the affine-coordinate terms (inertia, body
  forces, SO(dim) orthogonality penalty for ABD, augmented Lagrangian), evaluates
  each at y=to_affine(x), sums with the incremental-potential scaling, and applies
  the to-affine chain rule once. AL is separable via include_al for barrier-
  stiffness seeding.

- Fix a latent 2D bug in affine::BodyForces::update (gravity().head(dim)).

- Tests: test_to_affine (FD chain rule vs a synthetic affine quadratic, 2D+3D,
  rigid+affine) and test_body_potentials (aggregator FD gradient/Hessian).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…old terms

Stage 3 (final) of the merge. The demo simulator now drives the body (non-
contact) physics through a single dynamics::BodyPotentials behind the
parameterization's dynamics::ToAffine map, replacing the parallel rigid/affine
term set.

- Integrator state is affine-shaped [p; vec(Q)] per body in every dimension
  (rot_ndof = dim*dim); the 2D rigid scalar-angle special cases are removed
  (integrator decode, state init, ω extraction). The rigid optimization DOF
  stays reduced [p; θ]; only the state/affine coords/AL use the matrix form.

- Kinematics delegates poses()/to_integrator_state()/from_integrator_state() to
  a held ToAffine (single source of the θ→A map); candidates/CCD and the
  vertex-space maps stay in Kinematics.

- Simulator: the five term optionals + two AL optionals collapse to one
  BodyPotentials. energy/gradient/full_hessian drop their is_affine branches
  (inertia + body forces + orthogonality + AL, chain rule applied once); barrier
  and friction keep the vertex-space chain rule. AL is separable via include_al
  for barrier-stiffness seeding; DOF pinning reads the merged AL's satisfied
  flags. (βΔt)² scaling is unchanged. BodyPotentials gains AL params + satisfied
  accessors.

- Delete rigid::{InertialTerm, BodyForces, AugmentedLagrangian} and
  RigidPotential; their behavior is now the affine term + ToAffine pullback. The
  affine terms are the merged implementations and are retained.

- Tests: drop the rigid-term unit tests (covered by [to_affine]/[body_potentials]);
  retarget the AL test to the affine AL; drop the tautological rigid-vs-affine
  inertial cross-check. Re-baseline the 2D ballistic-rotation angle tolerance:
  2D rotation now integrates in matrix space (as in 3D), differing from the
  linear-in-angle value by O((Δtω)³).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document the merged formulation (body terms in affine coordinates aggregated by
dynamics::BodyPotentials behind the dynamics::ToAffine map) and refresh the
architecture, augmented-Lagrangian, and tests sections. Add a "Rigid and affine
dynamics" section spelling out the main difference from Rigid IPC: rigid bodies
optimize [p; θ] but integrate the rotation in matrix space (as in ABD), versus
Rigid IPC's rotation-vector dynamics throughout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/Wall enables off-by-default warnings (C4711/C4710/C4514/C4820/C5045)
that flooded CI with ~80k messages. /W4 is the MSVC equivalent of
-Wall -Wextra.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
M_PI is not defined by <cmath> on MSVC unless _USE_MATH_DEFINES is set
before the include, which broke the CI build. Switch to igl::PI (the
convention already used throughout the test suite) and include <igl/PI.h>.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pose::rotation_vector() asserted A.isUnitary(1e-9), a tolerance carried
over from the rigid case where A = exp(θ) is orthogonal to machine
precision. For affine bodies A is only softly held in SO(dim) by the
orthogonality potential and drifts ~1e-5 from unitary, so the assert
fired in debug builds (release compiles it out), aborting 8 simulator
tests. Relax to isUnitary(1e-3): comfortably above the observed ~2.8e-5
worst-case drift while still catching genuine non-rotations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hook overrode the mirror's default entry (clang-format -i) with a
bare `clang-format` and no -i flag, so clang-format printed to stdout,
left files unchanged, and always exited 0 -- the hook silently passed on
everything and never reformatted or blocked a commit.

Drop the redundant name/description/entry overrides and add -i to args
so
the hook reformats staged files in place.
Add MSVC /wd flags to cmake/ipc_toolkit/ipc_toolkit_warnings.cmake to silence warnings that don't have GCC/Clang equivalents (e.g. /wd4244, /wd4267, /wd4250, /wd4201, /wd4068, /wd4100), keeping MSVC output consistent with other compilers.

In src/ipc/math/math.cpp switch Eigen::JacobiSVD to take compute flags as a template parameter and add a clarifying comment (ComputeFullUV). This matches Eigen's preferred compile-time option and avoids runtime-deprecation/behavior issues.
Remove experimental /experimental:external and /external:W1 compiler flags for MSVC. These flags were used to suppress warnings from external files but are no longer needed.
- rigid.py: apply the original Rigid IPC 04-bolt.json fixture settings
  (dt=0.01, dhat=1e-4 < thread clearance of 1.69e-4, static nut, solver
  iteration budget, continue on non-convergence)
- rigid.py: disable contact area weighting (Rigid IPC parity). The barrier
  stiffness initialization's minimum-stiffness fallback (used whenever a
  step starts with no active contacts) does not account for the Voronoi
  area weights (~1e-6 m^2 on the fine thread mesh), leaving the barrier
  orders of magnitude too soft: the screw wedged nanometers into the
  threads and Newton ground to the iteration limit
- demo: use the IPC collision set type when area weighting is off (the
  improved max approximator assumes area weights)
- demo: log the actual (non-squared) minimum distance at stiffness init
- python: RigidBodies.__getitem__ returns a live reference; add
  vertex_to_body

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eCCD

Replace the direct Tight-Inclusion calls in the piecewise-linear driver
with calls through a stored NarrowPhaseCCD (set_narrow_phase_ccd; default
TightInclusionCCD). A new NarrowPhaseCCD::component_mode flag, set by
NonlinearCCD, tells the narrow phase it is a component of the piecewise
driver, which supplies its own conservative margins:

- TightInclusionCCD bypasses its standalone ccd_strategy (no minimum-
  separation inflation, small-TOI rerun, or rescaling) and forwards the
  driver's per-piece cm_no_zero_toi hint (true on the first piece), a new
  trailing parameter on the NarrowPhaseCCD interface
- AdditiveCCD uses COMPONENT_CONSERVATIVE_RESCALING = 0.99 (a raw TOI
  never terminates since its advancement gap is (1-cr) x initial gap),
  honors cm_no_zero_toi by halving the minimum separation until the
  initial distance clears it, and logs expected within-separation
  returns at trace level
- NonlinearCCD's tolerance/max_iterations move to the TightInclusionCCD
  component; conservative_rescaling stays (used by the driver and the
  plane-vertex CCD)
- demo: rigid mode uses AdditiveCCD per piece (bolt scene: 0.50 s vs
  0.65 s with Tight Inclusion vs 8.45 s original Rigid IPC, 20 steps;
  identical trajectories, intersection-free)
- python: NarrowPhaseCCD hierarchy switches to shared_ptr holders;
  NonlinearCCD.narrow_phase_ccd is a property whose setter enforces
  component_mode

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add IPC_TOOLKIT_BUILD_DEMO using main's unforced option pattern
- Keep the dynamics/demo Python bindings alongside the deprecated
  GCP-name __getattr__
- Keep AdditiveCCD::handle_initial_proximity with the templated
  additive_ccd
- Take main's MSVC warning variable and newer tests-data commit
- Drop unordered_map_and_set.cpp (header-only on main) and save_obj
  (replaced by write_candidates_obj on dynamics)

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

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