Libigl is used and developed by many people. This document highlights some style guidelines for developers of the library, but also acts as best-practices for users.
The structure of libigl is very flat and function-based. For every
function/sub-routine, create a single .h and .cpp file. For example, if you have
a function that determines connected components from a face list F
you would
create the header connected_components.h
and connected_components.cpp
and the only
function defined should be void connected_components(const ... F, ... C)
. If the
implementation of connected_components
requires a subroutine to compute an
adjacency matrix then create another pair adjacency_matrix.h
and
adjacency_matrix.cpp
with a single function void adjacency_matrix(const ... F, ... A)
.
Here is an example function that would be defined in
include/igl/example_fun.h
and implemented in include/igl/example_fun.cpp
.
example_fun.h
// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2015 [Your Name] [your email address]
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/
#ifndef IGL_EXAMPLE_FUN_H
#define IGL_EXAMPLE_FUN_H
#include "igl_inline.h"
namespace igl
{
// This is an example of a function, it takes a templated parameter and
// shovels it into cout
//
// Templates:
// T type that supports
// Input:
// input some input of a Printable type
// Returns true for the sake of returning something
template <typename Printable>
IGL_INLINE bool example_fun(const Printable & input);
}
#ifndef IGL_STATIC_LIBRARY
# include "example_fun.cpp"
#endif
#endif
example_fun.cpp
// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2015 [Your Name] [your email address]
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/
#include "igl/example_fun.h"
#include <iostream>
template <typename Printable>
IGL_INLINE bool igl::example_fun(const Printable & input)
{
using namespace std;
cout<<"example_fun: "<<input<<endl;
return true;
}
#ifdef IGL_STATIC_LIBRARY
template bool igl::example_fun<double>(const double& input);
template bool igl::example_fun<int>(const int& input);
#endif
Strive to encapsulate sub-functions that could possibly be useful outside of the implementation of your current function. This might mean abstracting the interface a bit. If it doesn't dramatically effect performance then create a new pair of .h/.cpp files with this sub-function.
If encapsulation in a separate file is not possible or does not make sense, then avoid crowding the namespace by creating lambda functions within the function implmentation.
Libigl is built around the high-performance paradigm of "struct of arrays" rather than "array of structs". The way we achieve this is to avoid classes and pass "basic types" directly. The price we pay is long function interfaces, but this increases code reuse dramatically. A "basic type" in our context is a Eigen type, stl type, or basic C type.
Each function prototype should be well documented in its corresponding .h header file. A typical documentation consists of four parts:
// [A human readable description of what the function does.]
//
// Inputs:
// [variable name of first (const) input] [dimensions and description of
// this input variable]
// [variable name of second (const) input] [dimensions and description of
// this input variable]
// ...
// Outputs:
// [variable name of first output ] [dimensions and description of this
// output variable]
// [variable name of second output ] [dimensions and description of this
// output variable]
// ...
// Returns [description of return value]
For example the header barycenter.h
// Computes the barycenter of every simplex
//
// Inputs:
// V #V by dim matrix of vertex coordinates
// F #F by simplex_size matrix of indices of simplex corners into V
// Output:
// BC #F by dim matrix of 3d vertices
//
All input parameters should be demarcated const
. If an input is also an
output than consider exposing two parameters (one const
) or be sure to list
the variable under both // Inputs:
and // Outputs:
in the header comments.
All but simple types should be passed by reference (e.g. Matrix & mat
) rather
than pointers (e.g. Matrix * mat
) or value (e.g. Matrix mat
).
All functions should be implemented with at least one overload that has a
void
or simple return type (e.g. bool
on success/failure). With this
implementation its then possible to write an overload that returns a single
output.
For example:
template <typename Atype>
void adjacency_matrix(const ... & F, Eigen::SparseMatrix<AType> & A);
template <typename Atype>
Eigen::SparseMatrix<Atype> adjacency_matrix(const ... & F);
Functions (and thus also files) should have simple,
descriptive names using lowercase letters and underscores between words. Avoid
unnecessary prefaces. For example, instead of compute_adjacency_matrix
,
construct_adjacency_matrix
, extract_adjacency_matrix
,
get_adjacency_matrix
, or set_adjacency_matrix
just call the function
adjacency_matrix
.
Libigl prefers short (even single character) variable names with heavy
documentation in the comments in the header file or above the declaration of
the function. When possible use V
to mean a list of vertex positions and F
to mean a list of faces/triangles.
Classes should be avoided. When naming a class use CamelCase (e.g. SortableRow.h).
Enums types should be placed in the appropriate igl::
namespace and should be
named in CamelCase (e.g. igl::SolverStatus
) and instances should be named in
ALL_CAPS with underscores between words and prefaced with the name of the enum.
For example:
namespace igl
{
enum SolverStatus
{
// Good
SOLVER_STATUS_CONVERGED = 0,
// OK
SOLVER_STATUS_MAX_ITER = 1,
// Bad
SOLVER_STATUS_ERROR = 2,
NUM_SOLVER_STATUSES = 3,
};
};
For legacy reasons, file reading and writing functions use a different naming
convention. A functions reading a .xyz
file should be named readXYZ
and a
function writing .xyz
files should be names writeXYZ
.
using namespace ...
in global scopeWriting using namespace std;
, using namespace Eigen;
etc. outside of a
global scope is strictly forbidden. Place these lines at the top of each
function instead.
Functions in the main library (directly in include/igl
) should only depend on
Eigen and stl. These functions should have the igl::
namespace.
Functions with other dependencies should be placed into
appropriate sub-directories (e.g. if myfunction
depends on tetgen then create
igl/copyleft/tetgen/myfunction.h
and igl/copyleft/tetgen/myfunction.cpp
and give the function
the namespace igl::copyleft::tetgen::myfunction
.
Dependencies that require users of libigl to release their projects open source
(e.g. GPL) are considered aggressively "copyleft" and should be placed in the
include/igl/copyleft/
sub-directory and igl::copyleft::
namespace.
Be generous with assertions and always identify the assertion with strings:
assert(m < n && "m must be less than n");
Every header file should be wrapped in an #ifndef
compiler directive. The
name of the guard should be in direct correspondence with the path of the .h
file. For example, include/igl/copyleft/tetgen/tetrahedralize.h
should be
#ifndef IGL_COPYLEFT_TETGEN_TETRAHEDRALIZE_H
#define IGL_COPYLEFT_TETGEN_TETRAHEDRALIZE_H
...
#endif
Do not use tabs. Use 2 spaces for each indentation level.
Limit lines to 80 characters. Break up long lines into many operations (this also helps performance).
#include
directives at the top of a .h or .cpp file should be sorted
according to a simple principle: place headers of files most likely to be
edited by you first. This means for
include/igl/copyleft/tetgen/tetrahedralize.cpp
you might see
// [Includes of headers in this directory]
#include "tetrahedralize.h"
#include "mesh_to_tetgenio.h"
#include "tetgenio_to_tetmesh.h"
// [Includes of headers in this project]
#include "../../matrix_to_list.h"
#include "../../list_to_matrix.h"
#include "../../boundary_facets.h"
// [Includes of headers of related projects]
#include <Eigen/Core>
// [Includes of headers of standard libraries]
#include <cassert>
#include <iostream>
Whenever possible #include
directives should be placed in the .cpp
implementation file rather than the .h
header file.
Code should compile without firing any warnings.
The only exception is for the use of the deprecated
Eigen::DynamicSparseMatrix
in core sub-routines (e.g. igl::cat
). This class
is still supported and faster than the standard, non-deprecated Eigen
implementation so we're keeping it as long as possible and profitable.