various stuff in opengl demos such as a better model,
stable trackball for the fly navigation mode, and started
to put some GUI elements...
diff --git a/demos/opengl/CMakeLists.txt b/demos/opengl/CMakeLists.txt
index 06b4945..968ed6c 100644
--- a/demos/opengl/CMakeLists.txt
+++ b/demos/opengl/CMakeLists.txt
@@ -9,7 +9,7 @@
 
 include_directories( ${QT_INCLUDE_DIR} )
 
-set(quaternion_demo_SRCS  gpuhelper.cpp camera.cpp trackball.cpp quaternion_demo.cpp)
+set(quaternion_demo_SRCS  gpuhelper.cpp icosphere.cpp camera.cpp trackball.cpp quaternion_demo.cpp)
 
 qt4_automoc(${quaternion_demo_SRCS})
 
diff --git a/demos/opengl/camera.cpp b/demos/opengl/camera.cpp
index 26118e6..f704771 100644
--- a/demos/opengl/camera.cpp
+++ b/demos/opengl/camera.cpp
@@ -42,8 +42,7 @@
     mVpX = 0;
     mVpY = 0;
 
-    setPosition(Vector3f::Constant(50.));
-
+    setPosition(Vector3f::Constant(100.));
     setTarget(Vector3f::Zero());
 }
 
@@ -179,6 +178,14 @@
     mViewIsUptodate = true;
 }
 
+void Camera::localRotate(const Quaternionf& q)
+{
+    float dist = (position() - mTarget).norm();
+    setOrientation(orientation() * q);
+    mTarget = position() + dist * direction();
+    mViewIsUptodate = false;
+}
+
 void Camera::zoom(float d)
 {
     float dist = (position() - mTarget).norm();
diff --git a/demos/opengl/camera.h b/demos/opengl/camera.h
index 46c709a..811b2c8 100644
--- a/demos/opengl/camera.h
+++ b/demos/opengl/camera.h
@@ -91,6 +91,7 @@
     const Eigen::Matrix4f& projectionMatrix(void) const;
     
     void rotateAroundTarget(const Eigen::Quaternionf& q);
+    void localRotate(const Eigen::Quaternionf& q);
     void zoom(float d);
     
     void localTranslate(const Eigen::Vector3f& t);
diff --git a/demos/opengl/gpuhelper.cpp b/demos/opengl/gpuhelper.cpp
index 7134873..921c95f 100644
--- a/demos/opengl/gpuhelper.cpp
+++ b/demos/opengl/gpuhelper.cpp
@@ -23,6 +23,7 @@
 // Eigen. If not, see <http://www.gnu.org/licenses/>.
 
 #include "gpuhelper.h"
+#include "icosphere.h"
 #include <GL/glu.h>
 // PLEASE don't look at this old code... ;)
 
@@ -31,26 +32,6 @@
 
 GpuHelper gpu;
 
-//--------------------------------------------------------------------------------
-// icosahedron
-//--------------------------------------------------------------------------------
-#define X .525731112119133606
-#define Z .850650808352039932
-
-static GLfloat vdata[12][3] = {
-   {-X, 0.0, Z}, {X, 0.0, Z}, {-X, 0.0, -Z}, {X, 0.0, -Z},
-   {0.0, Z, X}, {0.0, Z, -X}, {0.0, -Z, X}, {0.0, -Z, -X},
-   {Z, X, 0.0}, {-Z, X, 0.0}, {Z, -X, 0.0}, {-Z, -X, 0.0}
-};
-
-static GLint tindices[20][3] = {
-   {0,4,1}, {0,9,4}, {9,5,4}, {4,5,8}, {4,8,1},
-   {8,10,1}, {8,3,10}, {5,3,8}, {5,2,3}, {2,7,3},
-   {7,10,3}, {7,6,10}, {7,11,6}, {11,0,6}, {0,1,6},
-   {6,1,10}, {9,0,11}, {9,11,2}, {9,2,5}, {7,2,11} };
-//--------------------------------------------------------------------------------
-
-
 GpuHelper::GpuHelper()
 {
     mVpWidth = mVpHeight = 0;
@@ -151,61 +132,10 @@
     glEnd();
 }
 
-void _normalize(float* v)
+void GpuHelper::drawUnitSphere(int level)
 {
-    float s = 1.f/ei_sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
-    for (uint k=0; k<3; ++k)
-        v[k] *= s;
-}
-
-void _subdivide(float *v1, float *v2, float *v3, long depth)
-{
-    GLfloat v12[3], v23[3], v31[3];
-    GLint i;
-
-    if (depth == 0) {
-        //drawtriangle(v1, v2, v3);
-        glNormal3fv(v1);
-        glVertex3fv(v1);
-
-        glNormal3fv(v3);
-        glVertex3fv(v3);
-
-        glNormal3fv(v2);
-        glVertex3fv(v2);
-
-        return;
-    }
-    for (i = 0; i < 3; i++) {
-         v12[i] = v1[i]+v2[i];
-         v23[i] = v2[i]+v3[i];
-         v31[i] = v3[i]+v1[i];
-    }
-    _normalize(v12);
-    _normalize(v23);
-    _normalize(v31);
-    _subdivide(v1, v12, v31, depth-1);
-    _subdivide(v2, v23, v12, depth-1);
-    _subdivide(v3, v31, v23, depth-1);
-    _subdivide(v12, v23, v31, depth-1);
-}
-
-void GpuHelper::drawUnitLightSphere(int level)
-{
-  static int dlId = 0;
-  if (!dlId)
-  {
-    dlId = glGenLists(1);
-    glNewList(dlId, GL_COMPILE);
-    glBegin(GL_TRIANGLES);
-    for (int i = 0; i < 20; i++)
-    {
-      _subdivide(&vdata[tindices[i][0]][0], &vdata[tindices[i][1]][0], &vdata[tindices[i][2]][0], 1);
-    }
-    glEnd();
-    glEndList();
-  }
-  glCallList(dlId);
+  static IcoSphere sphere;
+  sphere.draw(level);
 }
 
 
diff --git a/demos/opengl/gpuhelper.h b/demos/opengl/gpuhelper.h
index fd07d53..4450eb5 100644
--- a/demos/opengl/gpuhelper.h
+++ b/demos/opengl/gpuhelper.h
@@ -100,7 +100,7 @@
     void drawVector(const Vector3f& position, const Vector3f& vec, const Color& color, float aspect = 50.);
     void drawVectorBox(const Vector3f& position, const Vector3f& vec, const Color& color, float aspect = 50.);
     void drawUnitCube(void);
-    void drawUnitLightSphere(int level=0);
+    void drawUnitSphere(int level=0);
 
     /// draw the \a nofElement first elements
     inline void draw(GLenum mode, uint nofElement);
diff --git a/demos/opengl/icosphere.cpp b/demos/opengl/icosphere.cpp
new file mode 100644
index 0000000..5f78231
--- /dev/null
+++ b/demos/opengl/icosphere.cpp
@@ -0,0 +1,119 @@
+// This file is part of Eigen, a lightweight C++ template library
+// for linear algebra. Eigen itself is part of the KDE project.
+//
+// Copyright (C) 2008 Gael Guennebaud <g.gael@free.fr>
+//
+// Eigen is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 3 of the License, or (at your option) any later version.
+//
+// Alternatively, you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// Eigen is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License or the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License and a copy of the GNU General Public License along with
+// Eigen. If not, see <http://www.gnu.org/licenses/>.
+
+#include "icosphere.h"
+
+#include <GL/gl.h>
+
+using namespace Eigen;
+
+//--------------------------------------------------------------------------------
+// icosahedron data
+//--------------------------------------------------------------------------------
+#define X .525731112119133606
+#define Z .850650808352039932
+
+static GLfloat vdata[12][3] = {
+   {-X, 0.0, Z}, {X, 0.0, Z}, {-X, 0.0, -Z}, {X, 0.0, -Z},
+   {0.0, Z, X}, {0.0, Z, -X}, {0.0, -Z, X}, {0.0, -Z, -X},
+   {Z, X, 0.0}, {-Z, X, 0.0}, {Z, -X, 0.0}, {-Z, -X, 0.0}
+};
+
+static GLint tindices[20][3] = {
+   {0,4,1}, {0,9,4}, {9,5,4}, {4,5,8}, {4,8,1},
+   {8,10,1}, {8,3,10}, {5,3,8}, {5,2,3}, {2,7,3},
+   {7,10,3}, {7,6,10}, {7,11,6}, {11,0,6}, {0,1,6},
+   {6,1,10}, {9,0,11}, {9,11,2}, {9,2,5}, {7,2,11} };
+//--------------------------------------------------------------------------------
+
+IcoSphere::IcoSphere(unsigned int levels)
+{
+  // init with an icosahedron
+  for (int i = 0; i < 12; i++)
+    mVertices.push_back(Map<Vector3f>(vdata[i]));
+  mIndices.push_back(new std::vector<int>);
+  std::vector<int>& indices = *mIndices.back();
+  for (int i = 0; i < 20; i++)
+  {
+    for (int k = 0; k < 3; k++)
+      indices.push_back(tindices[i][k]);
+  }
+  mListIds.push_back(0);
+
+  while(mIndices.size()<levels)
+    _subdivide();
+}
+
+const std::vector<int>& IcoSphere::indices(int level) const
+{
+  while (level>=int(mIndices.size()))
+    const_cast<IcoSphere*>(this)->_subdivide();
+  return *mIndices[level];
+}
+
+void IcoSphere::_subdivide(void)
+{
+  const std::vector<int>& indices = *mIndices.back();
+  mIndices.push_back(new std::vector<int>);
+  std::vector<int>& refinedIndices = *mIndices.back();
+  int end = indices.size();
+  for (int i=0; i<end; i+=3)
+  {
+    int i0, i1, i2;
+    Vector3f v0 = mVertices[i0=indices[i+0]];
+    Vector3f v1 = mVertices[i1=indices[i+1]];
+    Vector3f v2 = mVertices[i2=indices[i+2]];
+    int start = mVertices.size();
+    mVertices.push_back( (v0+v1).normalized() );
+    mVertices.push_back( (v1+v2).normalized() );
+    mVertices.push_back( (v2+v0).normalized() );
+    refinedIndices.push_back(i0); refinedIndices.push_back(start+0); refinedIndices.push_back(start+2);
+    refinedIndices.push_back(i1); refinedIndices.push_back(start+1); refinedIndices.push_back(start+0);
+    refinedIndices.push_back(i2); refinedIndices.push_back(start+2); refinedIndices.push_back(start+1);
+    refinedIndices.push_back(start+0); refinedIndices.push_back(start+1); refinedIndices.push_back(start+2);
+  }
+  mListIds.push_back(0);
+}
+
+void IcoSphere::draw(int level)
+{
+  while (level>=int(mIndices.size()))
+    const_cast<IcoSphere*>(this)->_subdivide();
+  if (mListIds[level]==0)
+  {
+    mListIds[level] = glGenLists(1);
+    glNewList(mListIds[level], GL_COMPILE);
+      glVertexPointer(3, GL_FLOAT, 0, mVertices[0].data());
+      glNormalPointer(GL_FLOAT, 0, mVertices[0].data());
+      glEnableClientState(GL_VERTEX_ARRAY);
+      glEnableClientState(GL_NORMAL_ARRAY);
+      glDrawElements(GL_TRIANGLES, mIndices[level]->size(), GL_UNSIGNED_INT, &(mIndices[level]->at(0)));
+      glDisableClientState(GL_VERTEX_ARRAY);
+      glDisableClientState(GL_NORMAL_ARRAY);
+    glEndList();
+  }
+  glCallList(mListIds[level]);
+}
+
+
diff --git a/demos/opengl/icosphere.h b/demos/opengl/icosphere.h
new file mode 100644
index 0000000..e5fa392
--- /dev/null
+++ b/demos/opengl/icosphere.h
@@ -0,0 +1,45 @@
+// This file is part of Eigen, a lightweight C++ template library
+// for linear algebra. Eigen itself is part of the KDE project.
+//
+// Copyright (C) 2008 Gael Guennebaud <g.gael@free.fr>
+//
+// Eigen is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 3 of the License, or (at your option) any later version.
+//
+// Alternatively, you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// Eigen is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License or the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License and a copy of the GNU General Public License along with
+// Eigen. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef EIGEN_ICOSPHERE_H
+#define EIGEN_ICOSPHERE_H
+
+#include <Eigen/Core>
+#include <vector>
+
+class IcoSphere
+{
+  public:
+    IcoSphere(unsigned int levels=1);
+    const std::vector<Eigen::Vector3f>& vertices() const { return mVertices; }
+    const std::vector<int>& indices(int level) const;
+    void draw(int level);
+  protected:
+    void _subdivide();
+    std::vector<Eigen::Vector3f> mVertices;
+    std::vector<std::vector<int>*> mIndices;
+    std::vector<int> mListIds;
+};
+
+#endif // EIGEN_ICOSPHERE_H
diff --git a/demos/opengl/quaternion_demo.cpp b/demos/opengl/quaternion_demo.cpp
index aef6083..f4bea26 100644
--- a/demos/opengl/quaternion_demo.cpp
+++ b/demos/opengl/quaternion_demo.cpp
@@ -23,6 +23,7 @@
 // Eigen. If not, see <http://www.gnu.org/licenses/>.
 
 #include "quaternion_demo.h"
+#include "icosphere.h"
 
 #include <Eigen/Array>
 #include <Eigen/QR>
@@ -31,6 +32,11 @@
 #include <QEvent>
 #include <QMouseEvent>
 #include <QInputDialog>
+#include <QGridLayout>
+#include <QButtonGroup>
+#include <QRadioButton>
+#include <QDockWidget>
+#include <QPushButton>
 
 using namespace Eigen;
 
@@ -57,21 +63,27 @@
                Quaternionf(::lerp(alpha,OrientationType(a.orientation),OrientationType(b.orientation))));
 }
 
-QuaternionDemo::QuaternionDemo()
+RenderingWidget::RenderingWidget()
 {
   mAnimate = false;
-  mTrackMode = TM_NO_TRACK;
+  mCurrentTrackingMode = TM_NO_TRACK;
+  mNavMode = NavTurnAround;
+  mLerpMode = LerpQuaternion;
+  mRotationMode = RotationStable;
   mTrackball.setCamera(&mCamera);
+
+  // required to capture key press events
+  setFocusPolicy(Qt::ClickFocus);
 }
 
-void QuaternionDemo::grabFrame(void)
+void RenderingWidget::grabFrame(void)
 {
     // ask user for a time
     bool ok = false;
     double t = 0;
     if (!m_timeline.empty())
       t = (--m_timeline.end())->first + 1.;
-    t = QInputDialog::getDouble(this, "Eigen's QuaternionDemo", "time value: ",
+    t = QInputDialog::getDouble(this, "Eigen's RenderingWidget", "time value: ",
       t, 0, 1e3, 1, &ok);
     if (ok)
     {
@@ -82,20 +94,47 @@
     }
 }
 
-void QuaternionDemo::drawScene()
+void RenderingWidget::drawScene()
 {
   float length = 50;
   gpu.drawVector(Vector3f::Zero(), length*Vector3f::UnitX(), Color(1,0,0,1));
   gpu.drawVector(Vector3f::Zero(), length*Vector3f::UnitY(), Color(0,1,0,1));
   gpu.drawVector(Vector3f::Zero(), length*Vector3f::UnitZ(), Color(0,0,1,1));
+
+  // draw the fractal object
+  float sqrt3 = ei_sqrt(3.);
+  glLightfv(GL_LIGHT0, GL_AMBIENT, Vector4f(0.5,0.5,0.5,1).data());
+  glLightfv(GL_LIGHT0, GL_DIFFUSE, Vector4f(0.5,1,0.5,1).data());
+  glLightfv(GL_LIGHT0, GL_SPECULAR, Vector4f(1,1,1,1).data());
+  glLightfv(GL_LIGHT0, GL_POSITION, Vector4f(-sqrt3,-sqrt3,sqrt3,0).data());
+
+  glLightfv(GL_LIGHT1, GL_AMBIENT, Vector4f(0,0,0,1).data());
+  glLightfv(GL_LIGHT1, GL_DIFFUSE, Vector4f(1,0.5,0.5,1).data());
+  glLightfv(GL_LIGHT1, GL_SPECULAR, Vector4f(1,1,1,1).data());
+  glLightfv(GL_LIGHT1, GL_POSITION, Vector4f(-sqrt3,sqrt3,-sqrt3,0).data());
+
+  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, Vector4f(0.7, 0.7, 0.7, 1).data());
+  glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Vector4f(0.8, 0.75, 0.6, 1).data());
+  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, Vector4f(1, 1, 1, 1).data());
+  glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 64);
+
+  glEnable(GL_LIGHTING);
+  glEnable(GL_LIGHT0);
+  glEnable(GL_LIGHT1);
+  
+  glColor3f(0.4, 0.7, 0.4);
+  glVertexPointer(3, GL_FLOAT, 0, mVertices[0].data());
+  glNormalPointer(GL_FLOAT, 0, mNormals[0].data());
+  glEnableClientState(GL_VERTEX_ARRAY);
+  glEnableClientState(GL_NORMAL_ARRAY);
+  glDrawArrays(GL_TRIANGLES, 0, mVertices.size());
+  glDisableClientState(GL_VERTEX_ARRAY);
+  glDisableClientState(GL_NORMAL_ARRAY);
+  
+  glDisable(GL_LIGHTING);
 }
 
-void QuaternionDemo::drawPath()
-{
-
-}
-
-void QuaternionDemo::animate()
+void RenderingWidget::animate()
 {
   m_alpha += double(m_timer.interval()) * 1e-3;
 
@@ -130,7 +169,7 @@
   updateGL();
 }
 
-void QuaternionDemo::keyPressEvent(QKeyEvent * e)
+void RenderingWidget::keyPressEvent(QKeyEvent * e)
 {
     switch(e->key())
     {
@@ -150,18 +189,8 @@
             break;
         // move the camera to initial pos
         case Qt::Key_R:
-          {
-            if (mAnimate)
-              stopAnimation();
-            m_timeline.clear();
-            float duration = 3/*AngleAxisf(mCamera.orientation().inverse()
-                              * mInitFrame.orientation).angle()*/;
-            Frame aux = mCamera.frame();
-            aux.orientation = aux.orientation.inverse();
-            aux.position = mCamera.viewMatrix().translation();
-            m_timeline[0] = aux;
-            m_timeline[duration] = mInitFrame;
-          }
+          resetCamera();
+          break;
         // start/stop the animation
         case Qt::Key_A:
             if (mAnimate)
@@ -183,7 +212,7 @@
     updateGL();
 }
 
-void QuaternionDemo::stopAnimation()
+void RenderingWidget::stopAnimation()
 {
   disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(animate()));
   m_timer.stop();
@@ -191,46 +220,48 @@
   m_alpha = 0;
 }
 
-void QuaternionDemo::mousePressEvent(QMouseEvent* e)
+void RenderingWidget::mousePressEvent(QMouseEvent* e)
 {
   mMouseCoords = Vector2i(e->pos().x(), e->pos().y());
+  bool fly = (mNavMode==NavFly) || (e->modifiers()&Qt::ControlModifier);
   switch(e->button())
   {
     case Qt::LeftButton:
-      if(e->modifiers()&Qt::ControlModifier)
+      if(fly)
       {
-        mTrackMode = TM_QUAKE_ROTATE;
+        mCurrentTrackingMode = TM_LOCAL_ROTATE;
+        mTrackball.start(Trackball::Local);
       }
       else
       {
-        mTrackMode = TM_ROTATE_AROUND;
-        mTrackball.reset();
-        mTrackball.track(mMouseCoords);
+        mCurrentTrackingMode = TM_ROTATE_AROUND;
+        mTrackball.start(Trackball::Around);
       }
+      mTrackball.track(mMouseCoords);
       break;
     case Qt::MidButton:
-      if(e->modifiers()&Qt::ControlModifier)
-        mTrackMode = TM_QUAKE_WALK;
+      if(fly)
+        mCurrentTrackingMode = TM_FLY_Z;
       else
-        mTrackMode = TM_ZOOM;
+        mCurrentTrackingMode = TM_ZOOM;
       break;
     case Qt::RightButton:
-        mTrackMode = TM_QUAKE_PAN;
+        mCurrentTrackingMode = TM_FLY_PAN;
       break;
     default:
       break;
   }
 }
-void QuaternionDemo::mouseReleaseEvent(QMouseEvent*)
+void RenderingWidget::mouseReleaseEvent(QMouseEvent*)
 {
-    mTrackMode = TM_NO_TRACK;
+    mCurrentTrackingMode = TM_NO_TRACK;
     updateGL();
 }
 
-void QuaternionDemo::mouseMoveEvent(QMouseEvent* e)
+void RenderingWidget::mouseMoveEvent(QMouseEvent* e)
 {
     // tracking
-    if(mTrackMode != TM_NO_TRACK)
+    if(mCurrentTrackingMode != TM_NO_TRACK)
     {
         float dx =   float(e->x() - mMouseCoords.x()) / float(mCamera.vpWidth());
         float dy = - float(e->y() - mMouseCoords.y()) / float(mCamera.vpHeight());
@@ -241,23 +272,21 @@
           dy *= 10.;
         }
 
-        switch(mTrackMode)
+        switch(mCurrentTrackingMode)
         {
-          case TM_ROTATE_AROUND :
+          case TM_ROTATE_AROUND:
+          case TM_LOCAL_ROTATE:
             mTrackball.track(Vector2i(e->pos().x(), e->pos().y()));
             break;
           case TM_ZOOM :
             mCamera.zoom(dy*50);
             break;
-          case TM_QUAKE_WALK :
-            mCamera.localTranslate(Vector3f(0, 0, dy*100));
+          case TM_FLY_Z :
+            mCamera.localTranslate(Vector3f(0, 0, -dy*100));
             break;
-          case TM_QUAKE_PAN :
+          case TM_FLY_PAN :
             mCamera.localTranslate(Vector3f(dx*100, dy*100, 0));
             break;
-          case TM_QUAKE_ROTATE :
-            mCamera.localRotate(-dx*M_PI, dy*M_PI);
-            break;
           default:
             break;
         }
@@ -268,7 +297,7 @@
     mMouseCoords = Vector2i(e->pos().x(), e->pos().y());
 }
 
-void QuaternionDemo::paintGL()
+void RenderingWidget::paintGL()
 {
   glEnable(GL_DEPTH_TEST);
   glDisable(GL_CULL_FACE);
@@ -288,26 +317,242 @@
   drawScene();
 }
 
-void QuaternionDemo::initializeGL()
+void RenderingWidget::initializeGL()
 {
   glClearColor(1., 1., 1., 0.);
   glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
   glDepthMask(GL_TRUE);
   glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
 
-  mInitFrame.orientation = mCamera.viewMatrix().linear();
+  mCamera.setPosition(Vector3f(-200, -200, -200));
+  mCamera.setTarget(Vector3f(0, 0, 0));
+  mInitFrame.orientation = mCamera.orientation().inverse();
   mInitFrame.position = mCamera.viewMatrix().translation();
+
+  // create a kind of fractal sphere
+  {
+    IcoSphere pattern;
+    
+    int levels = 3;
+    float scale = 0.45;
+    float radius = 100;
+    std::vector<Vector3f> centers;
+    std::vector<int> parents;
+    std::vector<float> radii;
+    centers.push_back(Vector3f::Zero());
+    parents.push_back(-1);
+    radii.push_back(radius);
+    radius *= scale;
+
+    // generate level 1 using icosphere vertices
+    {
+      float dist = radii[0]*0.9;
+      for (int i=0; i<12; ++i)
+      {
+        centers.push_back(pattern.vertices()[i] * dist);
+        radii.push_back(radius);
+        parents.push_back(0);
+      }
+    }
+
+    scale = 0.33;
+    static const float angles [10] = {
+      0, 0,
+      M_PI, 0.*M_PI,
+      M_PI, 0.5*M_PI,
+      M_PI, 1.*M_PI,
+      M_PI, 1.5*M_PI};
+    
+    // generate other levels
+    int start = 1;
+    float maxAngle = M_PI/2;
+    for (int l=1; l<levels; l++)
+    {
+      radius *= scale;
+      int end = centers.size();
+      for (int i=start; i<end; ++i)
+      {
+        Vector3f c = centers[i];
+        Vector3f ax0, ax1;
+        if (parents[i]==-1)
+          ax0 = Vector3f::UnitZ();
+        else
+          ax0 = (c - centers[parents[i]]).normalized();
+        ax1 = ax0.unitOrthogonal();
+        Quaternionf q;
+        q.setFromTwoVectors(Vector3f::UnitZ(), ax0);
+        Transform3f t = Translation3f(c) * q * Scaling3f(radii[i]+radius);
+        for (int j=0; j<5; ++j)
+        {
+          Vector3f newC = c + ( (AngleAxisf(angles[j*2+1], ax0)
+                               * AngleAxisf(angles[j*2+0] * (l==1 ? 0.35 : 0.5), ax1)) * ax0)*(radii[i] + radius*0.8);
+          centers.push_back(newC);
+          radii.push_back(radius);
+          parents.push_back(i);
+        }
+      }
+      start = end;
+      maxAngle = M_PI/2;
+    }
+    parents.clear();
+    // instanciate the geometry
+    {
+      const std::vector<int>& sphereIndices = pattern.indices(2);
+      std::cout << "instanciate geometry...  (" << sphereIndices.size() * centers.size() << " vertices)\n";
+      mVertices.reserve(sphereIndices.size() * centers.size());
+      mNormals.reserve(sphereIndices.size() * centers.size());
+      int end = centers.size();
+      for (int i=0; i<end; ++i)
+      {
+        Transform3f t = Translation3f(centers[i]) * Scaling3f(radii[i]);
+        // copy vertices
+        for (unsigned int j=0; j<sphereIndices.size(); ++j)
+        {
+          Vector3f v = pattern.vertices()[sphereIndices[j]];
+          mVertices.push_back(t * v);
+          mNormals.push_back(v);
+        }
+      }
+    }
+  }
 }
 
-void QuaternionDemo::resizeGL(int width, int height)
+void RenderingWidget::resizeGL(int width, int height)
 {
     mCamera.setViewport(width,height);
 }
 
+void RenderingWidget::setNavMode(int m)
+{
+  mNavMode = NavMode(m);
+}
+
+void RenderingWidget::setLerpMode(int m)
+{
+  mLerpMode = LerpMode(m);
+}
+
+void RenderingWidget::setRotationMode(int m)
+{
+  mRotationMode = RotationMode(m);
+}
+
+void RenderingWidget::resetCamera()
+{
+  if (mAnimate)
+    stopAnimation();
+  m_timeline.clear();
+  Frame aux0 = mCamera.frame();
+  aux0.orientation = aux0.orientation.inverse();
+  aux0.position = mCamera.viewMatrix().translation();
+  m_timeline[0] = aux0;
+
+  Vector3f currentTarget = mCamera.target();
+  mCamera.setTarget(Vector3f::Zero());
+
+  // compute the rotation duration to move the camera to the target
+  Frame aux1 = mCamera.frame();
+  aux1.orientation = aux1.orientation.inverse();
+  aux1.position = mCamera.viewMatrix().translation();
+  float rangle = AngleAxisf(aux0.orientation.inverse() * aux1.orientation).angle();
+  if (rangle>M_PI)
+    rangle = 2.*M_PI - rangle;
+  float duration = rangle * 0.9;
+  if (duration<0.1) duration = 0.1;
+
+  // put the camera at that time step:
+  aux1 = aux0.lerp(duration/2,mInitFrame);
+  // and make it look at teh target again
+  aux1.orientation = aux1.orientation.inverse();
+  aux1.position = - (aux1.orientation * aux1.position);
+  mCamera.setFrame(aux1);
+  mCamera.setTarget(Vector3f::Zero());
+
+  // add this camera keyframe
+  aux1.orientation = aux1.orientation.inverse();
+  aux1.position = mCamera.viewMatrix().translation();
+  m_timeline[duration] = aux1;
+
+  m_timeline[2] = mInitFrame;
+  m_alpha = 0;
+  animate();
+  connect(&m_timer, SIGNAL(timeout()), this, SLOT(animate()));
+  m_timer.start(1000/30);
+  mAnimate = true;
+}
+
+QWidget* RenderingWidget::createNavigationControlWidget()
+{
+  QWidget* panel = new QWidget();
+  QVBoxLayout* layout = new QVBoxLayout();
+
+  {
+    // navigation mode
+    QButtonGroup* group = new QButtonGroup(panel);
+    QRadioButton* but;
+    but = new QRadioButton("turn around");
+    group->addButton(but, NavTurnAround);
+    layout->addWidget(but);
+    but = new QRadioButton("fly");
+    group->addButton(but, NavFly);
+    layout->addWidget(but);
+    group->button(mNavMode)->setChecked(true);
+    connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setNavMode(int)));
+  }
+  {
+    QPushButton* but = new QPushButton("reset");
+    layout->addWidget(but);
+    connect(but, SIGNAL(clicked()), this, SLOT(resetCamera()));
+  }
+  {
+    // track ball, rotation mode
+    QButtonGroup* group = new QButtonGroup(panel);
+    QRadioButton* but;
+    but = new QRadioButton("stable trackball");
+    group->addButton(but, RotationStable);
+    layout->addWidget(but);
+    but = new QRadioButton("standard rotation");
+    group->addButton(but, RotationStandard);
+    layout->addWidget(but);
+    but->setEnabled(false);
+    group->button(mRotationMode)->setChecked(true);
+    connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setRotationMode(int)));
+  }
+  {
+    // interpolation mode
+    QButtonGroup* group = new QButtonGroup(panel);
+    QRadioButton* but;
+    but = new QRadioButton("quaternion slerp");
+    group->addButton(but, LerpQuaternion);
+    layout->addWidget(but);
+    but = new QRadioButton("euler angles");
+    group->addButton(but, LerpEulerAngles);
+    layout->addWidget(but);
+    but->setEnabled(false);
+    group->button(mNavMode)->setChecked(true);
+    connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setLerpMode(int)));
+  }
+  layout->addItem(new QSpacerItem(0,0,QSizePolicy::Minimum,QSizePolicy::Expanding));
+  panel->setLayout(layout);
+  return panel;
+}
+
+QuaternionDemo::QuaternionDemo()
+{
+  mRenderingWidget = new RenderingWidget();
+  setCentralWidget(mRenderingWidget);
+
+  QDockWidget* panel = new QDockWidget("navigation", this);
+  panel->setAllowedAreas((QFlags<Qt::DockWidgetArea>)(Qt::RightDockWidgetArea | Qt::LeftDockWidgetArea));
+  addDockWidget(Qt::RightDockWidgetArea, panel);
+  panel->setWidget(mRenderingWidget->createNavigationControlWidget());
+}
+
 int main(int argc, char *argv[])
 {
   QApplication app(argc, argv);
   QuaternionDemo demo;
+  demo.resize(600,500);
   demo.show();
   return app.exec();
 }
diff --git a/demos/opengl/quaternion_demo.h b/demos/opengl/quaternion_demo.h
index 11b0030..f0b883e 100644
--- a/demos/opengl/quaternion_demo.h
+++ b/demos/opengl/quaternion_demo.h
@@ -32,8 +32,9 @@
 #include <QTimer>
 #include <QtGui/QApplication>
 #include <QtOpenGL/QGLWidget>
+#include <QtGui/QMainWindow>
 
-class QuaternionDemo : public QGLWidget
+class RenderingWidget : public QGLWidget
 {
   Q_OBJECT
 
@@ -45,14 +46,31 @@
     bool mAnimate;
     float m_alpha;
 
-    
     enum TrackMode {
       TM_NO_TRACK=0, TM_ROTATE_AROUND, TM_ZOOM,
-      TM_QUAKE_ROTATE, TM_QUAKE_WALK, TM_QUAKE_PAN
+      TM_LOCAL_ROTATE, TM_FLY_Z, TM_FLY_PAN
+    };
+
+    enum NavMode {
+      NavTurnAround,
+      NavFly
+    };
+
+    enum LerpMode {
+      LerpQuaternion,
+      LerpEulerAngles
+    };
+
+    enum RotationMode {
+      RotationStable,
+      RotationStandard
     };
 
     Camera mCamera;
-    TrackMode mTrackMode;
+    TrackMode mCurrentTrackingMode;
+    NavMode mNavMode;
+    LerpMode mLerpMode;
+    RotationMode mRotationMode;
     Vector2i mMouseCoords;
     Trackball mTrackball;
 
@@ -60,15 +78,23 @@
 
     void setupCamera();
 
+    std::vector<Vector3f> mVertices;
+    std::vector<Vector3f> mNormals;
+    std::vector<int> mIndices;
+
   protected slots:
 
     virtual void animate(void);
     virtual void drawScene(void);
-    virtual void drawPath(void);
 
     virtual void grabFrame(void);
     virtual void stopAnimation();
 
+    virtual void setNavMode(int);
+    virtual void setLerpMode(int);
+    virtual void setRotationMode(int);
+    virtual void resetCamera();
+
   protected:
 
     virtual void initializeGL();
@@ -83,8 +109,19 @@
     //--------------------------------------------------------------------------------
 
   public:
+    RenderingWidget();
+    ~RenderingWidget() { }
+
+    QWidget* createNavigationControlWidget();
+};
+
+class QuaternionDemo : public QMainWindow
+{
+  Q_OBJECT
+  public:
     QuaternionDemo();
-    ~QuaternionDemo() { }
+  protected:
+    RenderingWidget* mRenderingWidget;
 };
 
 #endif // EIGEN_QUATERNION_DEMO_H
diff --git a/demos/opengl/trackball.cpp b/demos/opengl/trackball.cpp
index f66243d..83e74a6 100644
--- a/demos/opengl/trackball.cpp
+++ b/demos/opengl/trackball.cpp
@@ -27,12 +27,12 @@
 
 using namespace Eigen;
 
-void Trackball::track(const Vector2i& newPoint2D)
+void Trackball::track(const Vector2i& point2D)
 {
   if (mpCamera==0)
     return;
   Vector3f newPoint3D;
-  bool newPointOk = mapToSphere(newPoint2D, newPoint3D);
+  bool newPointOk = mapToSphere(point2D, newPoint3D);
 
   if (mLastPointOk && newPointOk)
   {
@@ -40,12 +40,14 @@
     float cos_angle = mLastPoint3D.dot(newPoint3D);
     if ( ei_abs(cos_angle) < 1.0 )
     {
-      float angle = 2.0 * acos(cos_angle);
-      mpCamera->rotateAroundTarget(Quaternionf(AngleAxisf(angle, axis)));
+      float angle = acos(cos_angle);
+      if (mMode==Around)
+        mpCamera->rotateAroundTarget(Quaternionf(AngleAxisf(2.*angle, axis))); // *2 to speedup the rotation
+      else
+        mpCamera->localRotate(Quaternionf(AngleAxisf(-angle, axis)));
     }
   }
 
-  mLastPoint2D = newPoint2D;
   mLastPoint3D = newPoint3D;
   mLastPointOk = newPointOk;
 }
diff --git a/demos/opengl/trackball.h b/demos/opengl/trackball.h
index 29413be..e9a8995 100644
--- a/demos/opengl/trackball.h
+++ b/demos/opengl/trackball.h
@@ -33,9 +33,11 @@
 {
   public:
 
+    enum Mode {Around, Local};
+
     Trackball() : mpCamera(0) {}
 
-    void reset() { mLastPointOk = false; }
+    void start(Mode m = Around) { mMode = m; mLastPointOk = false; }
 
     void setCamera(Camera* pCam) { mpCamera = pCam; }
 
@@ -46,8 +48,8 @@
     bool mapToSphere( const Eigen::Vector2i& p2, Eigen::Vector3f& v3);
 
     Camera* mpCamera;
-    Eigen::Vector2i mLastPoint2D;
     Eigen::Vector3f mLastPoint3D;
+    Mode mMode;
     bool mLastPointOk;
 
 };