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

major camera refactor

Former-commit-id: 9d4c733a4708eabfcee8255a9195d9de2a5cab6f
Alec Jacobson (jalec 11 жил өмнө
parent
commit
5b73716bcf

+ 0 - 280
examples/camera/Camera.h

@@ -1,280 +0,0 @@
-#include <Eigen/Geometry>
-#include <Eigen/Core>
-
-class Camera
-{
-  public:
-    //  m_zoom   Zoom of camera lens {1}
-    //  m_angle  Field of view angle in degrees {45}
-    //  m_aspect  Aspect ratio {1}
-    //  m_near  near clipping plane {1e-2}
-    //  m_far  far clipping plane {100}
-    //  m_at_dist  distance of looking at point {1}
-    //  m_rotation  Rotation part of rigid transformation of camera {identity}.
-    //    Note that this seems to the inverse of what's stored in the old
-    //    igl::Camera class.
-    //  m_translation  Translation part of rigid transformation of camera
-    //    {(0,0,1)}
-    double m_zoom, m_angle, m_aspect, m_near, m_far, m_at_dist;
-    Eigen::Quaterniond m_rotation;
-    Eigen::Vector3d m_translation;
-    Camera();
-    // Return projection matrix that takes relative camera coordinates and
-    // transforms it to viewport coordinates
-    //
-    // Note:
-    //
-    //     gluPerspective(m_angle,m_aspect,m_near,m_far);
-    //
-    // Is equivalent to
-    //
-    //     glMultMatrixd(v_camera.projection().data());
-    //
-    Eigen::Matrix4d projection() const;
-    // Return an Affine transformation (rigid actually) that takes a world 3d coordinate and
-    // transforms it into the relative camera coordinates.
-    Eigen::Affine3d affine() const;
-    // Return an Affine transformation (rigid actually) that takes relative
-    // coordinates and tramsforms them into world 3d coordinates.
-    //
-    // Note:
-    //
-    //     gluLookAt(
-    //       eye()(0), eye()(1), eye()(2),
-    //       at()(0), at()(1), at()(2),
-    //       up()(0), up()(1), up()(2));
-    //
-    // Is equivalent to
-    //
-    //     glMultMatrixd(camera.affine().matrix().data());
-    //
-    // See also: affine, eye, at, up
-    Eigen::Affine3d inverse() const;
-    // Returns world coordinates position of center or "eye" of camera.
-    Eigen::Vector3d eye() const;
-    // Returns world coordinate position of a point "eye" is looking at.
-    Eigen::Vector3d at() const;
-    // Returns world coordinate unit vector of "up" vector
-    Eigen::Vector3d up() const;
-    // Return top right corner of unit plane in relative coordinates, that is
-    // (w/2,h/2,1)
-    Eigen::Vector3d unit_plane() const;
-    // Move dv in the relative coordinate frame of the camera (move the FPS)
-    //
-    // Inputs:
-    //   dv  (x,y,z) displacement vector
-    //
-    void dolly(const Eigen::Vector3d & dv);
-    // "Scale zoom": Move `eye`, but leave `at`
-    //
-    // Input:
-    //   s  amount to scale distance to at
-    void push_away(const double s);
-    // Aka "Hitchcock", "Vertigo", "Spielberg" or "Trombone" zoom:
-    // simultaneously dolly while changing angle so that `at` not only stays
-    // put in relative coordinates but also projected coordinates. That is
-    //
-    // Inputs:
-    //   da  change in angle in degrees
-    void dolly_zoom(const double da);
-    // Turn around eye so that rotation is now q
-    //
-    // Inputs:
-    //   q  new rotation as quaternion
-    void turn_eye(const Eigen::Quaterniond & q);
-    // Orbit around at so that rotation is now q
-    //
-    // Inputs:
-    //   q  new rotation as quaternion
-    void orbit(const Eigen::Quaterniond & q);
-    // Rotate and translate so that camera is situated at "eye" looking at "at"
-    // with "up" pointing up.
-    //
-    // Inputs:
-    //   eye  (x,y,z) coordinates of eye position
-    //   at   (x,y,z) coordinates of at position
-    //   up   (x,y,z) coordinates of up vector
-    void look_at(
-      const Eigen::Vector3d & eye,
-      const Eigen::Vector3d & at,
-      const Eigen::Vector3d & up);
-};
-
-// Implementation
-#include <igl/PI.h>
-#include <igl/EPS.h>
-#include <cmath>
-#include <cassert>
-
-Camera::Camera():
-  m_zoom(1),m_angle(45.0),m_aspect(1),m_near(1e-2),m_far(100),m_at_dist(1),
-  m_rotation(1,0,0,0),
-  m_translation(0,0,1)
-{
-}
-
-Eigen::Matrix4d Camera::projection() const
-{
-  Eigen::Matrix4d P;
-  using namespace std;
-  using namespace igl;
-  // http://stackoverflow.com/a/3738696/148668
-  const double yScale = tan(PI*0.5 - 0.5*m_angle*PI/180.);
-  // http://stackoverflow.com/a/14975139/148668
-  const double xScale = yScale/m_aspect;
-  P<< 
-    xScale, 0, 0, 0,
-    0, yScale, 0, 0,
-    0, 0, -(m_far+m_near)/(m_far-m_near), -1,
-    0, 0, -2.*m_near*m_far/(m_far-m_near), 0;
-  return P.transpose();
-}
-
-Eigen::Affine3d Camera::affine() const
-{
-  using namespace Eigen;
-  Affine3d t = Affine3d::Identity();
-  t.rotate(m_rotation);
-  t.translate(m_translation);
-  return t;
-}
-
-Eigen::Affine3d Camera::inverse() const
-{
-  using namespace Eigen;
-  Affine3d t = Affine3d::Identity();
-  t.translate(-m_translation);
-  t.rotate(m_rotation.conjugate());
-  return t;
-}
-
-Eigen::Vector3d Camera::eye() const
-{
-  using namespace Eigen;
-  return affine() * Vector3d(0,0,0);
-}
-
-Eigen::Vector3d Camera::at() const
-{
-  using namespace Eigen;
-  return affine() * (Vector3d(0,0,-1)*m_at_dist);
-}
-
-Eigen::Vector3d Camera::up() const
-{
-  using namespace Eigen;
-  Affine3d t = Affine3d::Identity();
-  t.rotate(m_rotation);
-  return t * Vector3d(0,1,0);
-}
-
-Eigen::Vector3d Camera::unit_plane() const
-{
-  using namespace igl;
-  // Distance of center pixel to eye
-  const double d = 1.0;
-  const double a = m_aspect;
-  const double theta = m_angle*PI/180.;
-  const double w =
-    2.*sqrt(-d*d/(a*a*pow(tan(0.5*theta),2.)-1.))*a*tan(0.5*theta);
-  const double h = w/a;
-  return Eigen::Vector3d(w*0.5,h*0.5,-d);
-}
-
-void Camera::dolly(const Eigen::Vector3d & dv)
-{
-  m_translation += dv;
-}
-
-void Camera::push_away(const double s)
-{
-  using namespace Eigen;
-  using namespace igl;
-#ifndef NDEBUG
-  Vector3d old_at = at();
-#endif
-  const double old_at_dist = m_at_dist;
-  m_at_dist = old_at_dist * s;
-  dolly(Vector3d(0,0,1)*(m_at_dist - old_at_dist));
-  assert((old_at-at()).squaredNorm() < DOUBLE_EPS);
-}
-
-void Camera::dolly_zoom(const double da)
-{
-  using namespace std;
-  using namespace igl;
-  using namespace Eigen;
-#ifndef NDEBUG
-  Vector3d old_at = at();
-#endif
-  const double old_angle = m_angle;
-  m_angle += da;
-  m_angle = min(89.,max(5.,m_angle));
-  const double s = 
-    (2.*tan(old_angle/2./180.*M_PI)) /
-    (2.*tan(m_angle/2./180.*M_PI)) ;
-  const double old_at_dist = m_at_dist;
-  m_at_dist = old_at_dist * s;
-  dolly(Vector3d(0,0,1)*(m_at_dist - old_at_dist));
-  assert((old_at-at()).squaredNorm() < DOUBLE_EPS);
-}
-
-void Camera::turn_eye(const Eigen::Quaterniond & q)
-{
-  using namespace Eigen;
-  using namespace igl;
-  Vector3d old_eye = eye();
-  // eye should be fixed
-  //
-  // eye_1 = R_1 * t_1 = eye_0
-  // t_1 = R_1' * eye_0
-  m_rotation = q;
-  m_translation = m_rotation.conjugate() * old_eye;
-  assert((old_eye - eye()).squaredNorm() < DOUBLE_EPS);
-}
-
-void Camera::orbit(const Eigen::Quaterniond & q)
-{
-  using namespace Eigen;
-  using namespace igl;
-#ifndef NDEBUG
-  Vector3d old_at = at();
-#endif
-  // at should be fixed
-  //
-  // at_1 = R_1 * t_1 - R_1 * z = at_0
-  // t_1 = R_1' * (at_0 + R_1 * z)
-  m_rotation = q;
-  m_translation = 
-    m_rotation.conjugate() * 
-      (old_at + 
-         m_rotation * Vector3d(0,0,1) * m_at_dist);
-  assert((old_at - at()).squaredNorm() < DOUBLE_EPS);
-}
-
-void Camera::look_at(
-  const Eigen::Vector3d & eye,
-  const Eigen::Vector3d & at,
-  const Eigen::Vector3d & up)
-{
-  using namespace Eigen;
-  using namespace std;
-  using namespace igl;
-  // http://www.opengl.org/sdk/docs/man2/xhtml/gluLookAt.xml
-  // Normalize vector from at to eye
-  Vector3d F = eye-at;
-  m_at_dist = F.norm();
-  F.normalize();
-  // Project up onto plane orthogonal to F and normalize
-  const Vector3d proj_up = (up-(up.dot(F))*F).normalized();
-  Quaterniond a,b;
-  a.setFromTwoVectors(Vector3d(0,0,-1),-F);
-  b.setFromTwoVectors(a*Vector3d(0,1,0),proj_up);
-  m_rotation = a*b;
-  m_translation = m_rotation.conjugate() * eye;
-  assert(           (eye-this->eye()).squaredNorm() < DOUBLE_EPS);
-  assert((F-(this->eye()-this->at()).normalized()).squaredNorm() < 
-    DOUBLE_EPS);
-  assert(           (at-this->at()).squaredNorm() < DOUBLE_EPS);
-  assert(        (proj_up-this->up()).squaredNorm() < DOUBLE_EPS);
-}

+ 39 - 47
examples/camera/example.cpp

@@ -1,6 +1,6 @@
-#include "Camera.h"
 
 #include <igl/Viewport.h>
+#include <igl/Camera.h>
 #include <igl/matlab_format.h>
 #include <igl/report_gl_error.h>
 #include <igl/ReAntTweakBar.h>
@@ -49,12 +49,11 @@ int width,height;
 igl::ReTwBar rebar;
 struct State
 {
-  int viewing_camera;
-  std::vector<Camera> cameras;
+  std::vector<igl::Camera> cameras;
   std::vector<GLuint> tex_ids;
   std::vector<GLuint> fbo_ids;
   std::vector<GLuint> dfbo_ids;
-  State():viewing_camera(0),cameras(4),
+  State():cameras(4),
     tex_ids(cameras.size()),
     fbo_ids(cameras.size()),
     dfbo_ids(cameras.size())
@@ -63,7 +62,7 @@ struct State
 const Eigen::Vector4d back(1,1,1,1);
 std::stack<State> undo_stack;
 bool is_rotating = false;
-Camera down_camera;
+igl::Camera down_camera;
 int down_x,down_y;
 std::stack<State> redo_stack;
 Eigen::MatrixXd V,N;
@@ -97,11 +96,11 @@ void redo()
   }
 }
 
-void print(const Camera & camera)
+void print(const igl::Camera & camera)
 {
   using namespace std;
   cout<<
-    "rotation:    "<<camera.m_rotation.coeffs().transpose()<<endl<<
+    "rotation:    "<<camera.m_rotation_conj.conjugate().coeffs().transpose()<<endl<<
     "translation: "<<camera.m_translation.transpose()<<endl<<
     "eye:         "<<camera.eye().transpose()<<endl<<
     "at:          "<<camera.at().transpose()<<endl<<
@@ -209,7 +208,7 @@ void lights()
   glLightfv(GL_LIGHT0,GL_POSITION,pos.data());
 }
 
-void draw_scene(const Camera & v_camera,
+void draw_scene(const igl::Camera & v_camera,
   const bool render_to_texture,
   const GLuint & v_tex_id, 
   const GLuint & v_fbo_id, 
@@ -231,8 +230,32 @@ void draw_scene(const Camera & v_camera,
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glLoadIdentity();
-  //gluPerspective(v_camera.m_angle,v_camera.m_aspect,v_camera.m_near,v_camera.m_far);
+  if(v_camera.m_angle > Camera::MIN_ANGLE)
+  {
+    gluPerspective(v_camera.m_angle,v_camera.m_aspect,v_camera.m_near,v_camera.m_far);
+  }else
+  {
+    glOrtho(
+      -0.5*v_camera.m_aspect,
+      0.5*v_camera.m_aspect,
+      -0.5,
+      0.5,
+      v_camera.m_near,
+      v_camera.m_far);
+  }
+  //{
+  //  Matrix4d m;
+  //  glGetDoublev(GL_PROJECTION_MATRIX,m.data());
+  //  cout<<matlab_format(m,"glu")<<endl;
+  //}
+
+  glLoadIdentity();
   glMultMatrixd(v_camera.projection().data());
+  //{
+  //  Matrix4d m;
+  //  glGetDoublev(GL_PROJECTION_MATRIX,m.data());
+  //  cout<<matlab_format(m,"Camera")<<endl;
+  //}
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   //glLoadIdentity();
@@ -401,7 +424,7 @@ void display()
     }
   }
   {
-    auto & camera = s.cameras[s.viewing_camera];
+    auto & camera = s.cameras[0];
     draw_scene(camera,false,0,0,0);
   }
 
@@ -428,7 +451,7 @@ void mouse_wheel(int wheel, int direction, int mouse_x, int mouse_y)
     return;
   }
 
-  auto & camera = s.cameras[s.viewing_camera];
+  auto & camera = s.cameras[0];
   switch(center_type)
   {
     case CENTER_TYPE_ORBIT:
@@ -479,7 +502,7 @@ void mouse(int glutButton, int glutState, int mouse_x, int mouse_y)
             glutSetCursor(GLUT_CURSOR_CYCLE);
             // collect information for trackball
             is_rotating = true;
-            down_camera = s.cameras[s.viewing_camera];
+            down_camera = s.cameras[0];
             down_x = mouse_x;
             down_y = mouse_y;
           }
@@ -524,7 +547,7 @@ void mouse_drag(int mouse_x, int mouse_y)
   if(is_rotating)
   {
     glutSetCursor(GLUT_CURSOR_CYCLE);
-    auto & camera = s.cameras[s.viewing_camera];
+    auto & camera = s.cameras[0];
     Quaterniond q;
     switch(rotation_type)
     {
@@ -534,7 +557,7 @@ void mouse_drag(int mouse_x, int mouse_y)
         igl::trackball(
           width, height,
           2.0,
-          down_camera.m_rotation.conjugate(),
+          down_camera.m_rotation_conj,
           down_x, down_y, mouse_x, mouse_y,
           q);
           break;
@@ -545,7 +568,7 @@ void mouse_drag(int mouse_x, int mouse_y)
         two_axis_valuator_fixed_up(
           width, height,
           2.0,
-          down_camera.m_rotation.conjugate(),
+          down_camera.m_rotation_conj,
           down_x, down_y, mouse_x, mouse_y,
           q);
         break;
@@ -599,37 +622,6 @@ void key(unsigned char key, int mouse_x, int mouse_y)
   }
 }
 
-
-void TW_CALL set_rotation(const void * value, void * clientData)
-{
-  using namespace std;
-  using namespace Eigen;
-  const double * rt  = (const double*)(value);
-  Quaterniond conj;
-  copy(rt,rt+4,conj.coeffs().data());
-  auto & camera = s.cameras[s.viewing_camera];
-  switch(center_type)
-  {
-    default:
-    case CENTER_TYPE_ORBIT:
-      camera.orbit(conj.conjugate());
-      break;
-    case CENTER_TYPE_FPS:
-      camera.turn_eye(conj.conjugate());
-      break;
-  }
-}
-
-void TW_CALL get_rotation(void * value, void *clientData)
-{
-  using namespace std;
-  using namespace Eigen;
-  const auto & camera = s.cameras[s.viewing_camera];
-  double * rt  = (double*)(value);
-  Quaterniond conj = camera.m_rotation.conjugate();
-  copy(conj.coeffs().data(),conj.coeffs().data()+4,rt);
-}
-
 int main(int argc, char * argv[])
 {
   using namespace std;
@@ -664,9 +656,9 @@ int main(int argc, char * argv[])
   rebar.TwAddVarRW("rotation_type", RotationTypeTW,&rotation_type,
     "keyIncr=] keyDecr=[");
   TwType CenterTypeTW = ReTwDefineEnumFromString("CenterType","orbit,fps");
-  rebar.TwAddVarCB("rotation", TW_TYPE_QUAT4D,set_rotation,get_rotation,NULL,"");
   rebar.TwAddVarRW("center_type", CenterTypeTW,&center_type,
     "keyIncr={ keyDecr=}");
+  rebar.TwAddVarRW("rotation", TW_TYPE_QUAT4D,s.cameras[0].m_rotation_conj.coeffs().data(),"");
   rebar.load(REBAR_NAME);
   init_cameras();
 

+ 2 - 0
examples/flare-eyes/Makefile

@@ -37,6 +37,8 @@ LIB=$(OPENGL_LIB) $(GLUT_LIB) $(ANTTWEAKBAR_LIB) $(LIBIGL_LIB) $(MATLAB_LIB) $(C
 CPP_FILES=$(wildcard ./*.cpp)
 OBJ_FILES=$(addprefix obj/,$(notdir $(CPP_FILES:.cpp=.o))) 
 
+CFLAGS+=-g -std=c++11
+
 example: obj $(OBJ_FILES)
 	g++ $(OPENMP) $(AFLAGS) $(CFLAGS) -o example $(OBJ_FILES) $(LIB)
 

+ 104 - 122
examples/flare-eyes/example.cpp

@@ -1,6 +1,3 @@
-// Small GLUT application to test different scene rotation paradigms 
-//
-
 #include <igl/readOBJ.h>
 #include <igl/writeOBJ.h>
 #include <igl/writeOFF.h>
@@ -24,8 +21,10 @@
 #include <igl/PI.h>
 #include <igl/render_to_tga.h>
 #include <igl/STR.h>
-#define IGL_HEADER_ONLY
+#include <igl/two_axis_valuator_fixed_up.h>
+#include <igl/snap_to_fixed_up.h>
 #include <igl/lens_flare.h>
+#include <igl/get_seconds.h>
 
 #include <Eigen/Core>
 #include <Eigen/Geometry>
@@ -59,7 +58,7 @@ struct State
 } s;
 
 // See README for descriptions
-enum ROTATION_TYPE
+enum RotationType
 {
   ROTATION_TYPE_IGL_TRACKBALL = 0,
   ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP = 1,
@@ -81,27 +80,41 @@ Eigen::Vector4f light_pos(-0.1,-0.1,0.9,0);
 #define REBAR_NAME "temp.rbr"
 igl::ReTwBar rebar;
 
-void TW_CALL set_camera_rotation(const void * value, void *clientData)
+bool is_animating = false;
+double animation_start_time = 0;
+double ANIMATION_DURATION = 0.5;
+Eigen::Quaterniond animation_from_quat;
+Eigen::Quaterniond animation_to_quat;
+
+void push_undo()
 {
-  using namespace std;
-  // case current value to double
-  const double * quat = (const double *)(value);
-  std::copy(quat,quat+4,s.camera.rotation);
+  undo_stack.push(s);
+  // Clear
+  redo_stack = std::stack<State>();
 }
 
-void TW_CALL get_camera_rotation(void * value, void *clientData)
+void TW_CALL set_rotation_type(const void * value, void * clientData)
 {
+  using namespace Eigen;
   using namespace std;
-  // case current value to double
-  double * quat = (double *)(value);
-  std::copy(s.camera.rotation,s.camera.rotation+4,quat);
+  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 push_undo()
+void TW_CALL get_rotation_type(void * value, void *clientData)
 {
-  undo_stack.push(s);
-  // Clear
-  redo_stack = std::stack<State>();
+  RotationType * rt = (RotationType *)(value);
+  *rt = rotation_type;
 }
 
 void reshape(int width, int height)
@@ -111,52 +124,25 @@ void reshape(int width, int height)
   glViewport(0,0,width,height);
   // Send the new window size to AntTweakBar
   TwWindowSize(width, height);
+  s.camera.m_aspect = (double)width/(double)height;
 }
 
 void push_scene()
 {
   using namespace igl;
   using namespace std;
-  const double angle = s.camera.angle;
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glLoadIdentity();
-  double zNear = 1e-2;
-  double zFar = 100;
-  double aspect = ((double)width)/((double)height);
-  // Amount of scaling needed to "fix" perspective z-shift
-  double z_fix = 1.0;
-  // 5 is far enough to see unit "things" well
-  const double camera_z = 2;
-  // Test if should be using true orthographic projection
-  if(angle == 0)
-  {
-    glOrtho(
-      -0.5*camera_z*aspect,
-      0.5*camera_z*aspect,
-      -0.5*camera_z,
-      0.5*camera_z,
-      zNear,
-      zFar);
-  }else
-  {
-    // Make sure aspect is sane
-    aspect = aspect < 0.01 ? 0.01 : aspect;
-    gluPerspective(angle,aspect,zNear,zFar);
-    z_fix = 2.*tan(angle/2./360.*2.*M_PI);
-  }
-
+  auto & camera = s.camera;
+  glMultMatrixd(camera.projection().data());
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   glLoadIdentity();
-  gluLookAt(0,0,camera_z,0,0,0,0,1,0);
-  // Adjust scale to correct perspective
-  glScaled(z_fix,z_fix,z_fix);
-  // scale, pan
-  glScaled( s.camera.zoom, s.camera.zoom, s.camera.zoom);
-  double mat[4*4];
-  quat_to_mat(s.camera.rotation,mat);
-  glMultMatrixd(mat);
+  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()
@@ -236,7 +222,7 @@ void draw_eyes()
     LED_METHOD_COLORED_CIRCLE = 0,
     LED_METHOD_OUTLINED_CIRCLE = 1,
     LED_METHOD_TEXTURE_FLARE = 2
-  } method = LED_METHOD_COLORED_CIRCLE;
+  } method = LED_METHOD_TEXTURE_FLARE;
 
   
   for(int l = 0;l<NUM_LEDS;l++)
@@ -291,10 +277,24 @@ void draw_eyes()
 void display()
 {
   using namespace igl;
+  using namespace Eigen;
   using namespace std;
   glClearColor(0.03,0.03,0.04,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_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -363,37 +363,33 @@ void display()
 void mouse_wheel(int wheel, int direction, int mouse_x, int mouse_y)
 {
   using namespace std;
-  push_undo();
-  if(wheel == 0)
+  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;
-    // absolute scale difference when changing zooms (+1)
-    const double z_diff = 0.01;
-    GLint viewport[4];
-    glGetIntegerv(GL_VIEWPORT,viewport);
-    if(TwMouseMotion(mouse_x, viewport[3] - mouse_y))
-    {
-      TwMouseWheel(mouse_scroll_y);
-    }else
-    {
-      s.camera.zoom *= (1.0+double(direction)*z_diff);
-      const double min_zoom = 0.01;
-      const double max_zoom = 10.0;
-      s.camera.zoom = min(max_zoom,max(min_zoom,s.camera.zoom));
-    }
+    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
   {
-    if(!is_rotating)
-    {
-      // Change viewing angle (reshape will take care of adjust zoom)
-      const double a_diff = 1.0;
-      s.camera.angle += double(direction)*a_diff;
-      const double min_angle = 15.0;
-      s.camera.angle = 
-        min(90.0,max(min_angle,s.camera.angle));
-    }
+    // Dolly zoom:
+    camera.dolly_zoom((double)direction*1.0);
   }
 }
 
@@ -463,10 +459,13 @@ 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:
@@ -476,46 +475,29 @@ void mouse_drag(int mouse_x, int mouse_y)
           width,
           height,
           2.0,
-          down_camera.rotation,
+          down_camera.m_rotation_conj.coeffs().data(),
           down_x,
           down_y,
           mouse_x,
           mouse_y,
-          s.camera.rotation);
+          q.coeffs().data());
           break;
       }
       case ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP:
       {
-        Quaterniond down_q;
-        copy(down_camera.rotation,down_camera.rotation+4,down_q.coeffs().data());
-        Vector3d axis(0,1,0);
-        const double speed = 2.0;
-        Quaterniond q;
-        q = down_q * 
-          Quaterniond(
-            AngleAxisd(
-              M_PI*((double)(mouse_x-down_x))/(double)width*speed/2.0,
-              axis.normalized()));
-        q.normalize();
-        {
-          Vector3d axis(1,0,0);
-          const double speed = 2.0;
-          if(axis.norm() != 0)
-          {
-            q = 
-              Quaterniond(
-                AngleAxisd(
-                  M_PI*(mouse_y-down_y)/(double)width*speed/2.0,
-                  axis.normalized())) * q;
-            q.normalize();
-          }
-        }
-        copy(q.coeffs().data(),q.coeffs().data()+4,s.camera.rotation);
+        // 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());
   }
 }
 
@@ -565,6 +547,8 @@ void redo()
 void key(unsigned char key, int mouse_x, int mouse_y)
 {
   using namespace std;
+  using namespace igl;
+  using namespace Eigen;
   int mod = glutGetModifiers();
   switch(key)
   {
@@ -589,10 +573,9 @@ void key(unsigned char key, int mouse_x, int mouse_y)
       }else
       {
         push_undo();
-        igl::snap_to_canonical_view_quat<double>(
-          s.camera.rotation,
-          1.0,
-          s.camera.rotation);
+        Quaterniond q;
+        snap_to_canonical_view_quat(s.camera.m_rotation_conj,1.0,q);
+        s.camera.orbit(q.conjugate());
         break;
       }
     case ' ':
@@ -692,24 +675,23 @@ int main(int argc, char * argv[])
   }
   // Create a tweak bar
   rebar.TwNewBar("TweakBar");
-  rebar.TwAddVarCB("camera_rotation", TW_TYPE_QUAT4D, set_camera_rotation,get_camera_rotation, NULL, "open");
-  TwEnumVal RotationTypesEV[NUM_ROTATION_TYPES] = 
-  {
-    {ROTATION_TYPE_IGL_TRACKBALL,"igl trackball"},
-    {ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP,"two a... fixed up"},
-  };
-  TwType RotationTypeTW = 
-    ReTwDefineEnum(
-        "RotationType", 
-        RotationTypesEV, 
-        NUM_ROTATION_TYPES);
-  rebar.TwAddVarRW( "rotation_type", RotationTypeTW, &rotation_type,"keyIncr=] keyDecr=[");
+  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=[");
   rebar.TwAddVarRW( "x",TW_TYPE_DOUBLE, &x,"");
   rebar.TwAddVarRW( "y",TW_TYPE_DOUBLE, &y,"");
   rebar.TwAddVarRW( "z",TW_TYPE_DOUBLE, &z,"");
   rebar.TwAddVarRW( "eyes_visible",TW_TYPE_BOOLCPP, &eyes_visible,"key=e");
   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();
+
+
 
   // Init antweakbar
   glutInitDisplayString( "rgba depth double samples>=8 ");

+ 30 - 0
examples/multi-viewport/Makefile

@@ -0,0 +1,30 @@
+
+.PHONY: all
+
+# Shared flags etc.
+
+all: example 
+
+.PHONY:  example
+
+LIBIGL=/usr/local/igl/libigl/
+include $(LIBIGL)/Makefile.conf
+LIBIGL_INC=-I$(LIBIGL)/include
+LIBIGL_LIB=-L$(LIBIGL)/lib -ligl -liglembree
+
+EIGEN3_INC=-I/opt/local/include/eigen3 -I/opt/local/include/eigen3/unsupported
+CFLAGS+=-std=c++11 -g
+
+ANTTWEAKBAR_INC=-I$(LIBIGL)/external/AntTweakBar/include
+ANTTWEAKBAR_LIB=-L$(LIBIGL)/external/AntTweakBar/lib -lAntTweakBar -framework AppKit
+INC=$(LIBIGL_INC) $(ANTTWEAKBAR_INC) $(EIGEN3_INC)
+LIB=$(OPENGL_LIB) $(GLUT_LIB) $(ANTTWEAKBAR_LIB) $(LIBIGL_LIB)
+
+example: example.o
+	g++ $(OPENMP) $(AFLAGS) $(CFLAGS) -o example example.o $(LIB)
+
+example.o: example.cpp
+	g++ $(OPENMP) $(AFLAGS) $(CFLAGS) -c example.cpp -o example.o $(INC)
+clean:
+	rm -f example.o
+	rm -f example

+ 549 - 0
examples/multi-viewport/example.cpp

@@ -0,0 +1,549 @@
+#include <igl/OpenGL_convenience.h>
+#include <igl/per_face_normals.h>
+#include <igl/read.h>
+#include <igl/normalize_row_lengths.h>
+#include <igl/draw_mesh.h>
+#include <igl/unproject.h>
+#include <igl/quat_to_mat.h>
+#include <igl/trackball.h>
+#include <igl/report_gl_error.h>
+#include <igl/canonical_quaternions.h>
+#include <igl/snap_to_canonical_view_quat.h>
+#include <igl/unproject_to_zero_plane.h>
+#include <igl/Camera.h>
+#include <igl/STR.h>
+#include <igl/draw_beach_ball.h>
+#include <igl/Viewport.h>
+#include <igl/project.h>
+#include <igl/EPS.h>
+
+#ifdef __APPLE__
+#  include <OpenGL/gl.h>
+#  include <OpenGL/glu.h>
+#  include <GLUT/glut.h>
+#else
+#  include <GL/glut.h>
+#endif
+#include <Eigen/Core>
+
+#include <vector>
+#include <iostream>
+#include <string>
+#include <algorithm>
+#define IGL_HEADER_ONLY
+#include <igl/draw_floor.h>
+
+#define NUM_VIEWPORTS 4
+class AugViewport : public igl::Viewport
+{
+  public:
+    igl::Camera camera;
+} viewports[NUM_VIEWPORTS];
+double horiz = 0.5;
+double vert = 0.5;
+bool horiz_on = false, vert_on = false;
+
+// Width and height of window
+int width,height;
+// information at mouse down
+igl::Camera down_camera;
+bool trackball_on = false;
+int down_mouse_x,down_mouse_y,move_x,move_y;
+int down_vp;
+// Position of light
+float light_pos[4] = {0.1,0.1,-0.9,0};
+// Vertex positions, normals, colors and centroid
+Eigen::MatrixXd V,N,C,mean;
+// Bounding box diagonal length
+double bbd;
+// Faces
+Eigen::MatrixXi F;
+Eigen::Vector3d ball;
+
+
+void init_viewports()
+{
+  using namespace igl;
+  using namespace std;
+  for(auto & vp : viewports)
+  {
+    vp.camera.push_away(5.);
+  }
+  viewports[0].camera.dolly_zoom(0.-viewports[0].camera.m_angle);
+  viewports[1].camera.dolly_zoom(0.-viewports[1].camera.m_angle);
+  viewports[2].camera.dolly_zoom(0.-viewports[2].camera.m_angle);
+  viewports[3].camera.dolly_zoom(25.-viewports[3].camera.m_angle);
+  // Above view
+  double XZ_PLANE_QUAT_D_FLIP[4];
+  copy(XZ_PLANE_QUAT_D,XZ_PLANE_QUAT_D+4,XZ_PLANE_QUAT_D_FLIP);
+  XZ_PLANE_QUAT_D_FLIP[0] *= -1.0;
+  // Straight on
+  copy(
+    XZ_PLANE_QUAT_D_FLIP,
+    XZ_PLANE_QUAT_D_FLIP+4,
+    viewports[0].camera.m_rotation_conj.coeffs().data());
+  // Left side view
+  copy(
+    XY_PLANE_QUAT_D,
+    XY_PLANE_QUAT_D+4,
+    viewports[1].camera.m_rotation_conj.coeffs().data());
+  copy(
+    CANONICAL_VIEW_QUAT_D[14],
+    CANONICAL_VIEW_QUAT_D[14]+4,
+    viewports[2].camera.m_rotation_conj.coeffs().data());
+  // Straight on
+  copy(
+    XY_PLANE_QUAT_D,
+    XY_PLANE_QUAT_D+4,
+    viewports[3].camera.m_rotation_conj.coeffs().data());
+}
+
+const double BAR_THICKNESS = 3.0;
+void clamp(const double horiz, const double vert, double & eff_h, double & eff_v)
+{
+  eff_h = horiz;
+  eff_v = vert;
+  const double MIN_H = (BAR_THICKNESS/2.0)/(double)height;
+  const double MIN_V = (BAR_THICKNESS/2.0)/(double)width;
+  eff_h = eff_h < MIN_H ? MIN_H : eff_h;
+  eff_v = eff_v < MIN_V ? MIN_V : eff_v;
+  eff_h = eff_h > (1.0-MIN_H) ? (1.0-MIN_H) : eff_h;
+  eff_v = eff_v > (1.0-MIN_V) ? (1.0-MIN_V) : eff_v;
+}
+
+// Viewports are arranged like planar quadrants (CCW)
+// /-----.-----\
+// |  1  |  0  |
+// -------------
+// |  2  |  3  |
+// \-----.-----/
+void reshape_viewports()
+{
+  // Make horiz and vert sane
+  horiz = (horiz < 0 ? 0 : horiz);
+  vert = (vert < 0 ? 0 : vert);
+  horiz = (horiz > 1 ? 1 : horiz);
+  vert = (vert > 1 ? 1 : vert);
+  // Make effective horiz and vert even saner
+  double eff_h,eff_v;
+  clamp(horiz,vert,eff_h,eff_v);
+  viewports[0].reshape(eff_v*width,eff_h*height,(1.-eff_v)*width,(1.-eff_h)*height);
+  viewports[1].reshape(         0,eff_h*height,     eff_v*width,(1.-eff_h)*height);
+  viewports[2].reshape(         0,           0,     eff_v*width,eff_h*height);
+  viewports[3].reshape(eff_v*width,           0,(1.-eff_v)*width,eff_h*height);
+}
+
+void reshape(int width,int height)
+{
+  using namespace std;
+  // Save width and height
+  ::width = width;
+  ::height = height;
+  reshape_viewports();
+}
+
+// Set up double-sided lights
+void lights()
+{
+  using namespace std;
+  glEnable(GL_LIGHTING);
+  glLightModelf(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
+  glEnable(GL_LIGHT0);
+  glEnable(GL_LIGHT1);
+  float ones[4] = {1.0,1.0,1.0,1.0};
+  float zeros[4] = {0.0,0.0,0.0,0.0};
+  float pos[4];
+  copy(light_pos,light_pos+4,pos);
+  glLightfv(GL_LIGHT0,GL_AMBIENT,zeros);
+  glLightfv(GL_LIGHT0,GL_DIFFUSE,ones);
+  glLightfv(GL_LIGHT0,GL_SPECULAR,zeros);
+  glLightfv(GL_LIGHT0,GL_POSITION,pos);
+  pos[0] *= -1;
+  pos[1] *= -1;
+  pos[2] *= -1;
+  glLightfv(GL_LIGHT1,GL_AMBIENT,zeros);
+  glLightfv(GL_LIGHT1,GL_DIFFUSE,ones);
+  glLightfv(GL_LIGHT1,GL_SPECULAR,zeros);
+  glLightfv(GL_LIGHT1,GL_POSITION,pos);
+}
+
+// Set up projection and model view of scene
+void push_scene(const AugViewport & vp)
+{
+  using namespace igl;
+  using namespace std;
+  glMatrixMode(GL_PROJECTION);
+  glPushMatrix();
+  glLoadIdentity();
+  auto & camera = vp.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));
+}
+
+void pop_scene()
+{
+  glMatrixMode(GL_PROJECTION);
+  glPopMatrix();
+  glMatrixMode(GL_MODELVIEW);
+  glPopMatrix();
+}
+
+// Scale and shift for object
+void push_object()
+{
+  glPushMatrix();
+  glScaled(2./bbd,2./bbd,2./bbd);
+  glTranslated(-mean(0,0),-mean(0,1),-mean(0,2));
+}
+
+void pop_object()
+{
+  glPopMatrix();
+}
+
+const float back[4] = {190.0/255.0,190.0/255.0,190.0/255.0,0};
+void display()
+{
+  using namespace Eigen;
+  using namespace igl;
+  using namespace std;
+  glClearColor(back[0],back[1],back[2],0);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+  // All smooth points
+  glEnable( GL_POINT_SMOOTH );
+
+  // Flash lights
+  lights();
+  for(int vp = 0;vp<NUM_VIEWPORTS;vp++)
+  {
+    if(
+      viewports[vp].width <= 0 ||
+      viewports[vp].height <= 0)
+    {
+      continue;
+    }
+    glViewport(
+      viewports[vp].x,
+      viewports[vp].y,
+      viewports[vp].width,
+      viewports[vp].height);
+    push_scene(viewports[vp]);
+    glEnable(GL_DEPTH_TEST);
+    glDepthFunc(GL_LEQUAL);
+    glEnable(GL_NORMALIZE);
+    glEnable(GL_COLOR_MATERIAL);
+    glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
+    push_object();
+    // Draw the model
+    glEnable(GL_LIGHTING);
+    draw_mesh(V,F,N,C);
+    pop_object();
+
+    // Draw a nice floor
+    glPushMatrix();
+    glCullFace(GL_BACK);
+    glEnable(GL_CULL_FACE);
+    glEnable(GL_LIGHTING);
+    glTranslated(0,-1,0);
+    if(project(Vector3d(0,0,0))(2) - project(Vector3d(0,1,0))(2) > -FLOAT_EPS)
+    {
+      draw_floor_outline();
+    }
+    draw_floor();
+    glPopMatrix();
+    glDisable(GL_CULL_FACE);
+
+    // Draw a ball at the mouse
+    if(!horiz_on && !vert_on && !trackball_on)
+    {
+      glPushMatrix();
+      glTranslated(ball(0),ball(1),ball(2));
+      glScaled(0.1,0.1,0.1);
+      draw_beach_ball();
+      glPopMatrix();
+    }
+
+    pop_scene();
+  }
+
+  // Screen space
+  glDisable(GL_DEPTH_TEST);
+  glDisable(GL_LIGHTING);
+  glViewport(0,0,width,height);
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  gluOrtho2D(0,width,0,height);
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+
+  // Display mouse position at cursor
+  string str;
+  void * font = GLUT_BITMAP_HELVETICA_18;
+  glColor3f(1,0,0);
+  glRasterPos2d(move_x+18,height-move_y+18);
+  str = STR("("<<move_x<<","<<move_y<<")");
+  for_each(str.begin(),str.end(),bind1st(ptr_fun(&glutBitmapCharacter),font));
+  glColor3f(0,0,1);
+  glRasterPos2d(move_x+18,height-move_y-1.5*18);
+  for_each(str.begin(),str.end(),bind1st(ptr_fun(&glutBitmapCharacter),font));
+
+  double eff_h,eff_v;
+  clamp(horiz,vert,eff_h,eff_v);
+  glLineWidth(BAR_THICKNESS);
+  glColor3f(0.5,0.5,0.5);
+  glBegin(GL_LINES);
+  glVertex2f(0,eff_h*height);
+  glVertex2f(width,eff_h*height);
+  glVertex2f(eff_v*width,0);
+  glVertex2f(eff_v*width,height);
+  glEnd();
+
+  glLineWidth(BAR_THICKNESS/3.0);
+  glColor3f(0.8,0.8,0.8);
+  glBegin(GL_LINES);
+  glVertex2f(0,eff_h*height);
+  glVertex2f(width,eff_h*height);
+  glVertex2f(eff_v*width,0);
+  glVertex2f(eff_v*width,height);
+  glEnd();
+
+
+  report_gl_error();
+
+  glutSwapBuffers();
+  glutPostRedisplay();
+}
+
+// Initialize colors to a boring green
+void init_C()
+{
+  C.col(0).setConstant(0.4);
+  C.col(1).setConstant(0.8);
+  C.col(2).setConstant(0.3);
+}
+
+int in_viewport(const int x, const int y)
+{
+  int down_vp = -1;
+  for(int vp = 0;vp<NUM_VIEWPORTS;vp++)
+  {
+    if(
+      x >= viewports[vp].x && 
+      y >= viewports[vp].y && 
+      x <  viewports[vp].x+viewports[vp].width && 
+      y <  viewports[vp].y+viewports[vp].height)
+    {
+      down_vp = vp;
+      break;
+    }
+  }
+  return down_vp;
+}
+
+const double SNAP_DIST = 10;
+void mouse_move(int mouse_x, int mouse_y)
+{
+  using namespace std;
+  using namespace Eigen;
+  using namespace igl;
+  using namespace std;
+  move_x = mouse_x;
+  move_y = mouse_y;
+  const int in_vp = in_viewport(mouse_x,height-mouse_y);
+  if(in_vp >= 0)
+  {
+    if(
+      viewports[in_vp].width > 0 &&
+      viewports[in_vp].height > 0)
+    {
+      glViewport(
+        viewports[in_vp].x,
+        viewports[in_vp].y,
+        viewports[in_vp].width,
+        viewports[in_vp].height);
+      push_scene(viewports[in_vp]);
+      Vector3d screen_ball(mouse_x,height-mouse_y,0);
+      unproject_to_zero_plane(screen_ball,ball);
+      pop_scene();
+    }
+  }
+  if( (fabs((height-mouse_y) - horiz*height) < 2.*SNAP_DIST)
+   && (fabs(mouse_x - vert*width) < 2.*SNAP_DIST))
+  {
+    glutSetCursor(GLUT_CURSOR_TOP_LEFT_CORNER);
+  } else if( fabs((height-mouse_y) - horiz*height) < SNAP_DIST)
+  {
+    glutSetCursor(GLUT_CURSOR_UP_DOWN);
+  } else if( fabs(mouse_x - vert*width) < SNAP_DIST)
+  {
+    glutSetCursor(GLUT_CURSOR_LEFT_RIGHT);
+  } else
+  {
+    glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
+  }
+}
+
+void mouse(int glutButton, int glutState, int mouse_x, int mouse_y)
+{
+  using namespace std;
+  using namespace Eigen;
+  using namespace igl;
+  switch(glutState)
+  {
+    case 1:
+      // up
+      glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
+      trackball_on = false;
+      break;
+    case 0:
+      horiz_on = vert_on = false;
+      if( (fabs((height-mouse_y) - horiz*height) < 2.*SNAP_DIST)
+          && (fabs(mouse_x - vert*width) < 2.*SNAP_DIST))
+      {
+        glutSetCursor(GLUT_CURSOR_TOP_LEFT_CORNER);
+        horiz_on = vert_on = true;
+      } else if( fabs((height-mouse_y) - horiz*height) < SNAP_DIST)
+      {
+        glutSetCursor(GLUT_CURSOR_UP_DOWN);
+        horiz_on = true;
+      } else if( fabs(mouse_x - vert*width) < SNAP_DIST)
+      {
+        glutSetCursor(GLUT_CURSOR_LEFT_RIGHT);
+        vert_on = true;
+      } else
+      {
+        down_vp = in_viewport(mouse_x,height-mouse_y);
+        // down
+        if(down_vp >= 0)
+        {
+          glutSetCursor(GLUT_CURSOR_CYCLE);
+          // collect information for trackball
+          trackball_on = true;
+          down_camera = viewports[down_vp].camera;
+          down_mouse_x = mouse_x;
+          down_mouse_y = mouse_y;
+        }
+      }
+    break;
+  }
+}
+
+void mouse_drag(int mouse_x, int mouse_y)
+{
+  using namespace igl;
+  mouse_move(mouse_x,mouse_y);
+
+  if(horiz_on)
+  {
+    glutSetCursor(GLUT_CURSOR_UP_DOWN);
+    horiz = (double)(height-mouse_y)/(double)height;
+    reshape_viewports();
+  }
+  if(vert_on)
+  {
+    if(horiz_on)
+    {
+      glutSetCursor(GLUT_CURSOR_TOP_LEFT_CORNER);
+    }else
+    {
+      glutSetCursor(GLUT_CURSOR_LEFT_RIGHT);
+    }
+    vert = (double)mouse_x/(double)width;
+    reshape_viewports();
+  }
+  if(trackball_on)
+  {
+    glutSetCursor(GLUT_CURSOR_CYCLE);
+    // Rotate according to trackball
+    trackball<double>(
+      viewports[down_vp].width,
+      viewports[down_vp].height,
+      2.0,
+      down_camera.m_rotation_conj.coeffs().data(),
+      viewports[down_vp].mouse_x(down_mouse_x),
+      viewports[down_vp].mouse_y(down_mouse_y,height),
+      viewports[down_vp].mouse_x(mouse_x),
+      viewports[down_vp].mouse_y(mouse_y,height),
+      viewports[down_vp].camera.m_rotation_conj.coeffs().data());
+  }
+}
+
+
+void key(unsigned char key, int mouse_x, int mouse_y)
+{
+  using namespace std;
+  switch(key)
+  {
+    // Ctrl-c and esc exit
+    case char(3):
+    case char(27):
+      exit(0);
+    case 'Z':
+    {
+      const int in_vp = in_viewport(mouse_x,height-mouse_y);
+      if(in_vp >= 0)
+      {
+        igl::snap_to_canonical_view_quat(
+          viewports[in_vp].camera.m_rotation_conj.coeffs().data(),
+          1.0,
+          viewports[in_vp].camera.m_rotation_conj.coeffs().data());
+      }
+      break;
+    }
+    default:
+      cout<<"Unknown key command: "<<key<<" "<<int(key)<<endl;
+  }
+  
+}
+
+int main(int argc, char * argv[])
+{
+  using namespace Eigen;
+  using namespace igl;
+  using namespace std;
+
+  // init mesh
+  string filename = "/usr/local/igl/libigl/examples/shared/decimated-knight.obj" ;
+  if(argc > 1)
+  {
+    filename = argv[1];
+  }
+
+  if(!read(filename,V,F))
+  {
+    return 1;
+  }
+  // Compute normals, centroid, colors, bounding box diagonal
+  per_face_normals(V,F,N);
+  normalize_row_lengths(N,N);
+  mean = V.colwise().mean();
+  C.resize(F.rows(),3);
+  init_C();
+  bbd = 
+    (V.colwise().maxCoeff() -
+    V.colwise().minCoeff()).maxCoeff();
+
+
+  // Init viewports
+  init_viewports();
+
+  // Init glut
+  glutInit(&argc,argv);
+  glutInitDisplayString( "rgba depth double samples>=8 ");
+  glutInitWindowSize(glutGet(GLUT_SCREEN_WIDTH)/2.0,glutGet(GLUT_SCREEN_HEIGHT));
+  glutCreateWindow("multi-viewport");
+  glutDisplayFunc(display);
+  glutReshapeFunc(reshape);
+  glutKeyboardFunc(key);
+  glutMouseFunc(mouse);
+  glutMotionFunc(mouse_drag);
+  glutPassiveMotionFunc(mouse_move);
+  glutMainLoop();
+  return 0;
+}

+ 1 - 1
examples/patches/Makefile

@@ -8,7 +8,7 @@ all: obj example
 
 .PHONY: example
 
-CFLAGS=-g
+CFLAGS=-g -std=c++11
 
 LIBIGL=../../
 LIBIGL_INC=-I$(LIBIGL)/include

+ 106 - 141
examples/patches/example.cpp

@@ -1,6 +1,3 @@
-// Small GLUT application to test different scene rotation paradigms 
-//
-
 #include <igl/readOBJ.h>
 #include <igl/writeOBJ.h>
 #include <igl/writeOFF.h>
@@ -32,6 +29,7 @@
 #include <igl/unique_simplices.h>
 #include <igl/C_STR.h>
 #include <igl/write.h>
+#include <igl/two_axis_valuator_fixed_up.h>
 #include <igl/snap_to_fixed_up.h>
 
 #include <Eigen/Core>
@@ -80,6 +78,13 @@ enum RotationType
   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;
+
 enum OrientMethod
 {
   ORIENT_METHOD_OUTWARD = 0,
@@ -120,14 +125,6 @@ void push_undo()
   redo_stack = std::stack<State>();
 }
 
-void TW_CALL set_camera_rotation(const void * value, void *clientData)
-{
-  using namespace std;
-  // case current value to double
-  const double * quat = (const double *)(value);
-  std::copy(quat,quat+4,s.camera.rotation);
-}
-
 void TW_CALL set_orient_method(const void * value, void * clientData)
 {
   const OrientMethod old_orient_method = orient_method;
@@ -156,9 +153,8 @@ void TW_CALL set_rotation_type(const void * value, void * clientData)
     old_rotation_type != ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP)
   {
     push_undo();
-    copy(s.camera.rotation,s.camera.rotation+4,animation_from_quat.coeffs().data());
+    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;
@@ -170,14 +166,6 @@ void TW_CALL get_rotation_type(void * value, void *clientData)
   *rt = rotation_type;
 }
 
-void TW_CALL get_camera_rotation(void * value, void *clientData)
-{
-  using namespace std;
-  // case current value to double
-  double * quat = (double *)(value);
-  std::copy(s.camera.rotation,s.camera.rotation+4,quat);
-}
-
 void reshape(int width, int height)
 {
   ::width = width;
@@ -185,54 +173,26 @@ void reshape(int width, int height)
   glViewport(0,0,width,height);
   // Send the new window size to AntTweakBar
   TwWindowSize(width, height);
+  s.camera.m_aspect = (double)width/(double)height;
 }
 
 void push_scene()
 {
   using namespace igl;
   using namespace std;
-  const double angle = s.camera.angle;
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glLoadIdentity();
-  double zNear = 1e-2;
-  double zFar = 100;
-  double aspect = ((double)width)/((double)height);
-  // Amount of scaling needed to "fix" perspective z-shift
-  double z_fix = 1.0;
-  // 5 is far enough to see unit "things" well
-  const double camera_z = 2;
-  // Test if should be using true orthographic projection
-  if(angle == 0)
-  {
-    glOrtho(
-      -0.5*camera_z*aspect,
-      0.5*camera_z*aspect,
-      -0.5*camera_z,
-      0.5*camera_z,
-      zNear,
-      zFar);
-  }else
-  {
-    // Make sure aspect is sane
-    aspect = aspect < 0.01 ? 0.01 : aspect;
-    gluPerspective(angle,aspect,zNear,zFar);
-    z_fix = 2.*tan(angle/2./360.*2.*M_PI);
-  }
-
+  auto & camera = s.camera;
+  glMultMatrixd(camera.projection().data());
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   glLoadIdentity();
-  gluLookAt(0,0,camera_z,0,0,0,0,1,0);
-  // Adjust scale to correct perspective
-  glScaled(z_fix,z_fix,z_fix);
-  // scale, pan
-  glScaled( s.camera.zoom, s.camera.zoom, s.camera.zoom);
-  double mat[4*4];
-  quat_to_mat(s.camera.rotation,mat);
-  glMultMatrixd(mat);
+  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;
@@ -297,11 +257,18 @@ void display()
       t = 1;
       is_animating = false;
     }
-    Quaterniond q;
-    q.coeffs() = 
-      animation_to_quat.coeffs()*t + animation_from_quat.coeffs()*(1.-t);
-    q.normalize();
-    copy(q.coeffs().data(),q.coeffs().data()+4,s.camera.rotation);
+    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);
@@ -389,36 +356,43 @@ void display()
 void mouse_wheel(int wheel, int direction, int mouse_x, int mouse_y)
 {
   using namespace std;
-  if(wheel == 0)
+  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;
-    // absolute scale difference when changing zooms (+1)
-    const double z_diff = 0.01;
-    GLint viewport[4];
-    glGetIntegerv(GL_VIEWPORT,viewport);
-    if(TwMouseMotion(mouse_x, viewport[3] - mouse_y))
-    {
-      TwMouseWheel(mouse_scroll_y);
-    }else
-    {
-      s.camera.zoom *= (1.0+double(direction)*z_diff);
-      const double min_zoom = 0.01;
-      const double max_zoom = 10.0;
-      s.camera.zoom = min(max_zoom,max(min_zoom,s.camera.zoom));
-    }
-  }else
+    TwMouseWheel(mouse_scroll_y);
+    return;
+  }
+  push_undo();
+
+  auto & camera = s.camera;
+  switch(center_type)
   {
-    if(!is_rotating)
-    {
-      // Change viewing angle (reshape will take care of adjust zoom)
-      const double a_diff = 1.0;
-      s.camera.angle += double(direction)*a_diff;
-      const double min_angle = 15.0;
-      s.camera.angle = 
-        min(90.0,max(min_angle,s.camera.angle));
-    }
+    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;
   }
 }
 
@@ -492,6 +466,8 @@ void mouse_drag(int mouse_x, int mouse_y)
   if(is_rotating)
   {
     glutSetCursor(GLUT_CURSOR_CYCLE);
+    Quaterniond q;
+    auto & camera = s.camera;
     switch(rotation_type)
     {
       case ROTATION_TYPE_IGL_TRACKBALL:
@@ -501,46 +477,38 @@ void mouse_drag(int mouse_x, int mouse_y)
           width,
           height,
           2.0,
-          down_camera.rotation,
+          down_camera.m_rotation_conj.coeffs().data(),
           down_x,
           down_y,
           mouse_x,
           mouse_y,
-          s.camera.rotation);
+          q.coeffs().data());
           break;
       }
       case ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP:
       {
-        Quaterniond down_q;
-        copy(down_camera.rotation,down_camera.rotation+4,down_q.coeffs().data());
-        Vector3d axis(0,1,0);
-        const double speed = 2.0;
-        Quaterniond q;
-        q = down_q * 
-          Quaterniond(
-            AngleAxisd(
-              M_PI*((double)(mouse_x-down_x))/(double)width*speed/2.0,
-              axis.normalized()));
-        q.normalize();
-        {
-          Vector3d axis(1,0,0);
-          const double speed = 2.0;
-          if(axis.norm() != 0)
-          {
-            q = 
-              Quaterniond(
-                AngleAxisd(
-                  M_PI*(mouse_y-down_y)/(double)width*speed/2.0,
-                  axis.normalized())) * q;
-            q.normalize();
-          }
-        }
-        copy(q.coeffs().data(),q.coeffs().data()+4,s.camera.rotation);
+        // 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;
+    }
   }
 }
 
@@ -657,6 +625,8 @@ void TW_CALL saveCB(void * /*clientData*/)
 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)
   {
@@ -685,16 +655,23 @@ void key(unsigned char key, int mouse_x, int mouse_y)
         {
           undo();
         }
-        break;
       }else
       {
         push_undo();
-        igl::snap_to_canonical_view_quat<double>(
-          s.camera.rotation,
-          1.0,
-          s.camera.rotation);
-        break;
+        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;
@@ -810,29 +787,17 @@ int main(int argc, char * argv[])
   // Create a tweak bar
   rebar.TwNewBar("bar");
   TwDefine("bar label='Patches' size='200 550' text=light alpha='200' color='68 68 68'");
-  rebar.TwAddVarCB("camera_rotation", TW_TYPE_QUAT4D, set_camera_rotation,get_camera_rotation, NULL, "open readonly=true");
-  TwEnumVal RotationTypesEV[NUM_ROTATION_TYPES] = 
-  {
-    {ROTATION_TYPE_IGL_TRACKBALL,"igl trackball"},
-    {ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP,"two axis fixed up"}
-  };
-  TwType RotationTypeTW = 
-    ReTwDefineEnum(
-        "RotationType", 
-        RotationTypesEV, 
-        NUM_ROTATION_TYPES);
+  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=[");
-  TwEnumVal OrientMethodEV[NUM_ORIENT_METHODS] = 
-  {
-    {ORIENT_METHOD_OUTWARD,"outward"},
-    {ORIENT_METHOD_AO,"ambient occlusion"}
-  };
-  TwType OrientMethodTW = 
-    ReTwDefineEnum(
-        "OrientMethod", 
-        OrientMethodEV, 
-        NUM_ROTATION_TYPES);
+  TwType CenterTypeTW = ReTwDefineEnumFromString("CenterType","orbit,fps");
+  rebar.TwAddVarRW("center_type", CenterTypeTW,&center_type,
+    "keyIncr={ keyDecr=}");
+  TwType OrientMethodTW = ReTwDefineEnumFromString("OrientMethod",
+    "outward,ambient-occlusion");
   rebar.TwAddVarCB( "orient_method", OrientMethodTW,
     set_orient_method,get_orient_method,NULL,"keyIncr=< keyDecr=>");
 
@@ -850,7 +815,7 @@ int main(int argc, char * argv[])
 
 
   animation_from_quat = Quaterniond(1,0,0,0);
-  copy(s.camera.rotation,s.camera.rotation+4,animation_to_quat.coeffs().data());
+  s.camera.m_rotation_conj = animation_from_quat;
   animation_start_time = get_seconds();
 
   // Init antweakbar

+ 4 - 3
examples/patches/temp.rbr

@@ -1,6 +1,7 @@
+camera_rotation: TW_TYPE_QUAT4D -0.174598206033656 0.778057756210189 0.215373556063922 0.563698346457904
+center_type: CenterType orbit
 wireframe_visible: TW_TYPE_BOOLCPP 0
 fill_visible: TW_TYPE_BOOLCPP 1
-camera_rotation: TW_TYPE_QUAT4D 0.0458853 -0.838173 0.0713855 -0.538762
-rotation_type: RotationType two axis fixed up
-orient_method: OrientMethod ambient occlusion
+rotation_type: RotationType igl_trackball
+orient_method: OrientMethod ambient-occlusion
 

+ 10 - 0
examples/quicklook-mesh/README

@@ -24,6 +24,16 @@ Install Mesa3D using macports.
 
 Then re-install GLU à la http://www.alecjacobson.com/weblog/?p=2827
 
+= Testing =
+
+After installing you can test previews with:
+
+    /usr/bin/qlmanage -p ../shared/cheburashka.obj
+
+and thumbnails with:
+
+    /usr/bin/qlmanage -t ../shared/cheburashka.obj
+
 = Note about Mesa =
 
 If things look weird (too far away, blank, etc.) then maybe you should

+ 43 - 57
examples/quicklook-mesh/src/render_to_buffer.cpp

@@ -24,6 +24,7 @@ extern "C" {
 #include <igl/Camera.h>
 #include <igl/canonical_quaternions.h>
 #include <igl/quat_to_mat.h>
+#include <igl/Viewport.h>
 
 #include <Eigen/Core>
 #include <GL/glu.h>
@@ -39,36 +40,10 @@ static float background_color[4] = {0,0,0,1};
 
 // Small viewports struct for keeping track of size and camera info
 #define NUM_VIEWPORTS 6
-struct Viewport
+class AugViewport : public igl::Viewport
 {
-  int x,y,width,height;
-  igl::Camera camera;
-  Viewport():
-    x(0),y(0),width(0),height(0),camera(){};
-  Viewport(
-    const int x, 
-    const int y, 
-    const int width,
-    const int height, 
-    const igl::Camera & camera):
-    x(x),
-    y(y),
-    width(width),
-    height(height),
-    camera(camera)
-  {
-  };
-  void reshape(
-    const int x, 
-    const int y, 
-    const int width,
-    const int height)
-  {
-    this->x = x;
-    this->y = y;
-    this->width = width;
-    this->height = height;
-  };
+  public:
+    igl::Camera camera;
 } viewports[NUM_VIEWPORTS];
 
 // Red screen for errors
@@ -97,34 +72,42 @@ void init_viewports()
 {
   using namespace igl;
   using namespace std;
-  viewports[0].camera.angle = 10;
-  viewports[1].camera.angle = 10;
-  viewports[2].camera.angle = 10;
-  viewports[3].camera.angle = 10;
-  viewports[4].camera.angle = 10;
-  viewports[5].camera.angle = 10;
+  for(auto & vp : viewports)
+  {
+    vp.camera.push_away(3.);
+    vp.camera.dolly_zoom(10.-vp.camera.m_angle);
+  }
   // Above view
   double XZ_PLANE_QUAT_D_FLIP[4];
   copy(XZ_PLANE_QUAT_D,XZ_PLANE_QUAT_D+4,XZ_PLANE_QUAT_D_FLIP);
   XZ_PLANE_QUAT_D_FLIP[0] *= -1.0;
   // Straight on
-  copy(XY_PLANE_QUAT_D,XY_PLANE_QUAT_D+4,viewports[0].camera.rotation);
+  copy(
+    XY_PLANE_QUAT_D,
+    XY_PLANE_QUAT_D+4,
+    viewports[0].camera.m_rotation_conj.coeffs().data());
   // Left side view
   copy(
     CANONICAL_VIEW_QUAT_D[9],
     CANONICAL_VIEW_QUAT_D[9]+4,
-    viewports[1].camera.rotation);
+    viewports[1].camera.m_rotation_conj.coeffs().data());
   copy(
     CANONICAL_VIEW_QUAT_D[14],
     CANONICAL_VIEW_QUAT_D[14]+4,
-    viewports[2].camera.rotation);
+    viewports[2].camera.m_rotation_conj.coeffs().data());
   // Straight on
   copy(
     CANONICAL_VIEW_QUAT_D[4],
     CANONICAL_VIEW_QUAT_D[4]+4,
-    viewports[3].camera.rotation);
-  copy(XZ_PLANE_QUAT_D,XZ_PLANE_QUAT_D+4,viewports[4].camera.rotation);
-  copy(XZ_PLANE_QUAT_D_FLIP,XZ_PLANE_QUAT_D_FLIP+4,viewports[5].camera.rotation);
+    viewports[3].camera.m_rotation_conj.coeffs().data());
+  copy(
+    XZ_PLANE_QUAT_D,
+    XZ_PLANE_QUAT_D+4,
+    viewports[4].camera.m_rotation_conj.coeffs().data());
+  copy(
+    XZ_PLANE_QUAT_D_FLIP,
+    XZ_PLANE_QUAT_D_FLIP+4,
+    viewports[5].camera.m_rotation_conj.coeffs().data());
 }
 
 // Viewports are arranged to see all sides
@@ -146,6 +129,10 @@ void reshape_viewports()
   viewports[3].reshape(          0, 2./3.*height,1./3.*width,1./3.*height);
   viewports[4].reshape(          0, 1./3.*height,1./3.*width,1./3.*height);
   viewports[5].reshape(          0,            0,1./3.*width,1./3.*height);
+  for(auto & vp : viewports)
+  {
+    vp.camera.m_aspect = (double)vp.width/(double)vp.height;
+  }
 }
 
 void reshape(int width, int height)
@@ -184,34 +171,30 @@ void lights()
 }
 
 // Push scene based on viewport
-void push_scene(const Viewport & vp)
+void push_scene(const AugViewport & vp)
 {
   using namespace igl;
+  using namespace std;
   glMatrixMode(GL_PROJECTION);
+  glPushMatrix();
   glLoadIdentity();
-  const double angle = vp.camera.angle;
-  const double * rot = vp.camera.rotation;
-  gluPerspective(angle,(double)width/(double)height,1e-2,10);
-  const double z_fix = 2.*tan(angle/2./360.*2.*M_PI);
+  auto & camera = vp.camera;
+  glMultMatrixd(camera.projection().data());
   glMatrixMode(GL_MODELVIEW);
+  glPushMatrix();
   glLoadIdentity();
   // -1 because buffer y-coordinates are flipped
-  gluLookAt(0,0,2.3,0,0,0,0,-1,0);
-  glPushMatrix();
-  double mat[4*4];
-  quat_to_mat(rot,mat);
-  glMultMatrixd(mat);
-  glScaled(z_fix,z_fix,z_fix);
+  gluLookAt(
+    camera.eye()(0), camera.eye()(1), camera.eye()(2),
+    camera.at()(0), camera.at()(1), camera.at()(2),
+    camera.up()(0), -1*camera.up()(1), camera.up()(2));
 }
-
 // Scale and shift for object so that it fits current view
-void push_object(const Viewport & vp)
+void push_object(const AugViewport & vp)
 {
   using namespace Eigen;
   glPushMatrix();
-  const double * rot = vp.camera.rotation;
-  Quaterniond q(rot[3],rot[0],rot[1],rot[2]);
-  Matrix3d m = q.matrix();
+  Matrix3d m = vp.camera.m_rotation_conj.matrix();
   Vector3d eff_Vmax  = m*Vmax;
   Vector3d eff_Vmin  = m*Vmin;
   Vector3d eff_Vmean = m*Vmean;
@@ -237,6 +220,9 @@ void pop_object()
 
 void pop_scene()
 {
+  glMatrixMode(GL_PROJECTION);
+  glPopMatrix();
+  glMatrixMode(GL_MODELVIEW);
   glPopMatrix();
 }
 

+ 2 - 0
examples/scene-rotation/Makefile

@@ -35,6 +35,8 @@ LIB=$(OPENGL_LIB) $(GLUT_LIB) $(ANTTWEAKBAR_LIB) $(LIBIGL_LIB) $(MATLAB_LIB) $(C
 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)
 

+ 110 - 134
examples/scene-rotation/example.cpp

@@ -3,6 +3,7 @@
 
 #include "trackball.h"
 
+#include <igl/two_axis_valuator_fixed_up.h>
 #include <igl/readOBJ.h>
 #include <igl/writeOBJ.h>
 #include <igl/writeOFF.h>
@@ -20,6 +21,7 @@
 #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>
@@ -57,6 +59,13 @@ enum RotationType
   NUM_ROTATION_TYPES = 4,
 } 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;
 
@@ -99,17 +108,8 @@ void TW_CALL set_rotation_type(const void * value, void * clientData)
     old_rotation_type != ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP)
   {
     push_undo();
-    copy(s.camera.rotation,s.camera.rotation+4,animation_from_quat.coeffs().data());
-    const Vector3d up = animation_from_quat.matrix() * Vector3d(0,1,0);
-    Vector3d proj_up(0,up(1),up(2));
-    if(proj_up.norm() == 0)
-    {
-      proj_up = Vector3d(0,1,0);
-    }
-    proj_up.normalize();
-    Quaterniond dq;
-    dq = Quaterniond::FromTwoVectors(up,proj_up);
-    animation_to_quat = dq * animation_from_quat;
+    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;
@@ -121,14 +121,6 @@ void TW_CALL get_rotation_type(void * value, void *clientData)
   *rt = rotation_type;
 }
 
-void TW_CALL get_camera_rotation(void * value, void *clientData)
-{
-  using namespace std;
-  // case current value to double
-  double * quat = (double *)(value);
-  std::copy(s.camera.rotation,s.camera.rotation+4,quat);
-}
-
 void reshape(int width, int height)
 {
   ::width = width;
@@ -136,52 +128,25 @@ void reshape(int width, int height)
   glViewport(0,0,width,height);
   // Send the new window size to AntTweakBar
   TwWindowSize(width, height);
+  s.camera.m_aspect = (double)width/(double)height;
 }
 
 void push_scene()
 {
   using namespace igl;
   using namespace std;
-  const double angle = s.camera.angle;
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glLoadIdentity();
-  double zNear = 1e-2;
-  double zFar = 100;
-  double aspect = ((double)width)/((double)height);
-  // Amount of scaling needed to "fix" perspective z-shift
-  double z_fix = 1.0;
-  // 5 is far enough to see unit "things" well
-  const double camera_z = 2;
-  // Test if should be using true orthographic projection
-  if(angle == 0)
-  {
-    glOrtho(
-      -0.5*camera_z*aspect,
-      0.5*camera_z*aspect,
-      -0.5*camera_z,
-      0.5*camera_z,
-      zNear,
-      zFar);
-  }else
-  {
-    // Make sure aspect is sane
-    aspect = aspect < 0.01 ? 0.01 : aspect;
-    gluPerspective(angle,aspect,zNear,zFar);
-    z_fix = 2.*tan(angle/2./360.*2.*M_PI);
-  }
-
+  auto & camera = s.camera;
+  gluPerspective(camera.m_angle,camera.m_aspect,camera.m_near,camera.m_far);
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   glLoadIdentity();
-  gluLookAt(0,0,camera_z,0,0,0,0,1,0);
-  // Adjust scale to correct perspective
-  glScaled(z_fix,z_fix,z_fix);
-  // scale, pan
-  glScaled( s.camera.zoom, s.camera.zoom, s.camera.zoom);
-  double mat[4*4];
-  quat_to_mat(s.camera.rotation,mat);
-  glMultMatrixd(mat);
+  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()
@@ -247,11 +212,18 @@ void display()
       t = 1;
       is_animating = false;
     }
-    Quaterniond q;
-    q.coeffs() = 
-      animation_to_quat.coeffs()*t + animation_from_quat.coeffs()*(1.-t);
-    q.normalize();
-    copy(q.coeffs().data(),q.coeffs().data()+4,s.camera.rotation);
+    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);
@@ -297,37 +269,43 @@ void display()
 void mouse_wheel(int wheel, int direction, int mouse_x, int mouse_y)
 {
   using namespace std;
-  push_undo();
-  if(wheel == 0)
+  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;
-    // absolute scale difference when changing zooms (+1)
-    const double z_diff = 0.01;
-    GLint viewport[4];
-    glGetIntegerv(GL_VIEWPORT,viewport);
-    if(TwMouseMotion(mouse_x, viewport[3] - mouse_y))
-    {
-      TwMouseWheel(mouse_scroll_y);
-    }else
-    {
-      s.camera.zoom *= (1.0+double(direction)*z_diff);
-      const double min_zoom = 0.01;
-      const double max_zoom = 10.0;
-      s.camera.zoom = min(max_zoom,max(min_zoom,s.camera.zoom));
-    }
-  }else
+    TwMouseWheel(mouse_scroll_y);
+    return;
+  }
+  push_undo();
+
+  auto & camera = s.camera;
+  switch(center_type)
   {
-    if(!is_rotating)
-    {
-      // Change viewing angle (reshape will take care of adjust zoom)
-      const double a_diff = 1.0;
-      s.camera.angle += double(direction)*a_diff;
-      const double min_angle = 15.0;
-      s.camera.angle = 
-        min(90.0,max(min_angle,s.camera.angle));
-    }
+    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;
   }
 }
 
@@ -400,6 +378,8 @@ void mouse_drag(int mouse_x, int mouse_y)
   if(is_rotating)
   {
     glutSetCursor(GLUT_CURSOR_CYCLE);
+    Quaterniond q;
+    auto & camera = s.camera;
     switch(rotation_type)
     {
       case ROTATION_TYPE_IGL_TRACKBALL:
@@ -409,18 +389,21 @@ void mouse_drag(int mouse_x, int mouse_y)
           width,
           height,
           2.0,
-          down_camera.rotation,
+          down_camera.m_rotation_conj.coeffs().data(),
           down_x,
           down_y,
           mouse_x,
           mouse_y,
-          s.camera.rotation);
+          q.coeffs().data());
           break;
       }
       case ROTATION_TYPE_BELL_TRACKBALL:
       {
         float down_quaternion[4];
-        copy(down_camera.rotation,down_camera.rotation+4,down_quaternion);
+        copy(
+          down_camera.m_rotation_conj.coeffs().data(),
+          down_camera.m_rotation_conj.coeffs().data()+4,
+          down_quaternion);
         float new_quaternion[4];
         
         const float center_x = ((float)width)/2.0;
@@ -439,60 +422,49 @@ void mouse_drag(int mouse_x, int mouse_y)
         new_quaternion[2] = -new_quaternion[2];
         float float_quat[4];
         add_quats(down_quaternion,new_quaternion,float_quat);
-        copy(float_quat,float_quat+4,s.camera.rotation);
+        copy(float_quat,float_quat+4,q.coeffs().data());
         break;
       }
       case ROTATION_TYPE_TWO_AXIS_VALUATOR:
       {
-        Quaterniond down_q;
-        copy(down_camera.rotation,down_camera.rotation+4,down_q.coeffs().data());
+        Quaterniond down_q = camera.m_rotation_conj;
         Vector3d axis(mouse_y-down_y,mouse_x-down_x,0);
         const double speed = 2.0;
         if(axis.norm() != 0)
         {
-          Quaterniond q;
           q = 
             Quaterniond(
               AngleAxisd(
                 M_PI*axis.norm()/(double)width*speed/2.0,
                 axis.normalized())) * down_q;
           q.normalize();
-          copy(q.coeffs().data(),q.coeffs().data()+4,s.camera.rotation);
         }
         break;
       }
       case ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP:
       {
-        Quaterniond down_q;
-        copy(down_camera.rotation,down_camera.rotation+4,down_q.coeffs().data());
-        Vector3d axis(0,1,0);
-        const double speed = 2.0;
-        Quaterniond q;
-        q = down_q * 
-          Quaterniond(
-            AngleAxisd(
-              M_PI*((double)(mouse_x-down_x))/(double)width*speed/2.0,
-              axis.normalized()));
-        q.normalize();
-        {
-          Vector3d axis(1,0,0);
-          const double speed = 2.0;
-          if(axis.norm() != 0)
-          {
-            q = 
-              Quaterniond(
-                AngleAxisd(
-                  M_PI*(mouse_y-down_y)/(double)width*speed/2.0,
-                  axis.normalized())) * q;
-            q.normalize();
-          }
-        }
-        copy(q.coeffs().data(),q.coeffs().data()+4,s.camera.rotation);
+        // 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;
+    }
   }
 }
 
@@ -541,6 +513,8 @@ void redo()
 void key(unsigned char key, int mouse_x, int mouse_y)
 {
   using namespace std;
+  using namespace igl;
+  using namespace Eigen;
   GetKeys(keyStates);
   const bool command_down = IS_KEYDOWN(kVK_Command);
   const bool shift_down = IS_KEYDOWN(kVK_Shift);
@@ -567,10 +541,18 @@ void key(unsigned char key, int mouse_x, int mouse_y)
       }else
       {
         push_undo();
-        igl::snap_to_canonical_view_quat<double>(
-          s.camera.rotation,
-          1.0,
-          s.camera.rotation);
+        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;
       }
     default:
@@ -667,21 +649,15 @@ int main(int argc, char * argv[])
   }
   // Create a tweak bar
   rebar.TwNewBar("TweakBar");
-  rebar.TwAddVarCB("camera_rotation", TW_TYPE_QUAT4D, no_op,get_camera_rotation, NULL, "open readonly=true");
-  TwEnumVal RotationTypesEV[NUM_ROTATION_TYPES] = 
-  {
-    {ROTATION_TYPE_IGL_TRACKBALL,"igl trackball"},
-    {ROTATION_TYPE_BELL_TRACKBALL,"bell trackball"},
-    {ROTATION_TYPE_TWO_AXIS_VALUATOR,"two axis valuator"},
-    {ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP,"two a... fixed up"},
-  };
-  TwType RotationTypeTW = 
-    ReTwDefineEnum(
-        "RotationType", 
-        RotationTypesEV, 
-        NUM_ROTATION_TYPES);
+  rebar.TwAddVarRW("camera_rotation", TW_TYPE_QUAT4D, 
+    s.camera.m_rotation_conj.coeffs().data(), "open readonly=true");
+  TwType RotationTypeTW = ReTwDefineEnumFromString("RotationType",
+    "igl_trackball,bell_trackball,two-axis-valuator,two-a...-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.load(REBAR_NAME);
 
   // Init antweakbar

+ 2 - 1
examples/trackball/Makefile

@@ -12,7 +12,8 @@ igl_lib=../../
 
 CFLAGS=-g -Wall 
 #deps=-MMD -MF depends.txt
-inc=-I$(igl_lib)/include
+EIGEN3_INC=-I$(DEFAULT_PREFIX)/include/eigen3 -I$(DEFAULT_PREFIX)/include/eigen3/unsupported
+inc=-I$(igl_lib)/include $(EIGEN3_INC)
 lib=$(OPENGL_LIB) $(GLUT_LIB) -L$(igl_lib)/lib -ligl
 
 example: example.o

+ 31 - 0
examples/upright/Makefile

@@ -0,0 +1,31 @@
+
+.PHONY: all
+
+# Shared flags etc.
+
+all: upright
+
+.PHONY: upright
+
+LIBIGL=/usr/local/igl/libigl/
+include $(LIBIGL)/Makefile.conf
+LIBIGL_INC=-I$(LIBIGL)/include
+LIBIGL_LIB=-L$(LIBIGL)/lib -ligl
+
+EIGEN3_INC=-I/opt/local/include/eigen3 -I/opt/local/include/eigen3/unsupported
+
+CARBON_LIB=-framework Carbon
+
+ANTTWEAKBAR_INC=-I$(LIBIGL)/external/AntTweakBar/include
+ANTTWEAKBAR_LIB=-L$(LIBIGL)/external/AntTweakBar/lib -lAntTweakBar -framework AppKit
+INC=$(LIBIGL_INC) $(ANTTWEAKBAR_INC) $(EIGEN3_INC)
+LIB=$(OPENGL_LIB) $(GLUT_LIB) $(ANTTWEAKBAR_LIB) $(LIBIGL_LIB) $(CARBON_LIB)
+
+upright: upright.o
+	g++ $(OPENMP) $(AFLAGS) $(CFLAGS) -o upright upright.o $(LIB)
+
+upright.o: example.cpp
+	g++ $(OPENMP) $(AFLAGS) $(CFLAGS) -c example.cpp -o upright.o $(INC)
+clean:
+	rm -f upright.o
+	rm -f upright

+ 527 - 0
examples/upright/example.cpp

@@ -0,0 +1,527 @@
+// Small GLUT application to help quickly orient a model in an upright
+// position, scaled and shifted to fit the unit sphere.
+//
+// Demonstrates trackball mouse camera control and undo/redo stack with
+// immutable state object (i.e. not with reverse operations).
+//
+// Reads (V,F) from .obj,.off,.wrl files and saves (V,F) to .obj,.off. Any 
+// normals, texture coordinates, etc. in the input file will be ignored and
+// lost in the output file.
+//
+#include <igl/readOBJ.h>
+#include <igl/writeOBJ.h>
+#include <igl/writeOFF.h>
+#include <igl/readWRL.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/per_face_normals.h>
+#include <igl/material_colors.h>
+#include <igl/trackball.h>
+#include <igl/snap_to_canonical_view_quat.h>
+#include <igl/REDRUM.h>
+
+#include <Eigen/Core>
+#include <Eigen/Geometry>
+
+#include <GLUT/glut.h>
+
+#include <Carbon/Carbon.h>
+
+#include <string>
+#include <vector>
+#include <stack>
+#include <iostream>
+
+
+struct State
+{
+  Eigen::MatrixXd V,N;
+  Eigen::MatrixXi F;
+  Eigen::VectorXd Vmax,Vmin,Vmid;
+  double bbd;
+  Eigen::Quaterniond rot;
+} s;
+
+std::stack<State> undo_stack;
+std::stack<State> redo_stack;
+
+bool trackball_on = false;
+int down_x,down_y;
+Eigen::Quaterniond down_rot;
+
+int width,height;
+std::string out_filename;
+Eigen::Vector4f light_pos(-0.1,-0.1,0.9,0);
+
+void push_undo()
+{
+  undo_stack.push(s);
+  // Clear
+  redo_stack = std::stack<State>();
+}
+
+void reshape(int width, int height)
+{
+  ::width = width;
+  ::height = height;
+  glViewport(0,0,width,height);
+}
+
+void push_scene()
+{
+  using namespace igl;
+  using namespace std;
+  const double angle = 15;
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  double zNear = 1e-2;
+  double zFar = 100;
+  double aspect = ((double)width)/((double)height);
+  // Amount of scaling needed to "fix" perspective z-shift
+  double z_fix = 1.0;
+  // 5 is far enough to see unit "things" well
+  const double camera_z = 2;
+  // Test if should be using true orthographic projection
+  if(angle == 0)
+  {
+    glOrtho(
+      -0.5*camera_z*aspect,
+      0.5*camera_z*aspect,
+      -0.5*camera_z,
+      0.5*camera_z,
+      zNear,
+      zFar);
+  }else
+  {
+    // Make sure aspect is sane
+    aspect = aspect < 0.01 ? 0.01 : aspect;
+    gluPerspective(angle,aspect,zNear,zFar);
+    z_fix = 2.*tan(angle/2./360.*2.*M_PI);
+  }
+
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+  gluLookAt(0,0,camera_z,0,0,0,0,1,0);
+  // Adjust scale to correct perspective
+  glScaled(z_fix,z_fix,z_fix);
+  // scale, pan
+}
+
+void push_object()
+{
+  glPushMatrix();
+  glMultMatrixd(Eigen::Affine3d(s.rot).matrix().data());
+  glScaled(2./s.bbd,2./s.bbd,2./s.bbd);
+  glTranslated(-s.Vmid(0),-s.Vmid(1),-s.Vmid(2));
+}
+
+void pop_object()
+{
+  glPopMatrix();
+}
+
+void pop_scene()
+{
+  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;
+  glClearColor(1,1,1,0);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_NORMALIZE);
+  lights();
+  push_scene();
+  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);
+
+  
+  draw_mesh(s.V,s.F,s.N);
+
+  glDisable(GL_LIGHTING);
+  glPushMatrix();
+  glLineWidth(1.0);
+  glColor3f(0,0,0);
+  glTranslated( s.Vmid(0), s.Vmid(1), s.Vmid(2));
+  glScaled(
+    (s.Vmax(0)-s.Vmin(0)),
+    (s.Vmax(1)-s.Vmin(1)),
+    (s.Vmax(2)-s.Vmin(2)));
+  glutWireCube(1.0);
+  glPopMatrix();
+  pop_object();
+
+  glDisable(GL_LIGHTING);
+  glPushMatrix();
+  glLineWidth(2.0);
+  glColor3f(1,0,0);
+  //glTranslated(s.Vmid(0), s.Vmid(1), s.Vmid(2));
+  glScaled(
+    2*(s.Vmax(0)-s.Vmin(0))/s.bbd,
+    2*(s.Vmax(1)-s.Vmin(1))/s.bbd,
+    2*(s.Vmax(2)-s.Vmin(2))/s.bbd);
+  glutWireCube(1.0);
+  glPopMatrix();
+
+  //glPushMatrix();
+  //glRotated(90,1,0,0);
+  //glColor3f(0.5,0.5,0.5);
+  //glutWireSphere(1.0,20,10);
+  //glPopMatrix();
+
+  glEnable(GL_LIGHTING);
+  glPushMatrix();
+  glTranslated(0,-1,0);
+  draw_floor();
+  glPopMatrix();
+  pop_scene();
+
+  glutSwapBuffers();
+  glutPostRedisplay();
+}
+
+void mouse(int glutButton, int glutState, int mouse_x, int mouse_y)
+{
+  using namespace std;
+  using namespace Eigen;
+  using namespace igl;
+  switch(glutState)
+  {
+    case 1:
+      // up
+      glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
+      trackball_on = false;
+      break;
+    case 0:
+      push_undo();
+      glutSetCursor(GLUT_CURSOR_CYCLE);
+      // collect information for trackball
+      trackball_on = true;
+      down_rot = s.rot;
+      down_x = mouse_x;
+      down_y = mouse_y;
+    break;
+  }
+}
+
+void mouse_drag(int mouse_x, int mouse_y)
+{
+  using namespace igl;
+
+  if(trackball_on)
+  {
+    glutSetCursor(GLUT_CURSOR_CYCLE);
+    // Rotate according to trackball
+    trackball<double>(
+      width,
+      height,
+      2.0,
+      down_rot.coeffs().data(),
+      down_x,
+      down_y,
+      mouse_x,
+      mouse_y,
+      s.rot.coeffs().data());
+  }
+}
+
+// Bake rotation and scale into V
+void bake()
+{
+  s.V.col(0).array() -= s.Vmid(0);
+  s.V.col(1).array() -= s.Vmid(1);
+  s.V.col(2).array() -= s.Vmid(2);
+  s.V *= 2./s.bbd; 
+  s.V = (s.V * s.rot.matrix().transpose()).eval();
+}
+
+void init_relative()
+{
+  using namespace Eigen;
+  using namespace igl;
+  per_face_normals(s.V,s.F,s.N);
+  s.rot = Quaterniond(1,0,0,0);
+  s.Vmax = s.V.colwise().maxCoeff();
+  s.Vmin = s.V.colwise().minCoeff();
+  s.Vmid = 0.5*(s.Vmax + s.Vmin);
+  s.bbd = (s.Vmax-s.Vmin).norm();
+}
+
+
+bool save()
+{
+  using namespace std;
+  using namespace igl;
+  using namespace Eigen;
+  // dirname, basename, extension and filename
+  string d,b,ext,f;
+  pathinfo(out_filename,d,b,ext,f);
+  // Convert extension to lower case
+  transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
+  if(ext == "obj")
+  {
+    // Convert extension to lower case
+    if(!igl::writeOBJ(out_filename,s.V,s.F))
+    {
+      return false;
+    }
+  }else if(ext == "off")
+  {
+    // Convert extension to lower case
+    if(!igl::writeOFF(out_filename,s.V,s.F))
+    {
+      return false;
+    }
+  //}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);
+  //  }
+  }
+  cout<<GREENGIN("Current baked model written to "+out_filename+".")<<endl;
+  return true;
+}
+
+
+KeyMap keyStates ;
+bool IS_KEYDOWN( uint16_t vKey )
+{
+  uint8_t index = vKey / 32 ;
+  uint8_t shift = vKey % 32 ;
+  return keyStates[index].bigEndianValue & (1 << shift) ;
+}
+
+void undo()
+{
+  using namespace std;
+  if(!undo_stack.empty())
+  {
+    redo_stack.push(s);
+    s = undo_stack.top();
+    undo_stack.pop();
+  }
+}
+
+void redo()
+{
+  using namespace std;
+  if(!redo_stack.empty())
+  {
+    undo_stack.push(s);
+    s = redo_stack.top();
+    redo_stack.pop();
+  }
+}
+
+void key(unsigned char key, int mouse_x, int mouse_y)
+{
+  using namespace std;
+  GetKeys(keyStates);
+  const bool command_down = IS_KEYDOWN(kVK_Command);
+  const bool shift_down = IS_KEYDOWN(kVK_Shift);
+  switch(key)
+  {
+    // Ctrl-c and esc exit
+    case char(3):
+    case char(27):
+      exit(0);
+    case 'B':
+    case 'b':
+      push_undo();
+      bake();
+      init_relative();
+      break;
+    case 'I':
+    case 'i':
+      push_undo();
+      s.F = s.F.rowwise().reverse().eval();
+      break;
+    case 'R':
+    case 'r':
+      push_undo();
+      init_relative();
+      break;
+    case 'S':
+    case 's':
+      save();
+      break;
+    case 'z':
+    case 'Z':
+      if(command_down)
+      {
+        if(shift_down)
+        {
+          redo();
+        }else
+        {
+          undo();
+        }
+        break;
+      }else
+      {
+        push_undo();
+        igl::snap_to_canonical_view_quat<double>(
+          s.rot.coeffs().data(),
+          1.0,
+          s.rot.coeffs().data());
+        break;
+      }
+    default:
+      cout<<"Unknown key command: '"<<key<<"' ("<<int(key)<<")"<<endl;
+  }
+  
+}
+
+int main(int argc, char * argv[])
+{
+  using namespace std;
+  using namespace Eigen;
+  using namespace igl;
+  if(argc < 3)
+  {
+    cerr<<"Usage:"<<endl<<"    ./upright input.obj output.obj"<<endl;
+    return 1;
+  }
+
+  // print key commands
+  cout<<"[Click] and [drag]  Rotate model using trackball."<<endl;
+  cout<<"[Z,z]               Snap rotation to canonical view."<<endl;
+  cout<<"[B,b]               \"Bake\" current scale, shift, and rotation "
+    "into model."<<endl;
+  cout<<"[⌘ Z]               Undo."<<endl;
+  cout<<"[⇧ ⌘ Z]             Redo."<<endl;
+  cout<<"[R,r]               Reset rotation."<<endl;
+  cout<<"[S,s]               Save to output model path."<<endl;
+  cout<<"[I,i]               Flip orientation of faces (gold front, green "
+    "back)."<<endl;
+  cout<<"[^C,ESC]            Exit."<<endl;
+
+  // Read and prepare mesh
+  string filename = argv[1];
+  out_filename = argv[2];
+  // 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,s.V))
+    {
+      return 1;
+    }
+    triangulate(vF,s.F);
+  }
+
+  init_relative();
+
+  // Init glut
+  glutInit(&argc,argv);
+  glutInitDisplayString( "rgba depth double samples>=8 ");
+  glutInitWindowSize(glutGet(GLUT_SCREEN_WIDTH)/2.0,glutGet(GLUT_SCREEN_HEIGHT));
+  glutCreateWindow("upright");
+  glutDisplayFunc(display);
+  glutReshapeFunc(reshape);
+  glutKeyboardFunc(key);
+  glutMouseFunc(mouse);
+  glutMotionFunc(mouse_drag);
+  //glutPassiveMotionFunc(mouse_move);
+  glutMainLoop();
+
+  return 0;
+}

+ 0 - 37
include/igl/Camera.cpp

@@ -1,37 +0,0 @@
-// 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 "Camera.h"
-#include "canonical_quaternions.h"
-#include <algorithm>
-
-igl::Camera::Camera():
-  zoom(1.0),
-  angle(45)
-{
-  using namespace igl;
-  using namespace std;
-  // Defaults
-  // canonical (X,Y) view
-  copy(XY_PLANE_QUAT_D,XY_PLANE_QUAT_D+4,rotation);
-  pan[0] = 0.0;
-  pan[1] = 0.0;
-  pan[2] = 0.0;
-}
-
-igl::Camera::Camera(const Camera & that):
-  zoom(that.zoom),
-  angle(that.angle)
-{
-  pan[0] = that.pan[0];
-  pan[1] = that.pan[1];
-  pan[2] = that.pan[2];
-  for(int i = 0; i<4; i++)
-  {
-    rotation[i] = that.rotation[i];
-  }
-}

+ 340 - 28
include/igl/Camera.h

@@ -1,43 +1,355 @@
-// 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_CAMERA_H
 #define IGL_CAMERA_H
-#include "igl_inline.h"
+#include <Eigen/Geometry>
+#include <Eigen/Core>
 
-// Simple Camera class
 namespace igl
 {
+
+  // A simple camera class. The camera stores projection parameters (field of
+  // view angle, aspect ratio, near and far clips) as well as a rigid
+  // tranformation *of the camera as if it were also a scene object*. Thus, the
+  // **inverse** of this rigid transformation is the modelview transformation.
   class Camera
   {
-    // Public Fields
     public:
-      // Rotation as quaternion (0,0,0,1) is identity
-      double rotation[4];
-      // Translation of origin
-      double pan[3];
-      // Zoom scale
-      double zoom;
-      // Perspective angle
-      double angle;
-    // Public functions
+      // On windows you might need: -fno-delayed-template-parsing
+      static constexpr double MIN_ANGLE = 5.;
+      //  m_angle  Field of view angle in degrees {45}
+      //  m_aspect  Aspect ratio {1}
+      //  m_near  near clipping plane {1e-2}
+      //  m_far  far clipping plane {100}
+      //  m_at_dist  distance of looking at point {1}
+      //  m_rotation_conj  Conjugate of rotation part of rigid transformation of
+      //    camera {identity}. Note: we purposefully store the conjugate because
+      //    this is what TW_TYPE_QUAT4D is expecting.
+      //  m_translation  Translation part of rigid transformation of camera
+      //    {(0,0,1)}
+      double m_angle, m_aspect, m_near, m_far, m_at_dist;
+      Eigen::Quaterniond m_rotation_conj;
+      Eigen::Vector3d m_translation;
+    private:
+      // m_at_dist_min_angle  m_at_dist from last time m_angle set to <= MIN_ANGLE
+      double m_at_dist_min_angle;
+      double m_angle_min_angle;
+    //  // m_last_positive_m_angle
+    //  // m_last_positive_m_angle_m_at_dist
+    //  double m_last_positive_m_angle,m_last_positive_m_angle_m_at_dist;
     public:
-      Camera();
-      // Copy constructor
-      // 
+      inline Camera();
+      inline virtual ~Camera(){}
+      // Return projection matrix that takes relative camera coordinates and
+      // transforms it to viewport coordinates
+      //
+      // Note:
+      //
+      //     if(m_angle > 0)
+      //     {
+      //       gluPerspective(m_angle,m_aspect,m_near,m_at_dist+m_far);
+      //     }else
+      //     {
+      //       gluOrtho(-0.5*aspect,0.5*aspect,-0.5,0.5,m_at_dist+m_near,m_far);
+      //     }
+      //
+      // Is equivalent to
+      //
+      //     glMultMatrixd(projection().data());
+      //
+      inline Eigen::Matrix4d projection() const;
+      // Return an Affine transformation (rigid actually) that takes a world 3d coordinate and
+      // transforms it into the relative camera coordinates.
+      inline Eigen::Affine3d affine() const;
+      // Return an Affine transformation (rigid actually) that takes relative
+      // coordinates and tramsforms them into world 3d coordinates.
+      //
+      // Note:
+      //
+      //     gluLookAt(
+      //       eye()(0), eye()(1), eye()(2),
+      //       at()(0), at()(1), at()(2),
+      //       up()(0), up()(1), up()(2));
+      //
+      // Is equivalent to
+      //
+      //     glMultMatrixd(camera.affine().matrix().data());
+      //
+      // See also: affine, eye, at, up
+      inline Eigen::Affine3d inverse() const;
+      // Returns world coordinates position of center or "eye" of camera.
+      inline Eigen::Vector3d eye() const;
+      // Returns world coordinate position of a point "eye" is looking at.
+      inline Eigen::Vector3d at() const;
+      // Returns world coordinate unit vector of "up" vector
+      inline Eigen::Vector3d up() const;
+      // Return top right corner of unit plane in relative coordinates, that is
+      // (w/2,h/2,1)
+      inline Eigen::Vector3d unit_plane() const;
+      // Move dv in the relative coordinate frame of the camera (move the FPS)
+      //
+      // Inputs:
+      //   dv  (x,y,z) displacement vector
+      //
+      inline void dolly(const Eigen::Vector3d & dv);
+      // "Scale zoom": Move `eye`, but leave `at`
+      //
+      // Input:
+      //   s  amount to scale distance to at
+      inline void push_away(const double s);
+      // Aka "Hitchcock", "Vertigo", "Spielberg" or "Trombone" zoom:
+      // simultaneously dolly while changing angle so that `at` not only stays
+      // put in relative coordinates but also projected coordinates. That is
+      //
+      // Inputs:
+      //   da  change in angle in degrees
+      inline void dolly_zoom(const double da);
+      // Turn around eye so that rotation is now q
+      //
+      // Inputs:
+      //   q  new rotation as quaternion
+      inline void turn_eye(const Eigen::Quaterniond & q);
+      // Orbit around at so that rotation is now q
+      //
+      // Inputs:
+      //   q  new rotation as quaternion
+      inline void orbit(const Eigen::Quaterniond & q);
+      // Rotate and translate so that camera is situated at "eye" looking at "at"
+      // with "up" pointing up.
+      //
       // Inputs:
-      //   that  other Camera to be copied
-      Camera(const Camera & that);
-      ~Camera(){}
+      //   eye  (x,y,z) coordinates of eye position
+      //   at   (x,y,z) coordinates of at position
+      //   up   (x,y,z) coordinates of up vector
+      inline void look_at(
+        const Eigen::Vector3d & eye,
+        const Eigen::Vector3d & at,
+        const Eigen::Vector3d & up);
   };
-};
+}
+
+// Implementation
+#include "PI.h"
+#include "EPS.h"
+#include <cmath>
+#include <iostream>
+#include <cassert>
+
+inline igl::Camera::Camera():
+  m_angle(45.0),m_aspect(1),m_near(1e-2),m_far(100),m_at_dist(1),
+  m_rotation_conj(1,0,0,0),
+  m_translation(0,0,1),
+  m_at_dist_min_angle(m_at_dist),
+  m_angle_min_angle(m_angle)
+{
+}
+
+inline Eigen::Matrix4d igl::Camera::projection() const
+{
+  Eigen::Matrix4d P;
+  using namespace std;
+  using namespace igl;
+  // http://stackoverflow.com/a/3738696/148668
+  if(m_angle >= MIN_ANGLE)
+  {
+    const double yScale = tan(PI*0.5 - 0.5*m_angle*PI/180.);
+    // http://stackoverflow.com/a/14975139/148668
+    const double xScale = yScale/m_aspect;
+    const double far = m_at_dist + m_far;
+    const double near = m_near;
+    P<< 
+      xScale, 0, 0, 0,
+      0, yScale, 0, 0,
+      0, 0, -(far+near)/(far-near), -1,
+      0, 0, -2.*near*far/(far-near), 0;
+    P = P.transpose().eval();
+  }else
+  {
+    const double f = 0.5;
+    const double left = -f*m_aspect;
+    const double right = f*m_aspect;
+    const double bottom = -f;
+    const double top = f;
+    const double near = m_near;
+    const double far = m_at_dist + m_far;
+    const double tx = (right+left)/(right-left);
+    const double ty = (top+bottom)/(top-bottom);
+    const double tz = (far+near)/(far-near);
+    const double z_fix = 
+      0.5/(m_at_dist_min_angle * tan(m_angle_min_angle/2./180.*M_PI))+
+      (-m_at_dist+m_at_dist_min_angle)/m_at_dist_min_angle;
+    P<<
+      z_fix*2./(right-left), 0, 0, -tx,
+      0, z_fix*2./(top-bottom), 0, -ty,
+      0, 0, -z_fix*2./(far-near),  -tz,
+      0, 0, 0, 1;
+  }
+  return P;
+}
+
+inline Eigen::Affine3d igl::Camera::affine() const
+{
+  using namespace Eigen;
+  Affine3d t = Affine3d::Identity();
+  t.rotate(m_rotation_conj.conjugate());
+  t.translate(m_translation);
+  return t;
+}
+
+inline Eigen::Affine3d igl::Camera::inverse() const
+{
+  using namespace Eigen;
+  Affine3d t = Affine3d::Identity();
+  t.translate(-m_translation);
+  t.rotate(m_rotation_conj);
+  return t;
+}
+
+inline Eigen::Vector3d igl::Camera::eye() const
+{
+  using namespace Eigen;
+  return affine() * Vector3d(0,0,0);
+}
+
+inline Eigen::Vector3d igl::Camera::at() const
+{
+  using namespace Eigen;
+  return affine() * (Vector3d(0,0,-1)*m_at_dist);
+}
+
+inline Eigen::Vector3d igl::Camera::up() const
+{
+  using namespace Eigen;
+  Affine3d t = Affine3d::Identity();
+  t.rotate(m_rotation_conj.conjugate());
+  return t * Vector3d(0,1,0);
+}
+
+inline Eigen::Vector3d igl::Camera::unit_plane() const
+{
+  using namespace igl;
+  // Distance of center pixel to eye
+  const double d = 1.0;
+  const double a = m_aspect;
+  const double theta = m_angle*PI/180.;
+  const double w =
+    2.*sqrt(-d*d/(a*a*pow(tan(0.5*theta),2.)-1.))*a*tan(0.5*theta);
+  const double h = w/a;
+  return Eigen::Vector3d(w*0.5,h*0.5,-d);
+}
 
-#ifdef IGL_HEADER_ONLY
-#  include "Camera.cpp"
+inline void igl::Camera::dolly(const Eigen::Vector3d & dv)
+{
+  m_translation += dv;
+}
+
+inline void igl::Camera::push_away(const double s)
+{
+  using namespace Eigen;
+  using namespace igl;
+#ifndef NDEBUG
+  Vector3d old_at = at();
 #endif
+  const double old_at_dist = m_at_dist;
+  m_at_dist = old_at_dist * s;
+  dolly(Vector3d(0,0,1)*(m_at_dist - old_at_dist));
+  assert((old_at-at()).squaredNorm() < DOUBLE_EPS);
+}
+
+inline void igl::Camera::dolly_zoom(const double da)
+{
+  using namespace std;
+  using namespace igl;
+  using namespace Eigen;
+#ifndef NDEBUG
+  Vector3d old_at = at();
+#endif
+  const double old_angle = m_angle;
+  m_angle += da;
+  m_angle = min(89.,max(0.,m_angle));
+  const double eff_angle = (MIN_ANGLE > m_angle ? MIN_ANGLE : m_angle);
+  if(old_angle >= MIN_ANGLE)
+  {
+    // change in distance
+    const double s = 
+      (2.*tan(old_angle/2./180.*M_PI)) /
+      (2.*tan(eff_angle/2./180.*M_PI)) ;
+    const double old_at_dist = m_at_dist;
+    m_at_dist = old_at_dist * s;
+    dolly(Vector3d(0,0,1)*(m_at_dist - old_at_dist));
+    if(eff_angle == MIN_ANGLE)
+    {
+      m_at_dist_min_angle = m_at_dist;
+      m_angle_min_angle = eff_angle;
+    }
+    assert((old_at-at()).squaredNorm() < DOUBLE_EPS);
+  }else if(old_angle < MIN_ANGLE && m_angle >= MIN_ANGLE)
+  {
+    // Restore decent length
+    const double z_fix = 
+      // There should be some factor here based on the incoming angle
+      // (m_angle_min_angle) and outgoing angle (m_angle)... For now I set it
+      // to 1. (assumes equality)
+      //0.5/(m_at_dist_min_angle * tan(m_angle_min_angle/2./180.*M_PI))+
+      1.+(-m_at_dist+m_at_dist_min_angle)/m_at_dist_min_angle;
+    m_at_dist = m_at_dist_min_angle / z_fix;
+  }
+}
+
+inline void igl::Camera::turn_eye(const Eigen::Quaterniond & q)
+{
+  using namespace Eigen;
+  using namespace igl;
+  Vector3d old_eye = eye();
+  // eye should be fixed
+  //
+  // eye_1 = R_1 * t_1 = eye_0
+  // t_1 = R_1' * eye_0
+  m_rotation_conj = q.conjugate();
+  m_translation = m_rotation_conj * old_eye;
+  assert((old_eye - eye()).squaredNorm() < DOUBLE_EPS);
+}
+
+inline void igl::Camera::orbit(const Eigen::Quaterniond & q)
+{
+  using namespace Eigen;
+  using namespace igl;
+  Vector3d old_at = at();
+  // at should be fixed
+  //
+  // at_1 = R_1 * t_1 - R_1 * z = at_0
+  // t_1 = R_1' * (at_0 + R_1 * z)
+  m_rotation_conj = q.conjugate();
+  m_translation = 
+    m_rotation_conj * 
+      (old_at + 
+         m_rotation_conj.conjugate() * Vector3d(0,0,1) * m_at_dist);
+  assert((old_at - at()).squaredNorm() < DOUBLE_EPS);
+}
+
+inline void igl::Camera::look_at(
+  const Eigen::Vector3d & eye,
+  const Eigen::Vector3d & at,
+  const Eigen::Vector3d & up)
+{
+  using namespace Eigen;
+  using namespace std;
+  using namespace igl;
+  // http://www.opengl.org/sdk/docs/man2/xhtml/gluLookAt.xml
+  // Normalize vector from at to eye
+  Vector3d F = eye-at;
+  m_at_dist = F.norm();
+  F.normalize();
+  // Project up onto plane orthogonal to F and normalize
+  const Vector3d proj_up = (up-(up.dot(F))*F).normalized();
+  Quaterniond a,b;
+  a.setFromTwoVectors(Vector3d(0,0,-1),-F);
+  b.setFromTwoVectors(a*Vector3d(0,1,0),proj_up);
+  m_rotation_conj = (a*b).conjugate();
+  m_translation = m_rotation_conj * eye;
+  assert(           (eye-this->eye()).squaredNorm() < DOUBLE_EPS);
+  assert((F-(this->eye()-this->at()).normalized()).squaredNorm() < 
+    DOUBLE_EPS);
+  assert(           (at-this->at()).squaredNorm() < DOUBLE_EPS);
+  assert(        (proj_up-this->up()).squaredNorm() < DOUBLE_EPS);
+}
 
 #endif

+ 2 - 7
include/igl/Viewport.h

@@ -8,26 +8,21 @@
 #ifndef IGL_VIEWPORT_H
 #define IGL_VIEWPORT_H
 
-#include "Camera.h"
-
 namespace igl
 {
   struct Viewport
   {
     int x,y,width,height;
-    igl::Camera camera;
     // Constructors
     Viewport(
       const int x=0, 
       const int y=0, 
       const int width=0,
-      const int height=0, 
-      const igl::Camera & camera = igl::Camera()):
+      const int height=0):
       x(x),
       y(y),
       width(width),
-      height(height),
-      camera(camera)
+      height(height)
     {
     };
     virtual ~Viewport(){}

+ 4 - 6
include/igl/canonical_quaternions.h

@@ -13,9 +13,8 @@
 // such that q = x*i + y*j + z*k + w
 namespace igl
 {
-#  define SQRT_2_OVER_2 0.707106781f
-
   // Float versions
+#define SQRT_2_OVER_2 0.707106781f
   // Identity
   const float IDENTITY_QUAT_F[4] = {0,0,0,1};
   // The following match the Matlab canonical views
@@ -57,10 +56,9 @@ namespace igl
       {             0,-SQRT_2_OVER_2, SQRT_2_OVER_2,             0}, // 22
       {           0.5,          -0.5,           0.5,          -0.5}  // 23
     };
-#  undef SQRT_2_OVER_2
-
-#  define SQRT_2_OVER_2 0.707106781186548f
+#undef SQRT_2_OVER_2
   // Double versions
+#define SQRT_2_OVER_2 0.70710678118654757
   // Identity
   const double IDENTITY_QUAT_D[4] = {0,0,0,1};
   // The following match the Matlab canonical views
@@ -102,6 +100,7 @@ namespace igl
       {             0,-SQRT_2_OVER_2, SQRT_2_OVER_2,             0},
       {           0.5,          -0.5,           0.5,          -0.5}
     };
+#undef SQRT_2_OVER_2
 #define NUM_CANONICAL_VIEW_QUAT 24
 
   // NOTE: I want to rather be able to return a Q_type[][] but C++ is not
@@ -121,7 +120,6 @@ namespace igl
   template <> 
   IGL_INLINE double CANONICAL_VIEW_QUAT<double>(int i, int j);
 
-#  undef SQRT_2_OVER_2
 }
 
 #ifdef IGL_HEADER_ONLY

+ 1 - 0
include/igl/per_face_normals.h

@@ -8,6 +8,7 @@
 #ifndef IGL_PER_FACE_NORMALS_H
 #define IGL_PER_FACE_NORMALS_H
 #include "igl_inline.h"
+#include <utility>
 #include <Eigen/Core>
 namespace igl
 {

+ 9 - 0
include/igl/snap_to_canonical_view_quat.cpp

@@ -96,6 +96,15 @@ IGL_INLINE bool igl::snap_to_canonical_view_quat(
   return false;
 }
 
+IGL_INLINE bool igl::snap_to_canonical_view_quat(
+  const Eigen::Quaterniond & q,
+  const double threshold,
+  Eigen::Quaterniond & s)
+{
+  return snap_to_canonical_view_quat<double>( 
+    q.coeffs().data(),threshold,s.coeffs().data());
+}
+
 #ifndef IGL_HEADER_ONLY
 // Explicit template specialization
 // generated by autoexplicit.sh

+ 5 - 0
include/igl/snap_to_canonical_view_quat.h

@@ -8,6 +8,7 @@
 #ifndef IGL_SNAP_TO_CANONICAL_VIEW_QUAT_H
 #define IGL_SNAP_TO_CANONICAL_VIEW_QUAT_H
 #include "igl_inline.h"
+#include <Eigen/Geometry>
 // A Quaternion, q, is defined here as an arrays of four scalars (x,y,z,w),
 // such that q = x*i + y*j + z*k + w
 namespace igl
@@ -21,6 +22,10 @@ namespace igl
     const Q_type q[4],
     const Q_type threshold,
     Q_type s[4]);
+  IGL_INLINE bool snap_to_canonical_view_quat(
+    const Eigen::Quaterniond & q,
+    const double threshold,
+    Eigen::Quaterniond & s);
 }
 
 #ifdef IGL_HEADER_ONLY

+ 1 - 0
include/igl/tt.cpp

@@ -100,5 +100,6 @@ IGL_INLINE void igl::tt(const Eigen::PlainObjectBase<Scalar>& V,
 
 #ifndef IGL_HEADER_ONLY
 // Explicit template specialization
+template void igl::tt<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&);
 // generated by autoexplicit.sh
 #endif