16 KiB
libigl tutorial notes
originally presented by Daniele Panozzo and Alec Jacobson at SGP Graduate School 2014
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:
-
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.
-
Minimal dependencies. We use external libraries only when necessary and we wrap them in a small set of functions.
-
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)
-
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 or cloned with git:
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:
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:
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. We provide a mirror of CoMISo that works out of the box with libigl. To install it:
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 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 library to encode vector and matrices. We suggest that you keep the dense and sparse quick reference guides at hand while you read the examples in this tutorial.
A triangular mesh is encoded as a pair of matrices:
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:
- it is memory efficient and cache friendly
- the use of indices instead of pointers greatly simplifies debugging
- 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:
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:
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.
#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.
Interaction with keyboard and mouse
Keyboard and mouse events triggers callbacks that can be registered in the viewer. The viewer supports the following callbacks:
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:
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:
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:
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:
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 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.
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.
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.
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:
Eigen::Vector3d m = V.colwise().minCoeff();
Eigen::Vector3d M = V.colwise().maxCoeff();
Viewer Menu
As of latest version, the viewer uses a new menu and completely replaces AntTweakBar and nanogui with Dear 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):
// 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:
// 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();
};
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.


) loads and draws a mesh.](/phynexis-deps/igl/media/commit/2e39b6841c816fd96ebf28fdbe6ff4883aed6e80/docs/tutorial/images/102_DrawMesh.png)
) igl::jet converts a scalar field to a color field.](/phynexis-deps/igl/media/commit/2e39b6841c816fd96ebf28fdbe6ff4883aed6e80/docs/tutorial/images/104_Colors.png)
) The bounding box of a mesh is shown using overlays.](/phynexis-deps/igl/media/commit/2e39b6841c816fd96ebf28fdbe6ff4883aed6e80/docs/tutorial/images/105_Overlays.png)
) The UI of the viewer can be easily customized.](/phynexis-deps/igl/media/commit/2e39b6841c816fd96ebf28fdbe6ff4883aed6e80/docs/tutorial/images/106_ViewerMenu.png)
) The igl::opengl::glfw::Viewer can render multiple meshes, each with its own attributes like colors.](/phynexis-deps/igl/media/commit/2e39b6841c816fd96ebf28fdbe6ff4883aed6e80/docs/tutorial/images/multiple-meshes.png)