Просмотр исходного кода

Merge pull request #978 from jdumas/multiple-views

Multiple viewports
Jérémie Dumas 6 лет назад
Родитель
Сommit
daf7d0707c
38 измененных файлов с 538 добавлено и 202 удалено
  1. 32 5
      include/igl/opengl/ViewerCore.cpp
  2. 17 1
      include/igl/opengl/ViewerCore.h
  3. 20 2
      include/igl/opengl/ViewerData.cpp
  4. 21 8
      include/igl/opengl/ViewerData.h
  5. 198 67
      include/igl/opengl/glfw/Viewer.cpp
  6. 64 7
      include/igl/opengl/glfw/Viewer.h
  7. 9 0
      include/igl/opengl/glfw/imgui/ImGuiHelpers.h
  8. 32 22
      include/igl/opengl/glfw/imgui/ImGuiMenu.cpp
  9. 9 5
      python/modules/py_igl_opengl_glfw.cpp
  10. 2 2
      tutorial/103_Events/main.cpp
  11. 0 1
      tutorial/106_ViewerMenu/main.cpp
  12. 5 0
      tutorial/108_MultipleViews/CMakeLists.txt
  13. 46 0
      tutorial/108_MultipleViews/main.cpp
  14. 1 1
      tutorial/205_Laplacian/main.cpp
  15. 4 4
      tutorial/206_GeodesicDistance/main.cpp
  16. 6 6
      tutorial/401_BiharmonicDeformation/main.cpp
  17. 6 6
      tutorial/402_PolyharmonicDeformation/main.cpp
  18. 4 4
      tutorial/403_BoundedBiharmonicWeights/main.cpp
  19. 6 6
      tutorial/404_DualQuaternionSkinning/main.cpp
  20. 4 4
      tutorial/405_AsRigidAsPossible/main.cpp
  21. 5 5
      tutorial/406_FastAutomaticSkinningTransformations/main.cpp
  22. 5 5
      tutorial/407_BiharmonicCoordinates/main.cpp
  23. 2 2
      tutorial/501_HarmonicParam/main.cpp
  24. 2 2
      tutorial/502_LSCMParam/main.cpp
  25. 2 2
      tutorial/503_ARAPParam/main.cpp
  26. 1 1
      tutorial/505_MIQ/main.cpp
  27. 1 1
      tutorial/506_FrameField/main.cpp
  28. 5 5
      tutorial/606_AmbientOcclusion/main.cpp
  29. 2 2
      tutorial/607_ScreenCapture/main.cpp
  30. 3 3
      tutorial/609_Boolean/main.cpp
  31. 4 4
      tutorial/703_Decimation/main.cpp
  32. 1 1
      tutorial/704_SignedDistance/main.cpp
  33. 2 2
      tutorial/707_SweptVolume/main.cpp
  34. 3 3
      tutorial/708_Picking/main.cpp
  35. 3 3
      tutorial/709_SLIM/main.cpp
  36. 2 2
      tutorial/710_SCAF/main.cpp
  37. 8 8
      tutorial/716_HeatGeodesics/main.cpp
  38. 1 0
      tutorial/CMakeLists.txt

+ 32 - 5
include/igl/opengl/ViewerCore.cpp

@@ -87,11 +87,16 @@ IGL_INLINE void igl::opengl::ViewerCore::get_scale_and_shift_to_fit_mesh(
 
 IGL_INLINE void igl::opengl::ViewerCore::clear_framebuffers()
 {
+  // The glScissor call ensures we only clear this core's buffers,
+  // (in case the user wants different background colors in each viewport.)
+  glScissor(viewport(0), viewport(1), viewport(2), viewport(3));
+  glEnable(GL_SCISSOR_TEST);
   glClearColor(background_color[0],
                background_color[1],
                background_color[2],
                background_color[3]);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+  glDisable(GL_SCISSOR_TEST);
 }
 
 IGL_INLINE void igl::opengl::ViewerCore::draw(
@@ -175,16 +180,16 @@ IGL_INLINE void igl::opengl::ViewerCore::draw(
   if (data.V.rows()>0)
   {
     // Render fill
-    if (data.show_faces)
+    if (is_set(data.show_faces))
     {
       // Texture
-      glUniform1f(texture_factori, data.show_texture ? 1.0f : 0.0f);
+      glUniform1f(texture_factori, is_set(data.show_texture) ? 1.0f : 0.0f);
       data.meshgl.draw_mesh(true);
       glUniform1f(texture_factori, 0.0f);
     }
 
     // Render wireframe
-    if (data.show_lines)
+    if (is_set(data.show_lines))
     {
       glLineWidth(data.line_width);
       glUniform4f(fixed_colori,
@@ -196,9 +201,9 @@ IGL_INLINE void igl::opengl::ViewerCore::draw(
     }
   }
 
-  if (data.show_overlay)
+  if (is_set(data.show_overlay))
   {
-    if (data.show_overlay_depth)
+    if (is_set(data.show_overlay_depth))
       glEnable(GL_DEPTH_TEST);
     else
       glDisable(GL_DEPTH_TEST);
@@ -347,6 +352,28 @@ IGL_INLINE void igl::opengl::ViewerCore::set_rotation_type(
   }
 }
 
+IGL_INLINE void igl::opengl::ViewerCore::set(unsigned int &property_mask, bool value) const
+{
+  if (!value)
+    unset(property_mask);
+  else
+    property_mask |= id;
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::unset(unsigned int &property_mask) const
+{
+  property_mask &= ~id;
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::toggle(unsigned int &property_mask) const
+{
+  property_mask ^= id;
+}
+
+IGL_INLINE bool igl::opengl::ViewerCore::is_set(unsigned int property_mask) const
+{
+  return (property_mask & id);
+}
 
 IGL_INLINE igl::opengl::ViewerCore::ViewerCore()
 {

+ 17 - 1
include/igl/opengl/ViewerCore.h

@@ -37,7 +37,6 @@ public:
   // Serialization code
   IGL_INLINE void InitSerialization();
 
-
   // ------------------- Camera control functions
 
   // Adjust the view to see the entire model
@@ -91,8 +90,25 @@ public:
   };
   IGL_INLINE void set_rotation_type(const RotationType & value);
 
+  // ------------------- Option helpers
+
+  // Set a ViewerData visualization option for this viewport
+  IGL_INLINE void set(unsigned int &property_mask, bool value = true) const;
+
+  // Unset a ViewerData visualization option for this viewport
+  IGL_INLINE void unset(unsigned int &property_mask) const;
+
+  // Toggle a ViewerData visualization option for this viewport
+  IGL_INLINE void toggle(unsigned int &property_mask) const;
+
+  // Check whether a ViewerData visualization option is set for this viewport
+  IGL_INLINE bool is_set(unsigned int property_mask) const;
+
   // ------------------- Properties
 
+  // Unique identifier
+  unsigned int id = 1u;
+
   // Colors
   Eigen::Vector4f background_color;
 

+ 20 - 2
include/igl/opengl/ViewerData.cpp

@@ -7,6 +7,7 @@
 // obtain one at http://mozilla.org/MPL/2.0/.
 
 #include "ViewerData.h"
+#include "ViewerCore.h"
 
 #include "../per_face_normals.h"
 #include "../material_colors.h"
@@ -30,7 +31,8 @@ IGL_INLINE igl::opengl::ViewerData::ViewerData()
   line_width(0.5f),
   line_color(0,0,0,1),
   shininess(35.0f),
-  id(-1)
+  id(-1),
+  is_visible(1)
 {
   clear();
 };
@@ -112,6 +114,23 @@ IGL_INLINE void igl::opengl::ViewerData::set_normals(const Eigen::MatrixXd& N)
   dirty |= MeshGL::DIRTY_NORMAL;
 }
 
+IGL_INLINE void igl::opengl::ViewerData::set_visible(bool value, unsigned int core_id /*= 1*/)
+{
+  if (value)
+    is_visible |= core_id;
+  else
+  is_visible &= ~core_id;
+}
+
+IGL_INLINE void igl::opengl::ViewerData::copy_options(const ViewerCore &from, const ViewerCore &to)
+{
+  to.set(show_overlay      , from.is_set(show_overlay)      );
+  to.set(show_overlay_depth, from.is_set(show_overlay_depth));
+  to.set(show_texture      , from.is_set(show_texture)      );
+  to.set(show_faces        , from.is_set(show_faces)        );
+  to.set(show_lines        , from.is_set(show_lines)        );
+}
+
 IGL_INLINE void igl::opengl::ViewerData::set_colors(const Eigen::MatrixXd &C)
 {
   using namespace std;
@@ -213,7 +232,6 @@ IGL_INLINE void igl::opengl::ViewerData::set_uv(const Eigen::MatrixXd& UV_V, con
   dirty |= MeshGL::DIRTY_UV;
 }
 
-
 IGL_INLINE void igl::opengl::ViewerData::set_texture(
   const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& R,
   const Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& G,

+ 21 - 8
include/igl/opengl/ViewerData.h

@@ -34,6 +34,9 @@ namespace igl
 namespace opengl
 {
 
+// Forward declaration
+class ViewerCore;
+
 class ViewerData
 {
 public:
@@ -50,6 +53,8 @@ public:
   IGL_INLINE void set_vertices(const Eigen::MatrixXd& V);
   IGL_INLINE void set_normals(const Eigen::MatrixXd& N);
 
+  IGL_INLINE void set_visible(bool value, unsigned int core_id = 1);
+
   // Set the color of the mesh
   //
   // Inputs:
@@ -136,6 +141,9 @@ public:
   // Generates a default grid texture
   IGL_INLINE void grid_texture();
 
+  // Copy visualization options from one viewport to another
+  IGL_INLINE void copy_options(const ViewerCore &from, const ViewerCore &to);
+
   Eigen::MatrixXd V; // Vertices of the current mesh (#V x 3)
   Eigen::MatrixXi F; // Faces of the mesh (#F x 3)
 
@@ -187,16 +195,21 @@ public:
   // Enable per-face or per-vertex properties
   bool face_based;
 
-  // Visualization options
-  bool show_overlay;
-  bool show_overlay_depth;
-  bool show_texture;
-  bool show_faces;
-  bool show_lines;
-  bool show_vertid;
-  bool show_faceid;
+  // Invert mesh normals
   bool invert_normals;
 
+  // Visualization options
+  // Each option is a binary mask specifying on which viewport each option is set.
+  // When using a single viewport, standard boolean can still be used for simplicity.
+  unsigned int is_visible;
+  unsigned int show_overlay;
+  unsigned int show_overlay_depth;
+  unsigned int show_texture;
+  unsigned int show_faces;
+  unsigned int show_lines;
+  bool show_vertid; // shared across viewports for now
+  bool show_faceid; // shared across viewports for now
+
   // Point size / line width
   float point_size;
   float line_width;

+ 198 - 67
include/igl/opengl/glfw/Viewer.cpp

@@ -123,16 +123,18 @@ namespace opengl
 namespace glfw
 {
 
-  IGL_INLINE int Viewer::launch(bool resizable,bool fullscreen)
+  IGL_INLINE int Viewer::launch(bool resizable /*= true*/, bool fullscreen /*= false*/,
+    const std::string &name, int windowWidth /*= 1280*/, int windowHeight /*= 800*/)
   {
     // TODO return values are being ignored...
-    launch_init(resizable,fullscreen);
+    launch_init(resizable,fullscreen,name,windowWidth,windowHeight);
     launch_rendering(true);
     launch_shut();
     return EXIT_SUCCESS;
   }
 
-  IGL_INLINE int  Viewer::launch_init(bool resizable,bool fullscreen)
+  IGL_INLINE int  Viewer::launch_init(bool resizable, bool fullscreen,
+    const std::string &name, int windowWidth, int windowHeight)
   {
     glfwSetErrorCallback(glfw_error_callback);
     if (!glfwInit())
@@ -150,15 +152,11 @@ namespace glfw
     {
       GLFWmonitor *monitor = glfwGetPrimaryMonitor();
       const GLFWvidmode *mode = glfwGetVideoMode(monitor);
-      window = glfwCreateWindow(mode->width,mode->height,"libigl viewer",monitor,nullptr);
+      window = glfwCreateWindow(mode->width,mode->height,name.c_str(),monitor,nullptr);
     }
     else
     {
-      if (core.viewport.tail<2>().any()) {
-        window = glfwCreateWindow(core.viewport(2),core.viewport(3),"libigl viewer",nullptr,nullptr);
-      } else {
-        window = glfwCreateWindow(1280,800,"libigl viewer",nullptr,nullptr);
-      }
+      window = glfwCreateWindow(windowWidth,windowHeight,name.c_str(),nullptr,nullptr);
     }
     if (!window)
     {
@@ -198,17 +196,15 @@ namespace glfw
     glfwGetFramebufferSize(window, &width, &height);
     int width_window, height_window;
     glfwGetWindowSize(window, &width_window, &height_window);
-    highdpi = width/width_window;
+    highdpi = windowWidth/width_window;
     glfw_window_size(window,width_window,height_window);
     //opengl.init();
-    core.align_camera_center(data().V,data().F);
+    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);
@@ -217,16 +213,15 @@ namespace glfw
     int frame_counter = 0;
     while (!glfwWindowShouldClose(window))
     {
-
       double tic = get_seconds();
       draw();
       glfwSwapBuffers(window);
-      if(core.is_animating || frame_counter++ < num_extra_frames)
+      if(core().is_animating || frame_counter++ < num_extra_frames)
       {
         glfwPollEvents();
         // In microseconds
         double duration = 1000000.*(get_seconds()-tic);
-        const double min_duration = 1000000./core.animation_max_fps;
+        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)));
@@ -258,7 +253,7 @@ namespace glfw
     {
       data.meshgl.free();
     }
-    core.shut();
+    core().shut(); // Doesn't do anything
     shutdown_plugins();
     glfwDestroyWindow(window);
     glfwTerminate();
@@ -267,7 +262,7 @@ namespace glfw
 
   IGL_INLINE void Viewer::init()
   {
-    core.init();
+    core().init(); // Doesn't do anything
 
     if (callback_init)
       if (callback_init(*this))
@@ -296,11 +291,16 @@ namespace glfw
   IGL_INLINE Viewer::Viewer():
     data_list(1),
     selected_data_index(0),
-    next_data_id(1)
+    next_data_id(1),
+    selected_core_index(0),
+    next_core_id(2)
   {
     window = nullptr;
     data_list.front().id = 0;
 
+    core_list.emplace_back(ViewerCore());
+    core_list.front().id = 1;
+
     // Temporary variables initialization
     down = false;
     hack_never_moved = true;
@@ -431,8 +431,8 @@ namespace glfw
     {
       data().grid_texture();
     }
-
-    core.align_camera_center(data().V,data().F);
+    for(int i=0;i<core_list.size(); i++)
+        core_list[i].align_camera_center(data().V,data().F);
 
     for (unsigned int i = 0; i<plugins.size(); ++i)
       if (plugins[i]->post_load())
@@ -504,7 +504,7 @@ namespace glfw
       case 'A':
       case 'a':
       {
-        core.is_animating = !core.is_animating;
+        core().is_animating = !core().is_animating;
         return true;
       }
       case 'F':
@@ -523,19 +523,19 @@ namespace glfw
       case 'L':
       case 'l':
       {
-        data().show_lines = !data().show_lines;
+        core().toggle(data().show_lines);
         return true;
       }
       case 'O':
       case 'o':
       {
-        core.orthographic = !core.orthographic;
+        core().orthographic = !core().orthographic;
         return true;
       }
       case 'T':
       case 't':
       {
-        data().show_faces = !data().show_faces;
+        core().toggle(data().show_faces);
         return true;
       }
       case 'Z':
@@ -546,10 +546,10 @@ namespace glfw
       case '[':
       case ']':
       {
-        if(core.rotation_type == ViewerCore::ROTATION_TYPE_TRACKBALL)
-          core.set_rotation_type(ViewerCore::ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP);
+        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);
+          core().set_rotation_type(ViewerCore::ROTATION_TYPE_TRACKBALL);
 
         return true;
       }
@@ -560,6 +560,13 @@ namespace glfw
           (selected_data_index + data_list.size() + (unicode_key=='>'?1:-1))%data_list.size();
         return true;
       }
+      case '{':
+      case '}':
+      {
+        selected_core_index =
+          (selected_core_index + core_list.size() + (unicode_key=='}'?1:-1))%core_list.size();
+        return true;
+      }
       case ';':
         data().show_vertid = !data().show_vertid;
         return true;
@@ -597,6 +604,25 @@ namespace glfw
     return false;
   }
 
+  IGL_INLINE void Viewer::select_hovered_core()
+  {
+    int width_window, height_window;
+    glfwGetFramebufferSize(window, &width_window, &height_window);
+    for (int i = 0; i < core_list.size(); i++)
+    {
+      Eigen::Vector4f viewport = core_list[i].viewport;
+
+      if ((current_mouse_x > viewport[0]) &&
+          (current_mouse_x < viewport[0] + viewport[2]) &&
+          ((height_window - current_mouse_y) > viewport[1]) &&
+          ((height_window - current_mouse_y) < viewport[1] + viewport[3]))
+      {
+        selected_core_index = i;
+        break;
+      }
+    }
+  }
+
   IGL_INLINE bool Viewer::mouse_down(MouseButton button,int modifier)
   {
     // Remember mouse location at down even if used by callback/plugin
@@ -613,7 +639,10 @@ namespace glfw
 
     down = true;
 
-    down_translation = core.camera_translation;
+    // Select the core containing the click location.
+    select_hovered_core();
+
+    down_translation = core().camera_translation;
 
 
     // Initialization code for the trackball
@@ -629,18 +658,18 @@ namespace glfw
     Eigen::Vector3f coord =
       igl::project(
         Eigen::Vector3f(center(0),center(1),center(2)),
-        core.view,
-        core.proj,
-        core.viewport);
+        core().view,
+        core().proj,
+        core().viewport);
     down_mouse_z = coord[2];
-    down_rotation = core.trackball_angle;
+    down_rotation = core().trackball_angle;
 
     mouse_mode = MouseMode::Rotation;
 
     switch (button)
     {
       case MouseButton::Left:
-        if (core.rotation_type == ViewerCore::ROTATION_TYPE_NO_ROTATION) {
+        if (core().rotation_type == ViewerCore::ROTATION_TYPE_NO_ROTATION) {
           mouse_mode = MouseMode::Translation;
         } else {
           mouse_mode = MouseMode::Rotation;
@@ -692,16 +721,21 @@ namespace glfw
         return true;
 
     if (callback_mouse_move)
-      if (callback_mouse_move(*this,mouse_x,mouse_y))
+      if (callback_mouse_move(*this, mouse_x, mouse_y))
         return true;
 
+
     if (down)
     {
+      // We need the window height to transform the mouse click coordinates into viewport-mouse-click coordinates
+      // for igl::trackball and igl::two_axis_valuator_fixed_up
+      int width_window, height_window;
+      glfwGetFramebufferSize(window, &width_window, &height_window);
       switch (mouse_mode)
       {
         case MouseMode::Rotation:
         {
-          switch(core.rotation_type)
+          switch(core().rotation_type)
           {
             default:
               assert(false && "Unknown rotation type");
@@ -709,26 +743,29 @@ namespace glfw
               break;
             case ViewerCore::ROTATION_TYPE_TRACKBALL:
               igl::trackball(
-                core.viewport(2),
-                core.viewport(3),
+                core().viewport(2),
+                core().viewport(3),
                 2.0f,
                 down_rotation,
-                down_mouse_x,
-                down_mouse_y,
-                mouse_x,
-                mouse_y,
-                core.trackball_angle);
+                down_mouse_x - core().viewport(0),
+                down_mouse_y - (height_window - core().viewport(1) - core().viewport(3)),
+                mouse_x - core().viewport(0),
+                mouse_y - (height_window - core().viewport(1) - core().viewport(3)),
+                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),
+                core().viewport(2),core().viewport(3),
                 2.0,
                 down_rotation,
-                down_mouse_x, down_mouse_y, mouse_x, mouse_y,
-                core.trackball_angle);
+                down_mouse_x - core().viewport(0),
+                down_mouse_y - (height_window - core().viewport(1) - core().viewport(3)),
+                mouse_x - core().viewport(0),
+                mouse_y - (height_window - core().viewport(1) - core().viewport(3)),
+                core().trackball_angle);
               break;
           }
-          //Eigen::Vector4f snapq = core.trackball_angle;
+          //Eigen::Vector4f snapq = core().trackball_angle;
 
           break;
         }
@@ -736,18 +773,18 @@ namespace glfw
         case MouseMode::Translation:
         {
           //translation
-          Eigen::Vector3f pos1 = igl::unproject(Eigen::Vector3f(mouse_x, core.viewport[3] - mouse_y, down_mouse_z), core.view, 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.proj, core.viewport);
+          Eigen::Vector3f pos1 = igl::unproject(Eigen::Vector3f(mouse_x, core().viewport[3] - mouse_y, down_mouse_z), core().view, 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().proj, core().viewport);
 
           Eigen::Vector3f diff = pos1 - pos0;
-          core.camera_translation = down_translation + Eigen::Vector3f(diff[0],diff[1],diff[2]);
+          core().camera_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;
+          core().camera_zoom *= 1 + delta;
           down_mouse_x = mouse_x;
           down_mouse_y = mouse_y;
           break;
@@ -762,6 +799,10 @@ namespace glfw
 
   IGL_INLINE bool Viewer::mouse_scroll(float delta_y)
   {
+    // Direct the scrolling operation to the appropriate viewport
+    // (unless the core selection is locked by an ongoing mouse interaction).
+    if (!down)
+      select_hovered_core();
     scroll_position += delta_y;
 
     for (unsigned int i = 0; i<plugins.size(); ++i)
@@ -777,7 +818,7 @@ namespace glfw
     {
       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);
+      core().camera_zoom = (core().camera_zoom * mult > min_zoom ? core().camera_zoom * mult : min_zoom);
     }
     return true;
   }
@@ -792,7 +833,7 @@ namespace glfw
 
   IGL_INLINE bool Viewer::load_scene(std::string fname)
   {
-    igl::deserialize(core,"Core",fname.c_str());
+    igl::deserialize(core(),"Core",fname.c_str());
     igl::deserialize(data(),"Data",fname.c_str());
     return true;
   }
@@ -807,7 +848,7 @@ namespace glfw
 
   IGL_INLINE bool Viewer::save_scene(std::string fname)
   {
-    igl::serialize(core,"Core",fname.c_str(),true);
+    igl::serialize(core(),"Core",fname.c_str(),true);
     igl::serialize(data(),"Data",fname.c_str());
 
     return true;
@@ -832,7 +873,11 @@ namespace glfw
       highdpi=highdpi_tmp;
     }
 
-    core.clear_framebuffers();
+    for (auto& core : core_list)
+    {
+      core.clear_framebuffers();
+    }
+
     for (unsigned int i = 0; i<plugins.size(); ++i)
     {
       if (plugins[i]->pre_draw())
@@ -847,9 +892,16 @@ namespace glfw
         return;
       }
     }
-    for(int i = 0;i<data_list.size();i++)
+
+    for (auto& core : core_list)
     {
-      core.draw(data_list[i]);
+      for (auto& mesh : data_list)
+      {
+        if (mesh.is_visible & core.id)
+        {
+          core.draw(mesh);
+        }
+      }
     }
     for (unsigned int i = 0; i<plugins.size(); ++i)
     {
@@ -877,17 +929,29 @@ namespace glfw
 
   IGL_INLINE void Viewer::post_resize(int w,int h)
   {
-    core.viewport = Eigen::Vector4f(0,0,w,h);
+    if (core_list.size() == 1)
+    {
+      core().viewport = Eigen::Vector4f(0,0,w,h);
+    }
+    else
+    {
+      // It is up to the user to define the behavior of the post_resize() function
+      // when there are multiple viewports (through the `callback_post_resize` callback)
+    }
     for (unsigned int i = 0; i<plugins.size(); ++i)
     {
       plugins[i]->post_resize(w, h);
     }
+    if (callback_post_resize)
+    {
+      callback_post_resize(*this, 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);
+    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()
@@ -910,22 +974,32 @@ namespace glfw
     this->save_mesh_to_file(fname.c_str());
   }
 
-  IGL_INLINE ViewerData& Viewer::data()
+  IGL_INLINE ViewerData& Viewer::data(int mesh_id /*= -1*/)
   {
     assert(!data_list.empty() && "data_list should never be empty");
-    assert(
-      (selected_data_index >= 0 && selected_data_index < data_list.size()) &&
-      "selected_data_index should be in bounds");
-    return data_list[selected_data_index];
+    int index;
+    if (mesh_id == -1)
+      index = selected_data_index;
+    else
+      index = mesh_index(mesh_id);
+
+    assert((index >= 0 && index < data_list.size()) &&
+      "selected_data_index or mesh_id should be in bounds");
+    return data_list[index];
   }
 
-  IGL_INLINE int Viewer::append_mesh()
+  IGL_INLINE int Viewer::append_mesh(bool visible /*= true*/)
   {
     assert(data_list.size() >= 1);
 
     data_list.emplace_back();
     selected_data_index = data_list.size()-1;
     data_list.back().id = next_data_id++;
+    if (visible)
+        for (int i = 0; i < core_list.size(); i++)
+            data_list.back().set_visible(true, core_list[i].id);
+    else
+        data_list.back().is_visible = 0;
     return data_list.back().id;
   }
 
@@ -940,10 +1014,11 @@ namespace glfw
     }
     data_list[index].meshgl.free();
     data_list.erase(data_list.begin() + index);
-    if(selected_data_index >= index && selected_data_index>0)
+    if(selected_data_index >= index && selected_data_index > 0)
     {
       selected_data_index--;
     }
+
     return true;
   }
 
@@ -956,6 +1031,62 @@ namespace glfw
     return 0;
   }
 
+  IGL_INLINE ViewerCore& Viewer::core(unsigned core_id /*= 0*/)
+  {
+    assert(!core_list.empty() && "core_list should never be empty");
+    int core_index;
+    if (core_id == 0)
+      core_index = selected_core_index;
+    else
+      core_index = this->core_index(core_id);
+    assert((core_index >= 0 && core_index < core_list.size()) && "selected_core_index should be in bounds");
+    return core_list[core_index];
+  }
+
+  IGL_INLINE bool Viewer::erase_core(const size_t index)
+  {
+    assert((index >= 0 && index < core_list.size()) && "index should be in bounds");
+    assert(data_list.size() >= 1);
+    if (core_list.size() == 1)
+    {
+      // Cannot remove last viewport
+      return false;
+    }
+    core_list[index].shut(); // does nothing
+    core_list.erase(core_list.begin() + index);
+    if (selected_core_index >= index && selected_core_index > 0)
+    {
+      selected_core_index--;
+    }
+    return true;
+  }
+
+  IGL_INLINE size_t Viewer::core_index(const int id) const {
+    for (size_t i = 0; i < core_list.size(); ++i)
+    {
+      if (core_list[i].id == id)
+        return i;
+    }
+    return 0;
+  }
+
+  IGL_INLINE int Viewer::append_core(Eigen::Vector4f viewport, bool append_empty /*= false*/)
+  {
+    core_list.push_back(core()); // copies the previous active core and only changes the viewport
+    core_list.back().viewport = viewport;
+    core_list.back().id = next_core_id;
+    next_core_id <<= 1;
+    if (!append_empty)
+    {
+      for (auto &data : data_list)
+      {
+        data.set_visible(true, core_list.back().id);
+        data.copy_options(core(), core_list.back());
+      }
+    }
+    selected_core_index = core_list.size()-1;
+    return core_list.back().id;
+  }
 
 } // end namespace
 } // end namespace

+ 64 - 7
include/igl/opengl/glfw/Viewer.h

@@ -45,8 +45,8 @@ namespace glfw
     // UI Enumerations
     enum class MouseButton {Left, Middle, Right};
     enum class MouseMode { None, Rotation, Zoom, Pan, Translation} mouse_mode;
-    IGL_INLINE int launch(bool resizable = true,bool fullscreen = false);
-    IGL_INLINE int launch_init(bool resizable = true,bool fullscreen = false);
+    IGL_INLINE int launch(bool resizable = true, bool fullscreen = false, const std::string &name = "libigl viewer", int width = 1280, int height = 800);
+    IGL_INLINE int launch_init(bool resizable = true, bool fullscreen = false, const std::string &name = "libigl viewer", int width = 1280, int height = 800);
     IGL_INLINE bool launch_rendering(bool loop = true);
     IGL_INLINE void launch_shut();
     IGL_INLINE void init();
@@ -79,17 +79,28 @@ namespace glfw
     IGL_INLINE void snap_to_canonical_quaternion();
     IGL_INLINE void open_dialog_load_mesh();
     IGL_INLINE void open_dialog_save_mesh();
-    IGL_INLINE ViewerData& data();
 
-    // Append a new "slot" for a mesh (i.e., create empty entires at the end of
+    ////////////////////////
+    // Multi-mesh methods //
+    ////////////////////////
+
+    // Return the current mesh, or the mesh corresponding to a given unique identifier
+    //
+    // Inputs:
+    //   mesh_id  unique identifier associated to the desired mesh (current mesh if -1)
+    IGL_INLINE ViewerData& data(int mesh_id = -1);
+
+    // Append a new "slot" for a mesh (i.e., create empty entries at the end of
     // the data_list and opengl_state_list.
     //
+    // Inputs:
+    //   visible  If true, the new mesh is set to be visible on all existing viewports
     // Returns the id of the last appended mesh
     //
     // Side Effects:
     //   selected_data_index is set this newly created, last entry (i.e.,
     //   #meshes-1)
-    IGL_INLINE int append_mesh();
+    IGL_INLINE int append_mesh(bool visible = true);
 
     // Erase a mesh (i.e., its corresponding data and state entires in data_list
     // and opengl_state_list)
@@ -113,6 +124,47 @@ namespace glfw
     // Returns 0 if not found
     IGL_INLINE size_t mesh_index(const int id) const;
 
+    ////////////////////////////
+    // Multi-viewport methods //
+    ////////////////////////////
+
+    // Return the current viewport, or the viewport corresponding to a given unique identifier
+    //
+    // Inputs:
+    //   core_id  unique identifier corresponding to the desired viewport (current viewport if 0)
+    IGL_INLINE ViewerCore& core(unsigned core_id = 0);
+
+    // Append a new "slot" for a viewport (i.e., copy properties of the current viewport, only
+    // changing the viewport size/position)
+    //
+    // Inputs:
+    //   viewport      Vector specifying the viewport origin and size in screen coordinates.
+    //   append_empty  If true, existing meshes are hidden on the new viewport.
+    //
+    // Returns the unique id of the newly inserted viewport. There can be a maximum of 31
+    //   viewports created in the same viewport. Erasing a viewport does not change the id of
+    //   other existing viewports
+    IGL_INLINE int append_core(Eigen::Vector4f viewport, bool append_empty = false);
+
+    // Erase a viewport
+    //
+    // Inputs:
+    //   index  index of the viewport to erase
+    IGL_INLINE bool erase_core(const size_t index);
+
+    // Retrieve viewport index from its unique identifier
+    // Returns 0 if not found
+    IGL_INLINE size_t core_index(const int id) const;
+
+    // Change selected_core_index to the viewport containing the mouse
+    // (current_mouse_x, current_mouse_y)
+    IGL_INLINE void select_hovered_core();
+
+public:
+    //////////////////////
+    // Member variables //
+    //////////////////////
+
     // Alec: I call this data_list instead of just data to avoid confusion with
     // old "data" variable.
     // Stores all the data that should be visualized
@@ -121,8 +173,12 @@ namespace glfw
     size_t selected_data_index;
     int next_data_id;
     GLFWwindow* window;
+
     // Stores all the viewing options
-    ViewerCore core;
+    std::vector<ViewerCore> core_list;
+    size_t selected_core_index;
+    int next_core_id;
+
     // List of registered plugins
     std::vector<ViewerPlugin*> plugins;
     // Temporary data stored when the mouse button is pressed
@@ -148,6 +204,7 @@ namespace glfw
     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;
+    std::function<bool(Viewer& viewer, int w, int h)> callback_post_resize;
     // THESE SHOULD BE DEPRECATED:
     std::function<bool(Viewer& viewer, unsigned int key, int modifiers)> callback_key_down;
     std::function<bool(Viewer& viewer, unsigned int key, int modifiers)> callback_key_up;
@@ -164,7 +221,7 @@ namespace glfw
     void* callback_key_up_data;
 
   public:
-      EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
   };
 
 } // end namespace

+ 9 - 0
include/igl/opengl/glfw/imgui/ImGuiHelpers.h

@@ -101,6 +101,15 @@ inline bool SliderScalar(const char *label, T* value, T min = 0, T max = 0, cons
   return SliderScalar(label, ImGuiDataTypeTraits<T>::value, value, &min, &max, fmt);
 }
 
+template<typename Getter, typename Setter>
+inline bool Checkbox(const char* label, Getter get, Setter set)
+{
+  bool value = get();
+  bool ret = ImGui::Checkbox(label, &value);
+  set(value);
+  return ret;
+}
+
 } // namespace ImGui
 
 #endif // IGL_OPENGL_GLFW_IMGUI_IMGUIHELPERS_H

+ 32 - 22
include/igl/opengl/glfw/imgui/ImGuiMenu.cpp

@@ -7,6 +7,7 @@
 // obtain one at http://mozilla.org/MPL/2.0/.
 ////////////////////////////////////////////////////////////////////////////////
 #include "ImGuiMenu.h"
+#include "ImGuiHelpers.h"
 #include <igl/project.h>
 #include <imgui/imgui.h>
 #include <imgui_impl_glfw.h>
@@ -219,7 +220,7 @@ IGL_INLINE void ImGuiMenu::draw_viewer_menu()
   {
     if (ImGui::Button("Center object", ImVec2(-1, 0)))
     {
-      viewer->core.align_camera_center(viewer->data().V, viewer->data().F);
+      viewer->core().align_camera_center(viewer->data().V, viewer->data().F);
     }
     if (ImGui::Button("Snap canonical view", ImVec2(-1, 0)))
     {
@@ -228,39 +229,48 @@ IGL_INLINE void ImGuiMenu::draw_viewer_menu()
 
     // Zoom
     ImGui::PushItemWidth(80 * menu_scaling());
-    ImGui::DragFloat("Zoom", &(viewer->core.camera_zoom), 0.05f, 0.1f, 20.0f);
+    ImGui::DragFloat("Zoom", &(viewer->core().camera_zoom), 0.05f, 0.1f, 20.0f);
 
     // Select rotation type
-    int rotation_type = static_cast<int>(viewer->core.rotation_type);
+    int rotation_type = static_cast<int>(viewer->core().rotation_type);
     static Eigen::Quaternionf trackball_angle = Eigen::Quaternionf::Identity();
     static bool orthographic = true;
     if (ImGui::Combo("Camera Type", &rotation_type, "Trackball\0Two Axes\0002D Mode\0\0"))
     {
       using RT = igl::opengl::ViewerCore::RotationType;
       auto new_type = static_cast<RT>(rotation_type);
-      if (new_type != viewer->core.rotation_type)
+      if (new_type != viewer->core().rotation_type)
       {
         if (new_type == RT::ROTATION_TYPE_NO_ROTATION)
         {
-          trackball_angle = viewer->core.trackball_angle;
-          orthographic = viewer->core.orthographic;
-          viewer->core.trackball_angle = Eigen::Quaternionf::Identity();
-          viewer->core.orthographic = true;
+          trackball_angle = viewer->core().trackball_angle;
+          orthographic = viewer->core().orthographic;
+          viewer->core().trackball_angle = Eigen::Quaternionf::Identity();
+          viewer->core().orthographic = true;
         }
-        else if (viewer->core.rotation_type == RT::ROTATION_TYPE_NO_ROTATION)
+        else if (viewer->core().rotation_type == RT::ROTATION_TYPE_NO_ROTATION)
         {
-          viewer->core.trackball_angle = trackball_angle;
-          viewer->core.orthographic = orthographic;
+          viewer->core().trackball_angle = trackball_angle;
+          viewer->core().orthographic = orthographic;
         }
-        viewer->core.set_rotation_type(new_type);
+        viewer->core().set_rotation_type(new_type);
       }
     }
 
     // Orthographic view
-    ImGui::Checkbox("Orthographic view", &(viewer->core.orthographic));
+    ImGui::Checkbox("Orthographic view", &(viewer->core().orthographic));
     ImGui::PopItemWidth();
   }
 
+  // Helper for setting viewport specific mesh options
+  auto make_checkbox = [&](const char *label, unsigned int &option)
+  {
+    return ImGui::Checkbox(label,
+      [&]() { return viewer->core().is_set(option); },
+      [&](bool value) { return viewer->core().set(option, value); }
+    );
+  };
+
   // Draw options
   if (ImGui::CollapsingHeader("Draw Options", ImGuiTreeNodeFlags_DefaultOpen))
   {
@@ -268,14 +278,14 @@ IGL_INLINE void ImGuiMenu::draw_viewer_menu()
     {
       viewer->data().dirty = MeshGL::DIRTY_ALL;
     }
-    ImGui::Checkbox("Show texture", &(viewer->data().show_texture));
+    make_checkbox("Show texture", viewer->data().show_texture);
     if (ImGui::Checkbox("Invert normals", &(viewer->data().invert_normals)))
     {
       viewer->data().dirty |= igl::opengl::MeshGL::DIRTY_NORMAL;
     }
-    ImGui::Checkbox("Show overlay", &(viewer->data().show_overlay));
-    ImGui::Checkbox("Show overlay depth", &(viewer->data().show_overlay_depth));
-    ImGui::ColorEdit4("Background", viewer->core.background_color.data(),
+    make_checkbox("Show overlay", viewer->data().show_overlay);
+    make_checkbox("Show overlay depth", viewer->data().show_overlay_depth);
+    ImGui::ColorEdit4("Background", viewer->core().background_color.data(),
         ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_PickerHueWheel);
     ImGui::ColorEdit4("Line color", viewer->data().line_color.data(),
         ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_PickerHueWheel);
@@ -287,8 +297,8 @@ IGL_INLINE void ImGuiMenu::draw_viewer_menu()
   // Overlays
   if (ImGui::CollapsingHeader("Overlays", ImGuiTreeNodeFlags_DefaultOpen))
   {
-    ImGui::Checkbox("Wireframe", &(viewer->data().show_lines));
-    ImGui::Checkbox("Fill", &(viewer->data().show_faces));
+    make_checkbox("Wireframe", viewer->data().show_lines);
+    make_checkbox("Fill", viewer->data().show_faces);
     ImGui::Checkbox("Show vertex labels", &(viewer->data().show_vertid));
     ImGui::Checkbox("Show faces labels", &(viewer->data().show_faceid));
   }
@@ -357,14 +367,14 @@ IGL_INLINE void ImGuiMenu::draw_labels(const igl::opengl::ViewerData &data)
 
 IGL_INLINE void ImGuiMenu::draw_text(Eigen::Vector3d pos, Eigen::Vector3d normal, const std::string &text)
 {
-  pos += normal * 0.005f * viewer->core.object_scale;
+  pos += normal * 0.005f * viewer->core().object_scale;
   Eigen::Vector3f coord = igl::project(Eigen::Vector3f(pos.cast<float>()),
-    viewer->core.view, viewer->core.proj, viewer->core.viewport);
+    viewer->core().view, viewer->core().proj, viewer->core().viewport);
 
   // Draw text labels slightly bigger than normal text
   ImDrawList* drawList = ImGui::GetWindowDrawList();
   drawList->AddText(ImGui::GetFont(), ImGui::GetFontSize() * 1.2,
-      ImVec2(coord[0]/pixel_ratio_, (viewer->core.viewport[3] - coord[1])/pixel_ratio_),
+      ImVec2(coord[0]/pixel_ratio_, (viewer->core().viewport[3] - coord[1])/pixel_ratio_),
       ImGui::GetColorU32(ImVec4(0, 0, 10, 255)),
       &text[0], &text[0] + text.size());
 }

+ 9 - 5
python/modules/py_igl_opengl_glfw.cpp

@@ -380,7 +380,7 @@ py::class_<igl::opengl::ViewerCore> viewercore_class(me, "ViewerCore");
 
     .def("data", &igl::opengl::glfw::Viewer::data,pybind11::return_value_policy::reference)
 
-    .def_readwrite("core", &igl::opengl::glfw::Viewer::core)
+    //.def_readwrite("core", &igl::opengl::glfw::Viewer::core)
     //.def_readwrite("opengl", &igl::opengl::glfw::Viewer::opengl)
 
     #ifdef IGL_VIEWER_WITH_NANOGUI
@@ -388,15 +388,19 @@ py::class_<igl::opengl::ViewerCore> viewercore_class(me, "ViewerCore");
     .def_readwrite("screen", &igl::opengl::glfw::Viewer::screen)
     #endif
 
-    .def("launch", &igl::opengl::glfw::Viewer::launch, py::arg("resizable") = true, py::arg("fullscreen") = false)
-    .def("launch_init", &igl::opengl::glfw::Viewer::launch_init, py::arg("resizable") = true, py::arg("fullscreen") = false)
+    .def("launch", &igl::opengl::glfw::Viewer::launch, py::arg("resizable") = true,
+      py::arg("fullscreen") = false, py::arg("name") = "libigl viewer",
+      py::arg("windowWidth") = 1280, py::arg("windowHeight") = 800)
+    .def("launch_init", &igl::opengl::glfw::Viewer::launch_init, py::arg("resizable") = true,
+      py::arg("fullscreen") = false, py::arg("name") = "libigl viewer",
+      py::arg("windowWidth") = 1280, py::arg("windowHeight") = 800)
     .def("launch_rendering", &igl::opengl::glfw::Viewer::launch_rendering, py::arg("loop") = true)
     .def("launch_shut", &igl::opengl::glfw::Viewer::launch_shut)
     .def("init", &igl::opengl::glfw::Viewer::init)
     .def("serialize", [](igl::opengl::glfw::Viewer& viewer)
     {
       std::vector<char> a;
-      igl::serialize(viewer.core,"Core",a);
+      //igl::serialize(viewer.core,"Core",a);
       //igl::serialize(viewer.data,"Data",a); TODO
 
       return a;
@@ -404,7 +408,7 @@ py::class_<igl::opengl::ViewerCore> viewercore_class(me, "ViewerCore");
 
     .def("deserialize", [](igl::opengl::glfw::Viewer& viewer, const std::vector<char>& a)
     {
-      igl::deserialize(viewer.core,"Core",a);
+      //igl::deserialize(viewer.core,"Core",a);
       //igl::deserialize(viewer.data,"Data",a);
       return;
     })

+ 2 - 2
tutorial/103_Events/main.cpp

@@ -18,13 +18,13 @@ bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier
     // If a mesh is already displayed, draw_mesh returns an error if the given V and
     // F have size different than the current ones
     viewer.data().set_mesh(V1, F1);
-    viewer.core.align_camera_center(V1,F1);
+    viewer.core().align_camera_center(V1,F1);
   }
   else if (key == '2')
   {
     viewer.data().clear();
     viewer.data().set_mesh(V2, F2);
-    viewer.core.align_camera_center(V2,F2);
+    viewer.core().align_camera_center(V2,F2);
   }
 
   return false;

+ 0 - 1
tutorial/106_ViewerMenu/main.cpp

@@ -86,7 +86,6 @@ int main(int argc, char *argv[])
         ImGuiWindowFlags_NoSavedSettings
     );
 
-
     // Expose the same variable directly ...
     ImGui::PushItemWidth(-80);
     ImGui::DragScalar("double", ImGuiDataType_Double, &doubleVariable, 0.1, 0, 0, "%.4f");

+ 5 - 0
tutorial/108_MultipleViews/CMakeLists.txt

@@ -0,0 +1,5 @@
+get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
+project(${PROJECT_NAME})
+
+add_executable(${PROJECT_NAME}_bin main.cpp)
+target_link_libraries(${PROJECT_NAME}_bin igl::core igl::opengl igl::opengl_glfw tutorials)

+ 46 - 0
tutorial/108_MultipleViews/main.cpp

@@ -0,0 +1,46 @@
+#include "tutorial_shared_path.h"
+#include <igl/opengl/glfw/Viewer.h>
+#include <GLFW/glfw3.h>
+#include <string>
+#include <iostream>
+#include <map>
+
+int main(int argc, char * argv[])
+{
+  igl::opengl::glfw::Viewer viewer;
+
+  viewer.load_mesh_from_file(std::string(TUTORIAL_SHARED_PATH) + "/cube.obj");
+  viewer.load_mesh_from_file(std::string(TUTORIAL_SHARED_PATH) + "/sphere.obj");
+
+  unsigned int left_view, right_view;
+  int cube_id = viewer.data_list[0].id;
+  int sphere_id = viewer.data_list[1].id;
+  viewer.callback_init = [&](igl::opengl::glfw::Viewer &)
+  {
+    viewer.core().viewport = Eigen::Vector4f(0, 0, 640, 800);
+    left_view = viewer.core_list[0].id;
+    right_view = viewer.append_core(Eigen::Vector4f(640, 0, 640, 800));
+    return false;
+  };
+
+  viewer.callback_key_down = [&](igl::opengl::glfw::Viewer &, unsigned int key, int mod)
+  {
+    if(key == GLFW_KEY_SPACE)
+    {
+      // By default, when a core is appended, all loaded meshes will be displayed in that core.
+      // Displaying can be controlled by calling viewer.data().set_visible().
+      viewer.data(cube_id).set_visible(false, left_view);
+      viewer.data(sphere_id).set_visible(false, right_view);
+    }
+    return false;
+  };
+
+  viewer.callback_post_resize = [&](igl::opengl::glfw::Viewer &v, int w, int h) {
+    v.core( left_view).viewport = Eigen::Vector4f(0, 0, w / 2, h);
+    v.core(right_view).viewport = Eigen::Vector4f(w / 2, 0, w - (w / 2), h);
+    return true;
+  };
+
+  viewer.launch();
+  return EXIT_SUCCESS;
+}

+ 1 - 1
tutorial/205_Laplacian/main.cpp

@@ -83,7 +83,7 @@ int main(int argc, char *argv[])
     // Send new positions, update normals, recenter
     viewer.data().set_vertices(U);
     viewer.data().compute_normals();
-    viewer.core.align_camera_center(U,F);
+    viewer.core().align_camera_center(U,F);
     return true;
   };
 

+ 4 - 4
tutorial/206_GeodesicDistance/main.cpp

@@ -48,12 +48,12 @@ int main(int argc, char *argv[])
     Eigen::Vector3f bc;
     // Cast a ray in the view direction starting from the mouse position
     double x = viewer.current_mouse_x;
-    double y = viewer.core.viewport(3) - viewer.current_mouse_y;
+    double y = viewer.core().viewport(3) - viewer.current_mouse_y;
     if(igl::unproject_onto_mesh(
       Eigen::Vector2f(x,y),
-      viewer.core.view,
-      viewer.core.proj,
-      viewer.core.viewport,
+      viewer.core().view,
+      viewer.core().proj,
+      viewer.core().viewport,
       V,
       F,
       fid,

+ 6 - 6
tutorial/401_BiharmonicDeformation/main.cpp

@@ -19,7 +19,7 @@ bool pre_draw(igl::opengl::glfw::Viewer & viewer)
 {
   using namespace Eigen;
   // Determine boundary conditions
-  if(viewer.core.is_animating)
+  if(viewer.core().is_animating)
   {
     bc_frac += bc_dir;
     bc_dir *= (bc_frac>=1.0 || bc_frac<=0.0?-1.0:1.0);
@@ -46,7 +46,7 @@ bool key_down(igl::opengl::glfw::Viewer &viewer, unsigned char key, int mods)
   switch(key)
   {
     case ' ':
-      viewer.core.is_animating = !viewer.core.is_animating;
+      viewer.core().is_animating = !viewer.core().is_animating;
       return true;
     case 'D':
     case 'd':
@@ -113,12 +113,12 @@ int main(int argc, char *argv[])
   viewer.data().set_mesh(U, F);
   viewer.data().show_lines = false;
   viewer.data().set_colors(C);
-  viewer.core.trackball_angle = Eigen::Quaternionf(sqrt(2.0),0,sqrt(2.0),0);
-  viewer.core.trackball_angle.normalize();
+  viewer.core().trackball_angle = Eigen::Quaternionf(sqrt(2.0),0,sqrt(2.0),0);
+  viewer.core().trackball_angle.normalize();
   viewer.callback_pre_draw = &pre_draw;
   viewer.callback_key_down = &key_down;
-  //viewer.core.is_animating = true;
-  viewer.core.animation_max_fps = 30.;
+  //viewer.core().is_animating = true;
+  viewer.core().animation_max_fps = 30.;
   cout<<
     "Press [space] to toggle deformation."<<endl<<
     "Press 'd' to toggle between biharmonic surface or displacements."<<endl;

+ 6 - 6
tutorial/402_PolyharmonicDeformation/main.cpp

@@ -27,7 +27,7 @@ bool pre_draw(igl::opengl::glfw::Viewer & viewer)
   U.col(2) = z_max*Z;
   viewer.data().set_vertices(U);
   viewer.data().compute_normals();
-  if(viewer.core.is_animating)
+  if(viewer.core().is_animating)
   {
     z_max += z_dir;
     z_dir *= (z_max>=1.0 || z_max<=0.0?-1.0:1.0);
@@ -40,7 +40,7 @@ bool key_down(igl::opengl::glfw::Viewer &viewer, unsigned char key, int mods)
   switch(key)
   {
     case ' ':
-      viewer.core.is_animating = !viewer.core.is_animating;
+      viewer.core().is_animating = !viewer.core().is_animating;
       break;
     case '.':
       k++;
@@ -97,12 +97,12 @@ int main(int argc, char *argv[])
   viewer.data().set_mesh(U, F);
   viewer.data().show_lines = false;
   viewer.data().set_colors(C);
-  viewer.core.trackball_angle = Eigen::Quaternionf(0.81,-0.58,-0.03,-0.03);
-  viewer.core.trackball_angle.normalize();
+  viewer.core().trackball_angle = Eigen::Quaternionf(0.81,-0.58,-0.03,-0.03);
+  viewer.core().trackball_angle.normalize();
   viewer.callback_pre_draw = &pre_draw;
   viewer.callback_key_down = &key_down;
-  viewer.core.is_animating = true;
-  viewer.core.animation_max_fps = 30.;
+  viewer.core().is_animating = true;
+  viewer.core().animation_max_fps = 30.;
   cout<<
     "Press [space] to toggle animation."<<endl<<
     "Press '.' to increase k."<<endl<<

+ 4 - 4
tutorial/403_BoundedBiharmonicWeights/main.cpp

@@ -46,7 +46,7 @@ bool pre_draw(igl::opengl::glfw::Viewer & viewer)
 {
   using namespace Eigen;
   using namespace std;
-  if(viewer.core.is_animating)
+  if(viewer.core().is_animating)
   {
     // Interpolate pose and identity
     RotationList anim_pose(pose.size());
@@ -97,7 +97,7 @@ bool key_down(igl::opengl::glfw::Viewer &viewer, unsigned char key, int mods)
   switch(key)
   {
     case ' ':
-      viewer.core.is_animating = !viewer.core.is_animating;
+      viewer.core().is_animating = !viewer.core().is_animating;
       break;
     case '.':
       selected++;
@@ -169,8 +169,8 @@ int main(int argc, char *argv[])
   viewer.data().line_width = 1;
   viewer.callback_pre_draw = &pre_draw;
   viewer.callback_key_down = &key_down;
-  viewer.core.is_animating = false;
-  viewer.core.animation_max_fps = 30.;
+  viewer.core().is_animating = false;
+  viewer.core().animation_max_fps = 30.;
   cout<<
     "Press '.' to show next weight function."<<endl<<
     "Press ',' to show previous weight function."<<endl<<

+ 6 - 6
tutorial/404_DualQuaternionSkinning/main.cpp

@@ -80,7 +80,7 @@ bool pre_draw(igl::opengl::glfw::Viewer & viewer)
     viewer.data().set_vertices(U);
     viewer.data().set_edges(CT,BET,sea_green);
     viewer.data().compute_normals();
-    if(viewer.core.is_animating)
+    if(viewer.core().is_animating)
     {
       anim_t += anim_t_dir;
     }
@@ -102,7 +102,7 @@ bool key_down(igl::opengl::glfw::Viewer &viewer, unsigned char key, int mods)
       use_dqs = !use_dqs;
       return true;
     case ' ':
-      viewer.core.is_animating = !viewer.core.is_animating;
+      viewer.core().is_animating = !viewer.core().is_animating;
       return true;
   }
   return false;
@@ -136,12 +136,12 @@ int main(int argc, char *argv[])
   viewer.data().show_lines = false;
   viewer.data().show_overlay_depth = false;
   viewer.data().line_width = 1;
-  viewer.core.trackball_angle.normalize();
+  viewer.core().trackball_angle.normalize();
   viewer.callback_pre_draw = &pre_draw;
   viewer.callback_key_down = &key_down;
-  viewer.core.is_animating = false;
-  viewer.core.camera_zoom = 2.5;
-  viewer.core.animation_max_fps = 30.;
+  viewer.core().is_animating = false;
+  viewer.core().camera_zoom = 2.5;
+  viewer.core().animation_max_fps = 30.;
   cout<<"Press [d] to toggle between LBS and DQS"<<endl<<
     "Press [space] to toggle animation"<<endl;
   viewer.launch();

+ 4 - 4
tutorial/405_AsRigidAsPossible/main.cpp

@@ -70,7 +70,7 @@ bool pre_draw(igl::opengl::glfw::Viewer & viewer)
     igl::arap_solve(bc,arap_data,U);
     viewer.data().set_vertices(U);
     viewer.data().compute_normals();
-  if(viewer.core.is_animating)
+  if(viewer.core().is_animating)
   {
     anim_t += anim_t_dir;
   }
@@ -82,7 +82,7 @@ bool key_down(igl::opengl::glfw::Viewer &viewer, unsigned char key, int mods)
   switch(key)
   {
     case ' ':
-      viewer.core.is_animating = !viewer.core.is_animating;
+      viewer.core().is_animating = !viewer.core().is_animating;
       return true;
   }
   return false;
@@ -127,8 +127,8 @@ int main(int argc, char *argv[])
   viewer.data().set_colors(C);
   viewer.callback_pre_draw = &pre_draw;
   viewer.callback_key_down = &key_down;
-  viewer.core.is_animating = false;
-  viewer.core.animation_max_fps = 30.;
+  viewer.core().is_animating = false;
+  viewer.core().animation_max_fps = 30.;
   cout<<
     "Press [space] to toggle animation"<<endl;
   viewer.launch();

+ 5 - 5
tutorial/406_FastAutomaticSkinningTransformations/main.cpp

@@ -104,7 +104,7 @@ bool pre_draw(igl::opengl::glfw::Viewer & viewer)
     viewer.data().set_vertices(U);
     viewer.data().set_points(bc,sea_green);
     viewer.data().compute_normals();
-    if(viewer.core.is_animating)
+    if(viewer.core().is_animating)
     {
       anim_t += anim_t_dir;
     }else
@@ -132,8 +132,8 @@ bool key_down(igl::opengl::glfw::Viewer &viewer, unsigned char key, int mods)
       resolve = true;
       return true;
     case ' ':
-      viewer.core.is_animating = !viewer.core.is_animating;
-      if(viewer.core.is_animating)
+      viewer.core().is_animating = !viewer.core().is_animating;
+      if(viewer.core().is_animating)
       {
         resolve = true;
       }
@@ -212,8 +212,8 @@ int main(int argc, char *argv[])
   viewer.data().show_lines = false;
   viewer.callback_pre_draw = &pre_draw;
   viewer.callback_key_down = &key_down;
-  viewer.core.is_animating = false;
-  viewer.core.animation_max_fps = 30.;
+  viewer.core().is_animating = false;
+  viewer.core().animation_max_fps = 30.;
   cout<<
     "Press [space] to toggle animation."<<endl<<
     "Press '0' to reset pose."<<endl<<

+ 5 - 5
tutorial/407_BiharmonicCoordinates/main.cpp

@@ -121,7 +121,7 @@ int main(int argc, char * argv[])
       default: 
         return false;
       case ' ':
-        viewer.core.is_animating = !viewer.core.is_animating;
+        viewer.core().is_animating = !viewer.core().is_animating;
         return true;
       case 'r':
         low.U = low.V;
@@ -131,7 +131,7 @@ int main(int argc, char * argv[])
   viewer.callback_pre_draw = [&](igl::opengl::glfw::Viewer & viewer)->bool
   {
     glEnable(GL_CULL_FACE);
-    if(viewer.core.is_animating)
+    if(viewer.core().is_animating)
     {
       arap_solve(MatrixXd(0,3),arap_data,low.U);
       for(int v = 0;v<low.U.rows();v++)
@@ -157,14 +157,14 @@ int main(int argc, char * argv[])
     return false;
   };
   viewer.data().show_lines = false;
-  viewer.core.is_animating = true;
-  viewer.core.animation_max_fps = 30.;
+  viewer.core().is_animating = true;
+  viewer.core().animation_max_fps = 30.;
   viewer.data().set_face_based(true);
   cout<<R"(
 [space] to toggle animation
 'r'     to reset positions 
       )";
-  viewer.core.rotation_type = 
+  viewer.core().rotation_type = 
     igl::opengl::ViewerCore::ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP;
   viewer.launch();
 }

+ 2 - 2
tutorial/501_HarmonicParam/main.cpp

@@ -16,13 +16,13 @@ bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier
   {
     // Plot the 3D mesh
     viewer.data().set_mesh(V,F);
-    viewer.core.align_camera_center(V,F);
+    viewer.core().align_camera_center(V,F);
   }
   else if (key == '2')
   {
     // Plot the mesh in 2D using the UV coordinates as vertex coordinates
     viewer.data().set_mesh(V_uv,F);
-    viewer.core.align_camera_center(V_uv,F);
+    viewer.core().align_camera_center(V_uv,F);
   }
 
   viewer.data().compute_normals();

+ 2 - 2
tutorial/502_LSCMParam/main.cpp

@@ -17,13 +17,13 @@ bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier
   {
     // Plot the 3D mesh
     viewer.data().set_mesh(V,F);
-    viewer.core.align_camera_center(V,F);
+    viewer.core().align_camera_center(V,F);
   }
   else if (key == '2')
   {
     // Plot the mesh in 2D using the UV coordinates as vertex coordinates
     viewer.data().set_mesh(V_uv,F);
-    viewer.core.align_camera_center(V_uv,F);
+    viewer.core().align_camera_center(V_uv,F);
   }
 
   viewer.data().compute_normals();

+ 2 - 2
tutorial/503_ARAPParam/main.cpp

@@ -27,12 +27,12 @@ bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier
   if (show_uv)
   {
     viewer.data().set_mesh(V_uv,F);
-    viewer.core.align_camera_center(V_uv,F);
+    viewer.core().align_camera_center(V_uv,F);
   }
   else
   {
     viewer.data().set_mesh(V,F);
-    viewer.core.align_camera_center(V,F);
+    viewer.core().align_camera_center(V,F);
   }
 
   viewer.data().compute_normals();

+ 1 - 1
tutorial/505_MIQ/main.cpp

@@ -224,7 +224,7 @@ bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier
   line_texture(texture_R, texture_G, texture_B);
   viewer.data().set_texture(texture_R, texture_B, texture_G);
 
-  viewer.core.align_camera_center(viewer.data().V,viewer.data().F);
+  viewer.core().align_camera_center(viewer.data().V,viewer.data().F);
 
   return false;
 }

+ 1 - 1
tutorial/506_FrameField/main.cpp

@@ -170,7 +170,7 @@ bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier
   Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> texture_R, texture_G, texture_B;
   line_texture(texture_R, texture_G, texture_B);
   viewer.data().set_texture(texture_R, texture_B, texture_G);
-  viewer.core.align_camera_center(viewer.data().V,viewer.data().F);
+  viewer.core().align_camera_center(viewer.data().V,viewer.data().F);
 
   return false;
 }

+ 5 - 5
tutorial/606_AmbientOcclusion/main.cpp

@@ -35,15 +35,15 @@ bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier
       break;
     }
     case '.':
-      viewer.core.lighting_factor += 0.1;
+      viewer.core().lighting_factor += 0.1;
       break;
     case ',':
-      viewer.core.lighting_factor -= 0.1;
+      viewer.core().lighting_factor -= 0.1;
       break;
     default: break;
   }
-  viewer.core.lighting_factor = 
-    std::min(std::max(viewer.core.lighting_factor,0.f),1.f);
+  viewer.core().lighting_factor = 
+    std::min(std::max(viewer.core().lighting_factor,0.f),1.f);
 
   return false;
 }
@@ -74,6 +74,6 @@ int main(int argc, char *argv[])
   viewer.callback_key_down = &key_down;
   key_down(viewer,'2',0);
   viewer.data().show_lines = false;
-  viewer.core.lighting_factor = 0.0f;
+  viewer.core().lighting_factor = 0.0f;
   viewer.launch();
 }

+ 2 - 2
tutorial/607_ScreenCapture/main.cpp

@@ -17,7 +17,7 @@ bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier
     Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> A(1280,800);
 
     // Draw the scene in the buffers
-    viewer.core.draw_buffer(
+    viewer.core().draw_buffer(
       viewer.data(),false,R,G,B,A);
 
     // Save it to a PNG
@@ -53,7 +53,7 @@ bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier
     viewer.data().clear();
     viewer.data().set_mesh(V,F);
     viewer.data().set_uv(UV);
-    viewer.core.align_camera_center(V);
+    viewer.core().align_camera_center(V);
     viewer.data().show_texture = true;
 
     // Use the image as a texture

+ 3 - 3
tutorial/609_Boolean/main.cpp

@@ -61,10 +61,10 @@ bool key_down(igl::opengl::glfw::Viewer &viewer, unsigned char key, int mods)
           igl::NUM_MESH_BOOLEAN_TYPES);
       break;
     case '[':
-      viewer.core.camera_dnear -= 0.1;
+      viewer.core().camera_dnear -= 0.1;
       return true;
     case ']':
-      viewer.core.camera_dnear += 0.1;
+      viewer.core().camera_dnear += 0.1;
       return true;
   }
   update(viewer);
@@ -85,7 +85,7 @@ int main(int argc, char *argv[])
 
   viewer.data().show_lines = true;
   viewer.callback_key_down = &key_down;
-  viewer.core.camera_dnear = 3.9;
+  viewer.core().camera_dnear = 3.9;
   cout<<
     "Press '.' to switch to next boolean operation type."<<endl<<
     "Press ',' to switch to previous boolean operation type."<<endl<<

+ 4 - 4
tutorial/703_Decimation/main.cpp

@@ -68,7 +68,7 @@ int main(int argc, char * argv[])
   const auto &pre_draw = [&](igl::opengl::glfw::Viewer & viewer)->bool
   {
     // If animating then collapse 10% of edges
-    if(viewer.core.is_animating && !Q.empty())
+    if(viewer.core().is_animating && !Q.empty())
     {
       bool something_collapsed = false;
       // collapse edge
@@ -100,7 +100,7 @@ int main(int argc, char * argv[])
     switch(key)
     {
       case ' ':
-        viewer.core.is_animating ^= 1;
+        viewer.core().is_animating ^= 1;
         break;
       case 'R':
       case 'r':
@@ -113,8 +113,8 @@ int main(int argc, char * argv[])
   };
 
   reset();
-  viewer.core.background_color.setConstant(1);
-  viewer.core.is_animating = true;
+  viewer.core().background_color.setConstant(1);
+  viewer.core().is_animating = true;
   viewer.callback_key_down = key_down;
   viewer.callback_pre_draw = pre_draw;
   return viewer.launch();

+ 1 - 1
tutorial/704_SignedDistance/main.cpp

@@ -104,7 +104,7 @@ void update_visualization(igl::opengl::glfw::Viewer & viewer)
   viewer.data().clear();
   viewer.data().set_mesh(V_vis,F_vis);
   viewer.data().set_colors(C_vis);
-  viewer.core.lighting_factor = overlay;
+  viewer.core().lighting_factor = overlay;
 }
 
 bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int mod)

+ 2 - 2
tutorial/707_SweptVolume/main.cpp

@@ -34,7 +34,7 @@ int main(int argc, char * argv[])
   igl::opengl::glfw::Viewer viewer;
   viewer.data().set_mesh(V,F);
   viewer.data().set_face_based(true);
-  viewer.core.is_animating = !show_swept_volume;
+  viewer.core().is_animating = !show_swept_volume;
   const int grid_size = 50;
   const int time_steps = 200;
   const double isolevel = 0.1;
@@ -79,7 +79,7 @@ int main(int argc, char * argv[])
           {
             viewer.data().set_mesh(V,F);
           }
-          viewer.core.is_animating = !show_swept_volume;
+          viewer.core().is_animating = !show_swept_volume;
           viewer.data().set_face_based(true);
           break;
       }

+ 3 - 3
tutorial/708_Picking/main.cpp

@@ -23,9 +23,9 @@ int main(int argc, char *argv[])
     Eigen::Vector3f bc;
     // Cast a ray in the view direction starting from the mouse position
     double x = viewer.current_mouse_x;
-    double y = viewer.core.viewport(3) - viewer.current_mouse_y;
-    if(igl::unproject_onto_mesh(Eigen::Vector2f(x,y), viewer.core.view,
-      viewer.core.proj, viewer.core.viewport, V, F, fid, bc))
+    double y = viewer.core().viewport(3) - viewer.current_mouse_y;
+    if(igl::unproject_onto_mesh(Eigen::Vector2f(x,y), viewer.core().view,
+      viewer.core().proj, viewer.core().viewport, V, F, fid, bc))
     {
       // paint hit red
       C.row(fid)<<1,0,0;

+ 3 - 3
tutorial/709_SLIM/main.cpp

@@ -103,7 +103,7 @@ void param_2d_demo_iter(igl::opengl::glfw::Viewer& viewer) {
 
     uv_scale_param = 15 * (1./sqrt(sData.mesh_area));
     viewer.data().set_mesh(V, F);
-    viewer.core.align_camera_center(V,F);
+    viewer.core().align_camera_center(V,F);
     viewer.data().set_uv(sData.V_o*uv_scale_param);
     viewer.data().compute_normals();
     viewer.data().show_texture = true;
@@ -133,7 +133,7 @@ void soft_const_demo_iter(igl::opengl::glfw::Viewer& viewer) {
     slim_precompute(V,F,V_0,sData,igl::MappingEnergyType::SYMMETRIC_DIRICHLET,b,bc,soft_const_p);
 
     viewer.data().set_mesh(V, F);
-    viewer.core.align_camera_center(V,F);
+    viewer.core().align_camera_center(V,F);
     viewer.data().compute_normals();
     viewer.data().show_lines = true;
 
@@ -204,7 +204,7 @@ void display_3d_mesh(igl::opengl::glfw::Viewer& viewer) {
     F_temp.row(i*4+3) << (i*4)+1, (i*4)+2, (i*4)+3;
   }
   viewer.data().set_mesh(V_temp,F_temp);
-  viewer.core.align_camera_center(V_temp,F_temp);
+  viewer.core().align_camera_center(V_temp,F_temp);
   viewer.data().set_face_based(true);
   viewer.data().show_lines = true;
 }

+ 2 - 2
tutorial/710_SCAF/main.cpp

@@ -43,13 +43,13 @@ bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier
     viewer.data().clear();
     viewer.data().set_mesh(V_uv,F);
     viewer.data().set_uv(V_uv);
-    viewer.core.align_camera_center(V_uv,F);
+    viewer.core().align_camera_center(V_uv,F);
   }
   else
   {
     viewer.data().set_mesh(V,F);
     viewer.data().set_uv(V_uv);
-    viewer.core.align_camera_center(V,F);
+    viewer.core().align_camera_center(V,F);
   }
 
   viewer.data().compute_normals();

+ 8 - 8
tutorial/716_HeatGeodesics/main.cpp

@@ -15,7 +15,7 @@ int main(int argc, char *argv[])
   Eigen::MatrixXi F;
   Eigen::MatrixXd V;
   igl::read_triangle_mesh( argc>1?argv[1]: TUTORIAL_SHARED_PATH "/beetle.off",V,F);
-  
+
   // Precomputation
   igl::HeatGeodesicsData<double> data;
   double t = std::pow(igl::avg_edge_length(V,F),2);
@@ -39,12 +39,12 @@ int main(int argc, char *argv[])
     Eigen::Vector3f bc;
     // Cast a ray in the view direction starting from the mouse position
     double x = viewer.current_mouse_x;
-    double y = viewer.core.viewport(3) - viewer.current_mouse_y;
-    if(igl::unproject_onto_mesh(Eigen::Vector2f(x,y), viewer.core.view,
-      viewer.core.proj, viewer.core.viewport, V, F, fid, bc))
+    double y = viewer.core().viewport(3) - viewer.current_mouse_y;
+    if(igl::unproject_onto_mesh(Eigen::Vector2f(x,y), viewer.core().view,
+      viewer.core().proj, viewer.core().viewport, V, F, fid, bc))
     {
       // 3d position of hit
-      const Eigen::RowVector3d m3 = 
+      const Eigen::RowVector3d m3 =
         V.row(F(fid,0))*bc(0) + V.row(F(fid,1))*bc(1) + V.row(F(fid,2))*bc(2);
       int cid = 0;
       Eigen::Vector3d(
@@ -71,7 +71,7 @@ int main(int argc, char *argv[])
     }
     return false;
   };
-  viewer.callback_mouse_move = 
+  viewer.callback_mouse_move =
     [&](igl::opengl::glfw::Viewer& viewer, int, int)->bool
     {
       if(down_on_mesh)
@@ -94,7 +94,7 @@ int main(int argc, char *argv[])
 
 )";
 
-  viewer.callback_key_pressed = 
+  viewer.callback_key_pressed =
     [&](igl::opengl::glfw::Viewer& /*viewer*/, unsigned int key, int mod)->bool
   {
     switch(key)
@@ -184,7 +184,7 @@ void main()
 {
 vec3 Ia = La * vec3(Kai);    // ambient intensity
 float ni = 30.0; // number of intervals
-float t = 1.0-round(ni*Kdi.r)/ni; // quantize and reverse 
+float t = 1.0-round(ni*Kdi.r)/ni; // quantize and reverse
 vec3 Kdiq = clamp(vec3(2.*t,2.*t-1.,6.*t-5.),0,1); // heat map
 
 vec3 vector_to_light_eye = light_position_eye - position_eye;

+ 1 - 0
tutorial/CMakeLists.txt

@@ -60,6 +60,7 @@ if(TUTORIALS_CHAPTER1)
     add_subdirectory("106_ViewerMenu")
   endif()
   add_subdirectory("107_MultipleMeshes")
+  add_subdirectory("108_MultipleViews")
 endif()
 
 # Chapter 2