cgal/Surface_modeling/include/CGAL/Deform_mesh.h

1473 lines
53 KiB
C++

// Copyright (c) 2011 GeometryFactory
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org); you may redistribute it under
// the terms of the Q Public License version 1.0.
// See the file LICENSE.QPL distributed with CGAL.
//
// 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
#ifndef CGAL_DEFORM_MESH_H
#define CGAL_DEFORM_MESH_H
#include <CGAL/internal/Surface_modeling/Weights.h>
#include <CGAL/trace.h>
#include <CGAL/Timer.h>
#include <CGAL/boost/graph/graph_traits_Polyhedron_3.h>
#include <CGAL/boost/graph/properties_Polyhedron_3.h>
#include <CGAL/boost/graph/halfedge_graph_traits_Polyhedron_3.h>
#include <CGAL/FPU_extension.h>
#include <CGAL/Default.h>
#include <Eigen/Eigen>
#include <Eigen/SVD>
#include <vector>
#include <list>
#include <utility>
#include <limits>
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 Polyhedron, Deformation_algorithm_tag deformation_algorithm_tag>
struct Weight_calculator_selector {
typedef Uniform_weight<Polyhedron> weight_calculator;
};
template<class Polyhedron>
struct Weight_calculator_selector<Polyhedron, CGAL::SPOKES_AND_RIMS> {
typedef Single_cotangent_weight<Polyhedron> weight_calculator;
};
template<class Polyhedron>
struct Weight_calculator_selector<Polyhedron, CGAL::ORIGINAL_ARAP> {
typedef Cotangent_weight<Polyhedron> weight_calculator;
};
}//namespace internal
/// @endcond
/**
* \ingroup PkgSurfaceModeling
* @brief Class providing the functionalities for deforming a triangulated surface mesh
*
* @tparam P a model of HalfedgeGraph
* @tparam ST a model of SparseLinearAlgebraTraitsWithPreFactor_d
* @tparam VIM a model of `ReadWritePropertyMap`</a> with Deform_mesh::vertex_descriptor as key and `unsigned int` as value type
* @tparam EIM a model of `ReadWritePropertyMap`</a> with Deform_mesh::edge_descriptor as key and `unsigned int` as value type
* @tparam tag tag for selecting the deformation algorithm
* @tparam WC a model of SurfaceModelingWeightCalculator, with `WeightCalculator::Polyhedron` being `Polyhedron_`
*/
template <
class P,
class ST,
class VIM,
class EIM,
Deformation_algorithm_tag TAG = SPOKES_AND_RIMS,
class WC = Default
>
class Deform_mesh
{
//Typedefs
public:
/// \name Template parameter types
/// @{
// typedefed template parameters, main reason is doxygen creates autolink to typedefs but not template parameters
typedef P Polyhedron; /**< model of HalfedgeGraph */
typedef ST Sparse_linear_solver; /**< model of SparseLinearAlgebraTraitsWithPreFactor_d */
typedef VIM Vertex_index_map; /**< model of `ReadWritePropertyMap` with Deform_mesh::vertex_descriptor as key and `unsigned int` as value type */
typedef EIM Edge_index_map; /**< model of `ReadWritePropertyMap`</a> with Deform_mesh::edge_descriptor as key and `unsigned int` as value type */
#ifndef DOXYGEN_RUNNING
typedef typename Default::Get<
WC,
typename internal::Weight_calculator_selector<P, TAG>::weight_calculator
>::type Weight_calculator;
#else
typedef WC Weight_calculator; /**< model of SurfaceModelingWeightCalculator */
#endif
/// @}
typedef typename boost::graph_traits<Polyhedron>::vertex_descriptor vertex_descriptor; /**< The type for vertex representative objects */
typedef typename boost::graph_traits<Polyhedron>::edge_descriptor edge_descriptor; /**< The type for edge representative objects */
typedef typename Polyhedron::Traits::Vector_3 Vector; /**<The type for Vector_3 from Polyhedron traits */
typedef typename Polyhedron::Traits::Point_3 Point; /**<The type for Point_3 from Polyhedron traits */
private:
typedef Deform_mesh<P, ST, VIM, EIM, TAG, WC> Self;
// Repeat Polyhedron types
typedef typename boost::graph_traits<Polyhedron>::vertex_iterator vertex_iterator;
typedef typename boost::graph_traits<Polyhedron>::edge_iterator edge_iterator;
typedef typename boost::graph_traits<Polyhedron>::in_edge_iterator in_edge_iterator;
typedef typename boost::graph_traits<Polyhedron>::out_edge_iterator out_edge_iterator;
// Handle container types
typedef std::list<vertex_descriptor> Handle_container;
typedef std::list<Handle_container> Handle_group_container;
public:
/** The type used as the representative of a group of handles*/
typedef typename Handle_group_container::iterator Handle_group;
/** Const version of Handle_group*/
typedef typename Handle_group_container::const_iterator Const_handle_group;
/** The iterator type over the groups of handles. The type can be implicitly
converted to Deform_mesh::Handle_group or Deform_mesh::Const_handle_group */
typedef typename Handle_group_container::iterator Handle_group_iterator;
/** Const version of Handle_group_iterator */
typedef typename Handle_group_container::const_iterator Handle_group_const_iterator;
/** Iterator over vertex descriptors in a group of handles. Its value type is `vertex_descriptor` */
typedef typename Handle_container::iterator Handle_iterator;
/** Const version of Handle_iterator*/
typedef typename Handle_container::const_iterator Handle_const_iterator;
/** Iterator over vertex descriptors in the region-of-interest. Its value type is `vertex_descriptor` */
typedef typename std::vector<vertex_descriptor>::iterator Roi_iterator;
/** Const version of Roi_iterator*/
typedef typename std::vector<vertex_descriptor>::const_iterator Roi_const_iterator;
// Data members.
private:
Polyhedron& polyhedron; /**< 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
Edge_index_map edge_index_map; ///< storing indices of all edges
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_hdl_map; ///< (size: num vertices)
std::vector<double> edge_weight; ///< all edge weights
std::vector<Eigen::Matrix3d> rot_mtr; ///< rotation matrices of ros vertices (size: ros)
Sparse_linear_solver m_solver; ///< linear sparse solver
unsigned int iterations; ///< number of maximal iterations
double 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()
Handle_group_container handle_group_list; ///< user specified handles
Weight_calculator weight_calculator;
private:
Deform_mesh(const Self& s) { }
// Public methods
public:
/// \name Preprocess Section
/// @{
/**
* The constructor of a deformation object
*
* @pre the polyhedron consists of only triangular facets
* @param polyhedron triangulated surface mesh used to deform
* @param vertex_index_map property map for associating an id to each vertex
* @param edge_index_map property map for associating an id to each edge in the region of interest
* @param iterations see `set_iterations()` for more details
* @param tolerance see `set_tolerance()` for more details
* @param weight_calculator function object or pointer for weight calculation
*/
Deform_mesh(Polyhedron& polyhedron,
Vertex_index_map vertex_index_map,
Edge_index_map edge_index_map,
unsigned int iterations = 5,
double tolerance = 1e-4,
Weight_calculator weight_calculator = Weight_calculator())
: polyhedron(polyhedron), vertex_index_map(vertex_index_map), edge_index_map(edge_index_map),
ros_id_map(std::vector<std::size_t>(boost::num_vertices(polyhedron), (std::numeric_limits<std::size_t>::max)() )),
is_roi_map(std::vector<bool>(boost::num_vertices(polyhedron), false)),
is_hdl_map(std::vector<bool>(boost::num_vertices(polyhedron), false)),
iterations(iterations), tolerance(tolerance),
need_preprocess_factorization(true),
need_preprocess_region_of_solution(true),
last_preprocess_successful(false),
weight_calculator(weight_calculator)
{
// assign id to each vertex and edge
vertex_iterator vb, ve;
std::size_t id = 0;
for(boost::tie(vb, ve) = boost::vertices(polyhedron); vb != ve; ++vb, ++id)
{
put(vertex_index_map, *vb, id);
}
edge_iterator eb, ee;
id = 0;
for(boost::tie(eb, ee) = boost::edges(polyhedron); eb != ee; ++eb, ++id)
{
put(edge_index_map, *eb, id);
}
// compute edge weights
edge_weight.reserve(boost::num_edges(polyhedron));
for(boost::tie(eb, ee) = boost::edges(polyhedron); eb != ee; ++eb)
{
edge_weight.push_back(this->weight_calculator(*eb, polyhedron));
}
}
/**
* Puts the object in the same state after the creation (except iterations and tolerance).
*/
void reset()
{
need_preprocess_both();
// clear vertices
roi.clear();
handle_group_list.clear();
is_roi_map.assign(boost::num_vertices(polyhedron), false);
is_hdl_map.assign(boost::num_vertices(polyhedron), false);
}
/**
* Creates a new empty group of handles.
* `insert_handle(Handle_group handle_group, vertex_descriptor vd)` or `insert_handle(Handle_group handle_group, InputIterator begin, InputIterator end)`
* must be used to insert handles in a group.
* After inserting vertices, use `translate()` or `rotate()` for applying a transformation on all the vertices inside a group.
* @return a representative of the group of handles created (it is valid until `erase_handle(Handle_group handle_group)` is called)
*/
Handle_group create_handle_group()
{
// no need to need_preprocess = true;
handle_group_list.push_back(Handle_container());
return --handle_group_list.end();
}
/**
* Inserts a vertex into a group of handles. The vertex is also inserted in the region-of-interest if it is not already in.
* @param handle_group the group where the vertex will be inserted in
* @param vd the vertex to be inserted
* @return true if the insertion is successful
*/
bool insert_handle(Handle_group handle_group, vertex_descriptor vd)
{
if(is_handle(vd)) { return false; }
need_preprocess_both();
insert_roi(vd); // also insert it as roi
is_handle(vd) = true;
handle_group->push_back(vd);
return true;
}
/**
* Inserts a range of vertices in a group of handles. The vertices are also inserted in the region-of-interest if they are not already in.
* @tparam InputIterator input iterator type with `vertex_descriptor` as value type
* @param handle_group the group where the vertex will be inserted in
* @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_handle(Handle_group handle_group, InputIterator begin, InputIterator end)
{
for( ;begin != end; ++begin)
{
insert_handle(handle_group, *begin);
}
}
/**
* Erases a group of handles. Its representative becomes invalid.
* @param handle_group group to be erased
*/
void erase_handle(Handle_group handle_group)
{
need_preprocess_both();
for(typename Handle_container::iterator it = handle_group->begin(); it != handle_group->end(); ++it)
{
is_handle(*it) = false;
}
handle_group_list.erase(handle_group);
}
/**
* Erases a vertex from a group of handles. Note that the group of handles is not erased even if it becomes empty.
* @param handle_group the group of handles from which the vertex is erased
* @param vd the vertex to be erased
* @return true if the removal is successful
*/
bool erase_handle(Handle_group handle_group, vertex_descriptor vd)
{
if(!is_handle(vd)) { return false; }
typename Handle_container::iterator it = std::find(handle_group->begin(), handle_group->end(), vd);
if(it != handle_group->end())
{
is_handle(*it) = false;
handle_group->erase(it);
need_preprocess_both();
return true;
// Although the handle group might get empty, we do not delete it from handle_group
}
return false; // OK (vd is handle but placed in other handle group than handle_group argument)
}
/**
* Erases a vertex by searching it through all groups of handles. Note that the group of handles is not erased even if it becomes empty.
* @param vd the vertex to be erased
* @return true if the removal is successful
*/
bool erase_handle(vertex_descriptor vd)
{
if(!is_handle(vd)) { return false; }
for(Handle_group it = handle_group_list.begin(); it != handle_group_list.end(); ++it)
{
if(erase_handle(it, vd)) { return true; }
}
CGAL_assertion(false);// inconsistency between is_handle_map, and handle_group_list
return false;
}
/**
* Provides access to all the groups of handles.
* @return a range of iterators over all groups of handles
*/
std::pair<Handle_group_iterator, Handle_group_iterator> handle_groups()
{
return std::make_pair(handle_group_list.begin(), handle_group_list.end());
}
/**
* const version
*/
std::pair<Handle_group_const_iterator, Handle_group_const_iterator> handle_groups() const
{
return std::make_pair(handle_group_list.begin(), handle_group_list.end());
}
/**
* Provides access to all the handles of a group.
* @param handle_group the group of handles considered
* @return a range of iterators over all handle of a group
*
*/
std::pair<Handle_iterator, Handle_iterator> handles(Handle_group handle_group)
{
return std::make_pair(handle_group->begin(), handle_group->end());
}
/**
* const version
*/
std::pair<Handle_const_iterator, Handle_const_iterator> handles(Const_handle_group handle_group) const
{
return std::make_pair(handle_group->begin(), handle_group->end());
}
/**
* 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(InputIterator begin, InputIterator end)
{
for( ;begin != end; ++begin)
{
insert_roi(*begin);
}
}
/**
* Inserts a vertex in the region-of-interest
* @param vd vertex to be inserted
* @return true if the insertion is successful
*/
bool insert_roi(vertex_descriptor vd)
{
if(is_roi(vd)) { return false; }
need_preprocess_both();
is_roi(vd) = true;
roi.push_back(vd);
return true;
}
/**
* Erases a vertex from the region-of-interest. The vertex is also removed from any group of handles.
* Note that the next call to `preprocess()`, any vertex which is no longer in the region-of-interest will be assigned to its original position (i.e. position of the vertex at the time of construction).
* @param vd vertex to be erased
* @return true if the removal is successful
*/
bool erase_roi(vertex_descriptor vd)
{
if(!is_roi(vd)) { return false; }
erase_handle(vd); // also erase from being handle
typename std::vector<vertex_descriptor>::iterator it = std::find(roi.begin(), roi.end(), vd);
if(it != roi.end())
{
is_roi(vd) = false;
roi.erase(it);
need_preprocess_both();
return true;
}
CGAL_assertion(false); // inconsistency between is_roi_map, and roi vector!
return false;
}
/**
* Provides access to the vertices in the region-of-interest. Note that deleting a vertex from the region-of-interest will invalidate its iterator.
* @return an iterator range
*/
std::pair<Roi_iterator, Roi_iterator> roi_vertices()
{
return std::make_pair(roi.begin(), roi.end());
}
/**
* const version
*/
std::pair<Roi_const_iterator, Roi_const_iterator> roi_vertices() const
{
return std::make_pair(roi.begin(), roi.end());
}
/**
* Triggers the necessary precomputation work before beginning deformation.
* Note that the insertion of a vertex in a group of handles or in the region-of-interest invalidates the
* preprocessing data. This function need only to be called before calling `deform()`.
* @return true if Laplacian matrix factorization is successful.
* A common reason for failure is that the system is rank deficient,
* which happens if there is no path between a free vertex and a handle vertex (i.e. both fixed and user-inserted).
*/
bool preprocess()
{
region_of_solution();
assemble_laplacian_and_factorize();
return last_preprocess_successful; // which is set by assemble_laplacian_and_factorize()
}
/// @} Preprocess Section
/// \name Deform Section
/// @{
/**
* Sets the transformation to apply to all the vertices in a group of handles to be a translation by vector `t`.
* \note This transformation is applied on the original positions of the vertices (that is positions of vertices at the time of construction or after the last call to `overwrite_original_positions()`).
* \note A call to this function cancels the last call to `rotate()` or `translate()`.
* @param handle_group the representative of the group of handles to be translated
* @param t translation vector
*/
void translate(Const_handle_group handle_group, const Vector& t)
{
region_of_solution(); // we require ros ids, so if there is any need to preprocess of region of solution -do it.
for(typename Handle_container::const_iterator it = handle_group->begin();
it != handle_group->end(); ++it)
{
std::size_t v_id = ros_id(*it);
solution[v_id] = original[v_id] + t;
}
}
/**
* Sets the transformation to apply to all the vertices in a group of handles to be a rotation around `rotation_center`
* defined by the quaternion `quat`, followed by a translation by vector `t`.
* \note This transformation is applied on the original positions of the vertices (that is positions of vertices at the time of construction or after the last call to `overwrite_original_positions()`).
* \note A call to this function cancels the last call to `rotate()` or `translate()`.
* @tparam Quaternion is a quaternion class with `Vect operator*(Quaternion, Vect)` being defined and returns the product of a quaternion with a vector
* @tparam Vect is a 3D vector class, `Vect(double x,double y, double z)` being a constructor available and `Vect::operator[](int i)` with i=0,1 or 2 returns its coordinates
* @param handle_group the representative of the group of handles to be rotated and translated
* @param rotation_center center of rotation
* @param quat rotation holder quaternion
* @param t post translation vector
*/
template <typename Quaternion, typename Vect>
void rotate(Const_handle_group handle_group, const Point& rotation_center, const Quaternion& quat, 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(typename Handle_container::const_iterator it = handle_group->begin();
it != handle_group->end(); ++it)
{
std::size_t v_id = ros_id(*it);
Point p = CGAL::ORIGIN + ( original[v_id] - rotation_center);
Vect v = quat * Vect(p.x(),p.y(),p.z());
p = Point(v[0], v[1], v[2]) + (rotation_center - CGAL::ORIGIN);
p = p + Vector(t[0],t[1],t[2]);
solution[v_id] = p;
}
}
/**
* Assigns the target position of a handle vertex
* @param vd handle the vertex to be assigned target position
* @param target_position the new vertex position
*/
void assign(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_handle(vd)) { return; }
solution[ros_id(vd)] = target_position;
}
/**
* Deforms the region-of-interest according to the deformation algorithm, applying for each group of handles the transformation provided by `rotate()` or `translate()`
* to their original positions. The coordinates of the vertices of the input graph that are inside the region-of-interest are updated. The initial guess for solving the
* deformation problem is using the coordinates of the input graph before calling the function.
* \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(iterations, 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();
optimal_rotations_svd();
// 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 target mesh
assign_solution();
}
/// @} Deform Section
/// \name Utilities
/// @{
/**
* Getter of `set_iterations()`
*/
unsigned int get_iterations()
{ return iterations; }
/**
* Getter of `set_tolerance()`
*/
double get_tolerance()
{ return tolerance; }
/**
* Sets the number of iterations used in `deform()`
*/
void set_iterations(unsigned int iterations)
{ this->iterations = iterations; }
/// @brief Sets the tolerance of convergence used in `deform()`.
/// Set to zero if energy based termination is not required, which also eliminates energy calculation effort in each iteration.
///
/// `tolerance >` \f$|energy(m_i) - energy(m_{i-1})| / energy(m_i)\f$ will be used as a termination criterium.
void set_tolerance(double tolerance)
{ this->tolerance = tolerance; }
/**
* Queries whether a vertex is inside the region-of-interest.
* @param vd the query vertex
* @return true if the vertex is inside the region-of-interest
*/
bool is_roi(vertex_descriptor vd) const
{ return is_roi_map[id(vd)]; }
/**
* Queries whether a vertex is a handle.
* @param vd the query vertex
* @return true if the vertex is inside any group of handles
*/
bool is_handle(vertex_descriptor vd) const
{ return is_hdl_map[id(vd)]; }
/**
* Accessor for halfedge graph being deformed
* @return the halfedge graph
*/
const Polyhedron& halfedge_graph() const
{ return polyhedron; }
/**
* Sets the original positions to be the current positions. Calling this function as the same effect as creating
* a new deformation object with the current deformed polyhedron, keeping the region-of-interest and the groups of handles.
* \note if the region-of-interest or any group of handles have been modified since the last call to `preprocess()`,
* it will be called prior to the overwrite.
*/
void overwrite_original_positions()
{
if(roi.empty()) { return; } // no ROI to overwrite
region_of_solution(); // the roi should be preprocessed since we are using original_position vec
Roi_iterator rb, re;
for(boost::tie(rb, re) = roi_vertices(); rb != re; ++rb)
{
original[ros_id(*rb)] = (*rb)->point();
}
// now I need to compute weights for edges incident to roi vertices
std::vector<bool> is_weight_computed(boost::num_edges(polyhedron), false);
for(boost::tie(rb, re) = roi_vertices(); rb != re; ++rb)
{
in_edge_iterator e, e_end;
for (boost::tie(e,e_end) = boost::in_edges(*rb, polyhedron); e != e_end; e++)
{
std::size_t id_e = id(*e);
if(is_weight_computed[id_e]) { continue; }
edge_weight[id_e] = weight_calculator(*e, polyhedron);
is_weight_computed[id_e] = true;
edge_descriptor e_opp = CGAL::opposite_edge(*e, polyhedron);
std::size_t id_e_opp = id(e_opp);
edge_weight[id_e_opp] = weight_calculator(e_opp, polyhedron);
is_weight_computed[id_e_opp] = true;
}
}
// also set rotation matrix to identity
std::fill(rot_mtr.begin(), rot_mtr.end(), Eigen::Matrix3d().setIdentity());
need_preprocess_both(); // now we need reprocess
}
/// @} 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 (boost::tie(e,e_end) = boost::in_edges(vd, polyhedron); e != e_end; e++)
{
vertex_descriptor vt = boost::source(*e, polyhedron);
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<Eigen::Matrix3d> 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(*it)) {
(*it)->point() = 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(boost::num_vertices(polyhedron), (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 SVD 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].setIdentity();
}
}
// 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_svd() 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(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] = ros[i]->point();
original[v_ros_id] = ros[i]->point();
}
}
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] = outside_ros[i]->point();
solution[v_ros_id] = outside_ros[i]->point();
}
}
/// 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(vi) && !is_handle(vi) ) // vertices of ( roi - hdl )
{
double diagonal = 0;
in_edge_iterator e, e_end;
for (boost::tie(e,e_end) = boost::in_edges(vi, polyhedron); e != e_end; e++)
{
vertex_descriptor vj = boost::source(*e, polyhedron);
double wij = edge_weight[id(*e)]; // edge(pi - pj)
double wji = edge_weight[id(CGAL::opposite_edge(*e, polyhedron))]; // 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.pre_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(vi) && !is_handle(vi) ) // vertices of ( roi - hdl ): free vertices
{
double diagonal = 0;
out_edge_iterator e, e_end;
for (boost::tie(e,e_end) = boost::out_edges(vi, polyhedron); e != e_end; e++)
{
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(!boost::get(CGAL::edge_is_border, polyhedron, *e))
{
double wji = edge_weight[id(*e)]; // edge(pj - pi)
total_weight += wji;
}
edge_descriptor opp = CGAL::opposite_edge(*e, polyhedron);
if(!boost::get(CGAL::edge_is_border, polyhedron, opp))
{
double wij = edge_weight[id(opp)]; // edge(pi - pj)
total_weight += wij;
}
// place coefficient to matrix
vertex_descriptor vj = boost::target(*e, polyhedron);
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.pre_factor(A, D);
CGAL_warning(last_preprocess_successful);
}
/// Local step of iterations, computing optimal rotation matrices using SVD decomposition, stable
void optimal_rotations_svd()
{
if(TAG == SPOKES_AND_RIMS)
{
optimal_rotations_svd_spokes_and_rims();
}
else
{
optimal_rotations_svd_arap();
}
}
void optimal_rotations_svd_arap()
{
Eigen::Matrix3d cov; // covariance matrix
Eigen::JacobiSVD<Eigen::Matrix3d> svd; // SVD solver
// 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.setZero();
in_edge_iterator e, e_end;
for (boost::tie(e,e_end) = boost::in_edges(vi, polyhedron); e != e_end; e++)
{
vertex_descriptor vj = boost::source(*e, polyhedron);
std::size_t vj_id = ros_id(vj);
Eigen::Vector3d pij = sub_to_col(original[vi_id], original[vj_id]);
Eigen::RowVector3d qij = sub_to_row(solution[vi_id], solution[vj_id]);
double wij = edge_weight[id(*e)];
cov += wij * (pij * qij);
}
// svd decomposition
svd.compute( cov, Eigen::ComputeFullU | Eigen::ComputeFullV );
const Eigen::Matrix3d& u = svd.matrixU();
const Eigen::Matrix3d& v = svd.matrixV();
// extract rotation matrix
rot_mtr[vi_id] = v*u.transpose();
// checking negative determinant of r
if ( rot_mtr[vi_id].determinant() < 0 ) // changing the sign of column corresponding to smallest singular value
{
Eigen::Matrix3d u_m = u;
u_m.col(2) *= -1; // singular values are always sorted in decreasing order so use column 2
rot_mtr[vi_id] = v*u_m.transpose(); // re-extract rotation matrix
}
}
}
void optimal_rotations_svd_spokes_and_rims()
{
Eigen::Matrix3d cov; // covariance matrix
Eigen::JacobiSVD<Eigen::Matrix3d> svd; // SVD solver
// 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.setZero();
//iterate through all triangles
out_edge_iterator e, e_end;
for (boost::tie(e,e_end) = boost::out_edges(vi, polyhedron); e != e_end; e++)
{
if(boost::get(CGAL::edge_is_border, polyhedron, *e)) { continue; } // no facet
// iterate edges around facet
edge_descriptor edge_around_facet = *e;
do
{
vertex_descriptor v1 = boost::target(edge_around_facet, polyhedron);
vertex_descriptor v2 = boost::source(edge_around_facet, polyhedron);
std::size_t v1_id = ros_id(v1); std::size_t v2_id = ros_id(v2);
Eigen::Vector3d p12 = sub_to_col(original[v1_id], original[v2_id]);
Eigen::RowVector3d q12 = sub_to_row(solution[v1_id], solution[v2_id]);
double w12 = edge_weight[id(edge_around_facet)];
cov += w12 * (p12 * q12);
} while( (edge_around_facet = CGAL::next_edge(edge_around_facet, polyhedron)) != *e);
}
// svd decomposition
svd.compute( cov, Eigen::ComputeFullU | Eigen::ComputeFullV );
const Eigen::Matrix3d& u = svd.matrixU();
const Eigen::Matrix3d& v = svd.matrixV();
// extract rotation matrix
rot_mtr[vi_id] = v*u.transpose();
// checking negative determinant of r
if ( rot_mtr[vi_id].determinant() < 0 ) // changing the sign of column corresponding to smallest singular value
{
Eigen::Matrix3d u_m = u;
u_m.col(2) *= -1; // singular values are always sorted in decreasing order so use column 2
rot_mtr[vi_id] = v*u_m.transpose(); // re-extract rotation matrix
}
}
}
/// 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());
// 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(vi) && !is_handle(vi) )
{// free vertices
Eigen::Vector3d xyz; xyz.setZero(); // sum of right-hand side of eq:lap_ber in user manual
in_edge_iterator e, e_end;
for (boost::tie(e,e_end) = boost::in_edges(vi, polyhedron); e != e_end; e++)
{
vertex_descriptor vj = boost::source(*e, polyhedron);
std::size_t vj_id = ros_id(vj);
Eigen::Vector3d pij = sub_to_col(original[vi_id], original[vj_id]);
double wij = edge_weight[id(*e)];
double wji = edge_weight[id(CGAL::opposite_edge(*e, polyhedron))];
xyz += (wij*rot_mtr[vi_id] + wji*rot_mtr[vj_id]) * pij;
}
Bx[vi_id] = xyz(0,0); By[vi_id] = xyz(1,0); Bz[vi_id] = xyz(2,0);
}
else
{// constrained vertex
Bx[vi_id] = solution[vi_id].x(); By[vi_id] = solution[vi_id].y(); Bz[vi_id] = solution[vi_id].z();
}
}
// 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());
// 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(vi) && !is_handle(vi) )
{// free vertices
Eigen::Vector3d xyz; xyz.setZero(); // sum of right-hand side of eq:lap_ber_rims in user manual
out_edge_iterator e, e_end;
for (boost::tie(e,e_end) = boost::out_edges(vi, polyhedron); e != e_end; e++)
{
vertex_descriptor vj = boost::target(*e, polyhedron);
std::size_t vj_id = ros_id(vj);
Eigen::Vector3d pij = sub_to_col(original[vi_id], original[vj_id]);
if(!boost::get(CGAL::edge_is_border, polyhedron, *e))
{
vertex_descriptor vn = boost::target(CGAL::next_edge(*e, polyhedron), polyhedron); // opp vertex of e_ij
double wji = edge_weight[id(*e)] / 3.0; // edge(pj - pi)
xyz += wji*(rot_mtr[vi_id] + rot_mtr[vj_id] + rot_mtr[ros_id(vn)])*pij;
}
edge_descriptor opp = CGAL::opposite_edge(*e, polyhedron);
if(!boost::get(CGAL::edge_is_border, polyhedron, opp))
{
vertex_descriptor vm = boost::target(CGAL::next_edge(opp, polyhedron), polyhedron); // other opp vertex of e_ij
double wij = edge_weight[id(opp)] / 3.0; // edge(pi - pj)
xyz += wij*(rot_mtr[vi_id] + rot_mtr[vj_id] + rot_mtr[ros_id(vm)])*pij;
}
}
Bx[vi_id] = xyz(0,0); By[vi_id] = xyz(1,0); Bz[vi_id] = xyz(2,0);
}
else
{// constrained vertices
Bx[vi_id] = solution[vi_id].x(); By[vi_id] = solution[vi_id].y(); Bz[vi_id] = solution[vi_id].z();
}
}
// 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 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(ros[i]))
{
ros[i]->point() = 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
{
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 (boost::tie(e,e_end) = boost::in_edges(vi, polyhedron); e != e_end; e++)
{
vertex_descriptor vj = boost::source(*e, polyhedron);
std::size_t vj_id = ros_id(vj);
Eigen::Vector3d pij = sub_to_col(original[vi_id], original[vj_id]);
Eigen::Vector3d qij = sub_to_col(solution[vi_id], solution[vj_id]);
double wij = edge_weight[id(*e)];
sum_of_energy += wij * (qij - rot_mtr[vi_id]*pij).squaredNorm();
}
}
return sum_of_energy;
}
double energy_spokes_and_rims() const
{
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 (boost::tie(e,e_end) = boost::out_edges(vi, polyhedron); e != e_end; e++)
{
if(boost::get(CGAL::edge_is_border, polyhedron, *e)) { continue; } // no facet
// iterate edges around facet
edge_descriptor edge_around_facet = *e;
do
{
vertex_descriptor v1 = boost::target(edge_around_facet, polyhedron);
vertex_descriptor v2 = boost::source(edge_around_facet, polyhedron);
std::size_t v1_id = ros_id(v1); std::size_t v2_id = ros_id(v2);
Eigen::Vector3d p12 = sub_to_col(original[v1_id], original[v2_id]);
Eigen::Vector3d q12 = sub_to_col(solution[v1_id], solution[v2_id]);
double w12 = edge_weight[id(edge_around_facet)];
sum_of_energy += w12 * (q12 - rot_mtr[vi_id]*p12).squaredNorm();
} while( (edge_around_facet = CGAL::next_edge(edge_around_facet, polyhedron)) != *e);
}
}
return sum_of_energy;
}
void need_preprocess_both()
{
need_preprocess_factorization = true;
need_preprocess_region_of_solution = true;
}
/// p1 - p2, return Eigen Column Vector
Eigen::Vector3d sub_to_col(const Point& p1, const Point& p2) const
{
return Eigen::Vector3d(p1.x() - p2.x(), p1.y() - p2.y(), p1.z() - p2.z());
}
/// p1 - p2, return Eigen Row Vector
Eigen::RowVector3d sub_to_row(const Point& p1, const Point& p2) const
{
return Eigen::RowVector3d(p1.x() - p2.x(), p1.y() - p2.y(), p1.z() - p2.z());
}
/// 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)]; }
std::vector<bool>::reference is_handle(vertex_descriptor vd)
{ return is_hdl_map[id(vd)]; }
std::vector<bool>::reference is_roi(vertex_descriptor vd)
{ return is_roi_map[id(vd)]; }
/// shorthand of get(edge_index_map, e)
std::size_t id(edge_descriptor e) const
{
return get(edge_index_map, e);
}
#ifdef CGAL_DEFORM_EXPERIMENTAL // Experimental stuff, needs further testing
double norm_1(const Eigen::Matrix3d& X)
{
double sum = 0;
for ( int i = 0; i < 3; i++ )
{
for ( int j = 0; j < 3; j++ )
{
sum += abs(X(i,j));
}
}
return sum;
}
double norm_inf(const Eigen::Matrix3d& X)
{
double max_abs = abs(X(0,0));
for ( int i = 0; i < 3; i++ )
{
for ( int j = 0; j < 3; j++ )
{
double new_abs = abs(X(i,j));
if ( new_abs > max_abs )
{
max_abs = new_abs;
}
}
}
return max_abs;
}
// polar decomposition using Newton's method, with warm start, stable but slow
// not used, need to be investigated later
void polar_newton(const Eigen::Matrix3d& A, Eigen::Matrix3d &U, double tole)
{
Eigen::Matrix3d X = A;
Eigen::Matrix3d Y;
double alpha, beta, gamma;
do
{
Y = X.inverse();
alpha = sqrt( norm_1(X) * norm_inf(X) );
beta = sqrt( norm_1(Y) * norm_inf(Y) );
gamma = sqrt(beta/alpha);
X = 0.5*( gamma*X + Y.transpose()/gamma );
} while ( abs(gamma-1) > tole );
U = X;
}
// polar decomposition using Eigen, 5 times faster than SVD
template<typename Mat>
void polar_eigen(const Mat& A, Mat& R, bool& SVD)
{
typedef typename Mat::Scalar Scalar;
typedef Eigen::Matrix<typename Mat::Scalar,3,1> Vec;
const Scalar th = std::sqrt(Eigen::NumTraits<Scalar>::dummy_precision());
Eigen::SelfAdjointEigenSolver<Mat> eig;
feclearexcept(FE_UNDERFLOW);
eig.computeDirect(A.transpose()*A);
if(fetestexcept(FE_UNDERFLOW) || eig.eigenvalues()(0)/eig.eigenvalues()(2)<th)
{
// The computation of the eigenvalues might have diverged.
// Fallback to an accurate SVD based decompositon method.
Eigen::JacobiSVD<Mat> svd;
svd.compute(A, Eigen::ComputeFullU | Eigen::ComputeFullV );
const Mat& u = svd.matrixU(); const Mat& v = svd.matrixV();
R = u*v.transpose();
SVD = true;
return;
}
Vec S = eig.eigenvalues().cwiseSqrt();
R = A * eig.eigenvectors() * S.asDiagonal().inverse()
* eig.eigenvectors().transpose();
SVD = false;
if(std::abs(R.squaredNorm()-3.) > th)
{
// The computation of the eigenvalues might have diverged.
// Fallback to an accurate SVD based decomposition method.
Eigen::JacobiSVD<Mat> svd;
svd.compute(A, Eigen::ComputeFullU | Eigen::ComputeFullV );
const Mat& u = svd.matrixU(); const Mat& v = svd.matrixV();
R = u*v.transpose();
SVD = true;
return;
}
}
// Local step of iterations, computing optimal rotation matrices using Polar decomposition
void optimal_rotations_polar()
{
Eigen::Matrix3d u, v; // orthogonal matrices
Eigen::Vector3d w; // singular values
Eigen::Matrix3d cov; // covariance matrix
Eigen::Matrix3d r;
Eigen::JacobiSVD<Eigen::Matrix3d> svd; // SVD solver, for non-positive covariance matrices
int num_svd = 0;
bool SVD = false;
// only accumulate ros vertices
for ( std::size_t i = 0; i < ros.size(); i++ )
{
vertex_descriptor vi = ros[i];
// compute covariance matrix
cov.setZero();
in_edge_iterator e, e_end;
for (boost::tie(e,e_end) = boost::in_edges(vi, polyhedron); e != e_end; e++)
{
vertex_descriptor vj = boost::source(*e, polyhedron);
Vector pij = original[get(vertex_index_map, vi)] - original[get(vertex_index_map, vj)];
Vector qij = solution[get(vertex_index_map, vi)] - solution[get(vertex_index_map, vj)];
double wij = edge_weight[get(edge_index_map, *e)];
for (int j = 0; j < 3; j++)
{
for (int k = 0; k < 3; k++)
{
cov(j, k) += wij*pij[j]*qij[k];
}
}
}
// svd decomposition
if (cov.determinant() > 0)
{
polar_eigen<Eigen::Matrix3d> (cov, r, SVD);
//polar_newton(cov, r, 1e-4);
if(SVD)
num_svd++;
r.transposeInPlace(); // the optimal rotation matrix should be transpose of decomposition result
}
else
{
svd.compute( cov, Eigen::ComputeFullU | Eigen::ComputeFullV );
u = svd.matrixU(); v = svd.matrixV(); w = svd.singularValues();
r = v*u.transpose();
num_svd++;
}
// checking negative determinant of covariance matrix
if ( r.determinant() < 0 ) // back to SVD method
{
if (cov.determinant() > 0)
{
svd.compute( cov, Eigen::ComputeFullU | Eigen::ComputeFullV );
u = svd.matrixU(); v = svd.matrixV(); w = svd.singularValues();
num_svd++;
}
for (int j = 0; j < 3; j++)
{
int j0 = j;
int j1 = (j+1)%3;
int j2 = (j1+1)%3;
if ( w[j0] <= w[j1] && w[j0] <= w[j2] ) // smallest singular value as j0
{
u(0, j0) = - u(0, j0);
u(1, j0) = - u(1, j0);
u(2, j0) = - u(2, j0);
break;
}
}
// re-extract rotation matrix
r = v*u.transpose();
}
rot_mtr[i] = r;
}
double svd_percent = (double)(num_svd)/ros.size();
CGAL_TRACE_STREAM << svd_percent*100 << "% percentage SVD decompositions;";
CGAL_TRACE_STREAM << num_svd << " SVD decompositions\n";
}
#endif
};
} //namespace CGAL
#endif // CGAL_DEFORM_MESH_H