Quellcode durchsuchen

mv viewer to opengl/glfw dependency

Former-commit-id: 15c328640fd378e0e5169284ed24daddb84cceaf
Alec Jacobson vor 8 Jahren
Ursprung
Commit
d0b3111eae

+ 424 - 0
include/igl/ViewerData.cpp

@@ -0,0 +1,424 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@gmail.com>
+//
+// 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 "ViewerData.h"
+
+#include "per_face_normals.h"
+#include "material_colors.h"
+#include "parula.h"
+#include "per_vertex_normals.h"
+
+#include <iostream>
+
+
+IGL_INLINE igl::ViewerData::ViewerData()
+: dirty(DIRTY_ALL)
+{
+  clear();
+};
+
+IGL_INLINE void igl::ViewerData::set_face_based(bool newvalue)
+{
+  if (face_based != newvalue)
+  {
+    face_based = newvalue;
+    dirty = DIRTY_ALL;
+  }
+}
+
+// Helpers that draws the most common meshes
+IGL_INLINE void igl::ViewerData::set_mesh(
+    const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F)
+{
+  using namespace std;
+
+  Eigen::MatrixXd V_temp;
+
+  // If V only has two columns, pad with a column of zeros
+  if (_V.cols() == 2)
+  {
+    V_temp = Eigen::MatrixXd::Zero(_V.rows(),3);
+    V_temp.block(0,0,_V.rows(),2) = _V;
+  }
+  else
+    V_temp = _V;
+
+  if (V.rows() == 0 && F.rows() == 0)
+  {
+    V = V_temp;
+    F = _F;
+
+    compute_normals();
+    uniform_colors(
+      Eigen::Vector3d(GOLD_AMBIENT[0], GOLD_AMBIENT[1], GOLD_AMBIENT[2]),
+      Eigen::Vector3d(GOLD_DIFFUSE[0], GOLD_DIFFUSE[1], GOLD_DIFFUSE[2]),
+      Eigen::Vector3d(GOLD_SPECULAR[0], GOLD_SPECULAR[1], GOLD_SPECULAR[2]));
+
+    grid_texture();
+  }
+  else
+  {
+    if (_V.rows() == V.rows() && _F.rows() == F.rows())
+    {
+      V = V_temp;
+      F = _F;
+    }
+    else
+      cerr << "ERROR (set_mesh): The new mesh has a different number of vertices/faces. Please clear the mesh before plotting."<<endl;
+  }
+  dirty |= DIRTY_FACE | DIRTY_POSITION;
+}
+
+IGL_INLINE void igl::ViewerData::set_vertices(const Eigen::MatrixXd& _V)
+{
+  V = _V;
+  assert(F.size() == 0 || F.maxCoeff() < V.rows());
+  dirty |= DIRTY_POSITION;
+}
+
+IGL_INLINE void igl::ViewerData::set_normals(const Eigen::MatrixXd& N)
+{
+  using namespace std;
+  if (N.rows() == V.rows())
+  {
+    set_face_based(false);
+    V_normals = N;
+  }
+  else if (N.rows() == F.rows() || N.rows() == F.rows()*3)
+  {
+    set_face_based(true);
+    F_normals = N;
+  }
+  else
+    cerr << "ERROR (set_normals): Please provide a normal per face, per corner or per vertex."<<endl;
+  dirty |= DIRTY_NORMAL;
+}
+
+IGL_INLINE void igl::ViewerData::set_colors(const Eigen::MatrixXd &C)
+{
+  using namespace std;
+  using namespace Eigen;
+  if(C.rows()>0 && C.cols() == 1)
+  {
+    Eigen::MatrixXd C3;
+    igl::parula(C,true,C3);
+    return set_colors(C3);
+  }
+  // Ambient color should be darker color
+  const auto ambient = [](const MatrixXd & C)->MatrixXd
+  {
+    MatrixXd T = 0.1*C;
+    T.col(3) = C.col(3);
+    return T;
+  };
+  // Specular color should be a less saturated and darker color: dampened
+  // highlights
+  const auto specular = [](const MatrixXd & C)->MatrixXd
+  {
+    const double grey = 0.3;
+    MatrixXd T = grey+0.1*(C.array()-grey);
+    T.col(3) = C.col(3);
+    return T;
+  };
+  if (C.rows() == 1)
+  {
+    for (unsigned i=0;i<V_material_diffuse.rows();++i)
+    {
+      V_material_diffuse.row(i) << C.row(0),1;
+    }
+    V_material_ambient = ambient(V_material_diffuse);
+    V_material_specular = specular(V_material_diffuse);
+
+    for (unsigned i=0;i<F_material_diffuse.rows();++i)
+    {
+      F_material_diffuse.row(i) << C.row(0),1;
+    }
+    F_material_ambient = ambient(F_material_diffuse);
+    F_material_specular = specular(F_material_diffuse);
+  }
+  else if (C.rows() == V.rows())
+  {
+    set_face_based(false);
+    for (unsigned i=0;i<V_material_diffuse.rows();++i)
+    {
+      V_material_diffuse.row(i) << C.row(i),1;
+    }
+    V_material_ambient = ambient(V_material_diffuse);
+    V_material_specular = specular(V_material_diffuse);
+  }
+  else if (C.rows() == F.rows())
+  {
+    set_face_based(true);
+    for (unsigned i=0;i<F_material_diffuse.rows();++i)
+    {
+      F_material_diffuse.row(i) << C.row(i),1;
+    }
+    F_material_ambient = ambient(F_material_diffuse);
+    F_material_specular = specular(F_material_diffuse);
+  }
+  else
+    cerr << "ERROR (set_colors): Please provide a single color, or a color per face or per vertex."<<endl;;
+  dirty |= DIRTY_DIFFUSE;
+
+}
+
+IGL_INLINE void igl::ViewerData::set_uv(const Eigen::MatrixXd& UV)
+{
+  using namespace std;
+  if (UV.rows() == V.rows())
+  {
+    set_face_based(false);
+    V_uv = UV;
+  }
+  else
+    cerr << "ERROR (set_UV): Please provide uv per vertex."<<endl;;
+  dirty |= DIRTY_UV;
+}
+
+IGL_INLINE void igl::ViewerData::set_uv(const Eigen::MatrixXd& UV_V, const Eigen::MatrixXi& UV_F)
+{
+  set_face_based(true);
+  V_uv = UV_V.block(0,0,UV_V.rows(),2);
+  F_uv = UV_F;
+  dirty |= DIRTY_UV;
+}
+
+
+IGL_INLINE void igl::ViewerData::set_texture(
+  const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& R,
+  const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& G,
+  const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& B)
+{
+  texture_R = R;
+  texture_G = G;
+  texture_B = B;
+  texture_A = Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>::Constant(R.rows(),R.cols(),255);
+  dirty |= DIRTY_TEXTURE;
+}
+
+IGL_INLINE void igl::ViewerData::set_texture(
+  const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& R,
+  const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& G,
+  const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& B,
+  const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& A)
+{
+  texture_R = R;
+  texture_G = G;
+  texture_B = B;
+  texture_A = A;
+  dirty |= DIRTY_TEXTURE;
+}
+
+IGL_INLINE void igl::ViewerData::set_points(
+  const Eigen::MatrixXd& P,
+  const Eigen::MatrixXd& C)
+{
+  // clear existing points
+  points.resize(0,0);
+  add_points(P,C);
+}
+
+IGL_INLINE void igl::ViewerData::add_points(const Eigen::MatrixXd& P,  const Eigen::MatrixXd& C)
+{
+  Eigen::MatrixXd P_temp;
+
+  // If P only has two columns, pad with a column of zeros
+  if (P.cols() == 2)
+  {
+    P_temp = Eigen::MatrixXd::Zero(P.rows(),3);
+    P_temp.block(0,0,P.rows(),2) = P;
+  }
+  else
+    P_temp = P;
+
+  int lastid = points.rows();
+  points.conservativeResize(points.rows() + P_temp.rows(),6);
+  for (unsigned i=0; i<P_temp.rows(); ++i)
+    points.row(lastid+i) << P_temp.row(i), i<C.rows() ? C.row(i) : C.row(C.rows()-1);
+
+  dirty |= DIRTY_OVERLAY_POINTS;
+}
+
+IGL_INLINE void igl::ViewerData::set_edges(
+  const Eigen::MatrixXd& P,
+  const Eigen::MatrixXi& E,
+  const Eigen::MatrixXd& C)
+{
+  using namespace Eigen;
+  lines.resize(E.rows(),9);
+  assert(C.cols() == 3);
+  for(int e = 0;e<E.rows();e++)
+  {
+    RowVector3d color;
+    if(C.size() == 3)
+    {
+      color<<C;
+    }else if(C.rows() == E.rows())
+    {
+      color<<C.row(e);
+    }
+    lines.row(e)<< P.row(E(e,0)), P.row(E(e,1)), color;
+  }
+  dirty |= DIRTY_OVERLAY_LINES;
+}
+
+IGL_INLINE void igl::ViewerData::add_edges(const Eigen::MatrixXd& P1, const Eigen::MatrixXd& P2, const Eigen::MatrixXd& C)
+{
+  Eigen::MatrixXd P1_temp,P2_temp;
+
+  // If P1 only has two columns, pad with a column of zeros
+  if (P1.cols() == 2)
+  {
+    P1_temp = Eigen::MatrixXd::Zero(P1.rows(),3);
+    P1_temp.block(0,0,P1.rows(),2) = P1;
+    P2_temp = Eigen::MatrixXd::Zero(P2.rows(),3);
+    P2_temp.block(0,0,P2.rows(),2) = P2;
+  }
+  else
+  {
+    P1_temp = P1;
+    P2_temp = P2;
+  }
+
+  int lastid = lines.rows();
+  lines.conservativeResize(lines.rows() + P1_temp.rows(),9);
+  for (unsigned i=0; i<P1_temp.rows(); ++i)
+    lines.row(lastid+i) << P1_temp.row(i), P2_temp.row(i), i<C.rows() ? C.row(i) : C.row(C.rows()-1);
+
+  dirty |= DIRTY_OVERLAY_LINES;
+}
+
+IGL_INLINE void igl::ViewerData::add_label(const Eigen::VectorXd& P,  const std::string& str)
+{
+  Eigen::RowVectorXd P_temp;
+
+  // If P only has two columns, pad with a column of zeros
+  if (P.size() == 2)
+  {
+    P_temp = Eigen::RowVectorXd::Zero(3);
+    P_temp << P.transpose(), 0;
+  }
+  else
+    P_temp = P;
+
+  int lastid = labels_positions.rows();
+  labels_positions.conservativeResize(lastid+1, 3);
+  labels_positions.row(lastid) = P_temp;
+  labels_strings.push_back(str);
+}
+
+IGL_INLINE void igl::ViewerData::clear()
+{
+  V                       = Eigen::MatrixXd (0,3);
+  F                       = Eigen::MatrixXi (0,3);
+
+  F_material_ambient      = Eigen::MatrixXd (0,4);
+  F_material_diffuse      = Eigen::MatrixXd (0,4);
+  F_material_specular     = Eigen::MatrixXd (0,4);
+
+  V_material_ambient      = Eigen::MatrixXd (0,4);
+  V_material_diffuse      = Eigen::MatrixXd (0,4);
+  V_material_specular     = Eigen::MatrixXd (0,4);
+
+  F_normals               = Eigen::MatrixXd (0,3);
+  V_normals               = Eigen::MatrixXd (0,3);
+
+  V_uv                    = Eigen::MatrixXd (0,2);
+  F_uv                    = Eigen::MatrixXi (0,3);
+
+  lines                   = Eigen::MatrixXd (0,9);
+  points                  = Eigen::MatrixXd (0,6);
+  labels_positions        = Eigen::MatrixXd (0,3);
+  labels_strings.clear();
+
+  face_based = false;
+}
+
+IGL_INLINE void igl::ViewerData::compute_normals()
+{
+  igl::per_face_normals(V, F, F_normals);
+  igl::per_vertex_normals(V, F, F_normals, V_normals);
+  dirty |= DIRTY_NORMAL;
+}
+
+IGL_INLINE void igl::ViewerData::uniform_colors(Eigen::Vector3d ambient, Eigen::Vector3d diffuse, Eigen::Vector3d specular)
+{
+  Eigen::Vector4d ambient4;
+  Eigen::Vector4d diffuse4;
+  Eigen::Vector4d specular4;
+
+  ambient4 << ambient, 1;
+  diffuse4 << diffuse, 1;
+  specular4 << specular, 1;
+
+  uniform_colors(ambient4,diffuse4,specular4);
+}
+
+IGL_INLINE void igl::ViewerData::uniform_colors(Eigen::Vector4d ambient, Eigen::Vector4d diffuse, Eigen::Vector4d specular)
+{
+  V_material_ambient.resize(V.rows(),4);
+  V_material_diffuse.resize(V.rows(),4);
+  V_material_specular.resize(V.rows(),4);
+
+  for (unsigned i=0; i<V.rows();++i)
+  {
+    V_material_ambient.row(i) = ambient;
+    V_material_diffuse.row(i) = diffuse;
+    V_material_specular.row(i) = specular;
+  }
+
+  F_material_ambient.resize(F.rows(),4);
+  F_material_diffuse.resize(F.rows(),4);
+  F_material_specular.resize(F.rows(),4);
+
+  for (unsigned i=0; i<F.rows();++i)
+  {
+    F_material_ambient.row(i) = ambient;
+    F_material_diffuse.row(i) = diffuse;
+    F_material_specular.row(i) = specular;
+  }
+  dirty |= DIRTY_SPECULAR | DIRTY_DIFFUSE | DIRTY_AMBIENT;
+}
+
+IGL_INLINE void igl::ViewerData::grid_texture()
+{
+  // Don't do anything for an empty mesh
+  if(V.rows() == 0)
+  {
+    V_uv.resize(V.rows(),2);
+    return;
+  }
+  if (V_uv.rows() == 0)
+  {
+    V_uv = V.block(0, 0, V.rows(), 2);
+    V_uv.col(0) = V_uv.col(0).array() - V_uv.col(0).minCoeff();
+    V_uv.col(0) = V_uv.col(0).array() / V_uv.col(0).maxCoeff();
+    V_uv.col(1) = V_uv.col(1).array() - V_uv.col(1).minCoeff();
+    V_uv.col(1) = V_uv.col(1).array() / V_uv.col(1).maxCoeff();
+    V_uv = V_uv.array() * 10;
+    dirty |= DIRTY_TEXTURE;
+  }
+
+  unsigned size = 128;
+  unsigned size2 = size/2;
+  texture_R.resize(size, size);
+  for (unsigned i=0; i<size; ++i)
+  {
+    for (unsigned j=0; j<size; ++j)
+    {
+      texture_R(i,j) = 0;
+      if ((i<size2 && j<size2) || (i>=size2 && j>=size2))
+        texture_R(i,j) = 255;
+    }
+  }
+
+  texture_G = texture_R;
+  texture_B = texture_R;
+  texture_A = Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>::Constant(texture_R.rows(),texture_R.cols(),255);
+  dirty |= DIRTY_TEXTURE;
+}

+ 252 - 0
include/igl/ViewerData.h

@@ -0,0 +1,252 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@gmail.com>
+//
+// 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_VIEWERDATA_H
+#define IGL_VIEWERDATA_H
+
+#include <cstdint>
+#include <vector>
+#include <Eigen/Core>
+#include <igl/igl_inline.h>
+
+// Alec: This is a mesh class containing a variety of data types (normals,
+// overlays, material colors, etc.)
+//
+namespace igl
+{
+
+// TODO: write documentation
+
+class ViewerData
+{
+public:
+  ViewerData();
+
+  enum DirtyFlags
+  {
+    DIRTY_NONE           = 0x0000,
+    DIRTY_POSITION       = 0x0001,
+    DIRTY_UV             = 0x0002,
+    DIRTY_NORMAL         = 0x0004,
+    DIRTY_AMBIENT        = 0x0008,
+    DIRTY_DIFFUSE        = 0x0010,
+    DIRTY_SPECULAR       = 0x0020,
+    DIRTY_TEXTURE        = 0x0040,
+    DIRTY_FACE           = 0x0080,
+    DIRTY_MESH           = 0x00FF,
+    DIRTY_OVERLAY_LINES  = 0x0100,
+    DIRTY_OVERLAY_POINTS = 0x0200,
+    DIRTY_ALL            = 0x03FF
+  };
+
+  // Empy all fields
+  IGL_INLINE void clear();
+
+  // Change the visualization mode, invalidating the cache if necessary
+  IGL_INLINE void set_face_based(bool newvalue);
+
+  // Helpers that can draw the most common meshes
+  IGL_INLINE void set_mesh(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F);
+  IGL_INLINE void set_vertices(const Eigen::MatrixXd& V);
+  IGL_INLINE void set_normals(const Eigen::MatrixXd& N);
+
+  // Set the color of the mesh
+  //
+  // Inputs:
+  //   C  #V|#F|1 by 3 list of colors
+  IGL_INLINE void set_colors(const Eigen::MatrixXd &C);
+  // Set per-vertex UV coordinates
+  //
+  // Inputs:
+  //   UV  #V by 2 list of UV coordinates (indexed by F)
+  IGL_INLINE void set_uv(const Eigen::MatrixXd& UV);
+  // Set per-corner UV coordinates
+  //
+  // Inputs:
+  //   UV_V  #UV by 2 list of UV coordinates
+  //   UV_F  #F by 3 list of UV indices into UV_V
+  IGL_INLINE void set_uv(const Eigen::MatrixXd& UV_V, const Eigen::MatrixXi& UV_F);
+  // Set the texture associated with the mesh.
+  //
+  // Inputs:
+  //   R  width by height image matrix of red channel
+  //   G  width by height image matrix of green channel
+  //   B  width by height image matrix of blue channel
+  //
+  IGL_INLINE void set_texture(
+    const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& R,
+    const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& G,
+    const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& B);
+
+  // Set the texture associated with the mesh.
+  //
+  // Inputs:
+  //   R  width by height image matrix of red channel
+  //   G  width by height image matrix of green channel
+  //   B  width by height image matrix of blue channel
+  //   A  width by height image matrix of alpha channel
+  //
+  IGL_INLINE void set_texture(
+    const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& R,
+    const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& G,
+    const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& B,
+    const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& A);
+
+  // Sets points given a list of point vertices. In constrast to `set_points`
+  // this will (purposefully) clober existing points.
+  //
+  // Inputs:
+  //   P  #P by 3 list of vertex positions
+  //   C  #P|1 by 3 color(s)
+  IGL_INLINE void set_points(
+    const Eigen::MatrixXd& P,
+    const Eigen::MatrixXd& C);
+  IGL_INLINE void add_points(const Eigen::MatrixXd& P,  const Eigen::MatrixXd& C);
+  // Sets edges given a list of edge vertices and edge indices. In constrast
+  // to `add_edges` this will (purposefully) clober existing edges.
+  //
+  // Inputs:
+  //   P  #P by 3 list of vertex positions
+  //   E  #E by 2 list of edge indices into P
+  //   C  #E|1 by 3 color(s)
+  IGL_INLINE void set_edges (const Eigen::MatrixXd& P, const Eigen::MatrixXi& E, const Eigen::MatrixXd& C);
+  IGL_INLINE void add_edges (const Eigen::MatrixXd& P1, const Eigen::MatrixXd& P2, const Eigen::MatrixXd& C);
+  IGL_INLINE void add_label (const Eigen::VectorXd& P,  const std::string& str);
+
+  // Computes the normals of the mesh
+  IGL_INLINE void compute_normals();
+
+  // Assigns uniform colors to all faces/vertices
+  IGL_INLINE void uniform_colors(
+    Eigen::Vector3d ambient,
+    Eigen::Vector3d diffuse,
+    Eigen::Vector3d specular);
+
+  // Assigns uniform colors to all faces/vertices
+  IGL_INLINE void uniform_colors(
+    Eigen::Vector4d ambient,
+    Eigen::Vector4d diffuse,
+    Eigen::Vector4d specular);
+
+  // Generates a default grid texture
+  IGL_INLINE void grid_texture();
+
+  Eigen::MatrixXd V; // Vertices of the current mesh (#V x 3)
+  Eigen::MatrixXi F; // Faces of the mesh (#F x 3)
+
+  // Per face attributes
+  Eigen::MatrixXd F_normals; // One normal per face
+
+  Eigen::MatrixXd F_material_ambient; // Per face ambient color
+  Eigen::MatrixXd F_material_diffuse; // Per face diffuse color
+  Eigen::MatrixXd F_material_specular; // Per face specular color
+
+  // Per vertex attributes
+  Eigen::MatrixXd V_normals; // One normal per vertex
+
+  Eigen::MatrixXd V_material_ambient; // Per vertex ambient color
+  Eigen::MatrixXd V_material_diffuse; // Per vertex diffuse color
+  Eigen::MatrixXd V_material_specular; // Per vertex specular color
+
+  // UV parametrization
+  Eigen::MatrixXd V_uv; // UV vertices
+  Eigen::MatrixXi F_uv; // optional faces for UVs
+
+  // Texture
+  Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> texture_R;
+  Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> texture_G;
+  Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> texture_B;
+  Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> texture_A;
+
+  // Overlays
+
+  // Lines plotted over the scene
+  // (Every row contains 9 doubles in the following format S_x, S_y, S_z, T_x, T_y, T_z, C_r, C_g, C_b),
+  // with S and T the coordinates of the two vertices of the line in global coordinates, and C the color in floating point rgb format
+  Eigen::MatrixXd lines;
+
+  // Points plotted over the scene
+  // (Every row contains 6 doubles in the following format P_x, P_y, P_z, C_r, C_g, C_b),
+  // with P the position in global coordinates of the center of the point, and C the color in floating point rgb format
+  Eigen::MatrixXd points;
+
+  // Text labels plotted over the scene
+  // Textp contains, in the i-th row, the position in global coordinates where the i-th label should be anchored
+  // Texts contains in the i-th position the text of the i-th label
+  Eigen::MatrixXd           labels_positions;
+  std::vector<std::string>  labels_strings;
+
+  // Marks dirty buffers that need to be uploaded to OpenGL
+  uint32_t dirty;
+
+  // Enable per-face or per-vertex properties
+  bool face_based;
+  /*********************************/
+};
+
+}
+
+// Alec: This seems like the wrong place for this...  Should this just go in
+// serialize.h ?
+#include <igl/serialize.h>
+namespace igl {
+	namespace serialization {
+
+		inline void serialization(bool s, igl::ViewerData& obj, std::vector<char>& buffer)
+		{
+			SERIALIZE_MEMBER(V);
+			SERIALIZE_MEMBER(F);
+
+			SERIALIZE_MEMBER(F_normals);
+			SERIALIZE_MEMBER(F_material_ambient);
+			SERIALIZE_MEMBER(F_material_diffuse);
+			SERIALIZE_MEMBER(F_material_specular);
+
+			SERIALIZE_MEMBER(V_normals);
+			SERIALIZE_MEMBER(V_material_ambient);
+			SERIALIZE_MEMBER(V_material_diffuse);
+			SERIALIZE_MEMBER(V_material_specular);
+
+			SERIALIZE_MEMBER(V_uv);
+			SERIALIZE_MEMBER(F_uv);
+
+			SERIALIZE_MEMBER(texture_R);
+			SERIALIZE_MEMBER(texture_G);
+			SERIALIZE_MEMBER(texture_B);
+      SERIALIZE_MEMBER(texture_A);
+
+			SERIALIZE_MEMBER(lines);
+			SERIALIZE_MEMBER(points);
+
+			SERIALIZE_MEMBER(labels_positions);
+			SERIALIZE_MEMBER(labels_strings);
+
+			SERIALIZE_MEMBER(dirty);
+
+			SERIALIZE_MEMBER(face_based);
+		}
+
+		template<>
+		inline void serialize(const igl::ViewerData& obj, std::vector<char>& buffer)
+		{
+			serialization(true, const_cast<igl::ViewerData&>(obj), buffer);
+		}
+
+		template<>
+		inline void deserialize(igl::ViewerData& obj, const std::vector<char>& buffer)
+		{
+			serialization(false, obj, const_cast<std::vector<char>&>(buffer));
+			obj.dirty = igl::ViewerData::DIRTY_ALL;
+		}
+	}
+}
+
+#ifndef IGL_STATIC_LIBRARY
+#  include "ViewerData.cpp"
+#endif
+
+#endif

+ 173 - 0
include/igl/opengl/Shader.cpp

@@ -0,0 +1,173 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Wenzel Jacob <wenzel@inf.ethz.ch>
+//
+// 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 "Shader.h"
+
+#include <iostream>
+#include <fstream>
+
+IGL_INLINE bool igl::opengl::Shader::init_from_files(
+  const std::string &vertex_shader_filename,
+  const std::string &fragment_shader_filename,
+  const std::string &fragment_data_name,
+  const std::string &geometry_shader_filename,
+  int geometry_shader_max_vertices)
+{
+  auto file_to_string = [](const std::string &filename)->std::string
+  {
+    std::ifstream t(filename);
+    return std::string((std::istreambuf_iterator<char>(t)),
+                        std::istreambuf_iterator<char>());
+  };
+
+  return init(
+    file_to_string(vertex_shader_filename),
+    file_to_string(fragment_shader_filename),
+    fragment_data_name,
+    file_to_string(geometry_shader_filename),
+    geometry_shader_max_vertices
+ );
+}
+
+IGL_INLINE bool igl::opengl::Shader::init(
+  const std::string &vertex_shader_string,
+  const std::string &fragment_shader_string,
+  const std::string &fragment_data_name,
+  const std::string &geometry_shader_string,
+  int geometry_shader_max_vertices)
+{
+  using namespace std;
+  vertex_shader = create_shader_helper(GL_VERTEX_SHADER, vertex_shader_string);
+  geometry_shader = create_shader_helper(GL_GEOMETRY_SHADER, geometry_shader_string);
+  fragment_shader = create_shader_helper(GL_FRAGMENT_SHADER, fragment_shader_string);
+
+  if (!vertex_shader || !fragment_shader)
+    return false;
+
+  program_shader = glCreateProgram();
+
+  glAttachShader(program_shader, vertex_shader);
+  glAttachShader(program_shader, fragment_shader);
+
+  if (geometry_shader)
+  {
+    glAttachShader(program_shader, geometry_shader);
+
+    /* This covers only basic cases and may need to be modified */
+    glProgramParameteri(program_shader, GL_GEOMETRY_INPUT_TYPE, GL_TRIANGLES);
+    glProgramParameteri(program_shader, GL_GEOMETRY_OUTPUT_TYPE, GL_TRIANGLES);
+    glProgramParameteri(program_shader, GL_GEOMETRY_VERTICES_OUT, geometry_shader_max_vertices);
+  }
+
+  glBindFragDataLocation(program_shader, 0, fragment_data_name.c_str());
+  glLinkProgram(program_shader);
+
+  GLint status;
+  glGetProgramiv(program_shader, GL_LINK_STATUS, &status);
+
+  if (status != GL_TRUE)
+  {
+    char buffer[512];
+    glGetProgramInfoLog(program_shader, 512, NULL, buffer);
+    cerr << "Linker error: " << endl << buffer << endl;
+    program_shader = 0;
+    return false;
+  }
+
+  return true;
+}
+
+IGL_INLINE void igl::opengl::Shader::bind()
+{
+  glUseProgram(program_shader);
+}
+
+IGL_INLINE GLint igl::opengl::Shader::attrib(const std::string &name) const
+{
+  return glGetAttribLocation(program_shader, name.c_str());
+}
+
+IGL_INLINE GLint igl::opengl::Shader::uniform(const std::string &name) const
+{
+  return glGetUniformLocation(program_shader, name.c_str());
+}
+
+IGL_INLINE GLint igl::opengl::Shader::bindVertexAttribArray(
+  const std::string &name, GLuint bufferID, const Eigen::MatrixXf &M, bool refresh) const
+{
+  GLint id = attrib(name);
+  if (id < 0)
+    return id;
+  if (M.size() == 0)
+  {
+    glDisableVertexAttribArray(id);
+    return id;
+  }
+  glBindBuffer(GL_ARRAY_BUFFER, bufferID);
+  if (refresh)
+    glBufferData(GL_ARRAY_BUFFER, sizeof(float)*M.size(), M.data(), GL_DYNAMIC_DRAW);
+  glVertexAttribPointer(id, M.rows(), GL_FLOAT, GL_FALSE, 0, 0);
+  glEnableVertexAttribArray(id);
+  return id;
+}
+
+IGL_INLINE void igl::opengl::Shader::free()
+{
+  if (program_shader)
+  {
+    glDeleteProgram(program_shader);
+    program_shader = 0;
+  }
+  if (vertex_shader)
+  {
+    glDeleteShader(vertex_shader);
+    vertex_shader = 0;
+  }
+  if (fragment_shader)
+  {
+    glDeleteShader(fragment_shader);
+    fragment_shader = 0;
+  }
+  if (geometry_shader)
+  {
+    glDeleteShader(geometry_shader);
+    geometry_shader = 0;
+  }
+}
+
+IGL_INLINE GLuint igl::opengl::Shader::create_shader_helper(GLint type, const std::string &shader_string)
+{
+  using namespace std;
+  if (shader_string.empty())
+    return (GLuint) 0;
+
+  GLuint id = glCreateShader(type);
+  const char *shader_string_const = shader_string.c_str();
+  glShaderSource(id, 1, &shader_string_const, NULL);
+  glCompileShader(id);
+
+  GLint status;
+  glGetShaderiv(id, GL_COMPILE_STATUS, &status);
+
+  if (status != GL_TRUE)
+  {
+    char buffer[512];
+    if (type == GL_VERTEX_SHADER)
+      cerr << "Vertex shader:" << endl;
+    else if (type == GL_FRAGMENT_SHADER)
+      cerr << "Fragment shader:" << endl;
+    else if (type == GL_GEOMETRY_SHADER)
+      cerr << "Geometry shader:" << endl;
+    cerr << shader_string << endl << endl;
+    glGetShaderInfoLog(id, 512, NULL, buffer);
+    cerr << "Error: " << endl << buffer << endl;
+    return (GLuint) 0;
+  }
+
+  return id;
+}

+ 98 - 0
include/igl/opengl/Shader.h

@@ -0,0 +1,98 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Wenzel Jacob <wenzel@inf.ethz.ch>
+//
+// 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_OPENGL_SHADER_H
+#define IGL_OPENGL_SHADER_H
+
+#include <igl/igl_inline.h>
+#include <string>
+#include <Eigen/Core>
+
+#ifdef _WIN32
+#  include <windows.h>
+#  undef max
+#  undef min
+#  undef DrawText
+#endif
+
+#ifndef __APPLE__
+#  define GLEW_STATIC
+#  include <GL/glew.h>
+#endif
+
+#ifdef __APPLE__
+#   include <OpenGL/gl3.h>
+#   define __gl_h_ /* Prevent inclusion of the old gl.h */
+#else
+#   include <GL/gl.h>
+#endif
+
+namespace igl
+{
+namespace opengl
+{
+
+// This class wraps an OpenGL program composed of three shaders
+// TODO: write documentation
+
+class Shader
+{
+public:
+  typedef unsigned int GLuint;
+  typedef int GLint;
+
+  GLuint vertex_shader;
+  GLuint fragment_shader;
+  GLuint geometry_shader;
+  GLuint program_shader;
+
+  IGL_INLINE Shader() : vertex_shader(0), fragment_shader(0),
+    geometry_shader(0), program_shader(0) { }
+
+  // Create a new shader from the specified source strings
+  IGL_INLINE bool init(const std::string &vertex_shader_string,
+    const std::string &fragment_shader_string,
+    const std::string &fragment_data_name,
+    const std::string &geometry_shader_string = "",
+    int geometry_shader_max_vertices = 3);
+
+  // Create a new shader from the specified files on disk
+  IGL_INLINE bool init_from_files(const std::string &vertex_shader_filename,
+    const std::string &fragment_shader_filename,
+    const std::string &fragment_data_name,
+    const std::string &geometry_shader_filename = "",
+    int geometry_shader_max_vertices = 3);
+
+  // Select this shader for subsequent draw calls
+  IGL_INLINE void bind();
+
+  // Release all OpenGL objects
+  IGL_INLINE void free();
+
+  // Return the OpenGL handle of a named shader attribute (-1 if it does not exist)
+  IGL_INLINE GLint attrib(const std::string &name) const;
+
+  // Return the OpenGL handle of a uniform attribute (-1 if it does not exist)
+  IGL_INLINE GLint uniform(const std::string &name) const;
+
+  // Bind a per-vertex array attribute and refresh its contents from an Eigen amtrix
+  IGL_INLINE GLint bindVertexAttribArray(const std::string &name, GLuint bufferID,
+    const Eigen::MatrixXf &M, bool refresh) const;
+
+  IGL_INLINE GLuint create_shader_helper(GLint type, const std::string &shader_string);
+
+};
+
+}
+}
+
+#ifndef IGL_STATIC_LIBRARY
+#  include "Shader.cpp"
+#endif
+
+#endif

+ 488 - 0
include/igl/opengl/State.cpp

@@ -0,0 +1,488 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@gmail.com>
+//
+// 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 "State.h"
+#include "../ViewerData.h"
+
+IGL_INLINE void igl::opengl::State::init_buffers()
+{
+  // Mesh: Vertex Array Object & Buffer objects
+  glGenVertexArrays(1, &vao_mesh);
+  glBindVertexArray(vao_mesh);
+  glGenBuffers(1, &vbo_V);
+  glGenBuffers(1, &vbo_V_normals);
+  glGenBuffers(1, &vbo_V_ambient);
+  glGenBuffers(1, &vbo_V_diffuse);
+  glGenBuffers(1, &vbo_V_specular);
+  glGenBuffers(1, &vbo_V_uv);
+  glGenBuffers(1, &vbo_F);
+  glGenTextures(1, &vbo_tex);
+
+  // Line overlay
+  glGenVertexArrays(1, &vao_overlay_lines);
+  glBindVertexArray(vao_overlay_lines);
+  glGenBuffers(1, &vbo_lines_F);
+  glGenBuffers(1, &vbo_lines_V);
+  glGenBuffers(1, &vbo_lines_V_colors);
+
+  // Point overlay
+  glGenVertexArrays(1, &vao_overlay_points);
+  glBindVertexArray(vao_overlay_points);
+  glGenBuffers(1, &vbo_points_F);
+  glGenBuffers(1, &vbo_points_V);
+  glGenBuffers(1, &vbo_points_V_colors);
+
+  dirty = ViewerData::DIRTY_ALL;
+}
+
+IGL_INLINE void igl::opengl::State::free_buffers()
+{
+  glDeleteVertexArrays(1, &vao_mesh);
+  glDeleteVertexArrays(1, &vao_overlay_lines);
+  glDeleteVertexArrays(1, &vao_overlay_points);
+
+  glDeleteBuffers(1, &vbo_V);
+  glDeleteBuffers(1, &vbo_V_normals);
+  glDeleteBuffers(1, &vbo_V_ambient);
+  glDeleteBuffers(1, &vbo_V_diffuse);
+  glDeleteBuffers(1, &vbo_V_specular);
+  glDeleteBuffers(1, &vbo_V_uv);
+  glDeleteBuffers(1, &vbo_F);
+  glDeleteBuffers(1, &vbo_lines_F);
+  glDeleteBuffers(1, &vbo_lines_V);
+  glDeleteBuffers(1, &vbo_lines_V_colors);
+  glDeleteBuffers(1, &vbo_points_F);
+  glDeleteBuffers(1, &vbo_points_V);
+  glDeleteBuffers(1, &vbo_points_V_colors);
+
+  glDeleteTextures(1, &vbo_tex);
+}
+
+IGL_INLINE void igl::opengl::State::set_data(const igl::ViewerData &data, bool invert_normals)
+{
+  bool per_corner_uv = (data.F_uv.rows() == data.F.rows());
+  bool per_corner_normals = (data.F_normals.rows() == 3 * data.F.rows());
+
+  dirty |= data.dirty;
+
+  if (!data.face_based)
+  {
+    if (!per_corner_uv)
+    {
+      // Vertex positions
+      if (dirty & ViewerData::DIRTY_POSITION)
+        V_vbo = (data.V.transpose()).cast<float>();
+
+      // Vertex normals
+      if (dirty & ViewerData::DIRTY_NORMAL)
+      {
+        V_normals_vbo = (data.V_normals.transpose()).cast<float>();
+        if (invert_normals)
+          V_normals_vbo = -V_normals_vbo;
+      }
+
+      // Per-vertex material settings
+      if (dirty & ViewerData::DIRTY_AMBIENT)
+        V_ambient_vbo = (data.V_material_ambient.transpose()).cast<float>();
+      if (dirty & ViewerData::DIRTY_DIFFUSE)
+        V_diffuse_vbo = (data.V_material_diffuse.transpose()).cast<float>();
+      if (dirty & ViewerData::DIRTY_SPECULAR)
+        V_specular_vbo = (data.V_material_specular.transpose()).cast<float>();
+
+      // Face indices
+      if (dirty & ViewerData::DIRTY_FACE)
+        F_vbo = (data.F.transpose()).cast<unsigned>();
+
+      // Texture coordinates
+      if (dirty & ViewerData::DIRTY_UV)
+        V_uv_vbo = (data.V_uv.transpose()).cast<float>();
+    }
+    else
+    {
+      // Per vertex properties with per corner UVs
+      if (dirty & ViewerData::DIRTY_POSITION)
+      {
+        V_vbo.resize(3,data.F.rows()*3);
+        for (unsigned i=0; i<data.F.rows();++i)
+          for (unsigned j=0;j<3;++j)
+            V_vbo.col(i*3+j) = data.V.row(data.F(i,j)).transpose().cast<float>();
+      }
+
+      if (dirty & ViewerData::DIRTY_AMBIENT)
+      {
+        V_ambient_vbo.resize(3,data.F.rows()*3);
+        for (unsigned i=0; i<data.F.rows();++i)
+          for (unsigned j=0;j<3;++j)
+            V_ambient_vbo.col (i*3+j) = data.V_material_ambient.row(data.F(i,j)).transpose().cast<float>();
+      }
+
+      if (dirty & ViewerData::DIRTY_DIFFUSE)
+      {
+        V_diffuse_vbo.resize(3,data.F.rows()*3);
+        for (unsigned i=0; i<data.F.rows();++i)
+          for (unsigned j=0;j<3;++j)
+            V_diffuse_vbo.col (i*3+j) = data.V_material_diffuse.row(data.F(i,j)).transpose().cast<float>();
+      }
+
+      if (dirty & ViewerData::DIRTY_SPECULAR)
+      {
+        V_specular_vbo.resize(3,data.F.rows()*3);
+        for (unsigned i=0; i<data.F.rows();++i)
+          for (unsigned j=0;j<3;++j)
+            V_specular_vbo.col(i*3+j) = data.V_material_specular.row(data.F(i,j)).transpose().cast<float>();
+      }
+
+      if (dirty & ViewerData::DIRTY_NORMAL)
+      {
+        V_normals_vbo.resize(3,data.F.rows()*3);
+        for (unsigned i=0; i<data.F.rows();++i)
+          for (unsigned j=0;j<3;++j)
+            V_normals_vbo.col (i*3+j) = data.V_normals.row(data.F(i,j)).transpose().cast<float>();
+
+        if (invert_normals)
+          V_normals_vbo = -V_normals_vbo;
+      }
+
+      if (dirty & ViewerData::DIRTY_FACE)
+      {
+        F_vbo.resize(3,data.F.rows());
+        for (unsigned i=0; i<data.F.rows();++i)
+          F_vbo.col(i) << i*3+0, i*3+1, i*3+2;
+      }
+
+      if (dirty & ViewerData::DIRTY_UV)
+      {
+        V_uv_vbo.resize(2,data.F.rows()*3);
+        for (unsigned i=0; i<data.F.rows();++i)
+          for (unsigned j=0;j<3;++j)
+            V_uv_vbo.col(i*3+j) = data.V_uv.row(data.F(i,j)).transpose().cast<float>();
+      }
+    }
+  }
+  else
+  {
+    if (dirty & ViewerData::DIRTY_POSITION)
+    {
+      V_vbo.resize(3,data.F.rows()*3);
+      for (unsigned i=0; i<data.F.rows();++i)
+        for (unsigned j=0;j<3;++j)
+          V_vbo.col(i*3+j) = data.V.row(data.F(i,j)).transpose().cast<float>();
+    }
+
+    if (dirty & ViewerData::DIRTY_AMBIENT)
+    {
+      V_ambient_vbo.resize(4,data.F.rows()*3);
+      for (unsigned i=0; i<data.F.rows();++i)
+        for (unsigned j=0;j<3;++j)
+          V_ambient_vbo.col (i*3+j) = data.F_material_ambient.row(i).transpose().cast<float>();
+    }
+
+    if (dirty & ViewerData::DIRTY_DIFFUSE)
+    {
+      V_diffuse_vbo.resize(4,data.F.rows()*3);
+      for (unsigned i=0; i<data.F.rows();++i)
+        for (unsigned j=0;j<3;++j)
+          V_diffuse_vbo.col (i*3+j) = data.F_material_diffuse.row(i).transpose().cast<float>();
+    }
+
+    if (dirty & ViewerData::DIRTY_SPECULAR)
+    {
+      V_specular_vbo.resize(4,data.F.rows()*3);
+      for (unsigned i=0; i<data.F.rows();++i)
+        for (unsigned j=0;j<3;++j)
+          V_specular_vbo.col(i*3+j) = data.F_material_specular.row(i).transpose().cast<float>();
+    }
+
+    if (dirty & ViewerData::DIRTY_NORMAL)
+    {
+      V_normals_vbo.resize(3,data.F.rows()*3);
+      for (unsigned i=0; i<data.F.rows();++i)
+        for (unsigned j=0;j<3;++j)
+          V_normals_vbo.col (i*3+j) =
+             per_corner_normals ?
+               data.F_normals.row(i*3+j).transpose().cast<float>() :
+               data.F_normals.row(i).transpose().cast<float>();
+
+      if (invert_normals)
+        V_normals_vbo = -V_normals_vbo;
+    }
+
+    if (dirty & ViewerData::DIRTY_FACE)
+    {
+      F_vbo.resize(3,data.F.rows());
+      for (unsigned i=0; i<data.F.rows();++i)
+        F_vbo.col(i) << i*3+0, i*3+1, i*3+2;
+    }
+
+    if (dirty & ViewerData::DIRTY_UV)
+    {
+        V_uv_vbo.resize(2,data.F.rows()*3);
+        for (unsigned i=0; i<data.F.rows();++i)
+          for (unsigned j=0;j<3;++j)
+            V_uv_vbo.col(i*3+j) = data.V_uv.row(per_corner_uv ? data.F_uv(i,j) : data.F(i,j)).transpose().cast<float>();
+    }
+  }
+
+  if (dirty & ViewerData::DIRTY_TEXTURE)
+  {
+    tex_u = data.texture_R.rows();
+    tex_v = data.texture_R.cols();
+    tex.resize(data.texture_R.size()*4);
+    for (unsigned i=0;i<data.texture_R.size();++i)
+    {
+      tex(i*4+0) = data.texture_R(i);
+      tex(i*4+1) = data.texture_G(i);
+      tex(i*4+2) = data.texture_B(i);
+      tex(i*4+3) = data.texture_A(i);
+    }
+  }
+
+  if (dirty & ViewerData::DIRTY_OVERLAY_LINES)
+  {
+    lines_V_vbo.resize(3, data.lines.rows()*2);
+    lines_V_colors_vbo.resize(3, data.lines.rows()*2);
+    lines_F_vbo.resize(1, data.lines.rows()*2);
+    for (unsigned i=0; i<data.lines.rows();++i)
+    {
+      lines_V_vbo.col(2*i+0) = data.lines.block<1, 3>(i, 0).transpose().cast<float>();
+      lines_V_vbo.col(2*i+1) = data.lines.block<1, 3>(i, 3).transpose().cast<float>();
+      lines_V_colors_vbo.col(2*i+0) = data.lines.block<1, 3>(i, 6).transpose().cast<float>();
+      lines_V_colors_vbo.col(2*i+1) = data.lines.block<1, 3>(i, 6).transpose().cast<float>();
+      lines_F_vbo(2*i+0) = 2*i+0;
+      lines_F_vbo(2*i+1) = 2*i+1;
+    }
+  }
+
+  if (dirty & ViewerData::DIRTY_OVERLAY_POINTS)
+  {
+    points_V_vbo.resize(3, data.points.rows());
+    points_V_colors_vbo.resize(3, data.points.rows());
+    points_F_vbo.resize(1, data.points.rows());
+    for (unsigned i=0; i<data.points.rows();++i)
+    {
+      points_V_vbo.col(i) = data.points.block<1, 3>(i, 0).transpose().cast<float>();
+      points_V_colors_vbo.col(i) = data.points.block<1, 3>(i, 3).transpose().cast<float>();
+      points_F_vbo(i) = i;
+    }
+  }
+}
+
+IGL_INLINE void igl::opengl::State::bind_mesh()
+{
+  glBindVertexArray(vao_mesh);
+  shader_mesh.bind();
+  shader_mesh.bindVertexAttribArray("position", vbo_V, V_vbo, dirty & ViewerData::DIRTY_POSITION);
+  shader_mesh.bindVertexAttribArray("normal", vbo_V_normals, V_normals_vbo, dirty & ViewerData::DIRTY_NORMAL);
+  shader_mesh.bindVertexAttribArray("Ka", vbo_V_ambient, V_ambient_vbo, dirty & ViewerData::DIRTY_AMBIENT);
+  shader_mesh.bindVertexAttribArray("Kd", vbo_V_diffuse, V_diffuse_vbo, dirty & ViewerData::DIRTY_DIFFUSE);
+  shader_mesh.bindVertexAttribArray("Ks", vbo_V_specular, V_specular_vbo, dirty & ViewerData::DIRTY_SPECULAR);
+  shader_mesh.bindVertexAttribArray("texcoord", vbo_V_uv, V_uv_vbo, dirty & ViewerData::DIRTY_UV);
+
+  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_F);
+  if (dirty & ViewerData::DIRTY_FACE)
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned)*F_vbo.size(), F_vbo.data(), GL_DYNAMIC_DRAW);
+
+  glActiveTexture(GL_TEXTURE0);
+  glBindTexture(GL_TEXTURE_2D, vbo_tex);
+  if (dirty & ViewerData::DIRTY_TEXTURE)
+  {
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_u, tex_v, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.data());
+  }
+  glUniform1i(shader_mesh.uniform("tex"), 0);
+  dirty &= ~ViewerData::DIRTY_MESH;
+}
+
+IGL_INLINE void igl::opengl::State::bind_overlay_lines()
+{
+  bool is_dirty = dirty & ViewerData::DIRTY_OVERLAY_LINES;
+
+  glBindVertexArray(vao_overlay_lines);
+  shader_overlay_lines.bind();
+  shader_overlay_lines.bindVertexAttribArray("position", vbo_lines_V, lines_V_vbo, is_dirty);
+  shader_overlay_lines.bindVertexAttribArray("color", vbo_lines_V_colors, lines_V_colors_vbo, is_dirty);
+
+  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_lines_F);
+  if (is_dirty)
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned)*lines_F_vbo.size(), lines_F_vbo.data(), GL_DYNAMIC_DRAW);
+
+  dirty &= ~ViewerData::DIRTY_OVERLAY_LINES;
+}
+
+IGL_INLINE void igl::opengl::State::bind_overlay_points()
+{
+  bool is_dirty = dirty & ViewerData::DIRTY_OVERLAY_POINTS;
+
+  glBindVertexArray(vao_overlay_points);
+  shader_overlay_points.bind();
+  shader_overlay_points.bindVertexAttribArray("position", vbo_points_V, points_V_vbo, is_dirty);
+  shader_overlay_points.bindVertexAttribArray("color", vbo_points_V_colors, points_V_colors_vbo, is_dirty);
+
+  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_points_F);
+  if (is_dirty)
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned)*points_F_vbo.size(), points_F_vbo.data(), GL_DYNAMIC_DRAW);
+
+  dirty &= ~ViewerData::DIRTY_OVERLAY_POINTS;
+}
+
+IGL_INLINE void igl::opengl::State::draw_mesh(bool solid)
+{
+  glPolygonMode(GL_FRONT_AND_BACK, solid ? GL_FILL : GL_LINE);
+
+  /* Avoid Z-buffer fighting between filled triangles & wireframe lines */
+  if (solid)
+  {
+    glEnable(GL_POLYGON_OFFSET_FILL);
+    glPolygonOffset(1.0, 1.0);
+  }
+  glDrawElements(GL_TRIANGLES, 3*F_vbo.cols(), GL_UNSIGNED_INT, 0);
+
+  glDisable(GL_POLYGON_OFFSET_FILL);
+  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+}
+
+IGL_INLINE void igl::opengl::State::draw_overlay_lines()
+{
+  glDrawElements(GL_LINES, lines_F_vbo.cols(), GL_UNSIGNED_INT, 0);
+}
+
+IGL_INLINE void igl::opengl::State::draw_overlay_points()
+{
+  glDrawElements(GL_POINTS, points_F_vbo.cols(), GL_UNSIGNED_INT, 0);
+}
+
+IGL_INLINE void igl::opengl::State::init()
+{
+  std::string mesh_vertex_shader_string =
+  "#version 150\n"
+  "uniform mat4 model;"
+  "uniform mat4 view;"
+  "uniform mat4 proj;"
+  "in vec3 position;"
+  "in vec3 normal;"
+  "out vec3 position_eye;"
+  "out vec3 normal_eye;"
+  "in vec4 Ka;"
+  "in vec4 Kd;"
+  "in vec4 Ks;"
+  "in vec2 texcoord;"
+  "out vec2 texcoordi;"
+  "out vec4 Kai;"
+  "out vec4 Kdi;"
+  "out vec4 Ksi;"
+
+  "void main()"
+  "{"
+  "  position_eye = vec3 (view * model * vec4 (position, 1.0));"
+  "  normal_eye = vec3 (view * model * vec4 (normal, 0.0));"
+  "  normal_eye = normalize(normal_eye);"
+  "  gl_Position = proj * vec4 (position_eye, 1.0);" //proj * view * model * vec4(position, 1.0);"
+  "  Kai = Ka;"
+  "  Kdi = Kd;"
+  "  Ksi = Ks;"
+  "  texcoordi = texcoord;"
+  "}";
+
+  std::string mesh_fragment_shader_string =
+  "#version 150\n"
+  "uniform mat4 model;"
+  "uniform mat4 view;"
+  "uniform mat4 proj;"
+  "uniform vec4 fixed_color;"
+  "in vec3 position_eye;"
+  "in vec3 normal_eye;"
+  "uniform vec3 light_position_world;"
+  "vec3 Ls = vec3 (1, 1, 1);"
+  "vec3 Ld = vec3 (1, 1, 1);"
+  "vec3 La = vec3 (1, 1, 1);"
+  "in vec4 Ksi;"
+  "in vec4 Kdi;"
+  "in vec4 Kai;"
+  "in vec2 texcoordi;"
+  "uniform sampler2D tex;"
+  "uniform float specular_exponent;"
+  "uniform float lighting_factor;"
+  "uniform float texture_factor;"
+  "out vec4 outColor;"
+  "void main()"
+  "{"
+  "vec3 Ia = La * vec3(Kai);"    // ambient intensity
+
+  "vec3 light_position_eye = vec3 (view * vec4 (light_position_world, 1.0));"
+  "vec3 vector_to_light_eye = light_position_eye - position_eye;"
+  "vec3 direction_to_light_eye = normalize (vector_to_light_eye);"
+  "float dot_prod = dot (direction_to_light_eye, normal_eye);"
+  "float clamped_dot_prod = max (dot_prod, 0.0);"
+  "vec3 Id = Ld * vec3(Kdi) * clamped_dot_prod;"    // Diffuse intensity
+
+  "vec3 reflection_eye = reflect (-direction_to_light_eye, normal_eye);"
+  "vec3 surface_to_viewer_eye = normalize (-position_eye);"
+  "float dot_prod_specular = dot (reflection_eye, surface_to_viewer_eye);"
+  "dot_prod_specular = float(abs(dot_prod)==dot_prod) * max (dot_prod_specular, 0.0);"
+  "float specular_factor = pow (dot_prod_specular, specular_exponent);"
+  "vec3 Is = Ls * vec3(Ksi) * specular_factor;"    // specular intensity
+  "vec4 color = vec4(lighting_factor * (Is + Id) + Ia + (1.0-lighting_factor) * vec3(Kdi),(Kai.a+Ksi.a+Kdi.a)/3);"
+  "outColor = mix(vec4(1,1,1,1), texture(tex, texcoordi), texture_factor) * color;"
+  "if (fixed_color != vec4(0.0)) outColor = fixed_color;"
+  "}";
+
+  std::string overlay_vertex_shader_string =
+  "#version 150\n"
+  "uniform mat4 model;"
+  "uniform mat4 view;"
+  "uniform mat4 proj;"
+  "in vec3 position;"
+  "in vec3 color;"
+  "out vec3 color_frag;"
+
+  "void main()"
+  "{"
+  "  gl_Position = proj * view * model * vec4 (position, 1.0);"
+  "  color_frag = color;"
+  "}";
+
+  std::string overlay_fragment_shader_string =
+  "#version 150\n"
+  "in vec3 color_frag;"
+  "out vec4 outColor;"
+  "void main()"
+  "{"
+  "  outColor = vec4(color_frag, 1.0);"
+  "}";
+
+  std::string overlay_point_fragment_shader_string =
+  "#version 150\n"
+  "in vec3 color_frag;"
+  "out vec4 outColor;"
+  "void main()"
+  "{"
+  "  if (length(gl_PointCoord - vec2(0.5)) > 0.5)"
+  "    discard;"
+  "  outColor = vec4(color_frag, 1.0);"
+  "}";
+
+  init_buffers();
+  shader_mesh.init(mesh_vertex_shader_string,
+      mesh_fragment_shader_string, "outColor");
+  shader_overlay_lines.init(overlay_vertex_shader_string,
+      overlay_fragment_shader_string, "outColor");
+  shader_overlay_points.init(overlay_vertex_shader_string,
+      overlay_point_fragment_shader_string, "outColor");
+}
+
+IGL_INLINE void igl::opengl::State::free()
+{
+  shader_mesh.free();
+  shader_overlay_lines.free();
+  shader_overlay_points.free();
+  free_buffers();
+}

+ 122 - 0
include/igl/opengl/State.h

@@ -0,0 +1,122 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@gmail.com>
+//
+// 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_OPENGL_STATE_H
+#define IGL_OPENGL_STATE_H
+
+// Coverts mesh data inside a igl::ViewerData class in an OpenGL
+// compatible format The class includes a shader and the opengl calls to plot
+// the data
+
+// Alec: not sure why this is call "State". It seems this is a drawable mesh
+// equipt with its own shaders and dirty flags. "Mesh" would even be more
+// appropriate.
+
+#include <igl/igl_inline.h>
+#include "Shader.h"
+#include "../ViewerData.h"
+
+namespace igl
+{
+namespace opengl
+{
+
+class State
+{
+public:
+  typedef unsigned int GLuint;
+
+  GLuint vao_mesh;
+  GLuint vao_overlay_lines;
+  GLuint vao_overlay_points;
+  Shader shader_mesh;
+  Shader shader_overlay_lines;
+  Shader shader_overlay_points;
+
+  GLuint vbo_V; // Vertices of the current mesh (#V x 3)
+  GLuint vbo_V_uv; // UV coordinates for the current mesh (#V x 2)
+  GLuint vbo_V_normals; // Vertices of the current mesh (#V x 3)
+  GLuint vbo_V_ambient; // Ambient material  (#V x 3)
+  GLuint vbo_V_diffuse; // Diffuse material  (#V x 3)
+  GLuint vbo_V_specular; // Specular material  (#V x 3)
+
+  GLuint vbo_F; // Faces of the mesh (#F x 3)
+  GLuint vbo_tex; // Texture
+
+  GLuint vbo_lines_F;         // Indices of the line overlay
+  GLuint vbo_lines_V;         // Vertices of the line overlay
+  GLuint vbo_lines_V_colors;  // Color values of the line overlay
+  GLuint vbo_points_F;        // Indices of the point overlay
+  GLuint vbo_points_V;        // Vertices of the point overlay
+  GLuint vbo_points_V_colors; // Color values of the point overlay
+
+  // Temporary copy of the content of each VBO
+  Eigen::MatrixXf V_vbo;
+  Eigen::MatrixXf V_normals_vbo;
+  Eigen::MatrixXf V_ambient_vbo;
+  Eigen::MatrixXf V_diffuse_vbo;
+  Eigen::MatrixXf V_specular_vbo;
+  Eigen::MatrixXf V_uv_vbo;
+  Eigen::MatrixXf lines_V_vbo;
+  Eigen::MatrixXf lines_V_colors_vbo;
+  Eigen::MatrixXf points_V_vbo;
+  Eigen::MatrixXf points_V_colors_vbo;
+
+  int tex_u;
+  int tex_v;
+  Eigen::Matrix<char,Eigen::Dynamic,1> tex;
+
+  Eigen::Matrix<unsigned, Eigen::Dynamic, Eigen::Dynamic> F_vbo;
+  Eigen::Matrix<unsigned, Eigen::Dynamic, Eigen::Dynamic> lines_F_vbo;
+  Eigen::Matrix<unsigned, Eigen::Dynamic, Eigen::Dynamic> points_F_vbo;
+
+  // Marks dirty buffers that need to be uploaded to OpenGL
+  uint32_t dirty;
+
+  // Initialize shaders and buffers
+  IGL_INLINE void init();
+
+  // Release all resources
+  IGL_INLINE void free();
+
+  // Create a new set of OpenGL buffer objects
+  IGL_INLINE void init_buffers();
+
+  // Update contents from a 'Data' instance
+  IGL_INLINE void set_data(const igl::ViewerData &data, bool invert_normals);
+
+  // Bind the underlying OpenGL buffer objects for subsequent mesh draw calls
+  IGL_INLINE void bind_mesh();
+
+  /// Draw the currently buffered mesh (either solid or wireframe)
+  IGL_INLINE void draw_mesh(bool solid);
+
+  // Bind the underlying OpenGL buffer objects for subsequent line overlay draw calls
+  IGL_INLINE void bind_overlay_lines();
+
+  /// Draw the currently buffered line overlay
+  IGL_INLINE void draw_overlay_lines();
+
+  // Bind the underlying OpenGL buffer objects for subsequent point overlay draw calls
+  IGL_INLINE void bind_overlay_points();
+
+  /// Draw the currently buffered point overlay
+  IGL_INLINE void draw_overlay_points();
+
+  // Release the OpenGL buffer objects
+  IGL_INLINE void free_buffers();
+
+};
+
+}
+}
+
+#ifndef IGL_STATIC_LIBRARY
+#  include "State.cpp"
+#endif
+
+#endif

+ 449 - 0
include/igl/opengl/ViewerCore.cpp

@@ -0,0 +1,449 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@gmail.com>
+//
+// 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 "ViewerCore.h"
+#include <igl/quat_to_mat.h>
+#include <igl/snap_to_fixed_up.h>
+#include <igl/look_at.h>
+#include <igl/frustum.h>
+#include <igl/ortho.h>
+#include <igl/massmatrix.h>
+#include <igl/barycenter.h>
+#include <Eigen/Geometry>
+#include <iostream>
+
+IGL_INLINE void igl::opengl::ViewerCore::align_camera_center(
+  const Eigen::MatrixXd& V,
+  const Eigen::MatrixXi& F)
+{
+  if(V.rows() == 0)
+    return;
+
+  get_scale_and_shift_to_fit_mesh(V,F,model_zoom,model_translation);
+  // Rather than crash on empty mesh...
+  if(V.size() > 0)
+  {
+    object_scale = (V.colwise().maxCoeff() - V.colwise().minCoeff()).norm();
+  }
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::get_scale_and_shift_to_fit_mesh(
+  const Eigen::MatrixXd& V,
+  const Eigen::MatrixXi& F,
+  float& zoom,
+  Eigen::Vector3f& shift)
+{
+  if (V.rows() == 0)
+    return;
+
+  Eigen::MatrixXd BC;
+  if (F.rows() <= 1)
+  {
+    BC = V;
+  } else
+  {
+    igl::barycenter(V,F,BC);
+  }
+  return get_scale_and_shift_to_fit_mesh(BC,zoom,shift);
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::align_camera_center(
+  const Eigen::MatrixXd& V)
+{
+  if(V.rows() == 0)
+    return;
+
+  get_scale_and_shift_to_fit_mesh(V,model_zoom,model_translation);
+  // Rather than crash on empty mesh...
+  if(V.size() > 0)
+  {
+    object_scale = (V.colwise().maxCoeff() - V.colwise().minCoeff()).norm();
+  }
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::get_scale_and_shift_to_fit_mesh(
+  const Eigen::MatrixXd& V,
+  float& zoom,
+  Eigen::Vector3f& shift)
+{
+  if (V.rows() == 0)
+    return;
+
+  auto min_point = V.colwise().minCoeff();
+  auto max_point = V.colwise().maxCoeff();
+  auto centroid  = (0.5*(min_point + max_point)).eval();
+  shift.setConstant(0);
+  shift.head(centroid.size()) = -centroid.cast<float>();
+  zoom = 2.0 / (max_point-min_point).array().abs().maxCoeff();
+}
+
+
+IGL_INLINE void igl::opengl::ViewerCore::clear_framebuffers()
+{
+  glClearColor(background_color[0],
+               background_color[1],
+               background_color[2],
+               1.0f);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::draw(
+  ViewerData& data,
+  State& opengl,
+  bool update_matrices)
+{
+  using namespace std;
+  using namespace Eigen;
+
+  if (depth_test)
+    glEnable(GL_DEPTH_TEST);
+  else
+    glDisable(GL_DEPTH_TEST);
+
+  glEnable(GL_BLEND);
+  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+  /* Bind and potentially refresh mesh/line/point data */
+  if (data.dirty)
+  {
+    opengl.set_data(data, invert_normals);
+    data.dirty = ViewerData::DIRTY_NONE;
+  }
+  opengl.bind_mesh();
+
+  // Initialize uniform
+  glViewport(viewport(0), viewport(1), viewport(2), viewport(3));
+
+  if(update_matrices)
+  {
+    model = Eigen::Matrix4f::Identity();
+    view  = Eigen::Matrix4f::Identity();
+    proj  = Eigen::Matrix4f::Identity();
+
+    // Set view
+    look_at( camera_eye, camera_center, camera_up, view);
+
+    float width  = viewport(2);
+    float height = viewport(3);
+
+    // Set projection
+    if (orthographic)
+    {
+      float length = (camera_eye - camera_center).norm();
+      float h = tan(camera_view_angle/360.0 * M_PI) * (length);
+      ortho(-h*width/height, h*width/height, -h, h, camera_dnear, camera_dfar,proj);
+    }
+    else
+    {
+      float fH = tan(camera_view_angle / 360.0 * M_PI) * camera_dnear;
+      float fW = fH * (double)width/(double)height;
+      frustum(-fW, fW, -fH, fH, camera_dnear, camera_dfar,proj);
+    }
+    // end projection
+
+    // Set model transformation
+    float mat[16];
+    igl::quat_to_mat(trackball_angle.coeffs().data(), mat);
+
+    for (unsigned i=0;i<4;++i)
+      for (unsigned j=0;j<4;++j)
+        model(i,j) = mat[i+4*j];
+
+    // Why not just use Eigen::Transform<double,3,Projective> for model...?
+    model.topLeftCorner(3,3)*=camera_zoom;
+    model.topLeftCorner(3,3)*=model_zoom;
+    model.col(3).head(3) += model.topLeftCorner(3,3)*model_translation;
+  }
+
+  // Send transformations to the GPU
+  GLint modeli = opengl.shader_mesh.uniform("model");
+  GLint viewi  = opengl.shader_mesh.uniform("view");
+  GLint proji  = opengl.shader_mesh.uniform("proj");
+  glUniformMatrix4fv(modeli, 1, GL_FALSE, model.data());
+  glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data());
+  glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data());
+
+  // Light parameters
+  GLint specular_exponenti    = opengl.shader_mesh.uniform("specular_exponent");
+  GLint light_position_worldi = opengl.shader_mesh.uniform("light_position_world");
+  GLint lighting_factori      = opengl.shader_mesh.uniform("lighting_factor");
+  GLint fixed_colori          = opengl.shader_mesh.uniform("fixed_color");
+  GLint texture_factori       = opengl.shader_mesh.uniform("texture_factor");
+
+  glUniform1f(specular_exponenti, shininess);
+  Vector3f rev_light = -1.*light_position;
+  glUniform3fv(light_position_worldi, 1, rev_light.data());
+  glUniform1f(lighting_factori, lighting_factor); // enables lighting
+  glUniform4f(fixed_colori, 0.0, 0.0, 0.0, 0.0);
+
+  if (data.V.rows()>0)
+  {
+    // Render fill
+    if (show_faces)
+    {
+      // Texture
+      glUniform1f(texture_factori, show_texture ? 1.0f : 0.0f);
+      opengl.draw_mesh(true);
+      glUniform1f(texture_factori, 0.0f);
+    }
+
+    // Render wireframe
+    if (show_lines)
+    {
+      glLineWidth(line_width);
+      glUniform4f(fixed_colori, line_color[0], line_color[1],
+        line_color[2], 1.0f);
+      opengl.draw_mesh(false);
+      glUniform4f(fixed_colori, 0.0f, 0.0f, 0.0f, 0.0f);
+    }
+
+#ifdef IGL_VIEWER_WITH_NANOGUI
+    if (show_vertid)
+    {
+      textrenderer.BeginDraw(view*model, proj, viewport, object_scale);
+      for (int i=0; i<data.V.rows(); ++i)
+        textrenderer.DrawText(data.V.row(i),data.V_normals.row(i),to_string(i));
+      textrenderer.EndDraw();
+    }
+
+    if (show_faceid)
+    {
+      textrenderer.BeginDraw(view*model, proj, viewport, object_scale);
+
+      for (int i=0; i<data.F.rows(); ++i)
+      {
+        Eigen::RowVector3d p = Eigen::RowVector3d::Zero();
+        for (int j=0;j<data.F.cols();++j)
+          p += data.V.row(data.F(i,j));
+        p /= data.F.cols();
+
+        textrenderer.DrawText(p, data.F_normals.row(i), to_string(i));
+      }
+      textrenderer.EndDraw();
+    }
+#endif
+  }
+
+  if (show_overlay)
+  {
+    if (show_overlay_depth)
+      glEnable(GL_DEPTH_TEST);
+    else
+      glDisable(GL_DEPTH_TEST);
+
+    if (data.lines.rows() > 0)
+    {
+      opengl.bind_overlay_lines();
+      modeli = opengl.shader_overlay_lines.uniform("model");
+      viewi  = opengl.shader_overlay_lines.uniform("view");
+      proji  = opengl.shader_overlay_lines.uniform("proj");
+
+      glUniformMatrix4fv(modeli, 1, GL_FALSE, model.data());
+      glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data());
+      glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data());
+      // This must be enabled, otherwise glLineWidth has no effect
+      glEnable(GL_LINE_SMOOTH);
+      glLineWidth(line_width);
+
+      opengl.draw_overlay_lines();
+    }
+
+    if (data.points.rows() > 0)
+    {
+      opengl.bind_overlay_points();
+      modeli = opengl.shader_overlay_points.uniform("model");
+      viewi  = opengl.shader_overlay_points.uniform("view");
+      proji  = opengl.shader_overlay_points.uniform("proj");
+
+      glUniformMatrix4fv(modeli, 1, GL_FALSE, model.data());
+      glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data());
+      glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data());
+      glPointSize(point_size);
+
+      opengl.draw_overlay_points();
+    }
+
+#ifdef IGL_VIEWER_WITH_NANOGUI
+    if (data.labels_positions.rows() > 0)
+    {
+      textrenderer.BeginDraw(view*model, proj, viewport, object_scale);
+      for (int i=0; i<data.labels_positions.rows(); ++i)
+        textrenderer.DrawText(data.labels_positions.row(i), Eigen::Vector3d(0.0,0.0,0.0),
+            data.labels_strings[i]);
+      textrenderer.EndDraw();
+    }
+#endif
+
+    glEnable(GL_DEPTH_TEST);
+  }
+
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::draw_buffer(ViewerData& data,
+  State& opengl,
+  bool update_matrices,
+  Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& R,
+  Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& G,
+  Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& B,
+  Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& A)
+{
+  assert(R.rows() == G.rows() && G.rows() == B.rows() && B.rows() == A.rows());
+  assert(R.cols() == G.cols() && G.cols() == B.cols() && B.cols() == A.cols());
+
+  unsigned x = R.rows();
+  unsigned y = R.cols();
+
+  // Create frame buffer
+  GLuint frameBuffer;
+  glGenFramebuffers(1, &frameBuffer);
+  glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
+
+  // Create texture to hold color buffer
+  GLuint texColorBuffer;
+  glGenTextures(1, &texColorBuffer);
+  glBindTexture(GL_TEXTURE_2D, texColorBuffer);
+
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);
+
+  // Create Renderbuffer Object to hold depth and stencil buffers
+  GLuint rboDepthStencil;
+  glGenRenderbuffers(1, &rboDepthStencil);
+  glBindRenderbuffer(GL_RENDERBUFFER, rboDepthStencil);
+  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, x, y);
+  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rboDepthStencil);
+
+  assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+  glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
+
+  // Clear the buffer
+  glClearColor(background_color(0), background_color(1), background_color(2), 0.f);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  // Save old viewport
+  Eigen::Vector4f viewport_ori = viewport;
+  viewport << 0,0,x,y;
+
+  // Draw
+  draw(data,opengl,update_matrices);
+
+  // Restore viewport
+  viewport = viewport_ori;
+
+  // Copy back in the given Eigen matrices
+  GLubyte* pixels = (GLubyte*)calloc(x*y*4,sizeof(GLubyte));
+  glReadPixels
+  (
+   0, 0,
+   x, y,
+   GL_RGBA, GL_UNSIGNED_BYTE, pixels
+   );
+
+  int count = 0;
+  for (unsigned j=0; j<y; ++j)
+  {
+    for (unsigned i=0; i<x; ++i)
+    {
+      R(i,j) = pixels[count*4+0];
+      G(i,j) = pixels[count*4+1];
+      B(i,j) = pixels[count*4+2];
+      A(i,j) = pixels[count*4+3];
+      ++count;
+    }
+  }
+
+  // Clean up
+  free(pixels);
+  glBindFramebuffer(GL_FRAMEBUFFER, 0);
+  glDeleteRenderbuffers(1, &rboDepthStencil);
+  glDeleteTextures(1, &texColorBuffer);
+  glDeleteFramebuffers(1, &frameBuffer);
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::set_rotation_type(
+  const igl::opengl::ViewerCore::RotationType & value)
+{
+  using namespace Eigen;
+  using namespace std;
+  const RotationType old_rotation_type = rotation_type;
+  rotation_type = value;
+  if(rotation_type == ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP &&
+    old_rotation_type != ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP)
+  {
+    snap_to_fixed_up(Quaternionf(trackball_angle),trackball_angle);
+  }
+}
+
+
+IGL_INLINE igl::opengl::ViewerCore::ViewerCore()
+{
+  // Default shininess
+  shininess = 35.0f;
+
+  // Default colors
+  background_color << 0.3f, 0.3f, 0.5f, 1.0f;
+  line_color << 0.0f, 0.0f, 0.0f, 1.0f;
+
+  // Default lights settings
+  light_position << 0.0f, -0.30f, -5.0f;
+  lighting_factor = 1.0f; //on
+
+  // Default trackball
+  trackball_angle = Eigen::Quaternionf::Identity();
+  set_rotation_type(ViewerCore::ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP);
+
+  // Defalut model viewing parameters
+  model_zoom = 1.0f;
+  model_translation << 0,0,0;
+
+  // Camera parameters
+  camera_zoom = 1.0f;
+  orthographic = false;
+  camera_view_angle = 45.0;
+  camera_dnear = 1.0;
+  camera_dfar = 100.0;
+  camera_eye << 0, 0, 5;
+  camera_center << 0, 0, 0;
+  camera_up << 0, 1, 0;
+
+  // Default visualization options
+  show_faces = true;
+  show_lines = true;
+  invert_normals = false;
+  show_overlay = true;
+  show_overlay_depth = true;
+  show_vertid = false;
+  show_faceid = false;
+  show_texture = false;
+  depth_test = true;
+
+  // Default point size / line width
+  point_size = 30;
+  line_width = 0.5f;
+  is_animating = false;
+  animation_max_fps = 30.;
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::init()
+{
+#ifdef IGL_VIEWER_WITH_NANOGUI
+  textrenderer.Init();
+#endif
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::shut()
+{
+#ifdef IGL_VIEWER_WITH_NANOGUI
+  textrenderer.Shut();
+#endif
+}

+ 247 - 0
include/igl/opengl/ViewerCore.h

@@ -0,0 +1,247 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@gmail.com>
+//
+// 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_VIEWER_VIEWER_CORE_H
+#define IGL_VIEWER_VIEWER_CORE_H
+
+#ifdef IGL_VIEWER_WITH_NANOGUI
+#include <igl/viewer/TextRenderer.h>
+#endif
+#include <igl/ViewerData.h>
+#include <igl/opengl/State.h>
+
+#include <igl/igl_inline.h>
+#include <Eigen/Geometry>
+#include <Eigen/Core>
+
+namespace igl
+{
+namespace opengl
+{
+
+// Basic class of the 3D mesh viewer
+// TODO: write documentation
+
+class ViewerCore
+{
+public:
+  IGL_INLINE ViewerCore();
+
+  // Initialization
+  IGL_INLINE void init();
+
+  // Shutdown
+  IGL_INLINE void shut();
+
+  // Serialization code
+  IGL_INLINE void InitSerialization();
+
+
+  // ------------------- Camera control functions
+
+  // Adjust the view to see the entire model
+  IGL_INLINE void align_camera_center(
+    const Eigen::MatrixXd& V,
+    const Eigen::MatrixXi& F);
+
+  // Determines how much to zoom and shift such that the mesh fills the unit
+  // box (centered at the origin)
+  IGL_INLINE void get_scale_and_shift_to_fit_mesh(
+    const Eigen::MatrixXd& V,
+    const Eigen::MatrixXi& F,
+    float & zoom,
+    Eigen::Vector3f& shift);
+
+    // Adjust the view to see the entire model
+    IGL_INLINE void align_camera_center(
+      const Eigen::MatrixXd& V);
+
+    // Determines how much to zoom and shift such that the mesh fills the unit
+    // box (centered at the origin)
+    IGL_INLINE void get_scale_and_shift_to_fit_mesh(
+      const Eigen::MatrixXd& V,
+      float & zoom,
+      Eigen::Vector3f& shift);
+
+  // ------------------- Drawing functions
+
+  // Clear the frame buffers
+  IGL_INLINE void clear_framebuffers();
+
+  // Draw everything
+  IGL_INLINE void draw(ViewerData& data, State& opengl, bool update_matrices = true);
+  IGL_INLINE void draw_buffer(
+    ViewerData& data,
+    State& opengl,
+    bool update_matrices,
+    Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& R,
+    Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& G,
+    Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& B,
+    Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& A);
+
+  // Trackball angle (quaternion)
+  enum RotationType
+  {
+    ROTATION_TYPE_TRACKBALL = 0,
+    ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP = 1,
+    NUM_ROTATION_TYPES = 2
+  };
+  IGL_INLINE void set_rotation_type(const RotationType & value);
+
+  // ------------------- Properties
+
+#ifdef IGL_VIEWER_WITH_NANOGUI
+  // Text rendering helper
+  TextRenderer textrenderer;
+#endif
+
+  // Shape material
+  float shininess;
+
+  // Colors
+  Eigen::Vector4f background_color;
+  Eigen::Vector4f line_color;
+
+  // Lighting
+  Eigen::Vector3f light_position;
+  float lighting_factor;
+
+  RotationType rotation_type;
+
+  Eigen::Quaternionf trackball_angle;
+
+  // Model viewing parameters
+  float model_zoom;
+  Eigen::Vector3f model_translation;
+
+  // Model viewing paramters (uv coordinates)
+  float model_zoom_uv;
+  Eigen::Vector3f model_translation_uv;
+
+  // Camera parameters
+  float camera_zoom;
+  bool orthographic;
+  Eigen::Vector3f camera_eye;
+  Eigen::Vector3f camera_up;
+  Eigen::Vector3f camera_center;
+  float camera_view_angle;
+  float camera_dnear;
+  float camera_dfar;
+
+  // Visualization options
+  bool show_overlay;
+  bool show_overlay_depth;
+  bool show_texture;
+  bool show_faces;
+  bool show_lines;
+  bool show_vertid;
+  bool show_faceid;
+  bool invert_normals;
+  bool depth_test;
+
+  // Point size / line width
+  float point_size;
+  float line_width;
+
+  // Animation
+  bool is_animating;
+  double animation_max_fps;
+
+  // Caches the two-norm between the min/max point of the bounding box
+  float object_scale;
+
+  // Viewport size
+  Eigen::Vector4f viewport;
+
+  // Save the OpenGL transformation matrices used for the previous rendering pass
+  Eigen::Matrix4f view;
+  Eigen::Matrix4f model;
+  Eigen::Matrix4f proj;
+  public:
+      EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+};
+
+}
+}
+
+// Alec: Is this the best place for this?
+#ifdef ENABLE_SERIALIZATION
+#include <igl/serialize.h>
+namespace igl {
+	namespace serialization {
+
+		inline void serialization(bool s, igl::opengl::ViewerCore& obj, std::vector<char>& buffer)
+		{
+			SERIALIZE_MEMBER(shininess);
+
+			SERIALIZE_MEMBER(background_color);
+			SERIALIZE_MEMBER(line_color);
+
+			SERIALIZE_MEMBER(light_position);
+			SERIALIZE_MEMBER(lighting_factor);
+
+			SERIALIZE_MEMBER(trackball_angle);
+			SERIALIZE_MEMBER(rotation_type);
+
+			SERIALIZE_MEMBER(model_zoom);
+			SERIALIZE_MEMBER(model_translation);
+
+			SERIALIZE_MEMBER(model_zoom_uv);
+			SERIALIZE_MEMBER(model_translation_uv);
+
+			SERIALIZE_MEMBER(camera_zoom);
+			SERIALIZE_MEMBER(orthographic);
+			SERIALIZE_MEMBER(camera_view_angle);
+			SERIALIZE_MEMBER(camera_dnear);
+			SERIALIZE_MEMBER(camera_dfar);
+			SERIALIZE_MEMBER(camera_eye);
+			SERIALIZE_MEMBER(camera_center);
+			SERIALIZE_MEMBER(camera_up);
+
+			SERIALIZE_MEMBER(show_faces);
+			SERIALIZE_MEMBER(show_lines);
+			SERIALIZE_MEMBER(invert_normals);
+			SERIALIZE_MEMBER(show_overlay);
+			SERIALIZE_MEMBER(show_overlay_depth);
+			SERIALIZE_MEMBER(show_vertid);
+			SERIALIZE_MEMBER(show_faceid);
+			SERIALIZE_MEMBER(show_texture);
+			SERIALIZE_MEMBER(depth_test);
+
+			SERIALIZE_MEMBER(point_size);
+			SERIALIZE_MEMBER(line_width);
+			SERIALIZE_MEMBER(is_animating);
+			SERIALIZE_MEMBER(animation_max_fps);
+
+			SERIALIZE_MEMBER(object_scale);
+
+			SERIALIZE_MEMBER(viewport);
+			SERIALIZE_MEMBER(view);
+			SERIALIZE_MEMBER(model);
+			SERIALIZE_MEMBER(proj);
+		}
+
+		template<>
+		inline void serialize(const igl::opengl::ViewerCore& obj, std::vector<char>& buffer)
+		{
+			serialization(true, const_cast<igl::opengl::ViewerCore&>(obj), buffer);
+		}
+
+		template<>
+		inline void deserialize(igl::opengl::ViewerCore& obj, const std::vector<char>& buffer)
+		{
+			serialization(false, obj, const_cast<std::vector<char>&>(buffer));
+		}
+  }
+}
+#endif
+
+#ifndef IGL_STATIC_LIBRARY
+#  include "ViewerCore.cpp"
+#endif
+
+#endif

+ 94 - 0
include/igl/opengl/glfw/TextRenderer.cpp

@@ -0,0 +1,94 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Wenzel Jacob <wenzel@inf.ethz.ch>
+//
+// 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/.
+#ifdef IGL_VIEWER_WITH_NANOGUI
+#include "TextRenderer.h"
+#include "TextRenderer_fonts.h"
+#include <igl/project.h>
+
+#include <nanogui/opengl.h>
+#include <nanovg.h>
+
+#include <Eigen/Dense>
+
+#define NANOVG_GL3
+#include <nanovg_gl.h>
+
+
+IGL_INLINE igl::opengl::glfw::TextRenderer::TextRenderer(): ctx(nullptr) {}
+
+IGL_INLINE int igl::opengl::glfw::TextRenderer::Init()
+{
+  using namespace std;
+  #ifdef NDEBUG
+    ctx = nvgCreateGL3(NVG_STENCIL_STROKES | NVG_ANTIALIAS);
+  #else
+    ctx = nvgCreateGL3(NVG_STENCIL_STROKES | NVG_ANTIALIAS | NVG_DEBUG);
+  #endif
+
+  nvgCreateFontMem(ctx, "sans", igl_roboto_regular_ttf,
+                             igl_roboto_regular_ttf_size, 0);
+
+  return 0;
+}
+
+IGL_INLINE int igl::opengl::glfw::TextRenderer::Shut()
+{
+  using namespace std;
+  if(ctx)
+    nvgDeleteGL3(ctx);
+  return 0;
+}
+
+IGL_INLINE void igl::opengl::glfw::TextRenderer::BeginDraw(
+  const Eigen::Matrix4f &view,
+  const Eigen::Matrix4f &proj,
+  const Eigen::Vector4f &_viewport,
+  float _object_scale)
+{
+  using namespace std;
+  viewport = _viewport;
+  proj_matrix = proj;
+  view_matrix = view;
+  object_scale = _object_scale;
+
+  Eigen::Vector2i mFBSize;
+  Eigen::Vector2i mSize;
+
+  GLFWwindow* mGLFWWindow = glfwGetCurrentContext();
+  glfwGetFramebufferSize(mGLFWWindow,&mFBSize[0],&mFBSize[1]);
+  glfwGetWindowSize(mGLFWWindow,&mSize[0],&mSize[1]);
+  glViewport(0,0,mFBSize[0],mFBSize[1]);
+
+  glClear(GL_STENCIL_BUFFER_BIT);
+
+  /* Calculate pixel ratio for hi-dpi devices. */
+  mPixelRatio = (float)mFBSize[0] / (float)mSize[0];
+  nvgBeginFrame(ctx,mSize[0],mSize[1],mPixelRatio);
+}
+
+IGL_INLINE void igl::opengl::glfw::TextRenderer::EndDraw()
+{
+  using namespace std;
+  nvgEndFrame(ctx);
+}
+
+IGL_INLINE void igl::opengl::glfw::TextRenderer::DrawText(
+  Eigen::Vector3d pos, Eigen::Vector3d normal, const std::string &text)
+{
+  using namespace std;
+  pos += normal * 0.005f * object_scale;
+  Eigen::Vector3f coord = igl::project(Eigen::Vector3f(pos(0), pos(1), pos(2)),
+      view_matrix, proj_matrix, viewport);
+
+  nvgFontSize(ctx, 16*mPixelRatio);
+  nvgFontFace(ctx, "sans");
+  nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
+  nvgFillColor(ctx, nvgRGBA(10,10,250,255));
+  nvgText(ctx, coord[0]/mPixelRatio, (viewport[3] - coord[1])/mPixelRatio, text.c_str(), NULL);
+}
+#endif

+ 59 - 0
include/igl/opengl/glfw/TextRenderer.h

@@ -0,0 +1,59 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Wenzel Jacob <wenzel@inf.ethz.ch>
+//
+// 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_OPENGL_GLFW_TEXT_RENDERER_H
+#define IGL_OPENGL_GLFW_TEXT_RENDERER_H
+#ifdef IGL_VIEWER_WITH_NANOGUI
+
+#include <Eigen/Dense>
+
+#include <igl/igl_inline.h>
+#include <map>
+
+struct NVGcontext;
+
+namespace igl
+{
+namespace opengl
+{
+namespace glfw
+{
+
+  class TextRenderer
+  {
+  public:
+    IGL_INLINE TextRenderer();
+
+    IGL_INLINE virtual int Init();
+    IGL_INLINE virtual int Shut();
+
+    IGL_INLINE void BeginDraw(const Eigen::Matrix4f &view,const Eigen::Matrix4f &proj,
+      const Eigen::Vector4f &_viewport,float _object_scale);
+
+    IGL_INLINE void EndDraw();
+
+    IGL_INLINE void DrawText(Eigen::Vector3d pos,Eigen::Vector3d normal,const std::string &text);
+
+  protected:
+    std::map<std::string,void *> m_textObjects;
+    Eigen::Matrix4f view_matrix,proj_matrix;
+    Eigen::Vector4f viewport;
+    float object_scale;
+    float mPixelRatio;
+    NVGcontext *ctx;
+  };
+
+}
+}
+}
+
+#ifndef IGL_STATIC_LIBRARY
+#  include "TextRenderer.cpp"
+#endif
+
+#endif
+#endif

+ 38 - 0
include/igl/opengl/glfw/TextRenderer_fonts.h

@@ -0,0 +1,38 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Wenzel Jacob <wenzel@inf.ethz.ch>
+//
+// 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_OPENGL_GLFW_TEXT_RENDERER_FONTS_H
+#define IGL_OPENGL_GLFW_TEXT_RENDERER_FONTS_H
+
+#include <stdint.h>
+
+#ifndef IGL_STATIC_LIBRARY
+namespace
+{
+#endif
+  extern uint8_t igl_entypo_ttf[];
+  extern uint32_t igl_entypo_ttf_size;
+
+  extern uint8_t igl_roboto_bold_ttf[];
+  extern uint32_t igl_roboto_bold_ttf_size;
+
+  extern uint8_t igl_roboto_regular_ttf[];
+  extern uint32_t igl_roboto_regular_ttf_size;
+
+#ifndef IGL_STATIC_LIBRARY
+}
+#endif
+
+#ifndef IGL_STATIC_LIBRARY
+namespace
+{
+  #include "TextRenderer_fonts.cpp"
+}
+#endif
+
+#endif

+ 1033 - 0
include/igl/opengl/glfw/Viewer.cpp

@@ -0,0 +1,1033 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@gmail.com>
+//
+// 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/.
+
+// Must defined this before including Viewer.h
+#define IGL_VIEWER_VIEWER_CPP
+#include "Viewer.h"
+
+#ifdef _WIN32
+#  include <windows.h>
+#  undef max
+#  undef min
+#endif
+
+#include <chrono>
+#include <thread>
+
+#ifndef __APPLE__
+#  define GLEW_STATIC
+#  include <GL/glew.h>
+#endif
+
+#ifdef __APPLE__
+#   include <OpenGL/gl3.h>
+#   define __gl_h_ /* Prevent inclusion of the old gl.h */
+#else
+#   include <GL/gl.h>
+#endif
+
+#include <Eigen/LU>
+
+//#define GLFW_INCLUDE_GLU
+#if defined(__APPLE__)
+#define GLFW_INCLUDE_GLCOREARB
+#else
+#define GL_GLEXT_PROTOTYPES
+#endif
+
+#include <GLFW/glfw3.h>
+
+#include <cmath>
+#include <cstdio>
+#include <sstream>
+#include <iomanip>
+#include <iostream>
+#include <fstream>
+#include <algorithm>
+#include <limits>
+#include <cassert>
+
+#ifdef IGL_VIEWER_WITH_NANOGUI
+#  include <nanogui/formhelper.h>
+#  include <nanogui/screen.h>
+#endif
+
+#include <igl/project.h>
+#include <igl/get_seconds.h>
+#include <igl/readOBJ.h>
+#include <igl/readOFF.h>
+#include <igl/adjacency_list.h>
+#include <igl/writeOBJ.h>
+#include <igl/writeOFF.h>
+#include <igl/massmatrix.h>
+#include <igl/file_dialog_open.h>
+#include <igl/file_dialog_save.h>
+#include <igl/quat_mult.h>
+#include <igl/axis_angle_to_quat.h>
+#include <igl/trackball.h>
+#include <igl/two_axis_valuator_fixed_up.h>
+#include <igl/snap_to_canonical_view_quat.h>
+#include <igl/unproject.h>
+#ifdef ENABLE_SERIALIZATION
+#include <igl/serialize.h>
+#endif
+
+// Internal global variables used for glfw event handling
+static igl::opengl::glfw::Viewer * __viewer;
+static double highdpi = 1;
+static double scroll_x = 0;
+static double scroll_y = 0;
+
+static void glfw_mouse_press(GLFWwindow* window, int button, int action, int modifier)
+{
+  bool tw_used =
+#ifdef IGL_VIEWER_WITH_NANOGUI
+    __viewer->screen->mouseButtonCallbackEvent(button,action,modifier);
+#else
+    false;
+#endif
+
+  igl::opengl::glfw::Viewer::MouseButton mb;
+
+  if (button == GLFW_MOUSE_BUTTON_1)
+    mb = igl::opengl::glfw::Viewer::MouseButton::Left;
+  else if (button == GLFW_MOUSE_BUTTON_2)
+    mb = igl::opengl::glfw::Viewer::MouseButton::Right;
+  else //if (button == GLFW_MOUSE_BUTTON_3)
+    mb = igl::opengl::glfw::Viewer::MouseButton::Middle;
+
+  if (action == GLFW_PRESS)
+  {
+    if(!tw_used)
+    {
+      __viewer->mouse_down(mb,modifier);
+    }
+  } else
+  {
+    // Always call mouse_up on up
+    __viewer->mouse_up(mb,modifier);
+  }
+
+}
+
+static void glfw_error_callback(int error, const char* description)
+{
+  fputs(description, stderr);
+}
+
+static void glfw_char_mods_callback(GLFWwindow* window, unsigned int codepoint, int modifier)
+{
+  // TODO: pass to nanogui (although it's also using physical key down/up
+  // rather than character codes...
+#ifdef IGL_VIEWER_WITH_NANOGUI
+  if(! __viewer->screen->charCallbackEvent(codepoint) )
+#endif
+  {
+    __viewer->key_pressed(codepoint, modifier);
+  }
+}
+
+static void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int modifier)
+{
+  if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
+    glfwSetWindowShouldClose(window, GL_TRUE);
+
+#ifdef IGL_VIEWER_WITH_NANOGUI
+  if (__viewer->screen->keyCallbackEvent(key,scancode,action,modifier) == false)
+#endif
+  {
+    if (action == GLFW_PRESS)
+      __viewer->key_down(key, modifier);
+    else if(action == GLFW_RELEASE)
+      __viewer->key_up(key, modifier);
+  }
+}
+
+static void glfw_window_size(GLFWwindow* window, int width, int height)
+{
+  int w = width*highdpi;
+  int h = height*highdpi;
+
+  __viewer->resize(w, h);
+
+  // TODO: repositioning of the nanogui
+}
+
+static void glfw_mouse_move(GLFWwindow* window, double x, double y)
+{
+  if(
+#ifdef IGL_VIEWER_WITH_NANOGUI
+      __viewer->screen->cursorPosCallbackEvent(x,y) == false &&
+#endif
+      true
+    )
+  {
+    __viewer->mouse_move(x*highdpi, y*highdpi);
+  }
+}
+
+static void glfw_mouse_scroll(GLFWwindow* window, double x, double y)
+{
+  using namespace std;
+  scroll_x += x;
+  scroll_y += y;
+
+#ifdef IGL_VIEWER_WITH_NANOGUI
+  if (__viewer->screen->scrollCallbackEvent(x,y) == false)
+#endif
+  {
+    __viewer->mouse_scroll(y);
+  }
+}
+
+static void glfw_drop_callback(GLFWwindow *window,int count,const char **filenames)
+{
+#ifdef IGL_VIEWER_WITH_NANOGUI
+  __viewer->screen->dropCallbackEvent(count,filenames);
+#endif
+}
+
+namespace igl
+{
+namespace opengl
+{
+namespace glfw
+{
+  IGL_INLINE void Viewer::init()
+  {
+#ifdef IGL_VIEWER_WITH_NANOGUI
+    using namespace nanogui;
+
+    ngui->setFixedSize(Eigen::Vector2i(60,20));
+
+    // Create nanogui widgets
+    nanogui::Window *window = ngui->addWindow(Eigen::Vector2i(10,10),"libIGL-Viewer");
+
+    // ---------------------- LOADING ----------------------
+
+  #ifdef ENABLE_SERIALIZATION
+    ngui->addGroup("Workspace");
+    ngui->addButton("Load",[&](){this->load_scene();});
+    ngui->addButton("Save",[&](){this->save_scene();});
+  #endif
+
+  #ifdef ENABLE_IO
+    ngui->addGroup("Mesh");
+    ngui->addButton("Load",[&](){this->open_dialog_load_mesh();});
+    ngui->addButton("Save",[&](){this->open_dialog_save_mesh();});
+  #endif
+
+    ngui->addGroup("Viewing Options");
+    ngui->addButton("Center object",[&](){this->core.align_camera_center(this->data.V,this->data.F);});
+    ngui->addButton("Snap canonical view",[&]()
+    {
+      this->snap_to_canonical_quaternion();
+    });
+    ngui->addVariable("Zoom", core.camera_zoom);
+    ngui->addVariable("Orthographic view", core.orthographic);
+
+    ngui->addGroup("Draw options");
+
+    ngui->addVariable<bool>("Face-based", [&](bool checked)
+    {
+      this->data.set_face_based(checked);
+    },[&]()
+    {
+      return this->data.face_based;
+    });
+
+    ngui->addVariable("Show texture",core.show_texture);
+
+    ngui->addVariable<bool>("Invert normals",[&](bool checked)
+    {
+      this->data.dirty |= ViewerData::DIRTY_NORMAL;
+      this->core.invert_normals = checked;
+    },[&]()
+    {
+      return this->core.invert_normals;
+    });
+
+    ngui->addVariable("Show overlay", core.show_overlay);
+    ngui->addVariable("Show overlay depth", core.show_overlay_depth);
+    ngui->addVariable("Background", (nanogui::Color &) core.background_color);
+    ngui->addVariable("Line color", (nanogui::Color &) core.line_color);
+    ngui->addVariable("Shininess", core.shininess);
+
+    ngui->addGroup("Overlays");
+    ngui->addVariable("Wireframe", core.show_lines);
+    ngui->addVariable("Fill", core.show_faces);
+    ngui->addVariable("Show vertex labels", core.show_vertid);
+    ngui->addVariable("Show faces labels", core.show_faceid);
+
+    screen->setVisible(true);
+    screen->performLayout();
+#endif
+
+    core.init();
+
+    if (callback_init)
+      if (callback_init(*this))
+        return;
+
+    init_plugins();
+  }
+
+  IGL_INLINE Viewer::Viewer()
+  {
+#ifdef IGL_VIEWER_WITH_NANOGUI
+    ngui = nullptr;
+    screen = nullptr;
+#endif
+
+    // Temporary variables initialization
+    down = false;
+    hack_never_moved = true;
+    scroll_position = 0.0f;
+
+    // Per face
+    data.set_face_based(false);
+
+    // C-style callbacks
+    callback_init         = nullptr;
+    callback_pre_draw     = nullptr;
+    callback_post_draw    = nullptr;
+    callback_mouse_down   = nullptr;
+    callback_mouse_up     = nullptr;
+    callback_mouse_move   = nullptr;
+    callback_mouse_scroll = nullptr;
+    callback_key_down     = nullptr;
+    callback_key_up       = nullptr;
+
+    callback_init_data          = nullptr;
+    callback_pre_draw_data      = nullptr;
+    callback_post_draw_data     = nullptr;
+    callback_mouse_down_data    = nullptr;
+    callback_mouse_up_data      = nullptr;
+    callback_mouse_move_data    = nullptr;
+    callback_mouse_scroll_data  = nullptr;
+    callback_key_down_data      = nullptr;
+    callback_key_up_data        = nullptr;
+
+#ifndef IGL_VIEWER_VIEWER_QUIET
+    const std::string usage(R"(igl::opengl::glfw::Viewer usage:
+  [drag]  Rotate scene
+  A,a     Toggle animation (tight draw loop)
+  F,f     Toggle face based
+  I,i     Toggle invert normals
+  L,l     Toggle wireframe
+  O,o     Toggle orthographic/perspective projection
+  T,t     Toggle filled faces
+  Z       Snap to canonical view
+  [,]     Toggle between rotation control types (e.g. trackball, two-axis
+          valuator with fixed up))"
+#ifdef IGL_VIEWER_WITH_NANOGUI
+		R"(
+  ;       Toggle vertex labels
+  :       Toggle face labels)"
+#endif
+);
+    std::cout<<usage<<std::endl;
+#endif
+  }
+
+  IGL_INLINE void Viewer::init_plugins()
+  {
+    // Init all plugins
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+    {
+      plugins[i]->init(this);
+    }
+  }
+
+  IGL_INLINE Viewer::~Viewer()
+  {
+  }
+
+  IGL_INLINE void Viewer::shutdown_plugins()
+  {
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+    {
+      plugins[i]->shutdown();
+    }
+  }
+
+  IGL_INLINE bool Viewer::load_mesh_from_file(const char* mesh_file_name)
+  {
+    std::string mesh_file_name_string = std::string(mesh_file_name);
+
+    // first try to load it with a plugin
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+    {
+      if (plugins[i]->load(mesh_file_name_string))
+      {
+        return true;
+      }
+    }
+
+    data.clear();
+
+    size_t last_dot = mesh_file_name_string.rfind('.');
+    if (last_dot == std::string::npos)
+    {
+      printf("Error: No file extension found in %s\n",mesh_file_name);
+      return false;
+    }
+
+    std::string extension = mesh_file_name_string.substr(last_dot+1);
+
+    if (extension == "off" || extension =="OFF")
+    {
+      Eigen::MatrixXd V;
+      Eigen::MatrixXi F;
+      if (!igl::readOFF(mesh_file_name_string, V, F))
+        return false;
+      data.set_mesh(V,F);
+    }
+    else if (extension == "obj" || extension =="OBJ")
+    {
+      Eigen::MatrixXd corner_normals;
+      Eigen::MatrixXi fNormIndices;
+
+      Eigen::MatrixXd UV_V;
+      Eigen::MatrixXi UV_F;
+      Eigen::MatrixXd V;
+      Eigen::MatrixXi F;
+
+      if (!(
+            igl::readOBJ(
+              mesh_file_name_string,
+              V, UV_V, corner_normals, F, UV_F, fNormIndices)))
+      {
+        return false;
+      }
+
+      data.set_mesh(V,F);
+      data.set_uv(UV_V,UV_F);
+
+    }
+    else
+    {
+      // unrecognized file type
+      printf("Error: %s is not a recognized file type.\n",extension.c_str());
+      return false;
+    }
+
+    data.compute_normals();
+    data.uniform_colors(Eigen::Vector3d(51.0/255.0,43.0/255.0,33.3/255.0),
+                   Eigen::Vector3d(255.0/255.0,228.0/255.0,58.0/255.0),
+                   Eigen::Vector3d(255.0/255.0,235.0/255.0,80.0/255.0));
+    if (data.V_uv.rows() == 0)
+    {
+      data.grid_texture();
+    }
+
+    core.align_camera_center(data.V,data.F);
+
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+      if (plugins[i]->post_load())
+        return true;
+
+    return true;
+  }
+
+  IGL_INLINE bool Viewer::save_mesh_to_file(const char* mesh_file_name)
+  {
+    std::string mesh_file_name_string(mesh_file_name);
+
+    // first try to load it with a plugin
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+      if (plugins[i]->save(mesh_file_name_string))
+        return true;
+
+    size_t last_dot = mesh_file_name_string.rfind('.');
+    if (last_dot == std::string::npos)
+    {
+      // No file type determined
+      printf("Error: No file extension found in %s\n",mesh_file_name);
+      return false;
+    }
+    std::string extension = mesh_file_name_string.substr(last_dot+1);
+    if (extension == "off" || extension =="OFF")
+    {
+      return igl::writeOFF(mesh_file_name_string,data.V,data.F);
+    }
+    else if (extension == "obj" || extension =="OBJ")
+    {
+      Eigen::MatrixXd corner_normals;
+      Eigen::MatrixXi fNormIndices;
+
+      Eigen::MatrixXd UV_V;
+      Eigen::MatrixXi UV_F;
+
+      return igl::writeOBJ(mesh_file_name_string, data.V,
+          data.F, corner_normals, fNormIndices, UV_V, UV_F);
+    }
+    else
+    {
+      // unrecognized file type
+      printf("Error: %s is not a recognized file type.\n",extension.c_str());
+      return false;
+    }
+    return true;
+  }
+
+  IGL_INLINE bool Viewer::key_pressed(unsigned int unicode_key,int modifiers)
+  {
+    if (callback_key_pressed)
+      if (callback_key_pressed(*this,unicode_key,modifiers))
+        return true;
+
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+    {
+      if (plugins[i]->key_pressed(unicode_key, modifiers))
+      {
+        return true;
+      }
+    }
+
+    switch(unicode_key)
+    {
+      case 'A':
+      case 'a':
+      {
+        core.is_animating = !core.is_animating;
+        return true;
+      }
+      case 'F':
+      case 'f':
+      {
+        data.set_face_based(!data.face_based);
+        return true;
+      }
+      case 'I':
+      case 'i':
+      {
+        data.dirty |= ViewerData::DIRTY_NORMAL;
+        core.invert_normals = !core.invert_normals;
+        return true;
+      }
+      case 'L':
+      case 'l':
+      {
+        core.show_lines = !core.show_lines;
+        return true;
+      }
+      case 'O':
+      case 'o':
+      {
+        core.orthographic = !core.orthographic;
+        return true;
+      }
+      case 'T':
+      case 't':
+      {
+        core.show_faces = !core.show_faces;
+        return true;
+      }
+      case 'Z':
+      {
+        snap_to_canonical_quaternion();
+        return true;
+      }
+      case '[':
+      case ']':
+      {
+        if(core.rotation_type == ViewerCore::ROTATION_TYPE_TRACKBALL)
+        {
+          core.set_rotation_type(
+            ViewerCore::ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP);
+        }else
+        {
+          core.set_rotation_type(ViewerCore::ROTATION_TYPE_TRACKBALL);
+        }
+        return true;
+      }
+#ifdef IGL_VIEWER_WITH_NANOGUI
+      case ';':
+        core.show_vertid = !core.show_vertid;
+        return true;
+      case ':':
+        core.show_faceid = !core.show_faceid;
+        return true;
+#endif
+      default: break;//do nothing
+    }
+    return false;
+  }
+
+  IGL_INLINE bool Viewer::key_down(int key,int modifiers)
+  {
+    if (callback_key_down)
+      if (callback_key_down(*this,key,modifiers))
+        return true;
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+      if (plugins[i]->key_down(key, modifiers))
+        return true;
+    return false;
+  }
+
+  IGL_INLINE bool Viewer::key_up(int key,int modifiers)
+  {
+    if (callback_key_up)
+      if (callback_key_up(*this,key,modifiers))
+        return true;
+
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+      if (plugins[i]->key_up(key, modifiers))
+        return true;
+
+    return false;
+  }
+
+  IGL_INLINE bool Viewer::mouse_down(MouseButton button,int modifier)
+  {
+    // Remember mouse location at down even if used by callback/plugin
+    down_mouse_x = current_mouse_x;
+    down_mouse_y = current_mouse_y;
+
+    if (callback_mouse_down)
+      if (callback_mouse_down(*this,static_cast<int>(button),modifier))
+        return true;
+
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+      if(plugins[i]->mouse_down(static_cast<int>(button),modifier))
+        return true;
+
+    down = true;
+
+    down_translation = core.model_translation;
+
+
+    // Initialization code for the trackball
+    Eigen::RowVector3d center;
+    if (data.V.rows() == 0)
+    {
+      center << 0,0,0;
+    }else
+    {
+      center = data.V.colwise().sum()/data.V.rows();
+    }
+
+    Eigen::Vector3f coord =
+      igl::project(
+        Eigen::Vector3f(center(0),center(1),center(2)),
+        (core.view * core.model).eval(),
+        core.proj,
+        core.viewport);
+    down_mouse_z = coord[2];
+    down_rotation = core.trackball_angle;
+
+    mouse_mode = MouseMode::Rotation;
+
+    switch (button)
+    {
+      case MouseButton::Left:
+        mouse_mode = MouseMode::Rotation;
+        break;
+
+      case MouseButton::Right:
+        mouse_mode = MouseMode::Translation;
+        break;
+
+      default:
+        mouse_mode = MouseMode::None;
+        break;
+    }
+
+    return true;
+  }
+
+  IGL_INLINE bool Viewer::mouse_up(MouseButton button,int modifier)
+  {
+    down = false;
+
+    if (callback_mouse_up)
+      if (callback_mouse_up(*this,static_cast<int>(button),modifier))
+        return true;
+
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+      if(plugins[i]->mouse_up(static_cast<int>(button),modifier))
+          return true;
+
+    mouse_mode = MouseMode::None;
+
+    return true;
+  }
+
+  IGL_INLINE bool Viewer::mouse_move(int mouse_x,int mouse_y)
+  {
+    if(hack_never_moved)
+    {
+      down_mouse_x = mouse_x;
+      down_mouse_y = mouse_y;
+      hack_never_moved = false;
+    }
+    current_mouse_x = mouse_x;
+    current_mouse_y = mouse_y;
+
+    if (callback_mouse_move)
+      if (callback_mouse_move(*this,mouse_x,mouse_y))
+        return true;
+
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+      if (plugins[i]->mouse_move(mouse_x, mouse_y))
+        return true;
+
+    if (down)
+    {
+      switch (mouse_mode)
+      {
+        case MouseMode::Rotation:
+        {
+          switch(core.rotation_type)
+          {
+            default:
+              assert(false && "Unknown rotation type");
+            case ViewerCore::ROTATION_TYPE_TRACKBALL:
+              igl::trackball(
+                core.viewport(2),
+                core.viewport(3),
+                2.0f,
+                down_rotation,
+                down_mouse_x,
+                down_mouse_y,
+                mouse_x,
+                mouse_y,
+                core.trackball_angle);
+              break;
+            case ViewerCore::ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP:
+              igl::two_axis_valuator_fixed_up(
+                core.viewport(2),core.viewport(3),
+                2.0,
+                down_rotation,
+                down_mouse_x, down_mouse_y, mouse_x, mouse_y,
+                core.trackball_angle);
+              break;
+          }
+          //Eigen::Vector4f snapq = core.trackball_angle;
+
+          break;
+        }
+
+        case MouseMode::Translation:
+        {
+          //translation
+          Eigen::Vector3f pos1 = igl::unproject(Eigen::Vector3f(mouse_x, core.viewport[3] - mouse_y, down_mouse_z), (core.view * core.model).eval(), core.proj, core.viewport);
+          Eigen::Vector3f pos0 = igl::unproject(Eigen::Vector3f(down_mouse_x, core.viewport[3] - down_mouse_y, down_mouse_z), (core.view * core.model).eval(), core.proj, core.viewport);
+
+          Eigen::Vector3f diff = pos1 - pos0;
+          core.model_translation = down_translation + Eigen::Vector3f(diff[0],diff[1],diff[2]);
+
+          break;
+        }
+        case MouseMode::Zoom:
+        {
+          float delta = 0.001f * (mouse_x - down_mouse_x + mouse_y - down_mouse_y);
+          core.camera_zoom *= 1 + delta;
+          down_mouse_x = mouse_x;
+          down_mouse_y = mouse_y;
+          break;
+        }
+
+        default:
+          break;
+      }
+    }
+    return true;
+  }
+
+  IGL_INLINE bool Viewer::mouse_scroll(float delta_y)
+  {
+    scroll_position += delta_y;
+
+    if (callback_mouse_scroll)
+      if (callback_mouse_scroll(*this,delta_y))
+        return true;
+
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+      if (plugins[i]->mouse_scroll(delta_y))
+        return true;
+
+    // Only zoom if there's actually a change
+    if(delta_y != 0)
+    {
+      float mult = (1.0+((delta_y>0)?1.:-1.)*0.05);
+      const float min_zoom = 0.1f;
+      core.camera_zoom = (core.camera_zoom * mult > min_zoom ? core.camera_zoom * mult : min_zoom);
+    }
+    return true;
+  }
+
+  IGL_INLINE void Viewer::draw()
+  {
+    using namespace std;
+    using namespace Eigen;
+
+    core.clear_framebuffers();
+
+    if (callback_pre_draw)
+      if (callback_pre_draw(*this))
+        return;
+
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+      if (plugins[i]->pre_draw())
+        return;
+
+    core.draw(data,opengl);
+
+    if (callback_post_draw)
+      if (callback_post_draw(*this))
+        return;
+
+    for (unsigned int i = 0; i<plugins.size(); ++i)
+      if (plugins[i]->post_draw())
+        break;
+
+#ifdef IGL_VIEWER_WITH_NANOGUI
+	screen->drawContents();
+	screen->drawWidgets();
+#endif
+  }
+
+  IGL_INLINE bool Viewer::save_scene()
+  {
+    std::string fname = igl::file_dialog_save();
+    if (fname.length() == 0)
+      return false;
+
+#ifdef ENABLE_SERIALIZATION
+
+    igl::serialize(core,"Core",fname.c_str(),true);
+
+#ifndef ENABLE_SERIALIZATION_CORE_ONLY
+    igl::serialize(data,"Data",fname.c_str());
+    for(unsigned int i = 0; i <plugins.size(); ++i)
+      igl::serialize(*plugins[i],plugins[i]->plugin_name,fname.c_str());
+#endif
+
+#endif
+
+    return true;
+  }
+
+  IGL_INLINE bool Viewer::load_scene()
+  {
+    std::string fname = igl::file_dialog_open();
+    if(fname.length() == 0)
+      return false;
+
+    return load_scene(fname);
+  }
+
+  IGL_INLINE bool Viewer::load_scene(std::string fname)
+  {
+#ifdef ENABLE_SERIALIZATION
+
+    igl::deserialize(core,"Core",fname.c_str());
+
+#ifndef ENABLE_SERIALIZATION_CORE_ONLY
+    igl::deserialize(data,"Data",fname.c_str());
+    for(unsigned int i = 0; i <plugins.size(); ++i)
+      igl::deserialize(*plugins[i],plugins[i]->plugin_name,fname.c_str());
+#endif
+
+#endif
+
+    return true;
+  }
+
+  IGL_INLINE void Viewer::resize(int w,int h)
+  {
+    core.viewport = Eigen::Vector4f(0,0,w,h);
+  }
+
+  IGL_INLINE void Viewer::snap_to_canonical_quaternion()
+  {
+    Eigen::Quaternionf snapq = this->core.trackball_angle;
+    igl::snap_to_canonical_view_quat(snapq,1.0f,this->core.trackball_angle);
+  }
+
+  IGL_INLINE void Viewer::open_dialog_load_mesh()
+  {
+    std::string fname = igl::file_dialog_open();
+
+    if (fname.length() == 0)
+      return;
+
+    this->load_mesh_from_file(fname.c_str());
+  }
+
+  IGL_INLINE void Viewer::open_dialog_save_mesh()
+  {
+    std::string fname = igl::file_dialog_save();
+
+    if(fname.length() == 0)
+      return;
+
+    this->save_mesh_to_file(fname.c_str());
+  }
+
+
+  IGL_INLINE int  Viewer::launch_init(bool resizable,bool fullscreen)
+  {
+    glfwSetErrorCallback(glfw_error_callback);
+    if (!glfwInit())
+      return EXIT_FAILURE;
+
+    glfwWindowHint(GLFW_SAMPLES, 8);
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+
+    #ifdef __APPLE__
+      glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+      glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+    #endif
+
+    if(fullscreen)
+    {
+      GLFWmonitor *monitor = glfwGetPrimaryMonitor();
+      const GLFWvidmode *mode = glfwGetVideoMode(monitor);
+      window = glfwCreateWindow(mode->width,mode->height,"libigl viewer",monitor,nullptr);
+    }
+    else
+    {
+      window = glfwCreateWindow(1280,800,"libigl viewer",nullptr,nullptr);
+    }
+
+    if (!window)
+    {
+      glfwTerminate();
+      return EXIT_FAILURE;
+    }
+
+    glfwMakeContextCurrent(window);
+
+    #ifndef __APPLE__
+      glewExperimental = true;
+      GLenum err = glewInit();
+      if(GLEW_OK != err)
+      {
+        /* Problem: glewInit failed, something is seriously wrong. */
+       fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
+      }
+      glGetError(); // pull and savely ignonre unhandled errors like GL_INVALID_ENUM
+      fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));
+    #endif
+
+    #if defined(DEBUG) || defined(_DEBUG)
+      int major, minor, rev;
+      major = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MAJOR);
+      minor = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MINOR);
+      rev = glfwGetWindowAttrib(window, GLFW_CONTEXT_REVISION);
+      printf("OpenGL version recieved: %d.%d.%d\n", major, minor, rev);
+      printf("Supported OpenGL is %s\n", (const char*)glGetString(GL_VERSION));
+      printf("Supported GLSL is %s\n", (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION));
+    #endif
+
+    glfwSetInputMode(window,GLFW_CURSOR,GLFW_CURSOR_NORMAL);
+
+    // Initialize FormScreen
+#ifdef IGL_VIEWER_WITH_NANOGUI
+    screen = new nanogui::Screen();
+    screen->initialize(window, false);
+    ngui = new nanogui::FormHelper(screen);
+#endif
+
+    __viewer = this;
+
+    // Register callbacks
+    glfwSetKeyCallback(window, glfw_key_callback);
+    glfwSetCursorPosCallback(window,glfw_mouse_move);
+    glfwSetWindowSizeCallback(window,glfw_window_size);
+    glfwSetMouseButtonCallback(window,glfw_mouse_press);
+    glfwSetScrollCallback(window,glfw_mouse_scroll);
+    glfwSetCharModsCallback(window,glfw_char_mods_callback);
+    glfwSetDropCallback(window,glfw_drop_callback);
+
+    // Handle retina displays (windows and mac)
+    int width, height;
+    glfwGetFramebufferSize(window, &width, &height);
+
+    int width_window, height_window;
+    glfwGetWindowSize(window, &width_window, &height_window);
+
+    highdpi = width/width_window;
+
+    glfw_window_size(window,width_window,height_window);
+
+    opengl.init();
+
+    core.align_camera_center(data.V,data.F);
+
+    // Initialize IGL viewer
+    init();
+    return EXIT_SUCCESS;
+  }
+
+  IGL_INLINE bool Viewer::launch_rendering(bool loop)
+  {
+    // glfwMakeContextCurrent(window);
+
+    // Rendering loop
+    while (!glfwWindowShouldClose(window))
+    {
+      double tic = get_seconds();
+      draw();
+
+      glfwSwapBuffers(window);
+      if(core.is_animating)
+      {
+        glfwPollEvents();
+        // In microseconds
+        double duration = 1000000.*(get_seconds()-tic);
+        const double min_duration = 1000000./core.animation_max_fps;
+        if(duration<min_duration)
+        {
+          std::this_thread::sleep_for(std::chrono::microseconds((int)(min_duration-duration)));
+        }
+      }
+      else
+      {
+        glfwWaitEvents();
+      }
+
+      if (!loop)
+        return !glfwWindowShouldClose(window);
+    }
+    return EXIT_SUCCESS;
+  }
+
+  IGL_INLINE void Viewer::launch_shut()
+  {
+    opengl.free();
+    core.shut();
+
+    shutdown_plugins();
+#ifdef IGL_VIEWER_WITH_NANOGUI
+    delete ngui;
+    //delete screen;
+    screen = nullptr;
+    ngui = nullptr;
+#endif
+
+    glfwDestroyWindow(window);
+    glfwTerminate();
+    return;
+  }
+
+  IGL_INLINE int Viewer::launch(bool resizable,bool fullscreen)
+  {
+    // TODO return values are being ignored...
+    launch_init(resizable,fullscreen);
+    launch_rendering(true);
+    launch_shut();
+    return EXIT_SUCCESS;
+  }
+} // end namespace
+} // end namespace
+}

+ 170 - 0
include/igl/opengl/glfw/Viewer.h

@@ -0,0 +1,170 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@gmail.com>
+//
+// 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_OPENGL_GLFW_VIEWER_H
+#define IGL_OPENGL_GLFW_VIEWER_H
+
+#ifndef IGL_OPENGL_4
+#define IGL_OPENGL_4
+#endif
+
+#include <vector>
+#include <string>
+#include <cstdint>
+
+#include <Eigen/Core>
+#include <Eigen/Geometry>
+
+#include <igl/igl_inline.h>
+
+#include "../Shader.h"
+#include "../State.h"
+#include "../ViewerCore.h"
+#include "../../ViewerData.h"
+#include "ViewerPlugin.h"
+
+#define IGL_MOD_SHIFT           0x0001
+#define IGL_MOD_CONTROL         0x0002
+#define IGL_MOD_ALT             0x0004
+#define IGL_MOD_SUPER           0x0008
+
+#ifdef IGL_VIEWER_WITH_NANOGUI
+namespace nanogui { class FormHelper; class Screen; }
+#endif
+
+class GLFWwindow;
+
+namespace igl
+{
+namespace opengl
+{
+namespace glfw
+{
+  // GLFW-based mesh viewer
+  class Viewer
+  {
+  public:
+    GLFWwindow* window;
+
+    IGL_INLINE int launch(bool resizable = true,bool fullscreen = false);
+    IGL_INLINE int launch_init(bool resizable = true,bool fullscreen = false);
+    IGL_INLINE bool launch_rendering(bool loop = true);
+    IGL_INLINE void launch_shut();
+
+    IGL_INLINE void init();
+
+    // Stores all the viewing options
+    ViewerCore core;
+
+    // Stores all the data that should be visualized
+    ViewerData data;
+
+    // Stores the vbos indices and opengl related settings
+    State opengl;
+
+    // List of registered plugins
+    std::vector<ViewerPlugin*> plugins;
+    IGL_INLINE void init_plugins();
+    IGL_INLINE void shutdown_plugins();
+
+    // Temporary data stored when the mouse button is pressed
+    Eigen::Quaternionf down_rotation;
+    int current_mouse_x;
+    int current_mouse_y;
+    int down_mouse_x;
+    int down_mouse_y;
+    float down_mouse_z;
+    Eigen::Vector3f down_translation;
+    bool down;
+    bool hack_never_moved;
+
+#ifdef IGL_VIEWER_WITH_NANOGUI
+    nanogui::FormHelper* ngui;
+    nanogui::Screen* screen;
+#endif
+
+    // Keep track of the global position of the scrollwheel
+    float scroll_position;
+
+    // UI Enumerations
+    enum class MouseButton {Left, Middle, Right};
+    enum class MouseMode { None, Rotation, Zoom, Pan, Translation} mouse_mode;
+
+    Viewer();
+    ~Viewer();
+
+    // Mesh IO
+    IGL_INLINE bool load_mesh_from_file(const char* mesh_file_name);
+    IGL_INLINE bool save_mesh_to_file(const char* mesh_file_name);
+
+    // Callbacks
+    IGL_INLINE bool key_pressed(unsigned int unicode_key,int modifier);
+    IGL_INLINE bool key_down(int key,int modifier);
+    IGL_INLINE bool key_up(int key,int modifier);
+
+    IGL_INLINE bool mouse_down(MouseButton button,int modifier);
+    IGL_INLINE bool mouse_up(MouseButton button,int modifier);
+
+    IGL_INLINE bool mouse_move(int mouse_x,int mouse_y);
+    IGL_INLINE bool mouse_scroll(float delta_y);
+
+    // Scene IO
+    IGL_INLINE bool load_scene();
+    IGL_INLINE bool load_scene(std::string fname);
+    IGL_INLINE bool save_scene();
+
+    // Draw everything
+    IGL_INLINE void draw();
+
+    // OpenGL context resize
+    IGL_INLINE void resize(int w,int h);
+
+    // Helper functions
+    IGL_INLINE void snap_to_canonical_quaternion();
+    IGL_INLINE void open_dialog_load_mesh();
+    IGL_INLINE void open_dialog_save_mesh();
+
+    // C++-style functions
+    //
+    // Returns **true** if action should be cancelled.
+    std::function<bool(Viewer& viewer)> callback_init;
+    std::function<bool(Viewer& viewer)> callback_pre_draw;
+    std::function<bool(Viewer& viewer)> callback_post_draw;
+    std::function<bool(Viewer& viewer, int button, int modifier)> callback_mouse_down;
+    std::function<bool(Viewer& viewer, int button, int modifier)> callback_mouse_up;
+    std::function<bool(Viewer& viewer, int mouse_x, int mouse_y)> callback_mouse_move;
+    std::function<bool(Viewer& viewer, float delta_y)> callback_mouse_scroll;
+    std::function<bool(Viewer& viewer, unsigned int key, int modifiers)> callback_key_pressed;
+    // THESE SHOULD BE DEPRECATED:
+    std::function<bool(Viewer& viewer, unsigned char key, int modifiers)> callback_key_down;
+    std::function<bool(Viewer& viewer, unsigned char key, int modifiers)> callback_key_up;
+
+    // Pointers to per-callback data
+    void* callback_init_data;
+    void* callback_pre_draw_data;
+    void* callback_post_draw_data;
+    void* callback_mouse_down_data;
+    void* callback_mouse_up_data;
+    void* callback_mouse_move_data;
+    void* callback_mouse_scroll_data;
+    void* callback_key_pressed_data;
+    void* callback_key_down_data;
+    void* callback_key_up_data;
+
+  public:
+      EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+  };
+
+} // end namespace
+} // end namespace
+} // end namespace
+
+#ifndef IGL_STATIC_LIBRARY
+#  include "Viewer.cpp"
+#endif
+
+#endif

+ 179 - 0
include/igl/opengl/glfw/ViewerPlugin.h

@@ -0,0 +1,179 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@gmail.com>
+//
+// 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_OPENGL_GLFW_VIEWERPLUGIN_H
+#define IGL_OPENGL_GLFW_VIEWERPLUGIN_H
+
+// TODO:
+// * create plugins/skeleton.h
+// * pass time in draw function
+// * remove Preview3D from comments
+// * clean comments
+#include <string>
+#include <igl/igl_inline.h>
+#include <vector>
+
+namespace igl
+{
+namespace opengl
+{
+namespace glfw
+{
+
+// Abstract class for plugins
+// All plugins MUST have this class as their parent and may implement any/all
+// the callbacks marked `virtual` here.
+//
+// /////For an example of a basic plugins see plugins/skeleton.h
+//
+// Return value of callbacks: returning true to any of the callbacks tells
+// Viewer that the event has been handled and that it should not be passed to
+// other plugins or to other internal functions of Viewer
+
+// Forward declaration of the viewer
+class Viewer;
+
+class ViewerPlugin
+{
+public:
+  IGL_INLINE ViewerPlugin()
+  {plugin_name = "dummy";}
+
+  virtual ~ViewerPlugin(){}
+
+  // This function is called when the viewer is initialized (no mesh will be loaded at this stage)
+  IGL_INLINE virtual void init(Viewer *_viewer)
+  {
+    viewer = _viewer;
+  }
+
+  // This function is called before shutdown
+  IGL_INLINE virtual void shutdown()
+  {
+  }
+
+  // This function is called before a mesh is loaded
+  IGL_INLINE virtual bool load(std::string filename)
+  {
+    return false;
+  }
+
+  // This function is called before a mesh is saved
+  IGL_INLINE virtual bool save(std::string filename)
+  {
+    return false;
+  }
+
+  // This function is called when the scene is serialized
+  IGL_INLINE virtual bool serialize(std::vector<char>& buffer) const
+  {
+    return false;
+  }
+
+  // This function is called when the scene is deserialized
+  IGL_INLINE virtual bool deserialize(const std::vector<char>& buffer)
+  {
+    return false;
+  }
+
+  // Runs immediately after a new mesh has been loaded.
+  IGL_INLINE virtual bool post_load()
+  {
+    return false;
+  }
+
+  // This function is called before the draw procedure of Preview3D
+  IGL_INLINE virtual bool pre_draw()
+  {
+    return false;
+  }
+
+  // This function is called after the draw procedure of Preview3D
+  IGL_INLINE virtual bool post_draw()
+  {
+    return false;
+  }
+
+  // This function is called when the mouse button is pressed
+  // - button can be GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON or GLUT_RIGHT_BUTTON
+  // - modifiers is a bitfield that might one or more of the following bits Preview3D::NO_KEY, Preview3D::SHIFT, Preview3D::CTRL, Preview3D::ALT;
+  IGL_INLINE virtual bool mouse_down(int button, int modifier)
+  {
+    return false;
+  }
+
+  // This function is called when the mouse button is released
+  // - button can be GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON or GLUT_RIGHT_BUTTON
+  // - modifiers is a bitfield that might one or more of the following bits Preview3D::NO_KEY, Preview3D::SHIFT, Preview3D::CTRL, Preview3D::ALT;
+  IGL_INLINE virtual bool mouse_up(int button, int modifier)
+  {
+    return false;
+  }
+
+  // This function is called every time the mouse is moved
+  // - mouse_x and mouse_y are the new coordinates of the mouse pointer in screen coordinates
+  IGL_INLINE virtual bool mouse_move(int mouse_x, int mouse_y)
+  {
+    return false;
+  }
+
+  // This function is called every time the scroll wheel is moved
+  // Note: this callback is not working with every glut implementation
+  IGL_INLINE virtual bool mouse_scroll(float delta_y)
+  {
+    return false;
+  }
+
+  // This function is called when a keyboard key is pressed. Unlike key_down
+  // this will reveal the actual character being sent (not just the physical
+  // key)
+  // - modifiers is a bitfield that might one or more of the following bits Preview3D::NO_KEY, Preview3D::SHIFT, Preview3D::CTRL, Preview3D::ALT;
+  IGL_INLINE virtual bool key_pressed(unsigned int key, int modifiers)
+  {
+    return false;
+  }
+
+  // This function is called when a keyboard key is down
+  // - modifiers is a bitfield that might one or more of the following bits Preview3D::NO_KEY, Preview3D::SHIFT, Preview3D::CTRL, Preview3D::ALT;
+  IGL_INLINE virtual bool key_down(int key, int modifiers)
+  {
+    return false;
+  }
+
+  // This function is called when a keyboard key is release
+  // - modifiers is a bitfield that might one or more of the following bits Preview3D::NO_KEY, Preview3D::SHIFT, Preview3D::CTRL, Preview3D::ALT;
+  IGL_INLINE virtual bool key_up(int key, int modifiers)
+  {
+    return false;
+  }
+
+  std::string plugin_name;
+protected:
+  // Pointer to the main Viewer class
+  Viewer *viewer;
+};
+
+#ifdef ENABLE_SERIALIZATION
+namespace serialization
+{
+  inline void serialize(const ViewerPlugin& obj,std::vector<char>& buffer)
+  {
+    obj.serialize(buffer);
+  }
+
+  inline void deserialize(ViewerPlugin& obj,const std::vector<char>& buffer)
+  {
+    obj.deserialize(buffer);
+  }
+}
+#endif
+
+}
+}
+}
+
+#endif

+ 27 - 43
shared/cmake/CMakeLists.txt

@@ -17,14 +17,9 @@ option(LIBIGL_WITH_OPENGL_GLFW      "Use GLFW"           OFF)
 option(LIBIGL_WITH_PNG              "Use PNG"            OFF)
 option(LIBIGL_WITH_TETGEN           "Use Tetgen"         OFF)
 option(LIBIGL_WITH_TRIANGLE         "Use Triangle"       OFF)
-option(LIBIGL_WITH_VIEWER           "Use OpenGL viewer"  OFF)
 option(LIBIGL_WITH_XML              "Use XML"            OFF)
 option(LIBIGL_WITH_PYTHON           "Use Python"         OFF)
 
-if(LIBIGL_WITH_VIEWER AND (NOT LIBIGL_WITH_OPENGL_GLFW OR NOT LIBIGL_WITH_OPENGL) )
-  message(FATAL_ERROR "LIBIGL_WITH_VIEWER=ON requires LIBIGL_WITH_OPENGL_GLFW=ON and LIBIGL_WITH_OPENGL=ON")
-endif()
-
 ### Compilation configuration ###
 if(MSVC)
   ### Enable parallel compilation for Visual Studio
@@ -351,12 +346,36 @@ if(LIBIGL_WITH_OPENGL)
     # Note: if add_subdirectory("${NANOGUI_DIR}" "nanogui") runs below it will
     # add GLFW as a side-effect; in this case, CMake will complain about
     # duplicates if we add them here.
-    if (NOT (LIBIGL_WITH_VIEWER AND LIBIGL_WITH_NANOGUI))
-        add_subdirectory("${NANOGUI_DIR}/ext/glfw" "glfw")
-    endif()
     set(LIBIGL_OPENGL_GLFW_EXTRA_LIBRARIES "glfw" ${GLFW_LIBRARIES})
+
+    ### Compile the viewer with NANOGUI support ###
+    if(LIBIGL_WITH_NANOGUI)
+      list(APPEND LIBIGL_DEFINITIONS "-DIGL_VIEWER_WITH_NANOGUI")
+      if (LIBIGL_WITH_PYTHON)
+        set(NANOGUI_BUILD_PYTHON ON CACHE BOOL " " FORCE)
+      else()
+        set(NANOGUI_BUILD_PYTHON OFF CACHE BOOL " " FORCE)
+      endif()
+      set(NANOGUI_BUILD_EXAMPLE OFF CACHE BOOL " " FORCE)
+      set(NANOGUI_BUILD_SHARED  OFF CACHE BOOL " " FORCE)
+      add_subdirectory("${NANOGUI_DIR}" "nanogui")
+      set(NANOGUI_INCLUDE_DIRS
+        "${NANOGUI_DIR}/include"
+        "${NANOGUI_DIR}/ext/nanovg/src")
+      list(APPEND GLFW_INCLUDE_DIRS "${NANOGUI_INCLUDE_DIRS}")
+      list(APPEND LIBIGL_OPENGL_GLFW_EXTRA_LIBRARIES "nanogui" ${NANOGUI_EXTRA_LIBS})
+    else()
+      add_subdirectory("${NANOGUI_DIR}/ext/glfw" "glfw")
+    endif()
+    list(APPEND VIEWER_INCLUDE_DIRS ${GLFW_INCLUDE_DIRS})
+    list(APPEND LIBIGL_INCLUDE_DIRS ${VIEWER_INCLUDE_DIRS})
+    list(APPEND LIBIGL_EXTRA_LIBRARIES ${LIBIGL_VIEWER_EXTRA_LIBRARIES})
+
     if(LIBIGL_USE_STATIC_LIBRARY)
       CompileIGL_Module("opengl/glfw" "")
+      if(LIBIGL_WITH_NANOGUI)
+        target_compile_definitions(igl_opengl_glfw PRIVATE -DIGL_VIEWER_WITH_NANOGUI)
+      endif()
       target_include_directories(igl_opengl_glfw PRIVATE ${GLFW_INCLUDE_DIRS})
       target_include_directories(igl_opengl_glfw PRIVATE ${OPENGL_INCLUDE_DIR})
       if(NOT APPLE)
@@ -365,40 +384,6 @@ if(LIBIGL_WITH_OPENGL)
     endif()
     list(APPEND LIBIGL_INCLUDE_DIRS ${GLFW_INCLUDE_DIRS})
     list(APPEND LIBIGL_EXTRA_LIBRARIES ${LIBIGL_OPENGL_GLFW_EXTRA_LIBRARIES})
-
-    ### Compile the viewer ###
-    if(LIBIGL_WITH_VIEWER)
-      if(LIBIGL_WITH_NANOGUI)
-        list(APPEND LIBIGL_DEFINITIONS "-DIGL_VIEWER_WITH_NANOGUI")
-        if (LIBIGL_WITH_PYTHON)
-          set(NANOGUI_BUILD_PYTHON ON CACHE BOOL " " FORCE)
-        else()
-          set(NANOGUI_BUILD_PYTHON OFF CACHE BOOL " " FORCE)
-        endif()
-        set(NANOGUI_BUILD_EXAMPLE OFF CACHE BOOL " " FORCE)
-        set(NANOGUI_BUILD_SHARED  OFF CACHE BOOL " " FORCE)
-        add_subdirectory("${NANOGUI_DIR}" "nanogui")
-        set(VIEWER_INCLUDE_DIRS
-          "${NANOGUI_DIR}/include"
-          "${NANOGUI_DIR}/ext/nanovg/src")
-        set(LIBIGL_VIEWER_EXTRA_LIBRARIES "nanogui" ${NANOGUI_EXTRA_LIBS})
-      else()
-        set(VIEWER_INCLUDE_DIRS "${NANOGUI_DIR}/ext/glfw/include")
-      endif()
-      list(APPEND VIEWER_INCLUDE_DIRS ${GLFW_INCLUDE_DIRS})
-      list(APPEND LIBIGL_INCLUDE_DIRS ${VIEWER_INCLUDE_DIRS})
-      list(APPEND LIBIGL_EXTRA_LIBRARIES ${LIBIGL_VIEWER_EXTRA_LIBRARIES})
-      if(LIBIGL_USE_STATIC_LIBRARY)
-        CompileIGL_Module("viewer" "")
-        if(LIBIGL_WITH_NANOGUI)
-          target_compile_definitions(igl_viewer PRIVATE -DIGL_VIEWER_WITH_NANOGUI)
-        endif()
-        target_include_directories(igl_viewer PRIVATE ${VIEWER_INCLUDE_DIRS})
-        if(NOT APPLE)
-          target_include_directories(igl_viewer PRIVATE "${NANOGUI_DIR}/ext/glew/include")
-        endif()
-      endif()
-    endif()
   endif()
 
 endif()
@@ -495,7 +480,6 @@ if(NOT ${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME})
   set(LIBIGL_PNG_EXTRA_LIBRARIES         ${LIBIGL_PNG_EXTRA_LIBRARIES}         PARENT_SCOPE)
   set(LIBIGL_TETGEN_EXTRA_LIBRARIES      ${LIBIGL_TETGEN_EXTRA_LIBRARIES}      PARENT_SCOPE)
   set(LIBIGL_TRIANGLE_EXTRA_LIBRARIES    ${LIBIGL_TRIANGLE_EXTRA_LIBRARIES}    PARENT_SCOPE)
-  set(LIBIGL_VIEWER_EXTRA_LIBRARIES      ${LIBIGL_VIEWER_EXTRA_LIBRARIES}      PARENT_SCOPE)
   set(LIBIGL_XML_EXTRA_LIBRARIES         ${LIBIGL_XML_EXTRA_LIBRARIES}         PARENT_SCOPE)
   set(LIBIGL_EXTRA_LIBRARIES ${LIBIGL_EXTRA_LIBRARIES} PARENT_SCOPE)
   set(LIBIGL_DEFINITIONS ${LIBIGL_DEFINITIONS} PARENT_SCOPE)

+ 1 - 2
tutorial/CMakeLists.txt

@@ -5,8 +5,7 @@ message(STATUS "CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}")
 
 ### libIGL options: choose between header only and compiled static library
 option(LIBIGL_USE_STATIC_LIBRARY "Use LibIGL as static library" ON)
-option(LIBIGL_WITH_VIEWER      "Use OpenGL viewer"  ON)
-option(LIBIGL_WITH_NANOGUI     "Use Nanogui menu"   OFF)
+option(LIBIGL_WITH_NANOGUI          "Use Nanogui menu"   OFF)
 
 ### libIGL options: choose your dependencies (by default everything is OFF, in this example we need the viewer) ###
 find_package(CGAL QUIET COMPONENTS Core)