Lagrange
All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Modules Pages
close_small_holes.h
1/*
2 * Copyright 2020 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
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/legacy/inline.h>
21#include <lagrange/utils/DisjointSets.h>
22#include <lagrange/utils/chain_edges.h>
23#include <lagrange/utils/stl_eigen.h>
24
25#include <vector>
26
27namespace lagrange {
28LAGRANGE_LEGACY_INLINE
29namespace legacy {
30
45template <typename MeshType>
46std::unique_ptr<MeshType> close_small_holes(MeshType& mesh, size_t max_hole_size)
47{
48 static_assert(MeshTrait<MeshType>::is_mesh(), "Input type is not Mesh");
49 la_runtime_assert(mesh.get_vertex_per_facet() == 3, "This method is only for triangle meshes.");
50
51 using Scalar = typename MeshType::Scalar;
52 using Index = typename MeshType::Index;
53 using VertexArray = typename MeshType::VertexArray;
54 using FacetArray = typename MeshType::FacetArray;
55 using AttributeArray = typename MeshType::AttributeArray;
56
57 logger().trace("[close_small_holes] initialize edge data");
58 mesh.initialize_edge_data();
59
60 // Compute boundary edge list + reduce indexing of boundary vertices
61 logger().trace("[close_small_holes] clustering holes");
62 const Index num_vertices = mesh.get_num_vertices();
63 const Index num_facets = mesh.get_num_facets();
64 const Index nvpf = mesh.get_vertex_per_facet();
65 const Index dim = mesh.get_dim();
66 std::vector<Index> vertex_to_reduced(num_vertices, invalid<Index>());
67 std::vector<Index> reduced_to_vertex;
68 std::vector<std::array<Index, 2>> boundary_edges;
69 std::vector<std::array<Index, 2>> boundary_corners;
70
71 auto get_reduced_index = [&](Index v) {
72 if (vertex_to_reduced[v] == invalid<Index>()) {
73 vertex_to_reduced[v] = static_cast<Index>(reduced_to_vertex.size());
74 reduced_to_vertex.push_back(v);
75 }
76 return vertex_to_reduced[v];
77 };
78
79 for (Index e = 0; e < mesh.get_num_edges(); ++e) {
80 const Index c = mesh.get_one_corner_around_edge(e);
81 assert(c != invalid<Index>());
82 if (mesh.is_boundary_edge(e)) {
83 const Index f = c / nvpf;
84 const Index lv = c % nvpf;
85 const Index lv2 = (lv + 1) % nvpf;
86 const Index v1 = mesh.get_facets()(f, lv);
87 const Index v2 = mesh.get_facets()(f, lv2);
88 // Flip boundary edges to correctly orient the hole polygon
89 boundary_edges.push_back({{get_reduced_index(v2), get_reduced_index(v1)}});
90 boundary_corners.push_back({{f * nvpf + lv2, f * nvpf + lv}});
91 }
92 }
93
94 // Compute simple loops
95 logger().trace("[close_small_holes] chain edges into simple loops");
96 std::vector<std::vector<Index>> loops;
97 {
98 ChainEdgesOptions opt;
99 opt.output_edge_index = true;
100 auto result = chain_directed_edges<Index>(
101 {reinterpret_cast<Index*>(boundary_edges.data()), boundary_edges.size() * 2},
102 opt);
103 loops = std::move(result.loops);
104 }
105
106 auto get_from_corner = [](const auto& matrix, Index c) {
107 const Index _nvpf = static_cast<Index>(matrix.cols());
108 return matrix(c / _nvpf, c % _nvpf);
109 };
110
111 // Compute which hole can be filled with a single triangle,
112 // and which hole needs an additional vertex at its barycenter
113 std::vector<bool> needs_barycenter(loops.size(), false);
114 for (const auto& name : mesh.get_indexed_attribute_names()) {
115 const auto& attr = mesh.get_indexed_attribute(name);
116 const auto& indices = std::get<1>(attr);
117 for (size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
118 const auto& loop = loops[loop_id];
119 if (loop.size() <= max_hole_size && loop.size() <= 3) {
120 assert(loop.size() == 3); // holes of size 1 or 2 shouldn't be possible
121 for (Index lv = 0; lv < 3; ++lv) {
122 const Index c_prev = boundary_corners[loop[lv]][1];
123 const Index c_next = boundary_corners[loop[(lv + 1) % 3]][0];
124 const Index idx_prev = get_from_corner(indices, c_prev);
125 const Index idx_next = get_from_corner(indices, c_next);
126 if (idx_prev != idx_next) {
127 needs_barycenter[loop_id] = true;
128 }
129 }
130 }
131 }
132 }
133
134 // Compute hole barycenters + facets
135 logger().trace("[close_small_holes] compute hole barycenters + facets");
136 using Triangle = std::array<Index, 3>;
137 std::vector<Scalar> values;
138 std::vector<Triangle> new_facets;
139 eigen_to_flat_vector(mesh.get_vertices(), values, Eigen::RowMajor);
140 for (size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
141 const auto& loop = loops[loop_id];
142 if (loop.size() <= max_hole_size) {
143 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
144 assert(loop.size() == 3); // holes of size 1 or 2 shouldn't be possible
145 const Index v0 = reduced_to_vertex[boundary_edges[loop[0]][0]];
146 const Index v1 = reduced_to_vertex[boundary_edges[loop[1]][0]];
147 const Index v2 = reduced_to_vertex[boundary_edges[loop[2]][0]];
148 new_facets.push_back({{v0, v1, v2}});
149 } else {
150 // Hole with a barycenter
151 const Index vc = (Index)values.size() / dim;
152 values.insert(values.end(), dim, 0);
153 assert(values.size() % dim == 0);
154 for (auto e : loop) {
155 const Index vi = reduced_to_vertex[boundary_edges[e][0]];
156 const Index vj = reduced_to_vertex[boundary_edges[e][1]];
157 for (Index k = 0; k < dim; ++k) {
158 values[vc * dim + k] += mesh.get_vertices()(vi, k);
159 }
160 new_facets.push_back({{vi, vj, vc}});
161 }
162 for (Index k = 0; k < dim; ++k) {
163 values[vc * dim + k] /= static_cast<Scalar>(loop.size());
164 }
165 }
166 }
167 }
168
169 // Append elements to existing mesh
170 logger().trace("[close_small_holes] append new facets");
171 VertexArray vertices;
172 flat_vector_to_eigen(values, vertices, (Index)values.size() / dim, dim, Eigen::RowMajor);
173
174 FacetArray facets(num_facets + new_facets.size(), 3);
175 facets.topRows(num_facets) = mesh.get_facets();
176 for (Index f = 0; f < (Index)new_facets.size(); ++f) {
177 facets.row(num_facets + f) << new_facets[f][0], new_facets[f][1], new_facets[f][2];
178 }
179 auto new_mesh = lagrange::create_mesh(std::move(vertices), std::move(facets));
180
181 // Remap vertex attributes
182 for (const auto& name : mesh.get_vertex_attribute_names()) {
183 const auto& attr = mesh.get_vertex_attribute(name);
184 AttributeArray vals(new_mesh->get_num_vertices(), attr.cols());
185 vals.topRows(attr.rows()) = attr;
186 Index counter = mesh.get_num_vertices();
187 for (size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
188 const auto& loop = loops[loop_id];
189 if (loop.size() <= max_hole_size) {
190 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
191 // Nothing to do
192 } else {
193 // Compute average attribute value
194 vals.row(counter).setZero();
195 for (auto e : loop) {
196 const Index vi = reduced_to_vertex[boundary_edges[e][0]];
197 vals.row(counter) += attr.row(vi);
198 }
199 vals.row(counter) /= static_cast<Scalar>(loop.size());
200 ++counter;
201 }
202 }
203 }
204 new_mesh->add_vertex_attribute(name);
205 new_mesh->import_vertex_attribute(name, std::move(vals));
206 }
207
208 // Remap facet attributes
209 for (const auto& name : mesh.get_facet_attribute_names()) {
210 const auto& attr = mesh.get_facet_attribute(name);
211 AttributeArray vals(new_mesh->get_num_facets(), attr.cols());
212 vals.topRows(attr.rows()) = attr;
213 Index counter = mesh.get_num_facets();
214 for (size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
215 const auto& loop = loops[loop_id];
216 if (loop.size() <= max_hole_size) {
217 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
218 // Average attribute from opposite facets
219 vals.row(counter).setZero();
220 for (Index i = 0; i < 3; ++i) {
221 const Index c = boundary_corners[loop[i]][0];
222 vals.row(counter) += vals.row(c / nvpf);
223 }
224 vals.row(counter) /= 3;
225 ++counter;
226 } else {
227 // Copy attributes from opposite facets
228 for (auto e : loop) {
229 const Index c = boundary_corners[e][0];
230 vals.row(counter++) = attr.row(c / nvpf);
231 }
232 }
233 }
234 }
235 new_mesh->add_facet_attribute(name);
236 new_mesh->import_facet_attribute(name, std::move(vals));
237 }
238
239 // Remap edge attributes (new API, average values)
240 for (const auto& name : mesh.get_edge_attribute_names()) {
241 new_mesh->initialize_edge_data();
242 const auto& attr = mesh.get_edge_attribute(name);
243 AttributeArray vals(new_mesh->get_num_edges(), attr.cols());
244 vals.setZero();
245
246 // Remap old values
247 for (Index f = 0; f < mesh.get_num_facets(); ++f) {
248 for (Index lv = 0; lv < nvpf; ++lv) {
249 const Index old_e = mesh.get_edge(f, lv);
250 const Index new_e = new_mesh->get_edge(f, lv);
251 vals.row(new_e) = attr.row(old_e);
252 }
253 }
254
255 // Compute new values
256 Index facet_counter = mesh.get_num_facets();
257 for (size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
258 const auto& loop = loops[loop_id];
259 if (loop.size() <= max_hole_size) {
260 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
261 // Nothing to do
262 facet_counter++;
263 } else {
264 // Copy attributes from opposite facets
265 for (auto e : loop) {
266 const Index v0 = reduced_to_vertex[boundary_edges[e][0]];
267 (void)v0;
268 const Index c = boundary_corners[e][1];
269 const Index f = c / nvpf;
270 (void)f;
271 assert(f == boundary_corners[e][0] / nvpf);
272 const Index lv = c % nvpf;
273 (void)lv;
274 la_debug_assert(mesh.get_facets()(f, (lv + 1) % nvpf) == v0);
275 const Index e0 = new_mesh->get_edge(facet_counter, 0);
276 const Index e1 = new_mesh->get_edge(facet_counter, 1);
277 const Index e2 = new_mesh->get_edge(facet_counter, 2);
278 assert(e0 == new_mesh->get_edge(f, lv));
279 vals.row(e1) += Scalar(0.5) * vals.row(e0);
280 vals.row(e2) += Scalar(0.5) * vals.row(e0);
281 facet_counter++;
282 }
283 }
284 }
285 }
286 new_mesh->add_edge_attribute(name);
287 new_mesh->import_edge_attribute(name, std::move(vals));
288 }
289
290 // Remap corner attributes
291 for (const auto& name : mesh.get_corner_attribute_names()) {
292 const auto& attr = mesh.get_corner_attribute(name);
293 AttributeArray vals(new_mesh->get_num_facets() * nvpf, attr.cols());
294 vals.topRows(attr.rows()) = attr;
295 Index counter = mesh.get_num_facets();
296 for (size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
297 const auto& loop = loops[loop_id];
298 if (loop.size() <= max_hole_size) {
299 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
300 assert(loop.size() == 3); // holes of size 1 or 2 shouldn't be possible
301 // Average contributions from opposite corners
302 const auto c01 = boundary_corners[loop[0]];
303 const auto c12 = boundary_corners[loop[1]];
304 const auto c20 = boundary_corners[loop[2]];
305 const Scalar w = 0.5;
306 vals.row(counter * nvpf + 0) = w * (vals.row(c01[0]) + vals.row(c20[1]));
307 vals.row(counter * nvpf + 1) = w * (vals.row(c12[0]) + vals.row(c01[1]));
308 vals.row(counter * nvpf + 2) = w * (vals.row(c20[0]) + vals.row(c12[1]));
309 ++counter;
310 } else {
311 // Copy attributes from opposite facets, and average corner attributes in the
312 // barycenter
313 const Index shared_corner = counter * nvpf + 2;
314 vals.row(shared_corner).setZero();
315 for (auto e : loop) {
316 const auto c = boundary_corners[e];
317 vals.row(counter * nvpf + 0) = vals.row(c[0]);
318 vals.row(counter * nvpf + 1) = vals.row(c[1]);
319 vals.row(shared_corner) += vals.row(c[0]);
320 vals.row(shared_corner) += vals.row(c[1]);
321 counter++;
322 }
323 vals.row(shared_corner) /= static_cast<Scalar>(2 * loop.size());
324 for (Index i = 0; i < (Index)loop.size(); ++i) {
325 vals.row(shared_corner + i * nvpf) = vals.row(shared_corner);
326 }
327 }
328 }
329 }
330 new_mesh->add_corner_attribute(name);
331 new_mesh->import_corner_attribute(name, std::move(vals));
332 }
333
334 // Remap indexed attributes
335 // TODO: There should be way to iterate over attributes without going through a std::string
336 std::vector<Index> indices;
337 DisjointSets<Index> groups;
338 std::vector<Index> group_color;
339 std::vector<Index> group_sizes;
340 std::vector<Scalar> group_means;
341 for (const auto& name : mesh.get_indexed_attribute_names()) {
342 const auto& attr = mesh.get_indexed_attribute(name);
343 const Index num_coords = static_cast<Index>(std::get<0>(attr).cols());
344 logger().trace("[close_small_holes] remaping indexed attribute: {}", name);
345 eigen_to_flat_vector(std::get<0>(attr), values, Eigen::RowMajor);
346 eigen_to_flat_vector(std::get<1>(attr), indices, Eigen::RowMajor);
347 for (size_t loop_id = 0; loop_id < loops.size(); ++loop_id) {
348 const auto& loop = loops[loop_id];
349 if (loop.size() <= max_hole_size) {
350 if (loop.size() == 3 && !needs_barycenter[loop_id]) {
351 assert(loop.size() == 3); // holes of size 1 or 2 shouldn't be possible
352 indices.push_back(indices[boundary_corners[loop[0]][0]]);
353 indices.push_back(indices[boundary_corners[loop[1]][0]]);
354 indices.push_back(indices[boundary_corners[loop[2]][0]]);
355 } else {
356 // Compute groups by joining corners that share an attribute index
357 const size_t nv = loop.size(); // number of vertices/edges on the loop
358 groups.init(2 * nv); // number of loop corners
359 for (size_t i = 0; i < loop.size(); ++i) {
360 const size_t j = (i + 1) % loop.size();
361 const Index lci = static_cast<Index>(2 * i + 1);
362 const Index lcj = static_cast<Index>(2 * j + 0);
363 const Index ci = boundary_corners[loop[i]][1];
364 const Index cj = boundary_corners[loop[j]][0];
365 groups.merge(static_cast<Index>(2 * i), static_cast<Index>(2 * i + 1));
366 if (indices[ci] == indices[cj]) {
367 groups.merge(lci, lcj);
368 }
369 }
370 // Compute average attribute value per group
371 const Index num_groups =
372 static_cast<Index>(groups.extract_disjoint_set_indices(group_color));
373 logger().trace("num groups: {}", num_groups);
374 group_sizes.assign(num_groups, 0);
375 group_means.assign(num_groups * num_coords, 0);
376 for (size_t lc = 0; lc < 2 * loop.size(); ++lc) {
377 const Index idx = indices[boundary_corners[loop[lc / 2]][lc % 2]];
378 const Scalar* val = values.data() + idx * num_coords;
379 const Index repr = group_color[lc];
380 group_sizes[repr]++;
381 for (Index k = 0; k < num_coords; ++k) {
382 group_means[repr * num_coords + k] += val[k];
383 }
384 }
385 // Normalize mean value for each group + allocate new attribute vertices
386 const Index offset = static_cast<Index>(values.size()) / num_coords;
387 for (Index g = 0; g < num_groups; ++g) {
388 for (Index k = 0; k < num_coords; ++k) {
389 assert(group_sizes[g] > 0);
390 group_means[g * num_coords + k] /= static_cast<Scalar>(group_sizes[g]);
391 values.push_back(group_means[g * num_coords + k]);
392 }
393 assert(static_cast<Index>(values.size()) % num_coords == 0);
394 }
395 // Allocate new attribute indices
396 for (size_t i = 0; i < loop.size(); ++i) {
397 const Index lci = static_cast<Index>(2 * i + 0);
398 const Index idxi = indices[boundary_corners[loop[i]][0]];
399 const Index idxj = indices[boundary_corners[loop[i]][1]];
400 indices.push_back(idxi);
401 indices.push_back(idxj);
402 indices.push_back(offset + (group_color[lci] + 0) % 3);
403 }
404 }
405 }
406 }
407 size_t num_values = values.size() / num_coords;
408 size_t num_indices = indices.size() / nvpf;
409 AttributeArray values_;
410 FacetArray indices_;
411 flat_vector_to_eigen(values, values_, num_values, num_coords, Eigen::RowMajor);
412 flat_vector_to_eigen(indices, indices_, num_indices, nvpf, Eigen::RowMajor);
413 new_mesh->add_indexed_attribute(name);
414 new_mesh->import_indexed_attribute(name, std::move(values_), std::move(indices_));
415 }
416
417 logger().trace("[close_small_holes] cleanup");
418 return new_mesh;
419}
420
421} // namespace legacy
422} // namespace lagrange
Definition: Mesh.h:48
bool is_boundary_edge(Index e) const
Determines whether the specified edge e is a boundary edge.
Definition: SurfaceMesh.cpp:2615
Index get_num_vertices() const
Retrieves the number of vertices.
Definition: SurfaceMesh.h:671
Index get_edge(Index f, Index lv) const
Gets the edge index corresponding to (f, lv) – (f, lv+1).
Definition: SurfaceMesh.cpp:2435
auto get_indexed_attribute(std::string_view name) const -> const IndexedAttribute< ValueType, Index > &
Gets a read-only reference to an indexed attribute given its name.
Definition: SurfaceMesh.cpp:1310
Index get_num_edges() const
Retrieves the number of edges.
Definition: SurfaceMesh.h:692
Index get_one_corner_around_edge(Index e) const
Get the index of one corner around a given edge.
Definition: SurfaceMesh.cpp:2603
Index get_num_facets() const
Retrieves the number of facets.
Definition: SurfaceMesh.h:678
Index get_vertex_per_facet() const
Retrieves the number of vertex per facet in a regular mesh.
Definition: SurfaceMesh.cpp:2118
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
void close_small_holes(SurfaceMesh< Scalar, Index > &mesh, CloseSmallHolesOptions options={})
Close small topological holes.
Definition: close_small_holes.cpp:538