// 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 #include #include #include #include #include #include #include #include /* #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 // for sparse linear system solver #include // 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 struct Weight_calculator_selector { typedef Uniform_weight weight_calculator; }; template struct Weight_calculator_selector { typedef Single_cotangent_weight weight_calculator; }; template struct Weight_calculator_selector { typedef Cotangent_weight 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` with Deform_mesh::vertex_descriptor as key and `unsigned int` as value type. /// The default is `boost::property_map::%type`. /// @tparam HIM a model of `ReadablePropertyMap` with Deform_mesh::halfedge_descriptor as key and `unsigned int` as value type. /// The default is `boost::property_map::%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::EigenType, /// Eigen::COLAMDOrdering > > /// \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` 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::%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::type >::type Vertex_index_map; typedef typename Default::Get< HIM, typename boost::property_map::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::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::EigenType, Eigen::COLAMDOrdering > > #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::type >::type Vertex_point_map; #else /// typedef VPM Vertex_point_map; #endif /// The type for vertex descriptor typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; /// The type for halfedge descriptor typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; /// The 3D point type, model of `::RawPoint_3` typedef typename boost::property_traits::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 Roi_vertex_range; /// @} private: typedef Deform_mesh Self; // Repeat Halfedge_graph types typedef typename boost::graph_traits::vertex_iterator vertex_iterator; typedef typename boost::graph_traits::halfedge_iterator halfedge_iterator; typedef typename boost::graph_traits::in_edge_iterator in_edge_iterator; typedef typename boost::graph_traits::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 original; ///< original positions of roi (size: ros + boundary_of_ros) std::vector 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 roi; ///< region of interest std::vector ros; ///< region of solution, including roi and hard constraints on boundary of roi std::vector ros_id_map; ///< (size: num vertices) std::vector is_roi_map; ///< (size: num vertices) std::vector is_ctrl_map; ///< (size: num vertices) std::vector hedge_weight; ///< all halfedge weights std::vector 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 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(num_vertices(halfedge_graph), (std::numeric_limits::max)() )), is_roi_map(std::vector(num_vertices(halfedge_graph), false)), is_ctrl_map(std::vector(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(num_vertices(halfedge_graph), (std::numeric_limits::max)() )), is_roi_map(std::vector(num_vertices(halfedge_graph), false)), is_ctrl_map(std::vector(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(num_vertices(halfedge_graph), (std::numeric_limits::max)() )), is_roi_map(std::vector(num_vertices(halfedge_graph), false)), is_ctrl_map(std::vector(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(num_vertices(halfedge_graph), (std::numeric_limits::max)() )), is_roi_map(std::vector(num_vertices(halfedge_graph), false)), is_ctrl_map(std::vector(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 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 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::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 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 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 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 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 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& 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::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 old_ros_id_map = ros_id_map; std::vector old_rot_mtr = rot_mtr; std::vector old_solution = solution; std::vector 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::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::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 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::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::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 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 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