mirror of https://github.com/CGAL/cgal
1269 lines
44 KiB
C++
1269 lines
44 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/internal/Surface_modeling/Spokes_and_rims_iterator.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 <Eigen/Eigen>
|
|
#include <Eigen/SVD>
|
|
|
|
#include <set>
|
|
#include <vector>
|
|
#include <list>
|
|
|
|
#define CGAL_DEFORM_SPOKES_AND_RIMS // it uses all edges in facets around a vertex
|
|
|
|
namespace CGAL {
|
|
|
|
/// \ingroup PkgSurfaceModeling
|
|
/**
|
|
* @brief Class providing the functionalities for deforming a triangulated surface mesh
|
|
*
|
|
* @pre @a polyhedron.is_pure_triangle()
|
|
* @tparam Polyhedron a model of HalfedgeGraph
|
|
* @tparam SparseLinearAlgebraTraitsWithPreFactor_d sparse linear solver for square sparse linear systems
|
|
* @tparam VertexIndexMap a <a href="http://www.boost.org/doc/libs/release/libs/property_map/doc/ReadWritePropertyMap.html">`ReadWritePropertyMap`</a> with vertex_descriptor as key and `unsigned int` as value type
|
|
* @tparam EdgeIndexMap a <a href="http://www.boost.org/doc/libs/release/libs/property_map/doc/ReadWritePropertyMap.html">`ReadWritePropertyMap`</a> with edge_descriptor as key and `unsigned int` as value type
|
|
* @tparam WeightCalculator how to document this (should I provide a concept, like in SegmentationGeomTraits ?) */
|
|
/// \code
|
|
/// // a simple model to WeightCalculator concept, which provides uniform weights
|
|
/// template<class Polyhedron>
|
|
/// class Uniform_weight
|
|
/// {
|
|
/// public:
|
|
/// typedef typename boost::graph_traits<Polyhedron>::edge_descriptor edge_descriptor;
|
|
///
|
|
/// Uniform_weight(Polyhedron& /*polyhedron*/) { }
|
|
///
|
|
/// double operator()(edge_descriptor e)
|
|
/// { return 1.0; }
|
|
/// };
|
|
/// \endcode
|
|
|
|
template <
|
|
class Polyhedron,
|
|
class SparseLinearAlgebraTraits_d,
|
|
class VertexIndexMap,
|
|
class EdgeIndexMap,
|
|
#if defined(CGAL_DEFORM_SPOKES_AND_RIMS)
|
|
class WeightCalculator = internal::Single_cotangent_weight<Polyhedron >
|
|
#else
|
|
class WeightCalculator = internal::Cotangent_weight<Polyhedron >
|
|
#endif
|
|
>
|
|
class Deform_mesh
|
|
{
|
|
//Typedefs
|
|
public:
|
|
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:
|
|
// Geometric types
|
|
typedef typename Polyhedron::Traits Kernel;
|
|
|
|
// 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;
|
|
|
|
typedef internal::Spokes_and_rims_iterator<Polyhedron> Rims_iterator;
|
|
|
|
// Handle container types
|
|
typedef std::vector<vertex_descriptor> Handle_container;
|
|
typedef std::list<Handle_container> Handle_group_container;
|
|
public:
|
|
/** The type for returned handle group representative from insert_handle(vertex_descriptor vd), insert_handle(InputIterator begin, InputIterator end) */
|
|
typedef typename Handle_group_container::iterator Handle_group;
|
|
|
|
// Data members.
|
|
public:
|
|
Polyhedron& polyhedron; /**< Source triangulated surface mesh for modeling */
|
|
|
|
private:
|
|
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)
|
|
|
|
VertexIndexMap vertex_index_map; // storing indices of ros vertices
|
|
EdgeIndexMap edge_index_map; // storing indices of ros related edges
|
|
|
|
std::vector<vertex_descriptor> ros; // region of solution, including roi and hard constraints on boundary of roi
|
|
std::vector<bool> is_roi; // (size: ros)
|
|
std::vector<bool> is_hdl; // (size: ros)
|
|
|
|
std::vector<double> edge_weight; // weight of edges only those who are incident to ros
|
|
std::vector<Eigen::Matrix3d> rot_mtr; // rotation matrices of ros vertices (size: ros)
|
|
|
|
SparseLinearAlgebraTraits_d m_solver; // linear sparse solver
|
|
unsigned int iterations; // number of maximal iterations
|
|
double tolerance; // tolerance of convergence
|
|
|
|
bool need_preprocess; // is there any need to call preprocess() function
|
|
Handle_group_container handle_groups; // user specified handles
|
|
|
|
WeightCalculator weight_calculator; // calculate weight for an edge
|
|
|
|
// Public methods
|
|
public:
|
|
/**
|
|
* The constructor for deformation object
|
|
*
|
|
* @pre @a polyhedron.is_pure_triangle()
|
|
* @param polyhedron a triangulated surface mesh for modeling
|
|
* @param vertex_index_map_ vertex index map for associating ids with region of interest vertices
|
|
* @param edge_index_map_ edge index map for associating ids with region of interest edges
|
|
* @param iterations number of iterations for each call to deform()
|
|
* @param tolerance ...
|
|
*/
|
|
Deform_mesh(Polyhedron& polyhedron,
|
|
VertexIndexMap vertex_index_map,
|
|
EdgeIndexMap edge_index_map,
|
|
unsigned int iterations = 5,
|
|
double tolerance = 1e-4)
|
|
: polyhedron(polyhedron), vertex_index_map(vertex_index_map), edge_index_map(edge_index_map),
|
|
iterations(iterations), tolerance(tolerance), need_preprocess(true), weight_calculator(polyhedron)
|
|
{
|
|
CGAL_precondition(polyhedron.is_pure_triangle());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////// Vertex insertion deletion //////////////////////////////////
|
|
|
|
/**
|
|
* Clear the internal state of the object, after cleanup the object can be treated as if it is just constructed
|
|
*/
|
|
void clear()
|
|
{
|
|
need_preprocess = true;
|
|
// clear vertices
|
|
//roi.clear();
|
|
ros.clear();
|
|
handle_groups.clear();
|
|
// no need to clear vertex index map (or edge) since they are going to be reassigned
|
|
// (at least the useful parts will be reassigned)
|
|
}
|
|
|
|
/**
|
|
* Create a new empty handle group for inserting handles.
|
|
* insert_handle(Handle_group handle_group, vertex_descriptor vd) or insert_handle(Handle_group handle_group, InputIterator begin, InputIterator end)
|
|
* can be used for populating a group.
|
|
* After inserting vertices, one can use translate(Handle_group handle_group, const Vector& translation) or rotate(...)
|
|
* to apply transformations on all vertices inside the group.
|
|
* @return created handle group representative (returned representative is valid until erase_handle(Handle_group handle_group) is called [or copy constructor what to do about it?])
|
|
* @see insert_handle(vertex_descriptor vd), insert_handle(InputIterator begin, InputIterator end)
|
|
*/
|
|
Handle_group create_handle_group()
|
|
{
|
|
need_preprocess = true;
|
|
handle_groups.push_back(Handle_container());
|
|
return --handle_groups.end();
|
|
}
|
|
|
|
/**
|
|
* -- I think this function can be removed, since combination of other functions simply accomplish the same task.
|
|
\code
|
|
Handle_group handle_group = create_handle_group();
|
|
insert_handle(handle_group, vd);
|
|
// or
|
|
Handle_group handle_group = insert_handle(vd);
|
|
\endcode
|
|
* Create a new empty handle group and insert vd in it.
|
|
* @param vd vertex to be inserted
|
|
* @return created handle group representative
|
|
* @see insert_handle(Handle_group handle_group, vertex_descriptor vd),
|
|
* insert_handle(Handle_group handle_group, InputIterator begin, InputIterator end)
|
|
* for inserting more vertices into a handle group
|
|
*/
|
|
Handle_group insert_handle(vertex_descriptor vd)
|
|
{
|
|
need_preprocess = true;
|
|
Handle_group handle_group = create_handle_group();
|
|
|
|
insert_handle(handle_group, vd);
|
|
return handle_group;
|
|
}
|
|
|
|
/**
|
|
* Insert a vertex into a handle group
|
|
* @param handle_group group to be inserted into
|
|
* @param vd vertex to be inserted
|
|
*/
|
|
void insert_handle(Handle_group handle_group, vertex_descriptor vd)
|
|
{
|
|
need_preprocess = true;
|
|
handle_group->push_back(vd);
|
|
}
|
|
|
|
/**
|
|
* -- I think this function can be removed, since combination of other functions simply accomplish the same task.
|
|
\code
|
|
Handle_group handle_group = create_handle_group();
|
|
insert_handle(handle_group, begin, end);
|
|
// or
|
|
Handle_group handle_group = insert_handle(begin, end);
|
|
\endcode
|
|
* Create a new handle group and insert vertices in the range.
|
|
|
|
* @tparam InputIterator input iterator type which points to vertex descriptors
|
|
* @param begin iterators spesifying the range of vertices i.e. [begin, end)
|
|
* @param end iterators spesifying the range of vertices i.e. [begin, end)
|
|
* It simply corresponds to:
|
|
*/
|
|
template<class InputIterator>
|
|
Handle_group insert_handle(InputIterator begin, InputIterator end)
|
|
{
|
|
need_preprocess = true;
|
|
Handle_group handle_group = create_handle_group();
|
|
|
|
insert_handle(handle_group, begin, end);
|
|
return handle_group;
|
|
}
|
|
|
|
/**
|
|
* Insert vertices in the range to provided handle group
|
|
* @tparam InputIterator input iterator type which points to vertex descriptors
|
|
* @param handle_group group to be inserted in
|
|
* @param begin iterators spesifying the range of vertices [begin, end)
|
|
* @param end iterators spesifying the range of vertices [begin, end)
|
|
*/
|
|
template<class InputIterator>
|
|
void insert_handle(Handle_group handle_group, InputIterator begin, InputIterator end)
|
|
{
|
|
need_preprocess = true;
|
|
for( ;begin != end; ++begin)
|
|
{
|
|
insert_handle(handle_group, *begin);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Erase handle group, and invalidate the representative so that it should not be used anymore.
|
|
* @param handle_group group to be erased
|
|
*/
|
|
void erase_handle(Handle_group handle_group)
|
|
{
|
|
need_preprocess = true;
|
|
handle_groups.erase(handle_group);
|
|
}
|
|
|
|
/**
|
|
* Erase a vertex from a handle group, note that handle group is not erased even if it becomes empty.
|
|
* @param handle_group group to be erased from
|
|
* @param vd vertex to be erased
|
|
*/
|
|
void erase_handle(Handle_group handle_group, vertex_descriptor vd)
|
|
{
|
|
need_preprocess = true;
|
|
typename Handle_container::iterator it
|
|
= std::find(handle_group->begin(), handle_group->end(), vd);
|
|
if(vd != handle_group->end())
|
|
{
|
|
handle_group->erase(it);
|
|
// Although the handle group might get empty, we do not delete it from handle_groups
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Insert vertices in the range to region of interest
|
|
* @tparam InputIterator input iterator type which points to vertex descriptors
|
|
* @param begin iterators spesifying the range of vertices [begin, end)
|
|
* @param end iterators spesifying the range of vertices [begin, end)
|
|
*/
|
|
template<class InputIterator>
|
|
void insert_roi(InputIterator begin, InputIterator end)
|
|
{
|
|
need_preprocess = true;
|
|
for( ;begin != end; ++begin)
|
|
{
|
|
insert_roi(*begin);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Insert a vertex to region of interest
|
|
* @param vd vertex to be inserted
|
|
*/
|
|
void insert_roi(vertex_descriptor vd)
|
|
{
|
|
need_preprocess = true;
|
|
ros.push_back(vd);
|
|
}
|
|
|
|
/**
|
|
* Erease a vertex from ROI
|
|
* @param vd vertex to be erased
|
|
*/
|
|
void erase_roi(vertex_descriptor vd)
|
|
{
|
|
need_preprocess = true;
|
|
typename std::vector<vertex_descriptor>::iterator it = std::find(ros.begin(), ros.end(), vd);
|
|
if(vd != ros.end())
|
|
{
|
|
ros.erase(it);
|
|
}
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////// Other utilities ///////////////////////////////////////////
|
|
|
|
/**
|
|
* Set the number of iterations used in deform()
|
|
*/
|
|
void set_iterations(unsigned int iterations)
|
|
{
|
|
this->iterations = iterations;
|
|
}
|
|
|
|
/**
|
|
* Set the tolerance of convergence used in deform()
|
|
* Set to zero if energy based termination is not required.
|
|
*/
|
|
void set_tolerance(double tolerance)
|
|
{
|
|
this->tolerance = tolerance;
|
|
}
|
|
|
|
/**
|
|
* Translate the handle group by translation,
|
|
* in other words every handle vertex in the handle_group is translated from its original position
|
|
* @param handle_group representative of the handle group which is subject to translation
|
|
* @param translation translation vector
|
|
*/
|
|
void translate(Handle_group handle_group, const Vector& translation)
|
|
{
|
|
for(typename Handle_container::iterator it = handle_group->begin();
|
|
it != handle_group->end(); ++it)
|
|
{
|
|
std::size_t v_id = id(*it);
|
|
solution[v_id] = solution[v_id] + translation;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rotate the handle group around rotation center by quaternion then translate it by translation
|
|
* @tparam Quaternion quaternion type which defines a multiplication operator with Vect as quad * vector
|
|
* @tparam Vect vector type 3 param constructable and has operator[] ...
|
|
*/
|
|
template <typename Quaternion, typename Vect>
|
|
void rotate(Handle_group handle_group, const Point& rotation_center, const Quaternion& quat, const Vect& translation)
|
|
{
|
|
for(typename Handle_container::iterator it = handle_group->begin();
|
|
it != handle_group->end(); ++it)
|
|
{
|
|
std::size_t v_id = 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(translation[0],translation[1],translation[2]);
|
|
|
|
solution[v_id] = p;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assign positions in the range as target positions for the vertices in the handle group
|
|
* @tparam InputIterator input iterator type which points to Polyhedron::Traits::Point_3
|
|
* @param handle_group group of target vertices
|
|
* @param begin iterators spesifying the range of positions [begin, end)
|
|
* @param end iterators spesifying the range of positions [begin, end)
|
|
*/
|
|
template<class InputIterator>
|
|
void assign(Handle_group handle_group, InputIterator begin, InputIterator end)
|
|
{
|
|
for(typename Handle_container::iterator it = handle_group->begin();
|
|
(it != handle_group->end()) && (begin != end);
|
|
++it, ++begin)
|
|
{
|
|
solution[id(*it)] = *begin;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assign the target position for the handle vertes
|
|
* @param vd handle vertex to be assigned target position
|
|
* @param target_position constrained position
|
|
*/
|
|
void assign(vertex_descriptor vd, const Point& target_position)
|
|
{
|
|
solution[id(vd)] = target_position;
|
|
}
|
|
/**
|
|
* Reset position of deformed vertices to their original positions (i.e. positions at the time of last preprocess() call)
|
|
*/
|
|
void undo()
|
|
{
|
|
for(std::size_t i = 0; i < ros.size(); ++i)
|
|
{
|
|
ros[i]->point() = original[id(ros[i])];
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////// Deformation Core ///////////////////////////////////////////
|
|
|
|
/**
|
|
* Necessary precomputation work before beginning deformation.
|
|
* It needs to be called after insertion of vertices as handles or roi is done.
|
|
* @return true if Laplacian matrix factorization is successful
|
|
*/
|
|
bool preprocess()
|
|
{
|
|
need_preprocess = false;
|
|
|
|
region_of_solution();
|
|
compute_edge_weight(); // compute_edge_weight() has to come later then region_of_solution()
|
|
|
|
// Assemble linear system A*X=B
|
|
typename SparseLinearAlgebraTraits_d::Matrix A(ros.size()); // matrix is definite positive, and not necessarily symmetric
|
|
assemble_laplacian(A);
|
|
|
|
// Pre-factorizing the linear system A*X=B
|
|
double D;
|
|
return m_solver.pre_factor(A, D);
|
|
}
|
|
|
|
/**
|
|
* Deformation on roi vertices. Default iteration and tolerance values are used.
|
|
* @see set_iterations(unsigned int iterations), set_tolerance(double tolerance), deform(unsigned int iterations, double tolerance)
|
|
*/
|
|
void deform()
|
|
{
|
|
deform(iterations, tolerance);
|
|
}
|
|
|
|
/**
|
|
* Deformation on roi vertices,
|
|
*/
|
|
void deform(unsigned int iterations, double tolerance)
|
|
{
|
|
CGAL_precondition(!need_preprocess || !"preprocess() need to be called before deforming!");
|
|
|
|
// 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();
|
|
if(energy_this < 0)
|
|
{
|
|
std::cout << "Negative energy" << std::endl;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
private:
|
|
|
|
/// Compute cotangent weights of all edges
|
|
void compute_edge_weight()
|
|
{
|
|
#if defined(CGAL_DEFORM_SPOKES_AND_RIMS)
|
|
compute_edge_weight_spokes_and_rims();
|
|
#else
|
|
compute_edge_weight_arap();
|
|
#endif
|
|
}
|
|
void compute_edge_weight_arap()
|
|
{
|
|
std::set<edge_descriptor> have_id; // edges which has assigned ids (and also weights are calculated)
|
|
|
|
// iterate over ros vertices and calculate weights for edges which are incident to ros
|
|
std::size_t next_edge_id = 0;
|
|
for (std::size_t i = 0; i < ros.size(); i++)
|
|
{
|
|
vertex_descriptor vi = ros[i];
|
|
in_edge_iterator e, e_end;
|
|
for (boost::tie(e,e_end) = boost::in_edges(vi, polyhedron); e != e_end; e++)
|
|
{
|
|
typename std::set<edge_descriptor>::iterator it = have_id.find(*e);
|
|
if(it != have_id.end()) { continue; } // we have assigned an id already, which means we also calculted the weight
|
|
|
|
put(edge_index_map, *e, next_edge_id++);
|
|
have_id.insert(*e);
|
|
|
|
double weight = weight_calculator(*e);
|
|
edge_weight.push_back(weight);
|
|
}// end of edge loop
|
|
}// end of ros loop
|
|
}
|
|
void compute_edge_weight_spokes_and_rims()
|
|
{
|
|
std::set<edge_descriptor> have_id; // edges which has assigned ids (and also weights are calculated)
|
|
|
|
// iterate over ros vertices and calculate weights for edges which are incident to ros
|
|
std::size_t next_edge_id = 0;
|
|
for (std::size_t i = 0; i < ros.size(); i++)
|
|
{
|
|
vertex_descriptor vi = ros[i];
|
|
out_edge_iterator e_begin, e_end;
|
|
boost::tie(e_begin, e_end) = boost::out_edges(vi, polyhedron);
|
|
|
|
for (Rims_iterator rims_it(e_begin, polyhedron); rims_it.get_iterator() != e_end; ++rims_it )
|
|
{
|
|
edge_descriptor active_edge = rims_it.get_descriptor();
|
|
|
|
typename std::set<edge_descriptor>::iterator it = have_id.find(active_edge);
|
|
if(it == have_id.end()) // we have not assigned an id yet
|
|
{
|
|
put(edge_index_map, active_edge, next_edge_id++);
|
|
have_id.insert(active_edge);
|
|
double wji = weight_calculator(active_edge); // edge(pj - pi)
|
|
edge_weight.push_back(wji);
|
|
|
|
edge_descriptor opp = CGAL::opposite_edge(active_edge, polyhedron);
|
|
|
|
put(edge_index_map, opp, next_edge_id++);
|
|
have_id.insert(opp);
|
|
double wij = weight_calculator(opp); // edge(pi - pj)
|
|
edge_weight.push_back(wij);
|
|
}
|
|
}// end of edge loop
|
|
}// end of ros loop
|
|
}
|
|
|
|
/// Assigns id to one rign neighbor of vd, and also push them into push_vector
|
|
void assign_id_to_one_ring(vertex_descriptor vd,
|
|
std::size_t& next_id,
|
|
std::vector<vertex_descriptor>& push_vector,
|
|
std::set<vertex_descriptor>& have_id)
|
|
{
|
|
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);
|
|
typename std::set<vertex_descriptor>::iterator it = have_id.find(vt);
|
|
if( it == have_id.end() ) // neighboring vertex which is outside of roi and not visited previously (i.e. need an id)
|
|
{
|
|
put(vertex_index_map, vt, next_id++);
|
|
have_id.insert(vt);
|
|
push_vector.push_back(vt);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Find region of solution, including roi and hard constraints, which is the 1-ring vertices out roi
|
|
void region_of_solution()
|
|
{
|
|
// Important: at this point ros contains the roi vertices only.
|
|
// copy roi vertices to roi vector
|
|
std::vector<vertex_descriptor> roi; // we can remove this temp, but I try to simplify things below
|
|
roi.insert(roi.end(), ros.begin(), ros.end());
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// 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
|
|
|
|
std::set<vertex_descriptor> have_id; // keep vertices which are assigned an id
|
|
|
|
for(std::size_t i = 0; i < roi.size(); i++) // assign id to all roi vertices
|
|
{
|
|
put(vertex_index_map, roi[i], i);
|
|
}
|
|
|
|
have_id.insert(roi.begin(), roi.end()); // mark roi vertices since they have ids now
|
|
|
|
// 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_id_to_one_ring(roi[i], next_ros_index, ros, have_id);
|
|
}
|
|
|
|
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_id_to_one_ring(ros[i], next_ros_index, outside_ros, have_id);
|
|
}
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// initialize the rotation matrices (size: ros)
|
|
rot_mtr.resize(ros.size());
|
|
for(std::size_t i = 0; i < rot_mtr.size(); i++)
|
|
{
|
|
rot_mtr[i].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_id = id(ros[i]);
|
|
solution[v_id] = ros[i]->point();
|
|
original[v_id] = ros[i]->point();
|
|
}
|
|
for(std::size_t i = 0; i < outside_ros.size(); ++i)
|
|
{
|
|
std::size_t v_id = id(outside_ros[i]);
|
|
original[v_id] = outside_ros[i]->point();
|
|
solution[v_id] = outside_ros[i]->point();
|
|
}
|
|
|
|
// initialize flag vectors of roi, handle, ros
|
|
is_roi.assign(ros.size(), false);
|
|
is_hdl.assign(ros.size(), false);
|
|
for(std::size_t i = 0; i < roi.size(); i++)
|
|
{
|
|
std::size_t v_id = id(roi[i]);
|
|
is_roi[v_id] = true;
|
|
}
|
|
for(typename Handle_group_container::iterator it_group = handle_groups.begin();
|
|
it_group != handle_groups.end(); ++it_group)
|
|
{
|
|
for(typename Handle_container::iterator it_vertex = it_group->begin();
|
|
it_vertex != it_group->end(); ++it_vertex)
|
|
{
|
|
std::size_t v_id = id(*it_vertex);
|
|
is_hdl[v_id] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Assemble Laplacian matrix A of linear system A*X=B
|
|
void assemble_laplacian(typename SparseLinearAlgebraTraits_d::Matrix& A)
|
|
{
|
|
#if defined(CGAL_DEFORM_SPOKES_AND_RIMS)
|
|
assemble_laplacian_spokes_and_rims(A);
|
|
#else
|
|
assemble_laplacian_arap(A);
|
|
#endif
|
|
}
|
|
/// 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_arap(typename SparseLinearAlgebraTraits_d::Matrix& A)
|
|
{
|
|
/// 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 = id(vi);
|
|
if ( is_roi[vi_id] && !is_hdl[vi_id] ) // 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, 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);
|
|
}
|
|
}
|
|
}
|
|
/// 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_spokes_and_rims(typename SparseLinearAlgebraTraits_d::Matrix& A)
|
|
{
|
|
/// 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 = id(vi);
|
|
if ( is_roi[vi_id] && !is_hdl[vi_id] ) // 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, 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Local step of iterations, computing optimal rotation matrices using SVD decomposition, stable
|
|
void optimal_rotations_svd()
|
|
{
|
|
#if defined(CGAL_DEFORM_SPOKES_AND_RIMS)
|
|
optimal_rotations_svd_spokes_and_rims();
|
|
#else
|
|
optimal_rotations_svd_arap();
|
|
#endif
|
|
}
|
|
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 = 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 = 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 decresing 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 = 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 = id(v1); std::size_t v2_id = 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 decresing 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 defined(CGAL_DEFORM_SPOKES_AND_RIMS)
|
|
update_solution_spokes_and_rims();
|
|
#else
|
|
update_solution_arap();
|
|
#endif
|
|
}
|
|
void update_solution_arap()
|
|
{
|
|
typename SparseLinearAlgebraTraits_d::Vector X(ros.size()), Bx(ros.size());
|
|
typename SparseLinearAlgebraTraits_d::Vector Y(ros.size()), By(ros.size());
|
|
typename SparseLinearAlgebraTraits_d::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 = id(vi);
|
|
|
|
if ( is_roi[vi_id] && !is_hdl[vi_id] )
|
|
{// 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 = 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".
|
|
m_solver.linear_solver(Bx, X); m_solver.linear_solver(By, Y); m_solver.linear_solver(Bz, Z);
|
|
|
|
// copy to solution
|
|
for (std::size_t i = 0; i < ros.size(); i++)
|
|
{
|
|
std::size_t v_id = id(ros[i]);
|
|
Point p(X[v_id], Y[v_id], Z[v_id]);
|
|
solution[v_id] = p;
|
|
}
|
|
}
|
|
void update_solution_spokes_and_rims()
|
|
{
|
|
typename SparseLinearAlgebraTraits_d::Vector X(ros.size()), Bx(ros.size());
|
|
typename SparseLinearAlgebraTraits_d::Vector Y(ros.size()), By(ros.size());
|
|
typename SparseLinearAlgebraTraits_d::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 = id(vi);
|
|
|
|
if ( is_roi[vi_id] && !is_hdl[vi_id] )
|
|
{// 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 = 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[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[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".
|
|
m_solver.linear_solver(Bx, X); m_solver.linear_solver(By, Y); m_solver.linear_solver(Bz, Z);
|
|
|
|
// copy to solution
|
|
for (std::size_t i = 0; i < ros.size(); i++)
|
|
{
|
|
std::size_t v_id = 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 = id(ros[i]);
|
|
if(is_roi[v_id])
|
|
{
|
|
ros[i]->point() = solution[v_id];
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Compute modeling energy
|
|
double energy()
|
|
{
|
|
#ifdef CGAL_DEFORM_SPOKES_AND_RIMS
|
|
return energy_spokes_and_rims();
|
|
#else
|
|
return energy_arap();
|
|
#endif
|
|
}
|
|
double energy_arap()
|
|
{
|
|
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 = 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 = 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()
|
|
{
|
|
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 = 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 = id(v1); std::size_t v2_id = 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;
|
|
}
|
|
|
|
/// p1 - p2, return Eigen Column Vector
|
|
Eigen::Vector3d sub_to_col(const Point& p1, const Point& p2)
|
|
{
|
|
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)
|
|
{
|
|
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 v)
|
|
{
|
|
return get(vertex_index_map, v);
|
|
}
|
|
/// shorthand of get(edge_index_map, e)
|
|
std::size_t id(edge_descriptor e)
|
|
{
|
|
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 decomposiiton 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 decomposiiton 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
|
|
|