Mesh pose interpolation in C++11 with Eigen.
This is an implementation of as-rigid-as-possible surface morphing for 3D meshes. It is designed as a header-only library and it only depends on Eigen. Some bits are parallelized with OpenMP, although that can be disabled at compile time.
It allows you to create an animation for a 3D mesh based on a sequence of two or more key poses. Intermediate poses can be interpolated with arbitrary precision to generate animation frames, and the result shows an animation that is "as rigid as possible" in the sense that in minimizes the deformation of the triangles along the way.
In order to keep dependencies at minimum, the library uses exclusively Eigen matrices for its operation, and it does not implement or uses any mesh structure as such. However, integrating it with any mesh processing library should be rather straightforward. Check the app directory to see an example that integrates OpenMesh.
The library is quite simple and should be fairly easy to use. To install it,
just copy the content of the include directory to your project or
your system include path. Then, include the file surfmorph/surfmorph.h
in your
source.
The library contains just one template class, surfmorph::SurfaceMorph
, which
receives the preferred scalar type as template parameter (generally float
or
double
). The source code is quite commented and
describes the available operations in the class. The code below is a simple
example program demonstrating its usage.
//! Generate some triangles data and interpolate it along time.
#include <Eigen/Core>
#include <surfmorph/surfmorph.h>
#include <iostream>
#include <iomanip>
#include <vector>
int main()
{
// Define mesh matrices types
typedef Eigen::Matrix<unsigned int, 3, Eigen::Dynamic> TrianglesMat;
typedef Eigen::Matrix<double, 3, Eigen::Dynamic> CoordsMat;
// Define interpolator type
typedef surfmorph::SurfaceMorph<double> SurfaceMorph;
// Generate some data
TrianglesMat triangles(3, 2);
CoordsMat pose0(3, 4);
CoordsMat pose1(3, 4);
CoordsMat pose2(3, 4);
// Each triangle is a vector of vertex indices
triangles << 0, 1,
1, 2,
2, 3;
// Each pose has the coordinates of every vertex as column vectors
pose0 << 0.0, 1.0, 1.0, 0.0,
0.0, 1.0, 0.0, 1.0,
0.0, 1.0, 1.0, 1.0;
pose1 << 0.3, 1.5, 1.7, 0.2,
0.1, 1.2, 0.2, 1.8,
0.2, 1.3, 1.4, 1.5;
pose2 << 0.1, 1.2, 1.4, 0.5,
0.8, 1.3, 0.4, 1.8,
0.5, 1.9, 1.6, 1.3;
// Create the interpolator
SurfaceMorph sm(triangles, pose0);
sm.addPose(pose1);
sm.addPose(pose2);
// Print the triangles matrix
std::cout << "Triangles:" << std::endl;
for (int i = 0; i < triangles.rows(); i++) {
for (int j = 0; j < triangles.cols(); j++) {
std::cout << triangles(i, j) << " ";
}
std::cout << std::endl;
}
std::cout << std::endl;
// Each pose is assigned a sequential integer time point; time point 0
// is the first pose, time point 1 the second one, and time point 1.5
// is an interpolation midway between the first and the second pose.
std::cout << std::setprecision(2);
for (double t = 0.0; t <= double(sm.numPoses() - 1); t += .1) {
// Compute interpolated pose at time point
CoordsMat interp = sm.interpolatePoseAt(t);
// Print the pose coordinates
std::cout << "Interpolated pose at " << t << ":" << std::endl;
for (int i = 0; i < interp.rows(); i++) {
for (int j = 0; j < interp.cols(); j++) {
std::cout << interp(i, j) << " ";
}
std::cout << std::endl;
}
std::cout << std::endl;
}
return 0;
}
To compile this program with GCC on Unix, save it to a
file, say surfmorph_test.cpp
, and run:
$ gcc -std=gnu++11 -I<path to Eigen library> -I<path to this library> -fopenmp -o surfmorph_test surfmorph_test.cpp
Or, if you prefer not to use OpenMP (more on this later), then run:
$ gcc -std=gnu++11 -I<path to Eigen library> -I<path to this library> -DSURFMORPH_DONT_PARALLELIZE -o surfmorph_test surfmorph_test.cpp
And then test it with:
$ ./surfmorph_test.cpp
The app directory contains an application that displays an interactive animation interpolated along the given poses. Besides this library and Eigen, it requires OpenMesh, Qt 5 and libQGLViewer. Also, the compilation system has only been configure for Linux, although it should be straightforward to port to other platforms.
By default, the library uses OpenMP to parallelize some of
its operations. However, you can disable this by defining the macro
SURFMORPH_DONT_PARALLELIZE
at compile time.
Pose interpolation, which is the most expensive operation, is not parallelized
by default; the reason is that, in general, interpolations are computed for a
sequence of time points, and it is more efficient to parallelize at time-point
level (that is, compute multiple interpolations in parallel). Nonetheless, it is
possible to parallelize the interpolation operation by defining the macro
SURFMORPH_PARALLELIZE_INTERPOLATION
at compile time. Note that this is
generally not recommended unless only a single interpolation is computed.
What are Shape interpolation.ipynb and surfmorph_codegen.h?
The implementation of the algorithm requires some complex math derivation that is not practical to solve by hand. Shape interpolation.ipynb is a Python notebook that uses SymPy to compute this derivation and generate the code that implements it in surfmorph_codegen.h. Shape interpolation.ipynb is not necessary to use the library, but surfmorph_codegen.h is, although you should not include it directly in your source code.