mirror of https://github.com/CGAL/cgal
Merge pull request #3861 from MaelRL/PMP-Add_stitch_boundary_cycle_singular-GF
PMP: Introduce `stitch_boundary_cycle`
This commit is contained in:
commit
e4353afb13
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Reference in New Issue