cgal/Surface_modeling/include/CGAL/Deform_mesh.h

1511 lines
56 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$
//
// Author(s) : Yin Xu, Andreas Fabri and Ilker O. Yaz
#ifndef CGAL_DEFORM_MESH_H
#define CGAL_DEFORM_MESH_H
#include <CGAL/config.h>
#include <CGAL/internal/Surface_modeling/Weights.h>
#include <CGAL/Default.h>
#include <CGAL/tuple.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 PkgSurfaceModeling
///@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 */
};
/// @cond CGAL_DOCUMENT_INTERNAL
namespace internal {
template<class HalfedgeGraph, Deformation_algorithm_tag deformation_algorithm_tag>
struct Weight_calculator_selector {
typedef Uniform_weight<HalfedgeGraph> weight_calculator;
};
template<class HalfedgeGraph>
struct Weight_calculator_selector<HalfedgeGraph, CGAL::SPOKES_AND_RIMS> {
typedef Single_cotangent_weight<HalfedgeGraph> weight_calculator;
};
template<class HalfedgeGraph>
struct Weight_calculator_selector<HalfedgeGraph, CGAL::ORIGINAL_ARAP> {
typedef Cotangent_weight<HalfedgeGraph> weight_calculator;
};
}//namespace internal
/// @endcond
///
/// \ingroup PkgSurfaceModeling
/// @brief Class providing the functionalities for deforming a triangulated surface mesh
///
/// @tparam HG a model of HalfedgeGraph
/// @tparam VIM a model of `ReadablePropertyMap`</a> with Deform_mesh::vertex_descriptor as key and `unsigned int` as value type.
/// The default is `boost::property_map<HG, boost::%vertex_index_t>::%type`.
/// @tparam HIM a model of `ReadablePropertyMap`</a> with Deform_mesh::halfedge_descriptor as key and `unsigned int` as value type.
/// The default is `boost::property_map<HG, boost::%halfedge_index_t>::%type`.
/// @tparam TAG tag for selecting the deformation algorithm
/// @tparam WC a model of SurfaceModelingWeights, with `WC::Halfedge_graph` being `HG`.
/// 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 SparseLinearAlgebraTraitsWithFactor_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 Deform_mesh::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<HG, CGAL::vertex_point_t>::%type`.
template <
class HG,
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 Deform_mesh
{
//Typedefs
public:
/// \name Types
/// @{
// typedefed template parameters, main reason is doxygen creates autolink to typedefs but not template parameters
///
typedef HG Halfedge_graph;
// Index maps
#ifndef DOXYGEN_RUNNING
typedef typename Default::Get<
VIM,
typename boost::property_map<Halfedge_graph, boost::vertex_index_t>::type
>::type Vertex_index_map;
typedef typename Default::Get<
HIM,
typename boost::property_map<Halfedge_graph, boost::halfedge_index_t>::type
>::type Hedge_index_map;
#else
///
typedef VIM Vertex_index_map;
///
typedef HIM Hedge_index_map;
#endif
// weight calculator
#ifndef DOXYGEN_RUNNING
typedef typename Default::Get<
WC,
typename internal::Weight_calculator_selector<HG, TAG>::weight_calculator
>::type Weight_calculator;
#else
///
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
///
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
///
typedef CR Closest_rotation_traits;
#endif
// vertex point pmap
#ifndef DOXYGEN_RUNNING
typedef typename Default::Get<
VPM,
typename boost::property_map<Halfedge_graph, CGAL::vertex_point_t>::type
>::type Vertex_point_map;
#else
///
typedef VPM Vertex_point_map;
#endif
/// The type for vertex descriptor
typedef typename boost::graph_traits<Halfedge_graph>::vertex_descriptor vertex_descriptor;
/// The type for halfedge descriptor
typedef typename boost::graph_traits<Halfedge_graph>::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 Deform_mesh<HG, VIM, HIM, TAG, WC, ST, CR> Self;
// Repeat Halfedge_graph types
typedef typename boost::graph_traits<Halfedge_graph>::vertex_iterator vertex_iterator;
typedef typename boost::graph_traits<Halfedge_graph>::halfedge_iterator halfedge_iterator;
typedef typename boost::graph_traits<Halfedge_graph>::in_edge_iterator in_edge_iterator;
typedef typename boost::graph_traits<Halfedge_graph>::out_edge_iterator out_edge_iterator;
typedef typename Closest_rotation_traits::Matrix CR_matrix;
typedef typename Closest_rotation_traits::Vector CR_vector;
// Data members.
Halfedge_graph& m_halfedge_graph; /**< 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;
#ifdef CGAL_DEFORM_MESH_USE_EXPERIMENTAL_SR_ARAP
public:
// SR-ARAP [Zohar13]
double m_sr_arap_alpha;
private:
#endif
#ifdef CGAL_DEFORM_MESH_USE_EXPERIMENTAL_SCALE
std::vector<double> scales;
#endif
#ifndef CGAL_CFG_NO_CPP0X_DELETED_AND_DEFAULT_FUNCTIONS
public:
Deform_mesh(const Self&) = delete; // no copy
#else
private:
Deform_mesh(const Self&); // no copy
#endif
// Public methods
public:
/// \cond SKIP_FROM_MANUAL
//vertex_point_map set by default
Deform_mesh(Halfedge_graph& halfedge_graph,
Vertex_index_map vertex_index_map,
Hedge_index_map hedge_index_map
)
: m_halfedge_graph(halfedge_graph), vertex_index_map(vertex_index_map), hedge_index_map(hedge_index_map),
ros_id_map(std::vector<std::size_t>(num_vertices(halfedge_graph), (std::numeric_limits<std::size_t>::max)() )),
is_roi_map(std::vector<bool>(num_vertices(halfedge_graph), false)),
is_ctrl_map(std::vector<bool>(num_vertices(halfedge_graph), 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, halfedge_graph))
{
init();
}
//vertex_point_map and hedge_index_map set by default
Deform_mesh(Halfedge_graph& halfedge_graph,
Vertex_index_map vertex_index_map
)
: m_halfedge_graph(halfedge_graph), vertex_index_map(vertex_index_map),
hedge_index_map(get(boost::halfedge_index, halfedge_graph)),
ros_id_map(std::vector<std::size_t>(num_vertices(halfedge_graph), (std::numeric_limits<std::size_t>::max)() )),
is_roi_map(std::vector<bool>(num_vertices(halfedge_graph), false)),
is_ctrl_map(std::vector<bool>(num_vertices(halfedge_graph), 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, halfedge_graph))
{
init();
}
//vertex_point_map, hedge_index_map and vertex_index_map set by default
Deform_mesh(Halfedge_graph& halfedge_graph
)
: m_halfedge_graph(halfedge_graph),
vertex_index_map(get(boost::vertex_index, halfedge_graph)),
hedge_index_map(get(boost::halfedge_index, halfedge_graph)),
ros_id_map(std::vector<std::size_t>(num_vertices(halfedge_graph), (std::numeric_limits<std::size_t>::max)() )),
is_roi_map(std::vector<bool>(num_vertices(halfedge_graph), false)),
is_ctrl_map(std::vector<bool>(num_vertices(halfedge_graph), 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, halfedge_graph))
{
init();
}
// Constructor with all the parameters provided
Deform_mesh(Halfedge_graph& halfedge_graph,
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_halfedge_graph(halfedge_graph), vertex_index_map(vertex_index_map), hedge_index_map(hedge_index_map),
ros_id_map(std::vector<std::size_t>(num_vertices(halfedge_graph), (std::numeric_limits<std::size_t>::max)() )),
is_roi_map(std::vector<bool>(num_vertices(halfedge_graph), false)),
is_ctrl_map(std::vector<bool>(num_vertices(halfedge_graph), 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 the halfedge_graph consists of only triangular facets
* @param halfedge_graph triangulated surface mesh to deform
* @param vertex_index_map property map for associating an id to each vertex, from `0` to `num_vertices(halfedge_graph)-1`.
* @param hedge_index_map property map for associating an id to each halfedge, from `0` to `2*num_edges(halfedge_graph)-1`.
* @param vertex_point_map property map used to access the points associated to each vertex of the graph.
* @param weight_calculator function object or pointer for weight calculation
*/
Deform_mesh(Halfedge_graph& halfedge_graph,
Vertex_index_map vertex_index_map=get(boost::vertex_index, halfedge_graph),
Hedge_index_map hedge_index_map=get(boost::halfedge_index, halfedge_graph),
Vertex_point_map vertex_point_map=get(vertex_point, halfedge_graph),
Weight_calculator weight_calculator = Weight_calculator()
);
/// @}
#endif
private:
void init() {
// compute halfedge weights
halfedge_iterator eb, ee;
hedge_weight.reserve(2*num_edges(m_halfedge_graph));
for(cpp11::tie(eb, ee) = halfedges(m_halfedge_graph); eb != ee; ++eb)
{
hedge_weight.push_back(
this->weight_calculator(*eb, m_halfedge_graph, vertex_point_map));
}
#ifdef CGAL_DEFORM_MESH_USE_EXPERIMENTAL_SR_ARAP
m_sr_arap_alpha=2;
#endif
}
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_halfedge_graph), false);
is_ctrl_map.assign(num_vertices(m_halfedge_graph), 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_halfedge_graph), 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 graph 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()
{
if(roi.empty()) { return; } // no ROI to 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 halfedge-graph, 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()
{
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_halfedge_graph), false);
BOOST_FOREACH(vertex_descriptor vd, roi_vertices())
{
in_edge_iterator e, e_end;
for (cpp11::tie(e,e_end) = in_edges(vd, m_halfedge_graph); e != e_end; e++)
{
halfedge_descriptor he = halfedge(*e, m_halfedge_graph);
std::size_t id_e = id(he);
if(is_weight_computed[id_e]) { continue; }
hedge_weight[id_e] = weight_calculator(he, m_halfedge_graph, vertex_point_map);
is_weight_computed[id_e] = true;
halfedge_descriptor e_opp = opposite(he, m_halfedge_graph);
std::size_t id_e_opp = id(e_opp);
hedge_weight[id_e_opp] = weight_calculator(e_opp, m_halfedge_graph, 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.
*/
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 halfedge graph being deformed
*/
const Halfedge_graph& halfedge_graph() const
{ return m_halfedge_graph; }
/// @} 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_halfedge_graph); e != e_end; e++)
{
vertex_descriptor vt = source(*e, m_halfedge_graph);
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_halfedge_graph), (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_halfedge_graph); e != e_end; e++)
{
halfedge_descriptor he = halfedge(*e, m_halfedge_graph);
vertex_descriptor vj = source(he, m_halfedge_graph);
double wij = hedge_weight[id(he)]; // edge(pi - pj)
double wji = hedge_weight[id(opposite(he, m_halfedge_graph))]; // 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_halfedge_graph); e != e_end; e++)
{
halfedge_descriptor he = halfedge(*e, m_halfedge_graph);
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_halfedge_graph))
{
double wji = hedge_weight[id(he)]; // edge(pj - pi)
total_weight += wji;
}
halfedge_descriptor opp = opposite(he, m_halfedge_graph);
if(!is_border(opp, m_halfedge_graph))
{
double wij = hedge_weight[id(opp)]; // edge(pi - pj)
total_weight += wij;
}
// place coefficient to matrix
vertex_descriptor vj = target(he, m_halfedge_graph);
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;
#ifdef CGAL_DEFORM_MESH_USE_EXPERIMENTAL_SR_ARAP
cpp11::tie(e,e_end) = in_edges(vi, m_halfedge_graph);
double ne_i=std::distance(e,e_end);
#endif
for (cpp11::tie(e,e_end) = in_edges(vi, m_halfedge_graph); e != e_end; e++)
{
halfedge_descriptor he=halfedge(*e, m_halfedge_graph);
vertex_descriptor vj = source(he, m_halfedge_graph);
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)
#ifdef CGAL_DEFORM_MESH_USE_EXPERIMENTAL_SR_ARAP
// add neighbor rotation
cov += m_sr_arap_alpha * rot_mtr[vj_id].transpose() / ne_i;
#endif
}
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_halfedge_graph); e != e_end; e++)
{
halfedge_descriptor he = halfedge(*e, m_halfedge_graph);
if(is_border(he, m_halfedge_graph)) { continue; } // no facet
// iterate edges around facet
halfedge_descriptor hedge_around_facet = he;
do
{
vertex_descriptor v1 = target(hedge_around_facet, m_halfedge_graph);
vertex_descriptor v2 = source(hedge_around_facet, m_halfedge_graph);
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_halfedge_graph)) != 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_halfedge_graph); e != e_end; e++)
{
halfedge_descriptor he = *e;
vertex_descriptor vj = source(he, m_halfedge_graph);
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_halfedge_graph); e != e_end; e++)
{
halfedge_descriptor he = halfedge(*e, m_halfedge_graph);
vertex_descriptor vj = source(he, m_halfedge_graph);
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_halfedge_graph))];
#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]);
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_halfedge_graph); e != e_end; e++)
{
halfedge_descriptor he = halfedge(*e, m_halfedge_graph);
vertex_descriptor vj = target(he, m_halfedge_graph);
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_halfedge_graph))
{
vertex_descriptor vn = target(next(he, m_halfedge_graph), m_halfedge_graph); // 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_halfedge_graph);
if(!is_border(opp, m_halfedge_graph))
{
vertex_descriptor vm = target(next(opp, m_halfedge_graph), m_halfedge_graph); // 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]);
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();
return 0;
}
}
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_halfedge_graph); e != e_end; e++)
{
halfedge_descriptor he = halfedge(*e, m_halfedge_graph);
vertex_descriptor vj = source(he, m_halfedge_graph);
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_halfedge_graph); e != e_end; e++)
{
halfedge_descriptor he = halfedge(*e, m_halfedge_graph);
if(is_border(he, m_halfedge_graph)) { continue; } // no facet
// iterate edges around facet
halfedge_descriptor hedge_around_facet = he;
do
{
vertex_descriptor v1 = target(hedge_around_facet, m_halfedge_graph);
vertex_descriptor v2 = source(hedge_around_facet, m_halfedge_graph);
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_halfedge_graph)) != 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
#endif // CGAL_DEFORM_MESH_H