#include "frame_field.h" #include #include #include #include #include namespace igl { class FrameInterpolator { public: // Constraint type enum ConstraintType { FREE, SOFT, HARD }; // Init IGL_INLINE FrameInterpolator(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F); IGL_INLINE ~FrameInterpolator(); // Generate the frame field IGL_INLINE void solve(bool hard_const); IGL_INLINE void setConstraint(const int fid, const Eigen::VectorXd& v, ConstraintType type = HARD); IGL_INLINE void setConstraintProportionalScale(const int fid, const double scale, ConstraintType type = HARD); // Set the ratio between smoothness and soft constraints (0 -> smoothness only, 1 -> soft constr only) IGL_INLINE void setSoftAlpha(double alpha); // Reset constraints (at least one constraint must be present or solve will fail) IGL_INLINE void resetConstraints(); // Return the current field IGL_INLINE Eigen::MatrixXd getFieldPerFace(); // Return the singularities IGL_INLINE Eigen::VectorXd getSingularityIndexPerVertex(); // -------------- This is untested and unstable code IGL_INLINE void setConstraint_polar(const int fid, const Eigen::VectorXd& v, ConstraintType type); IGL_INLINE void interpolateSymmetric_polar(const bool bilaplacian); // Generate the frame field IGL_INLINE void solve_polar(const bool bilaplacian,bool hard_const); // Convert the frame field in the canonical representation IGL_INLINE void frame2canonical_polar(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_polar(const Eigen::MatrixXd& TP, const double theta, const Eigen::VectorXd& S, Eigen::RowVectorXd& v); IGL_INLINE Eigen::MatrixXd getFieldPerFace_polar(); IGL_INLINE void PolarDecomposition(Eigen::MatrixXd V, Eigen::MatrixXd& U, Eigen::MatrixXd& P); // Symmetric Eigen::MatrixXd S_polar; std::vector S_polar_c; // ------------------------------------------------- // Face Topology Eigen::MatrixXi TT, TTi; // Two faces are consistent if their representative vector are taken modulo PI std::vector 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); // Convert the frame field in the canonical representation IGL_INLINE void frame2canonical(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v, double& theta, double& beta, Eigen::RowVectorXd& d); // Convert the canonical representation in a frame field IGL_INLINE void canonical2frame(const Eigen::MatrixXd& TP, const double theta, const double beta, const Eigen::RowVectorXd& d, Eigen::RowVectorXd& v); // Interpolate the cross field (theta) IGL_INLINE void interpolateCross(bool hard_const); // Interpolate the skewness (beta) IGL_INLINE void interpolateSkewness(); // Interpolate the scale (d) IGL_INLINE void interpolateScale(); // 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 thetas_c; // Skewness Eigen::VectorXd betas; std::vector betas_c; // Scale Eigen::MatrixXd ds; std::vector ds_c; // Soft constraints strenght double softAlpha; // Edge Topology Eigen::MatrixXi EV, FE, EF; std::vector 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; // Singularity index Eigen::VectorXd singularityIndex; // Reference frame per triangle std::vector 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::tt(V,F,TT,TTi); igl::edgetopology(V,F, EV, FE, EF); // Flag border edges isBorderEdge.resize(EV.rows()); for(unsigned i=0; i0); ds.row(fid) = ds.row(fid) * scale; ds_c[fid] = type; } void FrameInterpolator::setSoftAlpha(double alpha) { assert(alpha >= 0 && alpha <= 1); softAlpha = alpha; } Eigen::MatrixXd FrameInterpolator::getFieldPerFace() { Eigen::MatrixXd R(F.rows(),6); for (unsigned i=0; i(0); RowVectorXd v1 = v.segment<3>(3); if (debug) { cerr << "v0:" << endl << v0 << endl; cerr << "v1:" << endl << v1 << endl; } // Find canonical cross field double theta0 = mod2pi(vector2theta(TP,v0)); double theta1 = mod2pi(vector2theta(TP,v1)); //cerr << mod2pi(theta0) << " " << mod2pi(theta1) << endl; // Find the closest pair // if (mod2pi(abs(theta0 + M_PI - theta1)) < mod2pi(abs(theta0 - theta1))) // theta0 = theta0 + M_PI; // Express theta1 as a displacement wrt theta0 theta1 = mod2pi(theta1 - theta0); // Find the closest representative if (theta1 > M_PI) theta1 -= M_PI; assert(theta1 < M_PI); // Extract theta and beta theta = mod2pi(theta1/2.0 + theta0); beta = theta1/2.0; //cerr << mod2pi(beta + theta) << " " << mod2pi(-beta + theta) << endl; d.resize(2); // Find the scaling factors d(0) = v0.norm(); d(1) = v1.norm(); if (debug) { cerr << "d:" << endl << d << endl; cerr << "thetavector:" << endl << theta2vector(TP, theta) << endl; } } void FrameInterpolator::canonical2frame(const Eigen::MatrixXd& TP, const double theta, const double beta, const Eigen::RowVectorXd& d, Eigen::RowVectorXd& v) { using namespace std; using namespace Eigen; assert(d.cols() == 2 && d.rows() == 1); double theta0 = theta + M_PI - beta; double theta1 = theta + M_PI + beta; RowVectorXd v0 = theta2vector(TP,modpi(theta0)) * d(0); RowVectorXd v1 = theta2vector(TP,modpi(theta1)) * d(1); // cerr << "v0 : " << v0 << endl; // cerr << "v1 : " << v1 << endl; v.resize(6); v << v0, v1; } void FrameInterpolator::interpolateCross(bool hard_const) { using namespace std; using namespace Eigen; NRosyField nrosy(V,F); nrosy.setSoftAlpha(softAlpha); for (unsigned i=0; i triplet; std::vector triplets; triplets.reserve(4*F.rows()); VectorXd b = VectorXd::Zero(F.rows()); // Build L and b for (unsigned eid=0; eid L(F.rows(),F.rows()); L.setFromTriplets(triplets.begin(), triplets.end()); // Solve Lx = b; SimplicialLDLT > solver; solver.compute(L); if(solver.info()!=Success) { std::cerr << "Cholesky failed - frame_interpolator.cpp" << std::endl; assert(0); } VectorXd x; x = solver.solve(b); // cerr << "Skewness: " << x << endl; if(solver.info()!=Success) { std::cerr << "Linear solve failed - frame_interpolator.cpp" << std::endl; assert(0); } // Copy back the result betas = x; } void FrameInterpolator::interpolateScale() { using namespace std; using namespace Eigen; compute_edge_consistency(); // Generate enriched Laplacian matrix (see notes) // the variables here are d1, d2 typedef Eigen::Triplet triplet; std::vector triplets; triplets.reserve(4*F.rows()*2); VectorXd b = VectorXd::Zero(F.rows()*2); // Build L and b for (unsigned eid=0; eid L(F.rows()*2,F.rows()*2); L.setFromTriplets(triplets.begin(), triplets.end()); // Solve Lx = b; SimplicialLDLT > solver; solver.compute(L); if(solver.info()!=Success) { std::cerr << "Cholesky failed - frame_interpolator.cpp" << std::endl; assert(0); } VectorXd x; x = solver.solve(b); // cerr << "Scale: " << endl << x << endl; if(solver.info()!=Success) { std::cerr << "Linear solve failed - frame_interpolator.cpp" << std::endl; assert(0); } // Copy back the result ds << x.block(0, 0, ds.rows(), 1), x.block(ds.rows(), 0, ds.rows(), 1); } void FrameInterpolator::resetConstraints() { thetas_c.resize(F.rows()); betas_c.resize(F.rows()); ds_c.resize(F.rows()); S_polar_c.resize(F.rows()); for(unsigned i=0; i 3*(M_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(); assert(tmp(0) - ref1(0) < (0.000001)); assert(tmp(1) - ref1(1) < (0.000001)); k[eid] = ktemp; } } } void FrameInterpolator::frame2canonical_polar(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 PolarDecomposition(M,R,S); // cerr << M << endl; // cerr << "--------" << endl; // cerr << R*S << endl; // Finally, express the cross field as an angle theta = atan2(R(1,0),R(0,0)); // atan2(R(1,0),R(0,0)); MatrixXd R2(2,2); R2 << cos(theta), -sin(theta), sin(theta), cos(theta); // cerr << "R: " << R << endl; // cerr << "Should be zero2: " << R2-R << endl; assert((R2-R).norm() < 10e-8); // Convert into rotation invariant form S = R * S * R.inverse(); // cerr << "R" << endl << R << endl; // cerr << "S" << endl << S << endl; // Copy in vector form S_v = VectorXd(3); S_v << S(0,0), S(0,1), S(1,1); } void FrameInterpolator::canonical2frame_polar(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(2,2); S << 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.col(0) << cos(theta), sin(theta); // R(0,1) = -R(1,0); // R(1,1) = R(0,0); R << cos(theta), -sin(theta), sin(theta), cos(theta); // cerr << "R2" << endl << R << endl; // cerr << "S2" << endl << S << endl; // Multiply to reconstruct //MatrixXd M = R * S; // Rotation invariant reconstruction MatrixXd M = S * R; // cerr << "M2: " << M << endl; 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_polar(const bool bilaplacian, bool hard_const) { interpolateCross(hard_const); //interpolateSymmetric_polar(bilaplacian); interpolateSymmetric_polar(false); } void FrameInterpolator::interpolateSymmetric_polar(const bool bilaplacian) { using namespace std; using namespace Eigen; //compute_edge_consistency(); // Generate uniform Laplacian matrix (see notes) typedef Eigen::Triplet triplet; std::vector 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 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_polar_c[fid] != FREE) { for (unsigned i=0;i<3;++i) { triplets.push_back(triplet(3*fid + i,3*fid + i,w)); b(3*fid + i) += w*S_polar(fid,i); } } } SparseMatrix soft(3*F.rows(),3*F.rows()); soft.setFromTriplets(triplets.begin(), triplets.end()); SparseMatrix M; if (!bilaplacian) M = L + soft; else M = L * L + soft; // Solve Lx = b; // MatrixXd Dense(L); // // cerr << "Is sym:" << (Dense - Dense.transpose()).maxCoeff() << endl; // SimplicialLDLT > solver; SparseLU > solver; solver.compute(M); if(solver.info()!=Success) { std::cerr << "Cholesky failed - frame_interpolator.cpp" << std::endl; assert(0); } MatrixXd x; x = solver.solve(b); // cerr << "Skewness: " << x << endl; if(solver.info()!=Success) { std::cerr << "Linear solve failed - frame_interpolator.cpp" << std::endl; assert(0); } S_polar = MatrixXd::Zero(F.rows(),3); // Copy back the result for (unsigned i=0;i S_es(T_POLAR); VectorXd eivals = S_es.eigenvalues(); // cerr << "eivals:" << eivals << endl; double t = eivals.minCoeff(); if (t < 0) { cerr << "NOOOOOOOO!" << endl; exit(1); } // cerr << S_polar << endl; } } } void FrameInterpolator::setConstraint_polar(const int fid, const Eigen::VectorXd& v, ConstraintType type) { using namespace std; using namespace Eigen; double t_; VectorXd S_; frame2canonical_polar(TPs[fid],v,t_,S_); Eigen::RowVectorXd v2; canonical2frame_polar(TPs[fid], t_, S_, v2); // cerr << "Constraint: " << t_ << " " << S_ << endl; // cerr << "Compare: " << endl << v.transpose() << endl << v2 << endl; // cerr << "Diff: " << endl << v.transpose() - v2 << endl; // cerr << "Diff: " << endl << v.transpose() + v2 << endl; thetas(fid) = t_; thetas_c[fid] = type; S_polar.row(fid) = S_; S_polar_c[fid] = type; } Eigen::MatrixXd FrameInterpolator::getFieldPerFace_polar() { using namespace std; using namespace Eigen; MatrixXd R(F.rows(),6); for (unsigned i=0; i svd(V,Eigen::ComputeFullU | Eigen::ComputeFullV); U = svd.matrixU() * svd.matrixV().transpose(); P = svd.matrixV() * svd.singularValues().asDiagonal() * svd.matrixV().transpose(); // If not SPD, change sign of both // Eigen::SelfAdjointEigenSolver S_es(P); // if (S_es.eigenvalues().minCoeff() < 0) // { // U = -U; // P = -P; // } } } IGL_INLINE void igl::frame_field( const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, const Eigen::VectorXi& b, const Eigen::MatrixXd& bc1, const Eigen::MatrixXd& bc2, Eigen::MatrixXd& F1, Eigen::MatrixXd& F2, Eigen::VectorXd& S ) { using namespace std; using namespace Eigen; assert(b.size() > 0); // Init Solver FrameInterpolator field(V,F); for (unsigned i=0; i