421 lines
16 KiB
Markdown
421 lines
16 KiB
Markdown
<h1>libigl tutorial notes</h1>
|
|
|
|
<h4>originally presented by Daniele Panozzo and Alec Jacobson at SGP Graduate School 2014</h4>
|
|
|
|

|
|
|
|
Libigl is an open source C++ library for geometry processing research and development. Dropping the heavy data structures of tradition geometry libraries, libigl is a simple header-only library of encapsulated functions. This combines the rapid prototyping familiar to Matlab or Python programmers with the performance and versatility of C++. The tutorial is a self-contained, hands-on introduction to libigl. Via interactive, step-by-step examples, we demonstrate how to accomplish common geometry processing tasks such as computation of differential quantities and operators, real-time deformation, parametrization, numerical optimization and remeshing. Each section of the lecture notes links to a cross-platform example application.
|
|
|
|
|
|
# Chapter 1
|
|
|
|
We introduce libigl with a series of self-contained examples. The purpose of
|
|
each example is to showcase a feature of libigl while applying to a practical
|
|
problem in geometry processing. In this chapter, we will present the basic
|
|
concepts of libigl and introduce a simple mesh viewer that allows to
|
|
visualize a surface mesh and its attributes. All the tutorial examples are
|
|
cross-platform and can be compiled on MacOSX, Linux and Windows.
|
|
|
|
## libigl design principles
|
|
|
|
Before getting into the examples, we summarize the main design principles in
|
|
libigl:
|
|
|
|
1. **No complex data types.** We mostly use matrices and vectors. This greatly
|
|
favors code reusability and forces the function authors to expose all the
|
|
parameters used by the algorithm.
|
|
|
|
2. **Minimal dependencies.** We use external libraries only when necessary and
|
|
we wrap them in a small set of functions.
|
|
|
|
3. **Header-only.** It is straight forward to use our library since it is only
|
|
one additional include directory in your project. (if you are worried about
|
|
compilation speed, it is also possible to build the library as a [static
|
|
library](../static-library/))
|
|
|
|
4. **Function encapsulation.** Every function (including its full
|
|
implementation) is contained in a pair of .h/.cpp files with the same name of
|
|
the function.
|
|
|
|
|
|
### Downloading libigl
|
|
libigl can be downloaded from our [github
|
|
repository](https://github.com/libigl/libigl) or cloned with git:
|
|
|
|
```bash
|
|
git clone --recursive https://github.com/libigl/libigl.git
|
|
```
|
|
|
|
The core libigl functionality only depends on the C++ Standard Library and
|
|
Eigen.
|
|
|
|
To build all the examples in the tutorial, you can use the CMakeLists.txt in
|
|
the tutorial folder:
|
|
|
|
```bash
|
|
cd tutorial
|
|
mkdir build
|
|
cd build
|
|
cmake -DCMAKE_BUILD_TYPE=Release ../
|
|
make
|
|
```
|
|
|
|
The examples can also be built independently using the CMakeLists.txt
|
|
inside each example folder.
|
|
|
|
*Note for linux users*: Many linux distributions do not include gcc and the basic development tools
|
|
in their default installation. On Ubuntu, you need to install the following packages:
|
|
|
|
```bash
|
|
sudo apt-get install git
|
|
sudo apt-get install build-essential
|
|
sudo apt-get install cmake
|
|
sudo apt-get install libx11-dev
|
|
sudo apt-get install mesa-common-dev libgl1-mesa-dev libglu1-mesa-dev
|
|
sudo apt-get install libxrandr-dev
|
|
sudo apt-get install libxi-dev
|
|
sudo apt-get install libxmu-dev
|
|
sudo apt-get install libblas-dev
|
|
sudo apt-get install libxinerama-dev
|
|
sudo apt-get install libxcursor-dev
|
|
```
|
|
*Note for windows users*: libigl only supports the Microsoft Visual Studio 2015 compiler in 64bit mode. It will not work with a 32bit build and it will not work
|
|
with older versions of visual studio.
|
|
|
|
A few examples in Chapter 5 requires the [CoMiSo
|
|
solver](http://www.graphics.rwth-aachen.de/software/comiso). We provide a
|
|
mirror of CoMISo that works out of the box with libigl. To install it:
|
|
|
|
```bash
|
|
cd libigl/external
|
|
git clone --recursive https://github.com/libigl/CoMISo.git
|
|
```
|
|
|
|
You can then build the tutorials again and it libigl will automatically find and
|
|
compile CoMISo.
|
|
|
|
*Note 1*: CoMISo is distributed under the GPL3 license, it does impose restrictions on commercial usage.
|
|
|
|
*Note 2*: CoMISo requires a blas implementation. We use the built-in blas in macosx and linux, and we bundle a precompiled binary for VS2015 64 bit. Do NOT compile the tutorials
|
|
in 32 bit on windows.
|
|
|
|
### libigl example project
|
|
|
|
We provide a [blank project example](https://github.com/libigl/libigl-example-project) showing how to use libigl and cmake. Feel free and encouraged to copy or fork this project as a way of starting a new personal project using libigl.
|
|
|
|
## Mesh representation
|
|
|
|
libigl uses the [Eigen](http://eigen.tuxfamily.org/) library to encode vector
|
|
and matrices. We suggest that you keep the
|
|
[dense](http://eigen.tuxfamily.org/dox/group__QuickRefPage.html) and
|
|
[sparse](http://eigen.tuxfamily.org/dox/group__SparseQuickRefPage.html) quick
|
|
reference guides at hand while you read the examples in this tutorial.
|
|
|
|
A triangular mesh is encoded as a pair of matrices:
|
|
|
|
```cpp
|
|
Eigen::MatrixXd V;
|
|
Eigen::MatrixXi F;
|
|
```
|
|
|
|
`V` is a #N by 3 matrix which stores the coordinates of the vertices. Each
|
|
row stores the coordinate of a vertex, with its x,y and z coordinates in the first,
|
|
second and third column, respectively. The matrix `F` stores the triangle
|
|
connectivity: each line of `F` denotes a triangle whose 3 vertices are
|
|
represented as indices pointing to rows of `V`.
|
|
|
|

|
|
|
|
Note that the order of the vertex indices in `F` determines the orientation of
|
|
the triangles and it should thus be consistent for the entire surface.
|
|
This simple representation has many advantages:
|
|
|
|
1. it is memory efficient and cache friendly
|
|
2. the use of indices instead of pointers greatly simplifies debugging
|
|
3. the data can be trivially copied and serialized
|
|
|
|
libigl provides input [output] functions to read [write] many common mesh formats.
|
|
The IO functions are contained in the files read\*.h and write\*.h. As a general
|
|
rule each libigl function is contained in a pair of .h/.cpp files with the same name.
|
|
By default, the .h files include the corresponding cpp files, making the library header-only.
|
|
|
|
Reading a mesh from a file requires a single libigl function call:
|
|
|
|
```cpp
|
|
igl::readOFF(TUTORIAL_SHARED_PATH "/cube.off", V, F);
|
|
```
|
|
|
|
The function reads the mesh cube.off and it fills the provided `V` and `F` matrices.
|
|
Similarly, a mesh can be written in an OBJ file using:
|
|
|
|
```cpp
|
|
igl::writeOBJ("cube.obj",V,F);
|
|
```
|
|
|
|
[Example 101]({{ repo_url }}/tutorial/101_FileIO/main.cpp) contains a simple mesh
|
|
converter from OFF to OBJ format.
|
|
|
|
## Visualizing surfaces
|
|
|
|
Libigl provides an glfw-based OpenGL 3.2 viewer to visualize surfaces, their
|
|
properties and additional debugging information.
|
|
|
|
The following code ([Example 102]({{ repo_url }}/tutorial/102_DrawMesh/main.cpp)) is a basic skeleton
|
|
for all the examples that will be used in the tutorial.
|
|
It is a standalone application that loads a mesh and uses the viewer to
|
|
render it.
|
|
|
|
```cpp
|
|
#include <igl/readOFF.h>
|
|
#include <igl/opengl/glfw/Viewer.h>
|
|
|
|
Eigen::MatrixXd V;
|
|
Eigen::MatrixXi F;
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
// Load a mesh in OFF format
|
|
igl::readOFF(TUTORIAL_SHARED_PATH "/bunny.off", V, F);
|
|
|
|
// Plot the mesh
|
|
igl::opengl::glfw::Viewer viewer;
|
|
viewer.data().set_mesh(V, F);
|
|
viewer.launch();
|
|
}
|
|
```
|
|
|
|
The function `set_mesh` copies the mesh into the viewer.
|
|
`Viewer.launch()` creates a window, an OpenGL context and it starts the draw loop.
|
|
Additional properties can be plotted on the mesh (as we will see later),
|
|
and it is possible to extend the viewer with standard OpenGL code.
|
|
Please see the documentation in
|
|
[Viewer.h]({{ repo_url }}/include/igl/opengl/glfw/Viewer.h) for more details.
|
|
|
|
) loads and draws a mesh.](images/102_DrawMesh.png)
|
|
|
|
## Interaction with keyboard and mouse
|
|
|
|
Keyboard and mouse events triggers callbacks that can be registered in the
|
|
viewer. The viewer supports the following callbacks:
|
|
|
|
```cpp
|
|
bool (*callback_pre_draw)(Viewer& viewer);
|
|
bool (*callback_post_draw)(Viewer& viewer);
|
|
bool (*callback_mouse_down)(Viewer& viewer, int button, int modifier);
|
|
bool (*callback_mouse_up)(Viewer& viewer, int button, int modifier);
|
|
bool (*callback_mouse_move)(Viewer& viewer, int mouse_x, int mouse_y);
|
|
bool (*callback_mouse_scroll)(Viewer& viewer, float delta_y);
|
|
bool (*callback_key_down)(Viewer& viewer, unsigned char key, int modifiers);
|
|
bool (*callback_key_up)(Viewer& viewer, unsigned char key, int modifiers);
|
|
```
|
|
|
|
A keyboard callback can be used to visualize multiple meshes or different
|
|
stages of an algorithm, as demonstrated in [Example 103]({{ repo_url }}/tutorial/103_Events/main.cpp), where
|
|
the keyboard callback changes the visualized mesh depending on the key pressed:
|
|
|
|
```cpp
|
|
bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier)
|
|
{
|
|
if (key == '1')
|
|
{
|
|
viewer.data().clear();
|
|
viewer.data().set_mesh(V1, F1);
|
|
viewer.core.align_camera_center(V1,F1);
|
|
}
|
|
else if (key == '2')
|
|
{
|
|
viewer.data().clear();
|
|
viewer.data().set_mesh(V2, F2);
|
|
viewer.core.align_camera_center(V2,F2);
|
|
}
|
|
return false;
|
|
}
|
|
```
|
|
|
|
The callback is registered in the viewer as follows:
|
|
|
|
```cpp
|
|
viewer.callback_key_down = &key_down;
|
|
```
|
|
|
|
Note that the mesh is cleared before using set_mesh. This has to be called
|
|
every time the number of vertices or faces of the plotted mesh changes. Every
|
|
callback returns a boolean value that tells the viewer if the event has been
|
|
handled by the plugin, or if the viewer should process it normally. This is
|
|
useful, for example, to disable the default mouse event handling if you want to
|
|
control the camera directly in your code.
|
|
|
|
The viewer can be extended using plugins, which are classes that implements all
|
|
the viewer's callbacks. See the
|
|
[Viewer_plugin]({{ repo_url }}/include/igl/opengl/glfw/ViewerPlugin.h) for more details.
|
|
|
|
## Scalar field visualization
|
|
|
|
Colors and normals can be associated to faces or vertices using the
|
|
set_colors function:
|
|
|
|
```cpp
|
|
viewer.data().set_colors(C);
|
|
```
|
|
|
|
`C` is a #C by 3 matrix with one RGB color per row. `C` must have as many
|
|
rows as the number of faces **or** the number of vertices of the mesh.
|
|
Depending on the size of `C`, the viewer applies the color to the faces or to
|
|
the vertices.
|
|
|
|
Colors can be used to visualize a scalar function defined on a surface. The
|
|
scalar function is converted to colors using a color transfer function, which
|
|
maps a scalar value between 0 and 1 to a color. A simple example of a scalar
|
|
field defined on a surface is the z coordinate of each point, which can be
|
|
extract from our mesh representation by taking the last column of `V`
|
|
([Example 104]({{ repo_url }}/tutorial/104_Colors/main.cpp)). The function `igl::jet` can be used to
|
|
convert it to colors:
|
|
|
|
```cpp
|
|
Eigen::VectorXd Z = V.col(2);
|
|
igl::jet(Z,true,C);
|
|
```
|
|
|
|
The first row extracts the third column from `V` (the z coordinate of each
|
|
vertex) and the second calls a libigl functions that converts a scalar field to colors. The second parameter of jet normalizes the scalar field to lie between 0 and 1 before applying the transfer function.
|
|
|
|
) igl::jet converts a scalar field to a color field.](images/104_Colors.png)
|
|
|
|
`igl::jet` is an example of a standard function in libigl: it takes simple
|
|
types and can be easily reused for many different tasks. Not committing to
|
|
heavy data structures types favors simplicity, ease of use and reusability.
|
|
|
|
## Overlays
|
|
|
|
In addition to plotting the surface, the viewer supports the visualization of points, lines and text labels: these overlays can be very helpful while developing geometric processing algorithms to plot debug information.
|
|
|
|
```cpp
|
|
viewer.data().add_points(P,Eigen::RowVector3d(r,g,b));
|
|
```
|
|
|
|
Draws a point of color r,g,b for each row of P. The point is placed at the coordinates specified in each row of P, which is a #P by 3 matrix.
|
|
|
|
```cpp
|
|
viewer.data().add_edges(P1,P2,Eigen::RowVector3d(r,g,b));
|
|
```
|
|
|
|
Draws a line of color r,g,b for each row of P1 and P2, which connects the 3D point in to the point in P2. Both P1 and P2 are of size #P by 3.
|
|
|
|
```cpp
|
|
viewer.data().add_label(p,str);
|
|
```
|
|
|
|
Draws a label containing the string str at the position p, which is a vector of length 3.
|
|
|
|
These functions are demonstrate in [Example 105]({{ repo_url }}/tutorial/105_Overlays/main.cpp) where
|
|
the bounding box of a mesh is plotted using lines and points.
|
|
Using matrices to encode the mesh and its attributes allows to write short and
|
|
efficient code for many operations, avoiding to write for loops. For example,
|
|
the bounding box of a mesh can be found by taking the colwise maximum and minimum of `V`:
|
|
|
|
```cpp
|
|
Eigen::Vector3d m = V.colwise().minCoeff();
|
|
Eigen::Vector3d M = V.colwise().maxCoeff();
|
|
```
|
|
|
|
) The bounding box of a mesh is shown using overlays.](images/105_Overlays.png)
|
|
|
|
## Viewer Menu
|
|
|
|
As of latest version, the viewer uses a new menu and completely replaces
|
|
[AntTweakBar](http://anttweakbar.sourceforge.net/doc/) and
|
|
[nanogui](https://github.com/wjakob/nanogui) with [Dear ImGui](https://github.com/ocornut/imgui). To extend the default menu of the
|
|
viewer and to expose more user defined variables you have to implement a custom interface, as in [Example 106]({{ repo_url }}/tutorial/106_ViewerMenu/main.cpp):
|
|
```cpp
|
|
// Add content to the default menu window
|
|
menu.callback_draw_viewer_menu = [&]()
|
|
{
|
|
// Draw parent menu content
|
|
menu.draw_viewer_menu();
|
|
|
|
// Add new group
|
|
if (ImGui::CollapsingHeader("New Group", ImGuiTreeNodeFlags_DefaultOpen))
|
|
{
|
|
// Expose variable directly ...
|
|
ImGui::InputFloat("float", &floatVariable, 0, 0, 3);
|
|
|
|
// ... or using a custom callback
|
|
static bool boolVariable = true;
|
|
if (ImGui::Checkbox("bool", &boolVariable))
|
|
{
|
|
// do something
|
|
std::cout << "boolVariable: " << std::boolalpha << boolVariable << std::endl;
|
|
}
|
|
|
|
// Expose an enumeration type
|
|
enum Orientation { Up=0, Down, Left, Right };
|
|
static Orientation dir = Up;
|
|
ImGui::Combo("Direction", (int *)(&dir), "Up\0Down\0Left\0Right\0\0");
|
|
|
|
// We can also use a std::vector<std::string> defined dynamically
|
|
static int num_choices = 3;
|
|
static std::vector<std::string> choices;
|
|
static int idx_choice = 0;
|
|
if (ImGui::InputInt("Num letters", &num_choices))
|
|
{
|
|
num_choices = std::max(1, std::min(26, num_choices));
|
|
}
|
|
if (num_choices != (int) choices.size())
|
|
{
|
|
choices.resize(num_choices);
|
|
for (int i = 0; i < num_choices; ++i)
|
|
choices[i] = std::string(1, 'A' + i);
|
|
if (idx_choice >= num_choices)
|
|
idx_choice = num_choices - 1;
|
|
}
|
|
ImGui::Combo("Letter", &idx_choice, choices);
|
|
|
|
// Add a button
|
|
if (ImGui::Button("Print Hello", ImVec2(-1,0)))
|
|
{
|
|
std::cout << "Hello\n";
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
If you need a separate new menu window implement:
|
|
|
|
```cpp
|
|
// Draw additional windows
|
|
menu.callback_draw_custom_window = [&]()
|
|
{
|
|
// Define next window position + size
|
|
ImGui::SetNextWindowPos(ImVec2(180.f * menu.menu_scaling(), 10), ImGuiSetCond_FirstUseEver);
|
|
ImGui::SetNextWindowSize(ImVec2(200, 160), ImGuiSetCond_FirstUseEver);
|
|
ImGui::Begin(
|
|
"New Window", nullptr,
|
|
ImGuiWindowFlags_NoSavedSettings
|
|
);
|
|
|
|
// Expose the same variable directly ...
|
|
ImGui::PushItemWidth(-80);
|
|
ImGui::DragFloat("float", &floatVariable, 0.0, 0.0, 3.0);
|
|
ImGui::PopItemWidth();
|
|
|
|
static std::string str = "bunny";
|
|
ImGui::InputText("Name", str);
|
|
|
|
ImGui::End();
|
|
};
|
|
```
|
|
|
|
) The UI of the viewer can be easily customized.](images/106_ViewerMenu.png)
|
|
|
|
## Multiple Meshes
|
|
|
|
Libigl's `igl::opengl::glfw::Viewer` provides basic support for rendering
|
|
multiple meshes.
|
|
|
|
Which mesh is _selected_ is controlled via the `viewer.selected_data_index`
|
|
field. By default it his is set to `0`, so in the typical case of a single mesh
|
|
`viewer.data()` returns the `igl::ViewerData` corresponding to the one
|
|
and only mesh.
|
|
|
|
) The `igl::opengl::glfw::Viewer` can render multiple meshes, each with its own attributes like colors.](images/multiple-meshes.png)
|