// This file is part of libigl, a simple c++ geometry processing library. // // Copyright (C) 2013 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 "arap_dof.h" #include "cotmatrix.h" #include "massmatrix.h" #include "speye.h" #include "repdiag.h" #include "repmat.h" #include "slice.h" #include "colon.h" #include "is_sparse.h" #include "mode.h" #include "is_symmetric.h" #include "group_sum_matrix.h" #include "arap_rhs.h" #include "covariance_scatter_matrix.h" #include "fit_rotations.h" #include "verbose.h" #include "print_ijv.h" #include "get_seconds_hires.h" //#include "MKLEigenInterface.h" #include "min_quad_dense.h" #include "get_seconds.h" #include "columnize.h" // defined if no early exit is supported, i.e., always take a fixed number of iterations #define IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT // A careful derivation of this implementation is given in the corresponding // matlab function arap_dof.m template IGL_INLINE bool igl::arap_dof_precomputation( const Eigen::MatrixXd & V, const Eigen::MatrixXi & F, const LbsMatrixType & M, const Eigen::Matrix & G, ArapDOFData & data) { using namespace Eigen; typedef Matrix MatrixXS; // number of mesh (domain) vertices int n = V.rows(); // cache problem size data.n = n; // dimension of mesh data.dim = V.cols(); assert(data.dim == M.rows()/n); assert(data.dim*n == M.rows()); if(data.dim == 3) { // Check if z-coordinate is all zeros if(V.col(2).minCoeff() == 0 && V.col(2).maxCoeff() == 0) { data.effective_dim = 2; } }else { data.effective_dim = data.dim; } // Number of handles data.m = M.cols()/data.dim/(data.dim+1); assert(data.m*data.dim*(data.dim+1) == M.cols()); //assert(m == C.rows()); //printf("n=%d; dim=%d; m=%d;\n",n,data.dim,data.m); // Build cotangent laplacian SparseMatrix Lcot; //printf("cotmatrix()\n"); cotmatrix(V,F,Lcot); // Discrete laplacian (should be minus matlab version) SparseMatrix Lapl = -2.0*Lcot; #ifdef EXTREME_VERBOSE cout<<"LaplIJV=["< G_sum; if(G.size() == 0) { speye(n,G_sum); }else { // groups are defined per vertex, convert to per face using mode Eigen::Matrix GG; if(data.energy == ARAP_ENERGY_TYPE_ELEMENTS) { MatrixXi GF(F.rows(),F.cols()); for(int j = 0;j GFj; slice(G,F.col(j),GFj); GF.col(j) = GFj; } mode(GF,2,GG); }else { GG=G; } //printf("group_sum_matrix()\n"); group_sum_matrix(GG,G_sum); } #ifdef EXTREME_VERBOSE cout<<"G_sumIJV=["< CSM; //printf("covariance_scatter_matrix()\n"); covariance_scatter_matrix(V,F,data.energy,CSM); #ifdef EXTREME_VERBOSE cout<<"CSMIJV=["< G_sum_dim; repdiag(G_sum,data.dim,G_sum_dim); CSM = (G_sum_dim * CSM).eval(); #ifdef EXTREME_VERBOSE cout<<"CSMIJV=["< span_n(n); for(int i = 0;i span_mlbs_cols(M.cols()); for(int i = 0;i CSMj; //printf("CSM_M(): slice\n"); slice( CSM, colon(j*k,(j+1)*k-1), colon(j*n,(j+1)*n-1), CSMj); assert(CSMj.rows() == k); assert(CSMj.cols() == n); LbsMatrixType CSMjM_i = CSMj * M_i; if(is_sparse(CSMjM_i)) { // Convert to full //printf("CSM_M(): full\n"); MatrixXd CSMjM_ifull(CSMjM_i); // printf("CSM_M[%d]: %d %d\n",i,data.CSM_M[i].rows(),data.CSM_M[i].cols()); // printf("CSM_M[%d].block(%d*%d=%d,0,%d,%d): %d %d\n",i,j,k,CSMjM_i.rows(),CSMjM_i.cols(), // data.CSM_M[i].block(j*k,0,CSMjM_i.rows(),CSMjM_i.cols()).rows(), // data.CSM_M[i].block(j*k,0,CSMjM_i.rows(),CSMjM_i.cols()).cols()); // printf("CSM_MjMi: %d %d\n",i,CSMjM_i.rows(),CSMjM_i.cols()); // printf("CSM_MjM_ifull: %d %d\n",i,CSMjM_ifull.rows(),CSMjM_ifull.cols()); data.CSM_M[i].block(j*k,0,CSMjM_i.rows(),CSMjM_i.cols()) = CSMjM_ifull; }else { data.CSM_M[i].block(j*k,0,CSMjM_i.rows(),CSMjM_i.cols()) = CSMjM_i; } } #ifdef EXTREME_VERBOSE cout<<"CSM_Mi=["< K; arap_rhs(V,F,V.cols(),data.energy,K); //#ifdef EXTREME_VERBOSE // cout<<"KIJV=["< G_sumT = G_sum.transpose(); SparseMatrix G_sumT_dim_dim; repdiag(G_sumT,data.dim*data.dim,G_sumT_dim_dim); LbsMatrixType MT = M.transpose(); // If this is a bottle neck then consider reordering matrix multiplication data.M_KG = -4.0 * (MT * (K * G_sumT_dim_dim)); //#ifdef EXTREME_VERBOSE // cout<<"data.M_KGIJV=["< A; repdiag(Lapl,data.dim,A); data.Q = MT * (A * M); //#ifdef EXTREME_VERBOSE // cout<<"QIJV=["< Mass; //printf("massmatrix()\n"); massmatrix(V,F,(F.cols()>3?MASSMATRIX_TYPE_BARYCENTRIC:MASSMATRIX_TYPE_VORONOI),Mass); //cout<<"MIJV=["< Mass_rep; repdiag(Mass,data.dim,Mass_rep); // Multiply either side by weights matrix (should be dense) data.Mass_tilde = MT * Mass_rep * M; MatrixXd ones(data.dim*data.n,data.dim); for(int i = 0;i inline static SSCALAR maxBlokErr(const Eigen::Matrix3f &blok) { SSCALAR mD; SSCALAR value = blok(0,0); SSCALAR diff1 = fabs(blok(1,1) - value); SSCALAR diff2 = fabs(blok(2,2) - value); if (diff1 > diff2) mD = diff1; else mD = diff2; for (int v=0; v<3; v++) { for (int w=0; w<3; w++) { if (v == w) { continue; } if (mD < fabs(blok(v, w))) { mD = fabs(blok(v, w)); } } } return mD; } // converts CSM_M_SSCALAR[0], CSM_M_SSCALAR[1], CSM_M_SSCALAR[2] into one // "condensed" matrix CSM while checking we're not losing any information by // this process; specifically, returns maximal difference from scaled 3x3 // identity blocks, which should be pretty small number template static typename MatrixXS::Scalar condense_CSM( const std::vector &CSM_M_SSCALAR, int numBones, int dim, MatrixXS &CSM) { const int numRows = CSM_M_SSCALAR[0].rows(); assert(CSM_M_SSCALAR[0].cols() == dim*(dim+1)*numBones); assert(CSM_M_SSCALAR[1].cols() == dim*(dim+1)*numBones); assert(CSM_M_SSCALAR[2].cols() == dim*(dim+1)*numBones); assert(CSM_M_SSCALAR[1].rows() == numRows); assert(CSM_M_SSCALAR[2].rows() == numRows); const int numCols = (dim + 1)*numBones; CSM.resize(numRows, numCols); typedef typename MatrixXS::Scalar SSCALAR; SSCALAR maxDiff = 0.0f; for (int r=0; r(blok); if (mD > maxDiff) maxDiff = mD; // use the first value: CSM(r, coord*numBones + b) = blok(0,0); } } } return maxDiff; } // splits x_0, ... , x_dim coordinates in column vector 'L' into a numBones*(dimp1) x dim matrix 'Lsep'; // assumes 'Lsep' has already been preallocated // // is this the same as uncolumnize? no. template static void splitColumns( const MatL &L, int numBones, int dim, int dimp1, MatLsep &Lsep) { assert(L.cols() == 1); assert(L.rows() == dim*(dimp1)*numBones); assert(Lsep.rows() == (dimp1)*numBones && Lsep.cols() == dim); for (int b=0; b static void mergeColumns(const MatrixXS &Lsep, int numBones, int dim, int dimp1, MatrixXS &L) { assert(L.cols() == 1); assert(L.rows() == dim*(dimp1)*numBones); assert(Lsep.rows() == (dimp1)*numBones && Lsep.cols() == dim); for (int b=0; b static typename MatrixXS::Scalar condense_Solve1(MatrixXS &Solve1, int numBones, int numGroups, int dim, MatrixXS &CSolve1) { assert(Solve1.rows() == dim*(dim + 1)*numBones); assert(Solve1.cols() == dim*dim*numGroups); typedef typename MatrixXS::Scalar SSCALAR; SSCALAR maxDiff = 0.0f; CSolve1.resize((dim + 1)*numBones, dim*numGroups); for (int rowCoord=0; rowCoord(blok); if (mD > maxDiff) maxDiff = mD; CSolve1(rowCoord*numBones + b, colCoord*numGroups + g) = blok(0,0); } } } } return maxDiff; } } template IGL_INLINE bool igl::arap_dof_recomputation( const Eigen::Matrix & fixed_dim, const Eigen::SparseMatrix & A_eq, ArapDOFData & data) { using namespace Eigen; typedef Matrix MatrixXS; LbsMatrixType * Q; LbsMatrixType Qdyn; if(data.with_dynamics) { // multiply by 1/timestep and to quadratic coefficients matrix // Might be missing a 0.5 here LbsMatrixType Q_copy = data.Q; Qdyn = Q_copy + (1.0/(data.h*data.h))*data.Mass_tilde; Q = &Qdyn; // This may/should be superfluous //printf("is_symmetric()\n"); if(!is_symmetric(*Q)) { //printf("Fixing symmetry...\n"); // "Fix" symmetry LbsMatrixType QT = (*Q).transpose(); LbsMatrixType Q_copy = *Q; *Q = 0.5*(Q_copy+QT); // Check that ^^^ this really worked. It doesn't always //assert(is_symmetric(*Q)); } }else { Q = &data.Q; } assert((int)data.CSM_M.size() == data.dim); assert(A_eq.cols() == data.m*data.dim*(data.dim+1)); data.fixed_dim = fixed_dim; if(fixed_dim.size() > 0) { assert(fixed_dim.maxCoeff() < data.m*data.dim*(data.dim+1)); assert(fixed_dim.minCoeff() >= 0); } #ifdef EXTREME_VERBOSE cout<<"data.fixed_dim=["<(), M_Solve.block(0, fsRows, fsRows, fsCols2).template cast(); if(data.with_dynamics) { printf( "---------------------------------------------------------------------\n" "\n\n\nWITH DYNAMICS recomputation\n\n\n" "---------------------------------------------------------------------\n" ); // Also need to save Π1 before it gets multiplied by Ktilde (aka M_KG) data.Pi_1 = M_Solve.block(0, 0, fsRows, fsRows).template cast(); } // Precompute condensed matrices, // first CSM: std::vector CSM_M_SSCALAR; CSM_M_SSCALAR.resize(data.dim); for (int i=0; i(); SSCALAR maxErr1 = condense_CSM(CSM_M_SSCALAR, data.m, data.dim, data.CSM); verbose("condense_CSM maxErr = %.15f (this should be close to zero)\n", maxErr1); assert(fabs(maxErr1) < 1e-5); // and then solveBlock1: // number of groups const int k = data.CSM_M[0].rows()/data.dim; MatrixXS SolveBlock1 = data.M_FullSolve.block(0, 0, data.M_FullSolve.rows(), data.dim * data.dim * k); SSCALAR maxErr2 = condense_Solve1(SolveBlock1, data.m, k, data.dim, data.CSolveBlock1); verbose("condense_Solve1 maxErr = %.15f (this should be close to zero)\n", maxErr2); assert(fabs(maxErr2) < 1e-5); return true; } template IGL_INLINE bool igl::arap_dof_update( const ArapDOFData & data, const Eigen::Matrix & B_eq, const Eigen::MatrixXd & L0, const int max_iters, const double #ifdef IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT tol, #else /*tol*/, #endif Eigen::MatrixXd & L ) { using namespace Eigen; typedef Matrix MatrixXS; #ifdef ARAP_GLOBAL_TIMING double timer_start = get_seconds_hires(); #endif // number of dimensions assert((int)data.CSM_M.size() == data.dim); assert((int)L0.size() == (data.m)*data.dim*(data.dim+1)); assert(max_iters >= 0); assert(tol >= 0); // timing variables double sec_start, sec_covGather, sec_fitRotations, //sec_rhs, sec_prepMult, sec_solve, sec_end; assert(L0.cols() == 1); #ifdef EXTREME_VERBOSE cout<<"dim="<(); int iters = 0; #ifndef IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT double max_diff = tol+1; #endif MatrixXS S(k*data.dim,data.dim); MatrixXS R(data.dim,data.dim*k); Eigen::Matrix Rcol(data.dim * data.dim * k); Matrix B_eq_SSCALAR = B_eq.cast(); Matrix B_eq_fix_SSCALAR; Matrix L0SSCALAR = L0.cast(); slice(L0SSCALAR, data.fixed_dim, B_eq_fix_SSCALAR); //MatrixXS rhsFull(Rcol.rows() + B_eq.rows() + B_eq_fix_SSCALAR.rows(), 1); MatrixXS Lsep(data.m*(data.dim + 1), 3); const MatrixXS L_part2 = data.M_FullSolve.block(0, Rcol.rows(), data.M_FullSolve.rows(), B_eq_SSCALAR.rows()) * B_eq_SSCALAR; const MatrixXS L_part3 = data.M_FullSolve.block(0, Rcol.rows() + B_eq_SSCALAR.rows(), data.M_FullSolve.rows(), B_eq_fix_SSCALAR.rows()) * B_eq_fix_SSCALAR; MatrixXS L_part2and3 = L_part2 + L_part3; // preallocate workspace variables: MatrixXS Rxyz(k*data.dim, data.dim); MatrixXS L_part1xyz((data.dim + 1) * data.m, data.dim); MatrixXS L_part1(data.dim * (data.dim + 1) * data.m, 1); #ifdef ARAP_GLOBAL_TIMING double timer_prepFinished = get_seconds_hires(); #endif #ifdef IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT while(iters < max_iters) #else while(iters < max_iters && max_diff > tol) #endif { if(data.print_timings) { sec_start = get_seconds_hires(); } #ifndef IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT L_prev = L_SSCALAR; #endif /////////////////////////////////////////////////////////////////////////// // Local step: Fix positions, fit rotations /////////////////////////////////////////////////////////////////////////// // Gather covariance matrices splitColumns(L_SSCALAR, data.m, data.dim, data.dim + 1, Lsep); S = data.CSM * Lsep; // interestingly, this doesn't seem to be so slow, but //MKL is still 2x faster (probably due to AVX) //#ifdef IGL_ARAP_DOF_DOUBLE_PRECISION_SOLVE // MKL_matMatMult_double(S, data.CSM, Lsep); //#else // MKL_matMatMult_single(S, data.CSM, Lsep); //#endif if(data.print_timings) { sec_covGather = get_seconds_hires(); } #ifdef EXTREME_VERBOSE cout<<"S=["<(); MatrixXd temp_g = data.fgrav*(data.grav_mag*data.grav_dir); assert(data.fext.rows() == temp_g.rows()); assert(data.fext.cols() == temp_g.cols()); MatrixXd temp2 = data.Mass_tilde * temp_d + temp_g + data.fext.template cast(); MatrixXS temp2_f = temp2.template cast(); L_part1_dyn = data.Pi_1 * temp2_f; L_part1.array() = L_part1.array() + L_part1_dyn.array(); } //L_SSCALAR = L_part1 + L_part2and3; assert(L_SSCALAR.rows() == L_part1.rows() && L_SSCALAR.rows() == L_part2and3.rows()); for (int i=0; i(); assert(L.cols() == 1); #ifdef ARAP_GLOBAL_TIMING double timer_finito = get_seconds_hires(); printf( "ARAP preparation = %f, " "all %i iterations = %f [ms]\n", (timer_prepFinished - timer_start)*1000.0, max_iters, (timer_finito - timer_prepFinished)*1000.0); #endif return true; } #ifdef IGL_STATIC_LIBRARY // Explicit template instantiation template bool igl::arap_dof_update, double>(ArapDOFData, double> const&, Eigen::Matrix const&, Eigen::Matrix const&, int, double, Eigen::Matrix&); template bool igl::arap_dof_recomputation, double>(Eigen::Matrix const&, Eigen::SparseMatrix const&, ArapDOFData, double>&); template bool igl::arap_dof_precomputation, double>(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, ArapDOFData, double>&); template bool igl::arap_dof_update, float>(igl::ArapDOFData, float> const&, Eigen::Matrix const&, Eigen::Matrix const&, int, double, Eigen::Matrix&); template bool igl::arap_dof_recomputation, float>(Eigen::Matrix const&, Eigen::SparseMatrix const&, igl::ArapDOFData, float>&); template bool igl::arap_dof_precomputation, float>(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, igl::ArapDOFData, float>&); #endif