Merge pull request #3861 from MaelRL/PMP-Add_stitch_boundary_cycle_singular-GF

PMP: Introduce `stitch_boundary_cycle`
This commit is contained in:
Laurent Rineau 2019-06-17 13:54:39 +02:00
commit e4353afb13
5 changed files with 155 additions and 74 deletions

View File

@ -39,8 +39,12 @@ Release date: September 2019
Parameters.
### Polygon Mesh Processing
- Added the function `CGAL::Polygon_mesh_processing::centroid()` which computes
- Added the function `CGAL::Polygon_mesh_processing::centroid()`, which computes
the centroid of a closed triangle mesh.
- Added the functions `CGAL::Polygon_mesh_processing::stitch_boundary_cycle()` and
`CGAL::Polygon_mesh_processing::stitch_boundary_cycles()`, which can be used
to try and merge together geometrically compatible but combinatorially different halfedges
that belong to the same boundary cycle.
### IO Streams
- **Breaking change:** The API of `CGAL::Color` has been cleaned up.

View File

@ -133,6 +133,8 @@ and provides a list of the parameters that are used in this package.
- `CGAL::Polygon_mesh_processing::merge_duplicate_polygons_in_polygon_soup()`
- `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()`
- `CGAL::Polygon_mesh_processing::repair_polygon_soup()`
- `CGAL::Polygon_mesh_processing::stitch_boundary_cycle()`
- `CGAL::Polygon_mesh_processing::stitch_boundary_cycles()`
- `CGAL::Polygon_mesh_processing::stitch_borders()`
- `CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh()`
- `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()`

View File

@ -547,17 +547,20 @@ to obtain an as-clean-as-possible polygon soup.
\subsection Stitching
It happens that a polygon mesh has several edges and vertices that are duplicated.
When handling polygon meshes, it might happen that a mesh has several edges and vertices that are duplicated.
For those edges and vertices, the connectivity of the mesh is incomplete, if not considered incorrect.
Stitching the borders of such a polygon mesh consists in two main steps.
First, border edges that are similar but duplicated are detected and paired.
Then, they are "stitched" together so that the edges and vertices duplicates are removed
from the mesh, and each of these remaining edges is incident to exactly two faces.
Stitching the borders of a polygon mesh can be done to fix some of the duplication.
It consists in two main steps. First, border edges that are geometrically identical but duplicated
are detected and paired. Then, they are "stitched" together so that edges and vertices duplicates
are removed from the mesh, and each of these remaining edges is incident to exactly two faces.
The function \link PMP_repairing_grp `CGAL::Polygon_mesh_processing::stitch_borders()` \endlink
performs such repairing operation. The input mesh should be manifold.
Otherwise, stitching is not guaranteed to succeed.
The functions `CGAL::Polygon_mesh_processing::stitch_boundary_cycle()`, `CGAL::Polygon_mesh_processing::stitch_boundary_cycles()`,
and `CGAL::Polygon_mesh_processing::stitch_borders()` can perform such repairing operations: the first two
functions can be used to stitch halfedges that are part of the same boundary(ies), whereas the third
function is more generic and can also stitch halfedges that live on different borders.
The input mesh should be manifold; otherwise, stitching is not guaranteed to succeed.
\subsubsection StitchingExample Stitching Example

View File

@ -529,25 +529,37 @@ void stitch_borders_impl(PM& pmesh,
run_stitch_borders(pmesh, to_stitch, uf_vertices, uf_handles);
}
// \ingroup PMP_repairing_grp
//
// Stitches together, whenever possible, two halfedges of the same border.
//
// \tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`
// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters"
//
// \param pm the polygon mesh to be modified by stitching
// \param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below
//
// \cgalNamedParamsBegin
// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`.
// If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` must be available in `PolygonMesh`.
// \cgalParamEnd
// \cgalNamedParamsEnd
//
} //end of namespace internal
/// \ingroup PMP_repairing_grp
///
/// Stitches together, whenever possible, two halfedges belonging to the boundary cycle described by the halfedge `h`.
/// Two border halfedges `h1` and `h2` can be stitched
/// if the points associated to the source and target vertices of `h1` are
/// the same as those of the target and source vertices of `h2` respectively.
///
/// \tparam PolygonMesh a model of `MutableFaceGraph`
/// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters"
///
/// \param h a border halfedge of the polygon mesh `pm`
/// \param pm the polygon mesh to be stitched
/// \param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below
///
/// \cgalNamedParamsBegin
/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`.
/// If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` must be available in `PolygonMesh`.
/// \cgalParamEnd
/// \cgalNamedParamsEnd
///
/// \returns the number of pairs of halfedges that were stitched.
///
/// \sa `stitch_boundary_cycles()`
/// \sa `stitch_borders()`
///
template <typename PolygonMesh, typename NamedParameters>
std::size_t stitch_boundary_cycles(PolygonMesh& pm,
const NamedParameters& np)
std::size_t stitch_boundary_cycle(const typename boost::graph_traits<PolygonMesh>::halfedge_descriptor h,
PolygonMesh& pm,
const NamedParameters& np)
{
using boost::choose_param;
using boost::get_param;
@ -558,9 +570,6 @@ std::size_t stitch_boundary_cycles(PolygonMesh& pm,
VPMap vpm = choose_param(get_param(np, internal_np::vertex_point),
get_const_property_map(vertex_point, pm));
std::vector<halfedge_descriptor> boundary_cycles;
extract_boundary_cycles(pm, std::back_inserter(boundary_cycles));
std::size_t stitched_boundary_cycles_n = 0;
// A boundary cycle might need to be stitched starting from different extremities
@ -573,62 +582,121 @@ std::size_t stitch_boundary_cycles(PolygonMesh& pm,
// so we mark which edges have been stitched
std::unordered_set<halfedge_descriptor> stitched_hedges;
for(halfedge_descriptor h : boundary_cycles)
std::vector<halfedge_descriptor> stitching_starting_points;
halfedge_descriptor hn = next(h, pm);
while(hn != h)
{
std::vector<halfedge_descriptor> stitching_starting_points;
if((get(vpm, source(hn, pm)) == get(vpm, target(next(hn, pm), pm))) &&
(get(vpm, source(hn, pm)) != get(vpm, target(hn, pm)))) // ignore degenerate edges
stitching_starting_points.push_back(hn);
hn = next(hn, pm);
}
for(std::size_t i=0, end=stitching_starting_points.size(); i<end; ++i)
{
halfedge_descriptor h = stitching_starting_points[i];
if(stitched_hedges.count(h) > 0) // already treated
continue;
std::vector<std::pair<halfedge_descriptor, halfedge_descriptor> > hedges_to_stitch;
halfedge_descriptor hn = next(h, pm);
while(hn != h)
bool do_stitching = true;
do
{
if(get(vpm, source(hn, pm)) == get(vpm, target(next(hn, pm), pm)))
stitching_starting_points.push_back(hn);
// Don't want to create an invalid polygon mesh, even if the geometry allows it
if(face(opposite(h, pm), pm) == face(opposite(hn, pm), pm))
break;
hedges_to_stitch.push_back(std::make_pair(h, hn));
#ifdef CGAL_PMP_STITCHING_DEBUG
std::cout << "Stitch halfegdes: "
<< h << "(" << get(vpm, source(h, pm)) << ") - (" << get(vpm, target(h, pm)) << ") and "
<< hn << "(" << get(vpm, source(hn, pm)) << ") - (" << get(vpm, target(hn, pm)) << ")" << std::endl;
#endif
stitched_hedges.insert(h);
stitched_hedges.insert(hn);
if(next(hn, pm) == h)
break;
h = prev(h, pm);
hn = next(hn, pm);
if((get(vpm, source(h, pm)) != get(vpm, target(hn, pm))) ||
(get(vpm, source(hn, pm)) == get(vpm, target(hn, pm)))) // ignore degenerate edges
do_stitching = false;
}
while(do_stitching);
for(std::size_t i=0, end=stitching_starting_points.size(); i<end; ++i)
if(!hedges_to_stitch.empty())
{
halfedge_descriptor h = stitching_starting_points[i];
if(stitched_hedges.count(h) > 0) // already treated
continue;
std::vector<std::pair<halfedge_descriptor, halfedge_descriptor> > hedges_to_stitch;
halfedge_descriptor hn = next(h, pm);
bool do_stitching = true;
do
{
hedges_to_stitch.push_back(std::make_pair(h, hn));
stitched_hedges.insert(h);
stitched_hedges.insert(hn);
if(next(hn, pm) == h)
break;
h = prev(h, pm);
hn = next(hn, pm);
if(get(vpm, source(h, pm)) != get(vpm, target(hn, pm)))
do_stitching = false;
}
while(do_stitching);
internal::stitch_borders_impl(pm, hedges_to_stitch);
++stitched_boundary_cycles_n;
stitched_boundary_cycles_n += hedges_to_stitch.size();
}
}
return stitched_boundary_cycles_n;
}
template <typename PolygonMesh>
std::size_t stitch_boundary_cycle(const typename boost::graph_traits<PolygonMesh>::halfedge_descriptor h,
PolygonMesh& pm)
{
return stitch_boundary_cycle(h, pm, CGAL::parameters::all_default());
}
/// \ingroup PMP_repairing_grp
///
/// Stitches together, whenever possible, two halfedges belonging to the same boundary cycle.
/// Two border halfedges `h1` and `h2` can be stitched
/// if the points associated to the source and target vertices of `h1` are
/// the same as those of the target and source vertices of `h2` respectively.
///
/// \tparam PolygonMesh a model of `MutableFaceGraph`
/// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters"
///
/// \param pm the polygon mesh to be stitched
/// \param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below
///
/// \cgalNamedParamsBegin
/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`.
/// If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` must be available in `PolygonMesh`.
/// \cgalParamEnd
/// \cgalNamedParamsEnd
///
/// \returns the number of pairs of halfedges that were stitched.
///
/// \sa `stitch_boundary_cycle()`
/// \sa `stitch_borders()`
///
template <typename PolygonMesh, typename NamedParameters>
std::size_t stitch_boundary_cycles(PolygonMesh& pm,
const NamedParameters& np)
{
typedef typename boost::graph_traits<PolygonMesh>::halfedge_descriptor halfedge_descriptor;
std::vector<halfedge_descriptor> boundary_cycles;
extract_boundary_cycles(pm, std::back_inserter(boundary_cycles));
std::size_t stitched_boundary_cycles_n = 0;
for(halfedge_descriptor h : boundary_cycles)
stitched_boundary_cycles_n += stitch_boundary_cycle(h, pm, np);
return stitched_boundary_cycles_n;
}
template <typename PolygonMesh>
std::size_t stitch_boundary_cycles(PolygonMesh& pm)
{
return stitch_boundary_cycles(pm, CGAL::parameters::all_default());
}
} //end of namespace internal
/*!
* \ingroup PMP_repairing_grp
* Stitches together border halfedges in a polygon mesh.
@ -636,7 +704,7 @@ std::size_t stitch_boundary_cycles(PolygonMesh& pm)
* For each pair `p` in this vector, `p.second` and its opposite will be removed
* from `pmesh`.
*
* @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`
* @tparam PolygonMesh a model of `MutableFaceGraph`
* @tparam HalfedgePairsRange a range of
* `std::pair<boost::graph_traits<PolygonMesh>::%halfedge_descriptor,
* boost::graph_traits<PolygonMesh>::%halfedge_descriptor>`,
@ -683,7 +751,10 @@ void stitch_borders(PolygonMesh& pmesh,
/// Default value is `false`.\cgalParamEnd
/// \cgalParamBegin{face_index_map} a property map containing the index of each face of `pmesh` \cgalParamEnd
/// \cgalNamedParamsEnd
///
/// @sa `stitch_boundary_cycle()`
/// @sa `stitch_boundary_cycles()`
///
template <typename PolygonMesh, class CGAL_PMP_NP_TEMPLATE_PARAMETERS>
void stitch_borders(PolygonMesh& pmesh, const CGAL_PMP_NP_CLASS& np)
{
@ -698,7 +769,7 @@ void stitch_borders(PolygonMesh& pmesh, const CGAL_PMP_NP_CLASS& np)
VPMap vpm = choose_param(get_param(np, internal_np::vertex_point),
get_const_property_map(vertex_point, pmesh));
internal::stitch_boundary_cycles(pmesh, np);
stitch_boundary_cycles(pmesh, np);
internal::collect_duplicated_stitchable_boundary_edges(pmesh,
std::back_inserter(hedge_pairs_to_stitch),
@ -706,7 +777,7 @@ void stitch_borders(PolygonMesh& pmesh, const CGAL_PMP_NP_CLASS& np)
vpm, np);
stitch_borders(pmesh, hedge_pairs_to_stitch);
internal::stitch_boundary_cycles(pmesh, np);
stitch_boundary_cycles(pmesh, np);
}

View File

@ -18,7 +18,8 @@ namespace params = CGAL::parameters;
typedef CGAL::Exact_predicates_inexact_constructions_kernel EPICK;
typedef CGAL::Exact_predicates_exact_constructions_kernel EPECK;
void test_stitch_boundary_cycles(const char* fname)
void test_stitch_boundary_cycles(const char* fname,
const std::size_t expected_n)
{
typedef CGAL::Surface_mesh<EPICK::Point_3> Mesh;
@ -29,8 +30,8 @@ void test_stitch_boundary_cycles(const char* fname)
return;
}
std::size_t res = PMP::internal::stitch_boundary_cycles(mesh);
assert(res > 0);
std::size_t res = PMP::stitch_boundary_cycles(mesh);
assert(res == expected_n);
assert(is_valid(mesh));
}
@ -134,8 +135,8 @@ void bug_test()
int main()
{
test_stitch_boundary_cycles("data_stitching/boundary_cycle.off");
test_stitch_boundary_cycles("data_stitching/boundary_cycle_2.off");
test_stitch_boundary_cycles("data_stitching/boundary_cycle.off", 5);
test_stitch_boundary_cycles("data_stitching/boundary_cycle_2.off", 2);
test_polyhedron<EPECK>("data_stitching/full_border.off");
test_polyhedron<EPICK>("data_stitching/full_border.off");