1 // Copyright (c) 2015 GeometryFactory (France).
2 // All rights reserved.
3 //
4 // This file is part of CGAL (www.cgal.org).
5 //
6 // $URL: https://github.com/CGAL/cgal/blob/v5.3/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h $
7 // $Id: repair.h 40338b2 2020-10-09T16:50:29+02:00 Laurent Rineau
8 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
9 //
10 //
11 // Author(s) : Sebastien Loriot,
12 // Mael Rouxel-Labbé
13
14 #ifndef CGAL_POLYGON_MESH_PROCESSING_REPAIR_H
15 #define CGAL_POLYGON_MESH_PROCESSING_REPAIR_H
16
17 #include <CGAL/license/Polygon_mesh_processing/repair.h>
18
19 #include <CGAL/Polygon_mesh_processing/manifoldness.h>
20 #include <CGAL/Polygon_mesh_processing/repair_degeneracies.h>
21 #include <CGAL/Polygon_mesh_processing/repair_self_intersections.h>
22 #include <CGAL/Polygon_mesh_processing/bbox.h>
23 #include <CGAL/Polygon_mesh_processing/measure.h>
24
25 #include <CGAL/boost/graph/iterator.h>
26
27 #include <vector>
28
29 namespace CGAL {
30 namespace Polygon_mesh_processing {
31
32 /// \ingroup PMP_repairing_grp
33 /// removes the isolated vertices from any polygon mesh.
34 /// A vertex is considered isolated if it is not incident to any simplex
35 /// of higher dimension.
36 ///
37 /// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`
38 ///
39 /// @param pmesh the polygon mesh to be repaired
40 ///
41 /// @return number of removed isolated vertices
42 ///
43 template <class PolygonMesh>
remove_isolated_vertices(PolygonMesh & pmesh)44 std::size_t remove_isolated_vertices(PolygonMesh& pmesh)
45 {
46 typedef typename boost::graph_traits<PolygonMesh>::vertex_descriptor vertex_descriptor;
47 std::vector<vertex_descriptor> to_be_removed;
48
49 for(vertex_descriptor v : vertices(pmesh))
50 {
51 if (CGAL::halfedges_around_target(v, pmesh).first
52 == CGAL::halfedges_around_target(v, pmesh).second)
53 to_be_removed.push_back(v);
54 }
55 std::size_t nb_removed = to_be_removed.size();
56 for(vertex_descriptor v : to_be_removed)
57 {
58 remove_vertex(v, pmesh);
59 }
60 return nb_removed;
61 }
62
63 /// \ingroup PMP_repairing_grp
64 ///
65 /// removes connected components whose area or volume is under a certain threshold value.
66 ///
67 /// Thresholds are provided via \ref bgl_namedparameters "Named Parameters". (see below).
68 /// If thresholds are not provided by the user, default values are computed as follows:
69 /// - the area threshold is taken as the square of one percent of the length of the diagonal
70 /// of the bounding box of the mesh.
71 /// - the volume threshold is taken as the third power of one percent of the length of the diagonal
72 /// of the bounding box of the mesh.
73 ///
74 /// The area and volume of a connected component will always be positive values (regardless
75 /// of the orientation of the mesh).
76 ///
77 /// As a consequence of the last sentence, the area or volume criteria can be disabled
78 /// by passing zero (`0`) as threshold value.
79 ///
80 /// \tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph`
81 /// \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
82 ///
83 /// \param tmesh the triangulated polygon mesh
84 /// \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
85 ///
86 /// \cgalNamedParamsBegin
87 /// \cgalParamNBegin{vertex_point_map}
88 /// \cgalParamDescription{a property map associating points to the vertices of `tmesh`}
89 /// \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits<TriangleMesh>::%vertex_descriptor`
90 /// as key type and `%Point_3` as value type}
91 /// \cgalParamDefault{`boost::get(CGAL::vertex_point, tmesh)`}
92 /// \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t`
93 /// must be available in `TriangleMesh`.}
94 /// \cgalParamNEnd
95 ///
96 /// \cgalParamNBegin{geom_traits}
97 /// \cgalParamDescription{an instance of a geometric traits class}
98 /// \cgalParamType{a class model of `Kernel`}
99 /// \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`}
100 /// \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.}
101 /// \cgalParamExtra{Exact constructions kernels are not supported by this function.}
102 /// \cgalParamNEnd
103 ///
104 /// \cgalParamNBegin{face_index_map}
105 /// \cgalParamDescription{a property map associating to each face of `tmesh` a unique index between `0` and `num_faces(tmesh) - 1`}
106 /// \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits<TriangleMesh>::%face_descriptor`
107 /// as key type and `std::size_t` as value type}
108 /// \cgalParamDefault{an automatically indexed internal map}
109 /// \cgalParamNEnd
110 ///
111 /// \cgalParamNBegin{area_threshold}
112 /// \cgalParamDescription{a fixed value such that only connected components whose area is larger than this value are kept}
113 /// \cgalParamType{`geom_traits::FT`}
114 /// \cgalParamDefault{1\% of the length of the diagonal of the axis-aligned bounding box of the mesh, squared}
115 /// \cgalParamNEnd
116 ///
117 /// \cgalParamNBegin{volume_threshold}
118 /// \cgalParamDescription{a fixed value such that only connected components whose volume is
119 /// larger than this value are kept (only applies to closed connected components)}
120 /// \cgalParamType{`geom_traits::FT`}
121 /// \cgalParamDefault{1\% of the length of the diagonal of the axis-aligned bounding box of the mesh, cubed}
122 /// \cgalParamExtra{The mesh must be closed.}
123 /// \cgalParamNEnd
124 ///
125 /// \cgalParamNBegin{edge_is_constrained_map}
126 /// \cgalParamDescription{a property map containing the constrained-or-not status of each edge of `tmesh`}
127 /// \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits<TriangleMesh>::%edge_descriptor`
128 /// as key type and `bool` as value type. It must be default constructible.}
129 /// \cgalParamDefault{a default property map where no edge is constrained}
130 /// \cgalParamExtra{A constrained edge can be split or collapsed, but not flipped, nor its endpoints moved by smoothing.}
131 /// \cgalParamNEnd
132 ///
133 /// \cgalParamNBegin{dry_run}
134 /// \cgalParamDescription{If `true`, the mesh will not be altered, but the number of components
135 /// that would be removed is returned.}
136 /// \cgalParamType{Boolean}
137 /// \cgalParamDefault{`false`}
138 /// \cgalParamNEnd
139 ///
140 /// \cgalParamNBegin{output_iterator}
141 /// \cgalParamDescription{An output iterator to collect the faces that would be removed by the algorithm,
142 /// when using the "dry run" mode (see parameter `dry_run`)}
143 /// \cgalParamType{a model of `OutputIterator` with value type `face_descriptor`}
144 /// \cgalParamDefault{unused}
145 /// \cgalParamNEnd
146 /// \cgalNamedParamsEnd
147 ///
148 /// \return the number of connected components removed (ignoring isolated vertices).
149 ///
150 template <typename TriangleMesh,
151 typename NamedParameters>
remove_connected_components_of_negligible_size(TriangleMesh & tmesh,const NamedParameters & np)152 std::size_t remove_connected_components_of_negligible_size(TriangleMesh& tmesh,
153 const NamedParameters& np)
154 {
155 using parameters::choose_parameter;
156 using parameters::is_default_parameter;
157 using parameters::get_parameter;
158
159 typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
160 typedef typename boost::graph_traits<TriangleMesh>::face_descriptor face_descriptor;
161
162 typedef typename GetGeomTraits<TriangleMesh, NamedParameters>::type GT;
163 typedef typename GT::FT FT;
164 const GT traits = choose_parameter<GT>(get_parameter(np, internal_np::vertex_point));
165
166 typedef typename GetVertexPointMap<TriangleMesh, NamedParameters>::const_type VPM;
167 typedef typename boost::property_traits<VPM>::value_type Point_3;
168 const VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point),
169 get_const_property_map(CGAL::vertex_point, tmesh));
170
171 typedef typename GetInitializedFaceIndexMap<TriangleMesh, NamedParameters>::type FaceIndexMap;
172 FaceIndexMap fim = CGAL::get_initialized_face_index_map(tmesh, np);
173
174 FT area_threshold = choose_parameter(get_parameter(np, internal_np::area_threshold), FT(-1));
175 FT volume_threshold = choose_parameter(get_parameter(np, internal_np::volume_threshold), FT(-1));
176
177 // If no threshold is provided, compute it as a % of the bbox
178 const bool is_default_area_threshold = is_default_parameter(get_parameter(np, internal_np::area_threshold));
179 const bool is_default_volume_threshold = is_default_parameter(get_parameter(np, internal_np::volume_threshold));
180
181 const bool dry_run = choose_parameter(get_parameter(np, internal_np::dry_run), false);
182
183 typedef typename internal_np::Lookup_named_param_def<internal_np::output_iterator_t,
184 NamedParameters,
185 Emptyset_iterator>::type Output_iterator;
186 Output_iterator out = choose_parameter<Output_iterator>(get_parameter(np, internal_np::output_iterator));
187
188 #ifdef CGAL_PMP_DEBUG_SMALL_CC_REMOVAL
189 std::cout << "default threshold? " << is_default_area_threshold << " " << is_default_volume_threshold << std::endl;
190 #endif
191
192 FT bbox_diagonal = FT(0), threshold_value = FT(0);
193
194 if(is_default_area_threshold || is_default_volume_threshold)
195 {
196 if(is_empty(tmesh))
197 return 0;
198
199 const Bbox_3 bb = bbox(tmesh, np);
200
201 bbox_diagonal = FT(CGAL::sqrt(CGAL::square(bb.xmax() - bb.xmin()) +
202 CGAL::square(bb.ymax() - bb.ymin()) +
203 CGAL::square(bb.zmax() - bb.zmin())));
204 threshold_value = bbox_diagonal / FT(100); // default filter is 1%
205
206 #ifdef CGAL_PMP_DEBUG_SMALL_CC_REMOVAL
207 std::cout << "bb xmin xmax: " << bb.xmin() << " " << bb.xmax() << std::endl;
208 std::cout << "bb ymin ymax: " << bb.ymin() << " " << bb.ymax() << std::endl;
209 std::cout << "bb zmin zmax: " << bb.zmin() << " " << bb.zmax() << std::endl;
210 std::cout << "bbox_diagonal: " << bbox_diagonal << std::endl;
211 std::cout << "threshold_value: " << threshold_value << std::endl;
212 #endif
213 }
214
215 if(is_default_area_threshold)
216 area_threshold = CGAL::square(threshold_value);
217
218 if(is_default_volume_threshold)
219 volume_threshold = CGAL::square(threshold_value);
220
221 const bool use_areas = (is_default_area_threshold || area_threshold > 0);
222 const bool use_volumes = (is_default_volume_threshold || volume_threshold > 0);
223
224 if(!use_areas && !use_volumes)
225 return 0;
226
227 // Compute the connected components only once
228 boost::vector_property_map<std::size_t, FaceIndexMap> face_cc(static_cast<unsigned>(num_faces(tmesh)), fim);
229 std::size_t num = connected_components(tmesh, face_cc, np);
230
231 #ifdef CGAL_PMP_DEBUG_SMALL_CC_REMOVAL
232 std::cout << num << " different connected components" << std::endl;
233 #endif
234
235 if(!dry_run)
236 CGAL::Polygon_mesh_processing::remove_isolated_vertices(tmesh);
237
238 // Compute CC-wide and total areas/volumes
239 FT total_area = 0;
240 std::vector<FT> component_areas(num, 0);
241
242 if(use_areas)
243 {
244 for(face_descriptor f : faces(tmesh))
245 {
246 const FT fa = face_area(f, tmesh, np);
247 component_areas[face_cc[f]] += fa;
248 total_area += fa;
249 }
250
251 #ifdef CGAL_PMP_DEBUG_SMALL_CC_REMOVAL
252 std::cout << "area threshold: " << area_threshold << std::endl;
253 std::cout << "total area: " << total_area << std::endl;
254 #endif
255 }
256
257 // Volumes make no sense for CCs that are not closed
258 std::vector<bool> cc_closeness(num, true);
259 std::vector<FT> component_volumes(num, FT(0));
260
261 if(use_volumes)
262 {
263 for(halfedge_descriptor h : halfedges(tmesh))
264 {
265 if(is_border(h, tmesh))
266 cc_closeness[face_cc[face(opposite(h, tmesh), tmesh)]] = false;
267 }
268
269 typename GT::Compute_volume_3 cv3 = traits.compute_volume_3_object();
270 Point_3 origin(0, 0, 0);
271
272 for(face_descriptor f : faces(tmesh))
273 {
274 const std::size_t i = face_cc[f];
275 if(!cc_closeness[i])
276 continue;
277
278 const FT fv = cv3(origin,
279 get(vpm, target(halfedge(f, tmesh), tmesh)),
280 get(vpm, target(next(halfedge(f, tmesh), tmesh), tmesh)),
281 get(vpm, target(prev(halfedge(f, tmesh), tmesh), tmesh)));
282
283 component_volumes[i] += fv;
284 }
285
286 // negative volume means the CC was oriented inward
287 FT total_volume = 0;
288 for(std::size_t i=0; i<num; ++i)
289 {
290 component_volumes[i] = CGAL::abs(component_volumes[i]);
291 total_volume += component_volumes[i];
292 }
293
294 #ifdef CGAL_PMP_DEBUG_SMALL_CC_REMOVAL
295 std::cout << "volume threshold: " << volume_threshold << std::endl;
296 std::cout << "total volume: " << total_volume << std::endl;
297 #endif
298 }
299
300 std::size_t res = 0;
301 std::vector<bool> is_to_be_removed(num, false);
302
303 for(std::size_t i=0; i<num; ++i)
304 {
305 #ifdef CGAL_PMP_DEBUG_SMALL_CC_REMOVAL
306 std::cout << "CC " << i << " has area: " << component_areas[i]
307 << " and volume: " << component_volumes[i] << std::endl;
308 #endif
309
310 if((use_volumes && cc_closeness[i] && component_volumes[i] <= volume_threshold) ||
311 (use_areas && component_areas[i] <= area_threshold))
312 {
313 is_to_be_removed[i] = true;
314 ++res;
315 }
316 }
317
318 #ifdef CGAL_PMP_DEBUG_SMALL_CC_REMOVAL
319 std::cout << "Removing " << res << " CCs" << std::endl;
320 #endif
321
322 if(dry_run)
323 {
324 for(face_descriptor f : faces(tmesh))
325 if(is_to_be_removed[face_cc[f]])
326 *out++ = f;
327 }
328 else
329 {
330 std::vector<std::size_t> ccs_to_remove;
331 for(std::size_t i=0; i<num; ++i)
332 if(is_to_be_removed[i])
333 ccs_to_remove.push_back(i);
334
335 remove_connected_components(tmesh, ccs_to_remove, face_cc, np);
336 CGAL_expensive_postcondition(is_valid_polygon_mesh(tmesh));
337 }
338
339 return res;
340 }
341
342 template <typename TriangleMesh>
remove_connected_components_of_negligible_size(TriangleMesh & tmesh)343 std::size_t remove_connected_components_of_negligible_size(TriangleMesh& tmesh)
344 {
345 return remove_connected_components_of_negligible_size(tmesh, parameters::all_default());
346 }
347
348 } // namespace Polygon_mesh_processing
349 } // namespace CGAL
350
351 #endif // CGAL_POLYGON_MESH_PROCESSING_REPAIR_H
352