IGL_INLINE igl::SolverStatus igl::active_set( const Eigen::SparseMatrix<AT>& A, const Eigen::PlainObjectBase<DerivedB> & B, const Eigen::PlainObjectBase<Derivedknown> & known, const Eigen::PlainObjectBase<DerivedY> & Y, const Eigen::SparseMatrix<AeqT>& Aeq, const Eigen::PlainObjectBase<DerivedBeq> & Beq, const Eigen::SparseMatrix<AieqT>& Aieq, const Eigen::PlainObjectBase<DerivedBieq> & Bieq, const Eigen::PlainObjectBase<Derivedlx> & p_lx, const Eigen::PlainObjectBase<Derivedux> & p_ux, const igl::active_set_params & params, Eigen::PlainObjectBase<DerivedZ> & Z ) { //#define ACTIVE_SET_CPP_DEBUG #if defined(ACTIVE_SET_CPP_DEBUG) && !defined(_MSC_VER) # warning "ACTIVE_SET_CPP_DEBUG" #endif using namespace Eigen; using namespace std; SolverStatus ret = SOLVER_STATUS_ERROR; const int n = A.rows(); assert(n == A.cols() && "A must be square"); // Discard const qualifiers //if(B.size() == 0) //{ // B = Eigen::PlainObjectBase<DerivedB>::Zero(n,1); //} assert(n == B.rows() && "B.rows() must match A.rows()"); assert(B.cols() == 1 && "B must be a column vector"); assert(Y.cols() == 1 && "Y must be a column vector"); assert((Aeq.size() == 0 && Beq.size() == 0) || Aeq.cols() == n); assert((Aeq.size() == 0 && Beq.size() == 0) || Aeq.rows() == Beq.rows()); assert((Aeq.size() == 0 && Beq.size() == 0) || Beq.cols() == 1); assert((Aieq.size() == 0 && Bieq.size() == 0) || Aieq.cols() == n); assert((Aieq.size() == 0 && Bieq.size() == 0) || Aieq.rows() == Bieq.rows()); assert((Aieq.size() == 0 && Bieq.size() == 0) || Bieq.cols() == 1); Eigen::Matrix<typename Derivedlx::Scalar,Eigen::Dynamic,1> lx; Eigen::Matrix<typename Derivedux::Scalar,Eigen::Dynamic,1> ux; if(p_lx.size() == 0) { lx = Eigen::PlainObjectBase<Derivedlx>::Constant( n,1,-numeric_limits<typename Derivedlx::Scalar>::max()); }else { lx = p_lx; } if(ux.size() == 0) { ux = Eigen::PlainObjectBase<Derivedux>::Constant( n,1,numeric_limits<typename Derivedux::Scalar>::max()); }else { ux = p_ux; } assert(lx.rows() == n && "lx must have n rows"); assert(ux.rows() == n && "ux must have n rows"); assert(ux.cols() == 1 && "lx must be a column vector"); assert(lx.cols() == 1 && "ux must be a column vector"); assert((ux.array()-lx.array()).minCoeff() > 0 && "ux(i) must be > lx(i)"); if(Z.size() != 0) { // Initial guess should have correct size assert(Z.rows() == n && "Z must have n rows"); assert(Z.cols() == 1 && "Z must be a column vector"); } assert(known.cols() == 1 && "known must be a column vector"); // Number of knowns const int nk = known.size(); // Initialize active sets typedef int BOOL; #define TRUE 1 #define FALSE 0 Matrix<BOOL,Dynamic,1> as_lx = Matrix<BOOL,Dynamic,1>::Constant(n,1,FALSE); Matrix<BOOL,Dynamic,1> as_ux = Matrix<BOOL,Dynamic,1>::Constant(n,1,FALSE); Matrix<BOOL,Dynamic,1> as_ieq = Matrix<BOOL,Dynamic,1>::Constant(Aieq.rows(),1,FALSE); // Keep track of previous Z for comparison PlainObjectBase<DerivedZ> old_Z; old_Z = PlainObjectBase<DerivedZ>::Constant( n,1,numeric_limits<typename DerivedZ::Scalar>::max()); int iter = 0; while(true) { #ifdef ACTIVE_SET_CPP_DEBUG cout<<"Iteration: "<<iter<<":"<<endl; cout<<" pre"<<endl; #endif // FIND BREACHES OF CONSTRAINTS int new_as_lx = 0; int new_as_ux = 0; int new_as_ieq = 0; if(Z.size() > 0) { for(int z = 0;z < n;z++) { if(Z(z) < lx(z)) { new_as_lx += (as_lx(z)?0:1); //new_as_lx++; as_lx(z) = TRUE; } if(Z(z) > ux(z)) { new_as_ux += (as_ux(z)?0:1); //new_as_ux++; as_ux(z) = TRUE; } } if(Aieq.rows() > 0) { PlainObjectBase<DerivedZ> AieqZ; AieqZ = Aieq*Z; for(int a = 0;a<Aieq.rows();a++) { if(AieqZ(a) > Bieq(a)) { new_as_ieq += (as_ieq(a)?0:1); as_ieq(a) = TRUE; } } } #ifdef ACTIVE_SET_CPP_DEBUG cout<<" new_as_lx: "<<new_as_lx<<endl; cout<<" new_as_ux: "<<new_as_ux<<endl; #endif const double diff = (Z-old_Z).squaredNorm(); #ifdef ACTIVE_SET_CPP_DEBUG cout<<"diff: "<<diff<<endl; #endif if(diff < params.solution_diff_threshold) { ret = SOLVER_STATUS_CONVERGED; break; } old_Z = Z; } const int as_lx_count = count(as_lx.data(),as_lx.data()+n,TRUE); const int as_ux_count = count(as_ux.data(),as_ux.data()+n,TRUE); const int as_ieq_count = count(as_ieq.data(),as_ieq.data()+as_ieq.size(),TRUE); #ifndef NDEBUG { int count = 0; for(int a = 0;a<as_ieq.size();a++) { if(as_ieq(a)) { assert(as_ieq(a) == TRUE); count++; } } assert(as_ieq_count == count); } #endif // PREPARE FIXED VALUES PlainObjectBase<Derivedknown> known_i; known_i.resize(nk + as_lx_count + as_ux_count,1); PlainObjectBase<DerivedY> Y_i; Y_i.resize(nk + as_lx_count + as_ux_count,1); { known_i.block(0,0,known.rows(),known.cols()) = known; Y_i.block(0,0,Y.rows(),Y.cols()) = Y; int k = nk; // Then all lx for(int z = 0;z < n;z++) { if(as_lx(z)) { known_i(k) = z; Y_i(k) = lx(z); k++; } } // Finally all ux for(int z = 0;z < n;z++) { if(as_ux(z)) { known_i(k) = z; Y_i(k) = ux(z); k++; } } assert(k==Y_i.size()); assert(k==known_i.size()); } //cout<<matlab_format((known_i.array()+1).eval(),"known_i")<<endl; // PREPARE EQUALITY CONSTRAINTS VectorXi as_ieq_list(as_ieq_count,1); // Gather active constraints and resp. rhss PlainObjectBase<DerivedBeq> Beq_i; Beq_i.resize(Beq.rows()+as_ieq_count,1); Beq_i.head(Beq.rows()) = Beq; { int k =0; for(int a=0;a<as_ieq.size();a++) { if(as_ieq(a)) { assert(k<as_ieq_list.size()); as_ieq_list(k)=a; Beq_i(Beq.rows()+k,0) = Bieq(k,0); k++; } } assert(k == as_ieq_count); } // extract active constraint rows SparseMatrix<AeqT> Aeq_i,Aieq_i; slice(Aieq,as_ieq_list,1,Aieq_i); // Append to equality constraints cat(1,Aeq,Aieq_i,Aeq_i); min_quad_with_fixed_data<AT> data; #ifndef NDEBUG { // NO DUPES! Matrix<BOOL,Dynamic,1> fixed = Matrix<BOOL,Dynamic,1>::Constant(n,1,FALSE); for(int k = 0;k<known_i.size();k++) { assert(!fixed[known_i(k)]); fixed[known_i(k)] = TRUE; } } #endif Eigen::PlainObjectBase<DerivedZ> sol; if(known_i.size() == A.rows()) { // Everything's fixed? #ifdef ACTIVE_SET_CPP_DEBUG cout<<" everything's fixed."<<endl; #endif Z.resize(A.rows(),Y_i.cols()); slice_into(Y_i,known_i,1,Z); sol.resize(0,Y_i.cols()); assert(Aeq_i.rows() == 0 && "All fixed but linearly constrained"); }else { #ifdef ACTIVE_SET_CPP_DEBUG cout<<" min_quad_with_fixed_precompute"<<endl; #endif if(!min_quad_with_fixed_precompute(A,known_i,Aeq_i,params.Auu_pd,data)) { cerr<<"Error: min_quad_with_fixed precomputation failed."<<endl; if(iter > 0 && Aeq_i.rows() > Aeq.rows()) { cerr<<" *Are you sure rows of [Aeq;Aieq] are linearly independent?*"<< endl; } ret = SOLVER_STATUS_ERROR; break; } #ifdef ACTIVE_SET_CPP_DEBUG cout<<" min_quad_with_fixed_solve"<<endl; #endif if(!min_quad_with_fixed_solve(data,B,Y_i,Beq_i,Z,sol)) { cerr<<"Error: min_quad_with_fixed solve failed."<<endl; ret = SOLVER_STATUS_ERROR; break; } //cout<<matlab_format((Aeq*Z-Beq).eval(),"cr")<<endl; //cout<<matlab_format(Z,"Z")<<endl; #ifdef ACTIVE_SET_CPP_DEBUG cout<<" post"<<endl; #endif // Computing Lagrange multipliers needs to be adjusted slightly if A is not symmetric assert(data.Auu_sym); } // Compute Lagrange multiplier values for known_i SparseMatrix<AT> Ak; // Slow slice(A,known_i,1,Ak); Eigen::PlainObjectBase<DerivedB> Bk; slice(B,known_i,Bk); MatrixXd Lambda_known_i = -(0.5*Ak*Z + 0.5*Bk); // reverse the lambda values for lx Lambda_known_i.block(nk,0,as_lx_count,1) = (-1*Lambda_known_i.block(nk,0,as_lx_count,1)).eval(); // Extract Lagrange multipliers for Aieq_i (always at back of sol) VectorXd Lambda_Aieq_i(Aieq_i.rows(),1); for(int l = 0;l<Aieq_i.rows();l++) { Lambda_Aieq_i(Aieq_i.rows()-1-l) = sol(sol.rows()-1-l); } // Remove from active set for(int l = 0;l<as_lx_count;l++) { if(Lambda_known_i(nk + l) < params.inactive_threshold) { as_lx(known_i(nk + l)) = FALSE; } } for(int u = 0;u<as_ux_count;u++) { if(Lambda_known_i(nk + as_lx_count + u) < params.inactive_threshold) { as_ux(known_i(nk + as_lx_count + u)) = FALSE; } } for(int a = 0;a<as_ieq_count;a++) { if(Lambda_Aieq_i(a) < params.inactive_threshold) { as_ieq(as_ieq_list(a)) = FALSE; } } iter++; //cout<<iter<<endl; if(params.max_iter>0 && iter>=params.max_iter) { ret = SOLVER_STATUS_MAX_ITER; break; } } return ret; }
IGL_INLINE void igl::orientable_patches( const Eigen::PlainObjectBase<DerivedF> & F, Eigen::PlainObjectBase<DerivedC> & C, Eigen::SparseMatrix<AScalar> & A) { using namespace Eigen; using namespace std; // simplex size assert(F.cols() == 3); // List of all "half"-edges: 3*#F by 2 Matrix<typename DerivedF::Scalar, Dynamic, 2> allE,sortallE,uE; allE.resize(F.rows()*3,2); Matrix<int,Dynamic,2> IX; VectorXi IA,IC; allE.block(0*F.rows(),0,F.rows(),1) = F.col(1); allE.block(0*F.rows(),1,F.rows(),1) = F.col(2); allE.block(1*F.rows(),0,F.rows(),1) = F.col(2); allE.block(1*F.rows(),1,F.rows(),1) = F.col(0); allE.block(2*F.rows(),0,F.rows(),1) = F.col(0); allE.block(2*F.rows(),1,F.rows(),1) = F.col(1); // Sort each row sort(allE,2,true,sortallE,IX); //IC(i) tells us where to find sortallE(i,:) in uE: // so that sortallE(i,:) = uE(IC(i),:) unique_rows(sortallE,uE,IA,IC); // uE2FT(e,f) = 1 means face f is adjacent to unique edge e vector<Triplet<AScalar> > uE2FTijv(IC.rows()); for(int e = 0;e<IC.rows();e++) { uE2FTijv[e] = Triplet<AScalar>(e%F.rows(),IC(e),1); } SparseMatrix<AScalar> uE2FT(F.rows(),uE.rows()); uE2FT.setFromTriplets(uE2FTijv.begin(),uE2FTijv.end()); // kill non-manifold edges for(int j=0; j<(int)uE2FT.outerSize();j++) { int degree = 0; for(typename SparseMatrix<AScalar>::InnerIterator it (uE2FT,j); it; ++it) { degree++; } // Iterate over inside if(degree > 2) { for(typename SparseMatrix<AScalar>::InnerIterator it (uE2FT,j); it; ++it) { uE2FT.coeffRef(it.row(),it.col()) = 0; } } } // Face-face Adjacency matrix SparseMatrix<AScalar> uE2F; uE2F = uE2FT.transpose().eval(); A = uE2FT*uE2F; // All ones for(int j=0; j<A.outerSize();j++) { // Iterate over inside for(typename SparseMatrix<AScalar>::InnerIterator it (A,j); it; ++it) { if(it.value() > 1) { A.coeffRef(it.row(),it.col()) = 1; } } } //% Connected components are patches //%C = components(A); % alternative to graphconncomp from matlab_bgl //[~,C] = graphconncomp(A); // graph connected components using boost components(A,C); }
IGL_INLINE void igl::upsample( const Eigen::PlainObjectBase<DerivedV>& V, const Eigen::PlainObjectBase<DerivedF>& F, Eigen::PlainObjectBase<DerivedNV>& NV, Eigen::PlainObjectBase<DerivedNF>& NF) { // Use "in place" wrapper instead assert(&V != &NV); assert(&F != &NF); using namespace std; using namespace Eigen; Eigen::Matrix< typename DerivedF::Scalar,Eigen::Dynamic,Eigen::Dynamic> FF,FFi; triangle_triangle_adjacency(F,FF,FFi); // TODO: Cache optimization missing from here, it is a mess // Compute the number and positions of the vertices to insert (on edges) Eigen::MatrixXi NI = Eigen::MatrixXi::Constant(FF.rows(),FF.cols(),-1); int counter = 0; for(int i=0;i<FF.rows();++i) { for(int j=0;j<3;++j) { if(NI(i,j) == -1) { NI(i,j) = counter; if (FF(i,j) != -1) // If it is not a border NI(FF(i,j),FFi(i,j)) = counter; ++counter; } } } int n_odd = V.rows(); int n_even = counter; // Preallocate NV and NF NV.resize(V.rows()+n_even,V.cols()); NF.resize(F.rows()*4,3); // Fill the odd vertices position NV.block(0,0,V.rows(),V.cols()) = V; // Fill the even vertices position for(int i=0;i<FF.rows();++i) { for(int j=0;j<3;++j) { NV.row(NI(i,j) + n_odd) = 0.5 * V.row(F(i,j)) + 0.5 * V.row(F(i,(j+1)%3)); } } // Build the new topology (Every face is replaced by four) for(int i=0; i<F.rows();++i) { VectorXi VI(6); VI << F(i,0), F(i,1), F(i,2), NI(i,0) + n_odd, NI(i,1) + n_odd, NI(i,2) + n_odd; VectorXi f0(3), f1(3), f2(3), f3(3); f0 << VI(0), VI(3), VI(5); f1 << VI(1), VI(4), VI(3); f2 << VI(3), VI(4), VI(5); f3 << VI(4), VI(2), VI(5); NF.row((i*4)+0) = f0; NF.row((i*4)+1) = f1; NF.row((i*4)+2) = f2; NF.row((i*4)+3) = f3; } }
IGL_INLINE bool igl::biharmonic_coordinates( const Eigen::PlainObjectBase<DerivedV> & V, const Eigen::PlainObjectBase<DerivedT> & T, const std::vector<std::vector<SType> > & S, const int k, Eigen::PlainObjectBase<DerivedW> & W) { using namespace Eigen; using namespace std; // This is not the most efficient way to build A, but follows "Linear // Subspace Design for Real-Time Shape Deformation" [Wang et al. 2015]. SparseMatrix<double> A; { SparseMatrix<double> N,Z,L,K,M; normal_derivative(V,T,N); Array<bool,Dynamic,1> I; Array<bool,Dynamic,Dynamic> C; on_boundary(T,I,C); { std::vector<Triplet<double> >ZIJV; for(int t =0;t<T.rows();t++) { for(int f =0;f<T.cols();f++) { if(C(t,f)) { const int i = t+f*T.rows(); for(int c = 1;c<T.cols();c++) { ZIJV.emplace_back(T(t,(f+c)%T.cols()),i,1); } } } } Z.resize(V.rows(),N.rows()); Z.setFromTriplets(ZIJV.begin(),ZIJV.end()); N = (Z*N).eval(); } cotmatrix(V,T,L); K = N+L; massmatrix(V,T,MASSMATRIX_TYPE_DEFAULT,M); // normalize M /= ((VectorXd)M.diagonal()).array().abs().maxCoeff(); DiagonalMatrix<double,Dynamic> Minv = ((VectorXd)M.diagonal().array().inverse()).asDiagonal(); switch(k) { default: assert(false && "unsupported"); case 2: // For C1 smoothness in 2D, one should use bi-harmonic A = K.transpose() * (Minv * K); break; case 3: // For C1 smoothness in 3D, one should use tri-harmonic A = K.transpose() * (Minv * (-L * (Minv * K))); break; } } // Vertices in point handles const size_t mp = count_if(S.begin(),S.end(),[](const vector<int> & h){return h.size()==1;}); // number of region handles const size_t r = S.size()-mp; // Vertices in region handles size_t mr = 0; for(const auto & h : S) { if(h.size() > 1) { mr += h.size(); } } const size_t dim = T.cols()-1; // Might as well be dense... I think... MatrixXd J = MatrixXd::Zero(mp+mr,mp+r*(dim+1)); VectorXi b(mp+mr); MatrixXd H(mp+r*(dim+1),dim); { int v = 0; int c = 0; for(int h = 0;h<S.size();h++) { if(S[h].size()==1) { H.row(c) = V.block(S[h][0],0,1,dim); J(v,c++) = 1; b(v) = S[h][0]; v++; }else { assert(S[h].size() >= dim+1); for(int p = 0;p<S[h].size();p++) { for(int d = 0;d<dim;d++) { J(v,c+d) = V(S[h][p],d); } J(v,c+dim) = 1; b(v) = S[h][p]; v++; } H.block(c,0,dim+1,dim).setIdentity(); c+=dim+1; } } } // minimize ½ W' A W' // subject to W(b,:) = J return min_quad_with_fixed( A,VectorXd::Zero(A.rows()).eval(),b,J,{},VectorXd(),true,W); }
IGL_INLINE bool igl::bbw::bbw( const Eigen::PlainObjectBase<DerivedV> & V, const Eigen::PlainObjectBase<DerivedEle> & Ele, const Eigen::PlainObjectBase<Derivedb> & b, const Eigen::PlainObjectBase<Derivedbc> & bc, igl::bbw::BBWData & data, Eigen::PlainObjectBase<DerivedW> & W ) { using namespace std; using namespace Eigen; // number of domain vertices int n = V.rows(); // number of handles int m = bc.cols(); // Build biharmonic operator SparseMatrix<typename DerivedW::Scalar> L; cotmatrix(V,Ele,L); SparseMatrix<typename DerivedW::Scalar> M; SparseMatrix<typename DerivedW::Scalar> Mi; massmatrix(V,Ele,MASSMATRIX_TYPE_DEFAULT,M); invert_diag(M,Mi); SparseMatrix<typename DerivedW::Scalar> Q = L.transpose() * Mi * L; assert(!data.partition_unity && "partition_unity not implemented yet"); W.derived().resize(n,m); { // No linear terms VectorXd c = VectorXd::Zero(n); // No linear constraints SparseMatrix<typename DerivedW::Scalar> A(0,n),Aeq(0,n),Aieq(0,n); VectorXd uc(0,1),Beq(0,1),Bieq(0,1),lc(0,1); // Upper and lower box constraints (Constant bounds) VectorXd ux = VectorXd::Ones(n); VectorXd lx = VectorXd::Zero(n); active_set_params eff_params = data.active_set_params; switch(data.qp_solver) { case QP_SOLVER_IGL_ACTIVE_SET: { if(data.verbosity >= 1) { cout<<"BBW: max_iter: "<<data.active_set_params.max_iter<<endl; cout<<"BBW: eff_max_iter: "<<eff_params.max_iter<<endl; } if(data.verbosity >= 1) { cout<<"BBW: Computing initial weights for "<<m<<" handle"<< (m!=1?"s":"")<<"."<<endl; } min_quad_with_fixed_data<typename DerivedW::Scalar > mqwf; min_quad_with_fixed_precompute(Q,b,Aeq,true,mqwf); min_quad_with_fixed_solve(mqwf,c,bc,Beq,W); // decrement eff_params.max_iter--; bool error = false; // Loop over handles #pragma omp parallel for for(int i = 0;i<m;i++) { // Quicker exit for openmp if(error) { continue; } if(data.verbosity >= 1) { #pragma omp critical cout<<"BBW: Computing weight for handle "<<i+1<<" out of "<<m<< "."<<endl; } VectorXd bci = bc.col(i); VectorXd Wi; // use initial guess Wi = W.col(i); SolverStatus ret = active_set( Q,c,b,bci,Aeq,Beq,Aieq,Bieq,lx,ux,eff_params,Wi); switch(ret) { case SOLVER_STATUS_CONVERGED: break; case SOLVER_STATUS_MAX_ITER: cerr<<"active_set: max iter without convergence."<<endl; break; case SOLVER_STATUS_ERROR: default: cerr<<"active_set error."<<endl; error = true; } W.col(i) = Wi; } if(error) { return false; } break; } case QP_SOLVER_MOSEK: { #ifdef IGL_NO_MOSEK assert(false && "Use another QPSolver. Recompile without IGL_NO_MOSEK defined."); cerr<<"Use another QPSolver. Recompile without IGL_NO_MOSEK defined."<<endl; return false; #else // Loop over handles for(int i = 0;i<m;i++) { if(data.verbosity >= 1) { cout<<"BBW: Computing weight for handle "<<i+1<<" out of "<<m<< "."<<endl; } VectorXd bci = bc.col(i); VectorXd Wi; // impose boundary conditions via bounds slice_into(bci,b,ux); slice_into(bci,b,lx); bool r = mosek_quadprog(Q,c,0,A,lc,uc,lx,ux,data.mosek_data,Wi); if(!r) { return false; } W.col(i) = Wi; } #endif break; } default: { assert(false && "Unknown qp_solver"); return false; } } #ifndef NDEBUG const double min_rowsum = W.rowwise().sum().array().abs().minCoeff(); if(min_rowsum < 0.1) { cerr<<"bbw.cpp: Warning, minimum row sum is very low. Consider more " "active set iterations or enforcing partition of unity."<<endl; } #endif } return true; }
IGL_INLINE void igl::outer_hull( const Eigen::PlainObjectBase<DerivedV> & V, const Eigen::PlainObjectBase<DerivedF> & F, const Eigen::PlainObjectBase<DerivedN> & N, Eigen::PlainObjectBase<DerivedG> & G, Eigen::PlainObjectBase<DerivedJ> & J, Eigen::PlainObjectBase<Derivedflip> & flip) { using namespace Eigen; using namespace std; using namespace igl; typedef typename DerivedF::Index Index; Matrix<Index,DerivedF::RowsAtCompileTime,1> C; typedef Matrix<typename DerivedV::Scalar,Dynamic,DerivedV::ColsAtCompileTime> MatrixXV; typedef Matrix<typename DerivedF::Scalar,Dynamic,DerivedF::ColsAtCompileTime> MatrixXF; typedef Matrix<typename DerivedG::Scalar,Dynamic,DerivedG::ColsAtCompileTime> MatrixXG; typedef Matrix<typename DerivedJ::Scalar,Dynamic,DerivedJ::ColsAtCompileTime> MatrixXJ; typedef Matrix<typename DerivedN::Scalar,1,3> RowVector3N; const Index m = F.rows(); #ifdef IGL_OUTER_HULL_DEBUG cout<<"outer hull..."<<endl; #endif #ifdef IGL_OUTER_HULL_DEBUG cout<<"edge map..."<<endl; #endif typedef Matrix<typename DerivedF::Scalar,Dynamic,2> MatrixX2I; typedef Matrix<typename DerivedF::Index,Dynamic,1> VectorXI; MatrixX2I E,uE; VectorXI EMAP; vector<vector<typename DerivedF::Index> > uE2E; unique_edge_map(F,E,uE,EMAP,uE2E); // TODO: // uE --> face-edge index, sorted CCW around edge according to normal // uE --> sorted order index // uE --> bool, whether needed to flip face to make "consistent" with unique // edge // Place order of each half-edge in its corresponding sorted list around edge VectorXI diIM(3*m); // Whether face's edge used for sorting is consistent with unique edge VectorXI dicons(3*m); // dihedral angles of faces around edge with face of edge in dicons vector<vector<typename DerivedV::Scalar> > di(uE2E.size()); // For each list of face-edges incide on a unique edge for(size_t ui = 0;ui<(size_t)uE.rows();ui++) { // Base normal vector to orient against const auto fe0 = uE2E[ui][0]; const RowVector3N & eVp = N.row(fe0%m); MatrixXd di_I(uE2E[ui].size(),2); const typename DerivedF::Scalar d = F(fe0%m,((fe0/m)+2)%3); const typename DerivedF::Scalar s = F(fe0%m,((fe0/m)+1)%3); // Edge vector const auto & eV = (V.row(d)-V.row(s)).normalized(); vector<bool> cons(uE2E[ui].size()); // Loop over incident face edges for(size_t fei = 0;fei<uE2E[ui].size();fei++) { const auto & fe = uE2E[ui][fei]; const auto f = fe % m; const auto c = fe / m; // source should match destination to be consistent cons[fei] = (d == F(f,(c+1)%3)); assert( cons[fei] || (d == F(f,(c+2)%3))); assert(!cons[fei] || (s == F(f,(c+2)%3))); assert(!cons[fei] || (d == F(f,(c+1)%3))); // Angle between n and f const RowVector3N & n = N.row(f); di_I(fei,0) = M_PI - atan2( eVp.cross(n).dot(eV), eVp.dot(n)); if(!cons[fei]) { di_I(fei,0) = di_I(fei,0) + M_PI; if(di_I(fei,0)>=2.*M_PI) { di_I(fei,0) = di_I(fei,0) - 2.*M_PI; } } // This signing is very important to make sure different edges sort // duplicate faces the same way, regardless of their orientations di_I(fei,1) = (cons[fei]?1.:-1.)*f; } VectorXi IM; //igl::sort(di[ui],true,di[ui],IM); // Sort, but break ties using index to ensure that duplicates always show // up in same order. MatrixXd s_di_I; igl::sortrows(di_I,true,s_di_I,IM); di[ui].resize(uE2E[ui].size()); for(size_t i = 0;i<di[ui].size();i++) { di[ui][i] = s_di_I(i,0); } // copy old list vector<typename DerivedF::Index> temp = uE2E[ui]; for(size_t fei = 0;fei<uE2E[ui].size();fei++) { uE2E[ui][fei] = temp[IM(fei)]; const auto & fe = uE2E[ui][fei]; diIM(fe) = fei; dicons(fe) = cons[IM(fei)]; } } vector<vector<vector<Index > > > TT,_1; triangle_triangle_adjacency(E,EMAP,uE2E,false,TT,_1); VectorXI counts; #ifdef IGL_OUTER_HULL_DEBUG cout<<"facet components..."<<endl; #endif facet_components(TT,C,counts); assert(C.maxCoeff()+1 == counts.rows()); const size_t ncc = counts.rows(); G.resize(0,F.cols()); J.resize(0,1); flip.setConstant(m,1,false); #ifdef IGL_OUTER_HULL_DEBUG cout<<"reindex..."<<endl; #endif // H contains list of faces on outer hull; vector<bool> FH(m,false); vector<bool> EH(3*m,false); vector<MatrixXG> vG(ncc); vector<MatrixXJ> vJ(ncc); vector<MatrixXJ> vIM(ncc); for(size_t id = 0;id<ncc;id++) { vIM[id].resize(counts[id],1); } // current index into each IM vector<size_t> g(ncc,0); // place order of each face in its respective component for(Index f = 0;f<m;f++) { vIM[C(f)](g[C(f)]++) = f; } #ifdef IGL_OUTER_HULL_DEBUG cout<<"barycenters..."<<endl; #endif // assumes that "resolve" has handled any coplanar cases correctly and nearly // coplanar cases can be sorted based on barycenter. MatrixXV BC; barycenter(V,F,BC); #ifdef IGL_OUTER_HULL_DEBUG cout<<"loop over CCs (="<<ncc<<")..."<<endl; #endif for(Index id = 0;id<(Index)ncc;id++) { auto & IM = vIM[id]; // starting face that's guaranteed to be on the outer hull and in this // component int f; bool f_flip; #ifdef IGL_OUTER_HULL_DEBUG cout<<"outer facet..."<<endl; #endif outer_facet(V,F,N,IM,f,f_flip); #ifdef IGL_OUTER_HULL_DEBUG cout<<"outer facet: "<<f<<endl; #endif int FHcount = 1; FH[f] = true; // Q contains list of face edges to continue traversing upong queue<int> Q; Q.push(f+0*m); Q.push(f+1*m); Q.push(f+2*m); flip(f) = f_flip; //cout<<"flip("<<f<<") = "<<(flip(f)?"true":"false")<<endl; #ifdef IGL_OUTER_HULL_DEBUG cout<<"BFS..."<<endl; #endif while(!Q.empty()) { // face-edge const int e = Q.front(); Q.pop(); // face const int f = e%m; // corner const int c = e/m; // Should never see edge again... if(EH[e] == true) { continue; } EH[e] = true; // source of edge according to f const int fs = flip(f)?F(f,(c+2)%3):F(f,(c+1)%3); // destination of edge according to f const int fd = flip(f)?F(f,(c+1)%3):F(f,(c+2)%3); // edge valence const size_t val = uE2E[EMAP(e)].size(); //// find overlapping face-edges //const auto & neighbors = uE2E[EMAP(e)]; //// normal after possible flipping //const auto & fN = (flip(f)?-1.:1.)*N.row(f); //// Edge vector according to f's (flipped) orientation. ////const auto & eV = (V.row(fd)-V.row(fs)).normalized(); //#warning "EXPERIMENTAL, DO NOT USE" //// THIS IS WRONG! The first face is---after sorting---no longer the face //// used for orienting the sort. //const auto ui = EMAP(e); //const auto fe0 = uE2E[ui][0]; //const auto es = F(fe0%m,((fe0/m)+1)%3); // is edge consistent with edge of face used for sorting const int e_cons = (dicons(e) ? 1: -1); int nfei = -1; // Loop once around trying to find suitable next face for(size_t step = 1; step<val+2;step++) { const int nfei_new = (diIM(e) + 2*val + e_cons*step*(flip(f)?-1:1))%val; const int nf = uE2E[EMAP(e)][nfei_new] % m; // Don't consider faces with identical dihedral angles if(di[EMAP(e)][diIM(e)] != di[EMAP(e)][nfei_new]) //#warning "THIS IS HACK, FIX ME" // if( abs(di[EMAP(e)][diIM(e)] - di[EMAP(e)][nfei_new]) < 1e-16 ) { //#ifdef IGL_OUTER_HULL_DEBUG // cout<<"Next facet: "<<(f+1)<<" --> "<<(nf+1)<<", |"<< // di[EMAP(e)][diIM(e)]<<" - "<<di[EMAP(e)][nfei_new]<<"| = "<< // abs(di[EMAP(e)][diIM(e)] - di[EMAP(e)][nfei_new]) // <<endl; //#endif // Only use this face if not already seen if(!FH[nf]) { nfei = nfei_new; } break; } //#ifdef IGL_OUTER_HULL_DEBUG // cout<<"Skipping co-planar facet: "<<(f+1)<<" --> "<<(nf+1)<<endl; //#endif } int max_ne = -1; //// Loop over and find max dihedral angle //typename DerivedV::Scalar max_di = -1; //for(const auto & ne : neighbors) //{ // const int nf = ne%m; // if(nf == f) // { // continue; // } // // Corner of neighbor // const int nc = ne/m; // // Is neighbor oriented consistently with (flipped) f? // //const int ns = F(nf,(nc+1)%3); // const int nd = F(nf,(nc+2)%3); // const bool cons = (flip(f)?fd:fs) == nd; // // Normal after possibly flipping to match flip or orientation of f // const auto & nN = (cons? (flip(f)?-1:1.) : (flip(f)?1.:-1.) )*N.row(nf); // // Angle between n and f // const auto & ndi = M_PI - atan2( fN.cross(nN).dot(eV), fN.dot(nN)); // if(ndi>=max_di) // { // max_ne = ne; // max_di = ndi; // } //} ////cout<<(max_ne != max_ne_2)<<" =?= "<<e_cons<<endl; //if(max_ne != max_ne_2) //{ // cout<<(f+1)<<" ---> "<<(max_ne%m)+1<<" != "<<(max_ne_2%m)+1<<" ... "<<e_cons<<" "<<flip(f)<<endl; // typename DerivedV::Scalar max_di = -1; // for(size_t nei = 0;nei<neighbors.size();nei++) // { // const auto & ne = neighbors[nei]; // const int nf = ne%m; // if(nf == f) // { // cout<<" "<<(ne%m)+1<<":\t"<<0<<"\t"<<di[EMAP[e]][nei]<<" "<<diIM(ne)<<endl; // continue; // } // // Corner of neighbor // const int nc = ne/m; // // Is neighbor oriented consistently with (flipped) f? // //const int ns = F(nf,(nc+1)%3); // const int nd = F(nf,(nc+2)%3); // const bool cons = (flip(f)?fd:fs) == nd; // // Normal after possibly flipping to match flip or orientation of f // const auto & nN = (cons? (flip(f)?-1:1.) : (flip(f)?1.:-1.) )*N.row(nf); // // Angle between n and f // const auto & ndi = M_PI - atan2( fN.cross(nN).dot(eV), fN.dot(nN)); // cout<<" "<<(ne%m)+1<<":\t"<<ndi<<"\t"<<di[EMAP[e]][nei]<<" "<<diIM(ne)<<endl; // if(ndi>=max_di) // { // max_ne = ne; // max_di = ndi; // } // } //} if(nfei >= 0) { max_ne = uE2E[EMAP(e)][nfei]; } if(max_ne>=0) { // face of neighbor const int nf = max_ne%m; #ifdef IGL_OUTER_HULL_DEBUG if(!FH[nf]) { // first time seeing face cout<<(f+1)<<" --> "<<(nf+1)<<endl; } #endif FH[nf] = true; FHcount++; // corner of neighbor const int nc = max_ne/m; const int nd = F(nf,(nc+2)%3); const bool cons = (flip(f)?fd:fs) == nd; flip(nf) = (cons ? flip(f) : !flip(f)); //cout<<"flip("<<nf<<") = "<<(flip(nf)?"true":"false")<<endl; const int ne1 = nf+((nc+1)%3)*m; const int ne2 = nf+((nc+2)%3)*m; if(!EH[ne1]) { Q.push(ne1); } if(!EH[ne2]) { Q.push(ne2); } } } { vG[id].resize(FHcount,3); vJ[id].resize(FHcount,1); //nG += FHcount; size_t h = 0; assert(counts(id) == IM.rows()); for(int i = 0;i<counts(id);i++) { const size_t f = IM(i); //if(f_flip) //{ // flip(f) = !flip(f); //} if(FH[f]) { vG[id].row(h) = (flip(f)?F.row(f).reverse().eval():F.row(f)); vJ[id](h,0) = f; h++; } } assert((int)h == FHcount); } } // Is A inside B? Assuming A and B are consistently oriented but closed and // non-intersecting. const auto & is_component_inside_other = []( const Eigen::PlainObjectBase<DerivedV> & V, const MatrixXV & BC, const MatrixXG & A, const MatrixXJ & AJ, const MatrixXG & B)->bool { const auto & bounding_box = []( const Eigen::PlainObjectBase<DerivedV> & V, const MatrixXG & F)-> MatrixXV { MatrixXV BB(2,3); BB<< 1e26,1e26,1e26, -1e26,-1e26,-1e26; const size_t m = F.rows(); for(size_t f = 0;f<m;f++) { for(size_t c = 0;c<3;c++) { const auto & vfc = V.row(F(f,c)); BB.row(0) = BB.row(0).array().min(vfc.array()).eval(); BB.row(1) = BB.row(1).array().max(vfc.array()).eval(); } } return BB; }; // A lot of the time we're dealing with unrelated, distant components: cull // them. MatrixXV ABB = bounding_box(V,A); MatrixXV BBB = bounding_box(V,B); if( (BBB.row(0)-ABB.row(1)).maxCoeff()>0 || (ABB.row(0)-BBB.row(1)).maxCoeff()>0 ) { // bounding boxes do not overlap return false; } //////////////////////////////////////////////////////////////////////// // POTENTIAL ROBUSTNESS WEAK AREA //////////////////////////////////////////////////////////////////////// // // q could be so close (<~1e-16) to B that the winding number is not a robust way to // determine inside/outsideness. We could try to find a _better_ q which is // farther away, but couldn't they all be bad? MatrixXV q = BC.row(AJ(0)); // In a perfect world, it's enough to test a single point. double w; // winding_number_3 expects colmajor const typename DerivedV::Scalar * Vdata; Vdata = V.data(); Matrix< typename DerivedV::Scalar, DerivedV::RowsAtCompileTime, DerivedV::ColsAtCompileTime, ColMajor> Vcol; if(DerivedV::IsRowMajor) { // copy to convert to colmajor Vcol = V; Vdata = Vcol.data(); } winding_number_3( Vdata,V.rows(), B.data(),B.rows(), q.data(),1,&w); return fabs(w)>0.5; }; // Reject components which are completely inside other components vector<bool> keep(ncc,true); size_t nG = 0; // This is O( ncc * ncc * m) for(size_t id = 0;id<ncc;id++) { for(size_t oid = 0;oid<ncc;oid++) { if(id == oid) { continue; } const bool inside = is_component_inside_other(V,BC,vG[id],vJ[id],vG[oid]); #ifdef IGL_OUTER_HULL_DEBUG cout<<id<<" is inside "<<oid<<" ? "<<inside<<endl; #endif keep[id] = keep[id] && !inside; } if(keep[id]) { nG += vJ[id].rows(); } } // collect G and J across components G.resize(nG,3); J.resize(nG,1); { size_t off = 0; for(Index id = 0;id<(Index)ncc;id++) { if(keep[id]) { assert(vG[id].rows() == vJ[id].rows()); G.block(off,0,vG[id].rows(),vG[id].cols()) = vG[id]; J.block(off,0,vJ[id].rows(),vJ[id].cols()) = vJ[id]; off += vG[id].rows(); } } } }
IGL_INLINE void igl::bounding_box( const Eigen::PlainObjectBase<DerivedV>& V, Eigen::PlainObjectBase<DerivedBV>& BV, Eigen::PlainObjectBase<DerivedBF>& BF) { using namespace std; const int dim = V.cols(); const auto & minV = V.colwise().minCoeff(); const auto & maxV = V.colwise().maxCoeff(); // 2^n vertices BV.resize((1<<dim),dim); // Recursive lambda to generate all 2^n combinations const std::function<void(const int,const int,int*,int)> combos = [&BV,&minV,&maxV,&combos]( const int dim, const int i, int * X, const int pre_index) { for(X[i] = 0;X[i]<2;X[i]++) { int index = pre_index*2+X[i]; if((i+1)<dim) { combos(dim,i+1,X,index); }else { for(int d = 0;d<dim;d++) { BV(index,d) = (X[d]?minV[d]:maxV[d]); } } } }; Eigen::VectorXi X(dim); combos(dim,0,X.data(),0); switch(dim) { case 2: BF.resize(4,2); BF<< 3,1, 1,0, 0,2, 2,3; break; case 3: BF.resize(12,3); BF<< 2,0,6, 0,4,6, 5,4,0, 5,0,1, 6,4,5, 5,7,6, 3,0,2, 1,0,3, 3,2,6, 6,7,3, 5,1,3, 3,7,5; break; default: assert(false && "Unsupported dimension."); break; } }
IGL_INLINE bool igl::writeOBJ( const std::string str, const Eigen::PlainObjectBase<DerivedV>& V, const Eigen::PlainObjectBase<DerivedF>& F, const Eigen::PlainObjectBase<DerivedV>& CN, const Eigen::PlainObjectBase<DerivedF>& FN, const Eigen::PlainObjectBase<DerivedT>& TC, const Eigen::PlainObjectBase<DerivedF>& FTC) { FILE * obj_file = fopen(str.c_str(),"w"); if(NULL==obj_file) { printf("IOError: %s could not be opened for writing...",str.c_str()); return false; } // Loop over V for(int i = 0;i<(int)V.rows();i++) { fprintf(obj_file,"v %0.15g %0.15g %0.15g\n", V(i,0), V(i,1), V(i,2) ); } bool write_N = CN.rows() >0; if(write_N) { for(int i = 0;i<(int)CN.rows();i++) { fprintf(obj_file,"v %0.15g %0.15g %0.15g\n", CN(i,0), CN(i,1), CN(i,2) ); } fprintf(obj_file,"\n"); } bool write_texture_coords = TC.rows() >0; if(write_texture_coords) { for(int i = 0;i<(int)TC.rows();i++) { fprintf(obj_file, "vt %0.15g %0.15g\n",TC(i,0),TC(i,1)); } fprintf(obj_file,"\n"); } // loop over F for(int i = 0;i<(int)F.rows();++i) { fprintf(obj_file,"f"); for(int j = 0; j<(int)F.cols();++j) { // OBJ is 1-indexed fprintf(obj_file," %u",F(i,j)+1); if(write_texture_coords) fprintf(obj_file,"/%u",FTC(i,j)+1); if(write_N) { if (write_texture_coords) fprintf(obj_file,"/%u",FN(i,j)+1); else fprintf(obj_file,"//%u",FN(i,j)+1); } } fprintf(obj_file,"\n"); } fclose(obj_file); return true; }
IGL_INLINE void igl::copyleft::cgal::order_facets_around_edge( const Eigen::PlainObjectBase<DerivedV>& V, const Eigen::PlainObjectBase<DerivedF>& F, size_t s, size_t d, const std::vector<int>& adj_faces, const Eigen::PlainObjectBase<DerivedV>& pivot_point, Eigen::PlainObjectBase<DerivedI>& order) { assert(V.cols() == 3); assert(F.cols() == 3); assert(pivot_point.cols() == 3); auto signed_index_to_index = [&](int signed_idx) { return abs(signed_idx) -1; }; auto get_opposite_vertex_index = [&](size_t fid) -> typename DerivedF::Scalar { typedef typename DerivedF::Scalar Index; if (F(fid, 0) != (Index)s && F(fid, 0) != (Index)d) return F(fid, 0); if (F(fid, 1) != (Index)s && F(fid, 1) != (Index)d) return F(fid, 1); if (F(fid, 2) != (Index)s && F(fid, 2) != (Index)d) return F(fid, 2); assert(false); // avoid warning return -1; }; { // Check if s, d and pivot are collinear. typedef CGAL::Exact_predicates_exact_constructions_kernel K; K::Point_3 ps(V(s,0), V(s,1), V(s,2)); K::Point_3 pd(V(d,0), V(d,1), V(d,2)); K::Point_3 pp(pivot_point(0,0), pivot_point(0,1), pivot_point(0,2)); if (CGAL::collinear(ps, pd, pp)) { throw std::runtime_error( "Pivot point is collinear with the outer edge!"); } } const size_t N = adj_faces.size(); const size_t num_faces = N + 1; // N adj faces + 1 pivot face // Because face indices are used for tie breaking, the original face indices // in the new faces array must be ascending. auto comp = [&](int i, int j) { return signed_index_to_index(adj_faces[i]) < signed_index_to_index(adj_faces[j]); }; std::vector<size_t> adj_order(N); for (size_t i=0; i<N; i++) adj_order[i] = i; std::sort(adj_order.begin(), adj_order.end(), comp); DerivedV vertices(num_faces + 2, 3); for (size_t i=0; i<N; i++) { const size_t fid = signed_index_to_index(adj_faces[adj_order[i]]); vertices.row(i) = V.row(get_opposite_vertex_index(fid)); } vertices.row(N ) = pivot_point; vertices.row(N+1) = V.row(s); vertices.row(N+2) = V.row(d); DerivedF faces(num_faces, 3); for (size_t i=0; i<N; i++) { if (adj_faces[adj_order[i]] < 0) { faces(i,0) = N+1; // s faces(i,1) = N+2; // d faces(i,2) = i ; } else { faces(i,0) = N+2; // d faces(i,1) = N+1; // s faces(i,2) = i ; } } // Last face is the pivot face. faces(N, 0) = N+1; faces(N, 1) = N+2; faces(N, 2) = N; std::vector<int> adj_faces_with_pivot(num_faces); for (size_t i=0; i<num_faces; i++) { if ((size_t)faces(i,0) == N+1 && (size_t)faces(i,1) == N+2) { adj_faces_with_pivot[i] = int(i+1) * -1; } else { adj_faces_with_pivot[i] = int(i+1); } } DerivedI order_with_pivot; order_facets_around_edge( vertices, faces, N+1, N+2, adj_faces_with_pivot, order_with_pivot); assert((size_t)order_with_pivot.size() == num_faces); order.resize(N); size_t pivot_index = num_faces + 1; for (size_t i=0; i<num_faces; i++) { if ((size_t)order_with_pivot[i] == N) { pivot_index = i; break; } } assert(pivot_index < num_faces); for (size_t i=0; i<N; i++) { order[i] = adj_order[order_with_pivot[(pivot_index+i+1)%num_faces]]; } }
IGL_INLINE void igl::ismember_rows( const Eigen::PlainObjectBase<DerivedA> & A, const Eigen::PlainObjectBase<DerivedB> & B, Eigen::PlainObjectBase<DerivedIA> & IA, Eigen::PlainObjectBase<DerivedLOCB> & LOCB) { using namespace Eigen; using namespace std; assert(A.cols() == B.cols() && "number of columns must match"); IA.resize(A.rows(),1); IA.setConstant(false); LOCB.resize(A.rows(),1); LOCB.setConstant(-1); // boring base cases if(A.size() == 0) { return; } if(B.size() == 0) { return; } // Get rid of any duplicates DerivedA uA; DerivedB uB; Eigen::Matrix<typename DerivedA::Index,Dynamic,1> uIA,uIuA,uIB,uIuB; unique_rows(A,uA,uIA,uIuA); unique_rows(B,uB,uIB,uIuB); // Sort both DerivedA sA; DerivedB sB; Eigen::Matrix<typename DerivedA::Index,Dynamic,1> sIA,sIB; sortrows(uA,true,sA,sIA); sortrows(uB,true,sB,sIB); Eigen::Matrix<bool,Eigen::Dynamic,1> uF = Eigen::Matrix<bool,Eigen::Dynamic,1>::Zero(sA.size(),1); Eigen::Matrix<typename DerivedLOCB::Scalar, Eigen::Dynamic,1> uLOCB = Eigen::Matrix<typename DerivedLOCB::Scalar,Eigen::Dynamic,1>:: Constant(sA.size(),1,-1); const auto & row_greater_than = [&sA,&sB](const int a, const int b) { for(int c = 0;c<sA.cols();c++) { if(sA(a,c) > sB(b,c)) return true; if(sA(a,c) < sB(b,c)) return false; } return false; }; { int bi = 0; // loop over sA bool past = false; for(int a = 0;a<sA.rows();a++) { while(!past && row_greater_than(a,bi)) { bi++; past = bi>=sB.size(); } if(!past && (sA.row(a).array()==sB.row(bi).array()).all() ) { uF(sIA(a)) = true; uLOCB(sIA(a)) = uIB(sIB(bi)); } } } for(int a = 0;a<A.rows();a++) { IA(a) = uF(uIuA(a)); LOCB(a) = uLOCB(uIuA(a)); } }
IGL_INLINE void igl::ismember( const Eigen::MatrixBase<DerivedA> & A, const Eigen::MatrixBase<DerivedB> & B, Eigen::PlainObjectBase<DerivedIA> & IA, Eigen::PlainObjectBase<DerivedLOCB> & LOCB) { using namespace Eigen; using namespace std; IA.resizeLike(A); IA.setConstant(false); LOCB.resizeLike(A); LOCB.setConstant(-1); // boring base cases if(A.size() == 0) { return; } if(B.size() == 0) { return; } // Get rid of any duplicates typedef Matrix<typename DerivedA::Scalar,Dynamic,1> VectorA; typedef Matrix<typename DerivedB::Scalar,Dynamic,1> VectorB; const VectorA vA(Eigen::Map<const VectorA>(DerivedA(A).data(), A.cols()*A.rows(),1)); const VectorB vB(Eigen::Map<const VectorB>(DerivedB(B).data(), B.cols()*B.rows(),1)); VectorA uA; VectorB uB; Eigen::Matrix<typename DerivedA::Index,Dynamic,1> uIA,uIuA,uIB,uIuB; unique(vA,uA,uIA,uIuA); unique(vB,uB,uIB,uIuB); // Sort both VectorA sA; VectorB sB; Eigen::Matrix<typename DerivedA::Index,Dynamic,1> sIA,sIB; sort(uA,1,true,sA,sIA); sort(uB,1,true,sB,sIB); Eigen::Matrix<bool,Eigen::Dynamic,1> uF = Eigen::Matrix<bool,Eigen::Dynamic,1>::Zero(sA.size(),1); Eigen::Matrix<typename DerivedLOCB::Scalar, Eigen::Dynamic,1> uLOCB = Eigen::Matrix<typename DerivedLOCB::Scalar,Eigen::Dynamic,1>:: Constant(sA.size(),1,-1); { int bi = 0; // loop over sA bool past = false; for(int a = 0;a<sA.size();a++) { while(!past && sA(a)>sB(bi)) { bi++; past = bi>=sB.size(); } if(!past && sA(a)==sB(bi)) { uF(sIA(a)) = true; uLOCB(sIA(a)) = uIB(sIB(bi)); } } } Map< Matrix<typename DerivedIA::Scalar,Dynamic,1> > vIA(IA.data(),IA.cols()*IA.rows(),1); Map< Matrix<typename DerivedLOCB::Scalar,Dynamic,1> > vLOCB(LOCB.data(),LOCB.cols()*LOCB.rows(),1); for(int a = 0;a<A.size();a++) { vIA(a) = uF(uIuA(a)); vLOCB(a) = uLOCB(uIuA(a)); } }
IGL_INLINE void igl::boolean::mesh_boolean( const Eigen::PlainObjectBase<DerivedVA > & VA, const Eigen::PlainObjectBase<DerivedFA > & FA, const Eigen::PlainObjectBase<DerivedVB > & VB, const Eigen::PlainObjectBase<DerivedFB > & FB, const MeshBooleanType & type, const std::function<void( const Eigen::Matrix<typename DerivedVC::Scalar,Eigen::Dynamic,3>&, const Eigen::Matrix<typename DerivedFC::Scalar, Eigen::Dynamic,3>&, Eigen::Matrix<typename DerivedVC::Scalar,Eigen::Dynamic,3>&, Eigen::Matrix<typename DerivedFC::Scalar, Eigen::Dynamic,3>&, Eigen::Matrix<typename DerivedJ::Scalar, Eigen::Dynamic,1>&)> & resolve_fun, Eigen::PlainObjectBase<DerivedVC > & VC, Eigen::PlainObjectBase<DerivedFC > & FC, Eigen::PlainObjectBase<DerivedJ > & J) { using namespace Eigen; using namespace std; using namespace igl; using namespace igl::cgal; MeshBooleanType eff_type = type; // Concatenate A and B into a single mesh typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel; typedef Kernel::FT ExactScalar; typedef typename DerivedVC::Scalar Scalar; typedef typename DerivedFC::Scalar Index; typedef Matrix<Scalar,Dynamic,3> MatrixX3S; typedef Matrix<ExactScalar,Dynamic,3> MatrixX3ES; typedef Matrix<Index,Dynamic,3> MatrixX3I; typedef Matrix<Index,Dynamic,2> MatrixX2I; typedef Matrix<Index,Dynamic,1> VectorXI; typedef Matrix<typename DerivedJ::Scalar,Dynamic,1> VectorXJ; #ifdef IGL_MESH_BOOLEAN_DEBUG cout<<"mesh boolean..."<<endl; #endif MatrixX3S V(VA.rows()+VB.rows(),3); MatrixX3I F(FA.rows()+FB.rows(),3); V.block(0,0,VA.rows(),VA.cols()) = VA; V.block(VA.rows(),0,VB.rows(),VB.cols()) = VB; #ifdef IGL_MESH_BOOLEAN_DEBUG cout<<"prepare selfintersect input..."<<endl; #endif switch(type) { // Minus is implemented by flipping B and computing union case MESH_BOOLEAN_TYPE_MINUS: F.block(0,0,FA.rows(),FA.cols()) = FA.rowwise().reverse(); F.block(FA.rows(),0,FB.rows(),FB.cols()) = FB.array()+VA.rows(); //F.block(0,0,FA.rows(),3) = FA; //F.block(FA.rows(),0,FB.rows(),3) = // FB.rowwise().reverse().array()+VA.rows(); eff_type = MESH_BOOLEAN_TYPE_INTERSECT; break; default: F.block(0,0,FA.rows(),FA.cols()) = FA; F.block(FA.rows(),0,FB.rows(),FB.cols()) = FB.array()+VA.rows(); break; } // Resolve intersections (assumes A and B are solid) const auto & libigl_resolve = []( const MatrixX3S & V, const MatrixX3I & F, MatrixX3ES & CV, MatrixX3I & CF, VectorXJ & J) { MatrixX3ES SV; MatrixX3I SF; MatrixX2I SIF; VectorXI SIM,UIM; igl::cgal::RemeshSelfIntersectionsParam params; remesh_self_intersections(V,F,params,SV,SF,SIF,J,SIM); for_each(SF.data(),SF.data()+SF.size(),[&SIM](int & a){a=SIM(a);}); { remove_unreferenced(SV,SF,CV,CF,UIM); } }; #ifdef IGL_MESH_BOOLEAN_DEBUG cout<<"resolve..."<<endl; #endif MatrixX3S CV; MatrixX3ES EV; MatrixX3I CF; VectorXJ CJ; if(resolve_fun) { resolve_fun(V,F,CV,CF,CJ); }else { libigl_resolve(V,F,EV,CF,CJ); CV.resize(EV.rows(), EV.cols()); std::transform(EV.data(), EV.data() + EV.rows()*EV.cols(), CV.data(), [&](ExactScalar val) { return CGAL::to_double(val); }); } if(type == MESH_BOOLEAN_TYPE_RESOLVE) { FC = CF; VC = CV; J = CJ; return; } #ifdef IGL_MESH_BOOLEAN_DEBUG cout<<"peel..."<<endl; #endif Matrix<bool,Dynamic,1> from_A(CF.rows()); // peel layers keeping track of odd and even flips VectorXi I; Matrix<bool,Dynamic,1> flip; peel_outer_hull_layers(EV,CF,I,flip); // 0 is "first" iteration, so it's odd Array<bool,Dynamic,1> odd = igl::mod(I,2).array()==0; #ifdef IGL_MESH_BOOLEAN_DEBUG cout<<"categorize..."<<endl; #endif const Index m = CF.rows(); // Faces of output vG[i] = j means ith face of output should be jth face in F std::vector<Index> vG; // Whether faces of output should be flipped, Gflip[i] = true means ith face // of output should be F.row(vG[i]).reverse() rather than F.row(vG[i]) std::vector<bool> Gflip; for(Index f = 0;f<m;f++) { switch(eff_type) { case MESH_BOOLEAN_TYPE_XOR: case MESH_BOOLEAN_TYPE_UNION: if((odd(f)&&!flip(f))||(!odd(f)&&flip(f))) { vG.push_back(f); Gflip.push_back(false); }else if(eff_type == MESH_BOOLEAN_TYPE_XOR) { vG.push_back(f); Gflip.push_back(true); } break; case MESH_BOOLEAN_TYPE_INTERSECT: if((!odd(f) && !flip(f)) || (odd(f) && flip(f))) { vG.push_back(f); Gflip.push_back(type == MESH_BOOLEAN_TYPE_MINUS); } break; default: assert(false && "Unknown type"); return; } } const Index gm = vG.size(); MatrixX3I G(gm,3); VectorXi GJ(gm,1); for(Index g = 0;g<gm;g++) { G.row(g) = Gflip[g] ? CF.row(vG[g]).reverse().eval() : CF.row(vG[g]); GJ(g) = CJ(vG[g]); } #ifdef IGL_MESH_BOOLEAN_DEBUG { MatrixXd O; boundary_facets(FC,O); cout<<"# boundary: "<<O.rows()<<endl; } cout<<"# exterior: "<<exterior_edges(FC).rows()<<endl; #endif #ifdef IGL_MESH_BOOLEAN_DEBUG cout<<"clean..."<<endl; #endif // Deal with duplicate faces { VectorXi IA,IC; MatrixX3I uG; unique_simplices(G,uG,IA,IC); assert(IA.rows() == uG.rows()); // faces ontop of each unique face vector<vector<Index> > uG2G(uG.rows()); // signed counts VectorXi counts = VectorXi::Zero(uG.rows()); VectorXi ucounts = VectorXi::Zero(uG.rows()); // loop over all faces for(Index g = 0;g<gm;g++) { const int ug = IC(g); assert(ug < uG2G.size()); uG2G[ug].push_back(g); // is uG(g,:) just a rotated version of G(g,:) ? const bool consistent = (G(g,0) == uG(ug,0) && G(g,1) == uG(ug,1) && G(g,2) == uG(ug,2)) || (G(g,0) == uG(ug,1) && G(g,1) == uG(ug,2) && G(g,2) == uG(ug,0)) || (G(g,0) == uG(ug,2) && G(g,1) == uG(ug,0) && G(g,2) == uG(ug,1)); counts(ug) += consistent ? 1 : -1; ucounts(ug)++; } MatrixX3I oldG = G; // Faces of output vG[i] = j means ith face of output should be jth face in // oldG vG.clear(); for(size_t ug = 0;ug < uG2G.size();ug++) { // if signed occurrences is zero or ±two then keep none // else if signed occurrences is ±one then keep just one facet switch(abs(counts(ug))) { case 1: assert(uG2G[ug].size() > 0); vG.push_back(uG2G[ug][0]); #ifdef IGL_MESH_BOOLEAN_DEBUG if(abs(ucounts(ug)) != 1) { cout<<"count,ucount of "<<counts(ug)<<","<<ucounts(ug)<<endl; } #endif break; case 0: #ifdef IGL_MESH_BOOLEAN_DEBUG cout<<"Skipping "<<uG2G[ug].size()<<" facets..."<<endl; if(abs(ucounts(ug)) != 0) { cout<<"count,ucount of "<<counts(ug)<<","<<ucounts(ug)<<endl; } #endif break; default: #ifdef IGL_MESH_BOOLEAN_DEBUG cout<<"Didn't expect to be here."<<endl; #endif assert(false && "Shouldn't count be -1/0/1 ?"); } } G.resize(vG.size(),3); J.resize(vG.size()); for(size_t g = 0;g<vG.size();g++) { G.row(g) = oldG.row(vG[g]); J(g) = GJ(vG[g]); } } // remove unreferenced vertices VectorXi newIM; remove_unreferenced(CV,G,VC,FC,newIM); //cerr<<"warning not removing unref"<<endl; //VC = CV; //FC = G; #ifdef IGL_MESH_BOOLEAN_DEBUG { MatrixXd O; boundary_facets(FC,O); cout<<"# boundary: "<<O.rows()<<endl; } cout<<"# exterior: "<<exterior_edges(FC).rows()<<endl; #endif }
IGL_INLINE void igl::orient_outward( const Eigen::PlainObjectBase<DerivedV> & V, const Eigen::PlainObjectBase<DerivedF> & F, const Eigen::PlainObjectBase<DerivedC> & C, Eigen::PlainObjectBase<DerivedFF> & FF, Eigen::PlainObjectBase<DerivedI> & I) { using namespace Eigen; using namespace std; assert(C.rows() == F.rows()); assert(F.cols() == 3); assert(V.cols() == 3); // number of faces const int m = F.rows(); // number of patches const int num_cc = C.maxCoeff()+1; I.resize(num_cc); if(&FF != &F) { FF = F; } PlainObjectBase<DerivedV> N,BC,BCmean; Matrix<typename DerivedV::Scalar,Dynamic,1> A; VectorXd totA(num_cc), dot(num_cc); Matrix<typename DerivedV::Scalar,3,1> Z(1,1,1); per_face_normals(V,F,Z.normalized(),N); barycenter(V,F,BC); doublearea(V,F,A); BCmean.setConstant(num_cc,3,0); dot.setConstant(num_cc,1,0); totA.setConstant(num_cc,1,0); // loop over faces for(int f = 0;f<m;f++) { BCmean.row(C(f)) += A(f)*BC.row(f); totA(C(f))+=A(f); } // take area weighted average for(int c = 0;c<num_cc;c++) { BCmean.row(c) /= (typename DerivedV::Scalar) totA(c); } // subtract bcmean for(int f = 0;f<m;f++) { BC.row(f) -= BCmean.row(C(f)); dot(C(f)) += A(f)*N.row(f).dot(BC.row(f)); } // take area weighted average for(int c = 0;c<num_cc;c++) { dot(c) /= (typename DerivedV::Scalar) totA(c); if(dot(c) < 0) { I(c) = true; }else { I(c) = false; } } // flip according to I for(int f = 0;f<m;f++) { if(I(C(f))) { FF.row(f) = FF.row(f).reverse().eval(); } } }
IGL_INLINE void igl::cotangent( const Eigen::PlainObjectBase<DerivedV>& V, const Eigen::PlainObjectBase<DerivedF>& F, Eigen::PlainObjectBase<DerivedC>& C) { using namespace igl; using namespace std; using namespace Eigen; // simplex size (3: triangles, 4: tetrahedra) int simplex_size = F.cols(); // Number of elements int m = F.rows(); // Law of cosines + law of sines switch(simplex_size) { case 3: { // Triangles //Matrix<typename DerivedC::Scalar,Dynamic,3> l; //edge_lengths(V,F,l); // edge lengths numbered same as opposite vertices Matrix<typename DerivedC::Scalar,Dynamic,3> l; igl::edge_lengths(V,F,l); // double area Matrix<typename DerivedC::Scalar,Dynamic,1> dblA; doublearea(l,dblA); // cotangents and diagonal entries for element matrices // correctly divided by 4 (alec 2010) C.resize(m,3); for(int i = 0;i<m;i++) { C(i,0) = (l(i,1)*l(i,1) + l(i,2)*l(i,2) - l(i,0)*l(i,0))/dblA(i)/4.0; C(i,1) = (l(i,2)*l(i,2) + l(i,0)*l(i,0) - l(i,1)*l(i,1))/dblA(i)/4.0; C(i,2) = (l(i,0)*l(i,0) + l(i,1)*l(i,1) - l(i,2)*l(i,2))/dblA(i)/4.0; } break; } case 4: { // edge lengths numbered same as opposite vertices Matrix<typename DerivedC::Scalar,Dynamic,6> l; edge_lengths(V,F,l); Matrix<typename DerivedC::Scalar,Dynamic,4> s; face_areas(l,s); Matrix<typename DerivedC::Scalar,Dynamic,6> cos_theta,theta; dihedral_angles_intrinsic(l,s,theta,cos_theta); // volume Matrix<typename DerivedC::Scalar,Dynamic,1> vol; volume(l,vol); // Law of sines // http://mathworld.wolfram.com/Tetrahedron.html Matrix<typename DerivedC::Scalar,Dynamic,6> sin_theta(m,6); sin_theta.col(0) = vol.array() / ((2./(3.*l.col(0).array())).array() * s.col(1).array() * s.col(2).array()); sin_theta.col(1) = vol.array() / ((2./(3.*l.col(1).array())).array() * s.col(2).array() * s.col(0).array()); sin_theta.col(2) = vol.array() / ((2./(3.*l.col(2).array())).array() * s.col(0).array() * s.col(1).array()); sin_theta.col(3) = vol.array() / ((2./(3.*l.col(3).array())).array() * s.col(3).array() * s.col(0).array()); sin_theta.col(4) = vol.array() / ((2./(3.*l.col(4).array())).array() * s.col(3).array() * s.col(1).array()); sin_theta.col(5) = vol.array() / ((2./(3.*l.col(5).array())).array() * s.col(3).array() * s.col(2).array()); // http://arxiv.org/pdf/1208.0354.pdf Page 18 C = (1./6.) * l.array() * cos_theta.array() / sin_theta.array(); break; } default: { fprintf(stderr, "cotangent.h: Error: Simplex size (%d) not supported\n", simplex_size); assert(false); } } }
IGL_INLINE void igl::boundary_loop( const Eigen::PlainObjectBase<DerivedF> & F, std::vector<std::vector<Index> >& L) { using namespace std; using namespace Eigen; using namespace igl; MatrixXd Vdummy(F.maxCoeff(),1); MatrixXi TT,TTi; vector<std::vector<int> > VF, VFi; triangle_triangle_adjacency(Vdummy,F,TT,TTi); vertex_triangle_adjacency(Vdummy,F,VF,VFi); vector<bool> unvisited = is_border_vertex(Vdummy,F); set<int> unseen; for (int i = 0; i < unvisited.size(); ++i) { if (unvisited[i]) unseen.insert(unseen.end(),i); } while (!unseen.empty()) { vector<Index> l; // Get first vertex of loop int start = *unseen.begin(); unseen.erase(unseen.begin()); unvisited[start] = false; l.push_back(start); bool done = false; while (!done) { // Find next vertex bool newBndEdge = false; int v = l[l.size()-1]; int next; for (int i = 0; i < (int)VF[v].size() && !newBndEdge; i++) { int fid = VF[v][i]; if (TT.row(fid).minCoeff() < 0.) // Face contains boundary edge { int vLoc; if (F(fid,0) == v) vLoc = 0; if (F(fid,1) == v) vLoc = 1; if (F(fid,2) == v) vLoc = 2; int vPrev = F(fid,(vLoc + F.cols()-1) % F.cols()); int vNext = F(fid,(vLoc + 1) % F.cols()); bool newBndEdge = false; if (unvisited[vPrev] && TT(fid,(vLoc+2) % F.cols()) < 0) { next = vPrev; newBndEdge = true; } else if (unvisited[vNext] && TT(fid,vLoc) < 0) { next = vNext; newBndEdge = true; } } } if (newBndEdge) { l.push_back(next); unseen.erase(next); unvisited[next] = false; } else done = true; } L.push_back(l); } }
IGL_INLINE void igl::embree::bone_visible( const Eigen::PlainObjectBase<DerivedV> & V, const Eigen::PlainObjectBase<DerivedF> & F, const EmbreeIntersector & ei, const Eigen::PlainObjectBase<DerivedSD> & s, const Eigen::PlainObjectBase<DerivedSD> & d, Eigen::PlainObjectBase<Derivedflag> & flag) { using namespace std; using namespace Eigen; flag.resize(V.rows()); const double sd_norm = (s-d).norm(); // Embree seems to be parallel when constructing but not when tracing rays #pragma omp parallel for // loop over mesh vertices for(int v = 0;v<V.rows();v++) { const Vector3d Vv = V.row(v); // Project vertex v onto line segment sd //embree.intersectSegment double t,sqrd; Vector3d projv; // degenerate bone, just snap to s if(sd_norm < DOUBLE_EPS) { t = 0; sqrd = (Vv-s).array().pow(2).sum(); projv = s; }else { // project onto (infinite) line project_to_line( Vv(0),Vv(1),Vv(2),s(0),s(1),s(2),d(0),d(1),d(2), projv(0),projv(1),projv(2),t,sqrd); // handle projections past endpoints if(t<0) { t = 0; sqrd = (Vv-s).array().pow(2).sum(); projv = s; } else if(t>1) { t = 1; sqrd = (Vv-d).array().pow(2).sum(); projv = d; } } igl::Hit hit; // perhaps 1.0 should be 1.0-epsilon, or actually since we checking the // incident face, perhaps 1.0 should be 1.0+eps const Vector3d dir = (Vv-projv)*1.0; if(ei.intersectSegment( projv.template cast<float>(), dir.template cast<float>(), hit)) { // mod for double sided lighting const int fi = hit.id % F.rows(); //if(v == 1228-1) //{ // Vector3d bc,P; // bc << 1 - hit.u - hit.v, hit.u, hit.v; // barycentric // P = V.row(F(fi,0))*bc(0) + // V.row(F(fi,1))*bc(1) + // V.row(F(fi,2))*bc(2); // cout<<(fi+1)<<endl; // cout<<bc.transpose()<<endl; // cout<<P.transpose()<<endl; // cout<<hit.t<<endl; // cout<<(projv + dir*hit.t).transpose()<<endl; // cout<<Vv.transpose()<<endl; //} // Assume hit is valid, so not visible flag(v) = false; // loop around corners of triangle for(int c = 0;c<F.cols();c++) { if(F(fi,c) == v) { // hit self, so no hits before, so vertex v is visible flag(v) = true; break; } } // Hit is actually past v if(!flag(v) && (hit.t*hit.t*dir.squaredNorm())>sqrd) { flag(v) = true; } }else { // no hit so vectex v is visible flag(v) = true; } } }
IGL_INLINE bool igl::writePLY( const std::string & filename, const Eigen::PlainObjectBase<DerivedV> & V, const Eigen::PlainObjectBase<DerivedF> & F, const Eigen::PlainObjectBase<DerivedN> & N, const Eigen::PlainObjectBase<DerivedUV> & UV, const bool ascii) { // Largely based on obj2ply.c typedef struct Vertex { double x,y,z,w; /* position */ double nx,ny,nz; /* surface normal */ double s,t; /* texture coordinates */ } Vertex; typedef struct Face { unsigned char nverts; /* number of vertex indices in list */ int *verts; /* vertex index list */ } Face; PlyProperty vert_props[] = { /* list of property information for a vertex */ {"x", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,x), 0, 0, 0, 0}, {"y", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,y), 0, 0, 0, 0}, {"z", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,z), 0, 0, 0, 0}, {"nx", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,nx), 0, 0, 0, 0}, {"ny", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,ny), 0, 0, 0, 0}, {"nz", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,nz), 0, 0, 0, 0}, {"s", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,s), 0, 0, 0, 0}, {"t", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,t), 0, 0, 0, 0}, }; PlyProperty face_props[] = { /* list of property information for a face */ {"vertex_indices", PLY_INT, PLY_INT, offsetof(Face,verts), 1, PLY_UCHAR, PLY_UCHAR, offsetof(Face,nverts)}, }; const bool has_normals = N.rows() > 0; const bool has_texture_coords = UV.rows() > 0; std::vector<Vertex> vlist(V.rows()); std::vector<Face> flist(F.rows()); for(size_t i = 0;i<(size_t)V.rows();i++) { vlist[i].x = V(i,0); vlist[i].y = V(i,1); vlist[i].z = V(i,2); if(has_normals) { vlist[i].nx = N(i,0); vlist[i].ny = N(i,1); vlist[i].nz = N(i,2); } if(has_texture_coords) { vlist[i].s = UV(i,0); vlist[i].t = UV(i,1); } } for(size_t i = 0;i<(size_t)F.rows();i++) { flist[i].nverts = F.cols(); flist[i].verts = new int[F.cols()]; for(size_t c = 0;c<(size_t)F.cols();c++) { flist[i].verts[c] = F(i,c); } } const char * elem_names[] = {"vertex","face"}; FILE * fp = fopen(filename.c_str(),"w"); if(fp==NULL) { return false; } PlyFile * ply = ply_write(fp, 2,elem_names, (ascii ? PLY_ASCII : PLY_BINARY_LE)); if(ply==NULL) { return false; } std::vector<PlyProperty> plist; plist.push_back(vert_props[0]); plist.push_back(vert_props[1]); plist.push_back(vert_props[2]); if (has_normals) { plist.push_back(vert_props[3]); plist.push_back(vert_props[4]); plist.push_back(vert_props[5]); } if (has_texture_coords) { plist.push_back(vert_props[6]); plist.push_back(vert_props[7]); } ply_describe_element(ply, "vertex", V.rows(),plist.size(), &plist[0]); ply_describe_element(ply, "face", F.rows(),1,&face_props[0]); ply_header_complete(ply); ply_put_element_setup(ply, "vertex"); for(const auto v : vlist) { ply_put_element(ply, (void *) &v); } ply_put_element_setup(ply, "face"); for(const auto f : flist) { ply_put_element(ply, (void *) &f); } ply_close(ply); for(size_t i = 0;i<(size_t)F.rows();i++) { delete[] flist[i].verts; } return true; }
IGL_INLINE void igl::adjacency_list( const Eigen::PlainObjectBase<Index> & F, std::vector<std::vector<IndexVector> >& A, bool sorted) { A.clear(); A.resize(F.maxCoeff()+1); // Loop over faces for(int i = 0;i<F.rows();i++) { // Loop over this face for(int j = 0;j<F.cols();j++) { // Get indices of edge: s --> d int s = F(i,j); int d = F(i,(j+1)%F.cols()); A.at(s).push_back(d); A.at(d).push_back(s); } } // Remove duplicates for(int i=0; i<(int)A.size();++i) { std::sort(A[i].begin(), A[i].end()); A[i].erase(std::unique(A[i].begin(), A[i].end()), A[i].end()); } // If needed, sort every VV if (sorted) { // Loop over faces // for every vertex v store a set of ordered edges not incident to v that belongs to triangle incident on v. std::vector<std::vector<std::vector<int> > > SR; SR.resize(A.size()); for(int i = 0;i<F.rows();i++) { // Loop over this face for(int j = 0;j<F.cols();j++) { // Get indices of edge: s --> d int s = F(i,j); int d = F(i,(j+1)%F.cols()); // Get index of opposing vertex v int v = F(i,(j+2)%F.cols()); std::vector<int> e(2); e[0] = d; e[1] = v; SR[s].push_back(e); } } for(int v=0; v<(int)SR.size();++v) { std::vector<IndexVector>& vv = A.at(v); std::vector<std::vector<int> >& sr = SR[v]; std::vector<std::vector<int> > pn = sr; // Compute previous/next for every element in sr for(int i=0;i<(int)sr.size();++i) { int a = sr[i][0]; int b = sr[i][1]; // search for previous int p = -1; for(int j=0;j<(int)sr.size();++j) if(sr[j][1] == a) p = j; pn[i][0] = p; // search for next int n = -1; for(int j=0;j<(int)sr.size();++j) if(sr[j][0] == b) n = j; pn[i][1] = n; } // assume manifoldness (look for beginning of a single chain) int c = 0; for(int j=0; j<=(int)sr.size();++j) if (pn[c][0] != -1) c = pn[c][0]; if (pn[c][0] == -1) // border case { // finally produce the new vv relation for(int j=0; j<(int)sr.size();++j) { vv[j] = sr[c][0]; if (pn[c][1] != -1) c = pn[c][1]; } vv.back() = sr[c][1]; } else { // finally produce the new vv relation for(int j=0; j<(int)sr.size();++j) { vv[j] = sr[c][0]; c = pn[c][1]; } } } } }