// This file is part of libigl, a simple c++ geometry processing library. // // Copyright (C) 2014 Daniele Panozzo // // 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 "nrosy.h" #include "nrosy.h" #include "../../triangle_triangle_adjacency.h" #include "../../edge_topology.h" #include "../../per_face_normals.h" #include #include "../../PI.h" #include #include #include #include #include #include #include #include namespace igl { namespace copyleft { namespace comiso { class NRosyField { public: // Init IGL_INLINE NRosyField(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F); // Generate the N-rosy field // N degree of the rosy field // round separately: round the integer variables one at a time, slower but higher quality IGL_INLINE void solve(int N = 4); // Set a hard constraint on fid // fid: face id // v: direction to fix (in 3d) IGL_INLINE void setConstraintHard(int fid, const Eigen::Vector3d& v); // Set a soft constraint on fid // fid: face id // w: weight of the soft constraint, clipped between 0 and 1 // v: direction to fix (in 3d) IGL_INLINE void setConstraintSoft(int fid, double w, const Eigen::Vector3d& v); // 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(); // Compute singularity indexes IGL_INLINE void findCones(int N); // Return the singularities IGL_INLINE Eigen::VectorXd getSingularityIndexPerVertex(); private: // Compute angle differences between reference frames IGL_INLINE void computek(); // Remove useless matchings IGL_INLINE void reduceSpace(); // Prepare the system matrix IGL_INLINE void prepareSystemMatrix(int N); // Solve with roundings using CoMIso IGL_INLINE void solveRoundings(); // Convert a vector in 3d to an angle wrt the local reference system IGL_INLINE double convert3DtoLocal(unsigned fid, const Eigen::Vector3d& v); // Convert an angle wrt the local reference system to a 3d vector IGL_INLINE Eigen::Vector3d convertLocalto3D(unsigned fid, double a); // Compute the per vertex angle defect IGL_INLINE Eigen::VectorXd angleDefect(); // Temporary variable for the field Eigen::VectorXd angles; // Hard constraints Eigen::VectorXd hard; std::vector isHard; // Soft constraints Eigen::VectorXd soft; Eigen::VectorXd wSoft; double softAlpha; // Face Topology Eigen::MatrixXi TT, TTi; // Edge Topology Eigen::MatrixXi EV, FE, EF; std::vector isBorderEdge; // Per Edge information // Angle between two reference frames Eigen::VectorXd k; // Jumps Eigen::VectorXi p; std::vector pFixed; // 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; // System stuff Eigen::SparseMatrix A; Eigen::VectorXd b; Eigen::VectorXi tag_t; Eigen::VectorXi tag_p; }; } // NAMESPACE COMISO } // NAMESPACE COPYLEFT } // NAMESPACE IGL igl::copyleft::comiso::NRosyField::NRosyField(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F) { 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= 0 && alpha < 1); softAlpha = alpha; } void igl::copyleft::comiso::NRosyField::prepareSystemMatrix(const int N) { double Nd = N; // Minimize the MIQ energy // Energy on edge ij is // (t_i - t_j + kij + pij*(2*pi/N))^2 // Partial derivatives: // t_i: 2 ( t_i - t_j + kij + pij*(2*pi/N)) = 0 // t_j: 2 (-t_i + t_j - kij - pij*(2*pi/N)) = 0 // pij: 4pi/N ( t_i - t_j + kij + pij*(2*pi/N)) = 0 // // t_i t_j pij kij // t_i [ 2 -2 4pi/N 2 ] // t_j [ -2 2 -4pi/N -2 ] // pij [ 4pi/N -4pi/N 2*(2pi/N)^2 4pi/N ] // Count and tag the variables tag_t = Eigen::VectorXi::Constant(F.rows(),-1); std::vector id_t; size_t count = 0; for(unsigned i=0; i id_p; for(unsigned i=0; i > T; T.reserve(3 * 4 * count_p); for(auto eid : id_p) { int i = EF(eid, 0); int j = EF(eid, 1); bool isFixed_i = isHard[i]; bool isFixed_j = isHard[j]; bool isFixed_p = pFixed[eid]; int row; // (i)-th row: t_i [ 2 -2 4pi/N 2 ] if (!isFixed_i) { row = tag_t[i]; T.emplace_back(row, tag_t[i], 2); if (isFixed_j) b(row) += 2 * hard[j]; else T.emplace_back(row, tag_t[j], -2); if (isFixed_p) b(row) += -((4. * igl::PI) / Nd) * p[eid]; else T.emplace_back(row, tag_p[eid], ((4. * igl::PI) / Nd)); b(row) += -2 * k[eid]; assert(hard[i] == hard[i]); assert(hard[j] == hard[j]); assert(p[eid] == p[eid]); assert(k[eid] == k[eid]); assert(b(row) == b(row)); } // (j)+1 -th row: t_j [ -2 2 -4pi/N -2 ] if (!isFixed_j) { row = tag_t[j]; T.emplace_back(row, tag_t[j], 2); if (isFixed_i) b(row) += 2 * hard[i]; else T.emplace_back(row, tag_t[i], -2); if (isFixed_p) b(row) += ((4. * igl::PI) / Nd) * p[eid]; else T.emplace_back(row, tag_p[eid], -((4. * igl::PI) / Nd)); b(row) += 2 * k[eid]; assert(k[eid] == k[eid]); assert(b(row) == b(row)); } // (r*3)+2 -th row: pij [ 4pi/N -4pi/N 2*(2pi/N)^2 4pi/N ] if (!isFixed_p) { row = tag_p[eid]; T.emplace_back(row, tag_p[eid], (2. * pow(((2. * igl::PI) / Nd), 2))); if (isFixed_i) b(row) += -(4. * igl::PI) / Nd * hard[i]; else T.emplace_back(row, tag_t[i], (4. * igl::PI) / Nd); if (isFixed_j) b(row) += (4. * igl::PI) / Nd * hard[j]; else T.emplace_back(row,tag_t[j], -(4. * igl::PI) / Nd); b(row) += - (4 * igl::PI)/Nd * k[eid]; assert(k[eid] == k[eid]); assert(b(row) == b(row)); } } A.resize(count_t + count_p, count_t + count_p); A.setFromTriplets(T.begin(), T.end()); // Soft constraints bool addSoft = false; for(unsigned i=0; i > TSoft; TSoft.reserve(2 * count_p); for(unsigned i=0; i ASoft(count_t + count_p, count_t + count_p); ASoft.setFromTriplets(TSoft.begin(), TSoft.end()); A = (1.0 - softAlpha) * A + softAlpha * ASoft; b = b * (1.0 - softAlpha) + bSoft * softAlpha; } } void igl::copyleft::comiso::NRosyField::solveRoundings() { unsigned n = A.rows(); gmm::col_matrix< gmm::wsvector< double > > gmm_A(n, n); std::vector gmm_b(n); std::vector ids_to_round; std::vector x(n); // Copy A for (int k=0; k::InnerIterator it(A, k); it; ++it) { gmm_A(it.row(),it.col()) += it.value(); } // Copy b for(unsigned int i = 0; i < n;++i) gmm_b[i] = b[i]; // Set variables to round ids_to_round.clear(); for(unsigned i=0; i > gmm_C(0, n); COMISO::ConstrainedSolver cs; cs.solve(gmm_C, gmm_A, x, gmm_b, ids_to_round, 0.0, false); // Copy the result back for(unsigned i=0; i igl::PI) ktemp -= 2*igl::PI; // just to be sure, rotate ref0 using angle ktemp... Eigen::MatrixXd R2(2,2); R2 << std::cos(-ktemp), -std::sin(-ktemp), std::sin(-ktemp), std::cos(-ktemp); tmp = R2*ref0.head<2>(); assert(tmp(0) - ref1(0) < 1e-10); assert(tmp(1) - ref1(1) < 1e-10); k[eid] = ktemp; } } } void igl::copyleft::comiso::NRosyField::reduceSpace() { // All variables are free in the beginning for(unsigned int i = 0; i < EV.rows(); ++i) pFixed[i] = false; std::vector visited(EV.rows(), false); std::vector starting(EV.rows(), false); std::queue q; for(unsigned int i = 0; i < F.rows(); ++i) if (isHard[i] || wSoft[i] != 0) { q.push(i); starting[i] = true; } // Reduce the search space (see MI paper) while (!q.empty()) { int c = q.front(); q.pop(); visited[c] = true; for(int i=0; i<3; ++i) { int eid = FE(c,i); int fid = TT(c,i); // skip borders if (fid != -1) { assert((EF(eid,0) == c && EF(eid,1) == fid) || (EF(eid,1) == c && EF(eid,0) == fid)); // for every neighbouring face if (!visited[fid] && !starting[fid]) { pFixed[eid] = true; p[eid] = 0; visited[fid] = true; q.push(fid); } } else { // fix borders pFixed[eid] = true; p[eid] = 0; } } } // Force matchings between fixed faces for(unsigned int i = 0; i < F.rows();++i) { if (isHard[i]) { for(unsigned int j = 0; j < 3; ++j) { int fid = TT(i,j); if ((fid!=-1) && (isHard[fid])) { // i and fid are adjacent and fixed int eid = FE(i,j); int fid0 = EF(eid,0); int fid1 = EF(eid,1); pFixed[eid] = true; p[eid] = (int)std::round(2.0 / igl::PI * (hard(fid1) - hard(fid0) - k(eid))); } } } } } double igl::copyleft::comiso::NRosyField::convert3DtoLocal(unsigned fid, const Eigen::Vector3d& v) { // Project onto the tangent plane Eigen::Vector2d vp = TPs[fid] * v; // Convert to angle return std::atan2(vp(1), vp(0)); } Eigen::Vector3d igl::copyleft::comiso::NRosyField::convertLocalto3D(unsigned fid, double a) { Eigen::Vector2d vp(std::cos(a), std::sin(a)); return vp.transpose() * TPs[fid]; } Eigen::VectorXd igl::copyleft::comiso::NRosyField::angleDefect() { Eigen::VectorXd A = Eigen::VectorXd::Constant(V.rows(), 2*igl::PI); for (unsigned int i = 0; i < F.rows(); ++i) { for (int j = 0; j < 3; ++j) { Eigen::VectorXd a = V.row(F(i,(j+1)%3)) - V.row(F(i,j)); Eigen::VectorXd b = V.row(F(i,(j+2)%3)) - V.row(F(i,j)); double t = a.transpose() * b; if(a.norm() > 0. && b.norm() > 0.) t /= (a.norm() * b.norm()); else throw std::runtime_error("igl::copyleft::comiso::NRosyField::angleDefect: Division by zero!"); A(F(i, j)) -= std::acos(std::max(std::min(t, 1.), -1.)); } } return A; } void igl::copyleft::comiso::NRosyField::findCones(int N) { // Compute I0, see http://www.graphics.rwth-aachen.de/media/papers/bommes_zimmer_2009_siggraph_011.pdf for details singularityIndex = Eigen::VectorXd::Zero(V.rows()); // first the k for (unsigned i = 0; i < EV.rows(); ++i) { if (!isBorderEdge[i]) { singularityIndex(EV(i, 0)) += k(i); singularityIndex(EV(i, 1)) -= k(i); } } // then the A Eigen::VectorXd A = angleDefect(); singularityIndex += A; // normalize singularityIndex /= (2 * igl::PI); // round to integer (remove numerical noise) for (unsigned i = 0; i < singularityIndex.size(); ++i) singularityIndex(i) = round(singularityIndex(i)); for (unsigned i = 0; i < EV.rows(); ++i) { if (!isBorderEdge[i]) { singularityIndex(EV(i, 0)) += double(p(i)) / double(N); singularityIndex(EV(i, 1)) -= double(p(i)) / double(N); } } // Clear the vertices on the edges for (unsigned i = 0; i < EV.rows(); ++i) { if (isBorderEdge[i]) { singularityIndex(EV(i,0)) = 0; singularityIndex(EV(i,1)) = 0; } } } Eigen::VectorXd igl::copyleft::comiso::NRosyField::getSingularityIndexPerVertex() { return singularityIndex; } IGL_INLINE void igl::copyleft::comiso::nrosy( const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, const Eigen::VectorXi& b, const Eigen::MatrixXd& bc, const Eigen::VectorXi& b_soft, const Eigen::VectorXd& w_soft, const Eigen::MatrixXd& bc_soft, const int N, const double soft, Eigen::MatrixXd& R, Eigen::VectorXd& S ) { // Init solver igl::copyleft::comiso::NRosyField solver(V, F); // Add hard constraints for (unsigned i = 0; i < b.size(); ++i) solver.setConstraintHard(b(i), bc.row(i)); // Add soft constraints for (unsigned i = 0; i < b_soft.size(); ++i) solver.setConstraintSoft(b_soft(i), w_soft(i), bc_soft.row(i)); // Set the soft constraints global weight solver.setSoftAlpha(soft); // Interpolate solver.solve(N); // Copy the result back R = solver.getFieldPerFace(); // Extract singularity indices S = solver.getSingularityIndexPerVertex(); } IGL_INLINE void igl::copyleft::comiso::nrosy( const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, const Eigen::VectorXi& b, const Eigen::MatrixXd& bc, const int N, Eigen::MatrixXd& R, Eigen::VectorXd& S ) { // Init solver igl::copyleft::comiso::NRosyField solver(V, F); // Add hard constraints for (unsigned i= 0; i < b.size(); ++i) solver.setConstraintHard(b(i), bc.row(i)); // Interpolate solver.solve(N); // Copy the result back R = solver.getFieldPerFace(); // Extract singularity indices S = solver.getSingularityIndexPerVertex(); }