Эх сурвалжийг харах

* notes for tutorial chapter 1
* various fixes in the tutorials


Former-commit-id: f7bc8b75024bc6a9da818b189938b2a2bd47e257

Daniele Panozzo 11 жил өмнө
parent
commit
7fabcee617

+ 235 - 0
tutorial/100_Introduction to libigl.md

@@ -0,0 +1,235 @@
+title: libigl Tutorial
+author: Alec Jacobson, Daniele Pannozo and others
+date: 20 June 2014
+css: style.css
+html header:   <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
+<link rel="stylesheet" href="http://yandex.st/highlightjs/7.3/styles/default.min.css">
+<script src="http://yandex.st/highlightjs/7.3/highlight.min.js"></script>
+<script>hljs.initHighlightingOnLoad();</script>
+
+* [Chapter 1: Introduction to libigl][100]
+    * [Mesh representation][101]
+    * [Plotting surfaces][102]
+    * [Interaction with keyboard and mouse][103]
+    * [Scalar field visualization][104]
+    * [Overlays][105]
+    * [Picking vertices and faces][106]
+    * [libigl design principles][107]
+
+# Chapter 1 [100]
+
+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 showcase the basic concepts of libigl and introduce a simple mesh viewer that allows to easily visualize surface mesh and its attributes. All the examples are cross-platform and can be compiled on MacOSX, Linux and Windows.
+
+All dependencies for the compilation of these examples are contained in libigl (external folder), with the exception of Eigen, which should be downloaded and unpacked in the folder containing the libigl root folder.
+
+All examples depends on glfw, glew and anttweakbar. A copy
+of the sourcecode of each library is provided together with libigl
+and they can be precompiled using:
+``` sh
+    sh compile_dependencies_macosx.sh (MACOSX)
+    sh compile_dependencies_linux.sh (LINUX)
+```
+Precompiled binaries are provided for Visual Studio 2014 64bit.
+
+Use the cmake file in the tutorial folder to build all the examples:
+``` sh
+  cd tutorial
+  mkdir build
+  cd build
+  cmake ../
+  make
+```
+
+For a few examples in Chapter 5, the [CoMiSo solver](http://www.graphics.rwth-aachen.de/software/comiso) has to be downloaded and compiled separately.
+
+## Mesh representation [101]
+
+libIGL uses the [Eigen](http://eigen.tuxfamily.org/) library to encode vector and matrices. We will review in this tutorial many of the basic operations that Eigen supports: If you want to get an idea of what operations are supported you can take a look at the [dense](http://eigen.tuxfamily.org/dox/group__QuickRefPage.html) and [sparse](http://eigen.tuxfamily.org/dox/group__SparseQuickRefPage.html) quick reference guides.
+
+We encode a triangular mesh 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 the x,y,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 vertex coordinates in **F**.
+
+![A simple mesh made of 2 triangles and 4 vertices.](images/VF.png)
+
+Note that the order of the vertex indices in F determines the orientation of the triangles and it should be consistent for the entire surface. As we will see later, additional properties of the mesh will be similarly stored as matrices. This simple representation has many advantages:
+
+* it is memory efficient and cache friendly
+* the use of indices instead of pointers greatly simplifies debuggind
+* the data can be trivially read/written on disk
+
+libIGL provides Input/Output functions to read and write common mesh formats.
+The reading/writing functions are named read\*.h and write\*.h, respectively.
+
+Reading a mesh from file requires a single igl function call:
+
+``` cpp
+igl::readOFF("../shared/cube.off", V, F);
+```
+
+The functions read the mesh cube.off and fills the provided matrices V and F.
+Similarly, to write a mesh to file (in OBJ format):
+
+``` cpp
+igl::writeOBJ("cube.obj",V,F);
+```
+
+See [Example 101](101_FileIO/main.cpp) for the source code of a simple mesh converter from OFF to OBJ format.
+
+## Plotting surfaces [102]
+
+libigl contains an OpenGL viewer that can visualize surface and their properties.
+
+The following code ([Example 102](102_DrawMesh/main.cpp)) is a basic skeleton that will be used over the entire tutorial. It is a standalone application that loads a mesh and visualize it.
+
+``` cpp
+#include <igl/readOFF.h>
+#include <igl/viewer/Viewer.h>
+
+Eigen::MatrixXd V;
+Eigen::MatrixXi F;
+
+int main(int argc, char *argv[])
+{
+  // Load a mesh in OFF format
+  igl::readOFF("../shared/bunny.off", V, F);
+
+  // Plot the mesh
+  igl::Viewer viewer;
+  viewer.set_mesh(V, F);
+  viewer.launch();
+}
+```
+
+The function set_mesh assigns to the viewer the mesh that we want to plot, and the last line creates an opengl context and starts the draw loop. Additional properties can be plotted on the mesh, and it is also possible to extend the viewer with standard OpenGL code. Please see the documentation in  [Viewer.h](../include/igl/Viewer/Viewer.h) for more details.
+
+![([Example 102](102_DrawMesh/main.cpp)) loads and draws a mesh.](images/102_DrawMesh.png)
+
+## Interaction with keyboard and mouse [103]
+
+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](103_Events/main.cpp). The keyboard callback changes the visualized mesh depending on the key pressed:
+
+``` cpp
+bool key_down(igl::Viewer& viewer, unsigned char key, int modifier)
+{
+  if (key == '1')
+  {
+    viewer.clear_mesh();
+    viewer.set_mesh(V1, F1);
+  }
+  else if (key == '2')
+  {
+    viewer.clear_mesh();
+    viewer.set_mesh(V2, F2);
+  }
+  return false;
+}
+```
+and it 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 class Viewer_plugin for more details.
+
+## Scalar field visualization [104]
+
+Colors and normals can be associated to both faces or normals using the set_colors function:
+``` cpp
+viewer.set_colors(C);
+```
+**C** is a #C by 3 matrix with one RGB color per row, and as many rows as the number of faces **or** the number of vertices. Depending on the size of **C**, the viewer applies the color to faces or vertices.
+
+Colors are commonly used to visualize scalar functions defined on a surface using a transfer functions, that maps a scalar value between 0 and 1 to a color scale. A simple example of a scalar field defined on a surface is the z coordinate of each point. We can extract this information from our mesh by taking the first column of V (which contains the stacked z coordiantes of all the vertices), and map it to colors using the igl::jet function:
+
+``` cpp
+Eigen::VectorXd x = V.col(2);
+igl::jet(x,true,C);
+```
+
+The first row extracts the third column from V and the second calls the 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 color scale.
+
+![([Example 104](104_Colors/main.cpp)) igl::jet converts a scalar field to a color field.](images/104_Colors.png)
+
+## Overlays [105]
+
+In addition to the surface, the viewer supports the visualization of points, lines and text label that can be very helful while developing geometric processing algorithms. These additional informations can be drawn using the following functions:
+
+``` cpp
+viewer.add_points(P,Eigen::RowVector3d(r,g,b));
+```
+
+Draws a point of color r,g,b for each row of P at the coordinates specified in each row of P, which is a #P by 3 matrix.
+
+``` cpp
+viewer.add_edges(P1,P2,Eigen::RowVector3d(r,g,b);
+```
+
+Draws a line for each line of P1 and P2, which connects the point in P1 to the point in P2.
+
+``` cpp
+viewer.add_label(p,str);
+```
+
+Draws a label containing the string str at the position p.
+
+These functions are demonstrate in [Example 105](105_Overlays/main.cpp) where the bounding box of the mesh is plotted using lines and points. The bounding box of a mesh can be found using Eigen:
+
+``` cpp
+Eigen::Vector3d m = V.colwise().minCoeff();
+Eigen::Vector3d M = V.colwise().maxCoeff();
+```
+
+![([Example 105](105_Overlays/main.cpp)) The bounding box of a mesh is shown using overlays.](images/105_Overlays.png)
+
+Using matrices to encode the mesh and its attributes allows to write short and efficient code for many operations, avoiding to write for loops.
+
+## Picking [106]
+
+Picking vertices and faces using the mouse is very common in geometry processing applications. While this might seem a simple operation, its implementation is quite involved. libigl contains a function that solves this problem using the [Embree](https://software.intel.com/en-us/articles/embree-photo-realistic-ray-tracing-kernels) raycaster. Its usage is demonstrated in [Example 106](106_Picking/main.cpp):
+``` cpp
+bool hit = igl::unproject_in_mesh(
+  Vector2f(x,y),
+  F,
+  viewer.view * viewer.model,
+  viewer.proj,
+  viewer.viewport,
+  *ei,
+  fid,
+  vid);
+```
+
+This function casts a ray from the view plane in the view direction. x,y are the position of the mouse on screen; view,model,proj are the view, model and projection matrix respectively, viewport is the viewport in opengl format; ei contains a [Bounding Volume Hierarchy](http://en.wikipedia.org/wiki/Bounding_volume_hierarchy) constructed by Embree, and fid and vid are the picked face and vertex respectively.
+
+This function is a good example of the design principles in libigl: the function takes very simple types, mostly matrix or vectors, and can be easily reused for many different tasks.
+Not committing to heavy data structures, favors simplicity, ease of use and reusability.
+
+# libigl design choices [107]
+
+To conclude the introduction, we summarize the main design principles in libigl:
+
+* No complex data types. Mostly matrices and vectors. This greatly favors code reusability and forces the 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 straighforward 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](../build/))
+
+![([Example 106](106_Picking/main.cpp)) Picking via ray casting. The selected vertices are colored in red.](images/106_Picking.png)

+ 0 - 1
tutorial/103_Events/main.cpp

@@ -1,4 +1,3 @@
-#define IGL_HEADER_ONLY
 #include <igl/readOFF.h>
 #include <igl/viewer/Viewer.h>
 

+ 5 - 12
tutorial/104_Colors/main.cpp

@@ -5,6 +5,7 @@
 
 Eigen::MatrixXd V;
 Eigen::MatrixXi F;
+Eigen::MatrixXd C;
 
 int main(int argc, char *argv[])
 {
@@ -15,19 +16,11 @@ int main(int argc, char *argv[])
   igl::Viewer viewer;
   viewer.set_mesh(V, F);
 
+  // Use the x coordinate as a scalar field over the surface
+  Eigen::VectorXd x = V.col(2);
 
-  // Normalize x coordinate between 0 and 1
-  Eigen::VectorXd value = V.col(0).array() - V.col(0).minCoeff();
-  value = value.array() / value.maxCoeff();
-
-  // Map to colors using jet colorramp
-  Eigen::MatrixXd C(V.rows(),3);
-  for (unsigned i=0; i<V.rows(); ++i)
-  {
-    double r,g,b;
-    igl::jet(value(i),r,g,b);
-    C.row(i) << r,g,b;
-  }
+  // Compute per-vertex colors
+  igl::jet(x,true,C);
 
   // Add per-vertex colors
   viewer.set_colors(C);

+ 3 - 1
tutorial/106_Picking/main.cpp

@@ -25,7 +25,9 @@ bool mouse_down(igl::Viewer& viewer, int button, int modifier)
   int vid, fid;
 
   // Cast a ray in the view direction starting from the mouse position
-  bool hit = unproject_in_mesh(Vector2f(viewer.current_mouse_x,viewer.viewport(3) - viewer.current_mouse_y),
+  double x = viewer.current_mouse_x;
+  double y = viewer.viewport(3) - viewer.current_mouse_y;
+  bool hit = unproject_in_mesh(Vector2f(x,y),
                                 F,
                                 viewer.view * viewer.model,
                                 viewer.proj,

+ 0 - 0
tutorial/compile_linux.sh → tutorial/compile_dependencies_linux.sh


+ 0 - 0
tutorial/compile_macosx.sh → tutorial/compile_dependencies_macosx.sh


+ 1 - 0
tutorial/images/102_DrawMesh.png.REMOVED.git-id

@@ -0,0 +1 @@
+3f7c5ae444c18f1c10501e0de58247c70fad7c9b

+ 1 - 0
tutorial/images/104_Colors.png.REMOVED.git-id

@@ -0,0 +1 @@
+f3849d85e8946a298f288132cd3b3a3eee9ff228

+ 1 - 0
tutorial/images/105_Overlays.png.REMOVED.git-id

@@ -0,0 +1 @@
+c81c264b82b4fd4cded7da143be3ae9cad47c634

+ 1 - 0
tutorial/images/106_Picking.png.REMOVED.git-id

@@ -0,0 +1 @@
+82a2caf85135d994691b249beef229bc7a192ea1

+ 1 - 0
tutorial/images/VF.pdf.REMOVED.git-id

@@ -0,0 +1 @@
+e37343c2bb0fffbe8572d6c0ecd62713430b76d3

+ 1 - 0
tutorial/images/VF.png.REMOVED.git-id

@@ -0,0 +1 @@
+8df9bef00048d9fd942f74ee127b14ab2d86984d

+ 28 - 58
tutorial/readme.md

@@ -21,10 +21,8 @@ of these lecture notes links to a cross-platform example application.
 
 # Table of Contents
 
-* Basic Usage
-    * **100_FileIO**: Example of reading/writing mesh files
-    * **101_Serialization**: Example of using the XML serialization framework
-    * **102_DrawMesh**: Example of plotting a mesh
+* [Chapter 1: Introduction to libigl]
+
 * [Chapter 2: Discrete Geometric Quantities and
   Operators](#chapter2:discretegeometricquantitiesandoperators)
     * [201 Normals](#normals)
@@ -41,7 +39,7 @@ of these lecture notes links to a cross-platform example application.
 * [Chapter 3: Matrices and Linear Algebra](#chapter3:matricesandlinearalgebra)
     * [301 Slice](#slice)
     * [302 Sort](#sort)
-        * [Other Matlab-style functions](#othermatlab-stylefunctions) 
+        * [Other Matlab-style functions](#othermatlab-stylefunctions)
     * [303 Laplace Equation](#laplaceequation)
         * [Quadratic energy minimization](#quadraticenergyminimization)
     * [304 Linear Equality Constraints](#linearequalityconstraints)
@@ -54,34 +52,6 @@ of these lecture notes links to a cross-platform example application.
     * [405 Fast automatic skinning
       transformations](#fastautomaticskinningtransformations)
 
-
-# Compilation Instructions
-
-All examples depends on glfw, glew and anttweakbar. A copy
-of the sourcecode of each library is provided together with libigl
-and they can be precompiled using:
-
-**Alec: Is this just compiling the dependencies? Then perhaps rename `compile_dependencies_*`**
-
-    sh compile_macosx.sh (MACOSX)
-    sh compile_linux.sh (LINUX)
-    compile_windows.bat (Visual Studio 2012)
-
-Every example can be compiled by using the cmake file provided in its folder.
-On Linux and MacOSX, you can use the provided bash script:
-
-    sh ../compile_example.sh
-
-## (Optional: compilation with libigl as static library)
-
-By default, libigl is a _headers only_ library, thus it does not require
-compilation. However, one can precompile libigl as a statically linked library.
-See `../README.md` in the main directory for compilations instructions to
-produce `libigl.a` and other libraries. Once compiled, these examples can be
-compiled using the `CMAKE` flag `-DLIBIGL_USE_STATIC_LIBRARY=ON`:
-
-    ../compile_example.sh -DLIBIGL_USE_STATIC_LIBRARY=ON
-
 # Chapter 2: Discrete Geometric Quantities and Operators
 This chapter illustrates a few discrete quantities that libigl can compute on a
 mesh. This also provides an introduction to basic drawing and coloring routines
@@ -187,13 +157,13 @@ orthogonal.
 
 Mean curvature is defined simply as the average of principal curvatures:
 
- $H = \frac{1}{2}(k_1 + k_2).$ 
+ $H = \frac{1}{2}(k_1 + k_2).$
 
 One way to extract mean curvature is by examining the Laplace-Beltrami operator
 applied to the surface positions. The result is a so-called mean-curvature
 normal:
 
-  $-\Delta \mathbf{x} = H \mathbf{n}.$ 
+  $-\Delta \mathbf{x} = H \mathbf{n}.$
 
 It is easy to compute this on a discrete triangle mesh in libigl using the cotangent
 Laplace-Beltrami operator [][#meyer_2003].
@@ -253,8 +223,8 @@ linear on incident triangles.](images/hat-function.jpg)
 Thus gradients of such piecewise linear functions are simply sums of gradients
 of the hat functions:
 
- $\nabla f(\mathbf{x}) \approx 
- \nabla \sum\limits_{i=0}^n \nabla \phi_i(\mathbf{x})\, f_i = 
+ $\nabla f(\mathbf{x}) \approx
+ \nabla \sum\limits_{i=0}^n \nabla \phi_i(\mathbf{x})\, f_i =
  \sum\limits_{i=0}^n \nabla \phi_i(\mathbf{x})\, f_i.$
 
 This reveals that the gradient is a linear function of the vector of $f_i$
@@ -276,12 +246,12 @@ visualizes the vector field.](images/cheburashka-gradient.jpg)
 ## Laplacian
 
 The discrete Laplacian is an essential geometry processing tool. Many
-interpretations and flavors of the Laplace and Laplace-Beltrami operator exist. 
+interpretations and flavors of the Laplace and Laplace-Beltrami operator exist.
 
 In open Euclidean space, the _Laplace_ operator is the usual divergence of gradient
 (or equivalently the Laplacian of a function is the trace of its Hessian):
 
- $\Delta f = 
+ $\Delta f =
  \frac{\partial^2 f}{\partial x^2} +
  \frac{\partial^2 f}{\partial y^2} +
  \frac{\partial^2 f}{\partial z^2}.$
@@ -383,8 +353,8 @@ Green's identity (ignoring boundary conditions for the moment):
 
 Or in matrix form which is immediately translatable to code:
 
-  $\mathbf{f}^T \mathbf{G}^T \mathbf{T} \mathbf{G} \mathbf{f} = 
-  \mathbf{f}^T \mathbf{M} \mathbf{M}^{-1} \mathbf{L} \mathbf{f} = 
+  $\mathbf{f}^T \mathbf{G}^T \mathbf{T} \mathbf{G} \mathbf{f} =
+  \mathbf{f}^T \mathbf{M} \mathbf{M}^{-1} \mathbf{L} \mathbf{f} =
   \mathbf{f}^T \mathbf{L} \mathbf{f}.$
 
 So we have that $\mathbf{L} = \mathbf{G}^T \mathbf{T} \mathbf{G}$. This also
@@ -478,7 +448,7 @@ where again `I` reveals the index of sort so that it can be reproduced with
 
 Analogous functions are available in libigl for: `max`, `min`, and `unique`.
 
-![The example `Sort` shows how to use `igl::sortrows` to 
+![The example `Sort` shows how to use `igl::sortrows` to
 pseudocolor triangles according to their barycenters' sorted
 order.](images/decimated-knight-sort-color.jpg)
 
@@ -533,27 +503,27 @@ vertices come first and then boundary vertices:
 
  $$\left(\begin{array}{cc}
  \mathbf{L}_{in,in} & \mathbf{L}_{in,b}\\
- \mathbf{L}_{b,in} & \mathbf{L}_{b,b}\end{array}\right) 
+ \mathbf{L}_{b,in} & \mathbf{L}_{b,b}\end{array}\right)
  \left(\begin{array}{c}
  \mathbf{z}_{in}\\
- \mathbf{L}_{b}\end{array}\right) = 
+ \mathbf{L}_{b}\end{array}\right) =
  \left(\begin{array}{c}
  \mathbf{0}_{in}\\
- \mathbf{*}_{b}\end{array}\right)$$ 
+ \mathbf{*}_{b}\end{array}\right)$$
 
 The bottom block of equations is no longer meaningful so we'll only consider
 the top block:
 
  $$\left(\begin{array}{cc}
- \mathbf{L}_{in,in} & \mathbf{L}_{in,b}\end{array}\right) 
+ \mathbf{L}_{in,in} & \mathbf{L}_{in,b}\end{array}\right)
  \left(\begin{array}{c}
  \mathbf{z}_{in}\\
- \mathbf{z}_{b}\end{array}\right) = 
+ \mathbf{z}_{b}\end{array}\right) =
  \mathbf{0}_{in}$$
 
 Where now we can move known values to the right-hand side:
 
- $$\mathbf{L}_{in,in} 
+ $$\mathbf{L}_{in,in}
  \mathbf{z}_{in} = -
  \mathbf{L}_{in,b}
  \mathbf{z}_{b}$$
@@ -587,7 +557,7 @@ On our discrete mesh, recall that this becomes
 
 The general problem of minimizing some energy over a mesh subject to fixed
 value boundary conditions is so wide spread that libigl has a dedicated api for
-solving such systems. 
+solving such systems.
 
 Let's consider a general quadratic minimization problem subject to different
 common constraints:
@@ -596,15 +566,15 @@ common constraints:
  \mathbf{z}^T \mathbf{B} + \text{constant},$$
 
  subject to
- 
+
  $$\mathbf{z}_b = \mathbf{z}_{bc} \text{ and } \mathbf{A}_{eq} \mathbf{z} =
  \mathbf{B}_{eq},$$
 
-where 
+where
 
   - $\mathbf{Q}$ is a (usually sparse) $n \times n$ positive semi-definite
-    matrix of quadratic coefficients (Hessian), 
-  - $\mathbf{B}$ is a $n \times 1$ vector of linear coefficients, 
+    matrix of quadratic coefficients (Hessian),
+  - $\mathbf{B}$ is a $n \times 1$ vector of linear coefficients,
   - $\mathbf{z}_b$ is a $|b| \times 1$ portion of
 $\mathbf{z}$ corresponding to boundary or _fixed_ vertices,
   - $\mathbf{z}_{bc}$ is a $|b| \times 1$ vector of known values corresponding to
@@ -673,10 +643,10 @@ saddle problem:
 This can be rewritten in a more familiar form by stacking $\mathbf{z}$ and
 $\lambda$ into one $(m+n) \times 1$ vector of unknowns:
 
- $$\mathop{\text{find saddle }}_{\mathbf{z},\lambda}\, 
+ $$\mathop{\text{find saddle }}_{\mathbf{z},\lambda}\,
  \frac{1}{2}
  \left(
-  \mathbf{z}^T 
+  \mathbf{z}^T
   \lambda^T
  \right)
  \left(
@@ -690,9 +660,9 @@ $\lambda$ into one $(m+n) \times 1$ vector of unknowns:
   \mathbf{z}\\
   \lambda
   \end{array}
- \right) + 
+ \right) +
  \left(
-  \mathbf{z}^T 
+  \mathbf{z}^T
   \lambda^T
  \right)
  \left(
@@ -707,7 +677,7 @@ Differentiating with respect to $\left( \mathbf{z}^T \lambda^T \right)$ reveals
 a linear system and we can solve for $\mathbf{z}$ and $\lambda$. The only
 difference from
 the straight quadratic
-_minimization_ system, is that 
+_minimization_ system, is that
 this saddle problem system will not be positive definite. Thus, we must use a
 different factorization technique (LDLT rather than LLT). Luckily, libigl's
 `min_quad_with_fixed_precompute` automatically chooses the correct solver in