Manifold Harmonics
Spectral Geometry Processing with Manifold Harmonics
In this tutorial, you will learn how to compute Manifold Harmonics, presented in Eurographics in 2008. Note: this plug-in is meant to be used by researchers in geometry processing, for the moment, there is probably no practical application of this (except curiosity...).
1) Installing the plugin
This is a user's tutorial, programmers who use sources should see ProgManifoldHarmonics first.
- Download the plugin manifold-harmonics-xxx-bin-windows.zip from Inria GForge
- Unpack the file
- move manifold-harmonics.dll to the directory where
graphite.exeis located (normallyGraphiteTwo\bin)
- move manifold-harmonics.dll to the directory where
- Configure Graphite
- run Graphite
- Menu File->Preferences

Modulestab- enter
manifold-harmonics(in the zone right to theAdd...button (1) ) - press the
Add...button (nowmanifold-harmonicsshould appear in the list) - press the
Save configuration filebutton (2)
- enter
Exit and restart Graphite
2) Computing Manifold Harmonics
Load a model
For instance, this one
Compute manifold harmonics
Mesh->Spectral->compute manifold harmonics order: 300
Display manifold harmonics

- Select the ManifoldHarmonics shader
- Select the colormap
- Change the displayed attribute
I like to move it move it

We can now animate the manifold harmonics (displacement along the normal vector), to do so:
- Activate the animator in Viewer controls (top left). You first need to use the little arrows next to the tabs in order to find the right tab, then push the 'play' button.
- Set scaling = 10 (how much the model is displaced along the normal vector)
- Set speed = 3
- click here for the music
- Change attribute to see Homer's different "dancing modes"
Show reconstruction

- Stop animator (top left, push 'pause' button)
- Set speed = 0
- Select geo_map
- Play with max_B
3) Spectral Conformal Parameterization

Try this
- Load a model, for instance this one
- Select
Texturedshader - Atlas->Parameterize->Parameterize
- Try
parameterizer = SpectralConformal(and compare withLSCMandABF++)
This gives a result that is intermediary in terms of quality between LSCM and ABF++. Although not as good as ABF++, we think that the result is interesting, since the implementation is extremely simple (given that we already have eigenvector computation in ARPACK and setup for LSCM matrix). Some variants of this method may lead to efficient global parameterization algorithms.
How it is implemented
Once we got efficient code to compute eigenvectors interfaced with the rest of Graphite, it is very easy to implement spectral conformal parameterization method (30 lines of code, see below). We first heard about this idea in 2002, from David Cohen Steiner. The idea was then published by Mullen, Tong, Alliez and Desbrun at ACM SGP 08. In our setting, this simply means using the same matrix as LSCM, but instead of avoiding the trivial solution by vertex pinning, compute the first solution that is orthogonal to the trivial solution. This means computing the eigenvector associated with the first non-zero eigenvalue of the LSCM matrix. This is implememted as follows. First, to reuse matrix assembly from LSCM, we declare a MapParameterizer derived from MapParameterizerLSCM:
class SpectralConformalMapParameterizer : public MapParameterizerLSCM
We just need to overload the do_parameterize_disc(Map* map) function:
bool SpectralConformalMapParameterizer::do_parameterize_disc(Map* map)
We will reuse the setup_conformal_map_relations function from LSCM. To do so, we need to declare a LinearSolver.
LinearSolver solver(2 * nb_distinct_vertices_) ; solver.set_least_squares(true) ; // Configure LinearSolver for SUPERLU (matrix storage) SystemSolverParameters params ; params.set_arg("method", "SUPERLU") ; solver.set_system_solver(params) ;
The LinearSolver is used to assemble the matrix.
solver.begin_system() ;
setup_conformal_map_relations(solver) ;
solver.end_system() ;
Then we take the matrix and throw the LinearSolver away, we do not need it anymore, it was just used to assemble the matrix.
SparseMatrix* M = solver.get_matrix() ;
Now we need to create and configure an EigenSolver:
EigenSolver_var eigen_solver = MathLibrary::instance()->create_eigen_solver("ARPACK") ; int nb_eigen = 10 ; eigen_solver->set_threshold(0.0) ; eigen_solver->set_max_iter(3000) ; eigen_solver->set_bruno(true) ; eigen_solver->set_shift(0.0) ; eigen_solver->set_direct_solver("SuperLU") ; eigen_solver->set_nb_eigens(nb_eigen) ; eigen_solver->set_mode(EigenSolver::SMALLEST) ; eigen_solver->set_compute_eigenvectors(true) ; eigen_solver->set_matrix(M) ;
And we compute the eigenvectors:
eigen_solver->solve()
Finally, we read the (u,v) coordinates in the eigenvector and copy them to texture coordinates. Note that the eigenvector associated with the first eigenvalue (number 0) corresponds to the trivial solution (all tex vertices at the same location), therefore we use number 1, i.e. the best minimizer of the conformal energy that is orthogonal to the trivial solution:
FOR_EACH_VERTEX(Map, map, it) {
int u_index = vertex_id_[it]*2 ;
int v_index = vertex_id_[it]*2+1 ;
double u = eigen_solver->get_eigen_vector(1)[u_index] ;
double v = eigen_solver->get_eigen_vector(1)[v_index] ;
it->halfedge()->set_tex_coord(Point2d(u,v)) ;
}