# 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.

• Unpack the file
• move manifold_harmonics.dll to the directory where `graphite.exe` is located (normally `GraphiteTwo\bin`)
• Configure Graphite
• run Graphite • `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

For instance, this one

### Compute manifold harmonics

```  Mesh->Spectral->compute manifold harmonics
order: 300
```

### Display manifold harmonics • 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
• 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)) ;
}
```