Files
OrcaSlicer-bambulab/src/libigl/igl/copyleft/comiso/frame_field.cpp
T
tamasmeszaros 2ae2672ee9 Building igl statically and moving to the dep scripts
Fixing dep build script on Windows and removing some warnings.

Use bundled igl by default.

Not building with the dependency scripts if not explicitly stated. This way, it will stay in
Fix the libigl patch to include C source files in header only mode.
2019-06-19 14:52:55 +02:00

689 lines
17 KiB
C++

// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2015 Daniele Panozzo <daniele.panozzo@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#include "frame_field.h"
#include <igl/triangle_triangle_adjacency.h>
#include <igl/edge_topology.h>
#include <igl/per_face_normals.h>
#include <igl/copyleft/comiso/nrosy.h>
#include <iostream>
namespace igl
{
namespace copyleft
{
namespace comiso
{
class FrameInterpolator
{
public:
// Init
IGL_INLINE FrameInterpolator(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F);
IGL_INLINE ~FrameInterpolator();
// Reset constraints (at least one constraint must be present or solve will fail)
IGL_INLINE void resetConstraints();
IGL_INLINE void setConstraint(const int fid, const Eigen::VectorXd& v);
IGL_INLINE void interpolateSymmetric();
// Generate the frame field
IGL_INLINE void solve();
// Convert the frame field in the canonical representation
IGL_INLINE void frame2canonical(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v, double& theta, Eigen::VectorXd& S);
// Convert the canonical representation in a frame field
IGL_INLINE void canonical2frame(const Eigen::MatrixXd& TP, const double theta, const Eigen::VectorXd& S, Eigen::RowVectorXd& v);
IGL_INLINE Eigen::MatrixXd getFieldPerFace();
IGL_INLINE void PolarDecomposition(Eigen::MatrixXd V, Eigen::MatrixXd& U, Eigen::MatrixXd& P);
// Symmetric
Eigen::MatrixXd S;
std::vector<bool> S_c;
// -------------------------------------------------
// Face Topology
Eigen::MatrixXi TT, TTi;
// Two faces are consistent if their representative vector are taken modulo PI
std::vector<bool> edge_consistency;
Eigen::MatrixXi edge_consistency_TT;
private:
IGL_INLINE double mod2pi(double d);
IGL_INLINE double modpi2(double d);
IGL_INLINE double modpi(double d);
// Convert a direction on the tangent space into an angle
IGL_INLINE double vector2theta(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v);
// Convert an angle in a vector in the tangent space
IGL_INLINE Eigen::RowVectorXd theta2vector(const Eigen::MatrixXd& TP, const double theta);
// Interpolate the cross field (theta)
IGL_INLINE void interpolateCross();
// Compute difference between reference frames
IGL_INLINE void computek();
// Compute edge consistency
IGL_INLINE void compute_edge_consistency();
// Cross field direction
Eigen::VectorXd thetas;
std::vector<bool> thetas_c;
// Edge Topology
Eigen::MatrixXi EV, FE, EF;
std::vector<bool> isBorderEdge;
// Angle between two reference frames
// R(k) * t0 = t1
Eigen::VectorXd k;
// Mesh
Eigen::MatrixXd V;
Eigen::MatrixXi F;
// Normals per face
Eigen::MatrixXd N;
// Reference frame per triangle
std::vector<Eigen::MatrixXd> TPs;
};
FrameInterpolator::FrameInterpolator(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F)
{
using namespace std;
using namespace Eigen;
V = _V;
F = _F;
assert(V.rows() > 0);
assert(F.rows() > 0);
// Generate topological relations
igl::triangle_triangle_adjacency(F,TT,TTi);
igl::edge_topology(V,F, EV, FE, EF);
// Flag border edges
isBorderEdge.resize(EV.rows());
for(unsigned i=0; i<EV.rows(); ++i)
isBorderEdge[i] = (EF(i,0) == -1) || ((EF(i,1) == -1));
// Generate normals per face
igl::per_face_normals(V, F, N);
// Generate reference frames
for(unsigned fid=0; fid<F.rows(); ++fid)
{
// First edge
Vector3d e1 = V.row(F(fid,1)) - V.row(F(fid,0));
e1.normalize();
Vector3d e2 = N.row(fid);
e2 = e2.cross(e1);
e2.normalize();
MatrixXd TP(2,3);
TP << e1.transpose(), e2.transpose();
TPs.push_back(TP);
}
// Reset the constraints
resetConstraints();
// Compute k, differences between reference frames
computek();
// Alloc internal variables
thetas = VectorXd::Zero(F.rows());
S = MatrixXd::Zero(F.rows(),3);
compute_edge_consistency();
}
FrameInterpolator::~FrameInterpolator()
{
}
double FrameInterpolator::mod2pi(double d)
{
while(d<0)
d = d + (2.0*igl::PI);
return fmod(d, (2.0*igl::PI));
}
double FrameInterpolator::modpi2(double d)
{
while(d<0)
d = d + (igl::PI/2.0);
return fmod(d, (igl::PI/2.0));
}
double FrameInterpolator::modpi(double d)
{
while(d<0)
d = d + (igl::PI);
return fmod(d, (igl::PI));
}
double FrameInterpolator::vector2theta(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v)
{
// Project onto the tangent plane
Eigen::Vector2d vp = TP * v.transpose();
// Convert to angle
double theta = atan2(vp(1),vp(0));
return theta;
}
Eigen::RowVectorXd FrameInterpolator::theta2vector(const Eigen::MatrixXd& TP, const double theta)
{
Eigen::Vector2d vp(cos(theta),sin(theta));
return vp.transpose() * TP;
}
void FrameInterpolator::interpolateCross()
{
using namespace std;
using namespace Eigen;
//olga: was
// NRosyField nrosy(V,F);
// for (unsigned i=0; i<F.rows(); ++i)
// if(thetas_c[i])
// nrosy.setConstraintHard(i,theta2vector(TPs[i],thetas(i)));
// nrosy.solve(4);
// MatrixXd R = nrosy.getFieldPerFace();
//olga: is
Eigen::MatrixXd R;
Eigen::VectorXd S;
Eigen::VectorXi b; b.resize(F.rows(),1);
Eigen::MatrixXd bc; bc.resize(F.rows(),3);
int num = 0;
for (unsigned i=0; i<F.rows(); ++i)
if(thetas_c[i])
{
b[num] = i;
bc.row(num) = theta2vector(TPs[i],thetas(i));
num++;
}
b.conservativeResize(num,Eigen::NoChange);
bc.conservativeResize(num,Eigen::NoChange);
igl::copyleft::comiso::nrosy(V, F, b, bc, 4, R, S);
//olga:end
assert(R.rows() == F.rows());
for (unsigned i=0; i<F.rows(); ++i)
thetas(i) = vector2theta(TPs[i],R.row(i));
}
void FrameInterpolator::resetConstraints()
{
thetas_c.resize(F.rows());
S_c.resize(F.rows());
for(unsigned i=0; i<F.rows(); ++i)
{
thetas_c[i] = false;
S_c[i] = false;
}
}
void FrameInterpolator::compute_edge_consistency()
{
using namespace std;
using namespace Eigen;
// Compute per-edge consistency
edge_consistency.resize(EF.rows());
edge_consistency_TT = MatrixXi::Constant(TT.rows(),3,-1);
// For every non-border edge
for (unsigned eid=0; eid<EF.rows(); ++eid)
{
if (!isBorderEdge[eid])
{
int fid0 = EF(eid,0);
int fid1 = EF(eid,1);
double theta0 = thetas(fid0);
double theta1 = thetas(fid1);
theta0 = theta0 + k(eid);
double r = modpi(theta0-theta1);
edge_consistency[eid] = r < igl::PI/4.0 || r > 3*(igl::PI/4.0);
// Copy it into edge_consistency_TT
int i1 = -1;
int i2 = -1;
for (unsigned i=0; i<3; ++i)
{
if (TT(fid0,i) == fid1)
i1 = i;
if (TT(fid1,i) == fid0)
i2 = i;
}
assert(i1 != -1);
assert(i2 != -1);
edge_consistency_TT(fid0,i1) = edge_consistency[eid];
edge_consistency_TT(fid1,i2) = edge_consistency[eid];
}
}
}
void FrameInterpolator::computek()
{
using namespace std;
using namespace Eigen;
k.resize(EF.rows());
// For every non-border edge
for (unsigned eid=0; eid<EF.rows(); ++eid)
{
if (!isBorderEdge[eid])
{
int fid0 = EF(eid,0);
int fid1 = EF(eid,1);
Vector3d N0 = N.row(fid0);
//Vector3d N1 = N.row(fid1);
// find common edge on triangle 0 and 1
int fid0_vc = -1;
int fid1_vc = -1;
for (unsigned i=0;i<3;++i)
{
if (EV(eid,0) == F(fid0,i))
fid0_vc = i;
if (EV(eid,1) == F(fid1,i))
fid1_vc = i;
}
assert(fid0_vc != -1);
assert(fid1_vc != -1);
Vector3d common_edge = V.row(F(fid0,(fid0_vc+1)%3)) - V.row(F(fid0,fid0_vc));
common_edge.normalize();
// Map the two triangles in a new space where the common edge is the x axis and the N0 the z axis
MatrixXd P(3,3);
VectorXd o = V.row(F(fid0,fid0_vc));
VectorXd tmp = -N0.cross(common_edge);
P << common_edge, tmp, N0;
P.transposeInPlace();
MatrixXd V0(3,3);
V0.row(0) = V.row(F(fid0,0)).transpose() -o;
V0.row(1) = V.row(F(fid0,1)).transpose() -o;
V0.row(2) = V.row(F(fid0,2)).transpose() -o;
V0 = (P*V0.transpose()).transpose();
assert(V0(0,2) < 10e-10);
assert(V0(1,2) < 10e-10);
assert(V0(2,2) < 10e-10);
MatrixXd V1(3,3);
V1.row(0) = V.row(F(fid1,0)).transpose() -o;
V1.row(1) = V.row(F(fid1,1)).transpose() -o;
V1.row(2) = V.row(F(fid1,2)).transpose() -o;
V1 = (P*V1.transpose()).transpose();
assert(V1(fid1_vc,2) < 10e-10);
assert(V1((fid1_vc+1)%3,2) < 10e-10);
// compute rotation R such that R * N1 = N0
// i.e. map both triangles to the same plane
double alpha = -atan2(V1((fid1_vc+2)%3,2),V1((fid1_vc+2)%3,1));
MatrixXd R(3,3);
R << 1, 0, 0,
0, cos(alpha), -sin(alpha) ,
0, sin(alpha), cos(alpha);
V1 = (R*V1.transpose()).transpose();
assert(V1(0,2) < 10e-10);
assert(V1(1,2) < 10e-10);
assert(V1(2,2) < 10e-10);
// measure the angle between the reference frames
// k_ij is the angle between the triangle on the left and the one on the right
VectorXd ref0 = V0.row(1) - V0.row(0);
VectorXd ref1 = V1.row(1) - V1.row(0);
ref0.normalize();
ref1.normalize();
double ktemp = atan2(ref1(1),ref1(0)) - atan2(ref0(1),ref0(0));
// just to be sure, rotate ref0 using angle ktemp...
MatrixXd R2(2,2);
R2 << cos(ktemp), -sin(ktemp), sin(ktemp), cos(ktemp);
tmp = R2*ref0.head<2>();
assert(tmp(0) - ref1(0) < (0.000001));
assert(tmp(1) - ref1(1) < (0.000001));
k[eid] = ktemp;
}
}
}
void FrameInterpolator::frame2canonical(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v, double& theta, Eigen::VectorXd& S_v)
{
using namespace std;
using namespace Eigen;
RowVectorXd v0 = v.segment<3>(0);
RowVectorXd v1 = v.segment<3>(3);
// Project onto the tangent plane
Vector2d vp0 = TP * v0.transpose();
Vector2d vp1 = TP * v1.transpose();
// Assemble matrix
MatrixXd M(2,2);
M << vp0, vp1;
if (M.determinant() < 0)
M.col(1) = -M.col(1);
assert(M.determinant() > 0);
// cerr << "M: " << M << endl;
MatrixXd R,S;
PolarDecomposition(M,R,S);
// Finally, express the cross field as an angle
theta = atan2(R(1,0),R(0,0));
MatrixXd R2(2,2);
R2 << cos(theta), -sin(theta), sin(theta), cos(theta);
assert((R2-R).norm() < 10e-8);
// Convert into rotation invariant form
S = R * S * R.inverse();
// Copy in vector form
S_v = VectorXd(3);
S_v << S(0,0), S(0,1), S(1,1);
}
void FrameInterpolator::canonical2frame(const Eigen::MatrixXd& TP, const double theta, const Eigen::VectorXd& S_v, Eigen::RowVectorXd& v)
{
using namespace std;
using namespace Eigen;
assert(S_v.size() == 3);
MatrixXd S_temp(2,2);
S_temp << S_v(0), S_v(1), S_v(1), S_v(2);
// Convert angle in vector in the tangent plane
// Vector2d vp(cos(theta),sin(theta));
// First reconstruct R
MatrixXd R(2,2);
R << cos(theta), -sin(theta), sin(theta), cos(theta);
// Rotation invariant reconstruction
MatrixXd M = S_temp * R;
Vector2d vp0(M(0,0),M(1,0));
Vector2d vp1(M(0,1),M(1,1));
// Unproject the vectors
RowVectorXd v0 = vp0.transpose() * TP;
RowVectorXd v1 = vp1.transpose() * TP;
v.resize(6);
v << v0, v1;
}
void FrameInterpolator::solve()
{
interpolateCross();
interpolateSymmetric();
}
void FrameInterpolator::interpolateSymmetric()
{
using namespace std;
using namespace Eigen;
// Generate uniform Laplacian matrix
typedef Eigen::Triplet<double> triplet;
std::vector<triplet> triplets;
// Variables are stacked as x1,y1,z1,x2,y2,z2
triplets.reserve(3*4*F.rows());
MatrixXd b = MatrixXd::Zero(3*F.rows(),1);
// Build L and b
for (unsigned eid=0; eid<EF.rows(); ++eid)
{
if (!isBorderEdge[eid])
{
for (int z=0;z<2;++z)
{
// W = [w_a, w_b
// w_b, w_c]
//
// It is not symmetric
int i = EF(eid,z==0?0:1);
int j = EF(eid,z==0?1:0);
int w_a_0 = (i*3)+0;
int w_b_0 = (i*3)+1;
int w_c_0 = (i*3)+2;
int w_a_1 = (j*3)+0;
int w_b_1 = (j*3)+1;
int w_c_1 = (j*3)+2;
// Rotation to change frame
double r_a = cos(z==1?k(eid):-k(eid));
double r_b = -sin(z==1?k(eid):-k(eid));
double r_c = sin(z==1?k(eid):-k(eid));
double r_d = cos(z==1?k(eid):-k(eid));
// First term
// w_a_0 = r_a^2 w_a_1 + 2 r_a r_b w_b_1 + r_b^2 w_c_1 = 0
triplets.push_back(triplet(w_a_0,w_a_0, -1 ));
triplets.push_back(triplet(w_a_0,w_a_1, r_a*r_a ));
triplets.push_back(triplet(w_a_0,w_b_1, 2 * r_a*r_b ));
triplets.push_back(triplet(w_a_0,w_c_1, r_b*r_b ));
// Second term
// w_b_0 = r_a r_c w_a + (r_b r_c + r_a r_d) w_b + r_b r_d w_c
triplets.push_back(triplet(w_b_0,w_b_0, -1 ));
triplets.push_back(triplet(w_b_0,w_a_1, r_a*r_c ));
triplets.push_back(triplet(w_b_0,w_b_1, r_b*r_c + r_a*r_d ));
triplets.push_back(triplet(w_b_0,w_c_1, r_b*r_d ));
// Third term
// w_c_0 = r_c^2 w_a + 2 r_c r_d w_b + r_d^2 w_c
triplets.push_back(triplet(w_c_0,w_c_0, -1 ));
triplets.push_back(triplet(w_c_0,w_a_1, r_c*r_c ));
triplets.push_back(triplet(w_c_0,w_b_1, 2 * r_c*r_d ));
triplets.push_back(triplet(w_c_0,w_c_1, r_d*r_d ));
}
}
}
SparseMatrix<double> L(3*F.rows(),3*F.rows());
L.setFromTriplets(triplets.begin(), triplets.end());
triplets.clear();
// Add soft constraints
double w = 100000;
for (unsigned fid=0; fid < F.rows(); ++fid)
{
if (S_c[fid])
{
for (unsigned i=0;i<3;++i)
{
triplets.push_back(triplet(3*fid + i,3*fid + i,w));
b(3*fid + i) += w*S(fid,i);
}
}
}
SparseMatrix<double> soft(3*F.rows(),3*F.rows());
soft.setFromTriplets(triplets.begin(), triplets.end());
SparseMatrix<double> M;
M = L + soft;
// Solve Lx = b;
SparseLU<SparseMatrix<double> > solver;
solver.compute(M);
if(solver.info()!=Success)
{
std::cerr << "LU failed - frame_interpolator.cpp" << std::endl;
assert(0);
}
MatrixXd x;
x = solver.solve(b);
if(solver.info()!=Success)
{
std::cerr << "Linear solve failed - frame_interpolator.cpp" << std::endl;
assert(0);
}
S = MatrixXd::Zero(F.rows(),3);
// Copy back the result
for (unsigned i=0;i<F.rows();++i)
S.row(i) << x(i*3+0), x(i*3+1), x(i*3+2);
}
void FrameInterpolator::setConstraint(const int fid, const Eigen::VectorXd& v)
{
using namespace std;
using namespace Eigen;
double t_;
VectorXd S_;
frame2canonical(TPs[fid],v,t_,S_);
Eigen::RowVectorXd v2;
canonical2frame(TPs[fid], t_, S_, v2);
thetas(fid) = t_;
thetas_c[fid] = true;
S.row(fid) = S_;
S_c[fid] = true;
}
Eigen::MatrixXd FrameInterpolator::getFieldPerFace()
{
using namespace std;
using namespace Eigen;
MatrixXd R(F.rows(),6);
for (unsigned i=0; i<F.rows(); ++i)
{
RowVectorXd v;
canonical2frame(TPs[i],thetas(i),S.row(i),v);
R.row(i) = v;
}
return R;
}
void FrameInterpolator::PolarDecomposition(Eigen::MatrixXd V, Eigen::MatrixXd& U, Eigen::MatrixXd& P)
{
using namespace std;
using namespace Eigen;
// Polar Decomposition
JacobiSVD<MatrixXd> svd(V,Eigen::ComputeFullU | Eigen::ComputeFullV);
U = svd.matrixU() * svd.matrixV().transpose();
P = svd.matrixV() * svd.singularValues().asDiagonal() * svd.matrixV().transpose();
}
}
}
}
IGL_INLINE void igl::copyleft::comiso::frame_field(
const Eigen::MatrixXd& V,
const Eigen::MatrixXi& F,
const Eigen::VectorXi& b,
const Eigen::MatrixXd& bc1,
const Eigen::MatrixXd& bc2,
Eigen::MatrixXd& FF1,
Eigen::MatrixXd& FF2
)
{
using namespace std;
using namespace Eigen;
assert(b.size() > 0);
// Init Solver
FrameInterpolator field(V,F);
for (unsigned i=0; i<b.size(); ++i)
{
VectorXd t(6); t << bc1.row(i).transpose(), bc2.row(i).transpose();
field.setConstraint(b(i), t);
}
// Solve
field.solve();
// Copy back
MatrixXd R = field.getFieldPerFace();
FF1 = R.block(0, 0, R.rows(), 3);
FF2 = R.block(0, 3, R.rows(), 3);
}