PolyFEM
Loading...
Searching...
No Matches
CollisionProxy.cpp
Go to the documentation of this file.
1#include "CollisionProxy.hpp"
2
8
9#include <SimpleBVH/BVH.hpp>
10#include <igl/edges.h>
11#include <igl/barycentric_coordinates.h>
12#include <h5pp/h5pp.h>
13// #include <fcpw/fcpw.h>
14
15namespace polyfem::mesh
16{
17 namespace
18 {
19 template <typename T>
20 using RowMajorMatrixX = Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
21
26 Eigen::MatrixXd uv_to_uvw(const Eigen::MatrixXd &uv, const int local_fid)
27 {
28 assert(uv.cols() == 2);
29
30 Eigen::MatrixXd uvw = Eigen::MatrixXd::Zero(uv.rows(), 3);
31 // u * A + v * B + w * C + (1 - u - v - w) * D
32 switch (local_fid)
33 {
34 case 0:
35 uvw.col(0) = uv.col(1);
36 uvw.col(1) = uv.col(0);
37 break;
38 case 1:
39 uvw.col(0) = uv.col(0);
40 uvw.col(2) = uv.col(1);
41 break;
42 case 2:
43 uvw.leftCols(2) = uv;
44 uvw.col(2) = 1 - uv.col(0).array() - uv.col(1).array();
45 break;
46 case 3:
47 uvw.col(1) = uv.col(1);
48 uvw.col(2) = uv.col(0);
49 break;
50 default:
51 log_and_throw_error("build_collision_proxy(): unknown local_fid={}", local_fid);
52 }
53 return uvw;
54 }
55
56 Eigen::MatrixXd extract_face_vertices(
57 const basis::ElementBases &element, const int local_fid)
58 {
59 Eigen::MatrixXd UV(3, 2);
60 // u * A + b * B + (1-u-v) * C
61 UV.row(0) << 1, 0;
62 UV.row(1) << 0, 1;
63 UV.row(2) << 0, 0;
64 const Eigen::MatrixXd UVW = uv_to_uvw(UV, local_fid);
65
66 Eigen::MatrixXd V;
67 element.eval_geom_mapping(UVW, V);
68
69 return V;
70 }
71 } // namespace
72
74 const std::vector<basis::ElementBases> &bases,
75 const std::vector<basis::ElementBases> &geom_bases,
76 const std::vector<LocalBoundary> &total_local_boundary,
77 const int n_bases,
78 const int dim,
79 const double max_edge_length,
80 Eigen::MatrixXd &proxy_vertices,
81 Eigen::MatrixXi &proxy_faces,
82 std::vector<Eigen::Triplet<double>> &displacement_map_entries,
83 const CollisionProxyTessellation tessellation)
84 {
85 // for each boundary element (f):
86 // tessilate f with triangles of max edge length (fₜ)
87 // for each node (x) of fₜ with global index (i):
88 // for each basis (ϕⱼ) in f's parent element:
89 // set Vᵢ = x where Vᵢ is the i-th proxy vertex
90 // set W(i, j) = ϕⱼ(g⁻¹(x)) where g is the geometry mapping of f
91 // caveats:
92 // • if x is provided in parametric coordinates, we can skip evaluating g⁻¹
93 // - Vᵢ = g(x) instead
94 // • the tessellations of all faces need to be stitched together
95 // - this means duplicate weights should be removed
96
97 std::vector<double> proxy_vertices_list;
98 std::vector<int> proxy_faces_list;
99 std::vector<Eigen::Triplet<double>> displacement_map_entries_tmp;
100
101 Eigen::MatrixXd UV;
102 Eigen::MatrixXi F_local;
103 if (tessellation == CollisionProxyTessellation::REGULAR)
104 {
105 // TODO: use max_edge_length to determine the tessellation
107 }
108
109 for (const LocalBoundary &local_boundary : total_local_boundary)
110 {
111 if (local_boundary.type() != BoundaryType::TRI)
112 log_and_throw_error("build_collision_proxy() is only implemented for tetrahedra!");
113
114 const basis::ElementBases elm = bases[local_boundary.element_id()];
115 const basis::ElementBases g = geom_bases[local_boundary.element_id()];
116 for (int fi = 0; fi < local_boundary.size(); fi++)
117 {
118 const int local_fid = local_boundary.local_primitive_id(fi);
119
120 if (tessellation == CollisionProxyTessellation::IRREGULAR)
121 {
122 // Use the shape of f to determine the tessellation
123 const Eigen::MatrixXd node_positions = extract_face_vertices(g, local_fid);
125 node_positions.row(0), node_positions.row(1), node_positions.row(2),
126 max_edge_length, UV, F_local);
127 }
128
129 // Convert UV to appropirate UVW based on the local face id
130 Eigen::MatrixXd UVW = uv_to_uvw(UV, local_fid);
131
132 Eigen::MatrixXd V_local;
133 g.eval_geom_mapping(UVW, V_local);
134 assert(V_local.rows() == UV.rows());
135
136 const int offset = proxy_vertices_list.size() / dim;
137 for (const double x : V_local.reshaped<Eigen::RowMajor>())
138 proxy_vertices_list.push_back(x);
139 for (const int i : F_local.reshaped<Eigen::RowMajor>())
140 proxy_faces_list.push_back(i + offset);
141
142 for (const basis::Basis &basis : elm.bases)
143 {
144 assert(basis.global().size() == 1);
145 const int basis_id = basis.global()[0].index;
146
147 const Eigen::MatrixXd basis_values = basis(UVW);
148
149 for (int i = 0; i < basis_values.size(); i++)
150 {
151 displacement_map_entries_tmp.emplace_back(
152 offset + i, basis_id, basis_values(i));
153 }
154 }
155 }
156 }
157
158 // stitch collision proxy together
160 Eigen::Map<RowMajorMatrixX<double>>(proxy_vertices_list.data(), proxy_vertices_list.size() / dim, dim),
161 Eigen::Map<RowMajorMatrixX<int>>(proxy_faces_list.data(), proxy_faces_list.size() / dim, dim),
162 displacement_map_entries_tmp,
163 proxy_vertices, proxy_faces, displacement_map_entries);
164 }
165
166 // ========================================================================
167
169 const std::vector<basis::ElementBases> &bases,
170 const std::vector<basis::ElementBases> &geom_bases,
171 const std::vector<mesh::LocalBoundary> &total_local_boundary,
172 const int n_bases,
173 const int dim,
174 const Eigen::MatrixXd &proxy_vertices,
175 std::vector<Eigen::Triplet<double>> &displacement_map_entries)
176 {
177 // for each vᵢ in proxy_vertices:
178 // find closest element (t)
179 // compute v̂ᵢ = g⁻¹(v) where g is the geometry mapping of t
180 // for each basis (ϕⱼ) in t:
181 // set W(i, j) = ϕⱼ(v̂ᵢ)
182
183 // NOTE: this is only implemented for P1 geometry
184 for (const basis::ElementBases &element_bases : geom_bases)
185 {
186 for (const basis::Basis &basis : element_bases.bases)
187 {
188 if (basis.order() != 1)
189 log_and_throw_error("build_collision_proxy_displacement_map() is only implemented for P1 geometry!");
190 }
191 }
192
193 // --------------------------------------------------------------------
194 // build a BVH to find which element each proxy vertex belongs to
195 std::vector<std::array<Eigen::Vector3d, 2>> boxes(
196 geom_bases.size(), {{Eigen::Vector3d::Zero(), Eigen::Vector3d::Zero()}});
197 for (int i = 0; i < geom_bases.size(); i++)
198 {
199 const Eigen::MatrixXd nodes = geom_bases[i].nodes();
200 boxes[i][0].head(dim) = nodes.colwise().minCoeff();
201 boxes[i][1].head(dim) = nodes.colwise().maxCoeff();
202 }
203
204 SimpleBVH::BVH bvh;
205 bvh.init(boxes);
206
207 // --------------------------------------------------------------------
208 // build wide BVH to find closest point on the surface
209
210 // assert(dim == 3);
211 // fcpw::Scene<3> scene;
212 // {
213 // std::vector<Eigen::Vector3d> vertices;
214 // vertices.reserve(geom_bases.size() * 4);
215 // std::vector<std::array<int, 3>> faces;
216 // faces.reserve(geom_bases.size() * 4);
217 // for (const basis::ElementBases &elm : geom_bases)
218 // {
219 // const int offset = vertices.size();
220 // for (int i = 0; i < 4; i++)
221 // vertices.push_back(elm.bases[i].global()[0].node);
222 // faces.push_back({{offset + 0, offset + 1, offset + 2}});
223 // faces.push_back({{offset + 0, offset + 1, offset + 3}});
224 // faces.push_back({{offset + 0, offset + 2, offset + 3}});
225 // faces.push_back({{offset + 1, offset + 2, offset + 3}});
226 // }
227
228 // scene.setObjectTypes({{fcpw::PrimitiveType::Triangle}});
229 // scene.setObjectVertexCount(vertices.size(), /*object_id=*/0);
230 // scene.setObjectTriangleCount(faces.size(), /*object_id=*/0);
231
232 // for (int i = 0; i < vertices.size(); i++)
233 // {
234 // scene.setObjectVertex(vertices[i].cast<float>(), i, /*object_id=*/0);
235 // }
236
237 // // specify the triangle indices
238 // for (int i = 0; i < faces.size(); i++)
239 // {
240 // scene.setObjectTriangle(faces[i].data(), i, 0);
241 // }
242
243 // scene.build(fcpw::AggregateType::Bvh_SurfaceArea, true); // the second boolean argument enables vectorization
244 // }
245
246 // --------------------------------------------------------------------
247
248 for (int i = 0; i < proxy_vertices.rows(); i++)
249 {
250 Eigen::Vector3d v = Eigen::Vector3d::Zero();
251 v.head(dim) = proxy_vertices.row(i);
252
253 std::vector<unsigned int> candidates;
254 bvh.intersect_box(v, v, candidates);
255
256 // find which element the proxy vertex belongs to
257 int closest_element_id = -1;
258 VectorNd vhat;
259 for (const unsigned int element_id : candidates)
260 {
261 const Eigen::MatrixXd nodes = geom_bases[element_id].nodes();
262 if (dim == 2)
263 {
264 assert(nodes.rows() == 3);
265 Eigen::RowVector3d bc;
266 igl::barycentric_coordinates(
267 proxy_vertices.row(i), nodes.row(0), nodes.row(1), nodes.row(2), bc);
268 vhat = bc.head<2>();
269 }
270 else
271 {
272 assert(dim == 3 && nodes.rows() == 4);
273 Eigen::RowVector4d bc;
274 igl::barycentric_coordinates(
275 proxy_vertices.row(i), nodes.row(0), nodes.row(1), nodes.row(2), nodes.row(3), bc);
276 vhat = bc.head<3>();
277 }
278 if (vhat.minCoeff() >= 0 && vhat.maxCoeff() <= 1 && vhat.sum() <= 1)
279 {
280 closest_element_id = element_id;
281 break;
282 }
283 }
284
285 if (closest_element_id < 0)
286 {
287 // perform a closest point query
288 log_and_throw_error("build_collision_proxy_displacement_map(): closest point query not implemented!");
289 // fcpw::Interaction<3> cpq_interaction;
290 // scene.findClosestPoint(v.cast<float>(), cpq_interaction);
291
292 // closest_element_id = cpq_interaction.primitiveIndex / 4;
293
294 // const Eigen::MatrixXd nodes = geom_bases[closest_element_id].nodes();
295
296 // assert(dim == 3 && nodes.rows() == 4);
297 // Eigen::RowVector4d bc;
298 // igl::barycentric_coordinates(
299 // proxy_vertices.row(i), nodes.row(0), nodes.row(1), nodes.row(2), nodes.row(3), bc);
300 // vhat = bc.head<3>();
301 }
302
303 // compute the displacement map entries
304 for (const basis::Basis &basis : bases[closest_element_id].bases)
305 {
306 assert(basis.global().size() == 1);
307 const int j = basis.global()[0].index;
308 displacement_map_entries.emplace_back(i, j, basis(vhat.transpose())(0));
309 }
310 }
311 }
312
313 // ========================================================================
314
316 const std::string &mesh_filename,
317 const std::string &weights_filename,
318 const Eigen::VectorXi &in_node_to_node,
319 const json &transformation,
320 Eigen::MatrixXd &vertices,
321 Eigen::VectorXi &codim_vertices,
322 Eigen::MatrixXi &edges,
323 Eigen::MatrixXi &faces,
324 std::vector<Eigen::Triplet<double>> &displacement_map_entries)
325 {
326 load_collision_proxy_mesh(mesh_filename, transformation, vertices, codim_vertices, edges, faces);
327 load_collision_proxy_displacement_map(weights_filename, in_node_to_node, vertices.rows(), displacement_map_entries);
328 }
329
331 const std::string &mesh_filename,
332 const json &transformation,
333 Eigen::MatrixXd &vertices,
334 Eigen::VectorXi &codim_vertices,
335 Eigen::MatrixXi &edges,
336 Eigen::MatrixXi &faces)
337 {
338 Eigen::MatrixXi codim_edges;
339 read_surface_mesh(mesh_filename, vertices, codim_vertices, codim_edges, faces);
340
341 if (faces.size())
342 igl::edges(faces, edges);
343
344 utils::append_rows(edges, codim_edges);
345
346 // Transform the collision proxy
347 std::array<RowVectorNd, 2> bbox;
348 bbox[0] = vertices.colwise().minCoeff();
349 bbox[1] = vertices.colwise().maxCoeff();
350
351 MatrixNd A;
352 VectorNd b;
353 // TODO: pass correct unit scale
355 /*unit_scale=*/1, transformation,
356 (bbox[1] - bbox[0]).cwiseAbs().transpose(),
357 A, b);
358 vertices = (vertices * A.transpose()).rowwise() + b.transpose();
359 }
360
362 const std::string &weights_filename,
363 const Eigen::VectorXi &in_node_to_node,
364 const size_t num_proxy_vertices,
365 std::vector<Eigen::Triplet<double>> &displacement_map_entries)
366 {
367#ifndef NDEBUG
368 const size_t num_fe_nodes = in_node_to_node.size();
369#endif
370 h5pp::File file(weights_filename, h5pp::FileAccess::READONLY);
371 const std::array<long, 2> shape = file.readAttribute<std::array<long, 2>>("weight_triplets", "shape");
372 assert(shape[0] == num_proxy_vertices && shape[1] == num_fe_nodes);
373 Eigen::VectorXd values = file.readDataset<Eigen::VectorXd>("weight_triplets/values");
374 Eigen::VectorXi rows = file.readDataset<Eigen::VectorXi>("weight_triplets/rows");
375 Eigen::VectorXi cols = file.readDataset<Eigen::VectorXi>("weight_triplets/cols");
376 assert(rows.maxCoeff() < num_proxy_vertices);
377 assert(cols.maxCoeff() < num_fe_nodes);
378
379 // TODO: use these to build the in_node_to_node map
380 // const Eigen::VectorXi in_ordered_vertices = file.exist("ordered_vertices") ? H5Easy::load<Eigen::VectorXi>(file, "ordered_vertices") : mesh->in_ordered_vertices();
381 // const Eigen::MatrixXi in_ordered_edges = file.exist("ordered_edges") ? H5Easy::load<Eigen::MatrixXi>(file, "ordered_edges") : mesh->in_ordered_edges();
382 // const Eigen::MatrixXi in_ordered_faces = file.exist("ordered_faces") ? H5Easy::load<Eigen::MatrixXi>(file, "ordered_faces") : mesh->in_ordered_faces();
383
384 displacement_map_entries.clear();
385 displacement_map_entries.reserve(values.size());
386
387 assert(in_node_to_node.size() == num_fe_nodes);
388 for (int i = 0; i < values.size(); i++)
389 {
390 // Rearrange the columns based on the FEM mesh node order
391 displacement_map_entries.emplace_back(rows[i], in_node_to_node[cols[i]], values[i]);
392 }
393 }
394} // namespace polyfem::mesh
int V
std::vector< Eigen::VectorXi > faces
int x
Represents one basis function and its gradient.
Definition Basis.hpp:44
Stores the basis functions for a given element in a mesh (facet in 2d, cell in 3d).
void eval_geom_mapping(const Eigen::MatrixXd &samples, Eigen::MatrixXd &mapped) const
Map the sample positions in the parametric domain to the object domain (if the element has no paramet...
std::vector< Basis > bases
one basis function per node in the element
Boundary primitive IDs for a single element.
str nodes
Definition p_bases.py:372
void load_collision_proxy(const std::string &mesh_filename, const std::string &weights_filename, const Eigen::VectorXi &in_node_to_node, const json &transformation, Eigen::MatrixXd &vertices, Eigen::VectorXi &codim_vertices, Eigen::MatrixXi &edges, Eigen::MatrixXi &faces, std::vector< Eigen::Triplet< double > > &displacement_map_entries)
Load a collision proxy mesh and displacement map from files.
void construct_affine_transformation(const double unit_scale, const json &transform, const VectorNd &mesh_dimensions, MatrixNd &A, VectorNd &b)
Construct an affine transformation .
void build_collision_proxy(const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &geom_bases, const std::vector< LocalBoundary > &total_local_boundary, const int n_bases, const int dim, const double max_edge_length, Eigen::MatrixXd &proxy_vertices, Eigen::MatrixXi &proxy_faces, std::vector< Eigen::Triplet< double > > &displacement_map_entries, const CollisionProxyTessellation tessellation)
void build_collision_proxy_displacement_map(const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &geom_bases, const std::vector< mesh::LocalBoundary > &total_local_boundary, const int n_bases, const int dim, const Eigen::MatrixXd &proxy_vertices, std::vector< Eigen::Triplet< double > > &displacement_map_entries)
Build a collision proxy displacement map for a given mesh and proxy mesh.
void load_collision_proxy_mesh(const std::string &mesh_filename, const json &transformation, Eigen::MatrixXd &vertices, Eigen::VectorXi &codim_vertices, Eigen::MatrixXi &edges, Eigen::MatrixXi &faces)
Load a collision proxy mesh from a file.
double max_edge_length(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F)
Compute the maximum edge length of a triangle mesh (V, F)
void regular_grid_triangle_barycentric_coordinates(const int n, Eigen::MatrixXd &V, Eigen::MatrixXi &F)
Compute the barycentric coordinates of a regular grid of triangles.
void irregular_triangle_barycentric_coordinates(const Eigen::Vector3d &a, const Eigen::Vector3d &b, const Eigen::Vector3d &c, const double max_edge_length, Eigen::MatrixXd &UV, Eigen::MatrixXi &F)
Refine a triangle (a, b, c) into a well shaped triangle mesh.
@ IRREGULAR
Irregular tessellation of the mesh (requires POLYFEM_WITH_TRIANGLE)
@ REGULAR
Regular tessellation of the mesh.
void load_collision_proxy_displacement_map(const std::string &weights_filename, const Eigen::VectorXi &in_node_to_node, const size_t num_proxy_vertices, std::vector< Eigen::Triplet< double > > &displacement_map_entries)
Load a collision proxy displacement map from files.
void stitch_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, Eigen::MatrixXd &V_out, Eigen::MatrixXi &F_out, const double epsilon)
Stitch a triangle mesh (V, F) together by removing duplicate vertices.
bool read_surface_mesh(const std::string &mesh_path, Eigen::MatrixXd &vertices, Eigen::VectorXi &codim_vertices, Eigen::MatrixXi &codim_edges, Eigen::MatrixXi &faces)
read a surface mesh
Eigen::Matrix< double, Eigen::Dynamic, 1, 0, 3, 1 > VectorNd
Definition Types.hpp:9
nlohmann::json json
Definition Common.hpp:9
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor, 3, 3 > MatrixNd
Definition Types.hpp:12
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:71