feat(constitution): EmbeddedCollisionMesh — barycentric coupling of passive surface mesh onto FEM tet mesh#461
Conversation
…of passive surface mesh onto FEM tet mesh Adds a new constitution that drives a dense passive surface mesh from a coarse FEM tet simulation via barycentric interpolation (forward pass) and scatters contact gradient back to tet DOFs (backward pass). CPU side (constitution/): - EmbeddedCollisionMesh::apply_to() — point-in-tet query, writes ecm_tet_index / ecm_bary / ecm_driven attributes on the surface mesh CUDA backend (finite_element/constraints/): - EmbeddedCollisionMeshVertexReporter — registers surface verts in GlobalVertexManager - EmbeddedCollisionMeshSystem — forward kernel (x_surface = J * x_tet) and backward kernel (grad_tet += Jt * grad_surface) via atomicAdd - EmbeddedCollisionMeshForceReporter — injects tet_extra_grad into FEM linear system Engine hooks (two new SimSystem events): - on_before_collision_detection / event_before_collision_detection — forward pass - on_after_contact_assembly / event_after_contact_assembly — backward pass Reference: SOFA BarycentricMapping (apply / applyJT) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
edde6c6 to
f13acd9
Compare
There was a problem hiding this comment.
Code Review
This pull request introduces the EmbeddedCollisionMesh system, which implements barycentric coupling to drive a dense surface mesh using a coarse FEM tetrahedral mesh. The implementation includes CPU-side logic for calculating initial embedding coordinates and a CUDA backend for forward position updates and backward force scattering. Feedback highlights critical issues, including a missing attribute (ecm_tet_geo_id) required for backend initialization and a mismatch between local and global tetrahedron indices. Other concerns involve the current limitation to a single embedded mesh per scene, the use of const_cast to bypass standard vertex reporting flows, and potential performance bottlenecks due to the brute-force point-in-tet search. A code suggestion was also provided to add a static_assert ensuring proper memory alignment for atomic operations.
| auto tet_id_attr = sc->meta().find<IndexT>("ecm_tet_geo_id"); | ||
| if(!tet_id_attr) |
There was a problem hiding this comment.
The backend searches for the ecm_tet_geo_id attribute to identify the driving tet mesh, but this attribute is never created or populated in the EmbeddedCollisionMesh::apply_to implementation in embedded_collision_mesh.cpp. This will cause the initialization to fail and the system to remain inactive because tet_sc will always be null.
|
|
||
| if(is_inside(bary)) | ||
| { | ||
| tet_index_view[si] = (IndexT)ti; |
There was a problem hiding this comment.
The tet_index_view stores the local index of the tetrahedron within tet_sc. However, the GPU backend in embedded_collision_mesh.cu uses this index to access fem.tets(), which is a global buffer containing all tetrahedra in the simulation. If tet_sc is not the first tet mesh in the scene, these indices will point to the wrong tetrahedra. The backend needs to account for the global tetrahedron offset of the driving mesh.
| tet_sc = geo_slots[tet_gi] | ||
| ->geometry() | ||
| .as<geometry::SimplicialComplex>(); | ||
| break; |
There was a problem hiding this comment.
The initialization logic and the EmbeddedCollisionMeshData structure are currently limited to a single embedded mesh per scene. The loop breaks after finding the first geometry with the ecm_driven attribute, and the GPU buffers are not managed as a collection. This prevents the simulation of multiple independent objects using embedded collision meshes.
|
|
||
| // Write directly into the GlobalVertexManager positions buffer at the | ||
| // surface mesh offset — this is what the CCD and contact systems see. | ||
| Vector3* surf_ptr = const_cast<Vector3*>(all_pos.data()) + d.surface_vertex_offset; |
There was a problem hiding this comment.
Using const_cast to write directly into the GlobalVertexManager positions buffer bypasses the intended reporter-based update flow. While this avoids a copy, it makes the system highly dependent on the internal implementation of the vertex manager and could lead to synchronization issues if the manager expects to control all writes to its buffers.
| coo_count, | ||
| (IndexT)d.surface_vertex_offset, | ||
| (int)d.n_surface_verts, | ||
| reinterpret_cast<Float*>(d.surface_grad_dense.data())); |
There was a problem hiding this comment.
Reinterpreting Vector3* as Float* for component-wise atomicAdd assumes that the Vector3 type (typically an Eigen type) has no internal padding or alignment overhead. A static_assert should be added to ensure this holds true across different platforms and Eigen configurations.
static_assert(sizeof(Vector3) == 3 * sizeof(Float), "Vector3 must not have padding for component-wise atomicAdd");
reinterpret_cast<Float*>(d.surface_grad_dense.data()));
| // Brute-force O(n_surface * n_tets) — fine at init. | ||
| // Replace with BVH if tet count > ~10k. |
There was a problem hiding this comment.
The
Motivation
When simulating characters or soft bodies, the FEM tet mesh is typically coarse for performance, but collision detection requires a dense, smooth surface. This contribution adds
EmbeddedCollisionMesh, a constitution that drives a dense passive surface mesh from a coarse FEM tet simulation via barycentric interpolation — the same approach used in SOFA'sBarycentricMapping.What this adds
CPU side —
constitution/EmbeddedCollisionMesh::apply_to(tet_sc, surface_sc)— point-in-tet query (brute-force, fine at init), writes three attributes on the surface mesh:ecm_tet_index(IndexT per vertex) — containing tet indexecm_bary(Vector4 per vertex) — barycentric coordinatesecm_driven(IndexT meta, value=1) — marks the mesh as drivenCUDA backend —
finite_element/constraints/Three systems registered as
SimSystem:EmbeddedCollisionMeshVertexReporterGlobalVertexManager(after ABD/FEM/HalfPlane)EmbeddedCollisionMeshSystemx_surface = J * x_tet) and backward pass (grad_tet += Jᵀ * grad_surface)EmbeddedCollisionMeshForceReportertet_extra_gradinto the FEM linear system viaFEMLinearSubsystemReporterEngine hooks — two new
SimSystemeventsThese are general-purpose hooks placed around collision detection in
advance_ipc.cu, useful for any future driven/passive body system.Usage
Reference
BarycentricMapping—apply()/applyJT()