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