PolyFEM
Loading...
Searching...
No Matches
Collapse.cpp
Go to the documentation of this file.
4
5#include <wmtk/ExecutionScheduler.hpp>
6#include <ipc/ipc.hpp>
7
8namespace polyfem::mesh
9{
10 template <class WMTKMesh>
12 {
13 op_cache = decltype(op_cache)::element_type::collapse_edge(*this, e);
14 op_cache->collapse_to = collapse_to;
15 }
16
17 template <class WMTKMesh>
19 {
20 if (!WMTKMesh::collapse_edge_before(t))
21 return false;
22
23 const double max_edge_length = std::min(
24 args["collapse"]["abs_max_edge_length"].template get<double>(),
25 state.starting_min_edge_length
26 * args["collapse"]["rel_max_edge_length"].template get<double>());
27
28 double vol_tol;
29 if constexpr (std::is_same_v<wmtk::TriMesh, WMTKMesh>)
30 vol_tol = std::pow(max_edge_length, 2) / 2;
31 else
32 vol_tol = std::pow(max_edge_length, 3) / (6 * sqrt(2));
33
34 // Dont collapse if the edge is large
35 if (edge_adjacent_element_volumes(t).minCoeff() > vol_tol
36 || rest_edge_length(t) > max_edge_length)
37 {
38 executor.m_cnt_fail--; // do not count this as a failed collapse
39 return false;
40 }
41
42 const int v0i = t.vid(*this);
43 const int v1i = t.switch_vertex(*this).vid(*this);
44
46 if (is_body_boundary_edge(t))
47 {
48 collapse_to = collapse_boundary_edge_to(t);
49 }
50 else
51 {
52 // NOTE: .fixed here assumed to be equal to is_vertex_on_body_boundary
53 if (vertex_attrs[v0i].fixed && vertex_attrs[v1i].fixed)
54 collapse_to = CollapseEdgeTo::ILLEGAL; // interior edge with both vertices fixed means it spans accross the body
55 else if (vertex_attrs[v0i].fixed)
56 collapse_to = CollapseEdgeTo::V0;
57 else if (vertex_attrs[v1i].fixed)
58 collapse_to = CollapseEdgeTo::V1;
59 else
60 collapse_to = CollapseEdgeTo::MIDPOINT;
61 }
62
63 if (collapse_to == CollapseEdgeTo::ILLEGAL)
64 {
65 executor.m_cnt_fail--; // do not count this as a failed collapse
66 return false;
67 }
68
69 cache_collapse_edge(t, collapse_to);
70
71 return true;
72 }
73
74 template <class WMTKMesh>
76 {
77 // POLYFEM_REMESHER_SCOPED_TIMER("Collapse edge before");
78
79 if (!Super::collapse_edge_before(t)) // NOTE: also calls cache_collapse_edge
80 return false;
81
82 if (this->edge_attr(t.eid(*this)).op_attempts++ >= this->max_op_attempts
83 || this->edge_attr(t.eid(*this)).op_depth >= args["collapse"]["max_depth"].template get<int>())
84 {
85 this->executor.m_cnt_fail--; // do not count this as a failed collapse
86 return false;
87 }
88
89 const VectorNd &v0 = vertex_attrs[t.vid(*this)].rest_position;
90 const VectorNd &v1 = vertex_attrs[t.switch_vertex(*this).vid(*this)].rest_position;
91
92 switch (this->op_cache->collapse_to)
93 {
95 this->op_cache->local_energy = local_mesh_energy(v0);
96 break;
98 this->op_cache->local_energy = local_mesh_energy(v1);
99 break;
101 this->op_cache->local_energy = local_mesh_energy((v0 + v1) / 2);
102 break;
103 default:
104 assert(false);
105 }
106
107 return true;
108 }
109
110 // -------------------------------------------------------------------------
111
112 template <class WMTKMesh>
114 {
115 if (!WMTKMesh::collapse_edge_after(t))
116 return false;
117
118 // 0) perform operation (done before this function)
119
120 // 1a) Update rest position of new vertex
121 map_edge_collapse_vertex_attributes(t);
122
123 if (state.is_contact_enabled() && is_boundary_vertex(t))
124 {
125 const double dhat = state.args["contact"]["dhat"].template get<double>();
126
127 // only enforce this invariant if it started valid
128 if (state.min_boundary_edge_length >= dhat)
130 for (const Tuple &e : get_one_ring_boundary_edges_for_vertex(t))
131 {
132 if (rest_edge_length(e) < dhat)
133 return false; // produced too small edge
134 }
135 }
136 }
137
138 // 1b) Assign edge attributes to the new edges
139 map_edge_collapse_boundary_attributes(t);
140 // Nothing to do for the element attributes because no new elements are created.
141
142 // 2) Interpolate quantaties for a good initialization to the local relaxation
143
144 // done by map_edge_collapse_vertex_attributes(t);
145
146 // Check the interpolated position does not cause inversions
147 for (const Tuple &t1 : get_one_ring_elements_for_vertex(t))
148 {
149 if (is_inverted(t1) || element_volume(t1) < 1e-16)
150 return false;
151 }
152
153#ifndef NDEBUG
154 // Check the volume of the rest mesh is preserved
155 double new_total_volume = 0;
156 for (const Tuple &t : get_elements())
157 new_total_volume += element_volume(t);
158 assert(std::abs(new_total_volume - total_volume) < std::max(1e-12 * total_volume, 1e-12));
159#endif
160
161 // Check the interpolated position does not cause intersections
162 if (state.is_contact_enabled() && is_boundary_op())
163 {
164 Eigen::MatrixXd V_rest = rest_positions();
165 utils::append_rows(V_rest, obstacle().v());
166
167 ipc::CollisionMesh collision_mesh = ipc::CollisionMesh::build_from_full_mesh(
168 V_rest, boundary_edges(), boundary_faces());
169
170#ifndef NDEBUG
171 // This should never happen because we only collapse collinear edges on the rest mesh boundary.
172 if (ipc::has_intersections(collision_mesh, collision_mesh.rest_positions()))
173 {
174 write_mesh(state.resolve_output_path("collapse_intersects.vtu"));
175 assert(false);
176 }
177#endif
178
179 Eigen::MatrixXd V = positions();
180 if (obstacle().n_vertices())
181 utils::append_rows(V, obstacle().v() + obstacle_displacements());
182 if (ipc::has_intersections(collision_mesh, collision_mesh.vertices(V)))
183 {
184 return false;
185 }
186
187 Eigen::MatrixXd prev_displacements = projection_quantities().leftCols(n_quantities() / 3);
188 utils::append_rows(prev_displacements, obstacle_quantities().leftCols(n_quantities() / 3));
189 for (const auto &u : prev_displacements.colwise())
190 {
191 if (ipc::has_intersections(collision_mesh, collision_mesh.displace_vertices(utils::unflatten(u, dim()))))
192 {
193 return false;
194 }
195 }
196 }
197
198 // ~2) Project quantities so to minimize the L2 error~ (done after all operations)
199 // project_quantities(); // also projects positions
200
201 return true;
202 }
203
204 template <class WMTKMesh>
206 {
207 utils::Timer timer(this->timings["Collapse edges after"]);
208 timer.start();
209 if (!Super::collapse_edge_after(t))
210 return false;
211 // local relaxation has its own timers
212 timer.stop();
213
214 // 3) Perform a local relaxation of the n-ring to get an estimate of the
215 // energy decrease/increase.
216 return local_relaxation(t, args["collapse"]["acceptance_tolerance"]);
217 }
218
219 // -------------------------------------------------------------------------
220
221 template <class WMTKMesh>
223 {
224 const auto collapsable = [this](const Tuple &e) {
225 return this->edge_attr(e.eid(*this)).energy_rank == Super::EdgeAttributes::EnergyRank::BOTTOM
226 || this->rest_edge_length(e) < 1e-3 * this->state.starting_min_edge_length; // collapse short edges
227 };
228
229 executor.priority = [](const WildRemesher<WMTKMesh> &m, std::string op, const Tuple &t) -> double {
230 // return -m.edge_elastic_energy(t); // invert the energy to get a reverse ordering
231 return -m.rest_edge_length(t);
232 };
233
234 executor.renew_neighbor_tuples = [&](const WildRemesher<WMTKMesh> &m, std::string op, const std::vector<Tuple> &tris) -> Operations {
235 const std::vector<Tuple> edges = m.get_edges_for_elements(
237 Operations new_ops;
238 for (auto &e : edges)
239 if (collapsable(e))
240 new_ops.emplace_back(op, e);
241 return new_ops;
242 };
243
244 std::vector<Tuple> included_edges;
245 {
246 const std::vector<Tuple> edges = WMTKMesh::get_edges();
247 std::copy_if(edges.begin(), edges.end(), std::back_inserter(included_edges), collapsable);
248 }
249
250 if (included_edges.empty())
251 return;
252
253 Operations collapses;
254 collapses.reserve(included_edges.size());
255 for (const Tuple &e : included_edges)
256 collapses.emplace_back("edge_collapse", e);
257
258 executor(*this, collapses);
259 }
260
261 // =========================================================================
262 // Map attributes
263 // =========================================================================
264
265 template <class WMTKMesh>
268 const VertexAttributes &v0,
269 const VertexAttributes &v1,
270 const CollapseEdgeTo collapse_to)
271 {
273
274 switch (collapse_to)
275 {
277 {
279 v.position = v0.position;
282 v.fixed = v0.fixed;
283 break;
284 }
286 {
288 v.position = v1.position;
291 v.fixed = v1.fixed;
292 break;
293 }
295 {
296 // TODO: using an average midpoint for now
297 v.rest_position = (v0.rest_position + v1.rest_position) / 2.0;
298 v.position = (v0.position + v1.position) / 2.0;
300 v.partition_id = v0.partition_id; // TODO: what should this be?
301 v.fixed = v0.fixed || v1.fixed;
302 break;
304 default:
305 assert(false);
306 }
307
308 return v;
309 }
310
311 template <>
313 {
315 op_cache->v0().second, op_cache->v1().second, op_cache->collapse_to);
316 }
317
318 template <>
320 {
322 op_cache->v0().second, op_cache->v1().second, op_cache->collapse_to);
323 }
324
325 // -------------------------------------------------------------------------
326
327 template <>
329
330 template <>
332 {
333 const auto &[old_v0_id, old_v0] = op_cache->v0();
334 const auto &[old_v1_id, old_v1] = op_cache->v1();
335 const auto &old_edges = op_cache->edges();
336
337 const size_t new_vid = t.vid(*this);
338
339 for (const Tuple &t : get_one_ring_tets_for_vertex(t))
340 {
341 for (const Tuple &e : tet_edges(t))
342 {
343 std::array<size_t, 2> vids{{e.vid(*this), e.switch_vertex(*this).vid(*this)}};
344 auto iter = std::find(vids.begin(), vids.end(), new_vid);
345
346 if (iter != vids.end())
347 {
348 *iter = old_v0_id;
349 const auto find_old_edge0 = old_edges.find(vids);
350 *iter = old_v1_id;
351 const auto find_old_edge1 = old_edges.find(vids);
352
353 if (find_old_edge0 != old_edges.end() && find_old_edge1 != old_edges.end())
354 {
355 edge_attr(e.eid(*this)).energy_rank = std::min(
356 find_old_edge0->second.energy_rank, find_old_edge1->second.energy_rank);
357 edge_attr(e.eid(*this)).op_depth =
358 std::max(find_old_edge0->second.op_depth, find_old_edge1->second.op_depth);
359 }
360 else if (find_old_edge0 != old_edges.end())
361 edge_attr(e.eid(*this)) = find_old_edge0->second;
362 else if (find_old_edge1 != old_edges.end())
363 edge_attr(e.eid(*this)) = find_old_edge1->second;
364 else
365 assert(false);
366
367 edge_attr(e.eid(*this)).op_attempts = 0;
368 edge_attr(e.eid(*this)).op_depth++;
369 }
370 else
371 {
372 edge_attr(e.eid(*this)) = old_edges.at(vids);
373 }
374 }
375 }
376 }
377
378 // -------------------------------------------------------------------------
379
380 template <>
382 {
383 const auto &[old_v0_id, old_v0] = op_cache->v0();
384 const auto &[old_v1_id, old_v1] = op_cache->v1();
385 const auto &old_edges = op_cache->edges();
386
387 const size_t new_vid = t.vid(*this);
388
389 const std::vector<Tuple> one_ring_edges = get_one_ring_edges_for_vertex(t);
390 for (const Tuple &e : one_ring_edges)
391 {
392 const size_t e_id = e.eid(*this);
393
394 size_t v0_id = e.vid(*this);
395 size_t v1_id = e.switch_vertex(*this).vid(*this);
396 if (v0_id > v1_id)
397 std::swap(v0_id, v1_id);
398 assert(v1_id == new_vid); // should be the new vertex because it has a larger id
399
400 const std::array<size_t, 2> old_edge0{{v0_id, old_v0_id}};
401 const std::array<size_t, 2> old_edge1{{v0_id, old_v1_id}};
402 const auto find_old_edge0 = old_edges.find(old_edge0);
403 const auto find_old_edge1 = old_edges.find(old_edge1);
404
405 if (find_old_edge0 != old_edges.end() && find_old_edge1 != old_edges.end())
406 {
407 // both cannot be boundary edges
408 assert(!(find_old_edge0->second.boundary_id >= 0 && find_old_edge1->second.boundary_id >= 0));
409 boundary_attrs[e_id].boundary_id = std::max(
410 find_old_edge0->second.boundary_id, find_old_edge1->second.boundary_id);
411 boundary_attrs[e_id].energy_rank = std::min(
412 find_old_edge0->second.energy_rank, find_old_edge1->second.energy_rank);
413 boundary_attrs[e_id].op_depth =
414 std::max(find_old_edge0->second.op_depth, find_old_edge1->second.op_depth);
415 }
416 else if (find_old_edge0 != old_edges.end())
417 boundary_attrs[e_id] = find_old_edge0->second;
418 else if (find_old_edge1 != old_edges.end())
419 boundary_attrs[e_id] = find_old_edge1->second;
420 else
421 assert(false);
422
423 boundary_attrs[e_id].op_attempts = 0;
424 boundary_attrs[e_id].op_depth++;
425
426#ifndef NDEBUG
427 if (is_boundary_facet(e))
428 assert(boundary_attrs[facet_id(e)].boundary_id >= 0);
429 else
430 assert(boundary_attrs[facet_id(e)].boundary_id == -1);
431#endif
432 }
433 }
434
435 template <>
437 {
438 const auto &[old_v0_id, old_v0] = op_cache->v0();
439 const auto &[old_v1_id, old_v1] = op_cache->v1();
440 const auto &old_faces = op_cache->faces();
441
442 const size_t new_vid = t.vid(*this);
443
445 {
446 assert(f.is_boundary_face(*this));
447 const size_t f_id = f.fid(*this);
448
449 const std::array<Tuple, 3> fv = get_face_vertices(f);
450 std::array<size_t, 3> vids{{fv[0].vid(*this), fv[1].vid(*this), fv[2].vid(*this)}};
451 auto iter = std::find(vids.begin(), vids.end(), new_vid);
452 assert(iter != vids.end());
453
454 *iter = old_v0_id;
455 const auto find_old_face0 = old_faces.find(vids);
456 *iter = old_v1_id;
457 const auto find_old_face1 = old_faces.find(vids);
458
459 if (find_old_face0 != old_faces.end() && find_old_face1 != old_faces.end())
460 {
461 // both cannot be boundary faces
462 assert(!(find_old_face0->second.boundary_id >= 0 && find_old_face1->second.boundary_id >= 0));
463 boundary_attrs[f_id].boundary_id = std::max(
464 find_old_face0->second.boundary_id, find_old_face1->second.boundary_id);
465 }
466 else if (find_old_face0 != old_faces.end())
467 boundary_attrs[f_id] = find_old_face0->second;
468 else if (find_old_face1 != old_faces.end())
469 boundary_attrs[f_id] = find_old_face1->second;
470 else
471 assert(false);
472
473#ifndef NDEBUG
474 if (is_boundary_facet(f))
475 assert(boundary_attrs[facet_id(f)].boundary_id >= 0);
476 else
477 assert(boundary_attrs[facet_id(f)].boundary_id == -1);
478#endif
479 }
480
482
483#ifndef NDEBUG
484 for (const Tuple &f : get_facets())
485 {
486 if (is_boundary_facet(f))
487 assert(boundary_attrs[facet_id(f)].boundary_id >= 0);
488 else
489 assert(boundary_attrs[facet_id(f)].boundary_id == -1);
490 }
491#endif
492 }
493
494 // =========================================================================
495
496 // ------------------------------------------------------------------------
497 // Template specializations
498
499 template class WildRemesher<wmtk::TriMesh>;
500 template class WildRemesher<wmtk::TetMesh>;
501 template class PhysicsRemesher<wmtk::TriMesh>;
502 template class PhysicsRemesher<wmtk::TetMesh>;
503
504} // namespace polyfem::mesh
int V
typename Super::Operations Operations
typename Super::VectorNd VectorNd
bool collapse_edge_after(const Tuple &t) override
Definition Collapse.cpp:205
bool collapse_edge_before(const Tuple &t) override
Definition Collapse.cpp:75
virtual bool collapse_edge_after(const Tuple &t) override
Definition Collapse.cpp:113
std::conditional< std::is_same< WMTKMesh, wmtk::TriMesh >::value, std::shared_ptr< TriOperationCache >, std::shared_ptr< TetOperationCache > >::type op_cache
size_t facet_id(const Tuple &t) const
Get the id of a facet (edge for triangle, triangle for tetrahedra)
wmtk::AttributeCollection< BoundaryAttributes > boundary_attrs
typename WMTKMesh::Tuple Tuple
wmtk::AttributeCollection< VertexAttributes > vertex_attrs
std::vector< Tuple > get_edges_for_elements(const std::vector< Tuple > &elements) const
bool is_boundary_facet(const Tuple &t) const
Is the given tuple on the boundary of the mesh?
double rest_edge_length(const Tuple &e) const
Compute the length of an edge.
std::vector< Tuple > get_one_ring_elements_for_vertex(const Tuple &t) const
Get the one ring of elements around a vertex.
void map_edge_collapse_boundary_attributes(const Tuple &t)
std::vector< Tuple > get_facets() const
Get a vector of all facets (edges or triangles)
EdgeAttributes & edge_attr(const size_t e_id)
Get a reference to an edge's attributes.
void map_edge_collapse_vertex_attributes(const Tuple &t)
void map_edge_collapse_edge_attributes(const Tuple &t)
std::vector< Tuple > get_one_ring_boundary_faces_for_vertex(const Tuple &v) const
void cache_collapse_edge(const Tuple &e, const CollapseEdgeTo collapse_to)
Cache the edge collapse operation.
Definition Collapse.cpp:11
virtual bool collapse_edge_before(const Tuple &t) override
Definition Collapse.cpp:18
WildTetRemesher::Tuple Tuple
double max_edge_length(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F)
Compute the maximum edge length of a triangle mesh (V, F)
Eigen::MatrixXd unflatten(const Eigen::VectorXd &x, int dim)
Unflatten rowwises, so every dim elements in x become a row.
void append_rows(DstMat &dst, const SrcMat &src)
Eigen::MatrixXd projection_quantities
Quantities to be projected (dim × n_quantities)
static VertexAttributes edge_collapse(const VertexAttributes &v0, const VertexAttributes &v1, const CollapseEdgeTo collapse_to)
Definition Collapse.cpp:267