mirror of https://github.com/CGAL/cgal
1406 lines
53 KiB
C++
1406 lines
53 KiB
C++
// Copyright (c) 2016 GeometryFactory (France).
|
|
// 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) :
|
|
|
|
#ifndef CGAL_ARAP_PARAMETERIZER_3_H
|
|
#define CGAL_ARAP_PARAMETERIZER_3_H
|
|
|
|
#include <CGAL/internal/Surface_mesh_parameterization/Containers_filler.h>
|
|
#include <CGAL/internal/Surface_mesh_parameterization/validity.h>
|
|
#include <CGAL/IO/Surface_mesh_parameterization/File_off.h>
|
|
|
|
#include <CGAL/Mean_value_coordinates_parameterizer_3.h>
|
|
#include <CGAL/MVC_post_processor_3.h>
|
|
#include <CGAL/LSCM_parameterizer_3.h>
|
|
#include <CGAL/Two_vertices_parameterizer_3.h>
|
|
|
|
#include <CGAL/Parameterizer_traits_3.h>
|
|
#include <CGAL/parameterize.h>
|
|
|
|
#include <CGAL/Algebraic_kernel_d_2.h>
|
|
#include <CGAL/Kernel/Conic_misc.h> // @tmp used for solving conic equations
|
|
#include <CGAL/Eigen_solver_traits.h>
|
|
#include <CGAL/OpenNL/linear_solver.h>
|
|
#include <CGAL/Polygon_mesh_processing/connected_components.h>
|
|
|
|
#include <CGAL/assertions.h>
|
|
#include <CGAL/circulator.h>
|
|
#include <CGAL/Timer.h>
|
|
|
|
#include <boost/foreach.hpp>
|
|
#include <boost/function_output_iterator.hpp>
|
|
#include <boost/functional/hash.hpp>
|
|
#include <boost/unordered_set.hpp>
|
|
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <limits>
|
|
#include <set>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
/// \file ARAP_parameterizer_3.h
|
|
|
|
// @todo Determine the proper name of this file
|
|
// @todo Handle the case cot = 0 with a local parameterization aligned with the axes
|
|
// (this produces C2=0 which is problematic to compute a & b)
|
|
// @todo Add to the polyhedron demo
|
|
// @todo Add distortion measures
|
|
// @todo is_one_to_one mapping functions in all parameterizers
|
|
|
|
// @todo The two systems A Xu = Bu and A Xv = BV could be merged in one system
|
|
// using complex numbers?
|
|
// @todo Parallelize the local phase?
|
|
|
|
namespace CGAL {
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
// Declaration
|
|
// ------------------------------------------------------------------------------------
|
|
|
|
/// \ingroup PkgSurfaceParameterizationMethods
|
|
///
|
|
/// The class `ARAP_parameterizer_3` implements the
|
|
/// *Local/Global Approach to Mesh Parameterization* \cgalCite{liu2008local}.
|
|
///
|
|
/// This parameterization allows the user to choose to give importance to angle preservation,
|
|
/// to shape preservation, or a balance of both.
|
|
/// A parameter λ is used to control whether the priority is given to angle
|
|
/// or to shape preservation: when λ = 0, the parameterization is
|
|
/// as-similar-as-possible (ASAP) and is equivalent to the (conforming) LSCM parameterization.
|
|
/// As λ grows, the shape preservation becomes more and more important,
|
|
/// yielding, when λ goes to infinity, a parameterization that is as-rigid-as-possible (ARAP).
|
|
///
|
|
/// This is a free border parameterization. There is no need to map the border of the surface
|
|
/// onto a convex polygon.
|
|
/// When λ = 0, only two pinned vertices are needed to ensure a unique solution.
|
|
/// When λ is non-null, the border does not need to be parameterized and
|
|
/// a random vertex is pinned.
|
|
///
|
|
/// If flips are present in the parameterization, a post-processing step is applied
|
|
/// using `CGAL::MVC_post_processor_3<TriangleMesh, SparseLinearAlgebraTraits_d>`
|
|
/// to attempt to obtain a valid final embedding.
|
|
///
|
|
/// A one-to-one mapping is *not* guaranteed.
|
|
///
|
|
/// \cgalModels `ParameterizerTraits_3`
|
|
///
|
|
/// \tparam TriangleMesh must be a model of `FaceGraph`.
|
|
/// \tparam BorderParameterizer_3 is a Strategy to parameterize the surface border.
|
|
/// \tparam SparseLinearAlgebraTraits_d is a Traits class to solve a sparse linear system. <br>
|
|
/// Note: the system is *not* symmetric.
|
|
///
|
|
/// \sa `CGAL::Parameterizer_traits_3<TriangleMesh>`
|
|
/// \sa `CGAL::Fixed_border_parameterizer_3<TriangleMesh, BorderParameterizer_3, SparseLinearAlgebraTraits_d>`
|
|
/// \sa `CGAL::Barycentric_mapping_parameterizer_3<TriangleMesh, BorderParameterizer_3, SparseLinearAlgebraTraits_d>`
|
|
/// \sa `CGAL::Discrete_authalic_parameterizer_3<TriangleMesh, BorderParameterizer_3, SparseLinearAlgebraTraits_d>`
|
|
/// \sa `CGAL::Discrete_conformal_map_parameterizer_3<TriangleMesh, BorderParameterizer_3, SparseLinearAlgebraTraits_d>`
|
|
/// \sa `CGAL::LSCM_parameterizer_3<TriangleMesh, BorderParameterizer_3>`
|
|
/// \sa `CGAL::Mean_value_coordinates_parameterizer_3<TriangleMesh, BorderParameterizer_3, SparseLinearAlgebraTraits_d>`
|
|
|
|
template
|
|
<
|
|
class TriangleMesh,
|
|
class BorderParameterizer_3
|
|
= Two_vertices_parameterizer_3<TriangleMesh>,
|
|
class SparseLinearAlgebraTraits_d
|
|
= Eigen_solver_traits< > // defaults to Eigen::BICGSTAB with Eigen_sparse_matrix
|
|
>
|
|
class ARAP_parameterizer_3
|
|
: public Parameterizer_traits_3<TriangleMesh>
|
|
{
|
|
// Private types
|
|
private:
|
|
// This class
|
|
typedef ARAP_parameterizer_3<TriangleMesh,
|
|
BorderParameterizer_3,
|
|
SparseLinearAlgebraTraits_d> Self;
|
|
|
|
// Superclass
|
|
typedef Parameterizer_traits_3<TriangleMesh> Base;
|
|
|
|
// Public types
|
|
public:
|
|
// We have to repeat the types exported by superclass
|
|
/// @cond SKIP_IN_MANUAL
|
|
typedef typename Base::Error_code Error_code;
|
|
/// @endcond
|
|
|
|
/// Export BorderParameterizer_3 template parameter.
|
|
typedef BorderParameterizer_3 Border_param;
|
|
|
|
// Private types
|
|
private:
|
|
typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor vertex_descriptor;
|
|
typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
|
|
typedef typename boost::graph_traits<TriangleMesh>::face_descriptor face_descriptor;
|
|
typedef typename boost::graph_traits<TriangleMesh>::face_iterator face_iterator;
|
|
typedef typename boost::graph_traits<TriangleMesh>::vertex_iterator vertex_iterator;
|
|
|
|
typedef CGAL::Halfedge_around_target_circulator<TriangleMesh> halfedge_around_target_circulator;
|
|
typedef CGAL::Halfedge_around_face_circulator<TriangleMesh> halfedge_around_face_circulator;
|
|
|
|
typedef boost::unordered_set<vertex_descriptor> Vertex_set;
|
|
typedef std::vector<face_descriptor> Faces_vector;
|
|
|
|
// Traits subtypes:
|
|
typedef Parameterizer_traits_3<TriangleMesh> Traits;
|
|
typedef typename Traits::NT NT;
|
|
typedef typename Traits::Point_2 Point_2;
|
|
typedef typename Traits::Point_3 Point_3;
|
|
typedef typename Traits::Vector_2 Vector_2;
|
|
typedef typename Traits::Vector_3 Vector_3;
|
|
|
|
// SparseLinearAlgebraTraits_d subtypes:
|
|
typedef SparseLinearAlgebraTraits_d Sparse_LA;
|
|
|
|
typedef typename Sparse_LA::Vector Vector;
|
|
typedef typename Sparse_LA::Matrix Matrix;
|
|
|
|
// Memory maps
|
|
// Each triangle is associated a linear transformation matrix
|
|
typedef std::pair<NT, NT> Lt_matrix;
|
|
typedef CGAL::Unique_hash_map<face_descriptor,
|
|
Lt_matrix,
|
|
boost::hash<face_descriptor> > Lt_hash_map;
|
|
typedef boost::associative_property_map<Lt_hash_map> Lt_map;
|
|
|
|
// Each angle (uniquely determined by the opposite half edge) has a cotangent
|
|
typedef CGAL::Unique_hash_map<halfedge_descriptor, NT,
|
|
boost::hash<halfedge_descriptor> > Cot_hm;
|
|
typedef boost::associative_property_map<Cot_hm> Cot_map;
|
|
|
|
// Each face has a local 2D isometric parameterization
|
|
typedef std::pair<int, int> Local_indices;
|
|
typedef CGAL::Unique_hash_map<halfedge_descriptor,
|
|
Local_indices,
|
|
boost::hash<halfedge_descriptor> > Lp_hm;
|
|
typedef boost::associative_property_map<Lp_hm> Lp_map;
|
|
typedef std::vector<Point_2> Local_points;
|
|
|
|
// Angle computations
|
|
using Base::cotangent;
|
|
|
|
// Private fields
|
|
private:
|
|
/// %Object that maps (at least two) border vertices onto a 2D space
|
|
Border_param m_borderParameterizer;
|
|
|
|
/// Traits object to solve a sparse linear system
|
|
Sparse_LA m_linearAlgebra;
|
|
|
|
/// Controlling parameters
|
|
const NT m_lambda;
|
|
const NT m_lambda_tolerance; // used to determine when we switch to full ARAP
|
|
|
|
/// Energy minimization parameters
|
|
const unsigned int m_iterations;
|
|
const NT m_tolerance;
|
|
|
|
// Private accessors
|
|
private:
|
|
/// Get the object that maps the surface's border onto a 2D space.
|
|
Border_param& get_border_parameterizer() { return m_borderParameterizer; }
|
|
|
|
/// Get the sparse linear algebra (traits object to access the linear system).
|
|
Sparse_LA& get_linear_algebra_traits() { return m_linearAlgebra; }
|
|
|
|
// Private utilities
|
|
private:
|
|
/// Print the parameterized mesh.
|
|
template <typename VertexUVMap,
|
|
typename VertexIndexMap>
|
|
void output_uvmap(const std::string filename,
|
|
const TriangleMesh& mesh,
|
|
const Vertex_set& vertices,
|
|
const Faces_vector& faces,
|
|
const VertexUVMap uvmap,
|
|
const VertexIndexMap vimap) const
|
|
{
|
|
std::ofstream out(filename.c_str());
|
|
CGAL::Parameterization::output_uvmap_to_off(mesh, vertices, faces,
|
|
uvmap, vimap, out);
|
|
}
|
|
|
|
/// Print the parameterized mesh.
|
|
template <typename VertexUVMap,
|
|
typename VertexIndexMap>
|
|
void output_uvmap(const std::string filename,
|
|
const unsigned int iter,
|
|
const TriangleMesh& mesh,
|
|
const Vertex_set& vertices,
|
|
const Faces_vector& faces,
|
|
const VertexUVMap uvmap,
|
|
const VertexIndexMap vimap) const
|
|
{
|
|
std::ostringstream out_ss;
|
|
out_ss << filename << iter << ".off" << std::ends;
|
|
std::ofstream out(out_ss.str().c_str());
|
|
CGAL::Parameterization::output_uvmap_to_off(mesh, vertices, faces,
|
|
uvmap, vimap, out);
|
|
}
|
|
|
|
/// Copy the data from two vectors to the UVmap.
|
|
template <typename VertexUVMap,
|
|
typename VertexIndexMap>
|
|
void assign_solution(const Vector& Xu,
|
|
const Vector& Xv,
|
|
const Vertex_set& vertices,
|
|
VertexUVMap uvmap,
|
|
const VertexIndexMap vimap)
|
|
{
|
|
BOOST_FOREACH(vertex_descriptor vd, vertices){
|
|
int index = get(vimap, vd);
|
|
NT u = Xu(index);
|
|
NT v = Xv(index);
|
|
put(uvmap, vd, Point_2(u, v));
|
|
}
|
|
}
|
|
|
|
// Private operations
|
|
private:
|
|
/// Store the vertices and faces of the mesh in memory.
|
|
void initialize_containers(const TriangleMesh& mesh,
|
|
halfedge_descriptor bhd,
|
|
Vertex_set& vertices,
|
|
Faces_vector& faces) const
|
|
{
|
|
CGAL::internal::Parameterization::Containers_filler<TriangleMesh>
|
|
fc(mesh, vertices, &faces);
|
|
CGAL::Polygon_mesh_processing::connected_component(
|
|
face(opposite(bhd, mesh), mesh),
|
|
mesh,
|
|
boost::make_function_output_iterator(fc));
|
|
CGAL_postcondition(vertices.size() == num_vertices(mesh) &&
|
|
faces.size() == num_faces(mesh));
|
|
std::cout << vertices.size() << " vertices & " << faces.size() << " faces" << std::endl;
|
|
}
|
|
|
|
/// Initialize the UV values with a first parameterization of the input.
|
|
template <typename VertexUVMap>
|
|
Error_code compute_initial_uv_map(TriangleMesh& mesh,
|
|
halfedge_descriptor bhd,
|
|
VertexUVMap uvmap) const
|
|
{
|
|
Error_code status;
|
|
|
|
unsigned int number_of_borders =
|
|
CGAL::Polygon_mesh_processing::number_of_borders(mesh);
|
|
if(number_of_borders == 0){
|
|
status = Base::ERROR_BORDER_TOO_SHORT;
|
|
return status;
|
|
}
|
|
|
|
// According to the paper, MVC is better for single border and LSCM is better
|
|
// when there are multiple borders
|
|
if(number_of_borders == 1){
|
|
typedef CGAL::Mean_value_coordinates_parameterizer_3<TriangleMesh> MVC_parameterizer;
|
|
status = CGAL::parameterize(mesh, MVC_parameterizer(), bhd, uvmap);
|
|
} else {
|
|
typedef CGAL::LSCM_parameterizer_3<TriangleMesh, Border_param> LSCM_parameterizer;
|
|
status = CGAL::parameterize(mesh, LSCM_parameterizer(), bhd, uvmap);
|
|
}
|
|
|
|
std::cout << "Computed initial parameterization" << std::endl;
|
|
return status;
|
|
}
|
|
|
|
/// Parameterize the border. The number of fixed vertices depends on the value
|
|
/// of the parameter λ.
|
|
template <typename VertexUVMap, typename VertexParameterizedMap>
|
|
Error_code parameterize_border(const TriangleMesh& mesh,
|
|
const Vertex_set& vertices,
|
|
halfedge_descriptor bhd,
|
|
VertexParameterizedMap vpmap)
|
|
{
|
|
Error_code status = Base::OK;
|
|
CGAL_precondition(!vertices.empty());
|
|
|
|
if(m_lambda != 0.){
|
|
// Fix a random vertex, the value in uvmap is already set
|
|
vertex_descriptor vd = *(vertices.begin());
|
|
put(vpmap, vd, true);
|
|
} else {
|
|
// This fixes two vertices that are far in the original geometry
|
|
|
|
// A local uvmap (that is then discarded) is passed to avoid changing
|
|
// the values of the real uvmap. Since we can't get VertexUVMap::C,
|
|
// we build a map with the same key and value type
|
|
typedef boost::unordered_map<typename VertexUVMap::key_type,
|
|
typename VertexUVMap::value_type> Useless_map;
|
|
typedef boost::associative_property_map<Useless_map> Useless_pmap;
|
|
|
|
Useless_map useless_map;
|
|
Useless_pmap useless_uvpmap(useless_map);
|
|
status = get_border_parameterizer().parameterize_border(mesh, bhd,
|
|
useless_uvpmap,
|
|
vpmap);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/// Compute the cotangent of the angle between the vectors ij and ik.
|
|
void compute_cotangent_angle(const TriangleMesh& mesh,
|
|
halfedge_descriptor hd,
|
|
vertex_descriptor vi,
|
|
vertex_descriptor vj,
|
|
vertex_descriptor vk,
|
|
Cot_map ctmap) const
|
|
{
|
|
typedef typename boost::property_map<TriangleMesh,
|
|
boost::vertex_point_t>::const_type PPmap;
|
|
PPmap ppmap = get(vertex_point, mesh);
|
|
|
|
Point_3 position_vi = get(ppmap, vi);
|
|
Point_3 position_vj = get(ppmap, vj);
|
|
Point_3 position_vk = get(ppmap, vk);
|
|
|
|
double cot = cotangent(position_vi, position_vj, position_vk);
|
|
put(ctmap, hd, cot);
|
|
}
|
|
|
|
/// Fill the map 'ctmap' with the cotangents of the angles of the faces of 'mesh'.
|
|
Error_code compute_cotangent_angles(const TriangleMesh& mesh,
|
|
const Faces_vector& faces,
|
|
Cot_map ctmap) const
|
|
{
|
|
BOOST_FOREACH(face_descriptor fd, faces){
|
|
halfedge_descriptor hd = halfedge(fd, mesh), hdb = hd;
|
|
|
|
vertex_descriptor vi = target(hd, mesh);
|
|
hd = next(hd, mesh);
|
|
vertex_descriptor vj = target(hd, mesh);
|
|
hd = next(hd, mesh);
|
|
vertex_descriptor vk = target(hd, mesh);
|
|
hd = next(hd, mesh);
|
|
|
|
if(hd != hdb){ // make sure that it is a triangular face
|
|
return Base::ERROR_NON_TRIANGULAR_MESH;
|
|
}
|
|
|
|
compute_cotangent_angle(mesh, hd, vk, vj, vi , ctmap); // angle at v_j
|
|
compute_cotangent_angle(mesh, next(hd, mesh), vi, vk, vj , ctmap); // angle at v_k
|
|
compute_cotangent_angle(mesh, prev(hd, mesh), vj, vi, vk , ctmap); // angle at v_i
|
|
}
|
|
|
|
return Base::OK;
|
|
}
|
|
|
|
/// Compute w_ij = (i, j) coefficient of matrix A for j neighbor vertex of i.
|
|
NT compute_w_ij(const TriangleMesh& mesh,
|
|
halfedge_descriptor hd,
|
|
const Cot_map ctmap) const
|
|
{
|
|
// Note that BGL halfedge and vertex circulators move clockwise!!
|
|
|
|
// coefficient corresponding to the angle at vk if vk is the vertex before vj
|
|
// while circulating around vi
|
|
NT c_k = get(ctmap, opposite(hd, mesh));
|
|
|
|
// coefficient corresponding to the angle at vl if vl is the vertex after vj
|
|
// while circulating around vi
|
|
NT c_l = get(ctmap, hd);
|
|
|
|
double weight = c_k + c_l;
|
|
return weight;
|
|
}
|
|
|
|
/// Compute the line i of matrix A
|
|
/// - call compute_w_ij() to compute the A coefficient w_ij for each neighbor v_j.
|
|
/// - compute w_ii = - sum of w_ijs.
|
|
///
|
|
/// \pre Vertices must be indexed.
|
|
/// \pre Vertex i musn't be already parameterized.
|
|
/// \pre Line i of A must contain only zeros.
|
|
template <typename VertexIndexMap>
|
|
Error_code fill_linear_system_matrix(Matrix& A,
|
|
const TriangleMesh& mesh,
|
|
vertex_descriptor vertex,
|
|
const Cot_map ct_map,
|
|
VertexIndexMap vimap) const
|
|
{
|
|
int i = get(vimap, vertex);
|
|
|
|
// Circulate over the vertices around 'vertex' to compute w_ii and w_ijs
|
|
NT w_ii = 0;
|
|
int vertexIndex = 0;
|
|
|
|
halfedge_around_target_circulator hc(vertex, mesh), end = hc;
|
|
CGAL_For_all(hc, end){
|
|
halfedge_descriptor hd = *hc;
|
|
CGAL_assertion(target(hd, mesh) == vertex);
|
|
|
|
NT w_ij = -1.0 * compute_w_ij(mesh, hd, ct_map);
|
|
|
|
// w_ii = - sum of w_ijs
|
|
w_ii -= w_ij;
|
|
|
|
// Get j index
|
|
vertex_descriptor v_j = source(hd, mesh);
|
|
int j = get(vimap, v_j);
|
|
|
|
// Set w_ij in matrix
|
|
A.set_coef(i, j, w_ij, true /*new*/);
|
|
vertexIndex++;
|
|
}
|
|
|
|
if (vertexIndex < 2)
|
|
return Base::ERROR_NON_TRIANGULAR_MESH;
|
|
|
|
// Set w_ii in matrix
|
|
A.set_coef(i, i, w_ii, true /*new*/);
|
|
return Base::OK;
|
|
}
|
|
|
|
/// Initialize the (constant) matrix A in the linear system "A*X = B",
|
|
/// after (at least two) border vertices parameterization.
|
|
template <typename VertexIndexMap,
|
|
typename VertexParameterizedMap>
|
|
Error_code initialize_matrix_A(const TriangleMesh& mesh,
|
|
const Vertex_set& vertices,
|
|
const Cot_map ctmap,
|
|
VertexIndexMap vimap,
|
|
VertexParameterizedMap vpmap,
|
|
Matrix& A) const
|
|
{
|
|
Error_code status = Base::OK;
|
|
|
|
// compute A
|
|
unsigned int count = 0;
|
|
BOOST_FOREACH(vertex_descriptor vd, vertices){
|
|
if(!get(vpmap, vd)){ // not yet parameterized
|
|
// Compute the line i of the matrix A
|
|
status = fill_linear_system_matrix(A, mesh, vd, ctmap, vimap);
|
|
if(status != Base::OK)
|
|
return status;
|
|
} else { // fixed vertices
|
|
int index = get(vimap, vd);
|
|
std::cout << index << " is fixed in A " << std::endl;
|
|
A.set_coef(index, index, 1, true /*new*/);
|
|
++count;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/// Solves the cubic equation a3 x^3 + a2 x^2 + a1 x + a0 = 0.
|
|
int solve_cubic_equation(const NT a3, const NT a2, const NT a1, const NT a0,
|
|
std::vector<NT>& roots) const
|
|
{
|
|
CGAL_precondition(roots.empty());
|
|
NT r1 = 0, r2 = 0, r3 = 0; // roots of the cubic equation
|
|
|
|
int root_n = CGAL::solve_cubic(a3, a2, a1, a0, r1, r2, r3);
|
|
CGAL_postcondition(root_n > 0);
|
|
|
|
roots.push_back(r1);
|
|
if(root_n > 1)
|
|
roots.push_back(r2);
|
|
if(root_n > 2)
|
|
roots.push_back(r3);
|
|
|
|
// std::cout << "coeffs: " << a3 << " " << a2 << " " << a1 << " " << a0 << std::endl;
|
|
// std::cout << root_n << " roots: " << r1 << " " << r2 << " " << r3 << std::endl;
|
|
|
|
return roots.size();
|
|
}
|
|
|
|
/// Solves the equation a3 x^3 + a2 x^2 + a1 x + a0 = 0, using CGAL's algeabric kernel.
|
|
int solve_cubic_equation_with_AK(const NT a3, const NT a2,
|
|
const NT a1, const NT a0,
|
|
std::vector<NT>& roots) const
|
|
{
|
|
CGAL_precondition(roots.empty());
|
|
|
|
typedef CGAL::GMP_arithmetic_kernel AK;
|
|
typedef CGAL::Algebraic_kernel_d_1<AK::Rational> Algebraic_kernel_d_1;
|
|
typedef typename Algebraic_kernel_d_1::Polynomial_1 Polynomial_1;
|
|
typedef typename Algebraic_kernel_d_1::Algebraic_real_1 Algebraic_real_1;
|
|
typedef typename Algebraic_kernel_d_1::Multiplicity_type Multiplicity_type;
|
|
typedef typename Algebraic_kernel_d_1::Coefficient Coefficient;
|
|
|
|
typedef CGAL::Polynomial_traits_d<Polynomial_1> Polynomial_traits_1;
|
|
|
|
typedef Algebraic_kernel_d_1::Solve_1 Solve_1;
|
|
|
|
Algebraic_kernel_d_1 ak_1;
|
|
const Solve_1 solve_1 = ak_1.solve_1_object();
|
|
typename Polynomial_traits_1::Construct_polynomial construct_polynomial_1;
|
|
std::pair<CGAL::Exponent_vector, Coefficient> coeffs_x[1]
|
|
= {std::make_pair(CGAL::Exponent_vector(1,0),Coefficient(1))};
|
|
Polynomial_1 x=construct_polynomial_1(coeffs_x, coeffs_x+1);
|
|
|
|
AK::Rational a3q(a3);
|
|
AK::Rational a2q(a2);
|
|
AK::Rational a1q(a1);
|
|
AK::Rational a0q(a0);
|
|
|
|
Polynomial_1 pol = a3q*CGAL::ipower(x,3) + a2q*CGAL::ipower(x,2)
|
|
+ a1q*x + a0q;
|
|
|
|
std::vector<std::pair<Algebraic_real_1, Multiplicity_type> > ak_roots;
|
|
solve_1(pol, std::back_inserter(ak_roots));
|
|
|
|
for(std::size_t i=0; i<ak_roots.size(); i++)
|
|
{
|
|
roots.push_back(ak_roots[i].first.to_double());
|
|
std::cout << CGAL::to_double(ak_roots[i].first) << std::endl;
|
|
}
|
|
|
|
return roots.size();
|
|
}
|
|
|
|
/// Solve the bivariate system
|
|
/// { C1 * a + 2 * lambda * a ( a^2 + b^2 - 1 ) = C2
|
|
/// { C1 * b + 2 * lambda * b ( a^2 + b^2 - 1 ) = C3
|
|
/// using CGAL's algeabric kernel.
|
|
int solve_bivariate_system(const NT C1, const NT C2, const NT C3,
|
|
std::vector<NT>& a_roots, std::vector<NT>& b_roots) const
|
|
{
|
|
typedef CGAL::GMP_arithmetic_kernel AK;
|
|
typedef CGAL::Algebraic_kernel_d_2<AK::Rational> Algebraic_kernel_d_2;
|
|
typedef typename Algebraic_kernel_d_2::Polynomial_2 Polynomial_2;
|
|
typedef typename Algebraic_kernel_d_2::Algebraic_real_2 Algebraic_real_2;
|
|
typedef typename Algebraic_kernel_d_2::Multiplicity_type Multiplicity_type;
|
|
typedef typename Algebraic_kernel_d_2::Coefficient Coefficient;
|
|
|
|
typedef CGAL::Polynomial_traits_d<Polynomial_2> Polynomial_traits_2;
|
|
|
|
typedef Algebraic_kernel_d_2::Solve_2 Solve_2;
|
|
typedef Algebraic_kernel_d_2::Is_coprime_2 Is_coprime_2;
|
|
typedef Algebraic_kernel_d_2::Make_coprime_2 Make_coprime_2;
|
|
typedef Algebraic_kernel_d_2::Is_square_free_2 Is_square_free_2;
|
|
|
|
Algebraic_kernel_d_2 ak_2;
|
|
const Is_coprime_2 is_coprime_2 = ak_2.is_coprime_2_object();
|
|
const Solve_2 solve_2 = ak_2.solve_2_object();
|
|
const Is_square_free_2 is_square_free_2 = ak_2.is_square_free_2_object();
|
|
const Make_coprime_2 make_coprime_2 = ak_2.make_coprime_2_object();
|
|
|
|
typename Polynomial_traits_2::Construct_polynomial construct_polynomial_2;
|
|
std::pair<CGAL::Exponent_vector, Coefficient> coeffs_x[1]
|
|
= {std::make_pair(CGAL::Exponent_vector(1,0),Coefficient(1))};
|
|
Polynomial_2 x=construct_polynomial_2(coeffs_x,coeffs_x+1);
|
|
std::pair<CGAL::Exponent_vector, Coefficient> coeffs_y[1]
|
|
= {std::make_pair(CGAL::Exponent_vector(0,1),Coefficient(1))};
|
|
Polynomial_2 y=construct_polynomial_2(coeffs_y,coeffs_y+1);
|
|
|
|
AK::Rational C1q(C1);
|
|
AK::Rational C2q(C2);
|
|
AK::Rational C3q(C3);
|
|
|
|
Polynomial_2 pol1 = C1q * x + 2 * m_lambda * x * ( x*x + y*y - 1) - C2q;
|
|
Polynomial_2 pol2 = C1q * y + 2 * m_lambda * y * ( x*x + y*y - 1) - C3q;
|
|
|
|
std::cout << "init" << std::endl;
|
|
std::cout << "pol1: " << pol1 << std::endl << "pol2: " << pol2 << std::endl;
|
|
|
|
CGAL_precondition(is_square_free_2(pol1));
|
|
CGAL_precondition(is_square_free_2(pol2));
|
|
if(!is_coprime_2(pol1, pol2)){
|
|
std::cout << "not coprime" << std::endl;
|
|
CGAL_assertion(false); // @todo handle that case
|
|
|
|
Polynomial_2 g, q1, q2;
|
|
make_coprime_2(pol1, pol2, g, q1, q2);
|
|
std::cout << "g: " << g << std::endl;
|
|
pol1 = q1;
|
|
pol2 = q2;
|
|
}
|
|
|
|
std::vector<std::pair<Algebraic_real_2, Multiplicity_type> > roots;
|
|
solve_2(pol1, pol2, std::back_inserter(roots));
|
|
std::cout << "solved with " << roots.size() << " roots." << std::endl;
|
|
|
|
for(std::size_t i=0; i<roots.size(); i++)
|
|
{
|
|
a_roots.push_back(roots[i].first.to_double().first);
|
|
b_roots.push_back(roots[i].first.to_double().second);
|
|
std::cout << CGAL::to_double(roots[i].first.to_double().first) << std::endl;
|
|
std::cout << CGAL::to_double(roots[i].first.to_double().second) << std::endl;
|
|
}
|
|
|
|
return a_roots.size();
|
|
}
|
|
|
|
/// Compute the root that gives the lowest face energy.
|
|
template <typename VertexUVMap>
|
|
std::size_t compute_root_with_lowest_energy(const TriangleMesh& mesh,
|
|
face_descriptor fd,
|
|
const Cot_map ctmap,
|
|
const Local_points& lp,
|
|
const Lp_map lpmap,
|
|
const VertexUVMap uvmap,
|
|
const NT C2_denom, const NT C3,
|
|
const std::vector<NT>& roots) const
|
|
{
|
|
CGAL_precondition(!roots.empty());
|
|
NT E_min = (std::numeric_limits<NT>::max)();
|
|
std::size_t index_arg = 0.;
|
|
for(std::size_t i=0; i<roots.size(); ++i)
|
|
{
|
|
const NT a = roots[i];
|
|
const NT b = C3 * C2_denom * a;
|
|
NT Ef = compute_current_face_energy(mesh, fd, ctmap, lp, lpmap, uvmap, a, b);
|
|
if(Ef < E_min){
|
|
E_min = Ef;
|
|
index_arg = i;
|
|
}
|
|
}
|
|
return index_arg;
|
|
}
|
|
|
|
/// Compute the root that gives the lowest face energy.
|
|
template <typename VertexUVMap>
|
|
std::size_t compute_root_with_lowest_energy(const TriangleMesh& mesh,
|
|
face_descriptor fd,
|
|
const Cot_map ctmap,
|
|
const Local_points& lp,
|
|
const Lp_map lpmap,
|
|
const VertexUVMap uvmap,
|
|
const std::vector<NT>& a_roots,
|
|
const std::vector<NT>& b_roots) const
|
|
{
|
|
CGAL_precondition(!a_roots.empty() && a_roots.size() == b_roots.size());
|
|
NT E_min = (std::numeric_limits<NT>::max)();
|
|
std::size_t index_arg = -1;
|
|
for(std::size_t i=0; i<a_roots.size(); ++i)
|
|
{
|
|
NT Ef = compute_current_face_energy(mesh, fd, ctmap, lp, lpmap, uvmap,
|
|
a_roots[i], b_roots[i]);
|
|
if(Ef < E_min){
|
|
E_min = Ef;
|
|
index_arg = i;
|
|
}
|
|
}
|
|
return index_arg;
|
|
}
|
|
|
|
/// Compute the optimal values of the linear transformation matrices Lt.
|
|
template <typename VertexUVMap>
|
|
Error_code compute_optimal_Lt_matrices(const TriangleMesh& mesh,
|
|
const Faces_vector& faces,
|
|
const Cot_map ctmap,
|
|
const Local_points& lp,
|
|
const Lp_map lpmap,
|
|
const VertexUVMap uvmap,
|
|
Lt_map ltmap) const
|
|
{
|
|
Error_code status = Base::OK;
|
|
|
|
BOOST_FOREACH(face_descriptor fd, faces){
|
|
// Compute the coefficients C1, C2, C3
|
|
NT C1 = 0., C2 = 0., C3 = 0.;
|
|
|
|
halfedge_around_face_circulator hc(halfedge(fd, mesh), mesh), end(hc);
|
|
CGAL_For_all(hc, end){
|
|
halfedge_descriptor hd = *hc;
|
|
NT c = get(ctmap, hd);
|
|
|
|
// UV positions
|
|
Point_2 uvpi = get(uvmap, source(hd, mesh));
|
|
Point_2 uvpj = get(uvmap, target(hd, mesh));
|
|
NT diff_x = uvpi.x() - uvpj.x();
|
|
NT diff_y = uvpi.y() - uvpj.y();
|
|
// CGAL_warning(diff_x == 0. && diff_y == 0.);
|
|
|
|
// local positions (in the isometric 2D param)
|
|
Local_indices li = get(lpmap, hd);
|
|
Point_2 ppi = lp[ li.first ];
|
|
Point_2 ppj = lp[ li.second ];
|
|
NT p_diff_x = ppi.x() - ppj.x();
|
|
NT p_diff_y = ppi.y() - ppj.y();
|
|
CGAL_precondition(p_diff_x != 0. || p_diff_y != 0.);
|
|
|
|
std::cout << "c: " << c << std::endl;
|
|
std::cout << "diff: " << diff_x << " " << diff_y << std::endl;
|
|
std::cout << "pdiff: " << p_diff_x << " " << p_diff_y << std::endl;
|
|
|
|
typedef typename boost::property_map<TriangleMesh,
|
|
boost::vertex_point_t>::const_type PPmap;
|
|
PPmap ppmap = get(vertex_point, mesh);
|
|
|
|
std::cout << "Point_3: " << get(ppmap, source(hd, mesh)) << " || "
|
|
<< get(ppmap, target(hd, mesh)) << std::endl;
|
|
|
|
std::cout << "ADD1: " << c * ( p_diff_x*p_diff_x + p_diff_y*p_diff_y ) << std::endl;
|
|
std::cout << "ADD2: " << c * ( diff_x*p_diff_x + diff_y*p_diff_y ) << std::endl;
|
|
std::cout << "ADD3: " << c * ( diff_x*p_diff_y - diff_y*p_diff_x ) << std::endl;
|
|
|
|
C1 += c * ( p_diff_x*p_diff_x + p_diff_y*p_diff_y );
|
|
C2 += c * ( diff_x*p_diff_x + diff_y*p_diff_y );
|
|
C3 += c * ( diff_x*p_diff_y - diff_y*p_diff_x );
|
|
}
|
|
|
|
// std::cout << "C1: " << C1 << " , C2: " << C2 << " , C3: " << C3 << std::endl;
|
|
|
|
// Compute a and b
|
|
NT a = 0., b = 0.;
|
|
|
|
if(m_lambda == 0.){ // ASAP
|
|
CGAL_precondition(C1 != 0.);
|
|
a = C2 / C1;
|
|
b = C3 / C1;
|
|
}
|
|
else if( std::abs(C1) < m_lambda_tolerance * m_lambda &&
|
|
std::abs(C2) < m_lambda_tolerance * m_lambda ){ // ARAP
|
|
// If lambda is large compared to C1 and C2, the cubic equation that
|
|
// determines a and b can be simplified to a simple quadric equation
|
|
|
|
CGAL_precondition(C2*C2 + C3*C3 != 0.);
|
|
NT denom = 1. / CGAL::sqrt(C2*C2 + C3*C3);
|
|
a = C2 * denom;
|
|
b = C3 * denom;
|
|
}
|
|
else { // general case
|
|
#define SOLVE_CUBIC_EQUATION
|
|
#ifdef SOLVE_CUBIC_EQUATION
|
|
CGAL_precondition(C2 != 0.);
|
|
NT C2_denom = 1. / C2;
|
|
NT a3_coeff = 2. * m_lambda * (C2 * C2 + C3 * C3) * C2_denom * C2_denom;
|
|
|
|
std::vector<NT> roots;
|
|
solve_cubic_equation(a3_coeff, 0., (C1 - 2. * m_lambda), -C2, roots);
|
|
|
|
// The function above is correct up to 10^{-14}, but below can be used
|
|
// if more precision is needed (should never be the case)
|
|
// std::vector<NT> roots_with_AK;
|
|
// solve_cubic_equation_with_AK(a3_coeff, 0., (C1 - 2. * m_lambda), -C2,
|
|
// roots_with_AK);
|
|
|
|
std::size_t ind = compute_root_with_lowest_energy(mesh, fd,
|
|
ctmap, lp, lpmap, uvmap,
|
|
C2_denom, C3, roots);
|
|
a = roots[ind];
|
|
b = C3 * C2_denom * a;
|
|
#else // solve the bivariate system
|
|
std::vector<NT> a_roots;
|
|
std::vector<NT> b_roots;
|
|
solve_bivariate_system(C1, C2, C3, a_roots, b_roots);
|
|
|
|
std::size_t ind = compute_root_with_lowest_energy(mesh, fd,
|
|
ctmap, lp, lpmap, uvmap,
|
|
a_roots, b_roots);
|
|
a = a_roots[ind];
|
|
b = b_roots[ind];
|
|
#endif
|
|
|
|
std::cout << "ab: " << a << " " << b << std::endl;
|
|
}
|
|
|
|
// Update the map faces --> optimal Lt matrices
|
|
Lt_matrix ltm = std::make_pair(a, b);
|
|
put(ltmap, fd, ltm);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/// Computes the coordinates of the vertices p0, p1, p2
|
|
/// in a local 2D orthonormal basis of the triangle's plane.
|
|
void project_triangle(const Point_3& p0, const Point_3& p1, const Point_3& p2, // in
|
|
Point_2& z0, Point_2& z1, Point_2& z2) const // out
|
|
{
|
|
Vector_3 X = p1 - p0;
|
|
NT X_norm = CGAL::sqrt(X * X);
|
|
if(X_norm != 0.0)
|
|
X = X / X_norm;
|
|
|
|
Vector_3 Z = CGAL::cross_product(X, p2 - p0);
|
|
NT Z_norm = CGAL::sqrt(Z * Z);
|
|
if(Z_norm != 0.0)
|
|
Z = Z / Z_norm;
|
|
|
|
Vector_3 Y = CGAL::cross_product(Z, X);
|
|
|
|
NT x0 = 0;
|
|
NT y0 = 0;
|
|
NT x1 = CGAL::sqrt( (p1 - p0)*(p1 - p0) );
|
|
NT y1 = 0;
|
|
NT x2 = (p2 - p0) * X;
|
|
NT y2 = (p2 - p0) * Y;
|
|
|
|
z0 = Point_2(x0, y0);
|
|
z1 = Point_2(x1, y1);
|
|
z2 = Point_2(x2, y2);
|
|
}
|
|
|
|
/// Compute the local parameterization (2D) of a face and store it in memory.
|
|
void project_face(const TriangleMesh& mesh,
|
|
vertex_descriptor vi,
|
|
vertex_descriptor vj,
|
|
vertex_descriptor vk,
|
|
Local_points& lp) const
|
|
{
|
|
typedef typename boost::property_map<TriangleMesh,
|
|
boost::vertex_point_t>::const_type PPmap;
|
|
PPmap ppmap = get(vertex_point, mesh);
|
|
|
|
Point_3 position_vi = get(ppmap, vi);
|
|
Point_3 position_vj = get(ppmap, vj);
|
|
Point_3 position_vk = get(ppmap, vk);
|
|
|
|
Point_2 pvi, pvj, pvk;
|
|
project_triangle(position_vi, position_vj, position_vk,
|
|
pvi, pvj, pvk);
|
|
|
|
// Add the newly computed 2D points to the vector of local positions
|
|
lp.push_back(pvi);
|
|
lp.push_back(pvj);
|
|
lp.push_back(pvk);
|
|
}
|
|
|
|
/// Utility for fill_linear_system_rhs():
|
|
/// Compute the local isometric parameterization (2D) of the faces of the mesh.
|
|
Error_code compute_local_parameterization(const TriangleMesh& mesh,
|
|
const Faces_vector& faces,
|
|
Local_points& lp,
|
|
Lp_map lpmap) const
|
|
{
|
|
int global_index = 0;
|
|
|
|
BOOST_FOREACH(face_descriptor fd, faces){
|
|
halfedge_descriptor hd = halfedge(fd, mesh), hdb = hd;
|
|
|
|
vertex_descriptor vi = target(hd, mesh); // hd is k -- > i
|
|
put(lpmap, hd, std::make_pair(global_index + 2, global_index));
|
|
|
|
hd = next(hd, mesh);
|
|
vertex_descriptor vj = target(hd, mesh); // hd is i -- > j
|
|
put(lpmap, hd, std::make_pair(global_index, global_index + 1));
|
|
|
|
hd = next(hd, mesh);
|
|
vertex_descriptor vk = target(hd, mesh); // hd is j -- > k
|
|
put(lpmap, hd, std::make_pair(global_index + 1, global_index + 2));
|
|
|
|
hd = next(hd, mesh);
|
|
if(hd != hdb){ // to make sure that it is a triangular face
|
|
return Base::ERROR_NON_TRIANGULAR_MESH;
|
|
}
|
|
|
|
project_face(mesh, vi, vj, vk, lp);
|
|
global_index += 3;
|
|
}
|
|
|
|
if(lp.size() != 3 * faces.size())
|
|
return Base::ERROR_NON_TRIANGULAR_MESH;
|
|
|
|
return Base::OK;
|
|
}
|
|
|
|
/// Compute the coefficient b_ij = (i, j) of the right hand side vector B,
|
|
/// for j neighbor vertex of i.
|
|
void compute_b_ij(const TriangleMesh& mesh,
|
|
halfedge_descriptor hd,
|
|
const Cot_map ctmap,
|
|
const Local_points& lp,
|
|
const Lp_map lpmap,
|
|
const Lt_map ltmap,
|
|
NT& x, NT& y) const // x for Bu and y for Bv
|
|
{
|
|
CGAL_precondition(x == 0.0 && y == 0.0);
|
|
|
|
// Note that :
|
|
// -- Circulators move in a clockwise manner
|
|
// -- The halfedge hd points to vi
|
|
|
|
// -- Face IJK with vk before vj while circulating around vi
|
|
halfedge_descriptor hd_opp = opposite(hd, mesh); // hd_opp points to vj
|
|
face_descriptor fd_k = face(hd_opp, mesh);
|
|
|
|
// Get the matrix L_t corresponding to the face ijk
|
|
Lt_matrix ltm_k = get(ltmap, fd_k);
|
|
NT a_k = ltm_k.first;
|
|
NT b_k = ltm_k.second;
|
|
|
|
// Get the local parameterization in the triangle ijk
|
|
Local_indices loc_indices_k = get(lpmap, hd_opp);
|
|
const Point_2& pvi_k = lp[ loc_indices_k.first ];
|
|
const Point_2& pvj_k = lp[ loc_indices_k.second ];
|
|
|
|
// x_i - x_j in the local parameterization of the triangle ijk
|
|
NT diff_k_x = pvi_k.x() - pvj_k.x();
|
|
NT diff_k_y = pvi_k.y() - pvj_k.y();
|
|
|
|
// get the cotangent angle at vk
|
|
NT ct_k = get(ctmap, hd_opp);
|
|
|
|
// cot_k * Lt_k * (xi - xj)_k
|
|
x = ct_k * ( a_k * diff_k_x + b_k * diff_k_y );
|
|
y = ct_k * ( -b_k * diff_k_x + a_k * diff_k_y );
|
|
|
|
// -- Face IJL with vl after vj while circulating around vi
|
|
face_descriptor fd_l = face(hd, mesh);
|
|
|
|
// Get the matrix L_t corresponding to the face ijl
|
|
Lt_matrix ltm_l = get(ltmap, fd_l);
|
|
NT a_l = ltm_l.first;
|
|
NT b_l = ltm_l.second;
|
|
|
|
// Get the local parameterization in the triangle ijl
|
|
Local_indices loc_indices_l = get(lpmap, hd);
|
|
const Point_2& pvi_l = lp[ loc_indices_l.second ]; // because hd points to vi
|
|
const Point_2& pvj_l = lp[ loc_indices_l.first ];
|
|
|
|
// x_i - x_j in the local parameterization of the triangle ijl
|
|
NT diff_l_x = pvi_l.x() - pvj_l.x();
|
|
NT diff_l_y = pvi_l.y() - pvj_l.y();
|
|
|
|
// get the cotangent angle at vl
|
|
NT ct_l = get(ctmap, hd);
|
|
|
|
// cot_l * Lt_l * (xi - xj)_l
|
|
x += ct_l * ( a_l * diff_l_x + b_l * diff_l_y );
|
|
y += ct_l * ( -b_l * diff_l_x + a_l * diff_l_y );
|
|
|
|
std::cout << "akbk: " << a_k << " " << b_k << std::endl;
|
|
std::cout << "albl: " << a_l << " " << b_l << std::endl;
|
|
std::cout << "xy: " << x << " " << y << std::endl;
|
|
}
|
|
|
|
/// Compute the line i of right hand side vectors Bu and Bv
|
|
/// - call compute_b_ij() for each neighbor v_j to compute the B coefficient b_i
|
|
///
|
|
/// \pre Vertices must be indexed.
|
|
/// \pre Vertex i musn't be already parameterized.
|
|
/// \pre Lines i of Bu and Bv must be zero.
|
|
template <typename VertexIndexMap>
|
|
Error_code fill_linear_system_rhs(const TriangleMesh& mesh,
|
|
vertex_descriptor vertex,
|
|
const Cot_map ctmap,
|
|
const Local_points& lp,
|
|
const Lp_map lpmap,
|
|
const Lt_map ltmap,
|
|
VertexIndexMap vimap,
|
|
Vector& Bu, Vector& Bv) const
|
|
{
|
|
int i = get(vimap, vertex);
|
|
|
|
// Circulate over vertices around 'vertex' to compute w_ii and w_ijs
|
|
NT bu_i = 0, bv_i = 0;
|
|
int vertexIndex = 0;
|
|
|
|
halfedge_around_target_circulator hc(vertex, mesh), end = hc;
|
|
CGAL_For_all(hc, end){
|
|
halfedge_descriptor hd = *hc;
|
|
CGAL_assertion(target(hd, mesh) == vertex);
|
|
|
|
NT x = 0., y = 0.;
|
|
compute_b_ij(mesh, hd, ctmap, lp, lpmap, ltmap, x, y);
|
|
|
|
bu_i += x;
|
|
bv_i += y;
|
|
vertexIndex++;
|
|
}
|
|
|
|
// Set the entries for the right hand side vectors
|
|
Bu.set(i, bu_i);
|
|
Bv.set(i, bv_i);
|
|
|
|
if (vertexIndex < 2)
|
|
return Base::ERROR_NON_TRIANGULAR_MESH;
|
|
|
|
return Base::OK;
|
|
}
|
|
|
|
/// Compute the entries of the right hand side of the ARAP linear system.
|
|
///
|
|
/// \pre Vertices must be indexed.
|
|
template <typename VertexUVMap,
|
|
typename VertexIndexMap,
|
|
typename VertexParameterizedMap>
|
|
Error_code compute_rhs(const TriangleMesh& mesh,
|
|
const Vertex_set& vertices,
|
|
const Cot_map ctmap,
|
|
const Local_points& lp,
|
|
const Lp_map lpmap,
|
|
const Lt_map ltmap,
|
|
VertexUVMap uvmap,
|
|
VertexIndexMap vimap,
|
|
VertexParameterizedMap vpmap,
|
|
Vector& Bu, Vector& Bv) const
|
|
{
|
|
// Initialize the right hand side B in the linear system "A*X = B"
|
|
Error_code status = Base::OK;
|
|
|
|
unsigned int count = 0;
|
|
BOOST_FOREACH(vertex_descriptor vd, vertices){
|
|
if(!get(vpmap, vd)){ // not yet parameterized
|
|
// Compute the lines i of the vectors Bu and Bv
|
|
status = fill_linear_system_rhs(mesh, vd, ctmap, lp, lpmap,
|
|
ltmap, vimap, Bu, Bv);
|
|
if(status != Base::OK)
|
|
return status;
|
|
} else { // fixed vertices
|
|
int index = get(vimap, vd);
|
|
Point_2 uv = get(uvmap, vd);
|
|
Bu.set(index, uv.x());
|
|
Bv.set(index, uv.y());
|
|
++count;
|
|
std::cout << index << " is fixed in B: ";
|
|
std::cout << uv.x() << " " << uv.y() << std::endl;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/// Compute the right hand side and solve the linear system to obtain the
|
|
/// new UV coordinates.
|
|
template <typename VertexUVMap,
|
|
typename VertexIndexMap,
|
|
typename VertexParameterizedMap>
|
|
Error_code update_solution(const TriangleMesh& mesh,
|
|
const Vertex_set& vertices,
|
|
const Cot_map ctmap,
|
|
const Local_points& lp,
|
|
const Lp_map lpmap,
|
|
const Lt_map ltmap,
|
|
VertexUVMap uvmap,
|
|
VertexIndexMap vimap,
|
|
VertexParameterizedMap vpmap,
|
|
const Matrix& A)
|
|
{
|
|
Error_code status = Base::OK;
|
|
|
|
// Create two sparse linear systems "A*Xu = Bu" and "A*Xv = Bv"
|
|
int nbVertices = num_vertices(mesh);
|
|
Vector Xu(nbVertices), Xv(nbVertices), Bu(nbVertices), Bv(nbVertices);
|
|
|
|
// Fill the right hand side vectors
|
|
status = compute_rhs(mesh, vertices, ctmap, lp, lpmap, ltmap,
|
|
uvmap, vimap, vpmap, Bu, Bv);
|
|
if (status != Base::OK)
|
|
return status;
|
|
|
|
std::cout << "A, B: " << std::endl << A.eigen_object() << std::endl << Bu << std::endl << Bv << std::endl;
|
|
|
|
// Solve "A*Xu = Bu". On success, the solution is (1/Du) * Xu.
|
|
// Solve "A*Xv = Bv". On success, the solution is (1/Dv) * Xv.
|
|
NT Du, Dv;
|
|
if(!get_linear_algebra_traits().linear_solver(A, Bu, Xu, Du) ||
|
|
!get_linear_algebra_traits().linear_solver(A, Bv, Xv, Dv))
|
|
{
|
|
std::cout << "Could not solve linear system" << std::endl;
|
|
status = Base::ERROR_CANNOT_SOLVE_LINEAR_SYSTEM;
|
|
return status;
|
|
}
|
|
|
|
// WARNING: this package does not support homogeneous coordinates!
|
|
CGAL_assertion(Du == 1.0);
|
|
CGAL_assertion(Dv == 1.0);
|
|
|
|
CGAL_postcondition_code
|
|
(
|
|
// make sure that the constrained vertices have not been moved
|
|
BOOST_FOREACH(vertex_descriptor vd, vertices){
|
|
if(get(vpmap, vd)){
|
|
int index = get(vimap, vd);
|
|
CGAL_postcondition(std::abs(Xu[index] - Bu[index] ) < 1e-10);
|
|
CGAL_postcondition(std::abs(Xv[index] - Bv[index] ) < 1e-10);
|
|
}
|
|
}
|
|
)
|
|
|
|
assign_solution(Xu, Xv, vertices, uvmap, vimap);
|
|
return status;
|
|
}
|
|
|
|
|
|
/// Compute the current energy of a face, given a linear transformation matrix.
|
|
template <typename VertexUVMap>
|
|
NT compute_current_face_energy(const TriangleMesh& mesh,
|
|
face_descriptor fd,
|
|
const Cot_map ctmap,
|
|
const Local_points& lp,
|
|
const Lp_map lpmap,
|
|
const VertexUVMap uvmap,
|
|
const NT a, const NT b) const
|
|
{
|
|
NT Ef = 0.;
|
|
|
|
halfedge_around_face_circulator hc(halfedge(fd, mesh), mesh), end(hc);
|
|
CGAL_For_all(hc, end){
|
|
halfedge_descriptor hd = *hc;
|
|
NT cot = get(ctmap, hd);
|
|
NT nabla_x = 0., nabla_y = 0.;
|
|
|
|
// UV positions
|
|
Point_2 pi = get(uvmap, source(hd, mesh));
|
|
Point_2 pj = get(uvmap, target(hd, mesh));
|
|
NT diff_x = pi.x() - pj.x();
|
|
NT diff_y = pi.y() - pj.y();
|
|
|
|
// local positions (in the 2D param)
|
|
Local_indices li = get(lpmap, hd);
|
|
Point_2 ppi = lp[ li.first ];
|
|
Point_2 ppj = lp[ li.second ];
|
|
NT p_diff_x = ppi.x() - ppj.x();
|
|
NT p_diff_y = ppi.y() - ppj.y();
|
|
|
|
nabla_x = diff_x - ( a * p_diff_x + b * p_diff_y );
|
|
nabla_y = diff_y - ( -b * p_diff_x + a * p_diff_y );
|
|
|
|
NT sq_nabla_norm = nabla_x * nabla_x + nabla_y * nabla_y;
|
|
Ef += cot * sq_nabla_norm;
|
|
}
|
|
|
|
NT s = a * a + b * b - 1;
|
|
Ef += m_lambda * s * s;
|
|
return Ef;
|
|
}
|
|
|
|
/// Compute the current energy of a face.
|
|
template <typename VertexUVMap>
|
|
NT compute_current_face_energy(const TriangleMesh& mesh,
|
|
face_descriptor fd,
|
|
const Cot_map ctmap,
|
|
const Local_points& lp,
|
|
const Lp_map lpmap,
|
|
const Lt_map ltmap,
|
|
const VertexUVMap uvmap) const
|
|
{
|
|
NT Ef = 0.;
|
|
|
|
Lt_matrix ltm = get(ltmap, fd); // the (current) optimal linear transformation
|
|
NT a = ltm.first;
|
|
NT b = ltm.second;
|
|
|
|
return compute_current_face_energy(mesh, fd, ctmap, lp, lpmap, uvmap, a, b);
|
|
}
|
|
|
|
/// Compute the current energy of the parameterization.
|
|
template <typename VertexUVMap>
|
|
NT compute_current_energy(const TriangleMesh& mesh,
|
|
const Faces_vector& faces,
|
|
const Cot_map ctmap,
|
|
const Local_points& lp,
|
|
const Lp_map lpmap,
|
|
const Lt_map ltmap,
|
|
const VertexUVMap uvmap) const
|
|
{
|
|
NT E = 0.;
|
|
|
|
BOOST_FOREACH(face_descriptor fd, faces){
|
|
NT Ef = compute_current_face_energy(mesh, fd, ctmap, lp, lpmap,
|
|
ltmap, uvmap);
|
|
E += Ef;
|
|
}
|
|
|
|
E *= 0.5;
|
|
return E;
|
|
}
|
|
|
|
// Post processing functions
|
|
/// Use the convex virtual boundary algorithm of Karni et al.[2005] to fix
|
|
/// the (hopefully few) flips in the result.
|
|
template <typename VertexUVMap,
|
|
typename VertexIndexMap>
|
|
Error_code post_process(const TriangleMesh& mesh,
|
|
const Vertex_set& vertices,
|
|
const Faces_vector& faces,
|
|
halfedge_descriptor bhd,
|
|
VertexUVMap uvmap,
|
|
const VertexIndexMap vimap) const
|
|
{
|
|
typedef CGAL::MVC_post_processor_3<TriangleMesh> Post_processor;
|
|
|
|
Post_processor p;
|
|
Error_code status = p.parameterize(mesh, vertices, faces, bhd, uvmap, vimap);
|
|
if(status != Base::OK)
|
|
return status;
|
|
|
|
output_uvmap("ARAP_final_post_processing.off", mesh, vertices, faces, uvmap, vimap);
|
|
|
|
return Base::OK;
|
|
}
|
|
|
|
/// Compute the quality of the parameterization.
|
|
void compute_quality(const TriangleMesh& mesh,
|
|
const Faces_vector& faces) const
|
|
{
|
|
|
|
BOOST_FOREACH(face_descriptor fd, faces){
|
|
// compute the jacobian
|
|
|
|
// compute the singular values
|
|
}
|
|
|
|
}
|
|
|
|
// Public operations
|
|
public:
|
|
/// Compute a mapping from a triangular 3D surface mesh to a piece of the 2D space.
|
|
/// The mapping is piecewise linear (linear in each triangle).
|
|
/// The result is the (u,v) pair image of each vertex of the 3D surface.
|
|
///
|
|
/// \tparam VertexUVmap must be a property map that associates a %Point_2
|
|
/// (type deduced by `Parameterized_traits_3`) to a `vertex_descriptor`
|
|
/// (type deduced by the graph traits of `TriangleMesh`).
|
|
/// \tparam VertexIndexMap must be a property map that associates a unique integer index
|
|
/// to a `vertex_descriptor` (type deduced by the graph traits of `TriangleMesh`).
|
|
/// \tparam VertexParameterizedMap must be a property map that associates a boolean
|
|
/// to a `vertex_descriptor` (type deduced by the graph traits of `TriangleMesh`).
|
|
///
|
|
/// \param mesh a triangulated surface.
|
|
/// \param bhd an halfedge descriptor on the boundary of `mesh`.
|
|
/// \param uvmap an instanciation of the class `VertexUVmap`.
|
|
/// \param vimap an instanciation of the class `VertexIndexMap`.
|
|
/// \param vpmap an instanciation of the class `VertexParameterizedMap`.
|
|
///
|
|
/// \pre `mesh` must be a surface with one connected component.
|
|
/// \pre `mesh` must be a triangular mesh.
|
|
/// \pre The vertices must be indexed (vimap must be initialized).
|
|
///
|
|
template <typename VertexUVMap,
|
|
typename VertexIndexMap,
|
|
typename VertexParameterizedMap>
|
|
Error_code parameterize(TriangleMesh& mesh,
|
|
halfedge_descriptor bhd,
|
|
VertexUVMap uvmap,
|
|
VertexIndexMap vimap,
|
|
VertexParameterizedMap vpmap)
|
|
{
|
|
Error_code status = Base::OK;
|
|
|
|
int nbVertices = num_vertices(mesh);
|
|
Matrix A(nbVertices, nbVertices); // the constant matrix using in the linear system A*X = B
|
|
|
|
// vertices and faces containers
|
|
Faces_vector faces;
|
|
Vertex_set vertices;
|
|
initialize_containers(mesh, bhd, vertices, faces);
|
|
|
|
// linear transformation matrices L_t
|
|
// Only need to store 2 indices since the complete matrix is {{a,b},{-b,a}}
|
|
Lt_hash_map lt_hm;
|
|
Lt_map ltmap(lt_hm); // will be filled in 'compute_optimal_Lt_matrices()'
|
|
|
|
// Compute the initial parameterization of the mesh
|
|
status = compute_initial_uv_map(mesh, bhd, uvmap);
|
|
output_uvmap("ARAP_initial_param.off", mesh, vertices, faces, uvmap, vimap);
|
|
if(status != Base::OK)
|
|
return status;
|
|
|
|
// Handle the boundary condition depending on lambda
|
|
status = parameterize_border<VertexUVMap>(mesh, vertices, bhd, vpmap);
|
|
if(status != Base::OK)
|
|
return status;
|
|
|
|
// Compute all cotangent angles
|
|
Cot_hm cthm;
|
|
Cot_map ctmap(cthm);
|
|
status = compute_cotangent_angles(mesh, faces, ctmap);
|
|
if(status != Base::OK)
|
|
return status;
|
|
|
|
// Compute all local 2D parameterizations
|
|
Lp_hm lphm;
|
|
Lp_map lpmap(lphm);
|
|
Local_points lp;
|
|
status = compute_local_parameterization(mesh, faces, lp, lpmap);
|
|
if(status != Base::OK)
|
|
return status;
|
|
|
|
std::cout << "All data structures initialized" << std::endl;
|
|
|
|
// The matrix A is constant and can be initialized outside of the loop
|
|
status = initialize_matrix_A(mesh, vertices, ctmap, vimap, vpmap, A);
|
|
if(status != Base::OK)
|
|
return status;
|
|
|
|
NT energy_this = compute_current_energy(mesh, faces, ctmap, lp, lpmap,
|
|
ltmap, uvmap);
|
|
NT energy_last;
|
|
std::cout << "Initial energy: " << energy_this << std::endl;
|
|
|
|
// main loop
|
|
for(unsigned int ite=1; ite<=m_iterations; ++ite)
|
|
{
|
|
compute_optimal_Lt_matrices(mesh, faces, ctmap, lp, lpmap, uvmap, ltmap);
|
|
status = update_solution(mesh, vertices, ctmap, lp, lpmap, ltmap,
|
|
uvmap, vimap, vpmap, A);
|
|
|
|
// Output the current situation
|
|
output_uvmap("ARAP_iteration_", ite, mesh, vertices, faces, uvmap, vimap);
|
|
energy_last = energy_this;
|
|
energy_this = compute_current_energy(mesh, faces, ctmap, lp, lpmap,
|
|
ltmap, uvmap);
|
|
std::cout << "Energy at iteration " << ite << " : " << energy_this << std::endl;
|
|
CGAL_warning(energy_this >= 0);
|
|
|
|
if(status != Base::OK)
|
|
return status;
|
|
|
|
// energy based termination
|
|
if(m_tolerance > 0.0 && ite <= m_iterations) // if tolerance <= 0, don't compute energy
|
|
{ // also no need compute energy if this iteration is the last iteration
|
|
double energy_diff = std::abs((energy_last - energy_this) / energy_this);
|
|
if(energy_diff < m_tolerance){
|
|
std::cout << "Minimization process ended after: "
|
|
<< ite + 1 << " iterations. "
|
|
<< "Energy diff: " << energy_diff << std::endl;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
output_uvmap("ARAP_final_pre_processing.off", mesh, vertices, faces, uvmap, vimap);
|
|
|
|
if(!internal::Parameterization::is_one_to_one_mapping(mesh, uvmap)){
|
|
// Use post processing to handle flipped elements
|
|
std::cout << "Parameterization is not valid; calling post processor" << std::endl;
|
|
status = post_process(mesh, vertices, faces, bhd, uvmap, vimap);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
public:
|
|
/// Constructor
|
|
///
|
|
/// \param border_param %Object that maps the surface's border to the 2D space.
|
|
/// \param sparse_la %Traits object to access a sparse linear system.
|
|
/// \param lambda Parameter to give importance to shape or angle preservation.
|
|
/// \param iterations Maximal number of iterations in the energy minimization process.
|
|
/// \param tolerance Minimal energy difference between two iterations for the minimization
|
|
/// process to continue.
|
|
///
|
|
ARAP_parameterizer_3(Border_param border_param = Border_param(),
|
|
Sparse_LA sparse_la = Sparse_LA(),
|
|
NT lambda = 0.,
|
|
unsigned int iterations = 10,
|
|
NT tolerance = 1e-6)
|
|
:
|
|
m_borderParameterizer(border_param),
|
|
m_linearAlgebra(sparse_la),
|
|
m_lambda(lambda),
|
|
m_lambda_tolerance(1e-10),
|
|
m_iterations(iterations),
|
|
m_tolerance(tolerance)
|
|
{ }
|
|
|
|
// Default copy constructor and operator=() are fine
|
|
};
|
|
|
|
} // namespace CGAL
|
|
|
|
#endif // CGAL_ARAP_PARAMETERIZER_3_H
|