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

components example

Former-commit-id: efbae6c151e11bca3d0f1ee432240ac45baeb937
Alec Jacobson 10 жил өмнө
parent
commit
f63d1447c4

+ 1095 - 0
examples/components/example.cpp

@@ -0,0 +1,1095 @@
+#include <igl/read_triangle_mesh.h>
+#include <igl/init_render_to_texture.h>
+#include <igl/draw_floor.h>
+#include <igl/report_gl_error.h>
+#include <igl/per_face_normals.h>
+#include <igl/trackball.h>
+#include <igl/snap_to_canonical_view_quat.h>
+#include <igl/REDRUM.h>
+#include <igl/Camera.h>
+#include <igl/ReAntTweakBar.h>
+#include <igl/get_seconds.h>
+#include <igl/jet.h>
+#include <igl/rgb_to_hsv.h>
+#include <igl/hsv_to_rgb.h>
+#include <igl/randperm.h>
+#include <igl/boost/components.h>
+#include <igl/C_STR.h>
+#include <igl/write_triangle_mesh.h>
+#include <igl/two_axis_valuator_fixed_up.h>
+#include <igl/snap_to_fixed_up.h>
+#include <igl/create_shader_program.h>
+
+#include <Eigen/Core>
+#include <Eigen/Geometry>
+
+#ifdef __APPLE__
+#include <GLUT/glut.h>
+#else
+#include <GL/glut.h>
+#endif
+
+#ifndef GLUT_WHEEL_UP
+#define GLUT_WHEEL_UP    3
+#endif
+#ifndef GLUT_WHEEL_DOWN
+#define GLUT_WHEEL_DOWN  4
+#endif
+#ifndef GLUT_WHEEL_RIGHT
+#define GLUT_WHEEL_RIGHT 5
+#endif
+#ifndef GLUT_WHEEL_LEFT
+#define GLUT_WHEEL_LEFT  6
+#endif
+#ifndef GLUT_ACTIVE_COMMAND
+#define GLUT_ACTIVE_COMMAND 8
+#endif
+
+#include <ctime>
+#include <string>
+#include <vector>
+#include <stack>
+#include <iostream>
+
+int cc_hover = -1;
+
+Eigen::MatrixXd V;
+Eigen::VectorXd Vmid,Vmin,Vmax;
+double bbd = 1.0;
+Eigen::MatrixXi F;
+Eigen::VectorXi CC;
+Eigen::MatrixXd N;
+struct State
+{
+  igl::Camera camera;
+  Eigen::VectorXf I;
+  Eigen::Matrix<GLubyte,Eigen::Dynamic,Eigen::Dynamic,Eigen::RowMajor> selected;
+  GLuint mask_id;
+} s;
+std::string out_filename;
+
+GLuint pick_tex = 0;
+GLuint pick_fbo = 0;
+GLuint pick_dfbo = 0;
+
+// See README for descriptions
+enum RotationType
+{
+  ROTATION_TYPE_IGL_TRACKBALL = 0,
+  ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP = 1,
+  NUM_ROTATION_TYPES = 2,
+} rotation_type;
+
+enum CenterType
+{
+  CENTER_TYPE_ORBIT = 0,
+  CENTER_TYPE_FPS  = 1,
+  NUM_CENTER_TYPES = 2,
+} center_type = CENTER_TYPE_ORBIT;
+
+
+std::stack<State> undo_stack;
+std::stack<State> redo_stack;
+
+bool wireframe_visible = false;
+bool fill_visible = true;
+
+bool is_rotating = false;
+int down_x,down_y;
+igl::Camera down_camera;
+
+bool is_animating = false;
+double animation_start_time = 0;
+double ANIMATION_DURATION = 0.5;
+Eigen::Quaterniond animation_from_quat;
+Eigen::Quaterniond animation_to_quat;
+
+int width,height;
+Eigen::Vector4f light_pos(-0.1,-0.1,0.9,0);
+
+#define REBAR_NAME "temp.rbr"
+igl::ReTwBar rebar;
+
+// Forward
+void init_components();
+void init_relative();
+
+void push_undo()
+{
+  undo_stack.push(s);
+  // Clear
+  redo_stack = std::stack<State>();
+}
+
+void TW_CALL set_rotation_type(const void * value, void * clientData)
+{
+  using namespace Eigen;
+  using namespace std;
+  using namespace igl;
+  const RotationType old_rotation_type = rotation_type;
+  rotation_type = *(const RotationType *)(value);
+  if(rotation_type == ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP &&
+    old_rotation_type != ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP)
+  {
+    animation_from_quat = s.camera.m_rotation_conj;
+    snap_to_fixed_up(animation_from_quat,animation_to_quat);
+    // start animation
+    animation_start_time = get_seconds();
+    is_animating = true;
+  }
+}
+void TW_CALL get_rotation_type(void * value, void *clientData)
+{
+  RotationType * rt = (RotationType *)(value);
+  *rt = rotation_type;
+}
+
+void reshape(int width, int height)
+{
+  ::width = width;
+  ::height = height;
+  glViewport(0,0,width,height);
+  // Send the new window size to AntTweakBar
+  TwWindowSize(width, height);
+  s.camera.m_aspect = (double)width/(double)height;
+  igl::init_render_to_texture(width,height, pick_tex, pick_fbo, pick_dfbo);
+  igl::report_gl_error("init_render_to_texture: ");
+  glutPostRedisplay();
+}
+
+void push_scene()
+{
+  using namespace igl;
+  using namespace std;
+  glMatrixMode(GL_PROJECTION);
+  glPushMatrix();
+  glLoadIdentity();
+  auto & camera = s.camera;
+  glMultMatrixd(camera.projection().data());
+  glMatrixMode(GL_MODELVIEW);
+  glPushMatrix();
+  glLoadIdentity();
+  gluLookAt(
+    camera.eye()(0), camera.eye()(1), camera.eye()(2),
+    camera.at()(0), camera.at()(1), camera.at()(2),
+    camera.up()(0), camera.up()(1), camera.up()(2));
+  glScaled(2./bbd,2./bbd,2./bbd);
+  glTranslated(-Vmid(0),-Vmid(1),-Vmid(2));
+}
+
+void pop_scene()
+{
+  glMatrixMode(GL_PROJECTION);
+  glPopMatrix();
+  glMatrixMode(GL_MODELVIEW);
+  glPopMatrix();
+}
+
+void draw_mesh(
+  const Eigen::MatrixXd & V,
+  const Eigen::MatrixXi & F,
+  const Eigen::MatrixXd & N,
+  const Eigen::VectorXf & S,
+  const GLuint & S_loc)
+{
+  using namespace Eigen;
+  using namespace std;
+  static Matrix<float,Dynamic,3,RowMajor> VR,NR;
+  static Matrix<int,Dynamic,3,RowMajor> FR;
+  static Matrix<float,Dynamic,1,ColMajor> SR;
+  static GLuint ibo,vbo,sbo,nbo;
+  static bool scene_dirty = true;
+  if(scene_dirty)
+  {
+    VR.resize(F.rows()*3,3);
+    NR.resize(F.rows()*3,3);
+    SR.resize(F.rows()*3,1);
+    FR.resize(F.rows(),3);
+    for(int f = 0;f<F.rows();f++)
+    {
+      for(int c = 0;c<3;c++)
+      {
+        VR.row(3*f+c) = V.row(F(f,c)).cast<float>();
+        SR(3*f+c) = S(F(f,c));
+        NR.row(3*f+c) = N.row(f).cast<float>();
+        FR(f,c) = 3*f+c;
+      }
+    }
+
+    glGenBuffers(1,&ibo);
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,ibo);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(GLuint)*FR.size(),FR.data(),GL_STATIC_DRAW);
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
+    glGenBuffers(1,&vbo);
+    glGenBuffers(1,&nbo);
+    glGenBuffers(1,&sbo);
+
+    glBindBuffer(GL_ARRAY_BUFFER,vbo);
+    glBufferData(GL_ARRAY_BUFFER,sizeof(float)*VR.size(),VR.data(),GL_STATIC_DRAW);
+    glBindBuffer(GL_ARRAY_BUFFER,nbo);
+    glBufferData(GL_ARRAY_BUFFER,sizeof(float)*NR.size(),NR.data(),GL_STATIC_DRAW);
+
+    glBindBuffer(GL_ARRAY_BUFFER,sbo);
+    glBufferData(GL_ARRAY_BUFFER,sizeof(float)*SR.size(),SR.data(),GL_STATIC_DRAW);
+    igl::report_gl_error("glBindBuffer: ");
+
+    scene_dirty = false;
+  }
+
+  glEnableClientState(GL_VERTEX_ARRAY);
+  glBindBuffer(GL_ARRAY_BUFFER,vbo);
+  glVertexPointer(3,GL_FLOAT,0,0);
+  glEnableClientState(GL_NORMAL_ARRAY);
+  glBindBuffer(GL_ARRAY_BUFFER,nbo);   
+  glNormalPointer(GL_FLOAT,0,0);
+
+  glBindBuffer(GL_ARRAY_BUFFER,sbo);   
+  glVertexAttribPointer(S_loc, 1, GL_FLOAT, GL_FALSE, 0, 0);
+  glEnableVertexAttribArray(S_loc);
+
+  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,ibo);
+  glDrawElements(GL_TRIANGLES,FR.size(),GL_UNSIGNED_INT,0);
+  glBindBuffer(GL_ARRAY_BUFFER,0);
+}
+
+// Set up double-sided lights
+void lights()
+{
+  using namespace std;
+  using namespace Eigen;
+  glEnable(GL_LIGHTING);
+  glLightModelf(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
+  glEnable(GL_LIGHT0);
+  float WHITE[4] = {1,1,1,1.};
+  float BLACK[4] = {0.,0.,0.,1.};
+  Vector4f pos = light_pos;
+  glLightfv(GL_LIGHT0,GL_AMBIENT,BLACK);
+  glLightfv(GL_LIGHT0,GL_DIFFUSE,WHITE);
+  glLightfv(GL_LIGHT0,GL_SPECULAR,BLACK);
+  glLightfv(GL_LIGHT0,GL_POSITION,pos.data());
+  //glEnable(GL_LIGHT1);
+  //pos(0) *= -1;
+  //pos(1) *= -1;
+  //pos(2) *= -1;
+  //glLightfv(GL_LIGHT1,GL_AMBIENT,BLACK);
+  //glLightfv(GL_LIGHT1,GL_DIFFUSE,NEAR_BLACK);
+  //glLightfv(GL_LIGHT1,GL_SPECULAR,BLACK);
+  //glLightfv(GL_LIGHT1,GL_POSITION,pos.data());
+}
+
+template <int Rows, int Cols>
+GLuint generate_1d_texture(
+  const Eigen::Matrix<GLubyte,Rows,Cols,Eigen::RowMajor> & colors)
+{
+  assert(colors.cols() == 3 && "Seems colors.cols() must be 3");
+  GLuint tex_id = 0;
+  glGenTextures(1,&tex_id);
+  glBindTexture(GL_TEXTURE_1D,tex_id);
+  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+  glTexImage1D(GL_TEXTURE_1D, 0, colors.cols(),colors.rows(),
+    0,GL_RGB, GL_UNSIGNED_BYTE,
+    colors.data());
+  igl::report_gl_error("glTexImage1D: ");
+  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
+  igl::report_gl_error("texture: ");
+  return tex_id;
+}
+ 
+GLuint color_shader(const size_t max_ids, GLuint & scalar_loc, GLuint & tex_id)
+{
+  std::string vertex_shader = R"(
+#version 120
+attribute float scalar_in;
+varying float scalar_out;
+void main()
+{
+  scalar_out = scalar_in;
+  gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+}
+)";
+  std::string fragment_shader = R"(
+#version 120
+varying float scalar_out;
+uniform float cmin;
+uniform float cmax;
+uniform sampler1D color_map;
+void main()
+{
+  float scalar_normalized = max(min((scalar_out-cmin)/(cmax-cmin),1.0),0.0);
+  gl_FragColor = texture1D(color_map,scalar_normalized);
+}
+)";
+  Eigen::Matrix<GLubyte,Eigen::Dynamic,3,Eigen::RowMajor> colors(max_ids,3);
+  for(size_t id = 0;id<max_ids;id++)
+  {
+    size_t index = id;
+    size_t re = (index)%(256*256);
+    colors(id,0) = (index-re)/(256*256);
+    index = re;
+    re = index%(256);
+    colors(id,1) = (index-re)/(256);
+    colors(id,2) = re;
+  }
+  tex_id = generate_1d_texture(colors);
+  return igl::create_shader_program(
+    vertex_shader.c_str(), 
+    fragment_shader.c_str(),
+    {{"scalar_in",scalar_loc}}
+    );
+}
+
+
+void display()
+{
+  using namespace igl;
+  using namespace std;
+  using namespace Eigen;
+  glClearColor(0.8,0.8,0.8,0);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  if(is_animating)
+  {
+    double t = (get_seconds() - animation_start_time)/ANIMATION_DURATION;
+    if(t > 1)
+    {
+      t = 1;
+      is_animating = false;
+    }
+    Quaterniond q = animation_from_quat.slerp(t,animation_to_quat).normalized();
+    auto & camera = s.camera;
+    switch(center_type)
+    {
+      default:
+      case CENTER_TYPE_ORBIT:
+        camera.orbit(q.conjugate());
+        break;
+      case CENTER_TYPE_FPS:
+        camera.turn_eye(q.conjugate());
+        break;
+    }
+  }
+
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_NORMALIZE);
+  lights();
+  push_scene();
+
+
+  const auto & color_components_shader = [](
+     const GLuint scalar_loc,
+     GLuint & tex_id)->GLuint
+  {
+  std::string vertex_shader = R"(
+#version 120
+attribute float scalar_in;
+varying vec3 normal;
+varying float scalar_out;
+void main()
+{
+  gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+  normal = normalize(gl_NormalMatrix * gl_Normal);
+  scalar_out = scalar_in;
+}
+)";
+  std::string fragment_shader = R"(
+#version 120
+varying vec3 normal;
+varying float scalar_out;
+uniform float cmin;
+uniform float cmax;
+uniform float cc_hover;
+uniform sampler1D color_map;
+uniform sampler1D selected_mask;
+void main()
+{
+  float scalar_normalized = max(min((scalar_out-cmin)/(cmax-cmin),1.0),0.0);
+  vec4 texture_color = texture1D(color_map,scalar_normalized);
+  bool is_selected = texture1D(selected_mask,scalar_normalized).x > 0.5;
+  const vec4 selected_color = vec4(1,0.2,0.2,1);
+  if(scalar_out==cc_hover)
+  {
+    texture_color = 0.5*(texture_color + selected_color);
+  }
+  if(is_selected)
+  {
+    texture_color = selected_color;
+  }
+  const float num_lights = 1.0;
+  vec4 diffuse = (1.0/num_lights)*(gl_LightSource[0].diffuse);
+  vec4 ambient = vec4(0,0,0,0);
+  ambient += (1.0/num_lights)*(gl_FrontMaterial.ambient * gl_LightSource[0].ambient);
+  ambient += (1.0/num_lights)*(gl_LightModel.ambient * gl_FrontMaterial.ambient);
+  vec4 color = ambient;
+  // Phong
+  vec3 lightDir = normalize(vec3(gl_LightSource[0].position));
+  vec3 halfVector = gl_LightSource[0].halfVector.xyz;
+  vec3 n = normalize(normal);
+  float NdotL = max(abs(dot(n.xyz,lightDir)), 0.0);  
+  vec4 specular = vec4(0.0,0.0,0.0,0.0);
+  if (NdotL > 0.0) {
+      color += diffuse * NdotL;
+      vec3 halfV = normalize(halfVector);
+      float NdotHV = max(abs(dot(n,halfV)),0.0);
+      specular += gl_FrontMaterial.specular * gl_LightSource[0].specular * pow(NdotHV, gl_FrontMaterial.shininess);
+  }
+  gl_FragColor = color * texture_color + specular;
+}
+
+)";
+
+    typedef Matrix<GLubyte,64,3,RowMajor> Matrix64_3_R_ubyte;
+    typedef Matrix<float,64,3,RowMajor> Matrix64_3_R_float;
+    Matrix64_3_R_ubyte colors;
+    {
+      Matrix64_3_R_float rgb = (Matrix64_3_R_ubyte()<<
+        255,   0,   0,
+        255,  24,   0,
+        255,  48,   0,
+        255,  72,   0,
+        255,  96,   0,
+        255, 120,   0,
+        255, 143,   0,
+        255, 167,   0,
+        255, 191,   0,
+        255, 215,   0,
+        255, 239,   0,
+        247, 255,   0,
+        223, 255,   0,
+        199, 255,   0,
+        175, 255,   0,
+        151, 255,   0,
+        128, 255,   0,
+        104, 255,   0,
+         80, 255,   0,
+         56, 255,   0,
+         32, 255,   0,
+          8, 255,   0,
+          0, 255,  16,
+          0, 255,  40,
+          0, 255,  64,
+          0, 255,  88,
+          0, 255, 112,
+          0, 255, 135,
+          0, 255, 159,
+          0, 255, 183,
+          0, 255, 207,
+          0, 255, 231,
+          0, 255, 255,
+          0, 231, 255,
+          0, 207, 255,
+          0, 183, 255,
+          0, 159, 255,
+          0, 135, 255,
+          0, 112, 255,
+          0,  88, 255,
+          0,  64, 255,
+          0,  40, 255,
+          0,  16, 255,
+          8,   0, 255,
+         32,   0, 255,
+         56,   0, 255,
+         80,   0, 255,
+        104,   0, 255,
+        128,   0, 255,
+        151,   0, 255,
+        175,   0, 255,
+        199,   0, 255,
+        223,   0, 255,
+        247,   0, 255,
+        255,   0, 239,
+        255,   0, 215,
+        255,   0, 191,
+        255,   0, 167,
+        255,   0, 143,
+        255,   0, 120,
+        255,   0,  96,
+        255,   0,  72,
+        255,   0,  48,
+        255,   0,  24).finished().cast<float>()/255.f;
+
+      Matrix64_3_R_float H;
+      rgb_to_hsv(rgb,H);
+      H.col(1) *= 0.1;
+      H.col(2) = (H.col(2).array() + 0.1*(1.-H.col(2).array())).eval();
+      hsv_to_rgb(H,rgb);
+      colors = (rgb*255.).cast<GLubyte>();
+    }
+
+    tex_id = generate_1d_texture(colors);
+
+    GLuint prog_id = igl::create_shader_program(
+      vertex_shader.c_str(), 
+      fragment_shader.c_str(),
+      {{"scalar_in",scalar_loc}}
+      );
+    igl::report_gl_error("create_shader_program: ");
+    return prog_id;
+  };
+  static GLuint scalar_loc = 1;
+  static GLuint tex_id = 0;
+  static GLuint color_components_prog = 
+    color_components_shader(scalar_loc,tex_id);
+
+  // Set material properties
+  glEnable(GL_COLOR_MATERIAL);
+  glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
+  glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,(const GLfloat[]){1,1,1,1});
+  if(wireframe_visible)
+  {
+    glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
+    if(fill_visible)
+    {
+      glColor3f(0,0,0);
+      glUseProgram(0);
+      draw_mesh(V,F,N,s.I,scalar_loc);
+    }else
+    {
+      glUseProgram(color_components_prog);
+      igl::report_gl_error("UseProgram: ");
+      draw_mesh(V,F,N,s.I,scalar_loc);
+    }
+  }
+
+  glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
+
+  glPushAttrib(GL_ALL_ATTRIB_BITS);
+  glUseProgram(color_components_prog);
+    igl::report_gl_error("use: ");
+  glUniform1f(glGetUniformLocation(color_components_prog,"cmin"),s.I.minCoeff());
+  glUniform1f(glGetUniformLocation(color_components_prog,"cmax"),s.I.maxCoeff());
+  //glUniform1f(glGetUniformLocation(color_components_prog,"cc_selected"),cc_selected);
+  glUniform1f(glGetUniformLocation(color_components_prog,"cc_hover"),cc_hover);
+  glActiveTexture(GL_TEXTURE0);
+  glBindTexture(GL_TEXTURE_1D, tex_id);
+  glUniform1i(glGetUniformLocation(color_components_prog,"color_map"),0);
+  glActiveTexture(GL_TEXTURE1);
+  glBindTexture(GL_TEXTURE_1D, s.mask_id);
+  glUniform1i(glGetUniformLocation(color_components_prog,"selected_mask"),1);
+
+    igl::report_gl_error("unif: ");
+  if(fill_visible)
+  {
+    glEnable(GL_POLYGON_OFFSET_FILL); // Avoid Stitching!
+    glPolygonOffset(1.0, 0);
+  }
+  draw_mesh(V,F,N,s.I,scalar_loc);
+  glPopAttrib();
+  glUseProgram(0);
+
+  // Draw a nice floor
+  glPushMatrix();
+  const double floor_offset =
+    -2./bbd*(V.col(1).maxCoeff()-Vmid(1));
+  glTranslated(0,floor_offset,0);
+  const float GREY[4] = {0.5,0.5,0.6,1.0};
+  const float DARK_GREY[4] = {0.2,0.2,0.3,1.0};
+  draw_floor(GREY,DARK_GREY);
+  glPopMatrix();
+
+  pop_scene();
+
+  TwDraw();
+  glutSwapBuffers();
+  if(is_animating)
+  {
+    glutPostRedisplay();
+  }
+}
+
+void mouse_wheel(int wheel, int direction, int mouse_x, int mouse_y)
+{
+  using namespace std;
+  using namespace igl;
+  using namespace Eigen;
+  GLint viewport[4];
+  glGetIntegerv(GL_VIEWPORT,viewport);
+  if(wheel == 0 && TwMouseMotion(mouse_x, viewport[3] - mouse_y))
+  {
+    static double mouse_scroll_y = 0;
+    const double delta_y = 0.125*direction;
+    mouse_scroll_y += delta_y;
+    TwMouseWheel(mouse_scroll_y);
+    return;
+  }
+
+  auto & camera = s.camera;
+  switch(center_type)
+  {
+    case CENTER_TYPE_ORBIT:
+      if(wheel==0)
+      {
+        // factor of zoom change
+        double s = (1.-0.01*direction);
+        //// FOV zoom: just widen angle. This is hardly ever appropriate.
+        //camera.m_angle *= s;
+        //camera.m_angle = min(max(camera.m_angle,1),89);
+        camera.push_away(s);
+      }else
+      {
+        // Dolly zoom:
+        camera.dolly_zoom((double)direction*1.0);
+      }
+      break;
+    default:
+    case CENTER_TYPE_FPS:
+      // Move `eye` and `at`
+      camera.dolly((wheel==0?Vector3d(0,0,1):Vector3d(-1,0,0))*0.1*direction);
+      break;
+  }
+  glutPostRedisplay();
+}
+
+bool pick(const int x, const int y, int & cc_selected)
+{
+  using namespace Eigen;
+  using namespace igl;
+  using namespace std;
+  static GLuint scalar_loc = 1;
+  static GLuint tex_id = 0;
+  static const size_t max_ids = s.I.maxCoeff()+1;
+  static GLuint color_shader_prog = color_shader(max_ids,scalar_loc,tex_id);
+  const int pick_s = 0;
+  const int pick_w = pick_s;
+  GLint old_vp[4];
+  glGetIntegerv(GL_VIEWPORT,old_vp);
+  const double pick_ratio = double(pick_w)/double(old_vp[2]);
+  // ceil, cause might otherwise round down to 0
+  const int pick_h = ceil(double(old_vp[3])*pick_ratio);
+  glViewport(
+    x-pick_w,
+    old_vp[3]-y-pick_h,2*pick_w+1,2*pick_h+1);
+  glMatrixMode(GL_PROJECTION);
+  Matrix4d proj;
+  glGetDoublev(GL_PROJECTION_MATRIX,proj.data());
+  glPushMatrix();
+  glLoadIdentity();
+  gluPickMatrix(
+    x,
+    old_vp[3]-y, 
+    pick_w*2+1,
+    pick_h*2+1,
+    old_vp);
+  glMultMatrixd(proj.data());
+  glMatrixMode(GL_MODELVIEW);
+  // Activate color shader
+  glUseProgram(color_shader_prog);
+  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,pick_fbo);
+  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT,pick_dfbo);
+  // Clear screen
+  glClearColor(0,0,0,0);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  glPushAttrib(GL_ALL_ATTRIB_BITS);
+  glEnable(GL_TEXTURE_1D);
+  glBindTexture(GL_TEXTURE_1D, tex_id);
+  glUniform1f(glGetUniformLocation(color_shader_prog,"cmin"),s.I.minCoeff());
+  glUniform1f(glGetUniformLocation(color_shader_prog,"cmax"),s.I.maxCoeff());
+  draw_mesh(V,F,N,s.I,scalar_loc);
+  glPopAttrib();
+
+  glMatrixMode(GL_PROJECTION);
+  glPopMatrix();
+  glMatrixMode(GL_MODELVIEW);
+  glViewport(old_vp[0],old_vp[1],old_vp[2],old_vp[3]);
+
+  Matrix<GLubyte,1,4> pixel;
+  glReadPixels(x,old_vp[3]-y,1,1,GL_RGBA,GL_UNSIGNED_BYTE,pixel.data());
+
+  glUseProgram(0);
+  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0);
+  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT,0);
+
+  if(pixel(3) == 0)
+  {
+    cc_selected = -1;
+    return false;
+  }
+  cc_selected = pixel(0)*256*256+pixel(1)*256+pixel(2);
+  return true;
+}
+
+void regenerate_mask()
+{
+  if(glIsTexture(s.mask_id))
+  {
+    glDeleteTextures(1,&s.mask_id);
+  }
+  s.mask_id = generate_1d_texture(s.selected);
+}
+
+void mouse(int glutButton, int glutState, int mouse_x, int mouse_y)
+{
+  using namespace std;
+  using namespace Eigen;
+  using namespace igl;
+  bool tw_using = TwEventMouseButtonGLUT(glutButton,glutState,mouse_x,mouse_y);
+  int mod = glutGetModifiers();
+  switch(glutButton)
+  {
+    case GLUT_RIGHT_BUTTON:
+    {
+      switch(glutState)
+      {
+        case 1:
+          // up
+          glutSetCursor(GLUT_CURSOR_INHERIT);
+          is_rotating = false;
+          break;
+        case 0:
+          glutSetCursor(GLUT_CURSOR_CYCLE);
+          // collect information for trackball
+          is_rotating = true;
+          down_camera = s.camera;
+          down_x = mouse_x;
+          down_y = mouse_y;
+        break;
+      }
+      break;
+    }
+    case GLUT_LEFT_BUTTON:
+    {
+      switch(glutState)
+      {
+        case 1:
+          // up
+          glutSetCursor(GLUT_CURSOR_INHERIT);
+          is_rotating = false;
+          break;
+        case 0:
+          if(!tw_using)
+          {
+            push_scene();
+            int cc_selected=-1;
+            if(pick(mouse_x,mouse_y,cc_selected))
+            {
+              push_undo();
+              if(!(mod & GLUT_ACTIVE_SHIFT))
+              {
+                s.selected.setConstant(0);
+              }
+              s.selected(cc_selected,0) = 255;
+              regenerate_mask();
+            }else
+            {
+              glutSetCursor(GLUT_CURSOR_CYCLE);
+              // collect information for trackball
+              is_rotating = true;
+              down_camera = s.camera;
+              down_x = mouse_x;
+              down_y = mouse_y;
+            }
+            pop_scene();
+          }
+        break;
+      }
+      break;
+    }
+    // Scroll down
+    case GLUT_WHEEL_DOWN:
+    {
+      mouse_wheel(0,-1,mouse_x,mouse_y);
+      break;
+    }
+    // Scroll up
+    case GLUT_WHEEL_UP:
+    {
+      mouse_wheel(0,1,mouse_x,mouse_y);
+      break;
+    }
+    // Scroll left
+    case GLUT_WHEEL_LEFT:
+    {
+      mouse_wheel(1,-1,mouse_x,mouse_y);
+      break;
+    }
+    // Scroll right
+    case GLUT_WHEEL_RIGHT:
+    {
+      mouse_wheel(1,1,mouse_x,mouse_y);
+      break;
+    }
+  }
+  glutPostRedisplay();
+}
+
+void mouse_move(int mouse_x, int mouse_y)
+{
+  using namespace igl;
+  using namespace std;
+  using namespace Eigen;
+  bool tw_using = TwMouseMotion(mouse_x,mouse_y);
+  push_scene();
+  pick(mouse_x,mouse_y,cc_hover);
+  pop_scene();
+  glutPostRedisplay();
+}
+
+void mouse_drag(int mouse_x, int mouse_y)
+{
+  using namespace igl;
+  using namespace std;
+  using namespace Eigen;
+  bool tw_using = TwMouseMotion(mouse_x,mouse_y);
+
+  if(is_rotating)
+  {
+    glutSetCursor(GLUT_CURSOR_CYCLE);
+    Quaterniond q;
+    auto & camera = s.camera;
+    switch(rotation_type)
+    {
+      case ROTATION_TYPE_IGL_TRACKBALL:
+      {
+        // Rotate according to trackball
+        igl::trackball<double>(
+          width,
+          height,
+          2.0,
+          down_camera.m_rotation_conj.coeffs().data(),
+          down_x,
+          down_y,
+          mouse_x,
+          mouse_y,
+          q.coeffs().data());
+          break;
+      }
+      case ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP:
+      {
+        // Rotate according to two axis valuator with fixed up vector
+        two_axis_valuator_fixed_up(
+          width, height,
+          2.0,
+          down_camera.m_rotation_conj,
+          down_x, down_y, mouse_x, mouse_y,
+          q);
+        break;
+      }
+      default:
+        break;
+    }
+    switch(center_type)
+    {
+      default:
+      case CENTER_TYPE_ORBIT:
+        camera.orbit(q.conjugate());
+        break;
+      case CENTER_TYPE_FPS:
+        camera.turn_eye(q.conjugate());
+        break;
+    }
+  }
+  glutPostRedisplay();
+}
+
+void init_relative()
+{
+  using namespace Eigen;
+  using namespace igl;
+  per_face_normals(V,F,N);
+  Vmax = V.colwise().maxCoeff();
+  Vmin = V.colwise().minCoeff();
+  Vmid = 0.5*(Vmax + Vmin);
+  bbd = (Vmax-Vmin).norm();
+}
+
+void init_components()
+{
+  using namespace Eigen;
+  using namespace igl;
+  using namespace std;
+  components(F,CC);
+  s.I = CC.cast<float>();
+  s.selected = Matrix<GLubyte,Dynamic,Dynamic>::Zero(s.I.maxCoeff()+1,3);
+  cout<<"s.selected: "<<s.selected.rows()<<endl;
+  regenerate_mask();
+}
+
+void undo()
+{
+  using namespace std;
+  if(!undo_stack.empty())
+  {
+    redo_stack.push(s);
+    s = undo_stack.top();
+    undo_stack.pop();
+  }
+  regenerate_mask();
+}
+
+void redo()
+{
+  using namespace std;
+  if(!redo_stack.empty())
+  {
+    undo_stack.push(s);
+    s = redo_stack.top();
+    redo_stack.pop();
+  }
+  regenerate_mask();
+}
+
+bool save(const std::string & out_filename)
+{
+  using namespace std;
+  using namespace igl;
+  if(write_triangle_mesh(out_filename,V,F))
+  {
+    cout<<GREENGIN("Saved mesh to `"<<out_filename<<"` successfully.")<<endl;
+    return true;
+  }else
+  {
+    cout<<REDRUM("Failed to save mesh to `"<<out_filename<<"`.")<<endl;
+    return false;
+  }
+}
+
+void TW_CALL saveCB(void * /*clientData*/)
+{
+  save(out_filename);
+}
+
+void key(unsigned char key, int mouse_x, int mouse_y)
+{
+  using namespace std;
+  using namespace Eigen;
+  using namespace igl;
+  int mod = glutGetModifiers();
+  switch(key)
+  {
+    // ESC
+    case char(27):
+      rebar.save(REBAR_NAME);
+    // ^C
+    case char(3):
+      exit(0);
+    case 'z':
+    case 'Z':
+      if(mod & GLUT_ACTIVE_COMMAND)
+      {
+        if(mod & GLUT_ACTIVE_SHIFT)
+        {
+          redo();
+        }else
+        {
+          undo();
+        }
+      }else
+      {
+        Quaterniond q;
+        snap_to_canonical_view_quat(s.camera.m_rotation_conj,1.0,q);
+        switch(center_type)
+        {
+          default:
+          case CENTER_TYPE_ORBIT:
+            s.camera.orbit(q.conjugate());
+            break;
+          case CENTER_TYPE_FPS:
+            s.camera.turn_eye(q.conjugate());
+            break;
+        }
+      }
+      break;
+    case 'u':
+        mouse_wheel(0, 1,mouse_x,mouse_y);
+        break;
+    case 'j':
+        mouse_wheel(0,-1,mouse_x,mouse_y);
+        break;
+    default:
+      if(!TwEventKeyboardGLUT(key,mouse_x,mouse_y))
+      {
+        cout<<"Unknown key command: "<<key<<" "<<int(key)<<endl;
+      }
+  }
+
+  glutPostRedisplay();
+}
+
+int main(int argc, char * argv[])
+{
+  using namespace std;
+  using namespace Eigen;
+  using namespace igl;
+  string filename = "../shared/truck.obj";
+  switch(argc)
+  {
+    case 3:
+      out_filename = argv[2];
+    case 2:
+      // Read and prepare mesh
+      filename = argv[1];
+      break;
+    default:
+      cerr<<"Usage:"<<endl<<"    ./example input.obj (output.obj)"<<endl;
+      cout<<endl<<"Opening default mesh..."<<endl;
+      break;
+  }
+
+  // print key commands
+  cout<<"[Click] and [drag]  Rotate model using trackball."<<endl;
+  cout<<"[Z,z]               Snap rotation to canonical view."<<endl;
+  cout<<"[Command+Z]         Undo."<<endl;
+  cout<<"[Shift+Command+Z]   Redo."<<endl;
+  cout<<"[^C,ESC]            Exit."<<endl;
+
+  read_triangle_mesh(filename,V,F);
+
+
+  // Init glut
+  glutInit(&argc,argv);
+  if( !TwInit(TW_OPENGL, NULL) )
+  {
+    // A fatal error occured
+    fprintf(stderr, "AntTweakBar initialization failed: %s\n", TwGetLastError());
+    return 1;
+  }
+  // Create a tweak bar
+  rebar.TwNewBar("bar");
+  TwDefine("bar label='Components' size='200 550' text=light alpha='200' color='68 68 68'");
+  rebar.TwAddVarRW("camera_rotation", TW_TYPE_QUAT4D,
+    s.camera.m_rotation_conj.coeffs().data(), "open readonly=true");
+  TwType RotationTypeTW = ReTwDefineEnumFromString("RotationType",
+    "igl_trackball,two-axis-valuator-fixed-up");
+  rebar.TwAddVarCB( "rotation_type", RotationTypeTW,
+    set_rotation_type,get_rotation_type,NULL,"keyIncr=] keyDecr=[");
+  TwType CenterTypeTW = ReTwDefineEnumFromString("CenterType","orbit,fps");
+  rebar.TwAddVarRW("center_type", CenterTypeTW,&center_type,
+    "keyIncr={ keyDecr=}");
+
+  rebar.TwAddVarRW("wireframe_visible",TW_TYPE_BOOLCPP,&wireframe_visible,"key=l");
+  rebar.TwAddVarRW("fill_visible",TW_TYPE_BOOLCPP,&fill_visible,"key=f");
+  if(out_filename != "")
+  {
+    rebar.TwAddButton("save",
+      saveCB,NULL,
+      C_STR("label='Save to `"<<out_filename<<"`' "<<
+      "key=s"));
+  }
+  rebar.load(REBAR_NAME);
+
+
+  animation_from_quat = Quaterniond(1,0,0,0);
+  s.camera.m_rotation_conj = animation_from_quat;
+  animation_start_time = get_seconds();
+
+  glutInitDisplayString( "rgba depth double samples>=8");
+  glutInitWindowSize(glutGet(GLUT_SCREEN_WIDTH)/2.0,glutGet(GLUT_SCREEN_HEIGHT)/2.0);
+  glutCreateWindow("components");
+  glutDisplayFunc(display);
+  glutReshapeFunc(reshape);
+  glutKeyboardFunc(key);
+  glutMouseFunc(mouse);
+  glutMotionFunc(mouse_drag);
+  glutPassiveMotionFunc(mouse_move);
+
+  init_components();
+  init_relative();
+  regenerate_mask();
+
+  std::cout<<"OpenGL version: "<<glGetString(GL_VERSION)<<std::endl;
+  glutMainLoop();
+
+  return 0;
+}