14#include <lagrange/Mesh.h>
15#include <lagrange/MeshTrait.h>
16#include <lagrange/attributes/map_attributes.h>
17#include <lagrange/chain_corners_around_edges.h>
18#include <lagrange/common.h>
19#include <lagrange/create_mesh.h>
20#include <lagrange/utils/DisjointSets.h>
21#include <lagrange/utils/chain_edges.h>
22#include <lagrange/utils/stl_eigen.h>
42template <
typename MeshType>
46 la_runtime_assert(mesh.get_vertex_per_facet() == 3,
"This method is only for triangle meshes.");
48 using Scalar =
typename MeshType::Scalar;
49 using Index =
typename MeshType::Index;
50 using VertexArray =
typename MeshType::VertexArray;
51 using FacetArray =
typename MeshType::FacetArray;
52 using AttributeArray =
typename MeshType::AttributeArray;
54 logger().trace(
"[close_small_holes] initialize edge data");
55 mesh.initialize_edge_data();
58 logger().trace(
"[close_small_holes] clustering holes");
59 const Index num_vertices = mesh.get_num_vertices();
60 const Index num_facets = mesh.get_num_facets();
61 const Index nvpf = mesh.get_vertex_per_facet();
62 const Index dim = mesh.get_dim();
63 std::vector<Index> vertex_to_reduced(num_vertices, invalid<Index>());
64 std::vector<Index> reduced_to_vertex;
65 std::vector<std::array<Index, 2>> boundary_edges;
66 std::vector<std::array<Index, 2>> boundary_corners;
68 auto get_reduced_index = [&](Index v) {
69 if (vertex_to_reduced[v] == invalid<Index>()) {
70 vertex_to_reduced[v] =
static_cast<Index
>(reduced_to_vertex.size());
71 reduced_to_vertex.push_back(v);
73 return vertex_to_reduced[v];
76 for (Index e = 0; e < mesh.get_num_edges(); ++e) {
77 const Index c = mesh.get_one_corner_around_edge(e);
78 assert(c != invalid<Index>());
79 if (mesh.is_boundary_edge(e)) {
80 const Index f = c / nvpf;
81 const Index lv = c % nvpf;
82 const Index lv2 = (lv + 1) % nvpf;
83 const Index v1 = mesh.get_facets()(f, lv);
84 const Index v2 = mesh.get_facets()(f, lv2);
86 boundary_edges.push_back({{get_reduced_index(v2), get_reduced_index(v1)}});
87 boundary_corners.push_back({{f * nvpf + lv2, f * nvpf + lv}});
92 logger().trace(
"[close_small_holes] chain edges into simple loops");
93 std::vector<std::vector<Index>> loops;
97 auto result = chain_directed_edges<Index>(
98 {
reinterpret_cast<Index*
>(boundary_edges.data()), boundary_edges.size() * 2},
100 loops = std::move(result.loops);
103 auto get_from_corner = [](
const auto& matrix, Index c) {
104 const Index _nvpf =
static_cast<Index
>(matrix.cols());
105 return matrix(c / _nvpf, c % _nvpf);
110 std::vector<bool> needs_barycenter(loops.size(),
false);
111 for (
const auto& name : mesh.get_indexed_attribute_names()) {
112 const auto& attr = mesh.get_indexed_attribute(name);
113 const auto& indices = std::get<1>(attr);
114 for (
size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
115 const auto& loop = loops[loop_id];
116 if (loop.size() <= max_hole_size && loop.size() <= 3) {
117 assert(loop.size() == 3);
118 for (Index lv = 0; lv < 3; ++lv) {
119 const Index c_prev = boundary_corners[loop[lv]][1];
120 const Index c_next = boundary_corners[loop[(lv + 1) % 3]][0];
121 const Index idx_prev = get_from_corner(indices, c_prev);
122 const Index idx_next = get_from_corner(indices, c_next);
123 if (idx_prev != idx_next) {
124 needs_barycenter[loop_id] =
true;
132 logger().trace(
"[close_small_holes] compute hole barycenters + facets");
133 using Triangle = std::array<Index, 3>;
134 std::vector<Scalar> values;
135 std::vector<Triangle> new_facets;
136 eigen_to_flat_vector(mesh.get_vertices(), values, Eigen::RowMajor);
137 for (
size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
138 const auto& loop = loops[loop_id];
139 if (loop.size() <= max_hole_size) {
140 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
141 assert(loop.size() == 3);
142 const Index v0 = reduced_to_vertex[boundary_edges[loop[0]][0]];
143 const Index v1 = reduced_to_vertex[boundary_edges[loop[1]][0]];
144 const Index v2 = reduced_to_vertex[boundary_edges[loop[2]][0]];
145 new_facets.push_back({{v0, v1, v2}});
148 const Index vc = (Index)values.size() / dim;
149 values.insert(values.end(), dim, 0);
150 assert(values.size() % dim == 0);
151 for (
auto e : loop) {
152 const Index vi = reduced_to_vertex[boundary_edges[e][0]];
153 const Index vj = reduced_to_vertex[boundary_edges[e][1]];
154 for (Index k = 0; k < dim; ++k) {
155 values[vc * dim + k] += mesh.get_vertices()(vi, k);
157 new_facets.push_back({{vi, vj, vc}});
159 for (Index k = 0; k < dim; ++k) {
160 values[vc * dim + k] /=
static_cast<Scalar
>(loop.size());
167 logger().trace(
"[close_small_holes] append new facets");
168 VertexArray vertices;
169 flat_vector_to_eigen(values, vertices, (Index)values.size() / dim, dim, Eigen::RowMajor);
171 FacetArray facets(num_facets + new_facets.size(), 3);
172 facets.topRows(num_facets) = mesh.get_facets();
173 for (Index f = 0; f < (Index)new_facets.size(); ++f) {
174 facets.row(num_facets + f) << new_facets[f][0], new_facets[f][1], new_facets[f][2];
179 for (
const auto& name : mesh.get_vertex_attribute_names()) {
180 const auto& attr = mesh.get_vertex_attribute(name);
181 AttributeArray vals(new_mesh->get_num_vertices(), attr.cols());
182 vals.topRows(attr.rows()) = attr;
183 Index counter = mesh.get_num_vertices();
184 for (
size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
185 const auto& loop = loops[loop_id];
186 if (loop.size() <= max_hole_size) {
187 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
191 vals.row(counter).setZero();
192 for (
auto e : loop) {
193 const Index vi = reduced_to_vertex[boundary_edges[e][0]];
194 vals.row(counter) += attr.row(vi);
196 vals.row(counter) /=
static_cast<Scalar
>(loop.size());
201 new_mesh->add_vertex_attribute(name);
202 new_mesh->import_vertex_attribute(name, std::move(vals));
206 for (
const auto& name : mesh.get_facet_attribute_names()) {
207 const auto& attr = mesh.get_facet_attribute(name);
208 AttributeArray vals(new_mesh->get_num_facets(), attr.cols());
209 vals.topRows(attr.rows()) = attr;
210 Index counter = mesh.get_num_facets();
211 for (
size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
212 const auto& loop = loops[loop_id];
213 if (loop.size() <= max_hole_size) {
214 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
216 vals.row(counter).setZero();
217 for (Index i = 0; i < 3; ++i) {
218 const Index c = boundary_corners[loop[i]][0];
219 vals.row(counter) += vals.row(c / nvpf);
221 vals.row(counter) /= 3;
225 for (
auto e : loop) {
226 const Index c = boundary_corners[e][0];
227 vals.row(counter++) = attr.row(c / nvpf);
232 new_mesh->add_facet_attribute(name);
233 new_mesh->import_facet_attribute(name, std::move(vals));
237 for (
const auto& name : mesh.get_edge_attribute_names()) {
238 new_mesh->initialize_edge_data();
239 const auto& attr = mesh.get_edge_attribute(name);
240 AttributeArray vals(new_mesh->get_num_edges(), attr.cols());
244 for (Index f = 0; f < mesh.get_num_facets(); ++f) {
245 for (Index lv = 0; lv < nvpf; ++lv) {
246 const Index old_e = mesh.get_edge(f, lv);
247 const Index new_e = new_mesh->get_edge(f, lv);
248 vals.row(new_e) = attr.row(old_e);
253 Index facet_counter = mesh.get_num_facets();
254 for (
size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
255 const auto& loop = loops[loop_id];
256 if (loop.size() <= max_hole_size) {
257 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
262 for (
auto e : loop) {
263 const Index v0 = reduced_to_vertex[boundary_edges[e][0]];
265 const Index c = boundary_corners[e][1];
266 const Index f = c / nvpf;
268 assert(f == boundary_corners[e][0] / nvpf);
269 const Index lv = c % nvpf;
272 const Index e0 = new_mesh->get_edge(facet_counter, 0);
273 const Index e1 = new_mesh->get_edge(facet_counter, 1);
274 const Index e2 = new_mesh->get_edge(facet_counter, 2);
275 assert(e0 == new_mesh->get_edge(f, lv));
276 vals.row(e1) += Scalar(0.5) * vals.row(e0);
277 vals.row(e2) += Scalar(0.5) * vals.row(e0);
283 new_mesh->add_edge_attribute(name);
284 new_mesh->import_edge_attribute(name, std::move(vals));
288 for (
const auto& name : mesh.get_corner_attribute_names()) {
289 const auto& attr = mesh.get_corner_attribute(name);
290 AttributeArray vals(new_mesh->get_num_facets() * nvpf, attr.cols());
291 vals.topRows(attr.rows()) = attr;
292 Index counter = mesh.get_num_facets();
293 for (
size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
294 const auto& loop = loops[loop_id];
295 if (loop.size() <= max_hole_size) {
296 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
297 assert(loop.size() == 3);
299 const auto c01 = boundary_corners[loop[0]];
300 const auto c12 = boundary_corners[loop[1]];
301 const auto c20 = boundary_corners[loop[2]];
302 const Scalar w = 0.5;
303 vals.row(counter * nvpf + 0) = w * (vals.row(c01[0]) + vals.row(c20[1]));
304 vals.row(counter * nvpf + 1) = w * (vals.row(c12[0]) + vals.row(c01[1]));
305 vals.row(counter * nvpf + 2) = w * (vals.row(c20[0]) + vals.row(c12[1]));
310 const Index shared_corner = counter * nvpf + 2;
311 vals.row(shared_corner).setZero();
312 for (
auto e : loop) {
313 const auto c = boundary_corners[e];
314 vals.row(counter * nvpf + 0) = vals.row(c[0]);
315 vals.row(counter * nvpf + 1) = vals.row(c[1]);
316 vals.row(shared_corner) += vals.row(c[0]);
317 vals.row(shared_corner) += vals.row(c[1]);
320 vals.row(shared_corner) /=
static_cast<Scalar
>(2 * loop.size());
321 for (Index i = 0; i < (Index)loop.size(); ++i) {
322 vals.row(shared_corner + i * nvpf) = vals.row(shared_corner);
327 new_mesh->add_corner_attribute(name);
328 new_mesh->import_corner_attribute(name, std::move(vals));
333 std::vector<Index> indices;
335 std::vector<Index> group_color;
336 std::vector<Index> group_sizes;
337 std::vector<Scalar> group_means;
338 for (
const auto& name : mesh.get_indexed_attribute_names()) {
339 const auto& attr = mesh.get_indexed_attribute(name);
340 const Index num_coords =
static_cast<Index
>(std::get<0>(attr).cols());
341 logger().trace(
"[close_small_holes] remaping indexed attribute: {}", name);
342 eigen_to_flat_vector(std::get<0>(attr), values, Eigen::RowMajor);
343 eigen_to_flat_vector(std::get<1>(attr), indices, Eigen::RowMajor);
344 for (
size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
345 const auto& loop = loops[loop_id];
346 if (loop.size() <= max_hole_size) {
347 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
348 assert(loop.size() == 3);
349 indices.push_back(indices[boundary_corners[loop[0]][0]]);
350 indices.push_back(indices[boundary_corners[loop[1]][0]]);
351 indices.push_back(indices[boundary_corners[loop[2]][0]]);
354 const size_t nv = loop.size();
356 for (
size_t i = 0; i < loop.size(); ++i) {
357 const size_t j = (i + 1) % loop.size();
358 const Index lci =
static_cast<Index
>(2 * i + 1);
359 const Index lcj =
static_cast<Index
>(2 * j + 0);
360 const Index ci = boundary_corners[loop[i]][1];
361 const Index cj = boundary_corners[loop[j]][0];
362 groups.
merge(
static_cast<Index
>(2 * i),
static_cast<Index
>(2 * i + 1));
363 if (indices[ci] == indices[cj]) {
364 groups.
merge(lci, lcj);
368 const Index num_groups =
370 logger().trace(
"num groups: {}", num_groups);
371 group_sizes.assign(num_groups, 0);
372 group_means.assign(num_groups * num_coords, 0);
373 for (
size_t lc = 0; lc < 2 * loop.size(); ++lc) {
374 const Index idx = indices[boundary_corners[loop[lc / 2]][lc % 2]];
375 const Scalar* val = values.data() + idx * num_coords;
376 const Index repr = group_color[lc];
378 for (Index k = 0; k < num_coords; ++k) {
379 group_means[repr * num_coords + k] += val[k];
383 const Index offset =
static_cast<Index
>(values.size()) / num_coords;
384 for (Index g = 0; g < num_groups; ++g) {
385 for (Index k = 0; k < num_coords; ++k) {
386 assert(group_sizes[g] > 0);
387 group_means[g * num_coords + k] /=
static_cast<Scalar
>(group_sizes[g]);
388 values.push_back(group_means[g * num_coords + k]);
390 assert(
static_cast<Index
>(values.size()) % num_coords == 0);
393 for (
size_t i = 0; i < loop.size(); ++i) {
394 const Index lci =
static_cast<Index
>(2 * i + 0);
395 const Index idxi = indices[boundary_corners[loop[i]][0]];
396 const Index idxj = indices[boundary_corners[loop[i]][1]];
397 indices.push_back(idxi);
398 indices.push_back(idxj);
399 indices.push_back(offset + (group_color[lci] + 0) % 3);
404 size_t num_values = values.size() / num_coords;
405 size_t num_indices = indices.size() / nvpf;
406 AttributeArray values_;
408 flat_vector_to_eigen(values, values_, num_values, num_coords, Eigen::RowMajor);
409 flat_vector_to_eigen(indices, indices_, num_indices, nvpf, Eigen::RowMajor);
410 new_mesh->add_indexed_attribute(name);
411 new_mesh->import_indexed_attribute(name, std::move(values_), std::move(indices_));
414 logger().trace(
"[close_small_holes] cleanup");
Disjoint sets computation.
Definition: DisjointSets.h:32
IndexType merge(IndexType i, IndexType j)
Merge the disjoint set containing entry i and the disjoint set containing entry j.
Definition: DisjointSets.h:80
size_t extract_disjoint_set_indices(std::vector< IndexType > &index_map)
Assign all elements their disjoint set index.
Definition: DisjointSets.cpp:60
void init(size_t n)
Initialize disjoint sets that contains n entries.
Definition: DisjointSets.cpp:28
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition: Logger.cpp:40
#define la_runtime_assert(...)
Runtime assertion check.
Definition: assert.h:169
#define la_debug_assert(...)
Debug assertion check.
Definition: assert.h:189
Main namespace for Lagrange.
Definition: AABBIGL.h:30
auto create_mesh(const Eigen::MatrixBase< DerivedV > &vertices, const Eigen::MatrixBase< DerivedF > &facets)
This function create a new mesh given the vertex and facet arrays by copying data into the Mesh objec...
Definition: create_mesh.h:39
std::unique_ptr< MeshType > close_small_holes(MeshType &mesh, size_t max_hole_size)
Close small topological holes.
Definition: close_small_holes.h:43
Options for chain_directed_edges and chain_undirected_edges.
Definition: chain_edges.h:49
bool output_edge_index
If true, the output will be a list of edges, otherwise a list of vertices.
Definition: chain_edges.h:51
MeshTrait class provide compiler check for different mesh types.
Definition: MeshTrait.h:108