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.exe is located (normally GraphiteTwo\bin)
  • Configure Graphite
    • run Graphite
    • Menu File->Preferences
  • Modules tab
    • enter manifold_harmonics (in the zone right to the Add... button (1) )
    • press the Add... button (now manifold-harmonics should appear in the list)
    • press the Save configuration file button (2)

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 Textured shader
  • Atlas->Parameterize->Parameterize
  • Try parameterizer = SpectralConformal (and compare with LSCM and ABF++)

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)) ;
        }