Skip to content

Commit 52028df

Browse files
authored
Add Support for Codim. Edge-Point Collision in 3D (#79)
* Add codim_edges to CollisionMesh * Add implementation of CE2CV contacts * Test codim. edge-vertex
1 parent 968331e commit 52028df

File tree

5 files changed

+258
-17
lines changed

5 files changed

+258
-17
lines changed

python/src/collision_mesh.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ void define_collision_mesh(py::module_& m)
6767
.def_property_readonly(
6868
"num_codim_vertices", &CollisionMesh::num_codim_vertices,
6969
"Get the number of codimensional vertices in the collision mesh.")
70+
.def_property_readonly(
71+
"num_codim_edges", &CollisionMesh::num_codim_edges,
72+
"Get the number of codimensional edges in the collision mesh.")
7073
.def_property_readonly(
7174
"num_edges", &CollisionMesh::num_edges,
7275
"Get the number of edges in the collision mesh.")
@@ -90,6 +93,9 @@ void define_collision_mesh(py::module_& m)
9093
.def_property_readonly(
9194
"codim_vertices", &CollisionMesh::codim_vertices,
9295
"Get the indices of codimensional vertices of the collision mesh (#CV x 1).")
96+
.def_property_readonly(
97+
"codim_edges", &CollisionMesh::codim_edges,
98+
"Get the indices of codimensional edges of the collision mesh (#CE x 1).")
9399
.def_property_readonly(
94100
"edges", &CollisionMesh::edges,
95101
"Get the edges of the collision mesh (#E × 2).")
@@ -303,8 +309,8 @@ void define_collision_mesh(py::module_& m)
303309
.def_readwrite(
304310
"can_collide", &CollisionMesh::can_collide,
305311
R"ipc_Qu8mg5v7(
306-
A function that takes two vertex IDs and returns true if the vertices
307-
(and faces or edges containing the vertices) can collide. By default all
308-
primitives can collide with all other primitives.
312+
A function that takes two vertex IDs and returns true if the vertices (and faces or edges containing the vertices) can collide.
313+
314+
By default all primitives can collide with all other primitives.
309315
)ipc_Qu8mg5v7");
310316
}

src/ipc/candidates/candidates.cpp

Lines changed: 123 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <ipc/config.hpp>
77

8+
#include <igl/remove_unreferenced.h>
89
#include <tbb/parallel_for.h>
910
#include <tbb/blocked_range.h>
1011
#include <shared_mutex>
@@ -19,6 +20,21 @@ namespace {
1920
return method != BroadPhaseMethod::SWEEP_AND_TINIEST_QUEUE
2021
&& method != BroadPhaseMethod::SWEEP_AND_TINIEST_QUEUE_GPU;
2122
}
23+
24+
// Pad codim_edges because remove_unreferenced requires a N×3 matrix.
25+
Eigen::MatrixXi pad_edges(const Eigen::MatrixXi& E)
26+
{
27+
assert(E.cols() == 2);
28+
Eigen::MatrixXi E_padded(E.rows(), 3);
29+
E_padded.leftCols(2) = E;
30+
E_padded.col(2) = E.col(1);
31+
return E_padded;
32+
}
33+
34+
Eigen::MatrixXi unpad_edges(const Eigen::MatrixXi& E_padded)
35+
{
36+
return E_padded.leftCols(2);
37+
}
2238
} // namespace
2339

2440
void Candidates::build(
@@ -37,23 +53,67 @@ void Candidates::build(
3753
broad_phase->build(vertices, mesh.edges(), mesh.faces(), inflation_radius);
3854
broad_phase->detect_collision_candidates(dim, *this);
3955

40-
if (mesh.num_codim_vertices()) {
41-
if (!implements_vertex_vertex(broad_phase_method)) {
42-
logger().warn(
43-
"STQ broad phase does not support codim. point-point, skipping.");
44-
return;
45-
}
56+
if (mesh.num_codim_vertices()
57+
&& !implements_vertex_vertex(broad_phase_method)) {
58+
// TODO: Assumes this is the same as implements_edge_vertex
59+
logger().warn(
60+
"STQ broad phase does not support codim. point-point nor point-edge, skipping.");
61+
return;
62+
}
4663

64+
// Codim. vertices to codim. vertices:
65+
if (mesh.num_codim_vertices()) {
4766
broad_phase->clear();
4867
broad_phase->build(
4968
vertices(mesh.codim_vertices(), Eigen::all), //
5069
Eigen::MatrixXi(), Eigen::MatrixXi(), inflation_radius);
70+
5171
broad_phase->detect_vertex_vertex_candidates(vv_candidates);
5272
for (auto& [vi, vj] : vv_candidates) {
5373
vi = mesh.codim_vertices()[vi];
5474
vj = mesh.codim_vertices()[vj];
5575
}
5676
}
77+
78+
// Codim. edges to codim. vertices:
79+
// Only need this in 3D because in 2D, the codim. edges are the same as the
80+
// edges of the boundary. Only need codim. edge to codim. vertex because
81+
// codim. edge to non-codim. vertex is the same as edge-edge or face-vertex.
82+
if (dim == 3 && mesh.num_codim_vertices() && mesh.num_codim_edges()) {
83+
// Extract the vertices of the codim. edges
84+
Eigen::MatrixXd CE_V; // vertices of codim. edges
85+
Eigen::MatrixXi CE; // codim. edges (indices into CEV)
86+
{
87+
Eigen::VectorXi _I, _J; // unused mappings
88+
igl::remove_unreferenced(
89+
vertices,
90+
pad_edges(mesh.edges()(mesh.codim_edges(), Eigen::all)), CE_V,
91+
CE, _I, _J);
92+
CE = unpad_edges(CE);
93+
}
94+
95+
const size_t nCV = mesh.num_codim_vertices();
96+
Eigen::MatrixXd V(nCV + CE_V.rows(), dim);
97+
V.topRows(nCV) = vertices(mesh.codim_vertices(), Eigen::all);
98+
V.bottomRows(CE_V.rows()) = CE_V;
99+
100+
CE.array() += nCV; // Offset indices to account for codim. vertices
101+
102+
// TODO: Can we reuse the broad phase from above?
103+
broad_phase->clear();
104+
broad_phase->can_vertices_collide = [&](size_t vi, size_t vj) {
105+
// Ignore c-edge to c-edge and c-vertex to c-vertex
106+
return ((vi < nCV) ^ (vj < nCV)) && mesh.can_collide(vi, vj);
107+
};
108+
broad_phase->build(V, CE, Eigen::MatrixXi(), inflation_radius);
109+
110+
broad_phase->detect_edge_vertex_candidates(ev_candidates);
111+
for (auto& [ei, vi] : ev_candidates) {
112+
assert(vi < mesh.codim_vertices().size());
113+
ei = mesh.codim_edges()[ei]; // Map back to mesh.edges
114+
vi = mesh.codim_vertices()[vi]; // Map back to vertices
115+
}
116+
}
57117
}
58118

59119
void Candidates::build(
@@ -74,24 +134,74 @@ void Candidates::build(
74134
vertices_t0, vertices_t1, mesh.edges(), mesh.faces(), inflation_radius);
75135
broad_phase->detect_collision_candidates(dim, *this);
76136

77-
if (mesh.num_codim_vertices()) {
78-
if (!implements_vertex_vertex(broad_phase_method)) {
79-
logger().warn(
80-
"STQ broad phase does not support codim. point-point, skipping.");
81-
return;
82-
}
137+
if (mesh.num_codim_vertices()
138+
&& !implements_vertex_vertex(broad_phase_method)) {
139+
// TODO: Assumes this is the same as implements_edge_vertex
140+
logger().warn(
141+
"STQ broad phase does not support codim. point-point nor point-edge, skipping.");
142+
return;
143+
}
83144

145+
// Codim. vertices to codim. vertices:
146+
if (mesh.num_codim_vertices()) {
84147
broad_phase->clear();
85148
broad_phase->build(
86149
vertices_t0(mesh.codim_vertices(), Eigen::all),
87150
vertices_t1(mesh.codim_vertices(), Eigen::all), //
88151
Eigen::MatrixXi(), Eigen::MatrixXi(), inflation_radius);
152+
89153
broad_phase->detect_vertex_vertex_candidates(vv_candidates);
90154
for (auto& [vi, vj] : vv_candidates) {
91155
vi = mesh.codim_vertices()[vi];
92156
vj = mesh.codim_vertices()[vj];
93157
}
94158
}
159+
160+
// Codim. edges to codim. vertices:
161+
// Only need this in 3D because in 2D, the codim. edges are the same as the
162+
// edges of the boundary. Only need codim. edge to codim. vertex because
163+
// codim. edge to non-codim. vertex is the same as edge-edge or face-vertex.
164+
if (dim == 3 && mesh.num_codim_vertices() && mesh.num_codim_edges()) {
165+
// Extract the vertices of the codim. edges
166+
Eigen::MatrixXd CE_V_t0, CE_V_t1; // vertices of codim. edges
167+
Eigen::MatrixXi CE; // codim. edges (indices into CEV)
168+
{
169+
Eigen::VectorXi _I, J;
170+
igl::remove_unreferenced(
171+
vertices_t0,
172+
pad_edges(mesh.edges()(mesh.codim_edges(), Eigen::all)),
173+
CE_V_t0, CE, _I, J);
174+
CE_V_t1 = vertices_t1(J, Eigen::all);
175+
CE = unpad_edges(CE);
176+
}
177+
178+
const size_t nCV = mesh.num_codim_vertices();
179+
180+
Eigen::MatrixXd V_t0(nCV + CE_V_t0.rows(), dim);
181+
V_t0.topRows(nCV) = vertices_t0(mesh.codim_vertices(), Eigen::all);
182+
V_t0.bottomRows(CE_V_t0.rows()) = CE_V_t0;
183+
184+
Eigen::MatrixXd V_t1(nCV + CE_V_t1.rows(), dim);
185+
V_t1.topRows(nCV) = vertices_t1(mesh.codim_vertices(), Eigen::all);
186+
V_t1.bottomRows(CE_V_t1.rows()) = CE_V_t1;
187+
188+
CE.array() += nCV; // Offset indices to account for codim. vertices
189+
190+
// TODO: Can we reuse the broad phase from above?
191+
broad_phase->clear();
192+
broad_phase->can_vertices_collide = [&](size_t vi, size_t vj) {
193+
// Ignore c-edge to c-edge and c-vertex to c-vertex
194+
return ((vi < nCV) ^ (vj < nCV)) && mesh.can_collide(vi, vj);
195+
};
196+
broad_phase->build(V_t0, V_t1, CE, Eigen::MatrixXi(), inflation_radius);
197+
198+
broad_phase->detect_edge_vertex_candidates(ev_candidates);
199+
for (auto& [ei, vi] : ev_candidates) {
200+
assert(vi < mesh.codim_vertices().size());
201+
ei = mesh.codim_edges()[ei]; // Map back to mesh.edges
202+
vi = mesh.codim_vertices()[vi]; // Map back to vertices
203+
}
204+
}
95205
}
96206

97207
bool Candidates::is_step_collision_free(
@@ -110,8 +220,7 @@ bool Candidates::is_step_collision_free(
110220
double toi;
111221
bool is_collision = (*this)[i].ccd(
112222
vertices_t0, vertices_t1, mesh.edges(), mesh.faces(), toi,
113-
min_distance,
114-
/*tmax=*/1.0, tolerance, max_iterations);
223+
min_distance, /*tmax=*/1.0, tolerance, max_iterations);
115224

116225
if (is_collision) {
117226
return false;

src/ipc/collision_mesh.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ CollisionMesh::CollisionMesh(
106106
m_faces_to_edges = construct_faces_to_edges(m_faces, m_edges);
107107

108108
init_codim_vertices();
109+
init_codim_edges();
109110
init_areas();
110111
init_adjacencies();
111112
// Compute these manually if needed.
@@ -134,6 +135,26 @@ void CollisionMesh::init_codim_vertices()
134135
assert(j == m_codim_vertices.size());
135136
}
136137

138+
void CollisionMesh::init_codim_edges()
139+
{
140+
std::vector<bool> is_codim_edge(num_edges(), true);
141+
for (int i : m_faces_to_edges.reshaped()) {
142+
is_codim_edge[i] = false;
143+
}
144+
145+
m_codim_edges.resize(
146+
std::count(is_codim_edge.begin(), is_codim_edge.end(), true));
147+
148+
int j = 0;
149+
for (int i = 0; i < num_edges(); i++) {
150+
if (is_codim_edge[i]) {
151+
assert(j < m_codim_edges.size());
152+
m_codim_edges[j++] = i;
153+
}
154+
}
155+
assert(j == m_codim_edges.size());
156+
}
157+
137158
// ============================================================================
138159

139160
void CollisionMesh::init_selection_matrices(const int dim)

src/ipc/collision_mesh.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ class CollisionMesh {
7272
/// @brief Get the number of codimensional vertices in the collision mesh.
7373
size_t num_codim_vertices() const { return codim_vertices().size(); }
7474

75+
/// @brief Get the number of codimensional edges in the collision mesh.
76+
size_t num_codim_edges() const { return codim_edges().size(); }
77+
7578
/// @brief Get the number of edges in the collision mesh.
7679
size_t num_edges() const { return edges().rows(); }
7780

@@ -96,6 +99,9 @@ class CollisionMesh {
9699
/// @brief Get the indices of codimensional vertices of the collision mesh (#CV x 1).
97100
const Eigen::VectorXi& codim_vertices() const { return m_codim_vertices; }
98101

102+
/// @brief Get the indices of codimensional edges of the collision mesh (#CE x 1).
103+
const Eigen::VectorXi& codim_edges() const { return m_codim_edges; }
104+
99105
/// @brief Get the edges of the collision mesh (#E × 2).
100106
const Eigen::MatrixXi& edges() const { return m_edges; }
101107

@@ -283,6 +289,7 @@ class CollisionMesh {
283289
// Helper initialization functions
284290

285291
void init_codim_vertices();
292+
void init_codim_edges();
286293

287294
/// @brief Initialize the selection matrix from full vertices/DOF to collision vertices/DOF.
288295
void init_selection_matrices(const int dim);
@@ -302,6 +309,8 @@ class CollisionMesh {
302309
Eigen::MatrixXd m_rest_positions;
303310
/// @brief The indices of codimensional vertices (#CV x 1).
304311
Eigen::VectorXi m_codim_vertices;
312+
/// @brief The indices of codimensional edges (#CE x 1).
313+
Eigen::VectorXi m_codim_edges;
305314
/// @brief Edges as rows of indicies into vertices (#E × 2).
306315
Eigen::MatrixXi m_edges;
307316
/// @brief Triangular faces as rows of indicies into vertices (#F × 3).

0 commit comments

Comments
 (0)