LinABF.h
#pragma once
// Based on code by Rhaleb Zayer
#include "SurfaceMeshModel.h"
#include "SurfaceMeshHelper.h"
using namespace SurfaceMesh;
#include <Eigen/Core>
#include <Eigen/Sparse>
#include <Eigen/CholmodSupport>
using namespace Eigen;
typedef Eigen::Triplet<Scalar> T;
#define M_PI 3.14159265358979323846
#define M_PI_2 1.57079632679489661923
#define cot(x) (tan(M_PI_2 - x))
struct LinABF{
LinABF( SurfaceMeshModel * useMesh )
{
if(!useMesh->is_triangle_mesh()){
qDebug() << "WARNING: mesh has been triangulated!";
useMesh->triangulate();
}
this->mesh = useMesh;
this->points = mesh->vertex_property<Vector3>(VPOINT);
this->np = mesh->n_vertices();
this->nt = mesh->n_faces();
this->nA = 3 * nt;
/// 1) Find boundary / internal vertices
foreach(Vertex v, mesh->vertices()){
if(mesh->is_boundary(v))
b.insert( v.idx() );
else
internal.push_back( v );
}
fv.resize( nt );
fv123.reserve( 3 * nt );
ang123 = Eigen::VectorXd::Zero( 3 * nt );
/// 2) Angle pre-processing
foreach(Face f, mesh->faces())
{
// Collect vertices of face
Surface_mesh::Vertex_around_face_circulator vit = mesh->vertices(f),vend=vit;
do{ fv[f.idx()].push_back( Vertex(vit) ); } while(++vit != vend);
std::vector<Vertex> & vface = fv[f.idx()];
Vector3 r23 = (points[vface[2]] - points[vface[1]]).normalized();
Vector3 r31 = (points[vface[0]] - points[vface[2]]).normalized();
Vector3 r12 = (points[vface[1]] - points[vface[0]]).normalized();
int i = f.idx() * 3;
ang123[i + 0] = acos(-dot(r12, r31));
ang123[i + 1] = acos(-dot(r12, r23));
ang123[i + 2] = acos(-dot(r31, r23));
fv123.push_back(vface[0]); fv123.push_back(vface[1]); fv123.push_back(vface[2]);
fv231.push_back(vface[1]); fv231.push_back(vface[2]); fv231.push_back(vface[0]);
}
/// 3) Update with optimal angles:
// Fill with corresponding ones
std::vector< T > Mang_T;
foreach(Face f, mesh->faces()){
int i = f.idx() * 3;
Mang_T.push_back( T(fv123[i+0].idx(), fv231[i+0].idx(), ang123[i+0]) );
Mang_T.push_back( T(fv123[i+1].idx(), fv231[i+1].idx(), ang123[i+1]) );
Mang_T.push_back( T(fv123[i+2].idx(), fv231[i+2].idx(), ang123[i+2]) );
}
// Ring angles
Mang = Eigen::SparseMatrix<Scalar,Eigen::RowMajor>(np,np);
Mang.setFromTriplets( Mang_T.begin(), Mang_T.end() );
// Sum it up
Eigen::VectorXd sumringangles(np);
for(int i = 0; i < np; i++) sumringangles(i) = Mang.row(i).sum();
// Optimal angles
foreach(Face f, mesh->faces())
{
int i = f.idx() * 3;
double ang1opt = 2 * M_PI * ang123[i+0] / sumringangles[ fv123[i+0].idx() ];
double ang2opt = 2 * M_PI * ang123[i+1] / sumringangles[ fv123[i+1].idx() ];
double ang3opt = 2 * M_PI * ang123[i+2] / sumringangles[ fv123[i+2].idx() ];
ang123opt.push_back(ang1opt); ang123opt.push_back(ang2opt); ang123opt.push_back(ang3opt);
}
// Find angles with big deficit
rhsk = Eigen::VectorXd::Constant(np, 2 * M_PI) - sumringangles;
// Threshold 1.0
QSet<int> icha;
for(int i = 0; i < (int)rhsk.rows(); i++) if(abs(rhsk(i)) > 1.0) icha.insert(i);
// Exclude boundary
QSet<int> icha_internal = icha - b;
//std::set_difference(icha.begin(), icha.end(), b.begin(), b.end(), std::inserter(icha_internal, icha_internal.end()));
// Go over all ang123 and replace 'vi' values with optimal
foreach(int vi, icha_internal){
foreach(Face f, mesh->faces()){
int fidx = f.idx();
int v1 = 3*fidx + 0;
int v2 = 3*fidx + 1;
int v3 = 3*fidx + 2;
if(vi == fv[fidx][0].idx()) ang123[v1] = ang123opt[v1];
if(vi == fv[fidx][1].idx()) ang123[v2] = ang123opt[v2];
if(vi == fv[fidx][2].idx()) ang123[v3] = ang123opt[v3];
}
}
/// 4) Fill condition matrices
std::vector< T > B1_T, B2_T, B3_T, tmp_T;
// B1:
rhs1 = Eigen::VectorXd(nt);
for(int i = 0; i < nt; i++){
B1_T.push_back(T(i,3*i+0, 1));
B1_T.push_back(T(i,3*i+1, 1));
B1_T.push_back(T(i,3*i+2, 1));
// Sum angles
Scalar sum = ang123[3*i+0] + ang123[3*i+1] + ang123[3*i+2];
rhs1(i) = M_PI - sum;
}
B1 = Eigen::SparseMatrix<Scalar>(nt,nA);
B1.setFromTriplets( B1_T.begin(), B1_T.end() );
B1_T.clear();
// B2:
for(int idx = 0; idx < nt; idx++)
{
int a1 = fv[idx][0].idx();
int a2 = fv[idx][1].idx();
int a3 = fv[idx][2].idx();
int it0 = 3*idx + 0;
int it1 = 3*idx + 1;
int it2 = 3*idx + 2;
if(!b.contains(a1)) tmp_T.push_back( T(a1,it0, 1) );
if(!b.contains(a2)) tmp_T.push_back( T(a2,it1, 1) );
if(!b.contains(a3)) tmp_T.push_back( T(a3,it2, 1) );
}
// Only compute for internals
{
std::set<int> usedRow;
std::map<int,int> rowMap;
foreach(T t, tmp_T) usedRow.insert(t.row());
foreach(int r, usedRow) rowMap[r] = rowMap.size();
foreach(T t, tmp_T) B2_T.push_back( T(rowMap[t.row()],t.col(),t.value()) );
}
B2 = Eigen::SparseMatrix<Scalar>(np - b.size(), nA);
B2.setFromTriplets( B2_T.begin(), B2_T.end() );
B2_T.clear();
Eigen::VectorXd b2_ang123 = B2 * ang123;
Eigen::VectorXd two_pi = Eigen::VectorXd::Constant(b2_ang123.rows(), 2 * M_PI);
rhs2 = two_pi - b2_ang123;
// Side condition
// B3:
tmp_T.clear();
for(int idx = 0; idx < nt; idx++)
{
int a1 = fv[idx][0].idx();
int a2 = fv[idx][1].idx();
int a3 = fv[idx][2].idx();
int it0 = 3*idx + 0;
int it1 = 3*idx + 1;
int it2 = 3*idx + 2;
if(!b.contains(a1)){
tmp_T.push_back( T(a1,it2, cot( ang123(it2) )) );
tmp_T.push_back( T(a1,it1, -cot( ang123(it1) )) );
}
if(!b.contains(a2)){
tmp_T.push_back( T(a2,it0, cot( ang123(it0) )) );
tmp_T.push_back( T(a2,it2, -cot( ang123(it2) )) );
}
if(!b.contains(a3)){
tmp_T.push_back( T(a3,it1, cot( ang123(it1) )) );
tmp_T.push_back( T(a3,it0, -cot( ang123(it0) )) );
}
}
// Only compute for internals
{
std::set<int> usedRow;
std::map<int,int> rowMap;
foreach(T t, tmp_T) usedRow.insert(t.row());
foreach(int r, usedRow) rowMap[r] = rowMap.size();
foreach(T t, tmp_T) B3_T.push_back( T(rowMap[ t.row() ],t.col(),t.value()) );
}
B3 = Eigen::SparseMatrix<Scalar>(np - b.size(), nA);
B3.setFromTriplets( B3_T.begin(), B3_T.end() );
B3_T.clear();
// CC:
tmp_T.clear();
for(int idx = 0; idx < nt; idx++)
{
int a1 = fv[idx][0].idx();
int a2 = fv[idx][1].idx();
int a3 = fv[idx][2].idx();
int it0 = 3*idx + 0;
int it1 = 3*idx + 1;
int it2 = 3*idx + 2;
double vs0 = log( sin(ang123(it0)) );
double vs1 = log( sin(ang123(it1)) );
double vs2 = log( sin(ang123(it2)) );
tmp_T.push_back( T(a1,it2, -vs2 ) );
tmp_T.push_back( T(a1,it1, vs1 ) );
tmp_T.push_back( T(a2,it0, -vs0) );
tmp_T.push_back( T(a2,it2, vs2) );
tmp_T.push_back( T(a3,it1, -vs1) );
tmp_T.push_back( T(a3,it0, vs0) );
}
CC = Eigen::SparseMatrix<Scalar,Eigen::RowMajor>(np, nA);
CC.setFromTriplets( tmp_T.begin(), tmp_T.end() );
tmp_T.clear();
Eigen::VectorXd sumCC(np);
for(int i = 0; i < np; i++) sumCC(i) = CC.row(i).sum();
// Fill 'rhs3' with only sum for internals
rhs3 = Eigen::VectorXd::Zero(np - b.size());
for(int i = 0, j = 0; i < np; i++) if(!b.contains(i)) rhs3(j++) = sumCC(i);
// Gather all
int b1 = B1.rows(), b2 = B2.rows(), b3 = B3.rows();
Eigen::SparseMatrix<Scalar,Eigen::RowMajor> M = Eigen::SparseMatrix<Scalar,Eigen::RowMajor>(b1 + b2 + b3, nA);
M.middleRows(0 , b1) = B1;
M.middleRows(b1 , b2) = B2;
M.middleRows(b1 + b2, b3) = B3;
// For change of variables
tmp_T.clear(); for(int i = 0; i < nA; i++) tmp_T.push_back(T(i,i,ang123(i)));
W = Eigen::SparseMatrix<Scalar>(nA,nA);
W.setFromTriplets(tmp_T.begin(),tmp_T.end()); tmp_T.clear();
// Matrix A:
A = M * W;
At = A.transpose();
// Vector rhs:
int j = 0;
rhs = Eigen::VectorXd::Zero( rhs1.size() + rhs2.size() + rhs3.size() );
for(int i = 0; i < rhs1.size(); i++) rhs(j++) = rhs1(i);
for(int i = 0; i < rhs2.size(); i++) rhs(j++) = rhs2(i);
for(int i = 0; i < rhs3.size(); i++) rhs(j++) = rhs3(i);
// Solve:
Eigen::CholmodSupernodalLLT< Eigen::SparseMatrix<Scalar> > solver(A * At);
Eigen::VectorXd ee = At * solver.solve( rhs );
ee = W * ee;
angsol = ang123 + ee;
solution.resize(nt,3);
for(int i = 0; i < nt; i++)
{
solution(i,0) = angsol(3*i + 0);
solution(i,1) = angsol(3*i + 1);
solution(i,2) = angsol(3*i + 2);
}
/// 5) Get positions from angles
Eigen::VectorXd coef(nt), a(nt), b(nt);
for(int i = 0; i < nt; i++)
{
coef(i) = sin(solution(i,1)) / sin(solution(i,2));
a(i) = coef(i) * cos( solution(i,0) );
b(i) = coef(i) * sin( solution(i,0) );
}
std::vector< T > MV_T;
for(int i = 0; i < nt; i++)
{
int a1 = fv[i][0].idx();
int a2 = fv[i][1].idx();
int a3 = fv[i][2].idx();
MV_T.push_back( T(i, a1 , 1-a(i) ) );
MV_T.push_back( T(i, a1+np , b(i) ) );
MV_T.push_back( T(i, a2 , a(i) ) );
MV_T.push_back( T(i, a2+np , -b(i) ) );
MV_T.push_back( T(i, a3 , -1 ) );
int j = i + nt;
MV_T.push_back( T(j, a1 , -b(i) ) );
MV_T.push_back( T(j, a1+np , 1-a(i) ) );
MV_T.push_back( T(j, a2 , b(i) ) );
MV_T.push_back( T(j, a2+np , a(i) ) );
MV_T.push_back( T(j, a3+np , -1 ) );
}
MV = Eigen::SparseMatrix<Scalar,Eigen::RowMajor>(2*nt,2*np);
MV.setFromTriplets( MV_T.begin(), MV_T.end() );
MVt = MV.transpose();
MV = MVt * MV;
// Pinned vertices conditions
int a1 = fv[0][0].idx(), a2 = fv[0][1].idx();
int pinned[] = { a1, a2, a1+np, a2+np };
Eigen::SparseMatrix<Scalar,Eigen::RowMajor> MV_pinned(4,2*np);
for(int i = 0; i < 4; i++) MV_pinned.insert(i,pinned[i]) = 1;
for(int i = 0; i < 4; i++) MV.middleRows(pinned[i],1) = MV_pinned.row(i);
uvc = Eigen::VectorXd::Zero( 2 * np );
uvc( pinned[1] ) = 1.0;
MVt = MV.transpose();
Eigen::CholmodSupernodalLLT< Eigen::SparseMatrix<Scalar> > postion_solver( MVt * MV );
Eigen::VectorXd uv = postion_solver.solve( MVt * uvc );
// Save final UV coordinates
this->tex_coord = mesh->vertex_property<Vec2d>("v:texture", Vec2d(0,0));
foreach(Vertex v, mesh->vertices())
{
int i = v.idx();
tex_coord[v] = Vec2d( uv(i), uv(i+np) );
}
}
void applyUVToMesh()
{
foreach(Vertex v, mesh->vertices())
points[v] = Vector3(tex_coord[v][0],tex_coord[v][1],0);
}
SurfaceMeshModel * mesh;
Vector3VertexProperty points;
int np, nt; // Number of points / triangles
int nA; // Number of angles on triangular mesh
QSet<int> b; // Boundary vertices
std::vector<Vertex> internal; // Internal vertices
std::vector< std::vector<Vertex> > fv; // Vertex indices per face
std::vector<Vertex> fv123; // Flat version of fv
std::vector<Vertex> fv231; // Re-ordered version of fv123
Eigen::VectorXd ang123, angsol; // Angles of all faces f_ang1,f_ang2,f_ang3,..
std::vector<Scalar> ang123opt; // Optimal angles
std::vector<Scalar> ang123sum; // Sum of angles
Eigen::SparseMatrix<Scalar,Eigen::RowMajor> Mang;
Eigen::VectorXd rhsk;
Eigen::SparseMatrix<Scalar> B1,B2,B3; // Parts of system matrix 'A'
Eigen::VectorXd rhs1, rhs2, rhs3; // Right hand side
Eigen::SparseMatrix<Scalar,Eigen::RowMajor> CC;
Eigen::SparseMatrix<Scalar> W;
// System
Eigen::SparseMatrix<Scalar> A, At;
Eigen::VectorXd rhs;
Eigen::MatrixXd solution;
Eigen::SparseMatrix<Scalar,Eigen::RowMajor> MV, MVt;
Eigen::MatrixXd uvc;
// Output
SurfaceMeshModel::Vertex_property<Vec2d> tex_coord;
};