Merge pull request #6772 from sloriot/PMP-remove_caps_needles_doc

Document remove_almost_degenerate_faces()
This commit is contained in:
Sebastien Loriot 2022-08-10 18:31:52 +02:00 committed by GitHub
commit d64d243bf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 268 additions and 140 deletions

View File

@ -15,6 +15,8 @@ Release date: December 2022
- Added the function `CGAL::Polygon_mesh_processing::surface_Delaunay_remeshing()`, that remeshes a surface triangle mesh following the
CGAL tetrahedral Delaunay refinement algorithm.
- Added the function `CGAL::Polygon_mesh_processing::remove_almost_degenerate_faces()` to remove badly shaped triangles faces in a mesh.
### [3D Simplicial Mesh Data Structure](https://doc.cgal.org/5.6/Manual/packages.html#PkgSMDS3) (new package)
- This new package wraps all the existing code that deals with a `MeshComplex_3InTriangulation_3` to describe 3D simplicial meshess, and makes the data structure independent from the tetrahedral mesh generation package.

View File

@ -179,6 +179,7 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage.
- `CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices()`
- `CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycle()`
- `CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycles()`
- `CGAL::Polygon_mesh_processing::remove_almost_degenerate_faces()`
\cgalCRPSection{Connected Components}
- `CGAL::Polygon_mesh_processing::connected_component()`

View File

@ -835,6 +835,15 @@ more than once (although, with different vertices) before reaching the initial b
`CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycle()`, which merge
vertices at identical positions, can be used to repair this configuration.
\subsection PMPRemoveCapsNeedles Removal of Almost Degenerate Triangle Faces
Triangle faces of a mesh made up of almost collinear points are badly shaped elements that
might not be desirable to have in a mesh. The function
`CGAL::Polygon_mesh_processing::remove_almost_degenerate_faces()` enables removing such elements,
with user-defined parameters to qualify what <i>almost</i> means (`cap_threshold` and `needle_threshold`).
As some badly shaped elements are inevitable (the triangulation of a long cylinder
with only vertices on the top and bottom circles for example), extra parameters can be passed
to prevent the removal of such elements (`collapse_length_threshold` and `flip_triangle_height_threshold`).
****************************************
\section PMPNormalComp Computing Normals

View File

@ -536,15 +536,95 @@ struct Filter_wrapper_for_cap_needle_removal<TriangleMesh, VPM, Traits, Identity
} // namespace internal
namespace experimental {
// @todo check what to use as priority queue with removable elements, set might not be optimal
/// \ingroup PMP_repairing_grp
///
/// removes almost degenerate faces in a range of faces from a triangulated surface mesh.
/// Almost degenerated triangle faces are classified as caps or needles: a triangle is said to be a <i>needle</i>
/// if its longest edge is much longer than its shortest edge. A triangle is said to be a <i>cap</i> if one of
/// its angles is close to `180` degrees. Needles are removed by collapsing their shortest edges, while caps are
/// removed by flipping the edge opposite to the largest angle (with the exception of caps on the boundary that are
/// simply removed from the mesh).
///
/// @pre `CGAL::is_triangle_mesh(tmesh)`
///
/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph`
/// @tparam FaceRange a model of `ConstRange` with `boost::graph_traits<TriangleMesh>::%face_descriptor` as value type
/// @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
///
/// @param face_range the initial range of faces to be considered to look for badly shaped triangles.
/// Note that modifications of `tmesh` are not limited to faces in `face_range`
/// and neighbor faces might also be impacted.
/// @param tmesh the triangulated surface mesh to be modified
/// @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
///
/// \cgalNamedParamsBegin
/// \cgalParamNBegin{cap_threshold}
/// \cgalParamDescription{the cosine of a minimum angle such that if a face has an angle greater than this bound,
/// it is a cap. The threshold is in range `[-1 0]` and corresponds to an angle between `90` and `180` degrees.}
/// \cgalParamType{double}
/// \cgalParamDefault{the cosinus corresponding to an angle of 160 degrees}
/// \cgalParamNEnd
/// \cgalParamNBegin{needle_threshold}
/// \cgalParamDescription{a bound on the ratio of the lengths of the longest edge and the shortest edge, such that a face having a ratio
/// larger than the threshold is a needle.}
/// \cgalParamType{double}
/// \cgalParamDefault{4}
/// \cgalParamNEnd
/// \cgalParamNBegin{collapse_length_threshold}
/// \cgalParamDescription{if different from 0, an edge collapsed will be prevented if the edge is longer than the threshold given.}
/// \cgalParamType{double}
/// \cgalParamDefault{0}
/// \cgalParamNEnd
/// \cgalParamNBegin{flip_triangle_height_threshold}
/// \cgalParamDescription{if different from 0, an edge flip will be prevented if the height of the triangle (whose base is the edge to be flipped)
/// is longer than the threshold given.}
/// \cgalParamType{double}
/// \cgalParamDefault{0}
/// \cgalParamNEnd
/// \cgalParamNBegin{vertex_point_map}
/// \cgalParamDescription{a property map associating points to the vertices of `tmesh`.}
/// \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits<TriangleMesh>::%vertex_descriptor`
/// as key type and `%Point_3` as value type}
/// \cgalParamDefault{`boost::get(CGAL::vertex_point, tmesh)`.}
/// \cgalParamNEnd
/// \cgalParamNBegin{geom_traits}
/// \cgalParamDescription{an instance of a geometric traits class.}
/// \cgalParamType{A model of `Kernel`.}
/// \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`.}
/// \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.}
/// \cgalParamNEnd
/// \cgalParamNBegin{edge_is_constrained_map}
/// \cgalParamDescription{a property map containing the constrained-or-not status of each edge of `tmesh`.}
/// \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits<PolygonMesh>::%edge_descriptor`
/// as key type and `bool` as value type.}
/// \cgalParamDefault{a default property map where no edge is constrained.}
/// \cgalParamExtra{A constrained edge can not be collapsed nor flipped.}
/// \cgalParamNEnd
/// \cgalParamNBegin{vertex_is_constrained_map}
/// \cgalParamDescription{a property map containing the constrained-or-not status of each vertex of `tmesh`.}
/// \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits<PolygonMesh>::%vertex_descriptor`
/// as key type and `bool` as value type.}
/// \cgalParamDefault{a default property map where no vertex is constrained.}
/// \cgalParamExtra{A constrained vertex is guaranteed to be present in `tmesh` after the function call.}
/// \cgalParamNEnd
/// \cgalParamNBegin{filter}
/// \cgalParamDescription{A function object providing `bool operator()(geom_traits::Point_3,geom_traits::Point_3,geom_traits::Point_3)`.}
/// \cgalParamType{The function object is queried each time a new triangle is about to be created by a flip or a collapse operation.
/// If `false` is returned, the operation is cancelled.}
/// \cgalParamDefault{a functor always returning `true`.}
/// \cgalParamNEnd
/// \cgalNamedParamsEnd
///
/// \return `true` if no almost degenerate face could not be removed (due to topological constraints), and `false` otherwise.
///
/// \sa `is_needle_triangle_face()`
/// \sa `is_cap_triangle_face()`
///
/// @todo check what to use as priority queue with removable elements, set might not be optimal
///
template <typename FaceRange, typename TriangleMesh, typename NamedParameters = parameters::Default_named_parameters>
bool remove_almost_degenerate_faces(const FaceRange& face_range,
TriangleMesh& tmesh,
const double cap_threshold,
const double needle_threshold,
const double collapse_length_threshold,
const NamedParameters& np = parameters::default_values())
{
using CGAL::parameters::choose_parameter;
@ -589,6 +669,13 @@ bool remove_almost_degenerate_faces(const FaceRange& face_range,
typedef typename boost::property_map<TriangleMesh, Vertex_property_tag>::type DVCM;
DVCM vcm = get(Vertex_property_tag(), tmesh);
// parameters
const double cap_threshold =
choose_parameter(get_parameter(np, internal_np::cap_threshold), -0.939692621); // cos(160)
const double needle_threshold =
choose_parameter(get_parameter(np, internal_np::needle_threshold), 4.);
const double collapse_length_threshold =
choose_parameter(get_parameter(np, internal_np::collapse_length_threshold), 0.);
const double flip_triangle_height_threshold_squared =
CGAL::square(choose_parameter(get_parameter(np, internal_np::flip_triangle_height_threshold), 0));
@ -936,18 +1023,16 @@ bool remove_almost_degenerate_faces(const FaceRange& face_range,
return false;
}
/// \ingroup PMP_repairing_grp
/// removes all almost degenerate faces from a triangulated surface mesh.
/// Equivalent to `remove_almost_degenerate_faces(faces(tmesh), tmesh, np)`
template <typename TriangleMesh, typename CGAL_NP_TEMPLATE_PARAMETERS>
bool remove_almost_degenerate_faces(TriangleMesh& tmesh,
const double cap_threshold,
const double needle_threshold,
const double collapse_length_threshold,
const CGAL_NP_CLASS& np = parameters::default_values())
{
return remove_almost_degenerate_faces(faces(tmesh), tmesh, cap_threshold, needle_threshold,
collapse_length_threshold, np);
return remove_almost_degenerate_faces(faces(tmesh), tmesh, np);
}
} // namespace experimental
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -388,6 +388,7 @@ struct Is_edge_length_ratio_over_threshold<K, true>
/// If the face contains degenerate edges, a halfedge corresponding to one of these edges is returned.
///
/// \sa `is_cap_triangle_face()`
/// \sa `remove_almost_degenerate_faces()`
template <typename TriangleMesh, typename NamedParameters = parameters::Default_named_parameters>
typename boost::graph_traits<TriangleMesh>::halfedge_descriptor
is_needle_triangle_face(typename boost::graph_traits<TriangleMesh>::face_descriptor f,
@ -536,6 +537,7 @@ struct Is_cap_angle_over_threshold<K, true>
/// \return the halfedge opposite of the largest angle if the face is a cap, and a null halfedge otherwise.
///
/// \sa `is_needle_triangle_face()`
/// \sa `remove_almost_degenerate_faces()`
template <typename TriangleMesh, typename NamedParameters = parameters::Default_named_parameters>
typename boost::graph_traits<TriangleMesh>::halfedge_descriptor
is_cap_triangle_face(typename boost::graph_traits<TriangleMesh>::face_descriptor f,

View File

@ -18,6 +18,7 @@ typedef boost::graph_traits<Mesh>::edge_descriptor edge_descriptor;
typedef boost::graph_traits<Mesh>::face_descriptor face_descriptor;
namespace PMP = CGAL::Polygon_mesh_processing;
namespace params = CGAL::parameters;
typedef CGAL::Polyhedral_envelope<K> Envelope;
@ -36,10 +37,10 @@ void general_test(std::string filename)
if (PMP::does_self_intersect(mesh))
std::cout << " Input mesh has self-intersections\n";
PMP::experimental::remove_almost_degenerate_faces(mesh,
std::cos(160. / 180 * CGAL_PI),
4,
0.14);
PMP::remove_almost_degenerate_faces(mesh,
params::cap_threshold(std::cos(160. / 180 * CGAL_PI))
.needle_threshold(4)
.collapse_length_threshold(0.14));
CGAL::IO::write_polygon_mesh("cleaned_mesh.off", mesh, CGAL::parameters::stream_precision(17));
@ -70,11 +71,11 @@ void test_with_envelope(std::string filename, double eps)
};
No_modification_allowed no_modif;
const std::size_t nbv = vertices(mesh).size();
PMP::experimental::remove_almost_degenerate_faces(mesh,
std::cos(160. / 180 * CGAL_PI),
4,
0.14,
CGAL::parameters::filter(no_modif));
PMP::remove_almost_degenerate_faces(mesh,
params::cap_threshold(std::cos(160. / 180 * CGAL_PI))
.needle_threshold(4)
.collapse_length_threshold(0.14)
.filter(no_modif));
assert(nbv == vertices(mesh).size());
// now the real test with a fixed envelope
@ -82,11 +83,11 @@ void test_with_envelope(std::string filename, double eps)
std::cout << " Input mesh has " << edges(mesh).size() << " edges\n";
bk=mesh;
Envelope envelope(mesh, eps);
PMP::experimental::remove_almost_degenerate_faces(mesh,
std::cos(160. / 180 * CGAL_PI),
4,
0.14,
CGAL::parameters::filter(std::ref(envelope)));
PMP::remove_almost_degenerate_faces(mesh,
params::cap_threshold(std::cos(160. / 180 * CGAL_PI))
.needle_threshold(4)
.collapse_length_threshold(0.14)
.filter(std::ref(envelope)));
CGAL::IO::write_polygon_mesh("cleaned_mesh_with_envelope.off", mesh, CGAL::parameters::stream_precision(17));
@ -104,11 +105,11 @@ void test_with_envelope(std::string filename, double eps)
return Envelope(frange, mesh, eps);
};
std::function<Envelope(const std::vector<Mesh::Face_index>&)> filter(create_envelope);
PMP::experimental::remove_almost_degenerate_faces(mesh,
std::cos(160. / 180 * CGAL_PI),
4,
0.14,
CGAL::parameters::filter(filter));
PMP::remove_almost_degenerate_faces(mesh,
params::cap_threshold(std::cos(160. / 180 * CGAL_PI))
.needle_threshold(4)
.collapse_length_threshold(0.14)
.filter(filter));
CGAL::IO::write_polygon_mesh("cleaned_mesh_with_iterative_envelope.off", mesh, CGAL::parameters::stream_precision(17));
@ -137,26 +138,25 @@ void test_parameters_on_pig(std::string filename)
bk=mesh;
PMP::experimental::remove_almost_degenerate_faces(mesh,
std::cos(160. / 180 * CGAL_PI),
4,
9999 /*no_constraints*/);
PMP::remove_almost_degenerate_faces(mesh,
params::cap_threshold(std::cos(160. / 180 * CGAL_PI))
.needle_threshold(4));
assert(vertices(mesh).size()!=vertices(bk).size());
mesh=bk;
PMP::experimental::remove_almost_degenerate_faces(mesh,
std::cos(160. / 180 * CGAL_PI),
4,
0.000000000000001); // no-collapse but flips
PMP::remove_almost_degenerate_faces(mesh,
params::cap_threshold(std::cos(160. / 180 * CGAL_PI))
.needle_threshold(4)
.collapse_length_threshold(0.000000000000001)); // no-collapse but flips
assert(vertices(mesh).size()==vertices(bk).size());
assert(!same_meshes(mesh,bk));
mesh=bk;
PMP::experimental::remove_almost_degenerate_faces(mesh,
std::cos(160. / 180 * CGAL_PI),
4,
0.000000000000001,
CGAL::parameters::flip_triangle_height_threshold(0.000000000000001)); // no-collapse and no flip
PMP::remove_almost_degenerate_faces(mesh,
params::cap_threshold(std::cos(160. / 180 * CGAL_PI))
.needle_threshold(4)
.collapse_length_threshold(0.000000000000001)
.flip_triangle_height_threshold(0.000000000000001)); // no-collapse and no flip
assert(vertices(mesh).size()==vertices(bk).size());
assert(same_meshes(mesh,bk));
}

View File

@ -6,113 +6,138 @@
<rect>
<x>0</x>
<y>0</y>
<width>607</width>
<height>368</height>
<width>744</width>
<height>373</height>
</rect>
</property>
<property name="windowTitle">
<string>Remove Needles and Cap</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="Box1">
<property name="toolTip">
<string>Threshold in degrees</string>
</property>
<property name="title">
<string>Cap threshold (max. angle allowed within a triangle): </string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<widget class="QGroupBox" name="Box1">
<property name="toolTip">
<string>Threshold in degrees</string>
</property>
<property name="title">
<string>Cap threshold (max. angle in degrees allowed within a triangle): </string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QDoubleSpinBox" name="capBox">
<property name="toolTip">
<string>Angle in degrees</string>
</property>
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="value">
<double>160.000000000000000</double>
</property>
</widget>
</item>
</layout>
<widget class="QDoubleSpinBox" name="capBox">
<property name="toolTip">
<string>Angle in degrees</string>
</property>
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="value">
<double>160.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="Box2">
<property name="toolTip">
<string>(size+big edge )/(size+small edge)</string>
</property>
<property name="title">
<string>Needle threshold (max. edge length ratio (longest/shortest) allowed within a triangle):</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="Box2">
<property name="toolTip">
<string>(size+big edge )/(size+small edge)</string>
</property>
<property name="title">
<string>Needle threshold (max. edge length ratio (longest/shortest) allowed within a triangle):</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QDoubleSpinBox" name="needleBox">
<property name="toolTip">
<string>Length Ratio</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>4.000000000000000</double>
</property>
</widget>
</item>
</layout>
<widget class="QDoubleSpinBox" name="needleBox">
<property name="toolTip">
<string>Length Ratio</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>4.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="Box3">
<property name="toolTip">
<string>Do not collapse edges begger than this threshold.</string>
</property>
<property name="title">
<string>Do not collapse needles (according to the criterion above) whose shortest edge has a length greater than:</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="Box3">
<property name="toolTip">
<string>Do not collapse edges begger than this threshold.</string>
</property>
<property name="title">
<string>Do not collapse needle edge larger than:</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QDoubleSpinBox" name="collapseBox">
<property name="toolTip">
<string>Length</string>
</property>
<property name="decimals">
<number>25</number>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
</widget>
</item>
</layout>
<widget class="QDoubleSpinBox" name="collapseBox">
<property name="toolTip">
<string>Length</string>
</property>
<property name="decimals">
<number>25</number>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="Box4">
<property name="toolTip">
<string>Do not collapse edges begger than this threshold.</string>
</property>
<property name="title">
<string>Do not flip cap edge (according to the criterion above) if height is greater than:</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QDoubleSpinBox" name="flipBox">
<property name="toolTip">
<string>Length</string>
</property>
<property name="decimals">
<number>25</number>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">

View File

@ -195,10 +195,11 @@ void Polyhedron_demo_repair_polyhedron_plugin::on_actionRemoveNeedlesAndCaps_tri
ui.collapseBox->setValue(sm_item->diagonalBbox()*0.01);
if(dialog.exec() != QDialog::Accepted)
return;
CGAL::Polygon_mesh_processing::experimental::remove_almost_degenerate_faces(*sm_item->face_graph(),
std::cos((ui.capBox->value()/180.0) * CGAL_PI),
ui.needleBox->value(),
ui.collapseBox->value());
CGAL::Polygon_mesh_processing::remove_almost_degenerate_faces(*sm_item->face_graph(),
CGAL::parameters::cap_threshold(std::cos((ui.capBox->value()/180.0) * CGAL_PI))
.needle_threshold(ui.needleBox->value())
.collapse_length_threshold(ui.collapseBox->value())
.flip_triangle_height_threshold(ui.flipBox->value()));
sm_item->invalidateOpenGLBuffers();
sm_item->itemChanged();
}

View File

@ -132,7 +132,10 @@ CGAL_add_named_parameter(match_faces_t, match_faces, match_faces)
CGAL_add_named_parameter(face_epsilon_map_t, face_epsilon_map, face_epsilon_map)
CGAL_add_named_parameter(maximum_number_t, maximum_number, maximum_number)
CGAL_add_named_parameter(use_one_sided_hausdorff_t, use_one_sided_hausdorff, use_one_sided_hausdorff)
CGAL_add_named_parameter(cap_threshold_t, cap_threshold, cap_threshold)
CGAL_add_named_parameter(needle_threshold_t, needle_threshold, needle_threshold)
CGAL_add_named_parameter(flip_triangle_height_threshold_t, flip_triangle_height_threshold, flip_triangle_height_threshold)
CGAL_add_named_parameter(collapse_length_threshold_t, collapse_length_threshold, collapse_length_threshold)
CGAL_add_named_parameter(features_angle_bound_t, features_angle_bound, features_angle_bound)
CGAL_add_named_parameter(mesh_edge_size_t, mesh_edge_size, mesh_edge_size)
CGAL_add_named_parameter(mesh_facet_size_t, mesh_facet_size, mesh_facet_size)