Background computation threads with igl::viewer::Viewer

Alec Jacobson

December 04, 2016

weblog/

Here's a minimal example showing how to launch background computation threads for each mesh in a list of meshes. Meanwhile the main thread runs a mesh viewer with all meshes concatenated into one huge multi-component mesh. Whenever a computation thread signals that an update to the mesh needs to be made, the main thread will re-concatenate the meshes and update the viewer. In this example, the "computation" is determining how much to move a clock "hand" (random mesh).

Here's the program running on the cow, cheburashka and knight models:

// Tiny example to demonstrate spawning a background computation thread for
// each mesh in a list and update the viewer when computation results in a
// change to (one of) the meshes.
//
// In this example, three meshes are read in and interpreted as "hands" of a
// clock. The "computation" is just a busy-wait until the hand should move
// (after one second, one minute, one hour). This is, of course, a terrible way
// to implement a clock.
//
// ./test libigl/tutorial/shared/{cow.off,cheburashka.off,decimated-knight.off}
#include <igl/read_triangle_mesh.h>
#include <igl/point_mesh_squared_distance.h>
#include <igl/combine.h>
#include <igl/viewer/Viewer.h>
#include <Eigen/Geometry>
#include <thread>
int main(int argc, char * argv[])
{
  using namespace std;
  using namespace Eigen;
  // Read in k meshes (<=3 will be used)
  std::vector<Eigen::MatrixXd> VV(argc-1);
  std::vector<Eigen::MatrixXi> FF(argc-1);
  for(int i = 1;i<argc;i++)
  {
    igl::read_triangle_mesh(argv[i],VV[i-1],FF[i-1]);
    VV[i-1].col(0).array() -= VV[i-1].col(0).mean();
    VV[i-1].col(1).array() -= VV[i-1].col(1).minCoeff();
  }
  bool continue_computing = true;
  // Flag to redraw and mutex to guard it
  bool redraw = false;
  std::mutex m;
  // After waiting `tic` seconds, rotate `V` by `deg` degrees
  const auto rot = [&continue_computing,&redraw,&m](
    const double tic, const double deg, Eigen::MatrixXd& V)
  {
    while(continue_computing)
    {
      // Let's watch this at 500x: Real clocks are slow.
      std::this_thread::sleep_for(std::chrono::milliseconds((int)tic*5));
      V *= Eigen::AngleAxisd(deg/180.*igl::PI,
          Eigen::Vector3d(0,0,1)).toRotationMatrix();
      {
        std::lock_guard<std::mutex> lock(m);
        redraw = true;
      }
    }
  };
  // Launch background "computation" threads for each "hand" of the clock
  // std::ref is important, otherwise std::bind passes by copy
  std::vector<std::thread> threads;
  switch(VV.size())
  {
    case 3:
      threads.emplace_back(std::bind(rot,60.*60.,30,std::ref(VV[2])));
    case 2:
      threads.emplace_back(std::bind(rot,60.,6,std::ref(VV[1])));
    case 1:
      threads.emplace_back(std::bind(rot,1.,6,std::ref(VV[0])));
    default: break;
  }
  igl::viewer::Viewer v;
  v.data.clear();
  // Helper function to view k meshes
  const auto & set_meshes = [](
    const std::vector<Eigen::MatrixXd> & VV,
    const std::vector<Eigen::MatrixXi> & FF,
    igl::viewer::Viewer & v)
  {
    Eigen::MatrixXd V;
    Eigen::MatrixXi F;
    igl::combine(VV,FF,V,F);
    v.data.set_mesh(V,F);
  };
  set_meshes(VV,FF,v);
  // Continuous draw loop. TODO: trigger draws using glfwPostEmptyEvent
  v.core.is_animating = true;
  // Before every draw check if the meshes have changed. 
  v.callback_pre_draw = 
    [&redraw,&m,&VV,&FF,&set_meshes](igl::viewer::Viewer & v)->bool
    { 
      if(redraw) 
      {
        set_meshes(VV,FF,v); 
        {
          std::lock_guard<std::mutex> lock(m);
          redraw = false;
        }
      }
      return false;
    };
  v.launch();
  // Tell computation threads to stop
  continue_computing = false;
  // Join with computation threads: return to serial main thread.
  for(auto & t : threads) if(t.joinable()) t.join();
}

mesh clock igl viewer threads

This is pretty hacky, but it will allow me to use the standard libigl viewer for making comparisons of mesh-editing methods whose performances are very different.