// This file is part of libigl, a simple c++ geometry processing library. // // Copyright (C) 2015 Alec Jacobson // // 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 "collapse_edge.h" #include "circulation.h" #include "edge_collapse_is_valid.h" #include "decimate_trivial_callbacks.h" #include IGL_INLINE bool igl::collapse_edge( const int e, const Eigen::RowVectorXd & p, Eigen::MatrixXd & V, Eigen::MatrixXi & F, Eigen::MatrixXi & E, Eigen::VectorXi & EMAP, Eigen::MatrixXi & EF, Eigen::MatrixXi & EI, int & e1, int & e2, int & f1, int & f2) { std::vector /*Nse,*/Nsf,Nsv; circulation(e, true,F,EMAP,EF,EI,/*Nse,*/Nsv,Nsf); std::vector /*Nde,*/Ndf,Ndv; circulation(e, false,F,EMAP,EF,EI,/*Nde,*/Ndv,Ndf); return collapse_edge( e,p,Nsv,Nsf,Ndv,Ndf,V,F,E,EMAP,EF,EI,e1,e2,f1,f2); } IGL_INLINE bool igl::collapse_edge( const int e, const Eigen::RowVectorXd & p, /*const*/ std::vector & Nsv, const std::vector & Nsf, /*const*/ std::vector & Ndv, const std::vector & Ndf, Eigen::MatrixXd & V, Eigen::MatrixXi & F, Eigen::MatrixXi & E, Eigen::VectorXi & EMAP, Eigen::MatrixXi & EF, Eigen::MatrixXi & EI, int & a_e1, int & a_e2, int & a_f1, int & a_f2) { // Assign this to 0 rather than, say, -1 so that deleted elements will get // draw as degenerate elements at vertex 0 (which should always exist and // never get collapsed to anything else since it is the smallest index) using namespace Eigen; using namespace std; const int eflip = E(e,0)>E(e,1); // source and destination const int s = eflip?E(e,1):E(e,0); const int d = eflip?E(e,0):E(e,1); if(!edge_collapse_is_valid(Nsv,Ndv)) { return false; } // OVERLOAD: caller may have just computed this // // Important to grab neighbors of d before monkeying with edges const std::vector & nV2Fd = (!eflip ? Nsf : Ndf); // The following implementation strongly relies on s > & Q, Eigen::VectorXi & EQ, Eigen::MatrixXd & C) { int e,e1,e2,f1,f2; decimate_pre_collapse_callback always_try; decimate_post_collapse_callback never_care; decimate_trivial_callbacks(always_try,never_care); return collapse_edge( cost_and_placement,always_try,never_care, V,F,E,EMAP,EF,EI,Q,EQ,C,e,e1,e2,f1,f2); } IGL_INLINE bool igl::collapse_edge( const decimate_cost_and_placement_callback & cost_and_placement, const decimate_pre_collapse_callback & pre_collapse, const decimate_post_collapse_callback & post_collapse, Eigen::MatrixXd & V, Eigen::MatrixXi & F, Eigen::MatrixXi & E, Eigen::VectorXi & EMAP, Eigen::MatrixXi & EF, Eigen::MatrixXi & EI, igl::min_heap< std::tuple > & Q, Eigen::VectorXi & EQ, Eigen::MatrixXd & C) { int e,e1,e2,f1,f2; return collapse_edge( cost_and_placement,pre_collapse,post_collapse, V,F,E,EMAP,EF,EI,Q,EQ,C,e,e1,e2,f1,f2); } IGL_INLINE bool igl::collapse_edge( const decimate_cost_and_placement_callback & cost_and_placement, const decimate_pre_collapse_callback & pre_collapse, const decimate_post_collapse_callback & post_collapse, Eigen::MatrixXd & V, Eigen::MatrixXi & F, Eigen::MatrixXi & E, Eigen::VectorXi & EMAP, Eigen::MatrixXi & EF, Eigen::MatrixXi & EI, igl::min_heap< std::tuple > & Q, Eigen::VectorXi & EQ, Eigen::MatrixXd & C, int & e, int & e1, int & e2, int & f1, int & f2) { using namespace Eigen; using namespace igl; std::tuple p; while(true) { // Check if Q is empty if(Q.empty()) { // no edges to collapse e = -1; return false; } // pop from Q p = Q.top(); if(std::get<0>(p) == std::numeric_limits::infinity()) { e = -1; // min cost edge is infinite cost return false; } Q.pop(); e = std::get<1>(p); // Check if matches timestamp if(std::get<2>(p) == EQ(e)) { break; } // must be stale or dead. assert(std::get<2>(p) < EQ(e) || EQ(e) == -1); // try again. } // Why is this computed up here? // If we just need original face neighbors of edge, could we gather that more // directly than gathering face neighbors of each vertex? std::vector /*Nse,*/Nsf,Nsv; circulation(e, true,F,EMAP,EF,EI,/*Nse,*/Nsv,Nsf); std::vector /*Nde,*/Ndf,Ndv; circulation(e, false,F,EMAP,EF,EI,/*Nde,*/Ndv,Ndf); bool collapsed = true; if(pre_collapse(V,F,E,EMAP,EF,EI,Q,EQ,C,e)) { collapsed = collapse_edge( e,C.row(e), Nsv,Nsf,Ndv,Ndf, V,F,E,EMAP,EF,EI,e1,e2,f1,f2); }else { // Aborted by pre collapse callback collapsed = false; } post_collapse(V,F,E,EMAP,EF,EI,Q,EQ,C,e,e1,e2,f1,f2,collapsed); if(collapsed) { // Erase the two, other collapsed edges by marking their timestamps as -1 EQ(e1) = -1; EQ(e2) = -1; // TODO: visits edges multiple times, ~150% more updates than should // // update local neighbors // loop over original face neighbors // // Can't use previous computed Nse and Nde because those refer to EMAP // before it was changed... std::vector Nf; Nf.reserve( Nsf.size() + Ndf.size() ); // preallocate memory Nf.insert( Nf.end(), Nsf.begin(), Nsf.end() ); Nf.insert( Nf.end(), Ndf.begin(), Ndf.end() ); // https://stackoverflow.com/a/1041939/148668 std::sort( Nf.begin(), Nf.end() ); Nf.erase( std::unique( Nf.begin(), Nf.end() ), Nf.end() ); // Collect all edges that must be updated std::vector Ne; Ne.reserve(3*Nf.size()); for(auto & n : Nf) { if(F(n,0) != IGL_COLLAPSE_EDGE_NULL || F(n,1) != IGL_COLLAPSE_EDGE_NULL || F(n,2) != IGL_COLLAPSE_EDGE_NULL) { for(int v = 0;v<3;v++) { // get edge id const int ei = EMAP(v*F.rows()+n); Ne.push_back(ei); } } } // Only process edge once std::sort( Ne.begin(), Ne.end() ); Ne.erase( std::unique( Ne.begin(), Ne.end() ), Ne.end() ); for(auto & ei : Ne) { // compute cost and potential placement double cost; RowVectorXd place; cost_and_placement(ei,V,F,E,EMAP,EF,EI,cost,place); // Increment timestamp EQ(ei)++; // Replace in queue Q.emplace(cost,ei,EQ(ei)); C.row(ei) = place; } }else { // reinsert with infinite weight (the provided cost function must **not** // have given this un-collapsable edge inf cost already) // Increment timestamp EQ(e)++; // Replace in queue Q.emplace(std::numeric_limits::infinity(),e,EQ(e)); } return collapsed; }