dust3d/thirdparty/cgal/CGAL-5.1/include/CGAL/Mean_curvature_flow_skeleto...

1451 lines
46 KiB
C++

// Copyright (c) 2013 GeometryFactory (France). All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v5.1/Surface_mesh_skeletonization/include/CGAL/Mean_curvature_flow_skeletonization.h $
// $Id: Mean_curvature_flow_skeletonization.h 0779373 2020-03-26T13:31:46+01:00 Sébastien Loriot
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s) : Xiang Gao <gaox@ethz.ch>
//
#ifndef CGAL_MEAN_CURVATURE_FLOW_SKELETONIZATION_H
#define CGAL_MEAN_CURVATURE_FLOW_SKELETONIZATION_H
#include <CGAL/license/Surface_mesh_skeletonization.h>
#include <CGAL/IO/trace.h>
#include <CGAL/Timer.h>
#include <CGAL/Default.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/Polyhedron_items_with_id_3.h>
#include <CGAL/boost/graph/graph_traits_Polyhedron_3.h>
#include <CGAL/boost/graph/copy_face_graph.h>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/copy.hpp>
#include <boost/unordered_map.hpp>
#include <boost/property_map/property_map.hpp>
#include <CGAL/boost/iterator/transform_iterator.hpp>
#include <CGAL/boost/graph/iterator.h>
// Compute cotangent Laplacian
#include <CGAL/Polygon_mesh_processing/Weights.h>
// Compute the vertex normal
#include <CGAL/Polygon_mesh_processing/compute_normal.h>
// Simplification function
#include <CGAL/boost/graph/Euler_operations.h>
// Curve skeleton data structure
#include <CGAL/internal/Surface_mesh_skeletonization/Curve_skeleton.h>
// For Voronoi diagram
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_3.h>
#include <CGAL/Delaunay_triangulation_cell_base_3.h>
#include <CGAL/Triangulation_vertex_base_with_info_3.h>
// For debugging macro
#include <CGAL/internal/Surface_mesh_skeletonization/Debug.h>
// Some helper functions
#include <CGAL/Polygon_mesh_processing/measure.h>
// For detect_degenarcy
#include <CGAL/internal/Surface_mesh_skeletonization/Detect_degeneracy.h>
// Inside mesh test
#include <CGAL/Side_of_triangle_mesh.h>
// Compute bounding box
#include <CGAL/Bbox_3.h>
#include <queue>
// for default parameters
#if defined(CGAL_EIGEN3_ENABLED)
#include <CGAL/Eigen_solver_traits.h> // for sparse linear system solver
// #include <Eigen/CholmodSupport>
#endif
namespace CGAL {
namespace internal{
template < class Refs, class Point, class ID, class vertex_descriptor>
struct Skel_HDS_vertex_type : public HalfedgeDS_vertex_max_base_with_id<Refs, Point, ID>
{
typedef HalfedgeDS_vertex_max_base_with_id<Refs, Point, ID> Base;
Skel_HDS_vertex_type() : Base (), pole(ORIGIN), is_fixed(false) {}
Skel_HDS_vertex_type( Point const& p) : Base(p), pole(ORIGIN), is_fixed(false) {}
std::vector<vertex_descriptor> vertices;
Point pole;
bool is_fixed;
};
template <class vertex_descriptor>
struct Skel_polyhedron_items_3: CGAL::Polyhedron_items_with_id_3 {
template < class Refs, class Traits>
struct Vertex_wrapper {
typedef typename Traits::Point_3 Point;
typedef Skel_HDS_vertex_type< Refs, Point, std::size_t, vertex_descriptor> Vertex;
};
};
} //end of namespace internal
/// \ingroup PkgSurfaceMeshSkeletonizationRef
/// Function object that enables to extract the mean curvature
/// flow skeleton of a triangulated surface mesh.
///
/// The algorithm used takes as input a triangulated surface mesh and iteratively contracts the surface mesh
/// following the mean curvature flow \cgalCite{tagliasacchi2012mean}. The intermediate contracted surface
/// mesh is called the <em>meso-skeleton</em>.
/// After each iteration, the meso-skeleton is locally remeshed using angle split and edge contraction.
/// The process ends when the modification of the meso-skeleton between two iterations is small.
///
/// @tparam TriangleMesh
/// a model of `FaceListGraph`
///
/// @tparam Traits
/// a model of `MeanCurvatureSkeletonizationTraits`<br>
/// <b>%Default:</b>
/// \code
/// CGAL::Kernel_traits<
/// boost::property_traits<
/// boost::property_map<TriangleMesh, CGAL::vertex_point_t>::type
/// >::value_type
/// >::Kernel
/// \endcode
///
/// @tparam VertexPointMap
/// a model of `ReadWritePropertyMap`
/// with `boost::graph_traits<TriangleMesh>::%vertex_descriptor` as key and
/// `Traits::Point_3` as value type.<br>
/// <b>%Default:</b>
/// \code
/// boost::property_map<TriangleMesh, CGAL::vertex_point_t>::const_type.
/// \endcode
///
/// @tparam SolverTraits_
/// a model of `NormalEquationSparseLinearAlgebraTraits_d`.<br>
/// <b>%Default:</b> If \ref thirdpartyEigen "Eigen" 3.2 (or greater) is available
/// and `CGAL_EIGEN3_ENABLED` is defined, then an overload of `Eigen_solver_traits` is provided as default parameter:
/// \code
/// CGAL::Eigen_solver_traits<
/// Eigen::SimplicialLDLT< Eigen::SparseMatrix<double> >
/// >
/// \endcode
///
/// @cond CGAL_DOCUMENT_INTERNAL
/// @tparam Degeneracy_algorithm_tag
/// tag for selecting the degeneracy detection algorithm
/// @endcond
template <class TriangleMesh,
class Traits_ = Default,
class VertexPointMap_ = Default,
class SolverTraits_ = Default>
class Mean_curvature_flow_skeletonization
{
// Public types
public:
/// \name Types
/// @{
// Template parameters
#ifndef DOXYGEN_RUNNING
typedef typename Default::Get<
VertexPointMap_,
typename boost::property_map<TriangleMesh, CGAL::vertex_point_t>::const_type
>::type VertexPointMap;
typedef typename Default::Get<
Traits_,
typename Kernel_traits<typename boost::property_traits<typename boost::property_map<TriangleMesh, CGAL::vertex_point_t>::type>::value_type>::Kernel
>::type Traits;
#endif
#ifndef DOXYGEN_RUNNING
typedef typename Default::Get<
SolverTraits_,
#if defined(CGAL_EIGEN3_ENABLED)
CGAL::Eigen_solver_traits<
Eigen::SimplicialLDLT< Eigen::SparseMatrix<double> >
// Eigen::CholmodDecomposition< Eigen::SparseMatrix<double> >
>
#else
SolverTraits_ // no parameter provided, and Eigen is not enabled: so don't compile!
#endif
>::type SolverTraits;
#endif
/// @cond CGAL_DOCUMENT_INTERNAL
typedef typename Traits::Point_3 Point;
typedef typename Traits::Vector_3 Vector;
typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor Input_vertex_descriptor;
typedef CGAL::Polyhedron_3<Traits,internal::Skel_polyhedron_items_3<Input_vertex_descriptor> > mTriangleMesh;
typedef typename boost::property_map<mTriangleMesh, CGAL::vertex_point_t>::type mVertexPointMap;
typedef typename boost::property_map<mTriangleMesh, boost::vertex_index_t>::type VertexIndexMap;
typedef typename boost::property_map<mTriangleMesh, boost::halfedge_index_t>::type HalfedgeIndexMap;
struct Vmap {
Point point;
std::vector<Input_vertex_descriptor> vertices;
};
///@endcond
/// The graph type representing the skeleton. The vertex property
/// `Vmap` is a struct with a member `point` of type `Traits::Point_3`
/// and a member `vertices` of type
/// `std::vector<boost::graph_traits<TriangleMesh>::%vertex_descriptor>`.
/// See <a href="https://www.boost.org/doc/libs/release/libs/graph/doc/adjacency_list.html"><tt>the boost documentation</tt></a> page for more details
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, Vmap> Skeleton;
/// @}
// Repeat mTriangleMesh types
typedef typename boost::graph_traits<mTriangleMesh>::vertex_descriptor vertex_descriptor;
typedef typename boost::graph_traits<mTriangleMesh>::halfedge_descriptor halfedge_descriptor;
typedef typename boost::graph_traits<mTriangleMesh>::face_descriptor face_descriptor;
typedef typename boost::graph_traits<mTriangleMesh>::vertex_iterator vertex_iterator;
typedef typename boost::graph_traits<mTriangleMesh>::edge_descriptor edge_descriptor;
typedef typename boost::graph_traits<mTriangleMesh>::edge_iterator edge_iterator;
// Cotangent weight calculator
typedef internal::Cotangent_weight<mTriangleMesh,
typename boost::property_map<mTriangleMesh, vertex_point_t>::type,
internal::Cotangent_value_minimum_zero<mTriangleMesh,
typename boost::property_map<mTriangleMesh, vertex_point_t>::type,
internal::Cotangent_value_Meyer_secure<mTriangleMesh> > > Weight_calculator;
typedef internal::Curve_skeleton<mTriangleMesh,
VertexIndexMap,
HalfedgeIndexMap,
mVertexPointMap> Curve_skeleton;
// Repeat Triangulation types
typedef CGAL::Exact_predicates_exact_constructions_kernel Exact_kernel;
typedef CGAL::Triangulation_vertex_base_with_info_3
<vertex_descriptor, Exact_kernel> Vb;
typedef CGAL::Delaunay_triangulation_cell_base_3<Exact_kernel> Cb;
typedef CGAL::Triangulation_data_structure_3<Vb, Cb> Tds;
typedef CGAL::Delaunay_triangulation_3<Exact_kernel, Tds> Delaunay;
typedef typename Delaunay::Point Exact_point;
typedef typename Delaunay::Cell_handle Cell_handle;
typedef typename Delaunay::Vertex_handle TriVertex_handle;
typedef typename Delaunay::Finite_cells_iterator Finite_cells_iterator;
// Data members
private:
/** The meso-skeleton */
mTriangleMesh m_tmesh;
/** Storing indices of all vertices. */
VertexIndexMap m_vertex_id_pmap;
/** Storing indices of all edges. */
HalfedgeIndexMap m_hedge_id_pmap;
/** Storing the point for mTriangleMesh vertex_descriptor. */
mVertexPointMap m_tmesh_point_pmap;
/** Traits class. */
Traits m_traits;
/** Controling the velocity of movement and approximation quality. */
double m_omega_H;
/** Controling the smoothness of the medial approximation. */
double m_omega_P;
/** Edges with length less than `min_edge_length` will be collapsed. */
double m_min_edge_length;
/** Triangles with angle greater than `alpha_TH` will be split. */
double m_alpha_TH;
/** Value very close to zero. */
double m_zero_TH;
/** `contract_until_convergence` will stop if the change of area in one iteration
* is less than `delta_area`. */
double m_delta_area;
/** Surface area of original surface mesh. */
double m_original_area;
/** Maximum number of iterations. */
std::size_t m_max_iterations;
/** Should the skeleton be medially centered? */
bool m_is_medially_centered;
/** Are poles computed? */
bool m_are_poles_computed;
/** Cotangent weight calculator. */
Weight_calculator m_weight_calculator;
/** Storing the weights for edges. */
std::vector<double> m_edge_weight;
/** The sparse solver. */
SolverTraits m_solver;
/** Assign a unique id to a new vertex. */
int m_vertex_id_count;
/** The maximum id for original surface. vertices with ids
* greater than `m_max_id` are created during split,
* thus will not be considered in correspondence tracking. */
int m_max_id;
/** Used when assembling the matrix. */
std::map<int, int> m_new_id;
/** The incident angle for a halfedge. */
std::vector<double> m_halfedge_angle;
/** The normal of surface points. */
std::vector<Vector> m_normals;
// Private functions and classes
struct Vertex_to_point{
mVertexPointMap ppmap;
Vertex_to_point(mVertexPointMap ppmap): ppmap(ppmap){}
typedef typename boost::property_traits<mVertexPointMap>::reference result_type;
result_type
operator()(vertex_descriptor vd) const{
return get(ppmap, vd);
}
};
double diagonal_length(const Bbox_3& bbox)
{
double dx = bbox.xmax() - bbox.xmin();
double dy = bbox.ymax() - bbox.ymin();
double dz = bbox.zmax() - bbox.zmin();
double diag = dx * dx + dy * dy + dz * dz;
return std::sqrt(diag);
}
double init_min_edge_length()
{
vertex_iterator vb, ve;
boost::tie(vb, ve) = vertices(m_tmesh);
Vertex_to_point v_to_p(m_tmesh_point_pmap);
Bbox_3 bbox = CGAL::bbox_3(boost::make_transform_iterator(vb, v_to_p),
boost::make_transform_iterator(ve, v_to_p));
return 0.002 * diagonal_length(bbox);
}
std::size_t collapse_short_edges();
// Public methods
public:
/// \name Constructor
///@{
#ifdef DOXYGEN_RUNNING
/**
* The constructor of a skeletonization object.
*
* The algorithm parameters are initialized such that:
* - `max_triangle_angle() == 110`
* - `quality_speed_tradeoff() == 0.1`
* - `medially_centered_speed_tradeoff() == 0.2`
* - `area_variation_factor() == 0.0001`
* - `max_iterations() == 500`
* - `is_medially_centered() == true`
* - `min_edge_length()` == 0.002 * the length of the diagonal of the bounding box of `tmesh`
*
* @pre `tmesh` is a triangulated surface mesh without borders and has exactly one connected component.
* @param tmesh
* input triangulated surface mesh.
* @param vertex_point_map
* property map which associates a point to each vertex of the graph.
* @param traits
* an instance of the traits class.
*/
Mean_curvature_flow_skeletonization(const TriangleMesh& tmesh,
VertexPointMap vertex_point_map = get(CGAL::vertex_point, tmesh),
Traits traits = Traits());
#else
Mean_curvature_flow_skeletonization(const TriangleMesh& tmesh,
VertexPointMap vertex_point_map,
const Traits& traits = Traits())
: m_traits(traits), m_weight_calculator(m_tmesh)
{
init(tmesh, vertex_point_map);
}
Mean_curvature_flow_skeletonization(const TriangleMesh& tmesh,
const Traits& traits = Traits())
: m_traits(traits), m_weight_calculator(m_tmesh)
{
init(tmesh);
}
#endif
/// @} Constructor
/// \name Local Remeshing Parameters
/// @{
/// During the local remeshing step, a triangle will be split
/// if it has an angle larger than `max_triangle_angle()`.
double max_triangle_angle()
{
return m_alpha_TH;
}
/// During the local remeshing step, an edge will be collapse
/// if it is length is less than `min_edge_length()`.
double min_edge_length()
{
return m_min_edge_length;
}
/// set function for `max_triangle_angle()`
void set_max_triangle_angle(double value)
{
m_alpha_TH = value;
}
/// set function for `min_edge_length()`
void set_min_edge_length(double value)
{
m_min_edge_length = value;
}
/// @}
/// \name Algorithm Termination Parameters
/// @{
/// Maximum number of iterations performed by `contract_until_convergence()`.
std::size_t max_iterations()
{
return m_max_iterations;
}
/// The convergence is considered to be reached if the variation of the area of
/// the meso-skeleton after one iteration is smaller than
/// `area_variation_factor()*original_area` where `original_area` is the area of the input
/// triangle mesh.
double area_variation_factor()
{
return m_delta_area;
}
/// set function for `max_iterations()`
void set_max_iterations(std::size_t value)
{
m_max_iterations = value;
}
/// set function for `area_variation_factor()`
void set_area_variation_factor(double value)
{
m_delta_area = value;
}
/// @}
/// \name Vertex Motion Parameters
/// @{
/// \cgalAdvancedFunction
/// \cgalAdvancedBegin
/// Controls the velocity of movement and approximation quality:
/// decreasing this value makes the mean curvature flow based contraction converge
/// faster, but results in a skeleton of lower quality.
/// This parameter corresponds to \f$ w_H \f$ in the original publication.
/// \cgalAdvancedEnd
double quality_speed_tradeoff()
{
return m_omega_H;
}
/// If `true`, the meso-skeleton placement will be attracted by an approximation
/// of the medial axis of the mesh during the contraction steps, so will be the result skeleton.
// (an additional energy is used during the contraction using the Voronoi poles of the input triangulated mesh
// as attractors).
bool is_medially_centered()
{
return m_is_medially_centered;
}
/// \cgalAdvancedFunction
/// \cgalAdvancedBegin
/// Controls the smoothness of the medial approximation:
/// increasing this value results in a (less smooth) skeleton closer
/// to the medial axis, as well as a lower convergence speed.
/// It is only used if `is_medially_centered()==true`.
/// This parameter corresponds to \f$ w_M \f$ in the original publication.
/// \cgalAdvancedEnd
double medially_centered_speed_tradeoff()
{
return m_omega_P;
}
/// set function for `quality_speed_tradeoff()`
void set_quality_speed_tradeoff(double value)
{
m_omega_H = value;
}
/// set function for `is_medially_centered()`
void set_is_medially_centered(bool value)
{
m_is_medially_centered = value;
}
/// set function for `medially_centered_speed_tradeoff()`
void set_medially_centered_speed_tradeoff(double value)
{
m_omega_P = value;
}
/// \cond SKIP_FROM_MANUAL
void set_zero_TH(double value)
{
m_zero_TH = value;
}
double zero_TH()
{
return m_zero_TH;
}
/// \endcond
/// @cond CGAL_DOCUMENT_INTERNAL
/**
* Get the positions of fixed(degenerate) points.
*
* @param fixed_points
* return the positions of fixed points
*/
void fixed_points(std::vector<Point>& fixed_points)
{
fixed_points.clear();
for(vertex_descriptor vd : vertices(m_tmesh))
{
if (vd->is_fixed)
fixed_points.push_back(get(m_tmesh_point_pmap, vd));
}
}
/**
* Get the positions of non-fixed(non-degenerate) points.
*
* @param non_fixed_points
* return the positions of non-fixed points
*/
void non_fixed_points(std::vector<Point>& non_fixed_points)
{
non_fixed_points.clear();
for(vertex_descriptor vd : vertices(m_tmesh))
{
if (!vd->is_fixed)
non_fixed_points.push_back(get(m_tmesh_point_pmap, vd));
}
}
/**
* Get the Voronoi pole for the polygonal mesh.
*
* @param max_poles
* for each surface mesh vertex, record its correspondent Voronoi pole position
*/
void poles(std::vector<Point>& max_poles)
{
max_poles.resize(num_vertices(m_tmesh));
int cnt = 0;
for(vertex_descriptor v : vertices(m_tmesh))
{
max_poles[cnt++] = v->pole;
}
}
/// @endcond
/// @} Setter and Getter
/// \name High Level Function
/// @{
/**
* Creates the curve skeleton: the input surface mesh is iteratively
* contracted until convergence, and then turned into a curve skeleton.
*
* This is equivalent to calling `contract_until_convergence()` and `convert_to_skeleton()`.
* @param skeleton
* graph that will contain the skeleton of the input triangulated surface mesh.
* For each vertex descriptor `vd` of `skeleton`, the corresponding point
* and the set of input vertices that contracted to `vd` can be retrieved
* using `skeleton[vd].point` and `skeleton[vd].vertices` respectively.
*/
void operator()(Skeleton& skeleton)
{
contract_until_convergence();
convert_to_skeleton(skeleton);
}
/// @}
/// \name Low Level Functions
/// \cgalAdvancedBegin
/// The following functions enable the user to run the mean curvature flow skeletonization algorithm step by step.
/// \cgalAdvancedEnd
/// @{
/**
* Runs one contraction step following the mean curvature flow.
*/
void contract_geometry()
{
MCFSKEL_DEBUG(std::cerr << "before contract geometry";)
update_vertex_id();
compute_edge_weight();
// AF: attention: num_vertices will not decrease for a Surface_mesh
int nver = static_cast<int>(num_vertices(m_tmesh));
int nrows;
if (m_is_medially_centered)
{
nrows = nver * 3;
if (!m_are_poles_computed)
{
compute_voronoi_pole();
}
}
else
{
nrows = nver * 2;
}
// Assemble linear system At * A * X = At * B
typename SolverTraits::Matrix A(nrows, nver);
assemble_LHS(A);
typename SolverTraits::Vector X(nver), Bx(nrows);
typename SolverTraits::Vector Y(nver), By(nrows);
typename SolverTraits::Vector Z(nver), Bz(nrows);
assemble_RHS(Bx, By, Bz);
MCFSKEL_DEBUG(std::cerr << "before solve\n";)
// solve "At * A * X = At * B".
m_solver.normal_equation_factor(A);
m_solver.normal_equation_solver(Bx, X);
m_solver.normal_equation_solver(By, Y);
m_solver.normal_equation_solver(Bz, Z);
MCFSKEL_DEBUG(std::cerr << "after solve\n";)
// copy to surface mesh
for(vertex_descriptor vd : vertices(m_tmesh))
{
int id = static_cast<int>(get(m_vertex_id_pmap, vd));
int i = m_new_id[id];
Point p = m_traits.construct_point_3_object()(X[i], Y[i], Z[i]);
put(m_tmesh_point_pmap, vd, p);
}
MCFSKEL_DEBUG(std::cerr << "leave contract geometry\n";)
}
/**
* Collapses edges of the meso-skeleton with length less than `min_edge_length()` and returns the number of edges collapsed.
*/
std::size_t collapse_edges()
{
return collapse_short_edges();
}
/**
* Splits faces of the meso-skeleton having one angle greater than `max_triangle_angle()` and returns the number of faces split.
*/
std::size_t split_faces()
{
MCFSKEL_DEBUG(std::cerr << "before split\n";)
std::size_t num_splits = 0;
while (true)
{
if (num_vertices(m_tmesh) <= 3)
{
break;
}
std::size_t cnt = split_flat_triangles();
if (cnt == 0)
{
break;
}
else
{
num_splits += cnt;
}
}
MCFSKEL_DEBUG(std::cerr << "after split\n";)
return num_splits;
}
#ifndef DOXYGEN_RUNNING
/**
* Sequentially calls `collapse_edges()` and `split_faces()` and returns the number of edges collapsed and faces split.
*/
std::size_t remesh()
{
MCFSKEL_DEBUG(std::cerr << "before collapse edges\n";)
std::size_t num_collapses = collapse_edges();
MCFSKEL_INFO(std::cerr << "collapse " << num_collapses << " edges.\n";)
std::size_t num_splits = split_faces();
MCFSKEL_INFO(std::cerr << "split " << num_splits << " edges.\n";)
return num_collapses + num_splits;
}
#endif
/**
* Prevents degenerate vertices to move during the following contraction steps and returns the number of newly fixed vertices.
*/
std::size_t detect_degeneracies()
{
return detect_degeneracies_in_disk();
}
/// Performs subsequent calls to `contract_geometry()`, `collapse_edges()`, `split_faces()` and `detect_degeneracies()`
void contract()
{
contract_geometry();
remesh();
detect_degeneracies();
MCFSKEL_DEBUG(print_edges();)
MCFSKEL_INFO(double area = CGAL::Polygon_mesh_processing::area(m_tmesh,
CGAL::Polygon_mesh_processing::parameters::vertex_point_map(m_tmesh_point_pmap));)
MCFSKEL_INFO(std::cout << "area " << area << "\n";)
}
/**
* Iteratively calls `contract()`
* until the change of surface area of the meso-skeleton after one iteration is smaller than
* `area_variation_factor()*original_area` where `original_area` is the area of the input triangle mesh,
* or if the maximum number of iterations has been reached.
*/
void contract_until_convergence()
{
double last_area = 0;
std::size_t num_iteration = 0;
while (true)
{
MCFSKEL_INFO(std::cout << "iteration " << num_iteration + 1 << "\n";)
contract_geometry();
remesh();
detect_degeneracies();
double area = CGAL::Polygon_mesh_processing::area(m_tmesh,
CGAL::Polygon_mesh_processing::parameters::vertex_point_map(m_tmesh_point_pmap)
.geom_traits(m_traits));
double area_ratio = fabs(last_area - area) / m_original_area;
MCFSKEL_INFO(std::cout << "area " << area << "\n";)
MCFSKEL_INFO(std::cout << "|area - last_area| / original_area "
<< area_ratio << "\n";)
if (area_ratio < m_delta_area)
{
break;
}
last_area = area;
num_iteration++;
if (num_iteration >= m_max_iterations)
{
break;
}
}
}
/**
* Converts the contracted surface mesh to a skeleton curve.
* @tparam Skeleton
* an instantiation of <A href="https://www.boost.org/libs/graph/doc/adjacency_list.html">`boost::adjacency_list`</a>
* as a data structure for the skeleton curve.
* @param skeleton
* graph that will contain the skeleton of `tmesh`. It should be empty before passed to the function.
*/
void convert_to_skeleton(Skeleton& skeleton)
{
skeleton.clear();
Curve_skeleton skeletonization(m_tmesh, m_vertex_id_pmap, m_hedge_id_pmap, m_tmesh_point_pmap);
skeletonization.extract_skeleton(skeleton);
}
/// @} Public Algorithm API
/// \name Access to the Meso-Skeleton
/// @{
/// When using the low level API it is possible to access the intermediate
/// results of the skeletonization process, called meso-skeleton.
/// It is a triangulated surface mesh which is model of `FaceListGraph` and `HalfedgeListGraph`.
#ifdef DOXYGEN_RUNNING
typedef unspecified_type Meso_skeleton;
#else
typedef mTriangleMesh Meso_skeleton;
#endif
/// Reference to the collapsed triangulated surface mesh.
const Meso_skeleton& meso_skeleton() const
{
return m_tmesh;
}
/// @}
private:
// --------------------------------------------------------------------------
// Initialization
// --------------------------------------------------------------------------
/// Initialize the parameters for Mean_curvature_flow_skeletonization
void init_args()
{
m_omega_H = 0.1;
m_omega_P = 0.2;
m_delta_area = 0.0001;
m_max_iterations = 500;
m_is_medially_centered = true;
m_min_edge_length = init_min_edge_length();
m_alpha_TH = 110 * (CGAL_PI / 180.0);
m_zero_TH = 1e-7;
}
/// Initialize some global data structures such as vertex id.
void init(const TriangleMesh& tmesh)
{
typedef std::pair<Input_vertex_descriptor, vertex_descriptor> Vertex_pair;
std::vector<Vertex_pair> v2v;
copy_face_graph(tmesh, m_tmesh,
CGAL::parameters::vertex_to_vertex_output_iterator(std::back_inserter(v2v)));
// copy input vertices to keep correspondence
for(const Vertex_pair& vp : v2v)
vp.second->vertices.push_back(vp.first);
//init indices
typedef typename boost::graph_traits<mTriangleMesh>::vertex_descriptor vertex_descriptor;
typedef typename boost::graph_traits<mTriangleMesh>::halfedge_descriptor halfedge_descriptor;
std::size_t i=0;
for(vertex_descriptor vd : vertices(m_tmesh) )
vd->id()=i++;
i=0;
for(halfedge_descriptor hd : halfedges(m_tmesh) )
hd->id()=i++;
m_hedge_id_pmap = get(boost::halfedge_index, m_tmesh);
m_vertex_id_pmap = get(boost::vertex_index, m_tmesh);
//, m_hedge_id_pmap(get(boost::halfedge_index, m_tmesh))
m_are_poles_computed = false;
m_original_area = CGAL::Polygon_mesh_processing::area(m_tmesh,
CGAL::Polygon_mesh_processing::parameters::vertex_point_map(m_tmesh_point_pmap)
.geom_traits(m_traits));
m_vertex_id_count = static_cast<int>(num_vertices(m_tmesh));
m_max_id = m_vertex_id_count;
if (m_is_medially_centered)
compute_voronoi_pole();
init_args();
}
// --------------------------------------------------------------------------
// Utilities
// --------------------------------------------------------------------------
double get_x(const Vector& v){ return m_traits.compute_x_3_object()(v); }
double get_y(const Vector& v){ return m_traits.compute_y_3_object()(v); }
double get_z(const Vector& v){ return m_traits.compute_z_3_object()(v); }
double get_x(const Point& v){ return m_traits.compute_x_3_object()(v); }
double get_y(const Point& v){ return m_traits.compute_y_3_object()(v); }
double get_z(const Point& v){ return m_traits.compute_z_3_object()(v); }
// --------------------------------------------------------------------------
// Contraction
// --------------------------------------------------------------------------
/// Compute cotangent weights of all edges.
void compute_edge_weight()
{
m_edge_weight.clear();
m_edge_weight.reserve(2 * num_edges(m_tmesh));
for(halfedge_descriptor hd : halfedges(m_tmesh))
{
m_edge_weight.push_back(m_weight_calculator(hd));
}
}
/// Assemble the left hand side.
void assemble_LHS(typename SolverTraits::Matrix& A)
{
MCFSKEL_DEBUG(std::cerr << "start LHS\n";)
std::size_t nver = num_vertices(m_tmesh);
Side_of_triangle_mesh<mTriangleMesh, Traits> test_inside(m_tmesh);
for(vertex_descriptor vd : vertices(m_tmesh))
{
int id = static_cast<int>(get(m_vertex_id_pmap, vd));
int i = m_new_id[id];
// if the vertex is fixed
if (vd->is_fixed)
{
A.set_coef(i + nver, i, 1.0 / m_zero_TH, true);
}
else
{
A.set_coef(i + nver, i, m_omega_H, true);
if (m_is_medially_centered)
{
if (id < m_max_id)
{
if (test_inside(vd->pole) == CGAL::ON_BOUNDED_SIDE)
{
A.set_coef(i + nver * 2, i, m_omega_P, true);
}
}
}
}
}
for(vertex_descriptor vd : vertices(m_tmesh))
{
int id = static_cast<int>(get(m_vertex_id_pmap, vd));
int i = m_new_id[id];
double L = 1.0;
// if the vertex is fixed
if (vd->is_fixed)
{
L = 0;
}
double diagonal = 0;
for(edge_descriptor ed : in_edges(vd, m_tmesh))
{
vertex_descriptor vj = source(ed, m_tmesh);
double wij = m_edge_weight[get(m_hedge_id_pmap, halfedge(ed, m_tmesh))] * 2.0;
int jd = static_cast<int>(get(m_vertex_id_pmap, vj));
int j = m_new_id[jd];
A.set_coef(i, j, wij * L, true);
diagonal += -wij;
}
A.set_coef(i, i, diagonal, true);
}
MCFSKEL_DEBUG(std::cerr << "end LHS\n";)
}
/// Assemble the right hand side.
void assemble_RHS(typename SolverTraits::Vector& Bx,
typename SolverTraits::Vector& By,
typename SolverTraits::Vector& Bz)
{
MCFSKEL_DEBUG(std::cerr << "start RHS\n";)
Side_of_triangle_mesh<mTriangleMesh, Traits> test_inside(m_tmesh);
// assemble right columns of linear system
int nver = static_cast<int>(num_vertices(m_tmesh));
for (int i = 0; i < nver; ++i)
{
Bx[i] = 0;
By[i] = 0;
Bz[i] = 0;
}
for(vertex_descriptor vd : vertices(m_tmesh))
{
int id = static_cast<int>(get(m_vertex_id_pmap, vd));
int i = m_new_id[id];
double oh, op = 0.0;
if (vd->is_fixed)
{
oh = 1.0 / m_zero_TH;
}
else
{
oh = m_omega_H;
if (m_is_medially_centered)
{
if (id < m_max_id)
{
if (test_inside(vd->pole) == CGAL::ON_BOUNDED_SIDE)
{
op = m_omega_P;
}
}
}
}
Bx[i + nver] = get_x(get(m_tmesh_point_pmap, vd)) * oh;
By[i + nver] = get_y(get(m_tmesh_point_pmap, vd)) * oh;
Bz[i + nver] = get_z(get(m_tmesh_point_pmap, vd)) * oh;
if (m_is_medially_centered)
{
double x = get_x(vd->pole);
double y = get_y(vd->pole);
double z = get_z(vd->pole);
Bx[i + nver * 2] = x * op;
By[i + nver * 2] = y * op;
Bz[i + nver * 2] = z * op;
}
}
MCFSKEL_DEBUG(std::cerr << "end RHS\n";)
}
/// The order of vertex id is the same as the traverse order.
void update_vertex_id()
{
m_new_id.clear();
int cnt = 0;
for(vertex_descriptor vd : vertices(m_tmesh))
{
int id = static_cast<int>(get(m_vertex_id_pmap, vd));
m_new_id[id] = cnt++;
}
}
// --------------------------------------------------------------------------
// Edge collapse
// --------------------------------------------------------------------------
/// Track correspondent original surface points during collapse.
void update_pole(vertex_descriptor v0, vertex_descriptor vkept)
{
if (m_is_medially_centered)
{
const Point& pole0 = v0->pole;
const Point& pole1 = vkept->pole;
Point p1 = get(m_tmesh_point_pmap, vkept);
double dis_to_pole0 = m_traits.compute_squared_distance_3_object()(pole0, p1);
double dis_to_pole1 = m_traits.compute_squared_distance_3_object()(pole1, p1);
if (dis_to_pole0 < dis_to_pole1)
vkept->pole = v0->pole;
}
}
bool edge_should_be_collapsed(edge_descriptor ed)
{
halfedge_descriptor h = halfedge(ed, m_tmesh);
vertex_descriptor vi = source(h, m_tmesh);
vertex_descriptor vj = target(h, m_tmesh);
// an edge cannot be collapsed if both vertices are degenerate.
if (vi->is_fixed && vj->is_fixed) return false;
double sq_edge_length = m_traits.compute_squared_distance_3_object()(
get(m_tmesh_point_pmap, vi),
get(m_tmesh_point_pmap, vj));
return sq_edge_length < m_min_edge_length * m_min_edge_length;
}
// --------------------------------------------------------------------------
// Triangle split
// --------------------------------------------------------------------------
/// Compute the incident angles for all the halfedges.
void compute_incident_angle()
{
m_halfedge_angle.clear();
int ne = 2 * static_cast<int>(num_edges(m_tmesh));
m_halfedge_angle.resize(ne, 0);
int idx = 0;
for(halfedge_descriptor hd : halfedges(m_tmesh))
{
put(m_hedge_id_pmap, hd, idx++);
}
for(halfedge_descriptor hd : halfedges(m_tmesh))
{
int e_id = static_cast<int>(get(m_hedge_id_pmap, hd));
if (is_border(hd, m_tmesh))
{
m_halfedge_angle[e_id] = -1;
}
else
{
vertex_descriptor vi = source(hd, m_tmesh);
vertex_descriptor vj = target(hd, m_tmesh);
halfedge_descriptor hd_next = next(hd, m_tmesh);
vertex_descriptor vk = target(hd_next, m_tmesh);
Point pi = get(m_tmesh_point_pmap, vi);
Point pj = get(m_tmesh_point_pmap, vj);
Point pk = get(m_tmesh_point_pmap, vk);
double dis2_ij = m_traits.compute_squared_distance_3_object()(pi, pj);
double dis2_ik = m_traits.compute_squared_distance_3_object()(pi, pk);
double dis2_jk = m_traits.compute_squared_distance_3_object()(pj, pk);
double dis_ij = std::sqrt(dis2_ij);
double dis_ik = std::sqrt(dis2_ik);
double dis_jk = std::sqrt(dis2_jk);
// A degenerate triangle will never undergo a split (but rather a collapse...)
if (dis_ij < m_zero_TH || dis_ik < m_zero_TH || dis_jk < m_zero_TH)
{
m_halfedge_angle[e_id] = -1;
}
else
{
m_halfedge_angle[e_id] =
acos((dis2_ik + dis2_jk - dis2_ij) / (2.0 * dis_ik * dis_jk));
}
}
}
}
void normalize(Vector& v)
{
CGAL::Polygon_mesh_processing::internal::normalize(v, m_traits);
}
/// Project the vertex `vk` to the line of `vs` and `vt`.
Point project_vertex(const vertex_descriptor vs,
const vertex_descriptor vt,
const vertex_descriptor vk,
const vertex_descriptor vnew)
{
Point ps = get(m_tmesh_point_pmap, vs);
Point pt = get(m_tmesh_point_pmap, vt);
Point pk = get(m_tmesh_point_pmap, vk);
Vector vec_st = m_traits.construct_vector_3_object()(ps, pt);
Vector vec_sk = m_traits.construct_vector_3_object()(ps, pk);
normalize(vec_st);
double t = m_traits.compute_scalar_product_3_object()(vec_st,vec_sk);
Point st = m_traits.construct_point_3_object()( get_x(vec_st) * t, get_y(vec_st) * t, get_z(vec_st) * t);
Point pn = m_traits.construct_point_3_object()( get_x(ps) + get_x(st), get_y(ps) + get_y(st), get_z(ps) + get_z(st));
// project the pole
if (m_is_medially_centered)
{
const Point& pole_s = vs->pole;
const Point& pole_t = vt->pole;
Vector pole_st = m_traits.construct_vector_3_object()(pole_s, pole_t );
normalize(pole_st);
vnew->pole = m_traits.construct_translated_point_3_object()(
pole_s,
m_traits.construct_scaled_vector_3_object()(pole_st, t)
);
}
return pn;
}
/// Split triangles with an angle greater than `alpha_TH`.
std::size_t split_flat_triangles()
{
compute_incident_angle();
// collect edges that should be split because
// both opposite angle are larger than the
// threshold
std::vector<edge_descriptor> edges_to_split;
for(edge_descriptor ed : edges(m_tmesh))
{
halfedge_descriptor ei = halfedge(ed, m_tmesh);
halfedge_descriptor ej = opposite(ei, m_tmesh);
int ei_id = static_cast<int>(get(m_hedge_id_pmap, ei));
int ej_id = static_cast<int>(get(m_hedge_id_pmap, ej));
double angle_i = m_halfedge_angle[ei_id];
double angle_j = m_halfedge_angle[ej_id];
if (angle_i >= m_alpha_TH && angle_j >= m_alpha_TH)
edges_to_split.push_back( ed );
}
// now split the edge
std::size_t cnt = 0;
for(edge_descriptor ed : edges_to_split)
{
halfedge_descriptor ei = halfedge(ed, m_tmesh);
halfedge_descriptor ej = opposite(ei, m_tmesh);
int ei_id = static_cast<int>(get(m_hedge_id_pmap, ei));
int ej_id = static_cast<int>(get(m_hedge_id_pmap, ej));
vertex_descriptor vs = source(ei, m_tmesh);
vertex_descriptor vt = target(ei, m_tmesh);
double angle_i = m_halfedge_angle[ei_id];
double angle_j = m_halfedge_angle[ej_id];
halfedge_descriptor ek = next(angle_i > angle_j ? ei : ej, m_tmesh);
vertex_descriptor vk = target(ek, m_tmesh);
// split the edge
halfedge_descriptor en = m_tmesh.split_edge(ei);
// split the incident faces
Euler::split_face(en, next(ei,m_tmesh), m_tmesh);
if (! is_border(ej,m_tmesh))
{
Euler::split_face(ej, next(next(ej,m_tmesh),m_tmesh), m_tmesh);
}
// set id for new vertex
put(m_vertex_id_pmap, target(en,m_tmesh), m_vertex_id_count++);
Point pn = project_vertex(vs, vt, vk, target(en, m_tmesh));
// set point of new vertex
put(m_tmesh_point_pmap, target(en,m_tmesh), pn);
target(en,m_tmesh)->vertices.clear(); // do no copy the info
++cnt;
}
return cnt;
}
// --------------------------------------------------------------------------
// Degeneracy detection
// --------------------------------------------------------------------------
/// Test degeneracy of a vertex by counting the euler characteristic of
/// its local neighborhood disk.
std::size_t detect_degeneracies_in_disk()
{
std::size_t num_fixed = 0;
for(vertex_descriptor v : vertices(m_tmesh))
{
if (!v->is_fixed)
{
bool willbefixed = internal::is_vertex_degenerate(m_tmesh, m_tmesh_point_pmap,
v, m_min_edge_length, m_traits);
if (willbefixed)
{
v->is_fixed=true;
++num_fixed;
}
}
}
MCFSKEL_INFO(std::cerr << "fixed " << num_fixed << " vertices.\n";)
return num_fixed;
}
// --------------------------------------------------------------------------
// Voronoi pole
// --------------------------------------------------------------------------
/// Compute the Voronoi pole for surface vertices. The pole is the furthest
/// vertex in the Voronoi cell containing the given vertex.
void compute_voronoi_pole()
{
MCFSKEL_DEBUG(std::cout << "start compute_voronoi_pole\n";)
compute_vertex_normal();
std::vector<std::pair<Exact_point, vertex_descriptor> > points;
std::vector<std::vector<int> > point_to_pole(num_vertices(m_tmesh));
for(vertex_descriptor v : vertices(m_tmesh))
{
const Point& input_pt = get(m_tmesh_point_pmap, v);
Exact_point tp(get_x(input_pt), get_y(input_pt), get_z(input_pt));
points.push_back(std::make_pair(tp, v));
}
Delaunay T(points.begin(), points.end());
Finite_cells_iterator cit;
int cell_id = 0;
std::vector<Point> cell_dual;
cell_dual.reserve(T.number_of_cells());
for (cit = T.finite_cells_begin(); cit != T.finite_cells_end(); ++cit)
{
Cell_handle cell = cit;
Exact_point point = T.dual(cell);
cell_dual.push_back(
m_traits.construct_point_3_object()(
to_double(point.x()),
to_double(point.y()),
to_double(point.z())
)
);
// each cell has 4 incident vertices
for (int i = 0; i < 4; ++i)
{
TriVertex_handle vt = cell->vertex(i);
std::size_t id = get(m_vertex_id_pmap, vt->info());
point_to_pole[id].push_back(cell_id);
}
++cell_id;
}
typedef std::pair<Exact_point, vertex_descriptor> Pair_type;
for(const Pair_type& p : points)
{
std::size_t vid = get(m_vertex_id_pmap, p.second);
Point surface_point = get(m_tmesh_point_pmap, p.second);
double max_neg_t = 1;
int max_neg_i = -1;
for (size_t j = 0; j < point_to_pole[vid].size(); ++j)
{
int pole_id = point_to_pole[vid][j];
Point cell_point = cell_dual[pole_id];
Vector vt = m_traits.construct_vector_3_object()(surface_point, cell_point);
Vector n = m_normals[vid];
double t = m_traits.compute_scalar_product_3_object()(vt, n);
// choose the one with maximum distance along the normal
if (t < 0 && t < max_neg_t)
{
max_neg_i = pole_id;
max_neg_t = t;
}
}
p.second->pole = cell_dual[max_neg_i];
}
m_are_poles_computed = true;
}
/// Compute an approximate vertex normal for all vertices.
void compute_vertex_normal()
{
namespace PMP = CGAL::Polygon_mesh_processing;
boost::unordered_map<face_descriptor, Vector> normals;
boost::associative_property_map<
boost::unordered_map<face_descriptor, Vector> > normals_pmap(normals);
PMP::compute_face_normals(m_tmesh, normals_pmap);
m_normals.resize(num_vertices(m_tmesh));
for(vertex_descriptor v : vertices(m_tmesh))
{
int vid = static_cast<int>(get(m_vertex_id_pmap, v));
m_normals[vid] = PMP::compute_vertex_normal(v
, m_tmesh
, PMP::parameters::geom_traits(m_traits)
.face_normal_map(normals_pmap));
}
}
// --------------------------------------------------------------------------
// Debug
// --------------------------------------------------------------------------
void print_edges()
{
std::map<halfedge_descriptor, bool> visited;
for(halfedge_descriptor hd : halfedges(m_tmesh))
{
if (!visited[hd])
{
vertex_descriptor vi = source(hd, m_tmesh);
vertex_descriptor vj = target(hd, m_tmesh);
size_t vi_idx = get(m_vertex_id_pmap, vi);
size_t vj_idx = get(m_vertex_id_pmap, vj);
std::cout << vi_idx << " " << vj_idx << "\n";
visited[hd] = true;
visited[opposite(hd,m_tmesh)] = true;
}
}
}
};
template <class TriangleMesh,
class Traits_,
class VertexPointMap_,
class SolverTraits_>
std::size_t Mean_curvature_flow_skeletonization<TriangleMesh, Traits_, VertexPointMap_, SolverTraits_>::collapse_short_edges()
{
std::size_t cnt=0, prev_cnt=0;
std::set<edge_descriptor> edges_to_collapse, non_topologically_valid_collapses;
for(edge_descriptor ed : edges(m_tmesh))
if ( edge_should_be_collapsed(ed) )
edges_to_collapse.insert(ed);
do{
prev_cnt=cnt;
while(!edges_to_collapse.empty())
{
edge_descriptor ed = *edges_to_collapse.begin();
edges_to_collapse.erase(edges_to_collapse.begin());
// skip the edge is it became long enough
if ( !edge_should_be_collapsed(ed) ) continue;
if ( !Euler::does_satisfy_link_condition(ed,m_tmesh) )
{
non_topologically_valid_collapses.insert(ed);
continue;
}
halfedge_descriptor h = halfedge(ed, m_tmesh);
vertex_descriptor vi = source(h, m_tmesh);
vertex_descriptor vj = target(h, m_tmesh);
Point p = m_traits.construct_midpoint_3_object()(
get(vertex_point, m_tmesh, vi),
get(vertex_point, m_tmesh, vj) );
// invalidate the edges that will be collapsed
edges_to_collapse.erase(edge(prev(h, m_tmesh), m_tmesh));
edges_to_collapse.erase(edge(prev(opposite(h, m_tmesh), m_tmesh), m_tmesh));
non_topologically_valid_collapses.erase(ed);
non_topologically_valid_collapses.erase(edge(prev(h, m_tmesh), m_tmesh));
non_topologically_valid_collapses.erase(edge(prev(opposite(h, m_tmesh), m_tmesh), m_tmesh));
// the mesh is closed, the target of h is always the one kept
put(m_tmesh_point_pmap, vj, p);
std::vector<Input_vertex_descriptor>& vec_kept = vj->vertices;
std::vector<Input_vertex_descriptor>& vec_removed = vi->vertices;
vec_kept.insert(vec_kept.end(), vec_removed.begin(), vec_removed.end());
if (vi->is_fixed) vj->is_fixed=true;
update_pole(vi, vj);
vertex_descriptor v = Euler::collapse_edge(ed, m_tmesh);
CGAL_assertion(vj==v);
for(edge_descriptor oed : out_edges(v, m_tmesh))
if ( edge_should_be_collapsed(oed) ) edges_to_collapse.insert(oed);
++cnt;
}
if (prev_cnt==cnt) break;
edges_to_collapse.swap(non_topologically_valid_collapses);
} while(!edges_to_collapse.empty());
return cnt;
}
} //namespace CGAL
#endif // CGAL_MEAN_CURVATURE_FLOW_SKELETONIZATION_H