Files
OrcaSlicer/deps_src/libigl/igl/arap_dof.cpp
Donovan Baarda dc5897d7b5 Update eigen to v5.0.1 and libigl to v2.6.0. (#11311)
* Update eigen from v3.3.7 to v5.0.1.

This updates eigen from v3.3.7 released on  December 11, 2018-12-11 to v5.0.1
released on 2025-11-11. There have be a large number of bug-fixes,
optimizations, and improvements between these releases. See the details at;

https://gitlab.com/libeigen/eigen/-/releases

It retains the previous custom minimal `CMakeLists.txt`, and adds a
README-OrcaSlicer.md that explains what version and parts of the upstream
eigen release have been included, and where the full release can be found.

* Update libigl from v2.0.0 (or older) to v2.6.0.

This updates libigl from what was probably v2.0.0 released on 2018-10-16 to
v2.6.0 released on 2025-05-15. It's possible the old version was even older
than that but there is no version indicators in the code and I ran out of
patience identifying missing changes and only went back as far as v2.0.0.

There have been a large number of bug-fixes, optimizations, and improvements
between these versions. See the following for details;

https://github.com/libigl/libigl/releases

I retained the minimal custom `CMakeLists.txt`, added `README.md` from the
libigl distribution which identifies the version, and added a
README-OrcaSlicer.md that details the version and parts that have been
included.

* Update libslic3r for libigl v2.6.0 changes.

This updates libslic3r for all changes moving to eigen v5.0.1 and libigl
v2.6.0. Despite the large number of updates to both dependencies, no changes
were required for the eigen update, and only one change was required for the
libigl update.

For libigl, `igl::Hit` was changed to a template taking the Scalar type to
use. Previously it was hard-coded to `float`, so to minimize possible impact
I've updated all places it is used from `igl::Hit` to `igl::Hit<float>`.

* Add compiler option `-DNOMINMAX` for libigl with MSVC.

MSVC by default defines `min(()` and `max()` macros that break
`std::numeric_limits<>::max()`. The upstream cmake that we don't include
adds `-DNOMINMAX` for the libigl module when compiling with MSVC, so we need
to add the same thing here.

* Fix src/libslic3r/TriangleMeshDeal.cpp for the unmodified upstream libigl.

This fixes `TriangleMeshDeal.cpp` to work with the unmodified upstream
libigl v2.6.0. loop.{h,cpp} implementation.

This file and feature was added in PR "BBS Port: Mesh Subdivision" (#12150)
which included changes to `loop.{h,cpp}` in the old version of libigl. This PR
avoids modifying the included dependencies, and uses the updated upstream
versions of those files without any modifications, which requires fixing
TriangleMeshDeal.cpp to work with them.

In particular, the modifications made to `loop.{h,cpp}` included changing the
return type from void to bool, adding additional validation checking of the
input meshes, and returning false if they failed validation. These added
checks looked unnecessary and would only have caught problems if the input
mesh was very corrupt.

To make `TriangleMeshDeal.cpp` work without this built-in checking
functionality, I removed checking/handling of any `false` return value.

There was also a hell of a lot of redundant copying and casting back and forth
between float and double, so I cleaned that up. The input and output meshs use
floats for the vertexes, and there would be no accuracy benefits from casting
to and from doubles for the simple weighted average operations done by
igl::loop(). So this just uses `Eigen:Map` to use the original input mesh
vertex data directly without requiring any copy or casting.

* Move eigen from included `deps_src` to externaly fetched `deps`.

This copys what PrusaSlicer did and moved it from an included dependency under
`deps_src` to an externaly fetched dependency under `deps`. This requires
updating some `CMakeList.txt` configs and removing the old and obsolete
`cmake/modules/FindEigen3.cmake`. The details of when this was done in
PrusaSlicer and the followup fixes are at;

* 21116995d7
* https://github.com/prusa3d/PrusaSlicer/issues/13608
* https://github.com/prusa3d/PrusaSlicer/pull/13609
* e3c277b9ee

For some reason I don't fully understand this also required fixing
`src/slic3r/GUI/GUI_App.cpp` by adding `#include <boost/nowide/cstdio.hpp>` to
fix an `error: ‘remove’ is not a member of ‘boost::nowide'`. The main thing I
don't understand is how it worked before. Note that this include is in the
PrusaSlicer version of this file, but it also significantly deviates from what
is currently in OrcaSlicer in many other ways.

* Whups... I missed adding the deps/Eigen/Eigen.cmake file...

* Tidy some whitespace indenting in CMakeLists.txt.

* Ugh... tabs indenting needing fixes.

* Change the include order of deps/Eigen.

It turns out that although Boost includes some references to Eigen, Eigen also
includes some references to Boost for supporting some of it's additional
numeric types.

I don't think it matters much since we are not using these features, but I
think technically its more correct to say Eigen depends on Boost than the
other way around, so I've re-ordered them.

* Add source for Eigen 5.0.1 download to flatpak yml config.

* Add explicit `DEPENDS dep_Boost to deps/Eigen.

I missed this before. This ensures we don't rely on include orders to make
sure Boost is installed before we configure Eigen.

* Add `DEPENDS dep_Boost dep_GMP dep_MPFR` to deps/Eigen.

It turns out Eigen can also use GMP and MPFR for multi-precision and
multi-precision-rounded numeric types if they are available.

Again, I don't think we are using these so it doesn't really matter, but it is
technically correct and ensures they are there if we ever do need them.

* Fix deps DEPENDENCY ordering for GMP, MPFR, Eigen, and CGAL.

I think this is finally correct. Apparently CGAL also optionally depends on
Eigen, so the correct dependency order from lowest to highest is GMP, MPFR, Eigen, and CGAL.

---------

Co-authored-by: Donovan Baarda <dbaarda@google.com>
Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
2026-05-12 15:09:13 +08:00

917 lines
30 KiB
C++

// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
//
// 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 "IGL_ASSERT.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 "MKLEigenInterface.h"
#include "kkt_inverse.h"
#include "get_seconds.h"
#include "columnize.h"
#include <type_traits>
// defined if no early exit is supported, i.e., always take a fixed number of iterations
#define IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT
// To avoid putting _any_ dense slices in the static library use a work around
// so that we can slice LbsMatrixType as sparse or dense below.
#if __cplusplus < 201703L
template <typename Mat, bool IsSparse> struct arap_dof_slice_helper;
template <typename Mat> struct arap_dof_slice_helper<Mat,true>
{
static void slice(const Mat & A, const Eigen::VectorXi & I, const Eigen::VectorXi & J, Mat & B)
{
static_assert(std::is_base_of<Eigen::SparseMatrixBase<Mat>, Mat>::value, "Mat must be sparse");
igl::slice(A,I,J,B);
}
};
template <typename Mat> struct arap_dof_slice_helper<Mat,false>
{
static void slice(const Mat & A, const Eigen::VectorXi & I, const Eigen::VectorXi & J, Mat & B)
{
B = A(I,J);
}
};
#endif
// A careful derivation of this implementation is given in the corresponding
// matlab function arap_dof.m
template <typename LbsMatrixType, typename SSCALAR>
IGL_INLINE bool igl::arap_dof_precomputation(
const Eigen::MatrixXd & V,
const Eigen::MatrixXi & F,
const LbsMatrixType & M,
const Eigen::Matrix<int,Eigen::Dynamic,1> & G,
ArapDOFData<LbsMatrixType, SSCALAR> & data)
{
using namespace Eigen;
typedef Matrix<SSCALAR, Dynamic, Dynamic> 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<double> Lcot;
//printf("cotmatrix()\n");
cotmatrix(V,F,Lcot);
// Discrete laplacian (should be minus matlab version)
SparseMatrix<double> Lapl = -2.0*Lcot;
#ifdef EXTREME_VERBOSE
cout<<"LaplIJV=["<<endl;print_ijv(Lapl,1);cout<<endl<<"];"<<
endl<<"Lapl=sparse(LaplIJV(:,1),LaplIJV(:,2),LaplIJV(:,3),"<<
Lapl.rows()<<","<<Lapl.cols()<<");"<<endl;
#endif
// Get group sum scatter matrix, when applied sums all entries of the same
// group according to G
SparseMatrix<double> G_sum;
if(G.size() == 0)
{
speye(n,G_sum);
}else
{
// groups are defined per vertex, convert to per face using mode
Eigen::Matrix<int,Eigen::Dynamic,1> GG;
if(data.energy == ARAP_ENERGY_TYPE_ELEMENTS)
{
MatrixXi GF(F.rows(),F.cols());
for(int j = 0;j<F.cols();j++)
{
GF.col(j) = G(F.col(j));
}
mode<int>(GF,2,GG);
}else
{
GG=G;
}
//printf("group_sum_matrix()\n");
group_sum_matrix(GG,G_sum);
}
#ifdef EXTREME_VERBOSE
cout<<"G_sumIJV=["<<endl;print_ijv(G_sum,1);cout<<endl<<"];"<<
endl<<"G_sum=sparse(G_sumIJV(:,1),G_sumIJV(:,2),G_sumIJV(:,3),"<<
G_sum.rows()<<","<<G_sum.cols()<<");"<<endl;
#endif
// Get covariance scatter matrix, when applied collects the covariance matrices
// used to fit rotations to during optimization
SparseMatrix<double> CSM;
//printf("covariance_scatter_matrix()\n");
covariance_scatter_matrix(V,F,data.energy,CSM);
#ifdef EXTREME_VERBOSE
cout<<"CSMIJV=["<<endl;print_ijv(CSM,1);cout<<endl<<"];"<<
endl<<"CSM=sparse(CSMIJV(:,1),CSMIJV(:,2),CSMIJV(:,3),"<<
CSM.rows()<<","<<CSM.cols()<<");"<<endl;
#endif
// Build the covariance matrix "constructor". This is a set of *scatter*
// matrices that when multiplied on the right by column of the transformation
// matrix entries (the degrees of freedom) L, we get a stack of dim by 1
// covariance matrix column, with a column in the stack for each rotation
// *group*. The output is a list of matrices because we construct each column
// in the stack of covariance matrices with an independent matrix-vector
// multiplication.
//
// We want to build S which is a stack of dim by dim covariance matrices.
// Thus S is dim*g by dim, where dim is the number of dimensions and g is the
// number of groups. We can precompute dim matrices CSM_M such that column i
// in S is computed as S(:,i) = CSM_M{i} * L, where L is a column of the
// skinning transformation matrix values. To be clear, the covariance matrix
// for group k is then given as the dim by dim matrix pulled from the stack:
// S((k-1)*dim + 1:dim,:)
// Apply group sum to each dimension's block of covariance scatter matrix
SparseMatrix<double> G_sum_dim;
repdiag(G_sum,data.dim,G_sum_dim);
CSM = (G_sum_dim * CSM).eval();
#ifdef EXTREME_VERBOSE
cout<<"CSMIJV=["<<endl;print_ijv(CSM,1);cout<<endl<<"];"<<
endl<<"CSM=sparse(CSMIJV(:,1),CSMIJV(:,2),CSMIJV(:,3),"<<
CSM.rows()<<","<<CSM.cols()<<");"<<endl;
#endif
//printf("CSM_M()\n");
// Precompute CSM times M for each dimension
data.CSM_M.resize(data.dim);
#ifdef EXTREME_VERBOSE
cout<<"data.CSM_M = cell("<<data.dim<<",1);"<<endl;
#endif
// span of integers from 0 to n-1
Eigen::Matrix<int,Eigen::Dynamic,1> span_n(n);
for(int i = 0;i<n;i++)
{
span_n(i) = i;
}
// span of integers from 0 to M.cols()-1
Eigen::Matrix<int,Eigen::Dynamic,1> span_mlbs_cols(M.cols());
for(int i = 0;i<M.cols();i++)
{
span_mlbs_cols(i) = i;
}
// number of groups
int k = CSM.rows()/data.dim;
for(int i = 0;i<data.dim;i++)
{
//printf("CSM_M(): Mi\n");
LbsMatrixType M_i;
//printf("CSM_M(): slice\n");
#if __cplusplus >= 201703L
// Check if LbsMatrixType is a sparse matrix
if constexpr (std::is_base_of<SparseMatrixBase<LbsMatrixType>, LbsMatrixType>::value)
{
slice(M,(span_n.array()+i*n).matrix().eval(),span_mlbs_cols,M_i);
}
else
{
M_i = M((span_n.array()+i*n).eval(),span_mlbs_cols);
}
#else
constexpr bool LbsMatrixTypeIsSparse = std::is_base_of<SparseMatrixBase<LbsMatrixType>, LbsMatrixType>::value;
arap_dof_slice_helper<LbsMatrixType,LbsMatrixTypeIsSparse>::slice(M,(span_n.array()+i*n).matrix().eval(),span_mlbs_cols,M_i);
#endif
LbsMatrixType M_i_dim;
data.CSM_M[i].resize(k*data.dim,data.m*data.dim*(data.dim+1));
assert(data.CSM_M[i].cols() == M.cols());
for(int j = 0;j<data.dim;j++)
{
SparseMatrix<double> CSMj;
//printf("CSM_M(): slice\n");
slice(
CSM,
colon<int>(j*k,(j+1)*k-1),
colon<int>(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=["<<endl<<data.CSM_M[i]<<endl<<"];"<<endl;
#endif
}
// precompute arap_rhs matrix
//printf("arap_rhs()\n");
SparseMatrix<double> K;
arap_rhs(V,F,V.cols(),data.energy,K);
//#ifdef EXTREME_VERBOSE
// cout<<"KIJV=["<<endl;print_ijv(K,1);cout<<endl<<"];"<<
// endl<<"K=sparse(KIJV(:,1),KIJV(:,2),KIJV(:,3),"<<
// K.rows()<<","<<K.cols()<<");"<<endl;
//#endif
// Precompute left muliplication by M and right multiplication by G_sum
SparseMatrix<double> G_sumT = G_sum.transpose();
SparseMatrix<double> 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=["<<endl;print_ijv(data.M_KG,1);cout<<endl<<"];"<<
// endl<<"data.M_KG=sparse(data.M_KGIJV(:,1),data.M_KGIJV(:,2),data.M_KGIJV(:,3),"<<
// data.M_KG.rows()<<","<<data.M_KG.cols()<<");"<<endl;
//#endif
// Precompute system matrix
//printf("A()\n");
SparseMatrix<double> A;
repdiag(Lapl,data.dim,A);
data.Q = MT * (A * M);
//#ifdef EXTREME_VERBOSE
// cout<<"QIJV=["<<endl;print_ijv(data.Q,1);cout<<endl<<"];"<<
// endl<<"Q=sparse(QIJV(:,1),QIJV(:,2),QIJV(:,3),"<<
// data.Q.rows()<<","<<data.Q.cols()<<");"<<endl;
//#endif
// Always do dynamics precomputation so we can hot-switch
//if(data.with_dynamics)
//{
// Build cotangent laplacian
SparseMatrix<double> Mass;
//printf("massmatrix()\n");
massmatrix(V,F,(F.cols()>3?MASSMATRIX_TYPE_BARYCENTRIC:MASSMATRIX_TYPE_VORONOI),Mass);
//cout<<"MIJV=["<<endl;print_ijv(Mass,1);cout<<endl<<"];"<<
// endl<<"M=sparse(MIJV(:,1),MIJV(:,2),MIJV(:,3),"<<
// Mass.rows()<<","<<Mass.cols()<<");"<<endl;
//speye(data.n,Mass);
SparseMatrix<double> 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<data.n;i++)
{
for(int d = 0;d<data.dim;d++)
{
ones(i+d*data.n,d) = 1;
}
}
data.fgrav = MT * (Mass_rep * ones);
data.fext = MatrixXS::Zero(MT.rows(),1);
//data.fgrav = MT * (ones);
//}
// This may/should be superfluous
//printf("is_symmetric()\n");
if(!is_symmetric(data.Q))
{
//printf("Fixing symmetry...\n");
// "Fix" symmetry
LbsMatrixType QT = data.Q.transpose();
LbsMatrixType Q_copy = data.Q;
data.Q = 0.5*(Q_copy+QT);
// Check that ^^^ this really worked. It doesn't always
//assert(is_symmetric(*Q));
}
//printf("arap_dof_precomputation() succeeded... so far...\n");
verbose("Number of handles: %i\n", data.m);
return true;
}
/////////////////////////////////////////////////////////////////////////
//
// STATIC FUNCTIONS (These should be removed or properly defined)
//
/////////////////////////////////////////////////////////////////////////
namespace igl
{
// returns maximal difference of 'blok' from scalar times 3x3 identity:
template <typename SSCALAR>
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 <typename MatrixXS>
static typename MatrixXS::Scalar condense_CSM(
const std::vector<MatrixXS> &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<numRows; r++)
{
for (int coord=0; coord<dim+1; coord++)
{
for (int b=0; b<numBones; b++)
{
// this is just a test if we really have a multiple of 3x3 identity
Eigen::Matrix3f blok;
for (int v=0; v<3; v++)
{
for (int w=0; w<3; w++)
{
blok(v,w) = CSM_M_SSCALAR[v](r, coord*(numBones*dim) + b + w*numBones);
}
}
//SSCALAR value[3];
//for (int v=0; v<3; v++)
// CSM_M_SSCALAR[v](r, coord*(numBones*dim) + b + v*numBones);
SSCALAR mD = maxBlokErr<SSCALAR>(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 <typename MatL, typename MatLsep>
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<numBones; b++)
{
for (int coord=0; coord<dimp1; coord++)
{
for (int c=0; c<dim; c++)
{
Lsep(coord*numBones + b, c) = L(coord*numBones*dim + c*numBones + b, 0);
}
}
}
}
// the inverse of splitColumns, i.e., takes numBones*(dimp1) x dim matrix 'Lsep' and merges the dimensions
// into columns vector 'L' (which is assumed to be already allocated):
//
// is this the same as columnize? no.
template <typename MatrixXS>
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<numBones; b++)
{
for (int coord=0; coord<dimp1; coord++)
{
for (int c=0; c<dim; c++)
{
L(coord*numBones*dim + c*numBones + b, 0) = Lsep(coord*numBones + b, c);
}
}
}
}
// converts "Solve1" the "rotations" part of FullSolve matrix (the first part)
// into one "condensed" matrix CSolve1 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 <typename MatrixXS>
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<dim+1; rowCoord++)
{
for (int b=0; b<numBones; b++)
{
for (int colCoord=0; colCoord<dim; colCoord++)
{
for (int g=0; g<numGroups; g++)
{
Eigen::Matrix3f blok;
for (int r=0; r<3; r++)
{
for (int c=0; c<3; c++)
{
blok(r, c) = Solve1(rowCoord*numBones*dim + r*numBones + b, colCoord*numGroups*dim + c*numGroups + g);
}
}
SSCALAR mD = maxBlokErr<SSCALAR>(blok);
if (mD > maxDiff) maxDiff = mD;
CSolve1(rowCoord*numBones + b, colCoord*numGroups + g) = blok(0,0);
}
}
}
}
return maxDiff;
}
}
template <typename LbsMatrixType, typename SSCALAR>
IGL_INLINE bool igl::arap_dof_recomputation(
const Eigen::Matrix<int,Eigen::Dynamic,1> & fixed_dim,
const Eigen::SparseMatrix<double> & A_eq,
ArapDOFData<LbsMatrixType, SSCALAR> & data)
{
using namespace Eigen;
typedef Matrix<SSCALAR, Dynamic, Dynamic> 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=["<<endl<<data.fixed_dim<<endl<<"]+1;"<<endl;
#endif
// Compute dense solve matrix (alternative of matrix factorization)
//printf("kkt_inverse()\n");
MatrixXd Qfull(*Q);
MatrixXd A_eqfull(A_eq);
MatrixXd M_Solve;
double timer0_start = get_seconds();
bool use_lu = data.effective_dim != 2;
//use_lu = false;
//printf("use_lu: %s\n",(use_lu?"TRUE":"FALSE"));
kkt_inverse(Qfull, A_eqfull, use_lu,M_Solve);
double timer0_end = get_seconds();
verbose("Bob timing: %.20f\n", (timer0_end - timer0_start)*1000.0);
// Precompute full solve matrix:
const int fsRows = data.m * data.dim * (data.dim + 1); // 12 * number_of_bones
const int fsCols1 = data.M_KG.cols(); // 9 * number_of_posConstraints
const int fsCols2 = A_eq.rows(); // number_of_posConstraints
data.M_FullSolve.resize(fsRows, fsCols1 + fsCols2);
// note the magical multiplicative constant "-0.5", I've no idea why it has
// to be there :)
data.M_FullSolve <<
(-0.5 * M_Solve.block(0, 0, fsRows, fsRows) * data.M_KG).template cast<SSCALAR>(),
M_Solve.block(0, fsRows, fsRows, fsCols2).template cast<SSCALAR>();
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<SSCALAR>();
}
// Precompute condensed matrices,
// first CSM:
std::vector<MatrixXS> CSM_M_SSCALAR;
CSM_M_SSCALAR.resize(data.dim);
for (int i=0; i<data.dim; i++) CSM_M_SSCALAR[i] = data.CSM_M[i].template cast<SSCALAR>();
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 <typename LbsMatrixType, typename SSCALAR>
IGL_INLINE bool igl::arap_dof_update(
const ArapDOFData<LbsMatrixType, SSCALAR> & data,
const Eigen::Matrix<double,Eigen::Dynamic,1> & 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<SSCALAR, Dynamic, Dynamic> MatrixXS;
#ifdef ARAP_GLOBAL_TIMING
double timer_start = get_seconds();
#endif
// number of dimensions
IGL_ASSERT((int)data.CSM_M.size() == data.dim);
IGL_ASSERT((int)L0.size() == (data.m)*data.dim*(data.dim+1));
IGL_ASSERT(max_iters >= 0);
IGL_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="<<data.dim<<";"<<endl;
cout<<"m="<<data.m<<";"<<endl;
#endif
// number of groups
const int k = data.CSM_M[0].rows()/data.dim;
for(int i = 0;i<data.dim;i++)
{
assert(data.CSM_M[i].rows()/data.dim == k);
}
#ifdef EXTREME_VERBOSE
cout<<"k="<<k<<";"<<endl;
#endif
// resize output and initialize with initial guess
L = L0;
#ifndef IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT
// Keep track of last solution
MatrixXS L_prev;
#endif
// We will be iterating on L_SSCALAR, only at the end we convert back to double
MatrixXS L_SSCALAR = L.cast<SSCALAR>();
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<SSCALAR,Eigen::Dynamic,1> Rcol(data.dim * data.dim * k);
Matrix<SSCALAR,Dynamic,1> B_eq_SSCALAR = B_eq.cast<SSCALAR>();
Matrix<SSCALAR,Dynamic,1> L0SSCALAR = L0.cast<SSCALAR>();
Matrix<SSCALAR,Dynamic,1> B_eq_fix_SSCALAR = L0SSCALAR(data.fixed_dim);
//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();
#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();
}
#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();
}
#ifdef EXTREME_VERBOSE
cout<<"S=["<<endl<<S<<endl<<"];"<<endl;
#endif
// Fit rotations to covariance matrices
if(data.effective_dim == 2)
{
fit_rotations_planar(S,R);
}else
{
#ifdef __SSE__ // fit_rotations_SSE will convert to float if necessary
fit_rotations_SSE(S,R);
#else
fit_rotations(S,false,R);
#endif
}
#ifdef EXTREME_VERBOSE
cout<<"R=["<<endl<<R<<endl<<"];"<<endl;
#endif
if(data.print_timings)
{
sec_fitRotations = get_seconds();
}
///////////////////////////////////////////////////////////////////////////
// "Global" step: fix rotations per mesh vertex, solve for
// linear transformations at handles
///////////////////////////////////////////////////////////////////////////
// all this shuffling is retarded and not completely negligible time-wise;
// TODO: change fit_rotations_XXX so it returns R in the format ready for
// CSolveBlock1 multiplication
columnize(R, k, 2, Rcol);
#ifdef EXTREME_VERBOSE
cout<<"Rcol=["<<endl<<Rcol<<endl<<"];"<<endl;
#endif
splitColumns(Rcol, k, data.dim, data.dim, Rxyz);
if(data.print_timings)
{
sec_prepMult = get_seconds();
}
L_part1xyz = data.CSolveBlock1 * Rxyz;
//#ifdef IGL_ARAP_DOF_DOUBLE_PRECISION_SOLVE
// MKL_matMatMult_double(L_part1xyz, data.CSolveBlock1, Rxyz);
//#else
// MKL_matMatMult_single(L_part1xyz, data.CSolveBlock1, Rxyz);
//#endif
mergeColumns(L_part1xyz, data.m, data.dim, data.dim + 1, L_part1);
if(data.with_dynamics)
{
// Consider reordering or precomputing matrix multiplications
MatrixXS L_part1_dyn(data.dim * (data.dim + 1) * data.m, 1);
// Eigen can't parse this:
//L_part1_dyn =
// -(2.0/(data.h*data.h)) * data.Pi_1 * data.Mass_tilde * data.L0 +
// (1.0/(data.h*data.h)) * data.Pi_1 * data.Mass_tilde * data.Lm1;
// -1.0 because we've moved these linear terms to the right hand side
//MatrixXS temp = -1.0 *
// ((-2.0/(data.h*data.h)) * data.L0.array() +
// (1.0/(data.h*data.h)) * data.Lm1.array()).matrix();
//MatrixXS temp = -1.0 *
// ( (-1.0/(data.h*data.h)) * data.L0.array() +
// (1.0/(data.h*data.h)) * data.Lm1.array()
// (-1.0/(data.h*data.h)) * data.L0.array() +
// ).matrix();
//Lvel0 = (1.0/(data.h)) * data.Lm1.array() - data.L0.array();
MatrixXS temp = -1.0 *
( (-1.0/(data.h*data.h)) * data.L0.array() +
(1.0/(data.h)) * data.Lvel0.array()
).matrix();
MatrixXd temp_d = temp.template cast<double>();
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<double>();
MatrixXS temp2_f = temp2.template cast<SSCALAR>();
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<L_SSCALAR.rows(); i++)
{
L_SSCALAR(i, 0) = L_part1(i, 0) + L_part2and3(i, 0);
}
#ifdef EXTREME_VERBOSE
cout<<"L=["<<endl<<L<<endl<<"];"<<endl;
#endif
if(data.print_timings)
{
sec_solve = get_seconds();
}
#ifndef IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT
// Compute maximum absolute difference with last iteration's solution
max_diff = (L_SSCALAR-L_prev).eval().array().abs().matrix().maxCoeff();
#endif
iters++;
if(data.print_timings)
{
sec_end = get_seconds();
#ifndef WIN32
// trick to get sec_* variables to compile without warning on mac
if(false)
#endif
printf(
"\ntotal iteration time = %f "
"[local: covGather = %f, "
"fitRotations = %f, "
"global: prep = %f, "
"solve = %f, "
"error = %f [ms]]\n",
(sec_end - sec_start)*1000.0,
(sec_covGather - sec_start)*1000.0,
(sec_fitRotations - sec_covGather)*1000.0,
(sec_prepMult - sec_fitRotations)*1000.0,
(sec_solve - sec_prepMult)*1000.0,
(sec_end - sec_solve)*1000.0 );
}
}
L = L_SSCALAR.template cast<double>();
assert(L.cols() == 1);
#ifdef ARAP_GLOBAL_TIMING
double timer_finito = get_seconds();
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<Eigen::Matrix<double, -1, -1, 0, -1, -1>, double>(ArapDOFData<Eigen::Matrix<double, -1, -1, 0, -1, -1>, double> const&, Eigen::Matrix<double, -1, 1, 0, -1, 1> const&, Eigen::Matrix<double, -1, -1, 0, -1, -1> const&, int, double, Eigen::Matrix<double, -1, -1, 0, -1, -1>&);
template bool igl::arap_dof_recomputation<Eigen::Matrix<double, -1, -1, 0, -1, -1>, double>(Eigen::Matrix<int, -1, 1, 0, -1, 1> const&, Eigen::SparseMatrix<double, 0, int> const&, ArapDOFData<Eigen::Matrix<double, -1, -1, 0, -1, -1>, double>&);
template bool igl::arap_dof_precomputation<Eigen::Matrix<double, -1, -1, 0, -1, -1>, double>(Eigen::Matrix<double, -1, -1, 0, -1, -1> const&, Eigen::Matrix<int, -1, -1, 0, -1, -1> const&, Eigen::Matrix<double, -1, -1, 0, -1, -1> const&, Eigen::Matrix<int, -1, 1, 0, -1, 1> const&, ArapDOFData<Eigen::Matrix<double, -1, -1, 0, -1, -1>, double>&);
template bool igl::arap_dof_update<Eigen::Matrix<double, -1, -1, 0, -1, -1>, float>(igl::ArapDOFData<Eigen::Matrix<double, -1, -1, 0, -1, -1>, float> const&, Eigen::Matrix<double, -1, 1, 0, -1, 1> const&, Eigen::Matrix<double, -1, -1, 0, -1, -1> const&, int, double, Eigen::Matrix<double, -1, -1, 0, -1, -1>&);
template bool igl::arap_dof_recomputation<Eigen::Matrix<double, -1, -1, 0, -1, -1>, float>(Eigen::Matrix<int, -1, 1, 0, -1, 1> const&, Eigen::SparseMatrix<double, 0, int> const&, igl::ArapDOFData<Eigen::Matrix<double, -1, -1, 0, -1, -1>, float>&);
template bool igl::arap_dof_precomputation<Eigen::Matrix<double, -1, -1, 0, -1, -1>, float>(Eigen::Matrix<double, -1, -1, 0, -1, -1> const&, Eigen::Matrix<int, -1, -1, 0, -1, -1> const&, Eigen::Matrix<double, -1, -1, 0, -1, -1> const&, Eigen::Matrix<int, -1, 1, 0, -1, 1> const&, igl::ArapDOFData<Eigen::Matrix<double, -1, -1, 0, -1, -1>, float>&);
#endif