1625 lines
60 KiB
C
1625 lines
60 KiB
C
|
// Copyright (c) 2014 GeometryFactory
|
||
|
// All rights reserved.
|
||
|
//
|
||
|
// This file is part of CGAL (www.cgal.org).
|
||
|
// You can redistribute it and/or modify it under the terms of the GNU
|
||
|
// General Public License as published by the Free Software Foundation,
|
||
|
// either version 3 of the License, or (at your option) any later version.
|
||
|
//
|
||
|
// Licensees holding a valid commercial license may use this file in
|
||
|
// accordance with the commercial license agreement provided with the software.
|
||
|
//
|
||
|
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
||
|
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||
|
//
|
||
|
// $URL$
|
||
|
// $Id$
|
||
|
// SPDX-License-Identifier: GPL-3.0+
|
||
|
//
|
||
|
// Author(s) : Yin Xu, Andreas Fabri and Ilker O. Yaz
|
||
|
|
||
|
#ifndef CGAL_SURFACE_MESH_DEFORMATION_H
|
||
|
#define CGAL_SURFACE_MESH_DEFORMATION_H
|
||
|
|
||
|
#include <CGAL/license/Surface_mesh_deformation.h>
|
||
|
|
||
|
#include <CGAL/disable_warnings.h>
|
||
|
|
||
|
#include <CGAL/config.h>
|
||
|
#include <CGAL/Default.h>
|
||
|
#include <CGAL/tuple.h>
|
||
|
|
||
|
#include <CGAL/Polygon_mesh_processing/Weights.h>
|
||
|
#include <CGAL/Simple_cartesian.h>
|
||
|
|
||
|
#include <vector>
|
||
|
#include <list>
|
||
|
#include <utility>
|
||
|
#include <limits>
|
||
|
#include <boost/foreach.hpp>
|
||
|
|
||
|
/*
|
||
|
#define CGAL_DEFORM_MESH_USE_EXPERIMENTAL_SCALE // define it to activate optimal scale calculation,
|
||
|
// then you can define below to just scale, if not both rotate and scale will be activated
|
||
|
#define CGAL_DEFORM_MESH_JUST_EXPERIMENTAL_SCALE // to not to rotate but just scale
|
||
|
*/
|
||
|
|
||
|
// for default parameters
|
||
|
#if defined(CGAL_EIGEN3_ENABLED)
|
||
|
#include <CGAL/Eigen_solver_traits.h> // for sparse linear system solver
|
||
|
#include <CGAL/Deformation_Eigen_polar_closest_rotation_traits_3.h> // for 3x3 closest rotation computer
|
||
|
#endif
|
||
|
|
||
|
namespace CGAL {
|
||
|
|
||
|
/// \ingroup PkgSurfaceMeshDeformation
|
||
|
///@brief Deformation algorithm type
|
||
|
enum Deformation_algorithm_tag
|
||
|
{
|
||
|
ORIGINAL_ARAP, /**< use original as-rigid-as possible algorithm */
|
||
|
SPOKES_AND_RIMS, /**< use spokes and rims version of as-rigid-as possible algorithm */
|
||
|
SRE_ARAP /**< use smooth rotation enhanced As-rigid-as-possible */
|
||
|
};
|
||
|
|
||
|
/// @cond CGAL_DOCUMENT_INTERNAL
|
||
|
namespace internal {
|
||
|
template<class TriangleMesh, Deformation_algorithm_tag deformation_algorithm_tag>
|
||
|
struct Types_selectors;
|
||
|
|
||
|
template<class TriangleMesh>
|
||
|
struct Types_selectors<TriangleMesh, CGAL::SPOKES_AND_RIMS> {
|
||
|
typedef internal::Single_cotangent_weight_impl<TriangleMesh> Weight_calculator;
|
||
|
|
||
|
struct ARAP_visitor{
|
||
|
template <class VertexPointMap>
|
||
|
void init(TriangleMesh, VertexPointMap){}
|
||
|
|
||
|
void rotation_matrix_pre(
|
||
|
typename boost::graph_traits<TriangleMesh>::vertex_descriptor,
|
||
|
TriangleMesh&){}
|
||
|
|
||
|
template <class Square_matrix_3>
|
||
|
void update_covariance_matrix(
|
||
|
Square_matrix_3&,
|
||
|
const Square_matrix_3&){}
|
||
|
|
||
|
void set_sre_arap_alpha(double){}
|
||
|
};
|
||
|
};
|
||
|
|
||
|
template<class TriangleMesh>
|
||
|
struct Types_selectors<TriangleMesh, CGAL::ORIGINAL_ARAP> {
|
||
|
typedef internal::Cotangent_weight_impl<TriangleMesh> Weight_calculator;
|
||
|
|
||
|
typedef typename Types_selectors<TriangleMesh, CGAL::SPOKES_AND_RIMS>
|
||
|
::ARAP_visitor ARAP_visitor;
|
||
|
};
|
||
|
|
||
|
template<class TriangleMesh>
|
||
|
struct Types_selectors<TriangleMesh, CGAL::SRE_ARAP> {
|
||
|
typedef internal::Cotangent_weight_impl<TriangleMesh> Weight_calculator;
|
||
|
|
||
|
class ARAP_visitor{
|
||
|
double m_nb_edges_incident;
|
||
|
double m_area;
|
||
|
double m_alpha;
|
||
|
|
||
|
public:
|
||
|
ARAP_visitor(): m_alpha(0.02) {}
|
||
|
|
||
|
template<class VertexPointMap>
|
||
|
void init(TriangleMesh triangle_mesh, const VertexPointMap& vpmap)
|
||
|
{
|
||
|
// calculate area
|
||
|
m_area = 0;
|
||
|
typedef typename boost::graph_traits<TriangleMesh>::face_descriptor face_descriptor;
|
||
|
BOOST_FOREACH(face_descriptor f, faces(triangle_mesh))
|
||
|
{
|
||
|
typename boost::graph_traits<TriangleMesh>::halfedge_descriptor
|
||
|
h = halfedge(f, triangle_mesh);
|
||
|
m_area += std::sqrt(CGAL::squared_area(
|
||
|
get(vpmap, source(h, triangle_mesh) ),
|
||
|
get(vpmap, target(h, triangle_mesh) ),
|
||
|
get(vpmap, target(next(h, triangle_mesh), triangle_mesh) ) ));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rotation_matrix_pre(
|
||
|
typename boost::graph_traits<TriangleMesh>::vertex_descriptor vi,
|
||
|
TriangleMesh& hg)
|
||
|
{
|
||
|
typename boost::graph_traits<TriangleMesh>::in_edge_iterator e, e_end;
|
||
|
cpp11::tie(e,e_end) = in_edges(vi, hg);
|
||
|
m_nb_edges_incident=(double) std::distance(e,e_end);
|
||
|
}
|
||
|
|
||
|
template <class Square_matrix_3>
|
||
|
void update_covariance_matrix(
|
||
|
Square_matrix_3& cov,
|
||
|
const Square_matrix_3& rot_mtr)
|
||
|
{
|
||
|
// add neighbor rotation
|
||
|
cov += m_alpha * m_area * rot_mtr.transpose() / m_nb_edges_incident;
|
||
|
}
|
||
|
|
||
|
void set_sre_arap_alpha(double a){ m_alpha=a; }
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// property map that create a Simple_cartesian<double>::Point_3
|
||
|
// on the fly in order the deformation class to be used
|
||
|
// with points with minimal requirements
|
||
|
template <class Vertex_point_map>
|
||
|
struct SC_on_the_fly_pmap: public Vertex_point_map{
|
||
|
typedef boost::readable_property_map_tag category;
|
||
|
typedef CGAL::Simple_cartesian<double>::Point_3 value_type;
|
||
|
typedef value_type reference;
|
||
|
typedef typename boost::property_traits<Vertex_point_map>::key_type key_type;
|
||
|
|
||
|
SC_on_the_fly_pmap(Vertex_point_map base):
|
||
|
Vertex_point_map(base) {}
|
||
|
|
||
|
friend value_type
|
||
|
get(const SC_on_the_fly_pmap map, key_type k)
|
||
|
{
|
||
|
typename boost::property_traits<Vertex_point_map>::reference base=
|
||
|
get(static_cast<const Vertex_point_map&>(map), k);
|
||
|
return value_type(base[0], base[1], base[2]);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
}//namespace internal
|
||
|
/// @endcond
|
||
|
|
||
|
///
|
||
|
/// \ingroup PkgSurfaceMeshDeformation
|
||
|
/// @brief Class providing the functionalities for deforming a triangulated surface mesh
|
||
|
///
|
||
|
/// @tparam TM a model of `HalfedgeGraph`
|
||
|
/// @tparam VIM a model of `ReadablePropertyMap` with `Surface_mesh_deformation::vertex_descriptor` as key and `unsigned int` as value type.
|
||
|
/// The default is `boost::property_map<TM, boost::%vertex_index_t>::%type`.
|
||
|
/// @tparam HIM a model of `ReadablePropertyMap` with `Surface_mesh_deformation::halfedge_descriptor` as key and `unsigned int` as value type.
|
||
|
/// The default is `boost::property_map<TM, boost::%halfedge_index_t>::%type`.
|
||
|
/// @tparam TAG tag for selecting the deformation algorithm
|
||
|
/// @tparam WC a model of SurfaceMeshDeformationWeights, with `WC::Triangle_mesh` being `TM`.
|
||
|
/// If `TAG` is `ORIGINAL_ARAP`, the weights must be positive to guarantee a correct energy minimization.
|
||
|
/// The default is the cotangent weighting scheme. In case `TAG` is `ORIGINAL_ARAP`, negative weights are clamped to zero.
|
||
|
/// @tparam ST a model of SparseLinearAlgebraWithFactorTraits_d. 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.\n
|
||
|
/// \code
|
||
|
/// CGAL::Eigen_solver_traits<
|
||
|
/// Eigen::SparseLU<
|
||
|
/// CGAL::Eigen_sparse_matrix<double>::EigenType,
|
||
|
/// Eigen::COLAMDOrdering<int> > >
|
||
|
/// \endcode
|
||
|
/// @tparam CR a model of DeformationClosestRotationTraits_3. If \ref thirdpartyEigen "Eigen" 3.1 (or greater) is available and `CGAL_EIGEN3_ENABLED` is defined,
|
||
|
/// `Deformation_Eigen_polar_closest_rotation_traits_3` is provided as default parameter.
|
||
|
/// @tparam VPM a model of `ReadWritePropertyMap`</a> with `Surface_mesh_deformation::vertex_descriptor` as key and a point as value type. The point type must be a model of `::RawPoint_3`.
|
||
|
/// The default is `boost::property_map<TM, CGAL::vertex_point_t>::%type`.
|
||
|
template <
|
||
|
class TM,
|
||
|
class VIM=Default,
|
||
|
class HIM=Default,
|
||
|
Deformation_algorithm_tag TAG = SPOKES_AND_RIMS,
|
||
|
class WC = Default,
|
||
|
class ST = Default,
|
||
|
class CR = Default,
|
||
|
class VPM = Default
|
||
|
>
|
||
|
class Surface_mesh_deformation
|
||
|
{
|
||
|
//Typedefs
|
||
|
public:
|
||
|
|
||
|
/// \name Types
|
||
|
/// @{
|
||
|
// typedefed template parameters, main reason is doxygen creates autolink to typedefs but not template parameters
|
||
|
///
|
||
|
/// Triangle mesh type
|
||
|
typedef TM Triangle_mesh;
|
||
|
/// \cond
|
||
|
typedef TM Halfedge_graph;
|
||
|
/// \endcond
|
||
|
|
||
|
// Index maps
|
||
|
#ifndef DOXYGEN_RUNNING
|
||
|
typedef typename Default::Get<
|
||
|
VIM,
|
||
|
typename boost::property_map<Triangle_mesh, boost::vertex_index_t>::type
|
||
|
>::type Vertex_index_map;
|
||
|
typedef typename Default::Get<
|
||
|
HIM,
|
||
|
typename boost::property_map<Triangle_mesh, boost::halfedge_index_t>::type
|
||
|
>::type Hedge_index_map;
|
||
|
#else
|
||
|
/// vertex index map type
|
||
|
typedef VIM Vertex_index_map;
|
||
|
/// halfedge index map type
|
||
|
typedef HIM Hedge_index_map;
|
||
|
#endif
|
||
|
|
||
|
// weight calculator
|
||
|
#ifndef DOXYGEN_RUNNING
|
||
|
typedef typename Default::Get<
|
||
|
WC,
|
||
|
typename internal::Types_selectors<TM, TAG>::Weight_calculator
|
||
|
>::type Weight_calculator;
|
||
|
#else
|
||
|
/// weight calculator functor type
|
||
|
typedef WC Weight_calculator;
|
||
|
#endif
|
||
|
|
||
|
// sparse linear solver
|
||
|
#ifndef DOXYGEN_RUNNING
|
||
|
typedef typename Default::Get<
|
||
|
ST,
|
||
|
#if defined(CGAL_EIGEN3_ENABLED)
|
||
|
CGAL::Eigen_solver_traits<
|
||
|
Eigen::SparseLU<
|
||
|
CGAL::Eigen_sparse_matrix<double>::EigenType,
|
||
|
Eigen::COLAMDOrdering<int> > >
|
||
|
#else
|
||
|
ST // no parameter provided, and Eigen is not enabled: so don't compile!
|
||
|
#endif
|
||
|
>::type Sparse_linear_solver;
|
||
|
#else
|
||
|
/// sparse linear solver type
|
||
|
typedef ST Sparse_linear_solver;
|
||
|
#endif
|
||
|
|
||
|
// CR helper
|
||
|
#ifndef DOXYGEN_RUNNING
|
||
|
typedef typename Default::Get<
|
||
|
CR,
|
||
|
#if defined(CGAL_EIGEN3_ENABLED)
|
||
|
Deformation_Eigen_polar_closest_rotation_traits_3
|
||
|
#else
|
||
|
CR // no parameter provided, and Eigen is not enabled: so don't compile!
|
||
|
#endif
|
||
|
>::type Closest_rotation_traits;
|
||
|
#else
|
||
|
/// closest rotation traits type
|
||
|
typedef CR Closest_rotation_traits;
|
||
|
#endif
|
||
|
|
||
|
// vertex point pmap
|
||
|
#ifndef DOXYGEN_RUNNING
|
||
|
typedef typename Default::Get<
|
||
|
VPM,
|
||
|
typename boost::property_map<Triangle_mesh, CGAL::vertex_point_t>::type
|
||
|
>::type Vertex_point_map;
|
||
|
#else
|
||
|
/// vertex point map type
|
||
|
typedef VPM Vertex_point_map;
|
||
|
#endif
|
||
|
|
||
|
/// The type for vertex descriptor
|
||
|
typedef typename boost::graph_traits<Triangle_mesh>::vertex_descriptor vertex_descriptor;
|
||
|
/// The type for halfedge descriptor
|
||
|
typedef typename boost::graph_traits<Triangle_mesh>::halfedge_descriptor halfedge_descriptor;
|
||
|
/// The 3D point type, model of `::RawPoint_3`
|
||
|
typedef typename boost::property_traits<Vertex_point_map>::value_type Point;
|
||
|
/// A constant iterator range over the vertices of the region-of-interest.
|
||
|
/// It is a model of `ConstRange` with `vertex_descriptor` as iterator value type.
|
||
|
typedef std::vector<vertex_descriptor> Roi_vertex_range;
|
||
|
/// @}
|
||
|
|
||
|
private:
|
||
|
typedef Surface_mesh_deformation<TM, VIM, HIM, TAG, WC, ST, CR> Self;
|
||
|
// Repeat Triangle_mesh types
|
||
|
typedef typename boost::graph_traits<Triangle_mesh>::vertex_iterator vertex_iterator;
|
||
|
typedef typename boost::graph_traits<Triangle_mesh>::halfedge_iterator halfedge_iterator;
|
||
|
typedef typename boost::graph_traits<Triangle_mesh>::in_edge_iterator in_edge_iterator;
|
||
|
typedef typename boost::graph_traits<Triangle_mesh>::out_edge_iterator out_edge_iterator;
|
||
|
|
||
|
typedef typename Closest_rotation_traits::Matrix CR_matrix;
|
||
|
typedef typename Closest_rotation_traits::Vector CR_vector;
|
||
|
|
||
|
// Data members.
|
||
|
Triangle_mesh& m_triangle_mesh; /**< Source triangulated surface mesh for modeling */
|
||
|
|
||
|
std::vector<Point> original; ///< original positions of roi (size: ros + boundary_of_ros)
|
||
|
std::vector<Point> solution; ///< storing position of ros vertices during iterations (size: ros + boundary_of_ros)
|
||
|
|
||
|
Vertex_index_map vertex_index_map; ///< storing indices of all vertices
|
||
|
Hedge_index_map hedge_index_map; ///< storing indices of all halfedges
|
||
|
|
||
|
std::vector<vertex_descriptor> roi; ///< region of interest
|
||
|
std::vector<vertex_descriptor> ros; ///< region of solution, including roi and hard constraints on boundary of roi
|
||
|
|
||
|
std::vector<std::size_t> ros_id_map; ///< (size: num vertices)
|
||
|
std::vector<bool> is_roi_map; ///< (size: num vertices)
|
||
|
std::vector<bool> is_ctrl_map; ///< (size: num vertices)
|
||
|
|
||
|
std::vector<double> hedge_weight; ///< all halfedge weights
|
||
|
std::vector<CR_matrix> rot_mtr; ///< rotation matrices of ros vertices (size: ros)
|
||
|
|
||
|
Sparse_linear_solver m_solver; ///< linear sparse solver
|
||
|
unsigned int m_iterations; ///< number of maximal iterations
|
||
|
double m_tolerance; ///< tolerance of convergence
|
||
|
|
||
|
bool need_preprocess_factorization; ///< is there any need to compute L and factorize
|
||
|
bool need_preprocess_region_of_solution; ///< is there any need to compute region of solution
|
||
|
|
||
|
bool last_preprocess_successful; ///< stores the result of last call to preprocess()
|
||
|
|
||
|
Weight_calculator weight_calculator;
|
||
|
|
||
|
Vertex_point_map vertex_point_map;
|
||
|
|
||
|
public:
|
||
|
typename internal::Types_selectors<TM, TAG>::ARAP_visitor arap_visitor;
|
||
|
private:
|
||
|
|
||
|
#ifdef CGAL_DEFORM_MESH_USE_EXPERIMENTAL_SCALE
|
||
|
std::vector<double> scales;
|
||
|
#endif
|
||
|
|
||
|
#ifndef CGAL_CFG_NO_CPP0X_DELETED_AND_DEFAULT_FUNCTIONS
|
||
|
public:
|
||
|
Surface_mesh_deformation(const Self&) = delete; // no copy
|
||
|
#else
|
||
|
private:
|
||
|
Surface_mesh_deformation(const Self&); // no copy
|
||
|
#endif
|
||
|
|
||
|
|
||
|
// Public methods
|
||
|
public:
|
||
|
|
||
|
/// \cond SKIP_FROM_MANUAL
|
||
|
//vertex_point_map set by default
|
||
|
Surface_mesh_deformation(Triangle_mesh& triangle_mesh,
|
||
|
Vertex_index_map vertex_index_map,
|
||
|
Hedge_index_map hedge_index_map
|
||
|
)
|
||
|
: m_triangle_mesh(triangle_mesh), vertex_index_map(vertex_index_map), hedge_index_map(hedge_index_map),
|
||
|
ros_id_map(std::vector<std::size_t>(num_vertices(triangle_mesh), (std::numeric_limits<std::size_t>::max)() )),
|
||
|
is_roi_map(std::vector<bool>(num_vertices(triangle_mesh), false)),
|
||
|
is_ctrl_map(std::vector<bool>(num_vertices(triangle_mesh), false)),
|
||
|
m_iterations(5), m_tolerance(1e-4),
|
||
|
need_preprocess_factorization(true),
|
||
|
need_preprocess_region_of_solution(true),
|
||
|
last_preprocess_successful(false),
|
||
|
weight_calculator(Weight_calculator()),
|
||
|
vertex_point_map(get(vertex_point, triangle_mesh))
|
||
|
{
|
||
|
init();
|
||
|
}
|
||
|
|
||
|
//vertex_point_map and hedge_index_map set by default
|
||
|
Surface_mesh_deformation(Triangle_mesh& triangle_mesh,
|
||
|
Vertex_index_map vertex_index_map
|
||
|
)
|
||
|
: m_triangle_mesh(triangle_mesh), vertex_index_map(vertex_index_map),
|
||
|
hedge_index_map(get(boost::halfedge_index, triangle_mesh)),
|
||
|
ros_id_map(std::vector<std::size_t>(num_vertices(triangle_mesh), (std::numeric_limits<std::size_t>::max)() )),
|
||
|
is_roi_map(std::vector<bool>(num_vertices(triangle_mesh), false)),
|
||
|
is_ctrl_map(std::vector<bool>(num_vertices(triangle_mesh), false)),
|
||
|
m_iterations(5), m_tolerance(1e-4),
|
||
|
need_preprocess_factorization(true),
|
||
|
need_preprocess_region_of_solution(true),
|
||
|
last_preprocess_successful(false),
|
||
|
weight_calculator(Weight_calculator()),
|
||
|
vertex_point_map(get(vertex_point, triangle_mesh))
|
||
|
{
|
||
|
init();
|
||
|
}
|
||
|
//vertex_point_map, hedge_index_map and vertex_index_map set by default
|
||
|
Surface_mesh_deformation(Triangle_mesh& triangle_mesh)
|
||
|
: m_triangle_mesh(triangle_mesh),
|
||
|
vertex_index_map(get(boost::vertex_index, triangle_mesh)),
|
||
|
hedge_index_map(get(boost::halfedge_index, triangle_mesh)),
|
||
|
ros_id_map(std::vector<std::size_t>(num_vertices(triangle_mesh), (std::numeric_limits<std::size_t>::max)() )),
|
||
|
is_roi_map(std::vector<bool>(num_vertices(triangle_mesh), false)),
|
||
|
is_ctrl_map(std::vector<bool>(num_vertices(triangle_mesh), false)),
|
||
|
m_iterations(5), m_tolerance(1e-4),
|
||
|
need_preprocess_factorization(true),
|
||
|
need_preprocess_region_of_solution(true),
|
||
|
last_preprocess_successful(false),
|
||
|
weight_calculator(Weight_calculator()),
|
||
|
vertex_point_map(get(vertex_point, triangle_mesh))
|
||
|
{
|
||
|
init();
|
||
|
}
|
||
|
|
||
|
// Constructor with all the parameters provided
|
||
|
Surface_mesh_deformation(Triangle_mesh& triangle_mesh,
|
||
|
Vertex_index_map vertex_index_map,
|
||
|
Hedge_index_map hedge_index_map,
|
||
|
Vertex_point_map vertex_point_map,
|
||
|
Weight_calculator weight_calculator = Weight_calculator()
|
||
|
)
|
||
|
: m_triangle_mesh(triangle_mesh), vertex_index_map(vertex_index_map), hedge_index_map(hedge_index_map),
|
||
|
ros_id_map(std::vector<std::size_t>(num_vertices(triangle_mesh), (std::numeric_limits<std::size_t>::max)() )),
|
||
|
is_roi_map(std::vector<bool>(num_vertices(triangle_mesh), false)),
|
||
|
is_ctrl_map(std::vector<bool>(num_vertices(triangle_mesh), false)),
|
||
|
m_iterations(5), m_tolerance(1e-4),
|
||
|
need_preprocess_factorization(true),
|
||
|
need_preprocess_region_of_solution(true),
|
||
|
last_preprocess_successful(false),
|
||
|
weight_calculator(weight_calculator),
|
||
|
vertex_point_map(vertex_point_map)
|
||
|
{
|
||
|
init();
|
||
|
}
|
||
|
/// \endcond
|
||
|
#if DOXYGEN_RUNNING
|
||
|
/// \name Construction
|
||
|
/// @{
|
||
|
/**
|
||
|
* The constructor of a deformation object
|
||
|
*
|
||
|
* @pre `triangle_mesh` consists of only triangular facets
|
||
|
* @param triangle_mesh triangulated surface mesh to deform
|
||
|
* @param vertex_index_map property map which associates an id to each vertex, from `0` to `num_vertices(triangle_mesh)-1`.
|
||
|
* @param hedge_index_map property map which associates an id to each halfedge, from `0` to `2*num_edges(triangle_mesh)-1`.
|
||
|
* @param vertex_point_map property map which associates a point to each vertex of the triangle mesh.
|
||
|
* @param weight_calculator function object or pointer for weight calculation
|
||
|
*/
|
||
|
Surface_mesh_deformation(Triangle_mesh& triangle_mesh,
|
||
|
Vertex_index_map vertex_index_map=get(boost::vertex_index, triangle_mesh),
|
||
|
Hedge_index_map hedge_index_map=get(boost::halfedge_index, triangle_mesh),
|
||
|
Vertex_point_map vertex_point_map=get(boost::vertex_point, triangle_mesh),
|
||
|
Weight_calculator weight_calculator = Weight_calculator()
|
||
|
);
|
||
|
/// @}
|
||
|
#endif
|
||
|
|
||
|
private:
|
||
|
void init() {
|
||
|
typedef internal::SC_on_the_fly_pmap<Vertex_point_map> Wrapper;
|
||
|
// compute halfedge weights
|
||
|
halfedge_iterator eb, ee;
|
||
|
hedge_weight.reserve(2*num_edges(m_triangle_mesh));
|
||
|
for(cpp11::tie(eb, ee) = halfedges(m_triangle_mesh); eb != ee; ++eb)
|
||
|
{
|
||
|
hedge_weight.push_back(
|
||
|
this->weight_calculator(*eb, m_triangle_mesh, Wrapper(vertex_point_map)));
|
||
|
}
|
||
|
arap_visitor.init(m_triangle_mesh, vertex_point_map);
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
|
||
|
/// \name Preprocessing
|
||
|
/// @{
|
||
|
/**
|
||
|
* Erases all the vertices from the region-of-interest (control vertices included).
|
||
|
*/
|
||
|
void clear_roi_vertices(){
|
||
|
need_preprocess_both();
|
||
|
// clear roi vertices
|
||
|
roi.clear();
|
||
|
//set to false all bits
|
||
|
is_roi_map.assign(num_vertices(m_triangle_mesh), false);
|
||
|
is_ctrl_map.assign(num_vertices(m_triangle_mesh), false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Erases all the vertices from the set of control vertices.
|
||
|
*/
|
||
|
void clear_control_vertices(){
|
||
|
need_preprocess_factorization=true;
|
||
|
//set to false all bits
|
||
|
is_ctrl_map.assign(num_vertices(m_triangle_mesh), false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Inserts a vertex in the set of control vertices. The vertex is also inserted in the region-of-interest if it is not already in it.
|
||
|
* @param vd the vertex to be inserted
|
||
|
* @return `true` if `vd` is not already a control vertex.
|
||
|
*/
|
||
|
bool insert_control_vertex(vertex_descriptor vd)
|
||
|
{
|
||
|
if(is_control_vertex(vd)) { return false; }
|
||
|
need_preprocess_factorization=true;
|
||
|
|
||
|
insert_roi_vertex(vd); // also insert it as roi
|
||
|
|
||
|
is_ctrl_map[id(vd)] = true;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Inserts a range of vertices in the set of control vertices. The vertices are also inserted in the region-of-interest if they are not already in it.
|
||
|
* @tparam InputIterator input iterator type with `vertex_descriptor` as value type
|
||
|
* @param begin first iterator of the range of vertices
|
||
|
* @param end past-the-end iterator of the range of vertices
|
||
|
*/
|
||
|
template<class InputIterator>
|
||
|
void insert_control_vertices(InputIterator begin, InputIterator end)
|
||
|
{
|
||
|
for( ;begin != end; ++begin)
|
||
|
{
|
||
|
insert_control_vertex(*begin);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Erases a vertex from the set of control vertices.
|
||
|
* @param vd the vertex to be erased
|
||
|
* @return `true` if `vd` was a control vertex.
|
||
|
*/
|
||
|
bool erase_control_vertex(vertex_descriptor vd)
|
||
|
{
|
||
|
if(!is_control_vertex(vd)) { return false; }
|
||
|
|
||
|
need_preprocess_factorization=true;
|
||
|
is_ctrl_map[id(vd)] = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Inserts a range of vertices in the region-of-interest
|
||
|
* @tparam InputIterator input iterator with `vertex_descriptor` as value type
|
||
|
* @param begin first iterator of the range of vertices
|
||
|
* @param end past-the-end iterator of the range of vertices
|
||
|
*/
|
||
|
template<class InputIterator>
|
||
|
void insert_roi_vertices(InputIterator begin, InputIterator end)
|
||
|
{
|
||
|
for( ;begin != end; ++begin)
|
||
|
{
|
||
|
insert_roi_vertex(*begin);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Inserts a vertex in the region-of-interest
|
||
|
* @param vd the vertex to be inserted
|
||
|
* @return `true` if `vd` is not already in the region-of-interest.
|
||
|
*/
|
||
|
bool insert_roi_vertex(vertex_descriptor vd)
|
||
|
{
|
||
|
if(is_roi_vertex(vd)) { return false; }
|
||
|
need_preprocess_both();
|
||
|
|
||
|
is_roi_map[id(vd)] = true;
|
||
|
roi.push_back(vd);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Erases a vertex from the region-of-interest and the set of control vertices.
|
||
|
* \note At the next call to `preprocess()`, any vertex that is no longer in the region-of-interest will be assigned to its original position
|
||
|
* (that is the position of the vertex at the time of construction or after the last call to `overwrite_initial_geometry()`).
|
||
|
* @param vd the vertex to be erased
|
||
|
* @return `true` `vd` was a vertex from the region-of-interest.
|
||
|
*/
|
||
|
bool erase_roi_vertex(vertex_descriptor vd)
|
||
|
{
|
||
|
if(!is_roi_vertex(vd)) { return false; }
|
||
|
|
||
|
erase_control_vertex(vd); // also erase from being control
|
||
|
|
||
|
typename std::vector<vertex_descriptor>::iterator it = std::find(roi.begin(), roi.end(), vd);
|
||
|
if(it != roi.end())
|
||
|
{
|
||
|
is_roi_map[id(vd)] = false;
|
||
|
roi.erase(it);
|
||
|
|
||
|
need_preprocess_both();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
CGAL_assertion(false); // inconsistency between is_roi_map, and roi vector!
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Preprocessing function that need to be called each time the region-of-interest or the set
|
||
|
* of control vertices are changed before calling `deform()`.
|
||
|
* If not already done, `deform()` first calls this function.
|
||
|
* \cgalAdvancedBegin
|
||
|
* Collects the vertices not in the region-of-interest that are adjacent to a vertex from the
|
||
|
* region-of-interest (these vertices are internally considered as fixed control vertices).
|
||
|
* Then assembles and factorizes the Laplacian matrix used in the function `deform()`.
|
||
|
* \cgalAdvancedEnd
|
||
|
* \note A modification of the set of control vertices or the region-of-interest invalidates the
|
||
|
* preprocessing data.
|
||
|
* @return `true` if successful.
|
||
|
* A common reason for failure is that the system is rank deficient,
|
||
|
* which happens for example when all the vertices are in the region-of-interest and no control vertices are set, or
|
||
|
* if the weighting scheme used features too many zero and breaks the connectivity information.
|
||
|
*/
|
||
|
bool preprocess()
|
||
|
{
|
||
|
region_of_solution();
|
||
|
assemble_laplacian_and_factorize();
|
||
|
return last_preprocess_successful; // which is set by assemble_laplacian_and_factorize()
|
||
|
}
|
||
|
/// @} Preprocessing
|
||
|
|
||
|
/// \name Deformation
|
||
|
/// @{
|
||
|
|
||
|
/**
|
||
|
* Sets the target position of a control vertex.
|
||
|
* @param vd the control vertex the target position is set
|
||
|
* @param target_position the new target position
|
||
|
*/
|
||
|
void set_target_position(vertex_descriptor vd, const Point& target_position)
|
||
|
{
|
||
|
region_of_solution(); // we require ros ids, so if there is any need to preprocess of region of solution -do it.
|
||
|
|
||
|
if(!is_control_vertex(vd)) { return; }
|
||
|
solution[ros_id(vd)] = target_position;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Updates the target position of `vd` by applying a translation of vector `t`.
|
||
|
*
|
||
|
* @tparam Vect is a 3D vector class, with `Vect(double x,double y, double z)` being a constructor from its %Cartesian coordinates
|
||
|
* and `double Vect::operator[](int i)` with i=0,1 or 2 returning its %Cartesian coordinates.
|
||
|
*
|
||
|
* @param vd a control vertex
|
||
|
* @param t translation vector
|
||
|
* \pre `is_control_vertex(vd)`
|
||
|
*/
|
||
|
template<class Vect>
|
||
|
void translate(vertex_descriptor vd, const Vect& t)
|
||
|
{
|
||
|
region_of_solution(); // we require ros ids, so if there is any need to preprocess of region of solution -do it.
|
||
|
|
||
|
std::size_t v_id = ros_id(vd);
|
||
|
solution[v_id] = add_to_point(solution[v_id], t);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Equivalent to calling the overload taking only one control vertex, for each vertex in the range `[begin,end[`.
|
||
|
*
|
||
|
* @tparam InputIterator input iterator type with `vertex_descriptor` as value type
|
||
|
* @tparam Vect is a 3D vector class, with `Vect(double x,double y, double z)` being a constructor from its %Cartesian coordinates
|
||
|
* and `double Vect::operator[](int i)` with i=0,1 or 2 returning its %Cartesian coordinates.
|
||
|
*
|
||
|
* @param begin first iterator of the range of vertices
|
||
|
* @param end past-the-end iterator of the range of vertices
|
||
|
* @param t translation vector
|
||
|
*/
|
||
|
template<class InputIterator, class Vect>
|
||
|
void translate(InputIterator begin, InputIterator end, const Vect& t)
|
||
|
{
|
||
|
region_of_solution(); // we require ros ids, so if there is any need to preprocess of region of solution -do it.
|
||
|
|
||
|
for(; begin != end; ++begin) {
|
||
|
std::size_t v_id = ros_id(*begin);
|
||
|
solution[v_id] = add_to_point(solution[v_id], t);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Updates the target position of `vd` by applying to its last target position
|
||
|
* a rotation defined by the quaternion `quat`, the center of the rotation being
|
||
|
* the origin translated by `to_rotation_center` .
|
||
|
*
|
||
|
* @tparam Quaternion is a quaternion class with `Vect operator*(Quaternion, Vect)` returning the product of a quaternion with a vector
|
||
|
* @tparam Vect is a 3D vector class, with `Vect(double x,double y, double z)` being a constructor from its %Cartesian coordinates
|
||
|
* and `double Vect::operator[](int i)` with i=0,1 or 2 returning its %Cartesian coordinates.
|
||
|
*
|
||
|
* @param vd a control vertex
|
||
|
* @param to_rotation_center the vector to translate the origin to the center of the rotation
|
||
|
* @param quat quaternion of the rotation
|
||
|
* \pre `is_control_vertex(vd)`
|
||
|
* \pre `quad` represents a rotation
|
||
|
*/
|
||
|
template <typename Quaternion, typename Vect>
|
||
|
void rotate(vertex_descriptor vd, const Vect& to_rotation_center, const Quaternion& quat)
|
||
|
{
|
||
|
CGAL_precondition( is_control_vertex(vd) );
|
||
|
region_of_solution(); // we require ros ids, so if there is any need to preprocess of region of solution -do it.
|
||
|
|
||
|
std::size_t v_id = ros_id(vd);
|
||
|
Vect v = quat * sub_to_vect(solution[v_id], to_rotation_center);
|
||
|
solution[v_id] = Point( to_rotation_center[0]+v[0],
|
||
|
to_rotation_center[1]+v[1],
|
||
|
to_rotation_center[2]+v[2] );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Equivalent to calling the overload taking only one control vertex, for each vertex in the range `[begin,end[`.
|
||
|
*
|
||
|
* @tparam InputIterator input iterator type with `vertex_descriptor` as value type
|
||
|
* @tparam Quaternion is a quaternion class with `Vect operator*(Quaternion, Vect)` returning the product of a quaternion with a vector
|
||
|
* @tparam Vect is a 3D vector class, with `Vect(double x,double y, double z)` being a constructor from its %Cartesian coordinates
|
||
|
* and `double Vect::operator[](int i)` with i=0,1 or 2 returning its %Cartesian coordinates.
|
||
|
*
|
||
|
* @param begin first iterator of the range of vertices
|
||
|
* @param end past-the-end iterator of the range of vertices
|
||
|
* @param to_rotation_center the vector to translate the origin to the center of the rotation
|
||
|
* @param quat quaternion of the rotation
|
||
|
* \pre `quad` represents a rotation
|
||
|
*/
|
||
|
template <typename InputIterator, typename Vect, typename Quaternion>
|
||
|
void rotate(InputIterator begin, InputIterator end, const Vect& to_rotation_center, const Quaternion& quat)
|
||
|
{
|
||
|
region_of_solution(); // we require ros ids, so if there is any need to preprocess of region of solution -do it.
|
||
|
|
||
|
for(; begin != end; ++begin) {
|
||
|
std::size_t v_id = ros_id(*begin);
|
||
|
Vect v = quat * sub_to_vect(solution[v_id], to_rotation_center);
|
||
|
solution[v_id] = Point( to_rotation_center[0]+v[0],
|
||
|
to_rotation_center[1]+v[1],
|
||
|
to_rotation_center[2]+v[2] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the target position of a control vertex.
|
||
|
* \param vd a control vertex
|
||
|
* \pre `is_control_vertex(vd)`
|
||
|
*/
|
||
|
const Point& target_position(vertex_descriptor vd)
|
||
|
{
|
||
|
region_of_solution();
|
||
|
|
||
|
CGAL_precondition( is_control_vertex(vd) );
|
||
|
return solution[ ros_id(vd) ];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Deforms the region-of-interest according to the deformation algorithm, using the target positions of each control vertex set by using `rotate()`, `translate()`, or `set_target_position()`.
|
||
|
* The points associated to each vertex of the input triangle mesh that are inside the region-of-interest are updated.
|
||
|
* \note Nothing happens if `preprocess()` returns `false`.
|
||
|
* @see set_iterations(unsigned int iterations), set_tolerance(double tolerance), deform(unsigned int iterations, double tolerance)
|
||
|
*/
|
||
|
void deform()
|
||
|
{
|
||
|
deform(m_iterations, m_tolerance);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Same as `deform()` but the number of iterations and the tolerance are one-time parameters.
|
||
|
* @param iterations number of iterations for optimization procedure
|
||
|
* @param tolerance tolerance of convergence (see explanations set_tolerance(double tolerance))
|
||
|
*/
|
||
|
void deform(unsigned int iterations, double tolerance)
|
||
|
{
|
||
|
preprocess();
|
||
|
|
||
|
if(!last_preprocess_successful) {
|
||
|
CGAL_warning(false);
|
||
|
return;
|
||
|
}
|
||
|
// Note: no energy based termination occurs at first iteration
|
||
|
// because comparing energy of original model (before deformation) and deformed model (deformed_1_iteration)
|
||
|
// simply does not make sense, comparison is meaningful between deformed_(i)_iteration & deformed_(i+1)_iteration
|
||
|
|
||
|
double energy_this = 0; // initial value is not important, because we skip first iteration
|
||
|
double energy_last;
|
||
|
|
||
|
// iterations
|
||
|
for ( unsigned int ite = 0; ite < iterations; ++ite)
|
||
|
{
|
||
|
// main steps of optimization
|
||
|
update_solution();
|
||
|
#ifndef CGAL_DEFORM_MESH_JUST_EXPERIMENTAL_SCALE
|
||
|
optimal_rotations();
|
||
|
#endif
|
||
|
#ifdef CGAL_DEFORM_MESH_USE_EXPERIMENTAL_SCALE
|
||
|
optimal_scales();
|
||
|
#endif
|
||
|
// energy based termination
|
||
|
if(tolerance > 0.0 && (ite + 1) < iterations) // if tolerance <= 0 then don't compute energy
|
||
|
{ // also no need compute energy if this iteration is the last iteration
|
||
|
energy_last = energy_this;
|
||
|
energy_this = energy();
|
||
|
CGAL_warning(energy_this >= 0);
|
||
|
|
||
|
if(ite != 0) // skip first iteration
|
||
|
{
|
||
|
double energy_dif = std::abs((energy_last - energy_this) / energy_this);
|
||
|
if ( energy_dif < tolerance ) { break; }
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// copy solution to the target surface mesh
|
||
|
assign_solution();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resets the points associated to the vertices of the region-of-interest at their
|
||
|
* initial positions at time of the functor construction or after
|
||
|
* the last call to `overwrite_initial_geometry()`.
|
||
|
* \note if the region-of-interest or the set of control vertices have been
|
||
|
* modified since the last call to `preprocess()`, it will be called prior
|
||
|
* to the reset.
|
||
|
*/
|
||
|
void reset()
|
||
|
{
|
||
|
region_of_solution(); // since we are using original vector
|
||
|
|
||
|
//restore the current positions to be the original positions
|
||
|
BOOST_FOREACH(vertex_descriptor vd, roi_vertices())
|
||
|
{
|
||
|
put(vertex_point_map, vd, original[ros_id(vd)]);
|
||
|
solution[ros_id(vd)]=original[ros_id(vd)];
|
||
|
}
|
||
|
|
||
|
// also set rotation matrix to identity
|
||
|
std::fill(rot_mtr.begin(), rot_mtr.end(),
|
||
|
Closest_rotation_traits().identity_matrix());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the initial positions of the vertices from the region-of-interest to the current positions. Calling this function has the same effect as creating
|
||
|
* a new deformation object with the current deformed triangle mesh, keeping the region-of-interest and the set of control vertices.
|
||
|
* \note if the region-of-interest or the set of control vertices have been modified since the last call to `preprocess()`,
|
||
|
* it will be called prior to the overwrite.
|
||
|
*
|
||
|
* \cgalAdvancedBegin
|
||
|
* This function might have a non-negligible effect on the result.
|
||
|
* The Laplacian matrix of the free vertices and the optimal rotations
|
||
|
* are computed using the original positions of the points
|
||
|
* associated to the vertices. Thus, if a deformed version of the surface mesh
|
||
|
* is used as reference, the surface mesh properties the algorithm
|
||
|
* tries to preserve are those of an altered version (which are already
|
||
|
* degraded).
|
||
|
* \cgalAdvancedEnd
|
||
|
*/
|
||
|
void overwrite_initial_geometry()
|
||
|
{
|
||
|
typedef internal::SC_on_the_fly_pmap<Vertex_point_map> Wrapper;
|
||
|
if(roi.empty()) { return; } // no ROI to overwrite
|
||
|
|
||
|
region_of_solution(); // the roi should be preprocessed since we are using original_position vec
|
||
|
|
||
|
BOOST_FOREACH(vertex_descriptor vd, roi_vertices())
|
||
|
{
|
||
|
original[ros_id(vd)] = get(vertex_point_map, vd);
|
||
|
}
|
||
|
|
||
|
// now I need to compute weights for halfedges incident to roi vertices
|
||
|
std::vector<bool> is_weight_computed(2*num_edges(m_triangle_mesh), false);
|
||
|
BOOST_FOREACH(vertex_descriptor vd, roi_vertices())
|
||
|
{
|
||
|
in_edge_iterator e, e_end;
|
||
|
for (cpp11::tie(e,e_end) = in_edges(vd, m_triangle_mesh); e != e_end; e++)
|
||
|
{
|
||
|
halfedge_descriptor he = halfedge(*e, m_triangle_mesh);
|
||
|
std::size_t id_e = id(he);
|
||
|
if(is_weight_computed[id_e]) { continue; }
|
||
|
|
||
|
hedge_weight[id_e] = weight_calculator(he, m_triangle_mesh, Wrapper(vertex_point_map));
|
||
|
is_weight_computed[id_e] = true;
|
||
|
|
||
|
halfedge_descriptor e_opp = opposite(he, m_triangle_mesh);
|
||
|
std::size_t id_e_opp = id(e_opp);
|
||
|
|
||
|
hedge_weight[id_e_opp] = weight_calculator(e_opp, m_triangle_mesh, Wrapper(vertex_point_map));
|
||
|
is_weight_computed[id_e_opp] = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// also set rotation matrix to identity
|
||
|
std::fill(rot_mtr.begin(), rot_mtr.end(), Closest_rotation_traits().identity_matrix());
|
||
|
|
||
|
need_preprocess_both(); // now we need reprocess
|
||
|
}
|
||
|
|
||
|
/// @} Deformation
|
||
|
|
||
|
/// \name Utilities
|
||
|
/// @{
|
||
|
|
||
|
/**
|
||
|
* Gets the default number of iterations (5) or the value passed to the function `set_iterations()`
|
||
|
*/
|
||
|
unsigned int iterations()
|
||
|
{ return m_iterations; }
|
||
|
|
||
|
/**
|
||
|
* Gets the default tolerance parameter (1e-4) or the value passed to the function `set_tolerance()`
|
||
|
*/
|
||
|
double tolerance()
|
||
|
{ return m_tolerance; }
|
||
|
|
||
|
/**
|
||
|
* Sets the maximum number of iterations ran by `deform()`
|
||
|
*/
|
||
|
void set_iterations(unsigned int iterations)
|
||
|
{ this->m_iterations = iterations; }
|
||
|
|
||
|
/// @brief Sets the tolerance of the convergence used in `deform()`.
|
||
|
/// If `tolerance==0`, no energy based termination criteria is used (preventing to do the energy computation at each iteration step)
|
||
|
///
|
||
|
/// `tolerance >` \f$|\mathrm{energy}(m_i) - \mathrm{energy}(m_{i-1})| / \mathrm{energy}(m_i)\f$ will be used as a termination criterium.
|
||
|
void set_tolerance(double tolerance)
|
||
|
{ this->m_tolerance = tolerance; }
|
||
|
|
||
|
/**
|
||
|
* Returns the range of vertices in the region-of-interest.
|
||
|
*/
|
||
|
const Roi_vertex_range& roi_vertices() const
|
||
|
{
|
||
|
return roi;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Tests whether a vertex is inside the region-of-interest.
|
||
|
* @param vd the query vertex
|
||
|
* @return `true` if `vd` has been inserted to (and not erased from) the region-of-interest.
|
||
|
*/
|
||
|
bool is_roi_vertex(vertex_descriptor vd) const
|
||
|
{ return is_roi_map[id(vd)]; }
|
||
|
|
||
|
/**
|
||
|
* Tests whether a vertex is a control vertex.
|
||
|
* @param vd the query vertex
|
||
|
* @return `true` if `vd` has been inserted to (and not erased from) the set of control vertices.
|
||
|
*/
|
||
|
bool is_control_vertex(vertex_descriptor vd) const
|
||
|
{ return is_ctrl_map[id(vd)]; }
|
||
|
|
||
|
/**
|
||
|
* Provides access to the triangle mesh being deformed
|
||
|
*/
|
||
|
const Triangle_mesh& triangle_mesh() const
|
||
|
{ return m_triangle_mesh; }
|
||
|
|
||
|
/// \cond
|
||
|
const Triangle_mesh& halfedge_graph() const
|
||
|
{ return m_triangle_mesh; }
|
||
|
/// \endcond
|
||
|
|
||
|
/**
|
||
|
* Sets the alpha coefficient that determines the weight of the bending term (rotation smoothness) for the SRE-ARAP deformation technique.
|
||
|
* The range of values can be from 0 to infinity. When alpha=0, the method reverts to ARAP. When alpha is increased, neighboring rotations become similar to each other, where alpha=infinity results in a global rigid transformation of the ROI. The value of alpha depends on the surface area and shape. Since alpha is not too sensitive, it can be tweaked in most cases by multiplying it by powers of 10.
|
||
|
* The default value for alpha is 0.02.
|
||
|
*/
|
||
|
void set_sre_arap_alpha(double a)
|
||
|
{
|
||
|
arap_visitor.set_sre_arap_alpha(a);
|
||
|
}
|
||
|
|
||
|
/// @} Utilities
|
||
|
|
||
|
|
||
|
private:
|
||
|
|
||
|
/// Assigns id to one ring neighbor of vd, and also push them into push_vector
|
||
|
void assign_ros_id_to_one_ring(vertex_descriptor vd,
|
||
|
std::size_t& next_id,
|
||
|
std::vector<vertex_descriptor>& push_vector)
|
||
|
{
|
||
|
in_edge_iterator e, e_end;
|
||
|
for (cpp11::tie(e,e_end) = in_edges(vd, m_triangle_mesh); e != e_end; e++)
|
||
|
{
|
||
|
vertex_descriptor vt = source(*e, m_triangle_mesh);
|
||
|
if(ros_id(vt) == (std::numeric_limits<std::size_t>::max)()) // neighboring vertex which is outside of roi and not visited previously (i.e. need an id)
|
||
|
{
|
||
|
ros_id(vt) = next_id++;
|
||
|
push_vector.push_back(vt);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Find region of solution, including roi and hard constraints, which is the 1-ring vertices out roi
|
||
|
/// Contains four parts:
|
||
|
/// - if there is any vertex which is no longer roi, set its position back to original position
|
||
|
/// - assign a ros id to vertices inside ros + ros-boundary
|
||
|
/// - reinitialize rotation matrices, if a vertex is previously ros, use its previous matrix, otherwise set zero
|
||
|
/// - reinitialize original, and solution,
|
||
|
/// + if a vertex is previously roi, then use its original position in old_origional, else use point().
|
||
|
/// In both case we are using "original position" of the vertex.
|
||
|
/// + same for solution (it is required to prevent jumping effects)
|
||
|
void region_of_solution()
|
||
|
{
|
||
|
if(!need_preprocess_region_of_solution) { return; }
|
||
|
need_preprocess_region_of_solution = false;
|
||
|
|
||
|
std::vector<std::size_t> old_ros_id_map = ros_id_map;
|
||
|
std::vector<CR_matrix> old_rot_mtr = rot_mtr;
|
||
|
std::vector<Point> old_solution = solution;
|
||
|
std::vector<Point> old_original = original;
|
||
|
|
||
|
// any vertices which are no longer ROI, should be assigned to their original position, so that:
|
||
|
// IF a vertex is ROI (actually if its ros + boundary) previously (when previous region_of_solution() is called)
|
||
|
// we have its original position in old_original
|
||
|
// ELSE
|
||
|
// we have its original position in vertex->point()
|
||
|
// (current ros is actually old ros - we did not change it yet)
|
||
|
for(typename std::vector<vertex_descriptor>::iterator it = ros.begin(); it != ros.end(); ++it)
|
||
|
{
|
||
|
if(!is_roi_vertex(*it)) {
|
||
|
put(vertex_point_map, *it, old_original[ old_ros_id_map[id(*it)] ]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
// assign id to vertices inside: roi, boundary of roi (roi + boundary of roi = ros),
|
||
|
// and boundary of ros
|
||
|
// keep in mind that id order does not have to be compatible with ros order
|
||
|
ros.clear(); // clear ros
|
||
|
ros.insert(ros.end(), roi.begin(), roi.end());
|
||
|
|
||
|
ros_id_map.assign(num_vertices(m_triangle_mesh), (std::numeric_limits<std::size_t>::max)()); // use max as not assigned mark
|
||
|
|
||
|
for(std::size_t i = 0; i < roi.size(); i++) // assign id to all roi vertices
|
||
|
{ ros_id(roi[i]) = i; }
|
||
|
|
||
|
// now assign an id to vertices on boundary of roi
|
||
|
std::size_t next_ros_index = roi.size();
|
||
|
for(std::size_t i = 0; i < roi.size(); i++)
|
||
|
{ assign_ros_id_to_one_ring(roi[i], next_ros_index, ros); }
|
||
|
|
||
|
std::vector<vertex_descriptor> outside_ros;
|
||
|
// boundary of ros also must have ids because in CR calculation,
|
||
|
// one-ring neighbor of ROS vertices are reached.
|
||
|
for(std::size_t i = roi.size(); i < ros.size(); i++)
|
||
|
{ assign_ros_id_to_one_ring(ros[i], next_ros_index, outside_ros); }
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// initialize the rotation matrices (size: ros)
|
||
|
rot_mtr.resize(ros.size());
|
||
|
for(std::size_t i = 0; i < rot_mtr.size(); i++)
|
||
|
{
|
||
|
std::size_t v_ros_id = ros_id(ros[i]);
|
||
|
std::size_t v_id = id(ros[i]);
|
||
|
|
||
|
// any vertex which is previously ROS has a rotation matrix
|
||
|
// use that matrix to prevent jumping effects
|
||
|
if(old_ros_id_map[v_id] != (std::numeric_limits<std::size_t>::max)()
|
||
|
&& old_ros_id_map[v_id] < old_rot_mtr.size()) {
|
||
|
// && boundary of ros vertices also have ids so check whether it is ros
|
||
|
rot_mtr[v_ros_id] = old_rot_mtr[ old_ros_id_map[v_id] ];
|
||
|
}
|
||
|
else {
|
||
|
rot_mtr[v_ros_id] = Closest_rotation_traits().identity_matrix();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// initialize solution and original (size: ros + boundary_of_ros)
|
||
|
|
||
|
// for simplifying coding effort, I also put boundary of ros into solution and original
|
||
|
// because boundary of ros vertices are reached in optimal_rotations() and energy()
|
||
|
solution.resize(ros.size() + outside_ros.size());
|
||
|
original.resize(ros.size() + outside_ros.size());
|
||
|
|
||
|
for(std::size_t i = 0; i < ros.size(); i++)
|
||
|
{
|
||
|
std::size_t v_ros_id = ros_id(ros[i]);
|
||
|
std::size_t v_id = id(ros[i]);
|
||
|
|
||
|
if(is_roi_vertex(ros[i]) && old_ros_id_map[v_id] != (std::numeric_limits<std::size_t>::max)()) {
|
||
|
// if it is currently roi and previously ros + boundary
|
||
|
// (actually I just need to assign old's to new's if a vertex is currently and previously ROI
|
||
|
// but no harm on assigning if its also previously ros + boundary because
|
||
|
// those(old_original & old_solution) will be equal to original position)
|
||
|
original[v_ros_id] = old_original[old_ros_id_map[v_id]];
|
||
|
solution[v_ros_id] = old_solution[old_ros_id_map[v_id]];
|
||
|
}
|
||
|
else {
|
||
|
solution[v_ros_id] = get(vertex_point_map, ros[i]);
|
||
|
original[v_ros_id] = get(vertex_point_map, ros[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for(std::size_t i = 0; i < outside_ros.size(); ++i)
|
||
|
{
|
||
|
std::size_t v_ros_id = ros_id(outside_ros[i]);
|
||
|
original[v_ros_id] = get(vertex_point_map, outside_ros[i]);
|
||
|
solution[v_ros_id] = get(vertex_point_map, outside_ros[i]);
|
||
|
}
|
||
|
|
||
|
#ifdef CGAL_DEFORM_MESH_USE_EXPERIMENTAL_SCALE
|
||
|
scales.resize(ros.size());
|
||
|
std::fill(scales.begin(), scales.end(), 1.0);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/// Assemble Laplacian matrix A of linear system A*X=B
|
||
|
void assemble_laplacian_and_factorize()
|
||
|
{
|
||
|
if(TAG == SPOKES_AND_RIMS)
|
||
|
{
|
||
|
assemble_laplacian_and_factorize_spokes_and_rims();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
assemble_laplacian_and_factorize_arap();
|
||
|
}
|
||
|
}
|
||
|
/// Construct matrix that corresponds to left-hand side of eq:lap_ber in user manual
|
||
|
/// Also constraints are integrated as eq:lap_energy_system in user manual
|
||
|
void assemble_laplacian_and_factorize_arap()
|
||
|
{
|
||
|
if(!need_preprocess_factorization) { return; }
|
||
|
need_preprocess_factorization = false;
|
||
|
|
||
|
typename Sparse_linear_solver::Matrix A(ros.size());
|
||
|
|
||
|
/// assign cotangent Laplacian to ros vertices
|
||
|
for(std::size_t k = 0; k < ros.size(); k++)
|
||
|
{
|
||
|
vertex_descriptor vi = ros[k];
|
||
|
std::size_t vi_id = ros_id(vi);
|
||
|
if ( is_roi_vertex(vi) && !is_control_vertex(vi) ) // vertices of ( roi - ctrl )
|
||
|
{
|
||
|
double diagonal = 0;
|
||
|
in_edge_iterator e, e_end;
|
||
|
for (cpp11::tie(e,e_end) = in_edges(vi, m_triangle_mesh); e != e_end; e++)
|
||
|
{
|
||
|
halfedge_descriptor he = halfedge(*e, m_triangle_mesh);
|
||
|
vertex_descriptor vj = source(he, m_triangle_mesh);
|
||
|
double wij = hedge_weight[id(he)]; // edge(pi - pj)
|
||
|
double wji = hedge_weight[id(opposite(he, m_triangle_mesh))]; // edge(pi - pj)
|
||
|
double total_weight = wij + wji;
|
||
|
|
||
|
A.set_coef(vi_id, ros_id(vj), -total_weight, true); // off-diagonal coefficient
|
||
|
diagonal += total_weight;
|
||
|
}
|
||
|
// diagonal coefficient
|
||
|
A.set_coef(vi_id, vi_id, diagonal, true);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
A.set_coef(vi_id, vi_id, 1.0, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// now factorize
|
||
|
double D;
|
||
|
last_preprocess_successful = m_solver.factor(A, D);
|
||
|
CGAL_warning(last_preprocess_successful);
|
||
|
}
|
||
|
/// Construct matrix that corresponds to left-hand side of eq:lap_ber_rims in user manual
|
||
|
/// Also constraints are integrated as eq:lap_energy_system in user manual
|
||
|
void assemble_laplacian_and_factorize_spokes_and_rims()
|
||
|
{
|
||
|
if(!need_preprocess_factorization) { return; }
|
||
|
need_preprocess_factorization = false;
|
||
|
|
||
|
typename Sparse_linear_solver::Matrix A(ros.size());
|
||
|
|
||
|
/// assign cotangent Laplacian to ros vertices
|
||
|
for(std::size_t k = 0; k < ros.size(); k++)
|
||
|
{
|
||
|
vertex_descriptor vi = ros[k];
|
||
|
std::size_t vi_id = ros_id(vi);
|
||
|
if ( is_roi_vertex(vi) && !is_control_vertex(vi) ) // vertices of ( roi - ctrl ): free vertices
|
||
|
{
|
||
|
double diagonal = 0;
|
||
|
out_edge_iterator e, e_end;
|
||
|
for (cpp11::tie(e,e_end) = out_edges(vi, m_triangle_mesh); e != e_end; e++)
|
||
|
{
|
||
|
halfedge_descriptor he = halfedge(*e, m_triangle_mesh);
|
||
|
double total_weight = 0;
|
||
|
// an edge contribute to energy only if it is part of an incident triangle
|
||
|
// (i.e it should not be a border edge)
|
||
|
if(!is_border(he, m_triangle_mesh))
|
||
|
{
|
||
|
double wji = hedge_weight[id(he)]; // edge(pj - pi)
|
||
|
total_weight += wji;
|
||
|
}
|
||
|
|
||
|
halfedge_descriptor opp = opposite(he, m_triangle_mesh);
|
||
|
if(!is_border(opp, m_triangle_mesh))
|
||
|
{
|
||
|
double wij = hedge_weight[id(opp)]; // edge(pi - pj)
|
||
|
total_weight += wij;
|
||
|
}
|
||
|
|
||
|
// place coefficient to matrix
|
||
|
vertex_descriptor vj = target(he, m_triangle_mesh);
|
||
|
A.set_coef(vi_id, ros_id(vj), -total_weight, true); // off-diagonal coefficient
|
||
|
diagonal += total_weight;
|
||
|
}
|
||
|
// diagonal coefficient
|
||
|
A.set_coef(vi_id, vi_id, diagonal, true);
|
||
|
}
|
||
|
else // constrained vertex
|
||
|
{
|
||
|
A.set_coef(vi_id, vi_id, 1.0, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// now factorize
|
||
|
double D;
|
||
|
last_preprocess_successful = m_solver.factor(A, D);
|
||
|
CGAL_warning(last_preprocess_successful);
|
||
|
}
|
||
|
|
||
|
/// Local step of iterations, computing optimal rotation matrices
|
||
|
void optimal_rotations()
|
||
|
{
|
||
|
if(TAG == SPOKES_AND_RIMS)
|
||
|
{
|
||
|
optimal_rotations_spokes_and_rims();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
optimal_rotations_arap();
|
||
|
}
|
||
|
}
|
||
|
void optimal_rotations_arap()
|
||
|
{
|
||
|
Closest_rotation_traits cr_traits;
|
||
|
CR_matrix cov = cr_traits.zero_matrix();
|
||
|
|
||
|
// only accumulate ros vertices
|
||
|
for ( std::size_t k = 0; k < ros.size(); k++ )
|
||
|
{
|
||
|
vertex_descriptor vi = ros[k];
|
||
|
std::size_t vi_id = ros_id(vi);
|
||
|
// compute covariance matrix (user manual eq:cov_matrix)
|
||
|
cov = cr_traits.zero_matrix();
|
||
|
|
||
|
in_edge_iterator e, e_end;
|
||
|
|
||
|
arap_visitor.rotation_matrix_pre(vi, m_triangle_mesh);
|
||
|
|
||
|
for (cpp11::tie(e,e_end) = in_edges(vi, m_triangle_mesh); e != e_end; e++)
|
||
|
{
|
||
|
halfedge_descriptor he=halfedge(*e, m_triangle_mesh);
|
||
|
vertex_descriptor vj = source(he, m_triangle_mesh);
|
||
|
std::size_t vj_id = ros_id(vj);
|
||
|
|
||
|
const CR_vector& pij = sub_to_CR_vector(original[vi_id], original[vj_id]);
|
||
|
const CR_vector& qij = sub_to_CR_vector(solution[vi_id], solution[vj_id]);
|
||
|
double wij = hedge_weight[id(he)];
|
||
|
|
||
|
cr_traits.add_scalar_t_vector_t_vector_transpose(cov, wij, pij, qij); // cov += wij * (pij * qij)
|
||
|
|
||
|
if ( vj_id < rot_mtr.size() )
|
||
|
arap_visitor.update_covariance_matrix(cov, rot_mtr[vj_id]);
|
||
|
}
|
||
|
|
||
|
cr_traits.compute_close_rotation(cov, rot_mtr[vi_id]);
|
||
|
}
|
||
|
}
|
||
|
void optimal_rotations_spokes_and_rims()
|
||
|
{
|
||
|
Closest_rotation_traits cr_traits;
|
||
|
CR_matrix cov =cr_traits.zero_matrix();
|
||
|
|
||
|
// only accumulate ros vertices
|
||
|
for ( std::size_t k = 0; k < ros.size(); k++ )
|
||
|
{
|
||
|
vertex_descriptor vi = ros[k];
|
||
|
std::size_t vi_id = ros_id(vi);
|
||
|
// compute covariance matrix
|
||
|
cov = cr_traits.zero_matrix();
|
||
|
|
||
|
//iterate through all triangles
|
||
|
out_edge_iterator e, e_end;
|
||
|
for (cpp11::tie(e,e_end) = out_edges(vi, m_triangle_mesh); e != e_end; e++)
|
||
|
{
|
||
|
halfedge_descriptor he = halfedge(*e, m_triangle_mesh);
|
||
|
if(is_border(he, m_triangle_mesh)) { continue; } // no facet
|
||
|
// iterate edges around facet
|
||
|
halfedge_descriptor hedge_around_facet = he;
|
||
|
do
|
||
|
{
|
||
|
vertex_descriptor v1 = target(hedge_around_facet, m_triangle_mesh);
|
||
|
vertex_descriptor v2 = source(hedge_around_facet, m_triangle_mesh);
|
||
|
|
||
|
std::size_t v1_id = ros_id(v1); std::size_t v2_id = ros_id(v2);
|
||
|
|
||
|
const CR_vector& p12 = sub_to_CR_vector(original[v1_id], original[v2_id]);
|
||
|
const CR_vector& q12 = sub_to_CR_vector(solution[v1_id], solution[v2_id]);
|
||
|
double w12 = hedge_weight[id(hedge_around_facet)];
|
||
|
|
||
|
cr_traits.add_scalar_t_vector_t_vector_transpose(cov, w12, p12, q12); // cov += w12 * (p12 * q12);
|
||
|
|
||
|
} while( (hedge_around_facet = next(hedge_around_facet, m_triangle_mesh)) != he);
|
||
|
}
|
||
|
|
||
|
cr_traits.compute_close_rotation(cov, rot_mtr[vi_id]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef CGAL_DEFORM_MESH_USE_EXPERIMENTAL_SCALE
|
||
|
void optimal_scales()
|
||
|
{
|
||
|
for ( std::size_t k = 0; k < ros.size(); k++ )
|
||
|
{
|
||
|
vertex_descriptor vi = ros[k];
|
||
|
std::size_t vi_id = ros_id(vi);
|
||
|
// compute covariance matrix (user manual eq:cov_matrix)
|
||
|
double eT_eR = 0, eRT_eR = 0;
|
||
|
|
||
|
in_edge_iterator e, e_end;
|
||
|
for (cpp11::tie(e,e_end) = in_edges(vi, m_triangle_mesh); e != e_end; e++)
|
||
|
{
|
||
|
halfedge_descriptor he = *e;
|
||
|
vertex_descriptor vj = source(he, m_triangle_mesh);
|
||
|
std::size_t vj_id = ros_id(vj);
|
||
|
|
||
|
const CR_vector& pij = sub_to_CR_vector(original[vi_id], original[vj_id]);
|
||
|
const CR_vector& qij = sub_to_CR_vector(solution[vi_id], solution[vj_id]);
|
||
|
|
||
|
double wij = hedge_weight[id(he)];
|
||
|
|
||
|
const CR_vector& pRij = rot_mtr[vi_id] * pij;
|
||
|
eRT_eR += pRij[0]*pRij[0] + pRij[1]*pRij[1] + pRij[2]*pRij[2];
|
||
|
eT_eR += qij[0]*pRij[0] + qij[1]*pRij[1] + qij[2]*pRij[2];
|
||
|
}
|
||
|
|
||
|
scales[vi_id] = eT_eR / eRT_eR;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/// Global step of iterations, updating solution
|
||
|
void update_solution()
|
||
|
{
|
||
|
if(TAG == SPOKES_AND_RIMS)
|
||
|
{
|
||
|
update_solution_spokes_and_rims();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
update_solution_arap();
|
||
|
}
|
||
|
}
|
||
|
/// calculate right-hand side of eq:lap_ber in user manual and solve the system
|
||
|
void update_solution_arap()
|
||
|
{
|
||
|
typename Sparse_linear_solver::Vector X(ros.size()), Bx(ros.size());
|
||
|
typename Sparse_linear_solver::Vector Y(ros.size()), By(ros.size());
|
||
|
typename Sparse_linear_solver::Vector Z(ros.size()), Bz(ros.size());
|
||
|
|
||
|
Closest_rotation_traits cr_traits;
|
||
|
|
||
|
// assemble right columns of linear system
|
||
|
for ( std::size_t k = 0; k < ros.size(); k++ )
|
||
|
{
|
||
|
vertex_descriptor vi = ros[k];
|
||
|
std::size_t vi_id = ros_id(vi);
|
||
|
|
||
|
if ( is_roi_vertex(vi) && !is_control_vertex(vi) )
|
||
|
{// free vertices
|
||
|
// sum of right-hand side of eq:lap_ber in user manual
|
||
|
CR_vector xyz = cr_traits.vector(0, 0, 0);
|
||
|
|
||
|
in_edge_iterator e, e_end;
|
||
|
for (cpp11::tie(e,e_end) = in_edges(vi, m_triangle_mesh); e != e_end; e++)
|
||
|
{
|
||
|
halfedge_descriptor he = halfedge(*e, m_triangle_mesh);
|
||
|
vertex_descriptor vj = source(he, m_triangle_mesh);
|
||
|
std::size_t vj_id = ros_id(vj);
|
||
|
|
||
|
const CR_vector& pij = sub_to_CR_vector(original[vi_id], original[vj_id]);
|
||
|
|
||
|
double wij = hedge_weight[id(he)];
|
||
|
double wji = hedge_weight[id(opposite(he, m_triangle_mesh))];
|
||
|
#ifndef CGAL_DEFORM_MESH_USE_EXPERIMENTAL_SCALE
|
||
|
cr_traits.add__scalar_t_matrix_p_scalar_t_matrix__t_vector(xyz, wij, rot_mtr[vi_id], wji, rot_mtr[vj_id], pij);
|
||
|
#else
|
||
|
cr_traits.add__scalar_t_matrix_p_scalar_t_matrix__t_vector(xyz, wij * scales[vi_id], rot_mtr[vi_id],
|
||
|
wji * scales[vj_id], rot_mtr[vj_id], pij);
|
||
|
#endif
|
||
|
// corresponds xyz += (wij*rot_mtr[vi_id] + wji*rot_mtr[vj_id]) * pij
|
||
|
}
|
||
|
Bx[vi_id] = cr_traits.vector_coordinate(xyz, 0);
|
||
|
By[vi_id] = cr_traits.vector_coordinate(xyz, 1);
|
||
|
Bz[vi_id] = cr_traits.vector_coordinate(xyz, 2);
|
||
|
}
|
||
|
else
|
||
|
{// constrained vertex
|
||
|
Bx[vi_id] = solution[vi_id][0]; By[vi_id] = solution[vi_id][1]; Bz[vi_id] = solution[vi_id][2];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// solve "A*X = B".
|
||
|
bool is_all_solved = m_solver.linear_solver(Bx, X) && m_solver.linear_solver(By, Y) && m_solver.linear_solver(Bz, Z);
|
||
|
if(!is_all_solved) {
|
||
|
CGAL_warning(false);
|
||
|
return;
|
||
|
}
|
||
|
// copy to solution
|
||
|
for (std::size_t i = 0; i < ros.size(); i++)
|
||
|
{
|
||
|
std::size_t v_id = ros_id(ros[i]);
|
||
|
Point p(X[v_id], Y[v_id], Z[v_id]);
|
||
|
if (!is_ctrl_map[id(ros[i])])
|
||
|
solution[v_id] = p;
|
||
|
}
|
||
|
}
|
||
|
/// calculate right-hand side of eq:lap_ber_rims in user manual and solve the system
|
||
|
void update_solution_spokes_and_rims()
|
||
|
{
|
||
|
typename Sparse_linear_solver::Vector X(ros.size()), Bx(ros.size());
|
||
|
typename Sparse_linear_solver::Vector Y(ros.size()), By(ros.size());
|
||
|
typename Sparse_linear_solver::Vector Z(ros.size()), Bz(ros.size());
|
||
|
|
||
|
Closest_rotation_traits cr_traits;
|
||
|
|
||
|
// assemble right columns of linear system
|
||
|
for ( std::size_t k = 0; k < ros.size(); k++ )
|
||
|
{
|
||
|
vertex_descriptor vi = ros[k];
|
||
|
std::size_t vi_id = ros_id(vi);
|
||
|
|
||
|
if ( is_roi_vertex(vi) && !is_control_vertex(vi) )
|
||
|
{// free vertices
|
||
|
// sum of right-hand side of eq:lap_ber_rims in user manual
|
||
|
CR_vector xyz = cr_traits.vector(0, 0, 0);
|
||
|
|
||
|
out_edge_iterator e, e_end;
|
||
|
for (cpp11::tie(e,e_end) = out_edges(vi, m_triangle_mesh); e != e_end; e++)
|
||
|
{
|
||
|
halfedge_descriptor he = halfedge(*e, m_triangle_mesh);
|
||
|
vertex_descriptor vj = target(he, m_triangle_mesh);
|
||
|
std::size_t vj_id = ros_id(vj);
|
||
|
|
||
|
const CR_vector& pij = sub_to_CR_vector(original[vi_id], original[vj_id]);
|
||
|
|
||
|
if(!is_border(he, m_triangle_mesh))
|
||
|
{
|
||
|
vertex_descriptor vn = target(next(he, m_triangle_mesh), m_triangle_mesh); // opp vertex of e_ij
|
||
|
double wji = hedge_weight[id(he)] / 3.0; // edge(pj - pi)
|
||
|
cr_traits.add_scalar_t_matrix_sum_t_vector(xyz, wji, rot_mtr[vi_id], rot_mtr[vj_id], rot_mtr[ros_id(vn)], pij);
|
||
|
// corresponds xyz += wji*(rot_mtr[vi_id] + rot_mtr[vj_id] + rot_mtr[ros_id(vn)])*pij;
|
||
|
}
|
||
|
|
||
|
halfedge_descriptor opp = opposite(he, m_triangle_mesh);
|
||
|
if(!is_border(opp, m_triangle_mesh))
|
||
|
{
|
||
|
vertex_descriptor vm = target(next(opp, m_triangle_mesh), m_triangle_mesh); // other opp vertex of e_ij
|
||
|
double wij = hedge_weight[id(opp)] / 3.0; // edge(pi - pj)
|
||
|
cr_traits.add_scalar_t_matrix_sum_t_vector(xyz, wij, rot_mtr[vi_id], rot_mtr[vj_id], rot_mtr[ros_id(vm)], pij);
|
||
|
// corresponds xyz += wij * ( rot_mtr[vi_id] + rot_mtr[vj_id] + rot_mtr[ros_id(vm)] ) * pij
|
||
|
}
|
||
|
}
|
||
|
Bx[vi_id] = cr_traits.vector_coordinate(xyz, 0);
|
||
|
By[vi_id] = cr_traits.vector_coordinate(xyz, 1);
|
||
|
Bz[vi_id] = cr_traits.vector_coordinate(xyz, 2);
|
||
|
}
|
||
|
else
|
||
|
{// constrained vertices
|
||
|
Bx[vi_id] = solution[vi_id][0]; By[vi_id] = solution[vi_id][1]; Bz[vi_id] = solution[vi_id][2];
|
||
|
}
|
||
|
}
|
||
|
// solve "A*X = B".
|
||
|
bool is_all_solved = m_solver.linear_solver(Bx, X) && m_solver.linear_solver(By, Y) && m_solver.linear_solver(Bz, Z);
|
||
|
if(!is_all_solved) {
|
||
|
CGAL_warning(false);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// copy to solution
|
||
|
for (std::size_t i = 0; i < ros.size(); i++)
|
||
|
{
|
||
|
std::size_t v_id = ros_id(ros[i]);
|
||
|
Point p(X[v_id], Y[v_id], Z[v_id]);
|
||
|
if (!is_ctrl_map[id(ros[i])])
|
||
|
solution[v_id] = p;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Assign solution to target surface mesh
|
||
|
void assign_solution()
|
||
|
{
|
||
|
for(std::size_t i = 0; i < ros.size(); ++i){
|
||
|
std::size_t v_id = ros_id(ros[i]);
|
||
|
if(is_roi_vertex(ros[i]))
|
||
|
{
|
||
|
put(vertex_point_map, ros[i], solution[v_id]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Compute modeling energy
|
||
|
double energy() const
|
||
|
{
|
||
|
if(TAG == SPOKES_AND_RIMS)
|
||
|
{
|
||
|
return energy_spokes_and_rims();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return energy_arap();
|
||
|
}
|
||
|
}
|
||
|
double energy_arap() const
|
||
|
{
|
||
|
Closest_rotation_traits cr_traits;
|
||
|
|
||
|
double sum_of_energy = 0;
|
||
|
// only accumulate ros vertices
|
||
|
for( std::size_t k = 0; k < ros.size(); k++ )
|
||
|
{
|
||
|
vertex_descriptor vi = ros[k];
|
||
|
std::size_t vi_id = ros_id(vi);
|
||
|
|
||
|
in_edge_iterator e, e_end;
|
||
|
for (cpp11::tie(e,e_end) = in_edges(vi, m_triangle_mesh); e != e_end; e++)
|
||
|
{
|
||
|
halfedge_descriptor he = halfedge(*e, m_triangle_mesh);
|
||
|
vertex_descriptor vj = source(he, m_triangle_mesh);
|
||
|
std::size_t vj_id = ros_id(vj);
|
||
|
|
||
|
const CR_vector& pij = sub_to_CR_vector(original[vi_id], original[vj_id]);
|
||
|
const CR_vector& qij = sub_to_CR_vector(solution[vi_id], solution[vj_id]);
|
||
|
|
||
|
double wij = hedge_weight[id(he)];
|
||
|
|
||
|
sum_of_energy += wij * cr_traits.squared_norm_vector_scalar_vector_subs(qij, rot_mtr[vi_id], pij);
|
||
|
// sum_of_energy += wij * ( qij - rot_mtr[vi_id]*pij )^2
|
||
|
}
|
||
|
}
|
||
|
return sum_of_energy;
|
||
|
}
|
||
|
double energy_spokes_and_rims() const
|
||
|
{
|
||
|
Closest_rotation_traits cr_traits;
|
||
|
|
||
|
double sum_of_energy = 0;
|
||
|
// only accumulate ros vertices
|
||
|
for( std::size_t k = 0; k < ros.size(); k++ )
|
||
|
{
|
||
|
vertex_descriptor vi = ros[k];
|
||
|
std::size_t vi_id = ros_id(vi);
|
||
|
//iterate through all triangles
|
||
|
out_edge_iterator e, e_end;
|
||
|
for (cpp11::tie(e,e_end) = out_edges(vi, m_triangle_mesh); e != e_end; e++)
|
||
|
{
|
||
|
halfedge_descriptor he = halfedge(*e, m_triangle_mesh);
|
||
|
if(is_border(he, m_triangle_mesh)) { continue; } // no facet
|
||
|
// iterate edges around facet
|
||
|
halfedge_descriptor hedge_around_facet = he;
|
||
|
do
|
||
|
{
|
||
|
vertex_descriptor v1 = target(hedge_around_facet, m_triangle_mesh);
|
||
|
vertex_descriptor v2 = source(hedge_around_facet, m_triangle_mesh);
|
||
|
std::size_t v1_id = ros_id(v1); std::size_t v2_id = ros_id(v2);
|
||
|
|
||
|
const CR_vector& p12 = sub_to_CR_vector(original[v1_id], original[v2_id]);
|
||
|
const CR_vector& q12 = sub_to_CR_vector(solution[v1_id], solution[v2_id]);
|
||
|
double w12 = hedge_weight[id(hedge_around_facet)];
|
||
|
|
||
|
sum_of_energy += w12 * cr_traits.squared_norm_vector_scalar_vector_subs(q12, rot_mtr[vi_id], p12);
|
||
|
// sum_of_energy += w12 * ( q12 - rot_mtr[vi_id]*p12 )^2
|
||
|
|
||
|
} while( (hedge_around_facet = next(hedge_around_facet, m_triangle_mesh)) != he);
|
||
|
}
|
||
|
}
|
||
|
return sum_of_energy;
|
||
|
}
|
||
|
|
||
|
void need_preprocess_both()
|
||
|
{
|
||
|
need_preprocess_factorization = true;
|
||
|
need_preprocess_region_of_solution = true;
|
||
|
}
|
||
|
|
||
|
/// p1 - p2, return CR_vector
|
||
|
CR_vector sub_to_CR_vector(const Point& p1, const Point& p2) const
|
||
|
{
|
||
|
return Closest_rotation_traits().vector(p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2]);
|
||
|
}
|
||
|
|
||
|
template<class Vect>
|
||
|
Point add_to_point(const Point& p, const Vect& v) {
|
||
|
return Point(p[0] + v[0], p[1] + v[1], p[2] + v[2]);
|
||
|
}
|
||
|
|
||
|
template<class Vect>
|
||
|
Vect sub_to_vect(const Point& p, const Vect& v) {
|
||
|
return Vect(p[0] - v[0], p[1] - v[1], p[2] - v[2]);
|
||
|
}
|
||
|
|
||
|
/// shorthand of get(vertex_index_map, v)
|
||
|
std::size_t id(vertex_descriptor vd) const
|
||
|
{ return get(vertex_index_map, vd); }
|
||
|
|
||
|
std::size_t& ros_id(vertex_descriptor vd)
|
||
|
{ return ros_id_map[id(vd)]; }
|
||
|
std::size_t ros_id(vertex_descriptor vd) const
|
||
|
{ return ros_id_map[id(vd)]; }
|
||
|
|
||
|
/// shorthand of get(hedge_index_map, e)
|
||
|
std::size_t id(halfedge_descriptor e) const
|
||
|
{
|
||
|
return get(hedge_index_map, e);
|
||
|
}
|
||
|
};
|
||
|
} //namespace CGAL
|
||
|
|
||
|
#include <CGAL/enable_warnings.h>
|
||
|
|
||
|
#endif // CGAL_SURFACE_MESH_DEFORMATION_H
|