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

rotation widget example

Former-commit-id: 808ed48a4422294e359a7fea2902c3584a9ca8db
Alec Jacobson (jalec 11 жил өмнө
parent
commit
df7b363ece

+ 54 - 0
examples/rotate-widget/Makefile

@@ -0,0 +1,54 @@
+
+.PHONY: all
+
+# Shared flags etc.
+include ../../Makefile.conf
+
+all: obj example
+
+.PHONY: example
+
+LIBIGL=../../
+LIBIGL_INC=-I$(LIBIGL)/include
+LIBIGL_LIB=-L$(LIBIGL)/lib -ligl -liglmatlab -liglembree
+
+EIGEN3_INC=-I/opt/local/include/eigen3 -I/opt/local/include/eigen3/unsupported
+
+ANTTWEAKBAR_INC=-I$(LIBIGL)/external/AntTweakBar/include
+ANTTWEAKBAR_LIB=-L$(LIBIGL)/external/AntTweakBar/lib -lAntTweakBar -framework AppKit
+
+MATLAB_INC=-I$(MATLAB)/extern/include/
+MATLAB_LIB=-L$(MATLAB)/bin/maci64 -lmx -lmat -lmex -lstdc++
+
+CARBON_LIB=-framework Carbon
+
+# Use free glut for mouse scrolling
+#FREE_GLUT=/opt/local/
+#FREE_GLUT_INC=-I$(FREE_GLUT)/include
+#FREE_GLUT_LIB=-L$(FREE_GLUT)/lib -lglut
+GLUT_LIB=-framework GLUT
+GLUT_INC=
+
+INC=$(LIBIGL_INC) $(ANTTWEAKBAR_INC) $(EIGEN3_INC) $(MATLAB_INC) $(GLUT_INC)
+LIB=$(OPENGL_LIB) $(GLUT_LIB) $(ANTTWEAKBAR_LIB) $(LIBIGL_LIB) $(MATLAB_LIB) $(CARBON_LIB)
+
+CPP_FILES=$(wildcard ./*.cpp)
+OBJ_FILES=$(addprefix obj/,$(notdir $(CPP_FILES:.cpp=.o))) 
+
+CFLAGS+=-std=c++11
+
+example: obj $(OBJ_FILES)
+	g++ $(OPENMP) $(AFLAGS) $(CFLAGS) -o example $(OBJ_FILES) $(LIB)
+
+obj:
+	mkdir -p obj
+
+obj/%.o: %.cpp
+	g++ $(OPENMP) $(AFLAGS) $(CFLAGS) -c $< -o $@ $(INC)
+
+obj/%.o: %.cpp %.h
+	g++ $(OPENMP) $(AFLAGS) $(CFLAGS) -c $< -o $@ $(INC)
+
+clean:
+	rm -f $(OBJ_FILES)
+	rm -f example

+ 447 - 0
examples/rotate-widget/RotateWidget.cpp

@@ -0,0 +1,447 @@
+// Implementation
+#include "RotateWidget.h"
+#include <igl/OpenGL_convenience.h>
+#include <igl/PI.h>
+#include <igl/EPS.h>
+#include <igl/ray_sphere_intersect.h>
+#include <igl/project.h>
+#include <igl/mat_to_quat.h>
+#include <igl/trackball.h>
+#include <igl/unproject.h>
+#include <iostream>
+
+Eigen::Quaterniond RotateWidget::axis_q[3] = {
+  Eigen::Quaterniond(Eigen::AngleAxisd(igl::PI*0.5,Eigen::Vector3d(0,1,0))),
+  Eigen::Quaterniond(Eigen::AngleAxisd(igl::PI*0.5,Eigen::Vector3d(1,0,0))),
+  Eigen::Quaterniond::Identity()};
+
+Eigen::Vector3d RotateWidget::view_direction(const int x, const int y)
+{
+  using namespace Eigen;
+  using namespace igl;
+  Vector4i vp;
+  glGetIntegerv(GL_VIEWPORT,vp.data());
+  const int height = vp(3);
+  const Vector3d win_s(x,height-y,0), win_d(x,height-y,1);
+  const Vector3d s = unproject(win_s);
+  const Vector3d d = unproject(win_d);
+  return d-s;
+}
+
+Eigen::Vector3d RotateWidget::view_direction(const Eigen::Vector3d & pos)
+{
+  using namespace Eigen;
+  using namespace igl;
+  const Vector3d ppos = project(pos);
+  return view_direction(ppos(0),ppos(1));
+}
+
+RotateWidget::RotateWidget():
+  pos(0,0,0),
+  rot(Eigen::Quaterniond::Identity()),
+  down_rot(rot),
+  down_xy(-1,-1),drag_xy(-1,-1),
+  outer_radius_on_screen(91.),
+  outer_over_inner(1.13684210526),
+  down_type(DOWN_TYPE_NONE), 
+  selected_type(DOWN_TYPE_NONE)
+{
+}
+
+bool RotateWidget::intersect(const int x, const int y, Eigen::Vector3d & hit)
+{
+  using namespace Eigen;
+  using namespace igl;
+  Vector3d view = view_direction(x,y);
+  Vector4i vp;
+  glGetIntegerv(GL_VIEWPORT,vp.data());
+  const Vector3d ppos = project(pos);
+  Vector3d uxy = unproject(Vector3d(x,vp(3)-y,ppos(2)));
+  double t0,t1;
+  if(!ray_sphere_intersect(uxy,view,pos,unprojected_inner_radius(),t0,t1))
+  {
+    return false;
+  }
+  hit = uxy+t0*view;
+  return true;
+}
+
+double RotateWidget::unprojected_inner_radius()
+{
+  using namespace Eigen;
+  using namespace igl;
+  Vector3d off,ppos,ppos_off,pos_off;
+  project(pos,ppos);
+  ppos_off = ppos;
+  ppos_off(0) += outer_radius_on_screen/outer_over_inner;
+  unproject(ppos_off,pos_off);
+  return (pos-pos_off).norm();
+}
+
+Eigen::Vector3d RotateWidget::unproject_onto(const int x, const int y)
+{
+  using namespace Eigen;
+  using namespace igl;
+  Vector4i vp;
+  glGetIntegerv(GL_VIEWPORT,vp.data());
+  const Vector3d ppos = project(pos);
+  Vector3d uxy = unproject( Vector3d(x,vp(3)-y,ppos(2)))-pos;
+  uxy /= unprojected_inner_radius()*outer_over_inner*outer_over_inner;
+  return uxy;
+}
+
+bool RotateWidget::down(const int x, const int y)
+{
+  using namespace Eigen;
+  using namespace igl;
+  using namespace std;
+  down_type = DOWN_TYPE_NONE;
+  selected_type = DOWN_TYPE_NONE;
+  down_xy = Vector2d(x,y);
+  drag_xy = down_xy;
+  down_rot = rot;
+  Vector3d ppos = project(pos);
+  const double r = (ppos.head(2) - down_xy).norm();
+  const double thresh = 3;
+  if(fabs(r - outer_radius_on_screen)<thresh)
+  {
+    udown = unproject_onto(x,y);
+    udrag = udown;
+    down_type = DOWN_TYPE_OUTLINE;
+    selected_type = DOWN_TYPE_OUTLINE;
+    // project mouse to same depth as pos
+    return true;
+  }else if(r < outer_radius_on_screen/outer_over_inner+thresh*0.5)
+  {
+    Vector3d hit;
+    const bool is_hit = intersect(down_xy(0),down_xy(1),hit);
+    if(!is_hit)
+    {
+      cout<<"~~~!is_hit"<<endl;
+    }
+    auto on_meridian = [&](
+      const Vector3d & hit, 
+      const Quaterniond & rot, 
+      const Quaterniond m) -> bool
+    {
+      // project onto rotate plane
+      Vector3d pl_hit = hit-pos;
+      pl_hit = (m.conjugate()*rot.conjugate()*pl_hit).eval();
+      pl_hit(2) = 0;
+      pl_hit = (rot*m*pl_hit).eval();
+      pl_hit.normalize();
+      pl_hit *= unprojected_inner_radius();
+      pl_hit += pos;
+      return (project(pl_hit).head(2)-project(hit).head(2)).norm()<2*thresh;
+    };
+    udown = (hit-pos).normalized()/outer_radius_on_screen;
+    udrag = udown;
+    for(int a = 0;a<3;a++)
+    {
+      if(on_meridian(hit,rot,Quaterniond(axis_q[a])))
+      {
+        down_type = DownType(DOWN_TYPE_X+a);
+        selected_type = down_type;
+        {
+          Vector3d dir3 = axis_q[a].conjugate()*down_rot.conjugate()*(hit-pos);
+          dir3 = AngleAxisd(-PI*0.5,Vector3d(0,0,1))*dir3;
+          dir3 = (rot*axis_q[a]*dir3).eval();
+          down_dir = (project((hit+dir3).eval())-project(hit)).head(2);
+          down_dir.normalize();
+          // flip y because y coordinate is going to be given backwards in
+          // drag()
+          down_dir(1) *= -1;
+          cout<<"down_dir: "<<down_dir.transpose()<<endl;
+        }
+        return true;
+      }
+    }
+    //assert(is_hit);
+    down_type = DOWN_TYPE_TRACKBALL;
+    selected_type = DOWN_TYPE_TRACKBALL;
+    return true;
+  }else
+  {
+    return false;
+  }
+}
+
+bool RotateWidget::drag(const int x, const int y)
+{
+  using namespace igl;
+  using namespace std;
+  using namespace Eigen;
+  drag_xy = Vector2d(x,y);
+  switch(down_type)
+  {
+    case DOWN_TYPE_NONE:
+      return false;
+    default:
+    {
+      const Quaterniond & q = axis_q[down_type-DOWN_TYPE_X];
+      //auto ray_plane_intersect = [&](
+      //  const Vector3d & o,
+      //  const Vector3d & d,
+      //  const Quaterniond & rot, 
+      //  const Quaterniond m) -> Vector3d
+      //{
+      //  const Vector3d eff_o = m.conjugate()*rot.conjugate()*o;
+      //  const Vector3d eff_d = m.conjugate()*rot.conjugate()*d;
+      //  const double t = eff_o(2)/eff_d(2);
+      //  Vector3d p = eff_o+t*eff_d;
+      //  p = (rot*m*p).eval();
+      //  return p;
+      //};
+      //Vector3d view = view_direction(x,y);
+      //Vector4i vp;
+      //glGetIntegerv(GL_VIEWPORT,vp.data());
+      //const Vector3d ppos = project(pos);
+      //Vector3d uxy = unproject(Vector3d(x,vp(3)-y,ppos(2)));
+      //udrag = ray_plane_intersect(uxy-pos,view,rot,q);
+      //debug_points.clear();
+      //debug_points.push_back(udrag+pos);
+      // project onto rotate plane
+
+      const double dtheta = -(drag_xy - down_xy).dot(down_dir)/
+        outer_radius_on_screen/outer_over_inner*PI/2.;
+      Quaterniond dq(AngleAxisd(dtheta,down_rot*q*Vector3d(0,0,1)));
+      rot = dq * down_rot;
+      udrag = dq * udown;
+      return true;
+    }
+    case DOWN_TYPE_OUTLINE:
+      {
+        Vector3d ppos = project(pos);
+        // project mouse to same depth as pos
+        udrag = unproject_onto(x,y);
+        const Vector2d A = down_xy - ppos.head(2);
+        const Vector2d B = drag_xy - ppos.head(2);
+        const double dtheta = atan2(A(0)*B(1)-A(1)*B(0),A(0)*B(0)+A(1)*B(1));
+        Vector3d view = view_direction(pos).normalized();
+        Quaterniond dq(AngleAxisd(dtheta,view));
+        rot = dq * down_rot;
+      }
+      return true;
+    case DOWN_TYPE_TRACKBALL:
+      {
+        Vector3d ppos = project(pos);
+        const int w = outer_radius_on_screen/outer_over_inner;//vp(2);
+        const int h = w;//vp(3);
+        Quaterniond dq;
+        trackball(
+          w,h,
+          1,
+          Quaterniond::Identity(),
+          (down_xy(0)-ppos(0)+w/2),
+          (down_xy(1)-ppos(1)+h/2),
+          (     x-ppos(0)+w/2),
+          (     y-ppos(1)+h/2),
+          dq);
+        // We've computed change in rotation according to this view:
+        // R = mv * r, R' = rot * (mv * r)
+        // But we only want new value for r:
+        // R' = mv * r'
+        // mv * r' = rot * (mv * r)
+        // r' = mv* * rot * mv * r
+        Matrix4d mv;
+        glGetDoublev(GL_MODELVIEW_MATRIX,mv.data());
+        Quaterniond scene_rot;
+        // Convert modelview matrix to quaternion
+        mat4_to_quat(mv.data(),scene_rot.coeffs().data());
+        scene_rot.normalize();
+        rot = scene_rot.conjugate() * dq * scene_rot * down_rot;
+      }
+      return true;
+  }
+}
+
+bool RotateWidget::up(const int x, const int y)
+{
+  down_type = DOWN_TYPE_NONE;
+  return false;
+}
+
+bool RotateWidget::is_down()
+{
+  return down_type != DOWN_TYPE_NONE;
+}
+
+void RotateWidget::draw()
+{
+  using namespace Eigen;
+  using namespace std;
+  using namespace igl;
+
+  const Vector4d 
+    MAYA_GREEN(128./255.,242./255.,0./255.,1.),
+    MAYA_YELLOW(255./255.,247./255.,50./255.,1.),
+    MAYA_RED(234./255.,63./255.,52./255.,1.),
+    MAYA_BLUE(0./255.,73./255.,252./255.,1.),
+    MAYA_PURPLE(180./255.,73./255.,200./255.,1.),
+    MAYA_GREY(0.5,0.5,0.5,1.0),
+    MAYA_CYAN(131./255.,219./255.,252./255.,1.);
+
+  glDisable(GL_LIGHTING);
+  glDisable(GL_DEPTH_TEST);
+  glLineWidth(2.0);
+
+  double r = unprojected_inner_radius();
+  Vector3d view = view_direction(pos).normalized();
+
+  auto draw_circle = [&](const bool cull)
+  {
+    Vector3d view = view_direction(pos).normalized();
+    glBegin(GL_LINES);
+    const double th_step = (2.0*igl::PI/100.0);
+    for(double th = 0;th<2.0*igl::PI+th_step;th+=th_step)
+    {
+      Vector3d a(cos(th),sin(th),0.0);
+      Vector3d b(cos(th+th_step),sin(th+th_step),0.0);
+      if(!cull || (0.5*(a+b)).dot(view)<FLOAT_EPS)
+      {
+        glVertex3dv(a.data());
+        glVertex3dv(b.data());
+      }
+    }
+    glEnd();
+  };
+
+
+  glPointSize(5.);
+  glColor4dv(MAYA_PURPLE.data());
+  glBegin(GL_POINTS);
+  for(auto & p : debug_points)
+  {
+    glVertex3dv(p.data());
+  }
+  glEnd();
+
+  glPushMatrix();
+  glTranslated(pos(0),pos(1),pos(2));
+
+  glScaled(r,r,r);
+  // Draw outlines
+  {
+    glPushMatrix();
+    glColor4dv(MAYA_GREY.data());
+    Quaterniond q;
+    q.setFromTwoVectors(Vector3d(0,0,1),view);
+    glMultMatrixd(Affine3d(q).matrix().data());
+    draw_circle(false);
+    glScaled(outer_over_inner,outer_over_inner,outer_over_inner);
+    if(selected_type == DOWN_TYPE_OUTLINE)
+    {
+      glColor4dv(MAYA_YELLOW.data());
+    }else
+    {
+      glColor4dv(MAYA_CYAN.data());
+    }
+    draw_circle(false);
+    glPopMatrix();
+  }
+  // Draw quartiles
+  {
+    glPushMatrix();
+    glMultMatrixd(Affine3d(rot).matrix().data());
+    if(selected_type == DOWN_TYPE_Z)
+    {
+      glColor4dv(MAYA_YELLOW.data());
+    }else
+    {
+      glColor4dv(MAYA_BLUE.data());
+    }
+    draw_circle(true);
+    if(selected_type == DOWN_TYPE_Y)
+    {
+      glColor4dv(MAYA_YELLOW.data());
+    }else
+    {
+      glColor4dv(MAYA_GREEN.data());
+    }
+    glRotated(90.0,1.0,0.0,0.0);
+    draw_circle(true);
+    if(selected_type == DOWN_TYPE_X)
+    {
+      glColor4dv(MAYA_YELLOW.data());
+    }else
+    {
+      glColor4dv(MAYA_RED.data());
+    }
+    glRotated(90.0,0.0,1.0,0.0);
+    draw_circle(true);
+    glPopMatrix();
+  }
+  glColor3dv(MAYA_GREY.data());
+  draw_guide();
+  glPopMatrix();
+  glEnable(GL_DEPTH_TEST);
+};
+
+void RotateWidget::draw_guide()
+{
+  using namespace Eigen;
+  using namespace std;
+  using namespace igl;
+  // http://www.codeproject.com/Articles/23444/A-Simple-OpenGL-Stipple-Polygon-Example-EP_OpenGL_
+  const GLubyte halftone[] = {
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
+    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55};
+
+
+  switch(down_type)
+  {
+    case DOWN_TYPE_NONE:
+    case DOWN_TYPE_TRACKBALL:
+      return;
+    case DOWN_TYPE_OUTLINE:
+      glScaled(outer_over_inner,outer_over_inner,outer_over_inner);
+      break;
+    default:
+      break;
+  }
+  const Vector3d nudown(udown.normalized()), 
+    nudrag(udrag.normalized());
+  glPushMatrix();
+  glDisable(GL_POINT_SMOOTH);
+  glPointSize(5.);
+  glBegin(GL_POINTS);
+  glVertex3dv(nudown.data());
+  glVertex3d(0,0,0);
+  glVertex3dv(nudrag.data());
+  glEnd();
+  glBegin(GL_LINE_STRIP);
+  glVertex3dv(nudown.data());
+  glVertex3d(0,0,0);
+  glVertex3dv(nudrag.data());
+  glEnd();
+  glEnable(GL_POLYGON_STIPPLE);
+  glPolygonStipple(halftone);
+  glBegin(GL_TRIANGLE_FAN);
+  glVertex3d(0,0,0);
+  Quaterniond dq = rot * down_rot.conjugate();
+  //dq.setFromTwoVectors(nudown,nudrag);
+  for(double t = 0;t<1;t+=0.1)
+  {
+    const Vector3d p = Quaterniond::Identity().slerp(t,dq) * nudown;
+    glVertex3dv(p.data());
+  }
+  glVertex3dv(nudrag.data());
+  glEnd();
+  glDisable(GL_POLYGON_STIPPLE);
+  glPopMatrix();
+}
+

+ 53 - 0
examples/rotate-widget/RotateWidget.h

@@ -0,0 +1,53 @@
+#include <Eigen/Core>
+#include <Eigen/Geometry>
+#include <vector>
+// 3D Rotate tool widget 
+class RotateWidget
+{
+  public:
+    static Eigen::Quaterniond axis_q[3];
+    static Eigen::Vector3d view_direction(const int x, const int y);
+    static Eigen::Vector3d view_direction(const Eigen::Vector3d & pos);
+    Eigen::Vector3d pos;
+    Eigen::Quaterniond rot,down_rot;
+    Eigen::Vector2d down_xy,drag_xy,down_dir;
+    Eigen::Vector3d udown,udrag;
+    double outer_radius_on_screen;
+    double outer_over_inner;
+    enum DownType
+    {
+      DOWN_TYPE_X = 0,
+      DOWN_TYPE_Y = 1,
+      DOWN_TYPE_Z = 2,
+      DOWN_TYPE_OUTLINE = 3,
+      DOWN_TYPE_TRACKBALL = 4,
+      DOWN_TYPE_NONE = 5,
+      NUM_DOWN_TYPES = 6
+    } down_type, selected_type;
+    std::vector<Eigen::Vector3d> debug_points;
+    RotateWidget();
+    // Vector from origin to mouse click "Unprojected" onto plane with depth of
+    // origin and scale to so that outer radius is 1
+    // 
+    // Inputs:
+    //   x  mouse x position
+    //   y  mouse y position
+    // Returns vector
+    Eigen::Vector3d unproject_onto(const int x, const int y);
+    // Shoot ray from mouse click to sphere
+    //
+    // Inputs:
+    //   x  mouse x position
+    //   y  mouse y position
+    // Outputs:
+    //   hit  position of hit
+    // Returns true only if there was a hit
+    bool intersect(const int x, const int y, Eigen::Vector3d & hit);
+    double unprojected_inner_radius();
+    bool down(const int x, const int y);
+    bool drag(const int x, const int y);
+    bool up(const int x, const int y);
+    bool is_down();
+    void draw();
+    void draw_guide();
+};

+ 617 - 0
examples/rotate-widget/example.cpp

@@ -0,0 +1,617 @@
+#include "RotateWidget.h"
+#include <igl/two_axis_valuator_fixed_up.h>
+#include <igl/readOBJ.h>
+#include <igl/readTGF.h>
+#include <igl/writeOBJ.h>
+#include <igl/writeOFF.h>
+#include <igl/readWRL.h>
+#include <igl/report_gl_error.h>
+#include <igl/triangulate.h>
+#include <igl/readOFF.h>
+#include <igl/readMESH.h>
+#include <igl/draw_mesh.h>
+#include <igl/draw_floor.h>
+#include <igl/pathinfo.h>
+#include <igl/list_to_matrix.h>
+#include <igl/quat_to_mat.h>
+#include <igl/per_face_normals.h>
+#include <igl/material_colors.h>
+#include <igl/trackball.h>
+#include <igl/snap_to_canonical_view_quat.h>
+#include <igl/snap_to_fixed_up.h>
+#include <igl/REDRUM.h>
+#include <igl/Camera.h>
+#include <igl/ReAntTweakBar.h>
+#include <igl/get_seconds.h>
+
+#include <Eigen/Core>
+#include <Eigen/Geometry>
+
+#include <GLUT/glut.h>
+
+#include <string>
+#include <vector>
+#include <list>
+#include <iostream>
+
+
+Eigen::MatrixXd V,N,C,W,M;
+Eigen::VectorXd Vmid,Vmin,Vmax;
+double bbd = 1.0;
+Eigen::MatrixXi F,BE;
+Eigen::VectorXi P;
+struct State
+{
+  igl::Camera camera;
+  RotateWidget widget;
+} s;
+
+bool wireframe = false;
+bool widget_on_top = false;
+
+// 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;
+
+std::list<State> undo_stack;
+std::list<State> redo_stack;
+
+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;
+
+void push_undo()
+{
+  undo_stack.push_back(s);
+  // Clear
+  redo_stack = std::list<State>();
+}
+
+// No-op setter, does nothing
+void TW_CALL no_op(const void * /*value*/, void * /*clientData*/)
+{
+}
+
+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)
+  {
+    push_undo();
+    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;
+  for(auto & s : undo_stack)
+  {
+    s.camera.m_aspect = (double)width/(double)height;
+  }
+  for(auto & s : redo_stack)
+  {
+    s.camera.m_aspect = (double)width/(double)height;
+  }
+}
+
+void push_scene()
+{
+  using namespace igl;
+  using namespace std;
+  glMatrixMode(GL_PROJECTION);
+  glPushMatrix();
+  glLoadIdentity();
+  auto & camera = s.camera;
+  gluPerspective(camera.m_angle,camera.m_aspect,camera.m_near,camera.m_far);
+  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));
+}
+
+void push_object()
+{
+  using namespace igl;
+  glPushMatrix();
+  glScaled(2./bbd,2./bbd,2./bbd);
+  glTranslated(-Vmid(0),-Vmid(1),-Vmid(2));
+}
+
+void pop_object()
+{
+  glPopMatrix();
+}
+
+void pop_scene()
+{
+  glMatrixMode(GL_PROJECTION);
+  glPopMatrix();
+  glMatrixMode(GL_MODELVIEW);
+  glPopMatrix();
+}
+
+// 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);
+  glEnable(GL_LIGHT1);
+  float WHITE[4] =  {0.8,0.8,0.8,1.};
+  float GREY[4] =  {0.4,0.4,0.4,1.};
+  float BLACK[4] =  {0.,0.,0.,1.};
+  Vector4f pos = light_pos;
+  glLightfv(GL_LIGHT0,GL_AMBIENT,GREY);
+  glLightfv(GL_LIGHT0,GL_DIFFUSE,WHITE);
+  glLightfv(GL_LIGHT0,GL_SPECULAR,BLACK);
+  glLightfv(GL_LIGHT0,GL_POSITION,pos.data());
+  pos(0) *= -1;
+  pos(1) *= -1;
+  pos(2) *= -1;
+  glLightfv(GL_LIGHT1,GL_AMBIENT,GREY);
+  glLightfv(GL_LIGHT1,GL_DIFFUSE,WHITE);
+  glLightfv(GL_LIGHT1,GL_SPECULAR,BLACK);
+  glLightfv(GL_LIGHT1,GL_POSITION,pos.data());
+}
+
+void display()
+{
+  using namespace igl;
+  using namespace std;
+  using namespace Eigen;
+  const float back[4] = {30.0/255.0,30.0/255.0,50.0/255.0,0};
+  glClearColor(back[0],back[1],back[2],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;
+    camera.orbit(q.conjugate());
+  }
+
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_NORMALIZE);
+  lights();
+  push_scene();
+
+  // Draw a nice floor
+  glEnable(GL_DEPTH_TEST);
+  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};
+  glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
+  draw_floor(GREY,DARK_GREY);
+  glPopMatrix();
+
+  push_object();
+
+
+  // Set material properties
+  glDisable(GL_COLOR_MATERIAL);
+  glMaterialfv(GL_FRONT, GL_AMBIENT,  GOLD_AMBIENT);
+  glMaterialfv(GL_FRONT, GL_DIFFUSE,  GOLD_DIFFUSE  );
+  glMaterialfv(GL_FRONT, GL_SPECULAR, GOLD_SPECULAR);
+  glMaterialf (GL_FRONT, GL_SHININESS, 128);
+  glMaterialfv(GL_BACK, GL_AMBIENT,  SILVER_AMBIENT);
+  glMaterialfv(GL_BACK, GL_DIFFUSE,  FAST_GREEN_DIFFUSE  );
+  glMaterialfv(GL_BACK, GL_SPECULAR, SILVER_SPECULAR);
+  glMaterialf (GL_BACK, GL_SHININESS, 128);
+
+  glPushMatrix();
+  glTranslated( s.widget.pos(0), s.widget.pos(1), s.widget.pos(2));
+  glMultMatrixd(Affine3d(s.widget.rot).matrix().data());
+  glTranslated( -s.widget.pos(0), -s.widget.pos(1), -s.widget.pos(2));
+  draw_mesh(V,F,N);
+  glPopMatrix();
+
+  if(widget_on_top)
+  {
+    glDisable(GL_DEPTH_TEST);
+  }
+
+  s.widget.draw();
+
+  pop_object();
+
+  pop_scene();
+
+  report_gl_error();
+
+  TwDraw();
+  glutSwapBuffers();
+  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;
+  }
+  push_undo();
+
+  auto & camera = s.camera;
+  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);
+  }
+}
+
+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);
+  push_scene();
+  push_object();
+  switch(glutButton)
+  {
+    case GLUT_RIGHT_BUTTON:
+    case GLUT_LEFT_BUTTON:
+    {
+      switch(glutState)
+      {
+        case 1:
+          // up
+          s.widget.up(mouse_x,mouse_y);
+          glutSetCursor(GLUT_CURSOR_INHERIT);
+          is_rotating = false;
+          break;
+        case 0:
+          bool widget_using = s.widget.down(mouse_x,mouse_y);
+          if(widget_using)
+          {
+            push_undo();
+          }
+          if(!tw_using && !widget_using)
+          {
+            push_undo();
+            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;
+    }
+    // Scroll down
+    case 3:
+    {
+      mouse_wheel(0,-1,mouse_x,mouse_y);
+      break;
+    }
+    // Scroll up
+    case 4:
+    {
+      mouse_wheel(0,1,mouse_x,mouse_y);
+      break;
+    }
+    // Scroll left
+    case 5:
+    {
+      mouse_wheel(1,-1,mouse_x,mouse_y);
+      break;
+    }
+    // Scroll right
+    case 6:
+    {
+      mouse_wheel(1,1,mouse_x,mouse_y);
+      break;
+    }
+  }
+  pop_object();
+  pop_scene();
+}
+
+void mouse_drag(int mouse_x, int mouse_y)
+{
+  using namespace igl;
+  using namespace std;
+  using namespace Eigen;
+
+  push_scene();
+  push_object();
+  s.widget.drag(mouse_x,mouse_y);
+  pop_object();
+  pop_scene();
+
+  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;
+    }
+    camera.orbit(q.conjugate());
+  }
+}
+
+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();
+  s.widget.pos = Vmid;
+}
+
+void undo()
+{
+  using namespace std;
+  if(!undo_stack.empty())
+  {
+    redo_stack.push_back(s);
+    s = undo_stack.back();
+    undo_stack.pop_back();
+  }
+}
+
+void redo()
+{
+  using namespace std;
+  if(!redo_stack.empty())
+  {
+    undo_stack.push_back(s);
+    s = redo_stack.back();
+    redo_stack.pop_back();
+  }
+}
+
+void key(unsigned char key, int mouse_x, int mouse_y)
+{
+  using namespace std;
+  using namespace igl;
+  using namespace Eigen;
+  int mod = glutGetModifiers();
+  const bool command_down = GLUT_ACTIVE_COMMAND & mod;
+  const bool shift_down = GLUT_ACTIVE_SHIFT & mod;
+  switch(key)
+  {
+    // ESC
+    case char(27):
+      rebar.save(REBAR_NAME);
+    // ^C
+    case char(3):
+      exit(0);
+    case 'z':
+    case 'Z':
+      if(command_down)
+      {
+        if(shift_down)
+        {
+          redo();
+        }else
+        {
+          undo();
+        }
+        break;
+      }else
+      {
+        push_undo();
+        Quaterniond q;
+        snap_to_canonical_view_quat(s.camera.m_rotation_conj,1.0,q);
+        s.camera.orbit(q.conjugate());
+      }
+    default:
+      if(!TwEventKeyboardGLUT(key,mouse_x,mouse_y))
+      {
+        cout<<"Unknown key command: "<<key<<" "<<int(key)<<endl;
+      }
+  }
+  
+}
+
+int main(int argc, char * argv[])
+{
+  using namespace std;
+  using namespace Eigen;
+  using namespace igl;
+  string filename = "../shared/cheburashka.obj";
+  if(argc < 3)
+  {
+    cerr<<"Usage:"<<endl<<"    ./example input.obj"<<endl;
+    cout<<endl<<"Opening default mesh..."<<endl;
+  }else
+  {
+    // Read and prepare mesh
+    filename = argv[1];
+  }
+
+  // print key commands
+  cout<<"[Click] and [drag]  Rotate model using trackball."<<endl;
+  cout<<"[Z,z]               Snap rotation to canonical view."<<endl;
+  cout<<"[⌘ Z]               Undo."<<endl;
+  cout<<"[⇧ ⌘ Z]             Redo."<<endl;
+  cout<<"[^C,ESC]            Exit."<<endl;
+
+  // dirname, basename, extension and filename
+  string d,b,ext,f;
+  pathinfo(filename,d,b,ext,f);
+  // Convert extension to lower case
+  transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
+  vector<vector<double > > vV,vN,vTC;
+  vector<vector<int > > vF,vFTC,vFN;
+  if(ext == "obj")
+  {
+    // Convert extension to lower case
+    if(!igl::readOBJ(filename,vV,vTC,vN,vF,vFTC,vFN))
+    {
+      return 1;
+    }
+  }else if(ext == "off")
+  {
+    // Convert extension to lower case
+    if(!igl::readOFF(filename,vV,vF,vN))
+    {
+      return 1;
+    }
+  }else if(ext == "wrl")
+  {
+    // Convert extension to lower case
+    if(!igl::readWRL(filename,vV,vF))
+    {
+      return 1;
+    }
+  //}else
+  //{
+  //  // Convert extension to lower case
+  //  MatrixXi T;
+  //  if(!igl::readMESH(filename,V,T,F))
+  //  {
+  //    return 1;
+  //  }
+  //  //if(F.size() > T.size() || F.size() == 0)
+  //  {
+  //    boundary_faces(T,F);
+  //  }
+  }
+  if(vV.size() > 0)
+  {
+    if(!list_to_matrix(vV,V))
+    {
+      return 1;
+    }
+    triangulate(vF,F);
+  }
+
+  init_relative();
+
+  // 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("TweakBar");
+  rebar.TwAddVarRW("camera_rotation", TW_TYPE_QUAT4D, 
+    s.camera.m_rotation_conj.coeffs().data(), "open readonly=true");
+  TwType RotationTypeTW = ReTwDefineEnumFromString("RotationType",
+    "igl_trackball,two-a...-fixed-up");
+  rebar.TwAddVarCB( "rotation_type", RotationTypeTW,
+    set_rotation_type,get_rotation_type,NULL,"keyIncr=] keyDecr=[");
+  rebar.TwAddVarRW("widget_on_top", TW_TYPE_BOOLCPP,&widget_on_top,"key=O");
+  rebar.TwAddVarRW("wireframe", TW_TYPE_BOOLCPP,&wireframe,"key=l");
+  rebar.load(REBAR_NAME);
+
+  // Init antweakbar
+  glutInitDisplayString( "rgba depth double samples>=8 ");
+  glutInitWindowSize(glutGet(GLUT_SCREEN_WIDTH)/2.0,glutGet(GLUT_SCREEN_HEIGHT)/2.0);
+  glutCreateWindow("upright");
+  glutDisplayFunc(display);
+  glutReshapeFunc(reshape);
+  glutKeyboardFunc(key);
+  glutMouseFunc(mouse);
+  glutMotionFunc(mouse_drag);
+  glutPassiveMotionFunc((GLUTmousemotionfun)TwEventMouseMotionGLUT);
+  glutMainLoop();
+
+  return 0;
+}

+ 0 - 1
include/igl/bbw/bbw.cpp

@@ -12,7 +12,6 @@
 #include <igl/invert_diag.h>
 #include <igl/speye.h>
 #include <igl/slice_into.h>
-#include <igl/normalize_row_sums.h>
 #include <igl/min_quad_with_fixed.h>
 
 #define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET

+ 7 - 0
include/igl/intersect.cpp

@@ -1,3 +1,10 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+// 
+// Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
+// 
+// This Source Code Form is subject to the terms of the Mozilla Public License 
+// v. 2.0. If a copy of the MPL was not distributed with this file, You can 
+// obtain one at http://mozilla.org/MPL/2.0/.
 #include "intersect.h"
 template <class M>
 IGL_INLINE void igl::intersect(const M & A, const M & B, M & C)

+ 7 - 0
include/igl/intersect.h

@@ -1,3 +1,10 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+// 
+// Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
+// 
+// This Source Code Form is subject to the terms of the Mozilla Public License 
+// v. 2.0. If a copy of the MPL was not distributed with this file, You can 
+// obtain one at http://mozilla.org/MPL/2.0/.
 #ifndef IGL_INTERSECT_H
 #define IGL_INTERSECT_H
 #include "igl_inline.h"

+ 1 - 0
include/igl/matlab_format.cpp

@@ -108,4 +108,5 @@ template Eigen::WithFormat<Eigen::Matrix<double, 4, 4, 0, 4, 4> > const igl::mat
 template Eigen::WithFormat<Eigen::Matrix<double, -1, 4, 0, -1, 4> > const igl::matlab_format<Eigen::Matrix<double, -1, 4, 0, -1, 4> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, 4, 0, -1, 4> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >);
 template Eigen::WithFormat<Eigen::Matrix<double, -1, 2, 0, -1, 2> > const igl::matlab_format<Eigen::Matrix<double, -1, 2, 0, -1, 2> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, 2, 0, -1, 2> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >);
 template Eigen::WithFormat<Eigen::Matrix<double, 2, 1, 0, 2, 1> > const igl::matlab_format<Eigen::Matrix<double, 2, 1, 0, 2, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, 2, 1, 0, 2, 1> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >);
+template Eigen::WithFormat<Eigen::Matrix<int, 1, -1, 1, 1, -1> > const igl::matlab_format<Eigen::Matrix<int, 1, -1, 1, 1, -1> >(Eigen::PlainObjectBase<Eigen::Matrix<int, 1, -1, 1, 1, -1> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >);
 #endif

+ 3 - 4
include/igl/normalize_row_sums.cpp

@@ -12,16 +12,15 @@ IGL_INLINE void igl::normalize_row_sums(
   const Eigen::MatrixBase<DerivedA>& A,
   Eigen::MatrixBase<DerivedB> & B)
 {
-  // Resize output
-  B.derived().resize(A.rows(),A.cols());
-
+#ifndef NDEBUG
   // loop over rows
   for(int i = 0; i < A.rows();i++)
   {
     typename DerivedB::Scalar sum = A.row(i).sum();
     assert(sum != 0);
-    B.row(i) = A.row(i).array()/sum;
   }
+#endif
+  B = (A.array().colwise() / A.rowwise().sum().array()).eval();
 }
 #ifndef IGL_HEADER_ONLY
 // Explicit template specialization

+ 2 - 0
include/igl/normalize_row_sums.h

@@ -18,6 +18,8 @@ namespace igl
   //   A  #rows by k input matrix
   // Outputs:
   //   B  #rows by k input matrix, can be the same as A
+  //
+  // Note: This is just calling an Eigen one-liner.
   template <typename DerivedA, typename DerivedB>
   IGL_INLINE void normalize_row_sums(
     const Eigen::MatrixBase<DerivedA>& A,

+ 1 - 0
include/igl/project_to_line.cpp

@@ -122,4 +122,5 @@ IGL_INLINE void igl::project_to_line(
 template void igl::project_to_line<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, 1, 0, -1, 1>, Eigen::Matrix<double, -1, 1, 0, -1, 1>, Eigen::Matrix<double, -1, 1, 0, -1, 1> >(Eigen::Matrix<double, -1, -1, 0, -1, -1> const&, Eigen::Matrix<double, -1, 1, 0, -1, 1> const&, Eigen::Matrix<double, -1, 1, 0, -1, 1> const&, Eigen::Matrix<double, -1, 1, 0, -1, 1>&, Eigen::Matrix<double, -1, 1, 0, -1, 1>&);
 template void igl::project_to_line<double>(double, double, double, double, double, double, double, double, double, double&, double&);
 template void igl::project_to_line<double>(double, double, double, double, double, double, double, double, double, double&, double&,double&,double&, double&);
+template void igl::project_to_line<Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 3, 1, 1, 3> >, Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 3, 1, 1, 3> >, Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 1, 0, 1, 1> >, Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 1, 0, 1, 1> > >(Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 3, 1, 1, 3> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 3, 1, 1, 3> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 3, 1, 1, 3> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 1, 0, 1, 1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 1, 0, 1, 1> >&);
 #endif

+ 2 - 2
include/igl/project_to_line.h

@@ -5,8 +5,8 @@
 // This Source Code Form is subject to the terms of the Mozilla Public License 
 // v. 2.0. If a copy of the MPL was not distributed with this file, You can 
 // obtain one at http://mozilla.org/MPL/2.0/.
-#ifndef IGL_PROJECT_TO_LINE
-#define IGL_PROJECT_TO_LINE
+#ifndef IGL_PROJECT_TO_LINE_H
+#define IGL_PROJECT_TO_LINE_H
 #include "igl_inline.h"
 
 namespace igl

+ 10 - 0
include/igl/readTGF.cpp

@@ -188,4 +188,14 @@ IGL_INLINE bool igl::readTGF(
 
   return true;
 }
+
+IGL_INLINE bool igl::readTGF(
+  const std::string tgf_filename,
+  Eigen::MatrixXd & C,
+  Eigen::MatrixXi & E)
+{
+  Eigen::VectorXi P;
+  Eigen::MatrixXi BE,CE,PE;
+  return readTGF(tgf_filename,C,E,P,BE,CE,PE);
+}
 #endif

+ 4 - 0
include/igl/readTGF.h

@@ -52,6 +52,10 @@ namespace igl
     Eigen::MatrixXi & BE,
     Eigen::MatrixXi & CE,
     Eigen::MatrixXi & PE);
+  IGL_INLINE bool readTGF(
+    const std::string tgf_filename,
+    Eigen::MatrixXd & C,
+    Eigen::MatrixXi & E);
   #endif
 }
 

+ 4 - 0
include/igl/tetgen/mesh_with_skeleton.cpp

@@ -101,3 +101,7 @@ bool igl::mesh_with_skeleton(
   return igl::mesh_with_skeleton(
     V,F,C,P,BE,CE,samples_per_bone,DEFAULT_TETGEN_FLAGS,VV,TT,FF);
 }
+
+#ifndef IGL_HEADER_ONLY
+// Explicit template instanciation
+#endif

+ 1 - 0
include/igl/unproject.cpp

@@ -61,6 +61,7 @@ template int igl::unproject<Eigen::Matrix<double, 3, 1, 0, 3, 1>, Eigen::Matrix<
 template int igl::unproject<Eigen::Matrix<float, 3, 1, 0, 3, 1>, Eigen::Matrix<float, 3, 1, 0, 3, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> >&);
 template Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > igl::unproject<Eigen::Matrix<float, 3, 1, 0, 3, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > const&);
 template int igl::unproject<Eigen::Matrix<double, 3, 1, 0, 3, 1>, Eigen::Matrix<float, 3, 1, 0, 3, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, 3, 1, 0, 3, 1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> >&);
+template Eigen::PlainObjectBase<Eigen::Matrix<double, 3, 1, 0, 3, 1> > igl::unproject<Eigen::Matrix<double, 3, 1, 0, 3, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, 3, 1, 0, 3, 1> > const&);
 #endif
 
 #endif

+ 16 - 0
include/igl/view_axis.cpp

@@ -23,4 +23,20 @@ IGL_INLINE void igl::view_axis(const double * mv, double * x, double * y, double
   *y = -mv[1*4+2];
   *z = -mv[2*4+2];
 }
+
+template <typename DerivedV>
+IGL_INLINE void igl::view_axis(Eigen::PlainObjectBase<DerivedV> & V)
+{
+  double x,y,z;
+  view_axis(&x,&y,&z);
+  V(0) = x;
+  V(1) = y;
+  V(2) = z;
+}
+
+#ifndef IGL_HEADER_ONLY
+// Explicit template instanciation
+template void igl::view_axis<Eigen::Matrix<double, 3, 1, 0, 3, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, 3, 1, 0, 3, 1> >&);
+#endif
+
 #endif

+ 3 - 0
include/igl/view_axis.h

@@ -8,6 +8,7 @@
 #ifndef IGL_VIEW_AXIS_H
 #define IGL_VIEW_AXIS_H 
 #include "igl_inline.h"
+#include <Eigen/Core>
 
 namespace igl
 {
@@ -24,6 +25,8 @@ namespace igl
   // Note: View axis is returned *UN-normalized*
   IGL_INLINE void view_axis(double * x, double * y, double * z);
   IGL_INLINE void view_axis(const double * mv, double * x, double * y, double * z);
+  template <typename DerivedV>
+  IGL_INLINE void view_axis(Eigen::PlainObjectBase<DerivedV> & V);
 };