Browse Source

Multiple viewports.

Roi Poranne 6 years ago
parent
commit
0f40780d5d
35 changed files with 383 additions and 165 deletions
  1. 3 0
      include/igl/opengl/ViewerCore.h
  2. 10 1
      include/igl/opengl/ViewerData.cpp
  3. 3 0
      include/igl/opengl/ViewerData.h
  4. 164 58
      include/igl/opengl/glfw/Viewer.cpp
  5. 59 7
      include/igl/opengl/glfw/Viewer.h
  6. 17 17
      include/igl/opengl/glfw/imgui/ImGuiMenu.cpp
  7. 5 5
      python/modules/py_igl_opengl_glfw.cpp
  8. 2 2
      tutorial/103_Events/main.cpp
  9. 5 0
      tutorial/108_MultipleViews/CMakeLists.txt
  10. 39 0
      tutorial/108_MultipleViews/main.cpp
  11. 1 1
      tutorial/205_Laplacian/main.cpp
  12. 4 4
      tutorial/206_GeodesicDistance/main.cpp
  13. 6 6
      tutorial/401_BiharmonicDeformation/main.cpp
  14. 6 6
      tutorial/402_PolyharmonicDeformation/main.cpp
  15. 4 4
      tutorial/403_BoundedBiharmonicWeights/main.cpp
  16. 6 6
      tutorial/404_DualQuaternionSkinning/main.cpp
  17. 4 4
      tutorial/405_AsRigidAsPossible/main.cpp
  18. 5 5
      tutorial/406_FastAutomaticSkinningTransformations/main.cpp
  19. 5 5
      tutorial/407_BiharmonicCoordinates/main.cpp
  20. 2 2
      tutorial/501_HarmonicParam/main.cpp
  21. 2 2
      tutorial/502_LSCMParam/main.cpp
  22. 2 2
      tutorial/503_ARAPParam/main.cpp
  23. 1 1
      tutorial/505_MIQ/main.cpp
  24. 1 1
      tutorial/506_FrameField/main.cpp
  25. 5 5
      tutorial/606_AmbientOcclusion/main.cpp
  26. 2 2
      tutorial/607_ScreenCapture/main.cpp
  27. 1 1
      tutorial/608_LIM/main.cpp
  28. 3 3
      tutorial/609_Boolean/main.cpp
  29. 4 4
      tutorial/703_Decimation/main.cpp
  30. 1 1
      tutorial/704_SignedDistance/main.cpp
  31. 2 2
      tutorial/707_SweptVolume/main.cpp
  32. 3 3
      tutorial/708_Picking/main.cpp
  33. 3 3
      tutorial/709_SLIM/main.cpp
  34. 2 2
      tutorial/710_SCAF/main.cpp
  35. 1 0
      tutorial/CMakeLists.txt

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

@@ -93,6 +93,9 @@ public:
 
   // ------------------- Properties
 
+  // Unique identifier
+  int id;
+
   // Colors
   Eigen::Vector4f background_color;
 

+ 10 - 1
include/igl/opengl/ViewerData.cpp

@@ -30,7 +30,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 +113,14 @@ 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::set_colors(const Eigen::MatrixXd &C)
 {
   using namespace std;

+ 3 - 0
include/igl/opengl/ViewerData.h

@@ -42,6 +42,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:
@@ -177,6 +179,7 @@ public:
   bool face_based;
 
   // Visualization options
+  unsigned int is_visible;
   bool show_overlay;
   bool show_overlay_depth;
   bool show_texture;

+ 164 - 58
include/igl/opengl/glfw/Viewer.cpp

@@ -123,16 +123,16 @@ namespace opengl
 namespace glfw
 {
 
-  IGL_INLINE int Viewer::launch(bool resizable,bool fullscreen)
+  IGL_INLINE int Viewer::launch(bool resizable /*= true*/,bool fullscreen /*= false*/, int windowWidth /*= 1280*/, int windowHeight /*= 800*/)
   {
     // TODO return values are being ignored...
-    launch_init(resizable,fullscreen);
+    launch_init(resizable,fullscreen,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, int windowWidth, int windowHeight)
   {
     glfwSetErrorCallback(glfw_error_callback);
     if (!glfwInit())
@@ -154,11 +154,7 @@ namespace glfw
     }
     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,"libigl viewer",nullptr,nullptr);
     }
     if (!window)
     {
@@ -198,17 +194,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 +211,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 +251,7 @@ namespace glfw
     {
       data.meshgl.free();
     }
-    core.shut();
+    core().shut(); // Doesn't do anything
     shutdown_plugins();
     glfwDestroyWindow(window);
     glfwTerminate();
@@ -267,7 +260,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 +289,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 +429,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 +502,7 @@ namespace glfw
       case 'A':
       case 'a':
       {
-        core.is_animating = !core.is_animating;
+        core().is_animating = !core().is_animating;
         return true;
       }
       case 'F':
@@ -529,7 +527,7 @@ namespace glfw
       case 'O':
       case 'o':
       {
-        core.orthographic = !core.orthographic;
+        core().orthographic = !core().orthographic;
         return true;
       }
       case 'T':
@@ -546,10 +544,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;
       }
@@ -613,7 +611,7 @@ namespace glfw
 
     down = true;
 
-    down_translation = core.camera_translation;
+    down_translation = core().camera_translation;
 
 
     // Initialization code for the trackball
@@ -629,18 +627,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 +690,30 @@ 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;
 
+    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 (mouse_x > viewport[0] && mouse_x < viewport[0] + viewport[2] &&
+            height_window-mouse_y > viewport[1] && height_window-mouse_y < viewport[1] + viewport[3])
+        {
+            selected_core_index = i;
+            break;
+        }
+    }
+
     if (down)
     {
       switch (mouse_mode)
       {
         case MouseMode::Rotation:
         {
-          switch(core.rotation_type)
+          switch(core().rotation_type)
           {
             default:
               assert(false && "Unknown rotation type");
@@ -709,26 +721,26 @@ 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);
+                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);
+                core().trackball_angle);
               break;
           }
-          //Eigen::Vector4f snapq = core.trackball_angle;
+          //Eigen::Vector4f snapq = core().trackball_angle;
 
           break;
         }
@@ -736,18 +748,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;
@@ -777,7 +789,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 +804,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 +819,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 +844,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 +863,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,7 +900,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
+    {
+      int original_w = 0;
+      int original_h = 0;
+      for (auto c: core_list)
+      {
+        int cur_x = c.viewport(0) + c.viewport(2);
+        int cur_y = c.viewport(1) + c.viewport(3);
+        if (original_w < cur_x) original_w = cur_x;
+        if (original_h < cur_y) original_h = cur_y;
+      }
+      for (auto &c: core_list)
+      {
+        c.viewport(0) *= w/original_w;
+        c.viewport(2) *= w/original_w;
+        c.viewport(1) *= h/original_h;
+        c.viewport(3) *= h/original_h;
+      }
+    }
     for (unsigned int i = 0; i<plugins.size(); ++i)
     {
       plugins[i]->post_resize(w, h);
@@ -886,8 +931,8 @@ namespace glfw
 
   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 +955,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;
   }
 
@@ -944,6 +999,7 @@ namespace glfw
     {
       selected_data_index--;
     }
+
     return true;
   }
 
@@ -956,6 +1012,56 @@ 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_ind;
+    if (core_id == 0)
+      core_ind = selected_core_index;
+    else
+      core_ind = core_index(core_id);
+    assert((core_ind >= 0 && core_ind < core_list.size()) && "selected_core_index should be in bounds");
+    return core_list[core_ind];
+  }
+
+  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 mesh
+      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>>=1;
+    }
+    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 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);
+    return core_list.back().id;
+  }
 
 } // end namespace
 } // end namespace

+ 59 - 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, int width = 1280, int height = 800);
+    IGL_INLINE int launch_init(bool resizable = true,bool fullscreen = false, 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,43 @@ 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;
+
+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 +169,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
@@ -164,7 +216,7 @@ namespace glfw
     void* callback_key_up_data;
 
   public:
-      EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
   };
 
 } // end namespace

+ 17 - 17
include/igl/opengl/glfw/imgui/ImGuiMenu.cpp

@@ -219,7 +219,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,36 +228,36 @@ 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();
   }
 
@@ -275,7 +275,7 @@ IGL_INLINE void ImGuiMenu::draw_viewer_menu()
     }
     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(),
+    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);
@@ -357,14 +357,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());
 }

+ 5 - 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,15 @@ 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("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("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 +404,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;

+ 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)

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

@@ -0,0 +1,39 @@
+#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");
+  
+  int left_view, right_view;
+  int cube_id = viewer.data_list[0].id, 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 true;
+  };
+
+  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 changing viewer.coreDataPairs
+	  viewer.data(cube_id).set_visible(false, left_view);
+	  viewer.data(sphere_id).set_visible(false, right_view);
+    }
+    return false;
+  };
+
+  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

+ 1 - 1
tutorial/608_LIM/main.cpp

@@ -110,6 +110,6 @@ int main(int argc, char *argv[])
   viewer.callback_key_down = &key_down;
   viewer.data().set_mesh(V1, F);
   viewer.data().show_lines = true;
-  viewer.core.lighting_factor = 0.0f;
+  viewer.core().lighting_factor = 0.0f;
   viewer.launch();
 }

+ 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();

+ 1 - 0
tutorial/CMakeLists.txt

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