mirror of https://github.com/CGAL/cgal
Merge remote-tracking branch 'gsoc2019-nccycles-thien' into Surface_mesh_topology-gdamiand
This is a merge done after the rebase of Surface_mesh_topology-gdamiand
Original commit id:
commit 5459085b7da44946a834ce155fcc4e9c83ad14b2 (HEAD)
Merge: 8138ac72694 349759a0547
Author: Thien Hoang <thienvhoang99@gmail.com>
Date: Sat Jul 13 02:46:50 2019 +0700
This commit is contained in:
commit
e4d63837fd
|
|
@ -230,16 +230,16 @@ namespace CGAL {
|
||||||
typename Storage2,
|
typename Storage2,
|
||||||
typename Converters, typename DartInfoConverter,
|
typename Converters, typename DartInfoConverter,
|
||||||
typename PointConverter>
|
typename PointConverter>
|
||||||
void copy(const Combinatorial_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
void copy(Combinatorial_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
||||||
const Converters& converters,
|
const Converters& converters,
|
||||||
const DartInfoConverter& dartinfoconverter,
|
const DartInfoConverter& dartinfoconverter,
|
||||||
const PointConverter& pointconverter,
|
const PointConverter& pointconverter,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<typename Combinatorial_map_base<d2, Refs2, Items2, Alloc2, Storage2>::
|
<typename Combinatorial_map_base<d2, Refs2, Items2, Alloc2, Storage2>::
|
||||||
Dart_const_handle, Dart_handle>* origin_to_copy=NULL,
|
Dart_handle, Dart_handle>* origin_to_copy,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<Dart_handle, typename Combinatorial_map_base<d2, Refs2, Items2,
|
<Dart_handle, typename Combinatorial_map_base<d2, Refs2, Items2,
|
||||||
Alloc2, Storage2>::Dart_const_handle>* copy_to_origin=NULL)
|
Alloc2, Storage2>::Dart_handle>* copy_to_origin)
|
||||||
{
|
{
|
||||||
typedef Combinatorial_map_base<d2, Refs2, Items2, Alloc2, Storage2> CMap2;
|
typedef Combinatorial_map_base<d2, Refs2, Items2, Alloc2, Storage2> CMap2;
|
||||||
this->clear();
|
this->clear();
|
||||||
|
|
@ -263,13 +263,13 @@ namespace CGAL {
|
||||||
// Create an mapping between darts of the two maps (originals->copies).
|
// Create an mapping between darts of the two maps (originals->copies).
|
||||||
// (here we cannot use CGAL::Unique_hash_map because it does not provide
|
// (here we cannot use CGAL::Unique_hash_map because it does not provide
|
||||||
// iterators...
|
// iterators...
|
||||||
boost::unordered_map<typename CMap2::Dart_const_handle, Dart_handle>
|
boost::unordered_map<typename CMap2::Dart_handle, Dart_handle>
|
||||||
local_dartmap;
|
local_dartmap;
|
||||||
if (origin_to_copy==NULL) // Used local_dartmap if user does not provides its own unordered_map
|
if (origin_to_copy==NULL) // Used local_dartmap if user does not provides its own unordered_map
|
||||||
{ origin_to_copy=&local_dartmap; }
|
{ origin_to_copy=&local_dartmap; }
|
||||||
|
|
||||||
Dart_handle new_dart;
|
Dart_handle new_dart;
|
||||||
for (typename CMap2::Dart_const_range::const_iterator
|
for (typename CMap2::Dart_range::iterator
|
||||||
it=amap.darts().begin(), itend=amap.darts().end();
|
it=amap.darts().begin(), itend=amap.darts().end();
|
||||||
it!=itend; ++it)
|
it!=itend; ++it)
|
||||||
{
|
{
|
||||||
|
|
@ -286,7 +286,7 @@ namespace CGAL {
|
||||||
|
|
||||||
unsigned int min_dim=(dimension<amap.dimension?dimension:amap.dimension);
|
unsigned int min_dim=(dimension<amap.dimension?dimension:amap.dimension);
|
||||||
|
|
||||||
typename boost::unordered_map<typename CMap2::Dart_const_handle,Dart_handle>
|
typename boost::unordered_map<typename CMap2::Dart_handle,Dart_handle>
|
||||||
::iterator dartmap_iter, dartmap_iter_end=origin_to_copy->end();
|
::iterator dartmap_iter, dartmap_iter_end=origin_to_copy->end();
|
||||||
for (dartmap_iter=origin_to_copy->begin(); dartmap_iter!=dartmap_iter_end;
|
for (dartmap_iter=origin_to_copy->begin(); dartmap_iter!=dartmap_iter_end;
|
||||||
++dartmap_iter)
|
++dartmap_iter)
|
||||||
|
|
@ -319,13 +319,13 @@ namespace CGAL {
|
||||||
|
|
||||||
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
||||||
typename Storage2>
|
typename Storage2>
|
||||||
void copy(const Combinatorial_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
void copy(Combinatorial_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<typename Combinatorial_map_base<d2, Refs2, Items2, Alloc2,
|
<typename Combinatorial_map_base<d2, Refs2, Items2, Alloc2,
|
||||||
Storage2>::Dart_const_handle, Dart_handle>* origin_to_copy=NULL,
|
Storage2>::Dart_handle, Dart_handle>* origin_to_copy,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<Dart_handle, typename Combinatorial_map_base<d2, Refs2, Items2,
|
<Dart_handle, typename Combinatorial_map_base<d2, Refs2, Items2,
|
||||||
Alloc2, Storage2>::Dart_const_handle>* copy_to_origin=NULL)
|
Alloc2, Storage2>::Dart_handle>* copy_to_origin)
|
||||||
|
|
||||||
{
|
{
|
||||||
CGAL::cpp11::tuple<> converters;
|
CGAL::cpp11::tuple<> converters;
|
||||||
|
|
@ -337,15 +337,15 @@ namespace CGAL {
|
||||||
|
|
||||||
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
||||||
typename Storage2, typename Converters>
|
typename Storage2, typename Converters>
|
||||||
void copy(const Combinatorial_map_base<d2, Refs2, Items2,
|
void copy(Combinatorial_map_base<d2, Refs2, Items2,
|
||||||
Alloc2, Storage2>& amap,
|
Alloc2, Storage2>& amap,
|
||||||
const Converters& converters,
|
const Converters& converters,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<typename Combinatorial_map_base<d2, Refs2, Items2, Alloc2,
|
<typename Combinatorial_map_base<d2, Refs2, Items2, Alloc2,
|
||||||
Storage2>::Dart_const_handle, Dart_handle>* origin_to_copy=NULL,
|
Storage2>::Dart_handle, Dart_handle>* origin_to_copy,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<Dart_handle, typename Combinatorial_map_base<d2, Refs2, Items2,
|
<Dart_handle, typename Combinatorial_map_base<d2, Refs2, Items2,
|
||||||
Alloc2, Storage2>::Dart_const_handle>* copy_to_origin=NULL)
|
Alloc2, Storage2>::Dart_handle>* copy_to_origin)
|
||||||
{
|
{
|
||||||
Default_converter_cmap_0attributes_with_point<Refs2, Refs> pointconverter;
|
Default_converter_cmap_0attributes_with_point<Refs2, Refs> pointconverter;
|
||||||
Default_converter_dart_info<Refs2, Refs> dartinfoconverter;
|
Default_converter_dart_info<Refs2, Refs> dartinfoconverter;
|
||||||
|
|
@ -356,22 +356,22 @@ namespace CGAL {
|
||||||
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
||||||
typename Storage2,
|
typename Storage2,
|
||||||
typename Converters, typename DartInfoConverter>
|
typename Converters, typename DartInfoConverter>
|
||||||
void copy(const Combinatorial_map_base<d2, Refs2, Items2,
|
void copy(Combinatorial_map_base<d2, Refs2, Items2,
|
||||||
Alloc2, Storage2>& amap,
|
Alloc2, Storage2>& amap,
|
||||||
const Converters& converters,
|
const Converters& converters,
|
||||||
const DartInfoConverter& dartinfoconverter,
|
const DartInfoConverter& dartinfoconverter,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<typename Combinatorial_map_base<d2, Refs2, Items2, Alloc2,
|
<typename Combinatorial_map_base<d2, Refs2, Items2, Alloc2,
|
||||||
Storage2>::Dart_const_handle, Dart_handle>* origin_to_copy=NULL,
|
Storage2>::Dart_handle, Dart_handle>* origin_to_copy,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<Dart_handle, typename Combinatorial_map_base<d2, Refs2, Items2,
|
<Dart_handle, typename Combinatorial_map_base<d2, Refs2, Items2,
|
||||||
Alloc2, Storage2>::Dart_const_handle>* copy_to_origin=NULL)
|
Alloc2, Storage2>::Dart_handle>* copy_to_origin)
|
||||||
{
|
{
|
||||||
Default_converter_cmap_0attributes_with_point<Refs2, Refs> pointconverter;
|
Default_converter_cmap_0attributes_with_point<Refs2, Refs> pointconverter;
|
||||||
copy(amap, converters, dartinfoconverter, pointconverter,
|
copy(amap, converters, dartinfoconverter, pointconverter,
|
||||||
origin_to_copy, copy_to_origin);
|
origin_to_copy, copy_to_origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy constructor from a map having exactly the same type.
|
// Copy constructor from a map having exactly the same type.
|
||||||
Combinatorial_map_base (const Self & amap)
|
Combinatorial_map_base (const Self & amap)
|
||||||
{ copy(amap); }
|
{ copy(amap); }
|
||||||
|
|
|
||||||
|
|
@ -152033,4 +152033,14 @@ pages = {179--189}
|
||||||
Year = {2012},
|
Year = {2012},
|
||||||
Pages = {440-449},
|
Pages = {440-449},
|
||||||
Url = {http://arxiv.org/abs/1110.4573}
|
Url = {http://arxiv.org/abs/1110.4573}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@article{cvl-ew-12,
|
||||||
|
Author = {Cabello, Sergio and de Verdière, {\'E}ric Colin and Lazarus, Francis},
|
||||||
|
Title = {Algorithms for the edge-width of an embedded graph},
|
||||||
|
Journal = {Computational Geometry},
|
||||||
|
Volume = {45},
|
||||||
|
Pages = {215--224},
|
||||||
|
Year = {2012},
|
||||||
|
Url = {http://monge.univ-mlv.fr/~colinde/pub/09edgewidth.pdf}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -194,16 +194,16 @@ namespace CGAL {
|
||||||
typename Storage2,
|
typename Storage2,
|
||||||
typename Converters, typename DartInfoConverter,
|
typename Converters, typename DartInfoConverter,
|
||||||
typename PointConverter>
|
typename PointConverter>
|
||||||
void copy(const Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
void copy(Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
||||||
const Converters& converters,
|
const Converters& converters,
|
||||||
const DartInfoConverter& dartinfoconverter,
|
const DartInfoConverter& dartinfoconverter,
|
||||||
const PointConverter& pointconverter,
|
const PointConverter& pointconverter,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<typename Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>::
|
<typename Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>::
|
||||||
Dart_const_handle, Dart_handle>* origin_to_copy=NULL,
|
Dart_handle, Dart_handle>* origin_to_copy,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<Dart_handle, typename Generalized_map_base<d2, Refs2, Items2,
|
<Dart_handle, typename Generalized_map_base<d2, Refs2, Items2,
|
||||||
Alloc2, Storage2>::Dart_const_handle>* copy_to_origin=NULL)
|
Alloc2, Storage2>::Dart_handle>* copy_to_origin)
|
||||||
{
|
{
|
||||||
typedef Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2> GMap2;
|
typedef Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2> GMap2;
|
||||||
this->clear();
|
this->clear();
|
||||||
|
|
@ -225,13 +225,13 @@ namespace CGAL {
|
||||||
// Create an mapping between darts of the two maps (originals->copies).
|
// Create an mapping between darts of the two maps (originals->copies).
|
||||||
// (here we cannot use CGAL::Unique_hash_map because it does not provide
|
// (here we cannot use CGAL::Unique_hash_map because it does not provide
|
||||||
// iterators...
|
// iterators...
|
||||||
boost::unordered_map<typename GMap2::Dart_const_handle, Dart_handle>
|
boost::unordered_map<typename GMap2::Dart_handle, Dart_handle>
|
||||||
local_dartmap;
|
local_dartmap;
|
||||||
if (origin_to_copy==NULL) // Used local_dartmap if user does not provides its own unordered_map
|
if (origin_to_copy==NULL) // Used local_dartmap if user does not provides its own unordered_map
|
||||||
{ origin_to_copy=&local_dartmap; }
|
{ origin_to_copy=&local_dartmap; }
|
||||||
|
|
||||||
Dart_handle new_dart;
|
Dart_handle new_dart;
|
||||||
for (typename GMap2::Dart_const_range::const_iterator
|
for (typename GMap2::Dart_range::iterator
|
||||||
it=amap.darts().begin(), itend=amap.darts().end();
|
it=amap.darts().begin(), itend=amap.darts().end();
|
||||||
it!=itend; ++it)
|
it!=itend; ++it)
|
||||||
{
|
{
|
||||||
|
|
@ -248,7 +248,7 @@ namespace CGAL {
|
||||||
|
|
||||||
unsigned int min_dim=(dimension<amap.dimension?dimension:amap.dimension);
|
unsigned int min_dim=(dimension<amap.dimension?dimension:amap.dimension);
|
||||||
|
|
||||||
typename boost::unordered_map<typename GMap2::Dart_const_handle,Dart_handle>
|
typename boost::unordered_map<typename GMap2::Dart_handle,Dart_handle>
|
||||||
::iterator dartmap_iter, dartmap_iter_end=origin_to_copy->end();
|
::iterator dartmap_iter, dartmap_iter_end=origin_to_copy->end();
|
||||||
for (dartmap_iter=origin_to_copy->begin(); dartmap_iter!=dartmap_iter_end;
|
for (dartmap_iter=origin_to_copy->begin(); dartmap_iter!=dartmap_iter_end;
|
||||||
++dartmap_iter)
|
++dartmap_iter)
|
||||||
|
|
@ -281,13 +281,13 @@ namespace CGAL {
|
||||||
|
|
||||||
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
||||||
typename Storage2>
|
typename Storage2>
|
||||||
void copy(const Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
void copy(Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<typename Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>::
|
<typename Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>::
|
||||||
Dart_const_handle, Dart_handle>* origin_to_copy=NULL,
|
Dart_handle, Dart_handle>* origin_to_copy,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<Dart_handle, typename Generalized_map_base<d2, Refs2, Items2,
|
<Dart_handle, typename Generalized_map_base<d2, Refs2, Items2,
|
||||||
Alloc2, Storage2>::Dart_const_handle>* copy_to_origin=NULL)
|
Alloc2, Storage2>::Dart_handle>* copy_to_origin)
|
||||||
{
|
{
|
||||||
CGAL::cpp11::tuple<> converters;
|
CGAL::cpp11::tuple<> converters;
|
||||||
Default_converter_dart_info<Refs2, Refs> dartinfoconverter;
|
Default_converter_dart_info<Refs2, Refs> dartinfoconverter;
|
||||||
|
|
@ -298,14 +298,14 @@ namespace CGAL {
|
||||||
|
|
||||||
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
||||||
typename Storage2, typename Converters>
|
typename Storage2, typename Converters>
|
||||||
void copy(const Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
void copy(Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
||||||
const Converters& converters,
|
const Converters& converters,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<typename Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>::
|
<typename Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>::
|
||||||
Dart_const_handle, Dart_handle>* origin_to_copy=NULL,
|
Dart_handle, Dart_handle>* origin_to_copy,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<Dart_handle, typename Generalized_map_base<d2, Refs2, Items2,
|
<Dart_handle, typename Generalized_map_base<d2, Refs2, Items2,
|
||||||
Alloc2, Storage2>::Dart_const_handle>* copy_to_origin=NULL)
|
Alloc2, Storage2>::Dart_handle>* copy_to_origin)
|
||||||
{
|
{
|
||||||
Default_converter_cmap_0attributes_with_point<Refs2, Refs> pointconverter;
|
Default_converter_cmap_0attributes_with_point<Refs2, Refs> pointconverter;
|
||||||
Default_converter_dart_info<Refs2, Refs> dartinfoconverter;
|
Default_converter_dart_info<Refs2, Refs> dartinfoconverter;
|
||||||
|
|
@ -316,15 +316,15 @@ namespace CGAL {
|
||||||
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
template <unsigned int d2, typename Refs2, typename Items2, typename Alloc2,
|
||||||
typename Storage2,
|
typename Storage2,
|
||||||
typename Converters, typename DartInfoConverter>
|
typename Converters, typename DartInfoConverter>
|
||||||
void copy(const Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
void copy(Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>& amap,
|
||||||
const Converters& converters,
|
const Converters& converters,
|
||||||
const DartInfoConverter& dartinfoconverter,
|
const DartInfoConverter& dartinfoconverter,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<typename Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>::
|
<typename Generalized_map_base<d2, Refs2, Items2, Alloc2, Storage2>::
|
||||||
Dart_const_handle, Dart_handle>* origin_to_copy=NULL,
|
Dart_handle, Dart_handle>* origin_to_copy,
|
||||||
boost::unordered_map
|
boost::unordered_map
|
||||||
<Dart_handle, typename Generalized_map_base<d2, Refs2, Items2,
|
<Dart_handle, typename Generalized_map_base<d2, Refs2, Items2,
|
||||||
Alloc2, Storage2>::Dart_const_handle>* copy_to_origin=NULL)
|
Alloc2, Storage2>::Dart_handle>* copy_to_origin)
|
||||||
{
|
{
|
||||||
Default_converter_cmap_0attributes_with_point<Refs2, Refs> pointconverter;
|
Default_converter_cmap_0attributes_with_point<Refs2, Refs> pointconverter;
|
||||||
copy(amap, converters, dartinfoconverter, pointconverter,
|
copy(amap, converters, dartinfoconverter, pointconverter,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
namespace CGAL {
|
||||||
|
namespace Surface_mesh_topology {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\ingroup PkgSurfaceMeshTopologyClasses
|
||||||
|
|
||||||
|
The class `Shortest_noncontractible_cycle` provides methods to find shortest non-contractible cycles on the surface. The input mesh can be `Combinatorial_map`, `Generalized_map`, `Surface_mesh`, `Polyhedron_3` or a linear cell complex. In the unweighted case, all edges have weight of 1. Otherwise, a weight functor must be provided. Often times, the functor measures the distance between two endpoints of the edge containing the given dart.
|
||||||
|
|
||||||
|
\tparam Mesh_ a model of `GenericMap` or of `FaceGraph`
|
||||||
|
\tparam Weight_ a model of `WeightFunctor`
|
||||||
|
*/
|
||||||
|
|
||||||
|
template <class Mesh_, class Weight_ = void>
|
||||||
|
class Shortest_noncontractible_cycle {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Default weight functor is used in the unweighted case.
|
||||||
|
/// Every edge has weight of 1.
|
||||||
|
struct Default_weight_functor {
|
||||||
|
/// Number type of the weight
|
||||||
|
using Weight_t = unsigned int;
|
||||||
|
|
||||||
|
/// Return 1 for any edge's weight
|
||||||
|
template <class T>
|
||||||
|
Weight_t operator()(T);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The weight functor used.
|
||||||
|
/// If the template parameter `Weight_` is void, set as `Default_weight_functor`; otherwise set as `Weight_`
|
||||||
|
using Weight = unspecified_type;
|
||||||
|
|
||||||
|
/// Number type of the weights.
|
||||||
|
using Distance_type = Weight::Weight_t;
|
||||||
|
|
||||||
|
/// Dart_handle of the input mesh.
|
||||||
|
/// If the template parameter `Mesh_` is a model of `GenericMap`, set as \link GenericMap::Dart_handle `Mesh::Dart_handle`\endlink ; otherwise set as `boost::graph_traits<Mesh_>::halfedge_descriptor`
|
||||||
|
using Dart_handle_orig = unspecified_type;
|
||||||
|
|
||||||
|
/// The cycle type is a container of dart handles of the original mesh.
|
||||||
|
using Path = std::vector<Dart_handle_orig>;
|
||||||
|
|
||||||
|
/// Constructor takes the input map and the weight functor. If not specified, the functor is default to its default constructor.
|
||||||
|
Shortest_noncontractible_cycle(Mesh_& mesh, const Weight& wf = Weight());
|
||||||
|
|
||||||
|
/// Find the shortest non-contractible cycle through a vertex.
|
||||||
|
/// The vertex is depicted by `root_vertex`, a \link GenericMap::Dart_handle `Dart_handle` \endlink of the input mesh. The cycle found is returned in `cycle`. The total length of the cycle is calculated by adding up the weights of all edges in the cycle and is returned through the pointer `length`.
|
||||||
|
bool find_cycle(Dart_handle_orig root_vertex, Path& cycle, Distance_type* length = NULL);
|
||||||
|
|
||||||
|
/// Find the edge width of the whole mesh
|
||||||
|
/// The edge width is returned in `cycle`. The total length of the cycle is calculated by adding up the weights of all edges in the cycle and is returned through the pointer `length`.
|
||||||
|
void edge_width(Path& cycle, Distance_type* length = NULL);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*!
|
||||||
|
\ingroup PkgSurfaceMeshTopologyConcepts
|
||||||
|
\cgalConcept
|
||||||
|
|
||||||
|
The concept `WeightFunctor` defines a functor to calculate the weight of an edge
|
||||||
|
|
||||||
|
\cgalHasModel \link CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle::Default_weight_functor `CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle::Default_weight_functor` \endlink
|
||||||
|
*/
|
||||||
|
|
||||||
|
class WeightFunctor {
|
||||||
|
public:
|
||||||
|
/// \name Public types
|
||||||
|
/// @{
|
||||||
|
|
||||||
|
/// Number type of the weights.
|
||||||
|
using Weight_t = unspecified_type;
|
||||||
|
|
||||||
|
/// Dart_const_handle of the input mesh.
|
||||||
|
using Dart_const_handle = unspecified_type;
|
||||||
|
/// @}
|
||||||
|
|
||||||
|
/// \name Public member functions
|
||||||
|
/// @{
|
||||||
|
|
||||||
|
/// Returns the weight of the edge containing `dh`.
|
||||||
|
Weight_t operator()(Dart_const_handle dh) const;
|
||||||
|
/// @}
|
||||||
|
};
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
/// \defgroup PkgDrawFaceGraphWithPaths Draw a Mesh with Paths
|
/// \defgroup PkgDrawFaceGraphWithPaths Draw a Mesh with Paths
|
||||||
/// \ingroup PkgSurfaceMeshTopology
|
/// \ingroup PkgSurfaceMeshTopology
|
||||||
|
|
||||||
|
/// \defgroup PkgSurfaceMeshTopologyConcepts Concepts
|
||||||
|
/// \ingroup PkgSurfaceMeshTopology
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\addtogroup PkgSurfaceMeshTopology
|
\addtogroup PkgSurfaceMeshTopology
|
||||||
|
|
@ -43,6 +45,11 @@
|
||||||
- `CGAL::Surface_mesh_topology::Polygonal_schema_with_combinatorial_map<Items,Alloc>`
|
- `CGAL::Surface_mesh_topology::Polygonal_schema_with_combinatorial_map<Items,Alloc>`
|
||||||
- `CGAL::Surface_mesh_topology::Polygonal_schema_with_generalized_map<Items,Alloc>`
|
- `CGAL::Surface_mesh_topology::Polygonal_schema_with_generalized_map<Items,Alloc>`
|
||||||
- `CGAL::Surface_mesh_topology::Polygonal_schema_min_items`
|
- `CGAL::Surface_mesh_topology::Polygonal_schema_min_items`
|
||||||
|
- `CGAL::Surface_mesh_topology::Combinatorial_map_2_incremental_builder<Mesh>`
|
||||||
|
- `CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<Mesh_, Weight_>`
|
||||||
|
|
||||||
|
## Concepts ##
|
||||||
|
- `WeightFunctor`
|
||||||
|
|
||||||
\cgalCRPSubsection{Draw a Mesh with Paths}
|
\cgalCRPSubsection{Draw a Mesh with Paths}
|
||||||
- \link PkgDrawFaceGraphWithPaths CGAL::draw<Mesh>() \endlink
|
- \link PkgDrawFaceGraphWithPaths CGAL::draw<Mesh>() \endlink
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,13 @@ The second query asks if the curves are <em>freely homotopic</em> while the thir
|
||||||
|
|
||||||
The algorithms used are based on a paper by Erickson and Whittlesey \cgalCite{ew-tcsr-13}, providing a linear time algorithm for the above homotopy tests. This is a simplified version of the linear time algorithm by Lazarus and Rivaud \cgalCite{lr-hts-12}.
|
The algorithms used are based on a paper by Erickson and Whittlesey \cgalCite{ew-tcsr-13}, providing a linear time algorithm for the above homotopy tests. This is a simplified version of the linear time algorithm by Lazarus and Rivaud \cgalCite{lr-hts-12}.
|
||||||
|
|
||||||
|
In addition, this package offers the following functions:
|
||||||
|
|
||||||
|
- Given a surface mesh \f$\cal{M}\f$ and a vertex \f$v\f$, find a shortest non-contractible cycle consisting of \f$v\f$.
|
||||||
|
- Given a surface mesh \f$\cal{M}\f$, find a shortest non-contractible cycle on the whole surface (also known as the edge-width of the surface).
|
||||||
|
|
||||||
|
The algorithm to find a shortest non-contractible cycle through a specified vertex is based on the paper by Cabello et al. \cgalCite{cvl-ew-12} The time complexity is linear, though in the weighted case it is raised by a logarithmic factor. Finding the edge-width takes quadratic time by running the first function on each vertex, and its complexity is also raised by a logarithmic factor when considering a weighted map.
|
||||||
|
|
||||||
\section SMTopology_HowToUse API Description
|
\section SMTopology_HowToUse API Description
|
||||||
|
|
||||||
\subsection SMTopology_Input Specifying the Input Surface and Curves
|
\subsection SMTopology_Input Specifying the Input Surface and Curves
|
||||||
|
|
@ -67,6 +74,12 @@ the input surface. This is otherwise independent of the size of input surface,
|
||||||
|
|
||||||
Each time a `Surface_mesh_topology::Path_on_surface` is provided for a homotopy test, it is first transformed to an equivalent path in the quadrangulation stored by the `Surface_mesh_topology::Curves_on_surface_topology`. This transformation is transparent to the user who has never access to the quadrangulation.
|
Each time a `Surface_mesh_topology::Path_on_surface` is provided for a homotopy test, it is first transformed to an equivalent path in the quadrangulation stored by the `Surface_mesh_topology::Curves_on_surface_topology`. This transformation is transparent to the user who has never access to the quadrangulation.
|
||||||
|
|
||||||
|
\subsubsection SMTopology_Close Preprocessing in finding Shortest Non-contractible Cycles
|
||||||
|
|
||||||
|
The algorithm to find shortest non-contractible cycles discussed in \cgalCite{cvl-ew-12} is expected to run perfectly even when the surface contains boundaries. In the constructor of `Shortest_noncontractible_cycle`, the given mesh is copied to a `GenericMap` local map. Then, we `close()` the local map, i.e. add new faces (if necessary) so that the map is 2-boundary-less (the added faces are marked as "holes" of the surface). Also in this constructor, the weight of every edge is measured via the weight functor and is stored as a 1-attribute in the local map.
|
||||||
|
|
||||||
|
The dart correspondence between the original mesh and the local map are supported by two `std::unordered_map`. Thus the user can query the cycle with their dart, then the algorithm is performed on the local map, and finally the darts of the original mesh are returned to the user.
|
||||||
|
|
||||||
\subsection SMTopology_Queries Testing Homotopy
|
\subsection SMTopology_Queries Testing Homotopy
|
||||||
|
|
||||||
Given two `Surface_mesh_topology::Path_on_surface` \f$p_1\f$ and \f$p_2\f$, the class `Surface_mesh_topology::Curves_on_surface_topology` provides the following three functions:
|
Given two `Surface_mesh_topology::Path_on_surface` \f$p_1\f$ and \f$p_2\f$, the class `Surface_mesh_topology::Curves_on_surface_topology` provides the following three functions:
|
||||||
|
|
@ -77,6 +90,14 @@ Given two `Surface_mesh_topology::Path_on_surface` \f$p_1\f$ and \f$p_2\f$, the
|
||||||
|
|
||||||
- \ref Surface_mesh_topology::Curves_on_surface_topology::are_homotopic_with_fixed_endpoints "are_homotopic_with_fixed_endpoints"(\f$p_1\f$, \f$p_2\f$) returns `true` if the paths \f$p_1\f$ and \f$p_2\f$ are homotopic with fixed endpoints. This call is equivalent to \ref Surface_mesh_topology::Curves_on_surface_topology::is_contractible "is_contractible"(\f$p_1\cdot \overline{p_2}\f$), where \f$p_1\cdot \overline{p_2}\f$ is the concatenation of \f$p_1\f$ and the reverse of \f$p_2\f$.
|
- \ref Surface_mesh_topology::Curves_on_surface_topology::are_homotopic_with_fixed_endpoints "are_homotopic_with_fixed_endpoints"(\f$p_1\f$, \f$p_2\f$) returns `true` if the paths \f$p_1\f$ and \f$p_2\f$ are homotopic with fixed endpoints. This call is equivalent to \ref Surface_mesh_topology::Curves_on_surface_topology::is_contractible "is_contractible"(\f$p_1\cdot \overline{p_2}\f$), where \f$p_1\cdot \overline{p_2}\f$ is the concatenation of \f$p_1\f$ and the reverse of \f$p_2\f$.
|
||||||
|
|
||||||
|
\subsection SMTopology_Find Find Shortest Non-contractible Cycle
|
||||||
|
|
||||||
|
Since the data structures to represent a surface are edge-centralized, in order to specify a vertex where the cycle is found, the user can use any dart belonging to this vertex.
|
||||||
|
|
||||||
|
The class `Shortest_noncontractible_cycle` provides two functions:
|
||||||
|
|
||||||
|
- \link Shortest_noncontractible_cycle::find_cycle `find_cycle(dh, cycle, &cycle_length)` \endlink : Find a shortest non-contractible cycle going through `dh`, while storing one dart per edge on the cycle to the variable `cycle`. The last argument is the total weight of the cycle and it is optional.
|
||||||
|
- \link Shortest_noncontractible_cycle::edge_width `edge_width(cycle, &cycle_length)` \endlink : Very similar to the previous function, except that one does not have to specify the vertex. It calls `find_cycle` on every vertex and returns the shortest cycle together with its length.
|
||||||
|
|
||||||
|
|
||||||
\section SMTopology_Examples Examples
|
\section SMTopology_Examples Examples
|
||||||
|
|
@ -97,6 +118,19 @@ In this second example, we build a genus two torus surface from a set of three s
|
||||||
In this third example, we create non closed paths on the same mesh as in the first example and perform homotopy tests with fixed endpoints. Here, a `Surface_mesh` is used as an alternative to a `CombinatorialMap`.
|
In this third example, we create non closed paths on the same mesh as in the first example and perform homotopy tests with fixed endpoints. Here, a `Surface_mesh` is used as an alternative to a `CombinatorialMap`.
|
||||||
\cgalExample{Surface_mesh_topology/open_path_homotopy.cpp}
|
\cgalExample{Surface_mesh_topology/open_path_homotopy.cpp}
|
||||||
|
|
||||||
|
\subsection SMTopology_Example_IV_V_VI Find Shortest Non-contractible Cycle Examples
|
||||||
|
|
||||||
|
In the next three examples, we present various way to use the class `Shortest_noncontractible_cycle`.
|
||||||
|
|
||||||
|
One can store the original mesh in a `Combinatorial_map` instance and run the algorithm without regarding the geometric distances, i.e. the unweighted case.
|
||||||
|
\cgalExample{Surface_mesh_topology/shortest_noncontractible_cycle_using_BFS.cpp}
|
||||||
|
|
||||||
|
Alternatively, one can take the geometric distances into consideration by providing a weight functor to calculate the weight of the edge containing the given dart. Note that the time complexity is raised by a logarithmic factor.
|
||||||
|
\cgalExample{Surface_mesh_topology/shortest_noncontractible_cycle_through_a_vertex.cpp}
|
||||||
|
|
||||||
|
In order to find the edge-width of the surface, one can make use of the routine `edge_width` as follows:
|
||||||
|
\cgalExample{Surface_mesh_topology/edgewidth_surface_mesh.cpp}
|
||||||
|
|
||||||
\section SMTopology_Benchmarks Benchmarks
|
\section SMTopology_Benchmarks Benchmarks
|
||||||
|
|
||||||
The machine used is a PC running Ubuntu 18.04 with an Intel CPU Core i7-4790 CPU clocked at 3.60GHz with 32GB of RAM.
|
The machine used is a PC running Ubuntu 18.04 with an Intel CPU Core i7-4790 CPU clocked at 3.60GHz with 32GB of RAM.
|
||||||
|
|
@ -183,6 +217,10 @@ The canonical form of a curve is obtained by flattening its brackets, removing i
|
||||||
\subsection SMTopology_Homotopy_Test Homotopy Test
|
\subsection SMTopology_Homotopy_Test Homotopy Test
|
||||||
It can be proven that the canonical form is uniquely defined and only depends on the homotopy class of the curve. Hence, the curves \f$C'\f$ and \f$D'\f$ in \f$\cal{Q}\f$ are homotopic if and only if their canonical forms are equal. Since each curve is defined as a sequence of (oriented) edges up to a cyclic permutation, we resort to the Knuth-Morris-Pratt algorithm to decide in linear time if the canonical forms are the same up to a cyclic permutation.
|
It can be proven that the canonical form is uniquely defined and only depends on the homotopy class of the curve. Hence, the curves \f$C'\f$ and \f$D'\f$ in \f$\cal{Q}\f$ are homotopic if and only if their canonical forms are equal. Since each curve is defined as a sequence of (oriented) edges up to a cyclic permutation, we resort to the Knuth-Morris-Pratt algorithm to decide in linear time if the canonical forms are the same up to a cyclic permutation.
|
||||||
|
|
||||||
|
\subsection SMTopology_SNC Find Shortest Non-contractible Cycle
|
||||||
|
|
||||||
|
Let \f$T\f$ be a spanning tree of the graph \f$G\f$ embedded in the surface \f$\Sigma\f$. Let \f$C^*\f$ be the subgraph of the dual graph \f$G^*\f$ of \f$G\f$ with the same vertex set as \f$G^*\f$ and the edge set be \f$E(G^*)\backslash E(T)^*\f$. Repeatedly remove from \f$C^*\f$ the edges with an incident vertex of degree one, the remaining set of edges is denoted as \f$E_{nc}(T)^*\f$. It has been proven that for any edge \f$ab\in E_{nc}(T)\f$, the path from a vertex \f$v\f$ following \f$T\f$ to \f$a\f$, the edge \f$ab\f$, and the path from \f$b\f$ following \f$T\f$ back to \f$v\f$ is a closed path (denoted as \f$\tau(T, v, ab)\f$) and is a non-contractible cycle. Furthermore, if \f$T\f$ is a BFS tree (or Dijkstra tree in the weighted case) rooted at \f$v\f$, the shortest cycle found among \f$\tau(T, v, e)\f$ for any \f$e\in E_{nc}(T)\f$ is the shortest non-contractible cycle through \f$v\f$.
|
||||||
|
|
||||||
\section Implementation History
|
\section Implementation History
|
||||||
|
|
||||||
The code was developed in 2018 by Guillaume Damiand and Francis Lazarus.
|
The code was developed in 2018 by Guillaume Damiand and Francis Lazarus.
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,8 @@
|
||||||
\example Surface_mesh_topology/path_homotopy_with_symbols.cpp
|
\example Surface_mesh_topology/path_homotopy_with_symbols.cpp
|
||||||
\example Surface_mesh_topology/path_homotopy_with_symbols_2.cpp
|
\example Surface_mesh_topology/path_homotopy_with_symbols_2.cpp
|
||||||
\example Surface_mesh_topology/open_path_homotopy.cpp
|
\example Surface_mesh_topology/open_path_homotopy.cpp
|
||||||
|
\example Surface_mesh_topology/shortest_noncontractible_cycle_through_a_vertex.cpp
|
||||||
|
\example Surface_mesh_topology/shortest_noncontractible_cycle_using_BFS.cpp
|
||||||
|
\example Surface_mesh_topology/edgewidth_surface_mesh.cpp
|
||||||
|
\example Surface_mesh_topology/unsew_edgewidth_repeatedly.cpp
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ if ( CGAL_FOUND )
|
||||||
target_link_libraries(basic_example_torus PUBLIC CGAL::CGAL_Qt5)
|
target_link_libraries(basic_example_torus PUBLIC CGAL::CGAL_Qt5)
|
||||||
target_link_libraries(open_path_homotopy PUBLIC CGAL::CGAL_Qt5)
|
target_link_libraries(open_path_homotopy PUBLIC CGAL::CGAL_Qt5)
|
||||||
target_link_libraries(surface_mesh_topology_with_sm_and_polyhedron PUBLIC CGAL::CGAL_Qt5)
|
target_link_libraries(surface_mesh_topology_with_sm_and_polyhedron PUBLIC CGAL::CGAL_Qt5)
|
||||||
|
target_link_libraries(unsew_edgewidth_repeatedly PUBLIC CGAL::CGAL_Qt5)
|
||||||
|
target_link_libraries(shortest_noncontractible_cycle_through_a_vertex PUBLIC CGAL::CGAL_Qt5)
|
||||||
|
target_link_libraries(shortest_noncontractible_cycle_using_BFS PUBLIC CGAL::CGAL_Qt5)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
else()
|
else()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
#include <CGAL/Simple_cartesian.h>
|
||||||
|
#include <CGAL/Surface_mesh.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <CGAL/Shortest_noncontractible_cycle.h>
|
||||||
|
#include <CGAL/squared_distance_3.h>
|
||||||
|
|
||||||
|
using Kernel = CGAL::Simple_cartesian<double>;
|
||||||
|
using Point = Kernel::Point_3;
|
||||||
|
using Mesh = CGAL::Surface_mesh<Point>;
|
||||||
|
|
||||||
|
struct Weight_functor {
|
||||||
|
using Weight_t = double;
|
||||||
|
Weight_functor(const Mesh& mesh) : m_mesh(mesh) {}
|
||||||
|
double operator()(Mesh::Halfedge_index he) const {
|
||||||
|
Point A = m_mesh.point(m_mesh.vertex(m_mesh.edge(he), 0));
|
||||||
|
Point B = m_mesh.point(m_mesh.vertex(m_mesh.edge(he), 1));
|
||||||
|
return CGAL::sqrt(CGAL::squared_distance(A, B));
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Mesh m_mesh;
|
||||||
|
};
|
||||||
|
|
||||||
|
using SNC = CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<Mesh, Weight_functor>;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
std::cout << "Program edgewidth_surface_mesh started.\n";
|
||||||
|
Mesh sm;
|
||||||
|
std::ifstream inp ((argc > 1) ? argv[1] : "../../examples/Surface_mesh_topology/data/3torus-smooth.off");
|
||||||
|
if (inp.fail()) {
|
||||||
|
std::cout << "Cannot read file. Exiting program\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
inp >> sm;
|
||||||
|
std::cout << "File loaded. Running the main program...\n";
|
||||||
|
|
||||||
|
Weight_functor wf(sm);
|
||||||
|
SNC snc(sm, wf);
|
||||||
|
SNC::Path cycle;
|
||||||
|
SNC::Distance_type cycle_length;
|
||||||
|
|
||||||
|
std::cout << "Finding edge-width of the mesh...\n";
|
||||||
|
snc.edge_width(cycle, &cycle_length);
|
||||||
|
if (cycle.size() == 0) {
|
||||||
|
std::cout << " Cannot find edge-width. Stop.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << " Number of edges in cycle: " << cycle.size() << std::endl;
|
||||||
|
std::cout << " Cycle length: " << cycle_length << std::endl;
|
||||||
|
std::cout << " Root: " << sm.point(sm.vertex(sm.edge(cycle[0]), 0)) << std::endl;
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
#include <CGAL/Linear_cell_complex_for_generalized_map.h>
|
||||||
|
#include <CGAL/Linear_cell_complex_constructors.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <CGAL/Shortest_noncontractible_cycle.h>
|
||||||
|
#include <CGAL/IO/Color.h>
|
||||||
|
#include <CGAL/draw_linear_cell_complex.h>
|
||||||
|
#include <CGAL/squared_distance_3.h>
|
||||||
|
#define CGAL_USE_BASIC_VIEWER 1
|
||||||
|
|
||||||
|
using LCC_3 = CGAL::Linear_cell_complex_for_generalized_map<2, 3>;
|
||||||
|
using Dart_const_handle = LCC_3::Dart_const_handle;
|
||||||
|
|
||||||
|
struct Weight_functor {
|
||||||
|
Weight_functor(const LCC_3& lcc) : m_lcc(lcc) { }
|
||||||
|
using Weight_t = double;
|
||||||
|
Weight_t operator()(Dart_const_handle dh) const {
|
||||||
|
auto x = m_lcc.point_of_vertex_attribute(m_lcc.vertex_attribute(dh));
|
||||||
|
auto y = m_lcc.point_of_vertex_attribute(m_lcc.vertex_attribute(m_lcc.template alpha<0>(dh)));
|
||||||
|
return CGAL::sqrt(CGAL::squared_distance(x, y));
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
const LCC_3& m_lcc;
|
||||||
|
};
|
||||||
|
|
||||||
|
using SNC = CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<LCC_3, Weight_functor>;
|
||||||
|
|
||||||
|
LCC_3 lcc;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
std::cout << "Program shortest_noncontractible_cycle_through_a_vertex started.\n";
|
||||||
|
std::ifstream inp;
|
||||||
|
if (argc == 1) inp = std::ifstream("../../examples/Surface_mesh_topology/data/3torus.off");
|
||||||
|
else inp = std::ifstream(argv[1]);
|
||||||
|
if (inp.fail()) {
|
||||||
|
std::cout << "Cannot load file. Exiting program...\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
CGAL::load_off(lcc, inp);
|
||||||
|
std::cout << "File loaded. Running the main program...\n";
|
||||||
|
|
||||||
|
Weight_functor wf(lcc);
|
||||||
|
SNC snc(lcc, wf);
|
||||||
|
SNC::Path cycle;
|
||||||
|
SNC::Distance_type cycle_length;
|
||||||
|
|
||||||
|
/// Change the value of `root` to test the algorithm at another vertex
|
||||||
|
auto root = lcc.darts().begin();
|
||||||
|
std::cout << "Finding the shortest noncontractible cycle...\n";
|
||||||
|
snc.find_cycle(root, cycle, &cycle_length);
|
||||||
|
if (cycle.size() == 0) {
|
||||||
|
std::cout << " Cannot find such cycle. Stop.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::cout << " Number of edges in cycle: " << cycle.size() << std::endl;
|
||||||
|
std::cout << " Cycle length: " << cycle_length << std::endl;
|
||||||
|
std::cout << " Root: " << lcc.point_of_vertex_attribute(lcc.vertex_attribute(root)) << std::endl;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
#include <CGAL/Linear_cell_complex_for_combinatorial_map.h>
|
||||||
|
#include <CGAL/Linear_cell_complex_constructors.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <CGAL/Shortest_noncontractible_cycle.h>
|
||||||
|
#include <CGAL/IO/Color.h>
|
||||||
|
#include <CGAL/draw_linear_cell_complex.h>
|
||||||
|
#include <CGAL/squared_distance_3.h>
|
||||||
|
#define CGAL_USE_BASIC_VIEWER 1
|
||||||
|
|
||||||
|
using LCC_3 = CGAL::Linear_cell_complex_for_combinatorial_map<2, 3>;
|
||||||
|
using SNC = CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<LCC_3>;
|
||||||
|
|
||||||
|
LCC_3 lcc;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
std::cout << "Program shortest_noncontractible_cycle_using_BFS started.\n";
|
||||||
|
std::ifstream inp;
|
||||||
|
if (argc == 1) inp = std::ifstream("../../examples/Surface_mesh_topology/data/3torus-smooth.off");
|
||||||
|
else inp = std::ifstream(argv[1]);
|
||||||
|
if (inp.fail()) {
|
||||||
|
std::cout << "Cannot read file. Exiting program\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
CGAL::load_off(lcc, inp);
|
||||||
|
std::cout << "File loaded. Running the main program...\n";
|
||||||
|
|
||||||
|
SNC snc(lcc);
|
||||||
|
SNC::Path cycle;
|
||||||
|
SNC::Distance_type cycle_length;
|
||||||
|
|
||||||
|
/// Change the value of `root` to test the algorithm at another vertex
|
||||||
|
auto root = lcc.darts().begin();
|
||||||
|
std::cout << "Finding the shortest noncontractible cycle...\n";
|
||||||
|
snc.find_cycle(root, cycle, &cycle_length);
|
||||||
|
if (cycle.size() == 0) {
|
||||||
|
std::cout << " Cannot find such cycle. Stop.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::cout << " Number of edges in cycle: " << cycle.size() << std::endl;
|
||||||
|
std::cout << " Cycle length: " << cycle_length << std::endl;
|
||||||
|
std::cout << " Root: " << lcc.point_of_vertex_attribute(lcc.vertex_attribute(root)) << std::endl;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
#include <CGAL/Linear_cell_complex_for_generalized_map.h>
|
||||||
|
#include <CGAL/Linear_cell_complex_constructors.h>
|
||||||
|
#include <CGAL/Shortest_noncontractible_cycle.h>
|
||||||
|
#include <CGAL/IO/Color.h>
|
||||||
|
#include <CGAL/draw_linear_cell_complex.h>
|
||||||
|
#include <CGAL/squared_distance_3.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <unordered_set>
|
||||||
|
#define CGAL_USE_BASIC_VIEWER 1
|
||||||
|
|
||||||
|
using LCC_3 = CGAL::Linear_cell_complex_for_generalized_map<2, 3>;
|
||||||
|
using Dart_handle = LCC_3::Dart_handle;
|
||||||
|
using Dart_const_handle = LCC_3::Dart_const_handle;
|
||||||
|
using Dart_container = std::vector<Dart_handle>;
|
||||||
|
using Point = LCC_3::Point;
|
||||||
|
|
||||||
|
struct Weight_functor {
|
||||||
|
Weight_functor(const LCC_3& lcc) : m_lcc(lcc) { }
|
||||||
|
using Weight_t = double;
|
||||||
|
Weight_t operator()(Dart_const_handle dh) const {
|
||||||
|
const Point& x = m_lcc.point(dh);
|
||||||
|
const Point& y = m_lcc.point(m_lcc.template alpha<0>(dh));
|
||||||
|
return CGAL::sqrt(CGAL::squared_distance(x, y));
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
const LCC_3& m_lcc;
|
||||||
|
};
|
||||||
|
|
||||||
|
using SNC = CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<LCC_3, Weight_functor>;
|
||||||
|
|
||||||
|
struct Draw_functor : public CGAL::DefaultDrawingFunctorLCC {
|
||||||
|
Draw_functor(LCC_3::size_type am1, LCC_3::size_type am2) : is_root(am1),
|
||||||
|
belong_to_cycle(am2)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<typename LCC>
|
||||||
|
bool colored_vertex(const LCC& alcc, typename LCC::Dart_const_handle dh) const
|
||||||
|
{ return alcc.is_marked(dh, is_root); }
|
||||||
|
|
||||||
|
template<typename LCC>
|
||||||
|
CGAL::Color vertex_color(const LCC& /* alcc */, typename LCC::Dart_const_handle /* dh */) const
|
||||||
|
{ return CGAL::Color(0,255,0); }
|
||||||
|
|
||||||
|
template<typename LCC>
|
||||||
|
bool colored_edge(const LCC& alcc, typename LCC::Dart_const_handle dh) const
|
||||||
|
{ return alcc.is_marked(dh, belong_to_cycle); }
|
||||||
|
|
||||||
|
template<typename LCC>
|
||||||
|
CGAL::Color edge_color(const LCC& /* alcc*/, typename LCC::Dart_const_handle /* dh */) const
|
||||||
|
{ return CGAL::Color(0, 0, 255); }
|
||||||
|
|
||||||
|
template<typename LCC>
|
||||||
|
bool colored_face(const LCC& /* alcc */, typename LCC::Dart_const_handle /* dh */) const {return true;}
|
||||||
|
|
||||||
|
template<typename LCC>
|
||||||
|
CGAL::Color face_color(const LCC& /* alcc */, typename LCC::Dart_const_handle /* dh */) const
|
||||||
|
{return CGAL::Color(211, 211, 211);}
|
||||||
|
|
||||||
|
template<typename LCC>
|
||||||
|
bool colored_volume(const LCC& /* alcc */, typename LCC::Dart_const_handle /* dh */) const { return false; }
|
||||||
|
|
||||||
|
LCC_3::size_type is_root;
|
||||||
|
LCC_3::size_type belong_to_cycle;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
std::cout << "Program unsew_edgewidth_repeatedly started.\n";
|
||||||
|
LCC_3 lccoriginal, lcccopy;
|
||||||
|
std::ifstream inp;
|
||||||
|
if (argc == 1) inp = std::ifstream("../../examples/Surface_mesh_topology/data/3torus.off");
|
||||||
|
else inp = std::ifstream(argv[1]);
|
||||||
|
CGAL::load_off(lccoriginal, inp);
|
||||||
|
|
||||||
|
boost::unordered_map<Dart_handle, Dart_handle> copy_to_origin;
|
||||||
|
lcccopy.copy(lccoriginal, NULL, ©_to_origin);
|
||||||
|
|
||||||
|
LCC_3::size_type is_root=lccoriginal.get_new_mark();
|
||||||
|
LCC_3::size_type belong_to_cycle=lccoriginal.get_new_mark();
|
||||||
|
Draw_functor df(is_root, belong_to_cycle);
|
||||||
|
|
||||||
|
std::cout << "File loaded. Running the main program...\n";
|
||||||
|
for (int loop = 1; ; ++loop) {
|
||||||
|
std::cout << "Finding #" << loop << " edge-width:\n";
|
||||||
|
Weight_functor wf(lcccopy);
|
||||||
|
SNC snc(lcccopy, wf);
|
||||||
|
SNC::Path cycle;
|
||||||
|
SNC::Distance_type x;
|
||||||
|
snc.edge_width(cycle, &x);
|
||||||
|
if (cycle.size() == 0) {
|
||||||
|
std::cout << " Cannot find edge-width. Stop.\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (LCC_3::Dart_of_cell_range<0>::iterator it=lcccopy.darts_of_cell<0>(cycle[0]).begin(),
|
||||||
|
itend=lcccopy.darts_of_cell<0>(cycle[0]).end(); it!=itend; ++it)
|
||||||
|
{
|
||||||
|
if (copy_to_origin.count(it)>0 &&
|
||||||
|
!lccoriginal.is_marked(copy_to_origin[it], is_root))
|
||||||
|
{
|
||||||
|
lccoriginal.mark_cell<0>(copy_to_origin[it], is_root);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto e : cycle)
|
||||||
|
{
|
||||||
|
for (auto it=lcccopy.darts_of_cell<1>(e).begin(),
|
||||||
|
itend=lcccopy.darts_of_cell<1>(e).end(); it!=itend; ++it)
|
||||||
|
{
|
||||||
|
if (copy_to_origin.count(it)>0 &&
|
||||||
|
!lccoriginal.is_marked(copy_to_origin[it], belong_to_cycle))
|
||||||
|
{
|
||||||
|
lccoriginal.mark_cell<1>(copy_to_origin[it], belong_to_cycle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!lcccopy.is_free<2>(e))
|
||||||
|
{ lcccopy.unsew<2>(e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << " Number of edges in cycle: " << cycle.size() << std::endl;
|
||||||
|
std::cout << " Cycle length: " << x << std::endl;
|
||||||
|
std::cout << " Root: " << lcccopy.point_of_vertex_attribute(lcccopy.vertex_attribute(cycle[0])) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CGAL::draw(lccoriginal, "Hello", false, df);
|
||||||
|
|
||||||
|
lccoriginal.free_mark(belong_to_cycle);
|
||||||
|
lccoriginal.free_mark(is_root);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,499 @@
|
||||||
|
#ifndef CGAL_SHORTEST_NONCONTRACTIBLE_CYCLE_H
|
||||||
|
#define CGAL_SHORTEST_NONCONTRACTIBLE_CYCLE_H
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
#include <CGAL/Generalized_map.h>
|
||||||
|
#include <CGAL/Linear_cell_complex_for_generalized_map.h>
|
||||||
|
#include <CGAL/Combinatorial_map.h>
|
||||||
|
#include <CGAL/Linear_cell_complex_for_combinatorial_map.h>
|
||||||
|
|
||||||
|
namespace CGAL {
|
||||||
|
namespace Surface_mesh_topology {
|
||||||
|
|
||||||
|
template <class Mesh_, class Weight_ = void>
|
||||||
|
class Shortest_noncontractible_cycle {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
using Mesh_original = Mesh_;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct Weight_functor { using Weight = T; };
|
||||||
|
|
||||||
|
template <bool, class T, class>
|
||||||
|
struct Weight_functor_selector : Weight_functor<T> { };
|
||||||
|
|
||||||
|
template <class T, class F>
|
||||||
|
struct Weight_functor_selector<false, T, F> : Weight_functor<F> { };
|
||||||
|
|
||||||
|
struct Default_weight_functor {
|
||||||
|
using Weight_t = unsigned int;
|
||||||
|
template <class T>
|
||||||
|
Weight_t operator() (T) const { return 1; }
|
||||||
|
};
|
||||||
|
|
||||||
|
using Weight = typename Weight_functor_selector<std::is_same<Weight_, void>::value,
|
||||||
|
Default_weight_functor,
|
||||||
|
Weight_>::Weight;
|
||||||
|
using Distance_type = typename Weight::Weight_t;
|
||||||
|
|
||||||
|
struct Attributes {
|
||||||
|
template <class GenericMap>
|
||||||
|
struct Dart_wrapper {
|
||||||
|
using Vertex_attribute = CGAL::Cell_attribute<GenericMap, int>;
|
||||||
|
using Edge_attribute = CGAL::Cell_attribute<GenericMap, Distance_type>;
|
||||||
|
using Face_attribute = CGAL::Cell_attribute<GenericMap, void>;
|
||||||
|
using Attributes = CGAL::cpp11::tuple<Vertex_attribute, Edge_attribute, Face_attribute>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SNC_for_generalized_map {
|
||||||
|
using Generic_map = CGAL::Generalized_map<2, Attributes>;
|
||||||
|
using Dart_handle_original = typename Mesh_original::Dart_handle;
|
||||||
|
using Dart_const_handle_original = typename Mesh_original::Dart_const_handle;
|
||||||
|
using Copy_to_origin_map = boost::unordered_map<typename Generic_map::Dart_handle,
|
||||||
|
Dart_handle_original>;
|
||||||
|
using Origin_to_copy_map = boost::unordered_map<Dart_handle_original,
|
||||||
|
typename Generic_map::Dart_handle>;
|
||||||
|
|
||||||
|
static typename Generic_map::Dart_handle opposite(Generic_map& amap, typename Generic_map::Dart_handle dh)
|
||||||
|
{
|
||||||
|
return amap.template alpha<2,0>(dh);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copy(Generic_map& target, Mesh_original& source,
|
||||||
|
Origin_to_copy_map& origin_to_copy, Copy_to_origin_map& copy_to_origin)
|
||||||
|
{
|
||||||
|
target.copy(source, &origin_to_copy, ©_to_origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_weights(Generic_map& target, Mesh_original& source,
|
||||||
|
Origin_to_copy_map& origin_to_copy, const Weight& wf)
|
||||||
|
{
|
||||||
|
for (auto it = source.darts().begin(), itend = source.darts().end(); it != itend; ++it)
|
||||||
|
target.template info<1>(origin_to_copy[it]) = wf(it);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SNC_for_combinatorial_map {
|
||||||
|
using Generic_map = CGAL::Combinatorial_map<2, Attributes>;
|
||||||
|
using Dart_handle_original = typename Mesh_original::Dart_handle;
|
||||||
|
using Dart_const_handle_original = typename Mesh_original::Dart_const_handle;
|
||||||
|
using Copy_to_origin_map = boost::unordered_map<typename Generic_map::Dart_handle,
|
||||||
|
Dart_handle_original>;
|
||||||
|
using Origin_to_copy_map = boost::unordered_map<Dart_handle_original,
|
||||||
|
typename Generic_map::Dart_handle>;
|
||||||
|
|
||||||
|
static typename Generic_map::Dart_handle opposite(Generic_map& amap, typename Generic_map::Dart_handle dh)
|
||||||
|
{
|
||||||
|
return amap.opposite(dh);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copy(Generic_map& target, Mesh_original& source,
|
||||||
|
Origin_to_copy_map& origin_to_copy, Copy_to_origin_map& copy_to_origin)
|
||||||
|
{
|
||||||
|
target.copy(source, &origin_to_copy, ©_to_origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_weights(Generic_map& target, Mesh_original& source,
|
||||||
|
Origin_to_copy_map& origin_to_copy, const Weight& wf)
|
||||||
|
{
|
||||||
|
for (auto it = source.darts().begin(), itend = source.darts().end(); it != itend; ++it)
|
||||||
|
target.template info<1>(origin_to_copy[it]) = wf(it);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class>
|
||||||
|
struct Generic_map_selector {
|
||||||
|
using Generic_map = CGAL::Combinatorial_map<2, Attributes>;
|
||||||
|
using Dart_handle_original = typename boost::graph_traits<Mesh_original>::halfedge_descriptor;
|
||||||
|
using Dart_const_handle_original = typename boost::graph_traits<Mesh_original>::halfedge_descriptor;
|
||||||
|
using Copy_to_origin_map = boost::unordered_map<typename Generic_map::Dart_handle,
|
||||||
|
Dart_handle_original>;
|
||||||
|
using Origin_to_copy_map = boost::unordered_map<Dart_handle_original,
|
||||||
|
typename Generic_map::Dart_handle>;
|
||||||
|
|
||||||
|
static typename Generic_map::Dart_handle opposite(Generic_map& amap, typename Generic_map::Dart_handle dh)
|
||||||
|
{
|
||||||
|
return amap.opposite(dh);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copy(Generic_map& target, Mesh_original& source,
|
||||||
|
Origin_to_copy_map& origin_to_copy, Copy_to_origin_map& copy_to_origin)
|
||||||
|
{
|
||||||
|
target.import_from_halfedge_graph(source, &origin_to_copy, ©_to_origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_weights(Generic_map& target, Mesh_original& source,
|
||||||
|
Origin_to_copy_map& origin_to_copy, const Weight& wf)
|
||||||
|
{
|
||||||
|
for (typename boost::graph_traits<Mesh_original>::halfedge_iterator it = source.halfedges_begin(), itend = source.halfedges_end(); it != itend; ++it)
|
||||||
|
target.template info<1>(origin_to_copy[*it]) = wf(*it);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <unsigned int d, class Items, class Alloc, class Storage>
|
||||||
|
struct Generic_map_selector< CGAL::Generalized_map<d, Items, Alloc, Storage> > : SNC_for_generalized_map {};
|
||||||
|
|
||||||
|
template <unsigned int d, class Refs, class Items, class Alloc, class Storage>
|
||||||
|
struct Generic_map_selector< CGAL::Generalized_map_base
|
||||||
|
<d, Refs, Items, Alloc, Storage> > : SNC_for_generalized_map {};
|
||||||
|
|
||||||
|
template <unsigned int d, unsigned int d2, class Traits, class Items,
|
||||||
|
class Alloc, template<unsigned int,class,class,class,class> class Map, class Storage>
|
||||||
|
struct Generic_map_selector< CGAL::Linear_cell_complex_for_generalized_map
|
||||||
|
<d, d2, Traits, Items, Alloc, Map, Storage> > : SNC_for_generalized_map {};
|
||||||
|
|
||||||
|
template <unsigned int d, class Items, class Alloc, class Storage>
|
||||||
|
struct Generic_map_selector< CGAL::Combinatorial_map<d, Items, Alloc, Storage> > : SNC_for_combinatorial_map {};
|
||||||
|
|
||||||
|
template <unsigned int d, class Refs, class Items, class Alloc, class Storage>
|
||||||
|
struct Generic_map_selector< CGAL::Combinatorial_map_base
|
||||||
|
<d, Refs, Items, Alloc, Storage> > : SNC_for_combinatorial_map {};
|
||||||
|
|
||||||
|
template <unsigned int d, unsigned int d2, class Traits, class Items,
|
||||||
|
class Alloc, template<unsigned int,class,class,class,class> class Map, class Storage>
|
||||||
|
struct Generic_map_selector< CGAL::Linear_cell_complex_for_combinatorial_map
|
||||||
|
<d, d2, Traits, Items, Alloc, Map, Storage> > : SNC_for_combinatorial_map {};
|
||||||
|
|
||||||
|
using Gmap = typename Generic_map_selector<Mesh_original>::Generic_map;
|
||||||
|
using Gmap_wrapper = Generic_map_selector<Mesh_original>;
|
||||||
|
using Dart_handle_original = typename Gmap_wrapper::Dart_handle_original;
|
||||||
|
using Dart_const_handle_original = typename Gmap_wrapper::Dart_const_handle_original;
|
||||||
|
using Dart_handle = typename Gmap::Dart_handle;
|
||||||
|
using Attribute_handle_0 = typename Gmap::template Attribute_handle<0>::type;
|
||||||
|
using Attribute_handle_1 = typename Gmap::template Attribute_handle<1>::type;
|
||||||
|
using size_type = typename Gmap::size_type;
|
||||||
|
using Dart_container = std::vector<Dart_handle>;
|
||||||
|
using Path = std::vector<Dart_handle_original>; // Consider: CGAL::Path_on_surface<Gmap>;
|
||||||
|
|
||||||
|
Shortest_noncontractible_cycle(Mesh_original& gmap, const Weight& wf = Weight())
|
||||||
|
{
|
||||||
|
Gmap_wrapper::copy(m_gmap, gmap, m_origin_to_copy, m_copy_to_origin);
|
||||||
|
// m_gmap.display_characteristics(std::cerr);
|
||||||
|
// std::cerr << '\n';
|
||||||
|
// Initialize 2-attributes
|
||||||
|
for (auto it = m_gmap.darts().begin(), itend = m_gmap.darts().end(); it != itend; ++it) {
|
||||||
|
if (m_gmap.template attribute<2>(it)==NULL)
|
||||||
|
{ m_gmap.template set_attribute<2>(it, m_gmap.template create_attribute<2>()); }
|
||||||
|
}
|
||||||
|
// Remove all boundary by adding faces
|
||||||
|
m_gmap.template close<2>();
|
||||||
|
for (auto it = m_gmap.darts().begin(), itend = m_gmap.darts().end(); it != itend; ++it) {
|
||||||
|
if (m_gmap.template attribute<1>(it)==NULL)
|
||||||
|
{ m_gmap.template set_attribute<1>(it, m_gmap.template create_attribute<1>()); }
|
||||||
|
if (m_gmap.template attribute<0>(it)==NULL)
|
||||||
|
{ m_gmap.template set_attribute<0>(it, m_gmap.template create_attribute<0>()); }
|
||||||
|
}
|
||||||
|
// Initialize 1-attributes
|
||||||
|
Gmap_wrapper::set_weights(m_gmap, gmap, m_origin_to_copy, wf);
|
||||||
|
// Count number of vertices
|
||||||
|
int cnt = 0;
|
||||||
|
for (auto it = m_gmap.template one_dart_per_cell<0>().begin(),
|
||||||
|
itend = m_gmap.template one_dart_per_cell<0>().end(); it != itend; ++it)
|
||||||
|
++cnt;
|
||||||
|
for (auto it = m_gmap.template one_dart_per_cell<2>().begin(),
|
||||||
|
itend = m_gmap.template one_dart_per_cell<2>().end(); it != itend; ++it)
|
||||||
|
m_face_list.push_back(it);
|
||||||
|
m_spanning_tree.reserve(cnt - 1);
|
||||||
|
m_distance_from_root.reserve(cnt);
|
||||||
|
m_trace_index.reserve(cnt - 1);
|
||||||
|
// m_gmap.display_characteristics(std::cerr);
|
||||||
|
// std::cerr << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool find_cycle(Dart_handle_original root_vertex, Path& cycle, Distance_type* length = NULL)
|
||||||
|
{
|
||||||
|
Dart_handle root = m_origin_to_copy[root_vertex];
|
||||||
|
return this->find_cycle(root, cycle, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void edge_width(Path& cycle, Distance_type* length = NULL)
|
||||||
|
{
|
||||||
|
cycle.clear();
|
||||||
|
bool first_check = true;
|
||||||
|
Distance_type min_length = 0;
|
||||||
|
for (Attribute_handle_0 att_it = m_gmap.template attributes<0>().begin(), att_itend = m_gmap.template attributes<0>().end(); att_it != att_itend; ++att_it) {
|
||||||
|
Dart_handle it = att_it->dart();
|
||||||
|
Distance_type temp_length;
|
||||||
|
if (first_check) {
|
||||||
|
if (!find_cycle(it, cycle, &temp_length)) continue;
|
||||||
|
min_length = temp_length;
|
||||||
|
first_check = false;
|
||||||
|
} else {
|
||||||
|
if (find_cycle(it, cycle, &temp_length, &min_length))
|
||||||
|
min_length = temp_length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (length != NULL) *length = min_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void find_spanning_tree(Dart_handle root, Dart_container& spanning_tree,
|
||||||
|
std::vector<Distance_type>& distance_from_root, std::vector<int>& trace_index)
|
||||||
|
{
|
||||||
|
if (std::is_same<Weight_, void>::value)
|
||||||
|
find_BFS_tree(root, spanning_tree, distance_from_root, trace_index);
|
||||||
|
else
|
||||||
|
find_Dijkstra_tree(root, spanning_tree, distance_from_root, trace_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Dijkstra_comparator {
|
||||||
|
Dijkstra_comparator(const std::vector<Distance_type>& distance_from_root) : m_distance(distance_from_root) {}
|
||||||
|
bool operator()(const int x, const int y) const { return m_distance[x] > m_distance[y]; }
|
||||||
|
private:
|
||||||
|
const std::vector<Distance_type>& m_distance;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create a spanning tree using Dijkstra
|
||||||
|
void find_Dijkstra_tree(Dart_handle root, Dart_container& spanning_tree,
|
||||||
|
std::vector<Distance_type>& distance_from_root, std::vector<int>& trace_index)
|
||||||
|
{
|
||||||
|
// Preparation
|
||||||
|
Dijkstra_comparator dc (distance_from_root);
|
||||||
|
std::priority_queue<int, std::vector<int>, Dijkstra_comparator> pq(dc);
|
||||||
|
int vertex_index = 0;
|
||||||
|
size_type vertex_visited;
|
||||||
|
try {
|
||||||
|
vertex_visited = m_gmap.get_new_mark();
|
||||||
|
} catch (typename Gmap::Exception_no_more_available_mark) {
|
||||||
|
std::cerr << "No more free mark, exit." << std::endl;
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
// Begin Dijkstra
|
||||||
|
pq.push(0);
|
||||||
|
m_gmap.template info<0>(root) = vertex_index;
|
||||||
|
m_gmap.template mark_cell<0>(root, vertex_visited);
|
||||||
|
distance_from_root.push_back(0);
|
||||||
|
|
||||||
|
while (pq.size()) {
|
||||||
|
int u_index = pq.top();
|
||||||
|
pq.pop();
|
||||||
|
Dart_handle u = (u_index == 0) ? root : m_gmap.next(spanning_tree[u_index - 1]);
|
||||||
|
CGAL_assertion(u_index == m_gmap.template info<0>(u));
|
||||||
|
bool first_run = true;
|
||||||
|
for (Dart_handle it = u; first_run || it != u; it = m_gmap.next(Gmap_wrapper::opposite(m_gmap, it))) {
|
||||||
|
first_run = false;
|
||||||
|
Dart_handle v = m_gmap.next(it);
|
||||||
|
Distance_type w = m_gmap.template info<1>(it);
|
||||||
|
if (!m_gmap.is_marked(v, vertex_visited)) {
|
||||||
|
int v_index = ++vertex_index;
|
||||||
|
CGAL_assertion(v_index == distance_from_root.size());
|
||||||
|
distance_from_root.push_back(distance_from_root[u_index] + w);
|
||||||
|
spanning_tree.push_back(it);
|
||||||
|
trace_index.push_back(u_index - 1);
|
||||||
|
m_gmap.template info<0>(v) = v_index;
|
||||||
|
m_gmap.template mark_cell<0>(v, vertex_visited);
|
||||||
|
pq.push(v_index);
|
||||||
|
} else {
|
||||||
|
int v_index = m_gmap.template info<0>(v);
|
||||||
|
if (distance_from_root[v_index] > distance_from_root[u_index] + w) {
|
||||||
|
CGAL_assertion(v_index > 0);
|
||||||
|
distance_from_root[v_index] = distance_from_root[u_index] + w;
|
||||||
|
spanning_tree[v_index - 1] = it;
|
||||||
|
trace_index[v_index - 1] = u_index - 1;
|
||||||
|
pq.push(v_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// std::cerr << '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_gmap.free_mark(vertex_visited);
|
||||||
|
// End Dijkstra
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a spanning tree using BFS
|
||||||
|
void find_BFS_tree(Dart_handle root, Dart_container& spanning_tree,
|
||||||
|
std::vector<Distance_type>& distance_from_root, std::vector<int>& trace_index)
|
||||||
|
{
|
||||||
|
// Preparation
|
||||||
|
std::queue<int> q;
|
||||||
|
int vertex_index = 0;
|
||||||
|
size_type vertex_visited;
|
||||||
|
try {
|
||||||
|
vertex_visited = m_gmap.get_new_mark();
|
||||||
|
} catch (typename Gmap::Exception_no_more_available_mark) {
|
||||||
|
std::cerr << "No more free mark, exit." << std::endl;
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
// Begin BFS
|
||||||
|
q.push(0);
|
||||||
|
m_gmap.template info<0>(root) = vertex_index;
|
||||||
|
m_gmap.template mark_cell<0>(root, vertex_visited);
|
||||||
|
distance_from_root.push_back(0);
|
||||||
|
while (q.size()) {
|
||||||
|
int u_index = q.front();
|
||||||
|
q.pop();
|
||||||
|
Dart_handle u = (u_index == 0) ? root : m_gmap.next(spanning_tree[u_index - 1]);
|
||||||
|
CGAL_assertion(u_index == m_gmap.template info<0>(u));
|
||||||
|
bool first_run = true;
|
||||||
|
for (Dart_handle it = u; first_run || it != u; it = m_gmap.next(Gmap_wrapper::opposite(m_gmap, it))) {
|
||||||
|
first_run = false;
|
||||||
|
Dart_handle v = m_gmap.next(it);
|
||||||
|
if (!m_gmap.is_marked(v, vertex_visited)) {
|
||||||
|
int v_index = ++vertex_index;
|
||||||
|
distance_from_root.push_back(1 + distance_from_root[u_index]);
|
||||||
|
spanning_tree.push_back(it);
|
||||||
|
// `it` will lead to v
|
||||||
|
q.push(v_index);
|
||||||
|
trace_index.push_back(u_index-1);
|
||||||
|
m_gmap.template info<0>(v) = v_index;
|
||||||
|
m_gmap.template mark_cell<0>(v, vertex_visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_gmap.free_mark(vertex_visited);
|
||||||
|
// End BFS
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Check if there is only one unmarked edge around a face.
|
||||||
|
/// If there is, let dh_adjacent_edge = the edge separating it and its only adjacent face.
|
||||||
|
bool is_degree_one_face(Dart_handle dh_face, Dart_handle& dh_only_edge, size_type edge_deleted)
|
||||||
|
{
|
||||||
|
Dart_handle dh_edge = NULL;
|
||||||
|
bool first_run = true;
|
||||||
|
for (Dart_handle dh = dh_face; first_run || dh != dh_face; dh = m_gmap.next(dh)) {
|
||||||
|
first_run = false;
|
||||||
|
if (!m_gmap.is_marked(dh, edge_deleted)) {
|
||||||
|
if (dh_edge!=NULL) return false;
|
||||||
|
dh_edge=dh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dh_edge == NULL) return false;
|
||||||
|
dh_only_edge = dh_edge;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find E_nc
|
||||||
|
void find_noncon_edges(const Dart_container& spanning_tree, Dart_container& noncon_edges)
|
||||||
|
{
|
||||||
|
noncon_edges.clear();
|
||||||
|
size_type face_deleted, edge_deleted;
|
||||||
|
try {
|
||||||
|
face_deleted = m_gmap.get_new_mark();
|
||||||
|
edge_deleted = m_gmap.get_new_mark();
|
||||||
|
} catch (typename Gmap::Exception_no_more_available_mark) {
|
||||||
|
std::cerr << "No more free mark, exit." << std::endl;
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
for (Dart_handle dh_face : m_face_list) {
|
||||||
|
if (m_gmap.template attribute<2>(dh_face) == NULL) {
|
||||||
|
bool first_run = true;
|
||||||
|
for (Dart_handle it = dh_face; first_run || it != dh_face; it = m_gmap.next(it)) {
|
||||||
|
first_run = false;
|
||||||
|
if (m_gmap.is_marked(it, edge_deleted)) continue;
|
||||||
|
m_gmap.template mark_cell<1>(it, edge_deleted);
|
||||||
|
}
|
||||||
|
m_gmap.template mark_cell<2>(dh_face, face_deleted);
|
||||||
|
}
|
||||||
|
// std::cerr << '!';
|
||||||
|
}
|
||||||
|
for (auto dh : spanning_tree) {
|
||||||
|
if (m_gmap.is_marked(dh, edge_deleted)) continue;
|
||||||
|
m_gmap.template mark_cell<1>(dh, edge_deleted);
|
||||||
|
}
|
||||||
|
std::queue<Dart_handle> degree_one_faces;
|
||||||
|
// Add to queue the degree-1 faces
|
||||||
|
for (Dart_handle it : m_face_list) {
|
||||||
|
if (m_gmap.is_marked(it, face_deleted)) continue;
|
||||||
|
Dart_handle dh_only_edge = NULL;
|
||||||
|
if (is_degree_one_face(it, dh_only_edge, edge_deleted))
|
||||||
|
degree_one_faces.push(dh_only_edge);
|
||||||
|
}
|
||||||
|
// Remove the degree-1 faces
|
||||||
|
while (degree_one_faces.size()) {
|
||||||
|
Dart_handle dh_face = degree_one_faces.front();
|
||||||
|
degree_one_faces.pop();
|
||||||
|
if (!m_gmap.is_marked(dh_face, face_deleted))
|
||||||
|
m_gmap.template mark_cell<2>(dh_face, face_deleted);
|
||||||
|
if (!m_gmap.is_marked(dh_face, edge_deleted))
|
||||||
|
m_gmap.template mark_cell<1>(dh_face, edge_deleted);
|
||||||
|
Dart_handle dh_adj_face = Gmap_wrapper::opposite(m_gmap, dh_face);
|
||||||
|
if (m_gmap.is_marked(dh_adj_face, face_deleted)) continue;
|
||||||
|
Dart_handle dh_only_edge = NULL;
|
||||||
|
if (is_degree_one_face(dh_adj_face, dh_only_edge, edge_deleted))
|
||||||
|
degree_one_faces.push(dh_only_edge);
|
||||||
|
}
|
||||||
|
for (Attribute_handle_1 att_it = m_gmap.template attributes<1>().begin(), att_itend = m_gmap.template attributes<1>().end(); att_it != att_itend; ++att_it) {
|
||||||
|
Dart_handle it = att_it->dart();
|
||||||
|
if (m_gmap.template info<0>(it) >= 0 && !m_gmap.is_marked(it, edge_deleted)) {
|
||||||
|
noncon_edges.push_back(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_gmap.free_mark(edge_deleted);
|
||||||
|
m_gmap.free_mark(face_deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_to_cycle(Dart_handle dh, Path& cycle)
|
||||||
|
{
|
||||||
|
CGAL_assertion(dh != NULL);
|
||||||
|
if (m_gmap.template attribute<2>(dh) == NULL)
|
||||||
|
dh = Gmap_wrapper::opposite(m_gmap, dh);
|
||||||
|
CGAL_assertion(m_gmap.template attribute<2>(dh) != NULL);
|
||||||
|
cycle.push_back(m_copy_to_origin[dh]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool find_cycle(Dart_handle root, Path& cycle, Distance_type* length = NULL, const Distance_type* max_length = NULL) {
|
||||||
|
m_spanning_tree.clear();
|
||||||
|
m_distance_from_root.clear();
|
||||||
|
m_trace_index.clear();
|
||||||
|
for (Attribute_handle_0 att_it = m_gmap.template attributes<0>().begin(), att_itend = m_gmap.template attributes<0>().end(); att_it != att_itend; ++att_it) {
|
||||||
|
Dart_handle it = att_it->dart();
|
||||||
|
m_gmap.template info<0>(it) = -1;
|
||||||
|
}
|
||||||
|
find_spanning_tree(root, m_spanning_tree, m_distance_from_root, m_trace_index);
|
||||||
|
find_noncon_edges(m_spanning_tree, m_noncon_edges);
|
||||||
|
// std::cerr << "Done find_noncon_edges. noncon_edges.size() = " << m_noncon_edges.size() << '\n';
|
||||||
|
|
||||||
|
bool first_check = true;
|
||||||
|
Distance_type min_distance = 0;
|
||||||
|
Dart_handle min_noncon_edge;
|
||||||
|
int min_a = -1, min_b = -1;
|
||||||
|
for (auto dh : m_noncon_edges) {
|
||||||
|
Dart_handle a = dh, b = m_gmap.next(dh);
|
||||||
|
int index_a = m_gmap.template info<0>(a), index_b = m_gmap.template info<0>(b);
|
||||||
|
Distance_type sum_distance = m_distance_from_root[index_a] + m_distance_from_root[index_b]
|
||||||
|
+ m_gmap.template info<1>(dh);
|
||||||
|
if (first_check || min_distance > sum_distance) {
|
||||||
|
min_distance = sum_distance;
|
||||||
|
min_noncon_edge = dh;
|
||||||
|
min_a = index_a;
|
||||||
|
min_b = index_b;
|
||||||
|
first_check = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (first_check) return false; // no cycle found
|
||||||
|
if (length != NULL) *length = min_distance < 0 ? 0 : min_distance;
|
||||||
|
if (max_length != NULL)
|
||||||
|
if (min_distance >= *max_length) return false; // abort
|
||||||
|
|
||||||
|
cycle.clear();
|
||||||
|
// Trace back the path from `a` to root
|
||||||
|
for (int ind = min_a - 1; ind != -1; ind = m_trace_index[ind])
|
||||||
|
add_to_cycle(m_spanning_tree[ind], cycle);
|
||||||
|
// Reverse: now it is the path from root to `a`
|
||||||
|
std::reverse(cycle.begin(), cycle.end());
|
||||||
|
add_to_cycle(min_noncon_edge, cycle);
|
||||||
|
// Trace back the path from `b` to root
|
||||||
|
for (int ind = min_b - 1; ind != -1; ind = m_trace_index[ind])
|
||||||
|
add_to_cycle(Gmap_wrapper::opposite(m_gmap, m_spanning_tree[ind]), cycle);
|
||||||
|
// CGAL_assertion(cycle.is_closed());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gmap m_gmap;
|
||||||
|
typename Gmap_wrapper::Origin_to_copy_map m_origin_to_copy;
|
||||||
|
typename Gmap_wrapper::Copy_to_origin_map m_copy_to_origin;
|
||||||
|
unsigned int m_nb_of_vertices = 0;
|
||||||
|
Dart_container m_spanning_tree, m_noncon_edges, m_face_list;
|
||||||
|
std::vector<Distance_type> m_distance_from_root;
|
||||||
|
std::vector<int> m_trace_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Surface_mesh_topology
|
||||||
|
} // namespace CGAL
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,368 @@
|
||||||
|
#include <CGAL/Generalized_map.h>
|
||||||
|
#include <CGAL/Linear_cell_complex_for_combinatorial_map.h>
|
||||||
|
#include <CGAL/Linear_cell_complex_for_generalized_map.h>
|
||||||
|
#include <CGAL/Linear_cell_complex_constructors.h>
|
||||||
|
#include <CGAL/IO/Polyhedron_iostream.h>
|
||||||
|
#include <CGAL/Polyhedron_3.h>
|
||||||
|
#include <CGAL/Surface_mesh.h>
|
||||||
|
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
|
||||||
|
#include <CGAL/squared_distance_3.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <CGAL/Shortest_noncontractible_cycle.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Test the following cases:
|
||||||
|
Input type: GMap, LCC_for_CMap, Surface_mesh
|
||||||
|
Orientation: oriented, non-oriented
|
||||||
|
Distance: weighted, unweighted
|
||||||
|
Function: find_cycle, edge_width
|
||||||
|
*/
|
||||||
|
|
||||||
|
using GMap_2 = CGAL::Generalized_map<2>;
|
||||||
|
using LCC_for_CMap_2 = CGAL::Linear_cell_complex_for_combinatorial_map<2,3>;
|
||||||
|
using LCC_for_GMap_2 = CGAL::Linear_cell_complex_for_generalized_map<2,3>;
|
||||||
|
using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel;
|
||||||
|
using Point = Kernel::Point_3;
|
||||||
|
using Surface_mesh = CGAL::Surface_mesh<Point>;
|
||||||
|
using Polyhedron = CGAL::Polyhedron_3<Kernel>;
|
||||||
|
|
||||||
|
struct Weight_functor_for_GM {
|
||||||
|
using Weight_t = unsigned int;
|
||||||
|
Weight_functor_for_GM(const GMap_2& gm, GMap_2::size_type amark) : m_gm(gm), m_mark(amark) {}
|
||||||
|
unsigned int operator() (GMap_2::Dart_handle dh) const {
|
||||||
|
if (m_gm.is_marked(dh, m_mark)) return 3;
|
||||||
|
else return 4;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
const GMap_2& m_gm;
|
||||||
|
GMap_2::size_type m_mark;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Weight_functor_for_SM {
|
||||||
|
using Weight_t = double;
|
||||||
|
Weight_functor_for_SM(const Surface_mesh& mesh) : m_mesh(mesh) {}
|
||||||
|
double operator()(Surface_mesh::Halfedge_index he) const {
|
||||||
|
Point A = m_mesh.point(m_mesh.vertex(m_mesh.edge(he), 0));
|
||||||
|
Point B = m_mesh.point(m_mesh.vertex(m_mesh.edge(he), 1));
|
||||||
|
return CGAL::sqrt(CGAL::squared_distance(A, B));
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
const Surface_mesh& m_mesh;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class LCC_3>
|
||||||
|
struct Weight_functor_for_LCC {
|
||||||
|
Weight_functor_for_LCC(const LCC_3& lcc) : m_lcc(lcc) { }
|
||||||
|
using Weight_t = double;
|
||||||
|
Weight_t operator()(typename LCC_3::Dart_const_handle dh) const {
|
||||||
|
auto x = m_lcc.point_of_vertex_attribute(m_lcc.vertex_attribute(dh));
|
||||||
|
auto y = m_lcc.point_of_vertex_attribute(m_lcc.vertex_attribute(m_lcc.next(dh)));
|
||||||
|
return CGAL::sqrt(CGAL::squared_distance(x, y));
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
const LCC_3& m_lcc;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool get_data(Surface_mesh& sm) {
|
||||||
|
std::ifstream in("./data/3torus-smooth.off");
|
||||||
|
if (in.fail()) return false;
|
||||||
|
in >> sm;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_data(Polyhedron& sm) {
|
||||||
|
std::ifstream in("./data/3torus-smooth.off");
|
||||||
|
if (in.fail()) return false;
|
||||||
|
in >> sm;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool get_data(T& lcc) {
|
||||||
|
std::ifstream in("./data/3torus-smooth.off");
|
||||||
|
if (in.fail()) return false;
|
||||||
|
CGAL::load_off(lcc, in);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool find_cycle_in_unweighted_cmap_and_polyhedron() {
|
||||||
|
LCC_for_CMap_2 lcc;
|
||||||
|
if (!get_data(lcc)) {
|
||||||
|
std::cerr << "Fail find_cycle_in_unweighted_cmap_and_polyhedron: Cannot locate file data/3torus.off\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<LCC_for_CMap_2> snc1(lcc);
|
||||||
|
CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<LCC_for_CMap_2>::Path cycle1;
|
||||||
|
unsigned int cycle_length1;
|
||||||
|
LCC_for_CMap_2::Dart_handle root1 = lcc.darts().begin();
|
||||||
|
Point R = lcc.point_of_vertex_attribute(lcc.vertex_attribute(root1));
|
||||||
|
snc1.find_cycle(root1, cycle1, &cycle_length1);
|
||||||
|
if (cycle1.size() != cycle_length1) {
|
||||||
|
std::cerr << "Fail find_cycle_in_unweighted_cmap_and_polyhedron: cycle1.size() != cycle_length1\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto e : cycle1)
|
||||||
|
if (e == NULL) {
|
||||||
|
std::cerr << "Fail find_cycle_in_unweighted_cmap_and_polyhedron: NULL dart handle found in cycle1\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Polyhedron p;
|
||||||
|
if (!get_data(p)) {
|
||||||
|
std::cerr << "Fail find_cycle_in_unweighted_cmap_and_polyhedron: Cannot locate file data/3torus.off\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<Polyhedron> snc2(p);
|
||||||
|
CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<Polyhedron>::Path cycle2;
|
||||||
|
unsigned int cycle_length2;
|
||||||
|
boost::graph_traits<Polyhedron>::halfedge_iterator root2 = p.halfedges_begin(), endit = p.halfedges_end();
|
||||||
|
for (; root2 != endit; ++root2) {
|
||||||
|
if ((*root2)->vertex()->point() == R) break;
|
||||||
|
}
|
||||||
|
if (root2 == endit) {
|
||||||
|
std::cerr << "Fail find_cycle_in_unweighted_cmap_and_polyhedron: Cannot find CMap's root in the Polyhedron\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
snc2.find_cycle(*root2, cycle2, &cycle_length2);
|
||||||
|
if (cycle2.size() != cycle_length2) {
|
||||||
|
std::cerr << "Fail find_cycle_in_unweighted_cmap_and_polyhedron: cycle.size() != cycle_length\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto e : cycle2)
|
||||||
|
if (e == NULL) {
|
||||||
|
std::cerr << "Fail find_cycle_in_unweighted_cmap_and_polyhedron: NULL dart handle found in cycle\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cycle_length1 != cycle_length2) {
|
||||||
|
std::cerr << "Fail find_cycle_in_unweighted_cmap_and_polyhedron: Inconsistency in cycle length\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool edge_width_in_unweighted_polyhedron() {
|
||||||
|
Polyhedron p;
|
||||||
|
if (!get_data(p)) {
|
||||||
|
std::cerr << "Fail edge_width_in_unweighted_polyhedron: Cannot locate file data/3torus.off\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<Polyhedron> snc(p);
|
||||||
|
CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<Polyhedron>::Path cycle;
|
||||||
|
unsigned int cycle_length;
|
||||||
|
snc.edge_width(cycle, &cycle_length);
|
||||||
|
if (cycle.size() != cycle_length) {
|
||||||
|
std::cerr << "Fail edge_width_in_unweighted_polyhedron: cycle.size() != cycle_length\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto e : cycle)
|
||||||
|
if (e == NULL) {
|
||||||
|
std::cerr << "Fail edge_width_in_unweighted_polyhedron: NULL dart handle found in cycle\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool find_cycle_in_nonorientable_gmap() { // Make a non-oriented case here
|
||||||
|
// Sewing the Petersen graph embedded in a Klein bottle surface
|
||||||
|
GMap_2 gm;
|
||||||
|
std::vector<GMap_2::Dart_handle> faces;
|
||||||
|
for (int i = 0; i < 6; ++i) faces.push_back(gm.make_combinatorial_polygon(5));
|
||||||
|
gm.sew<2>(faces[0], faces[1]); // 1-2
|
||||||
|
gm.sew<2>(gm.alpha<1>(faces[1]), gm.alpha<1>(faces[2])); // 1-6
|
||||||
|
gm.sew<2>(gm.alpha<1>(faces[0]), faces[2]); // 1-5
|
||||||
|
gm.sew<2>(gm.next(faces[0]), gm.next(faces[4])); // 2-3
|
||||||
|
gm.sew<2>(gm.next(faces[1]), gm.alpha<0>(faces[4])); // 2-7
|
||||||
|
gm.sew<2>(gm.alpha<1,0,1>(faces[1]), gm.alpha<0,1,0,1,0>(faces[3])); // 6-9
|
||||||
|
gm.sew<2>(gm.alpha<1,0,1>(faces[2]), gm.alpha<1,0,1,0>(faces[3])); // 6-8
|
||||||
|
gm.sew<2>(gm.alpha<1,0,1>(faces[0]), gm.alpha<1,0,1,0,1>(faces[5])); // 5-4
|
||||||
|
gm.sew<2>(gm.next(faces[2]), gm.alpha<1,0,1,0>(faces[5])); // 5-10
|
||||||
|
gm.sew<2>(gm.alpha<0,1,0,1>(faces[0]), faces[3]); // 3-4
|
||||||
|
gm.sew<2>(gm.alpha<0,1,0,1>(faces[1]), faces[5]); // 7-9
|
||||||
|
gm.sew<2>(gm.alpha<0,1,0,1,0>(faces[2]), gm.alpha<1,0,1,0>(faces[4])); // 8-10
|
||||||
|
gm.sew<2>(gm.alpha<1>(faces[4]), gm.alpha<1>(faces[5])); // 7-10
|
||||||
|
gm.sew<2>(gm.alpha<0,1,0,1>(faces[4]), gm.alpha<1>(faces[3])); // 3-8
|
||||||
|
gm.sew<2>(gm.alpha<0,1>(faces[5]), gm.alpha<0,1,0>(faces[3])); // 9-4
|
||||||
|
|
||||||
|
// gm.display_characteristics(std::cerr);
|
||||||
|
// std::cerr << '\n';
|
||||||
|
|
||||||
|
GMap_2::size_type chosen_cycle = gm.get_new_mark(), smallest_edge = gm.get_new_mark();
|
||||||
|
gm.mark_cell<1>(gm.alpha<0,1>(faces[5]), smallest_edge); // 9-4
|
||||||
|
|
||||||
|
typedef CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<GMap_2, Weight_functor_for_GM> SNC;
|
||||||
|
|
||||||
|
Weight_functor_for_GM wf (gm, smallest_edge);
|
||||||
|
SNC snc(gm, wf);
|
||||||
|
SNC::Path cycle;
|
||||||
|
SNC::Distance_type cycle_length;
|
||||||
|
|
||||||
|
snc.find_cycle(faces[0], cycle, &cycle_length);
|
||||||
|
|
||||||
|
gm.mark_cell<1>(gm.alpha<1>(faces[1]), chosen_cycle); // 1-6
|
||||||
|
gm.mark_cell<1>(gm.alpha<1,0,1>(faces[1]), chosen_cycle); // 6-9
|
||||||
|
gm.mark_cell<1>(gm.alpha<0,1>(faces[5]), chosen_cycle); // 9-4
|
||||||
|
gm.mark_cell<1>(gm.alpha<1,0,1,0>(faces[0]), chosen_cycle); // 4-5
|
||||||
|
gm.mark_cell<1>(gm.alpha<0>(faces[2]), chosen_cycle); // 5-1
|
||||||
|
|
||||||
|
for (GMap_2::Dart_handle dh : cycle)
|
||||||
|
if (!gm.is_marked(dh, chosen_cycle)) {
|
||||||
|
std::cerr << "Fail find_cycle_in_nonorientable_gmap: Cycle found is not the same as expected cycle.\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cycle_length != 19) {
|
||||||
|
std::cerr << "Fail find_cycle_in_nonorientable_gmap: Cycle length (" << cycle_length << ") is not as expected (should be 19).\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool edge_width_in_weighted_cmap_gmap_mesh() {
|
||||||
|
LCC_for_CMap_2 lcc_cm;
|
||||||
|
LCC_for_GMap_2 lcc_gm;
|
||||||
|
Surface_mesh sm;
|
||||||
|
if (!get_data(lcc_cm) || !get_data(lcc_gm) || !get_data(sm)) {
|
||||||
|
std::cerr << "Fail edge_width_in_weighted_cmap_gmap_mesh: Cannot locate file data/3torus.off\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Weight_functor_for_LCC<LCC_for_CMap_2> wf_cm(lcc_cm);
|
||||||
|
Weight_functor_for_LCC<LCC_for_GMap_2> wf_gm(lcc_gm);
|
||||||
|
Weight_functor_for_SM wf_sm(sm);
|
||||||
|
typedef CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<LCC_for_CMap_2, Weight_functor_for_LCC<LCC_for_CMap_2> > SNC_1;
|
||||||
|
typedef CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<LCC_for_GMap_2, Weight_functor_for_LCC<LCC_for_GMap_2> > SNC_2;
|
||||||
|
typedef CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<Surface_mesh, Weight_functor_for_SM> SNC_3;
|
||||||
|
SNC_1 snc1(lcc_cm, wf_cm);
|
||||||
|
SNC_2 snc2(lcc_gm, wf_gm);
|
||||||
|
SNC_3 snc3(sm, wf_sm);
|
||||||
|
SNC_1::Path cycle1;
|
||||||
|
SNC_2::Path cycle2;
|
||||||
|
SNC_3::Path cycle3;
|
||||||
|
double cycle_length1, cycle_length2, cycle_length3;
|
||||||
|
snc1.edge_width(cycle1, &cycle_length1);
|
||||||
|
snc2.edge_width(cycle2, &cycle_length2);
|
||||||
|
snc3.edge_width(cycle3, &cycle_length3);
|
||||||
|
std::vector<Point> v1, v2, v3;
|
||||||
|
for (auto e : cycle1) {
|
||||||
|
if (e == NULL) {
|
||||||
|
std::cerr << "Fail edge_width_in_weighted_cmap_gmap_mesh: NULL dart handle found in cycle\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Point a = lcc_cm.point_of_vertex_attribute(lcc_cm.vertex_attribute(e));
|
||||||
|
Point b = lcc_cm.point_of_vertex_attribute(lcc_cm.vertex_attribute(lcc_cm.next(e)));
|
||||||
|
if (v1.empty()) {
|
||||||
|
v1.push_back(a);
|
||||||
|
v1.push_back(b);
|
||||||
|
} else {
|
||||||
|
if (a == v1.back()) v1.push_back(b);
|
||||||
|
else if (b == v1.back()) v1.push_back(a);
|
||||||
|
else {
|
||||||
|
std::cerr << "Fail edge_width_in_weighted_cmap_gmap_mesh: The cycle is ill-formed\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto e : cycle2) {
|
||||||
|
if (e == NULL) {
|
||||||
|
std::cerr << "Fail edge_width_in_weighted_cmap_gmap_mesh: NULL dart handle found in cycle\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Point a = lcc_gm.point_of_vertex_attribute(lcc_gm.vertex_attribute(e));
|
||||||
|
Point b = lcc_gm.point_of_vertex_attribute(lcc_gm.vertex_attribute(lcc_gm.next(e)));
|
||||||
|
if (v2.empty()) {
|
||||||
|
v2.push_back(a);
|
||||||
|
v2.push_back(b);
|
||||||
|
} else {
|
||||||
|
if (a == v2.back()) v2.push_back(b);
|
||||||
|
else if (b == v2.back()) v2.push_back(a);
|
||||||
|
else {
|
||||||
|
std::cerr << "Fail edge_width_in_weighted_cmap_gmap_mesh: The cycle is ill-formed\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// It is observed that reversing v2 will make it identical to v1 and v3
|
||||||
|
std::reverse(v2.begin(), v2.end());
|
||||||
|
for (auto e : cycle3) {
|
||||||
|
Point a = sm.point(sm.vertex(sm.edge(e), 0));
|
||||||
|
Point b = sm.point(sm.vertex(sm.edge(e), 1));
|
||||||
|
if (v3.empty()) {
|
||||||
|
v3.push_back(a);
|
||||||
|
v3.push_back(b);
|
||||||
|
} else {
|
||||||
|
if (a == v3.back()) v3.push_back(b);
|
||||||
|
else if (b == v3.back()) v3.push_back(a);
|
||||||
|
else {
|
||||||
|
std::cerr << "Fail edge_width_in_weighted_cmap_gmap_mesh: The cycle is ill-formed\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// for (Point x : v1) std::cout << x << " --- ";
|
||||||
|
// std::cout << '\n';
|
||||||
|
// for (Point x : v2) std::cout << x << " --- ";
|
||||||
|
// std::cout << '\n';
|
||||||
|
// for (Point x : v3) std::cout << x << " --- ";
|
||||||
|
// std::cout << '\n';
|
||||||
|
if (v1.size() != v2.size() || v1.size() != v3.size()) {
|
||||||
|
std::cerr << "Fail edge_width_in_weighted_cmap_gmap_mesh: Inconsistency in number of edges of the edge-width "
|
||||||
|
<< "(" << cycle1.size() << ", " << cycle2.size() << ", " << cycle3.size() << ").\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < v1.size(); ++i) {
|
||||||
|
if (v1[i] != v2[i] || v1[i] != v3[i]) {
|
||||||
|
std::cerr << "Fail edge_width_in_weighted_cmap_gmap_mesh: Inconsistency in the vertex ordering";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (v1[0] != v1.back()) {
|
||||||
|
std::cerr << "Fail edge_width_in_weighted_cmap_gmap_mesh: The path is not a cycle";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cycle_length1 - cycle_length2 > 1e-5 || cycle_length1 - cycle_length3 > 1e-5) {
|
||||||
|
std::cerr << "Fail edge_width_in_weighted_cmap_gmap_mesh: Inconsistency in the edge-width length"
|
||||||
|
<< std::fixed << std::setprecision(6)
|
||||||
|
<< "(" << cycle_length1 << ", " << cycle_length2 << ", " << cycle_length3 << ").\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool unsew_edge_width_repeatedly_in_unweighted_gmap() {
|
||||||
|
LCC_for_GMap_2 lcc_gm;
|
||||||
|
if (!get_data(lcc_gm)) {
|
||||||
|
std::cerr << "Fail unsew_edge_width_repeatedly_in_unweighted_gmap: Cannot locate file data/3torus.off\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<unsigned int> cycle_lengths;
|
||||||
|
unsigned int length;
|
||||||
|
do {
|
||||||
|
CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<LCC_for_GMap_2> snc(lcc_gm);
|
||||||
|
CGAL::Surface_mesh_topology::Shortest_noncontractible_cycle<LCC_for_GMap_2>::Path cycle;
|
||||||
|
snc.edge_width(cycle, &length);
|
||||||
|
for (auto e : cycle) {
|
||||||
|
if (e == NULL) {
|
||||||
|
std::cerr << "Fail unsew_edge_width_repeatedly_in_unweighted_gmap: NULL dart handle found in cycle\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lcc_gm.unsew<2>(e);
|
||||||
|
}
|
||||||
|
cycle_lengths.push_back(length);
|
||||||
|
} while (length != 0);
|
||||||
|
for (int i = 1; i < cycle_lengths.size(); ++i)
|
||||||
|
if (cycle_lengths[i] > cycle_lengths[i-1]) {
|
||||||
|
std::cerr << "Fail unsew_edge_width_repeatedly_in_unweighted_gmap: Edge width length decreases instead of increases\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
if (find_cycle_in_unweighted_cmap_and_polyhedron() &&
|
||||||
|
edge_width_in_unweighted_polyhedron() &&
|
||||||
|
find_cycle_in_nonorientable_gmap() &&
|
||||||
|
edge_width_in_weighted_cmap_gmap_mesh() &&
|
||||||
|
unsew_edge_width_repeatedly_in_unweighted_gmap())
|
||||||
|
{
|
||||||
|
std::cout << "All tests passed\n";
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
} else return EXIT_FAILURE;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue