1 // This file is part of Eigen, a lightweight C++ template library
2 // for linear algebra.
3 //
4 // Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr>
5 //
6 // This Source Code Form is subject to the terms of the Mozilla
7 // Public License v. 2.0. If a copy of the MPL was not distributed
8 // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 
10 #include "quaternion_demo.h"
11 #include "icosphere.h"
12 
13 #include <Eigen/Geometry>
14 #include <Eigen/QR>
15 #include <Eigen/LU>
16 
17 #include <iostream>
18 #include <QEvent>
19 #include <QMouseEvent>
20 #include <QInputDialog>
21 #include <QGridLayout>
22 #include <QButtonGroup>
23 #include <QRadioButton>
24 #include <QDockWidget>
25 #include <QPushButton>
26 #include <QGroupBox>
27 
28 using namespace Eigen;
29 
30 class FancySpheres
31 {
32   public:
33     EIGEN_MAKE_ALIGNED_OPERATOR_NEW
34 
FancySpheres()35     FancySpheres()
36     {
37       const int levels = 4;
38       const float scale = 0.33;
39       float radius = 100;
40       std::vector<int> parents;
41 
42       // leval 0
43       mCenters.push_back(Vector3f::Zero());
44       parents.push_back(-1);
45       mRadii.push_back(radius);
46 
47       // generate level 1 using icosphere vertices
48       radius *= 0.45;
49       {
50         float dist = mRadii[0]*0.9;
51         for (int i=0; i<12; ++i)
52         {
53           mCenters.push_back(mIcoSphere.vertices()[i] * dist);
54           mRadii.push_back(radius);
55           parents.push_back(0);
56         }
57       }
58 
59       static const float angles [10] = {
60         0, 0,
61         M_PI, 0.*M_PI,
62         M_PI, 0.5*M_PI,
63         M_PI, 1.*M_PI,
64         M_PI, 1.5*M_PI
65       };
66 
67       // generate other levels
68       int start = 1;
69       for (int l=1; l<levels; l++)
70       {
71         radius *= scale;
72         int end = mCenters.size();
73         for (int i=start; i<end; ++i)
74         {
75           Vector3f c = mCenters[i];
76           Vector3f ax0 = (c - mCenters[parents[i]]).normalized();
77           Vector3f ax1 = ax0.unitOrthogonal();
78           Quaternionf q;
79           q.setFromTwoVectors(Vector3f::UnitZ(), ax0);
80           Affine3f t = Translation3f(c) * q * Scaling(mRadii[i]+radius);
81           for (int j=0; j<5; ++j)
82           {
83             Vector3f newC = c + ( (AngleAxisf(angles[j*2+1], ax0)
84                                 * AngleAxisf(angles[j*2+0] * (l==1 ? 0.35 : 0.5), ax1)) * ax0)
85                                 * (mRadii[i] + radius*0.8);
86             mCenters.push_back(newC);
87             mRadii.push_back(radius);
88             parents.push_back(i);
89           }
90         }
91         start = end;
92       }
93     }
94 
draw()95     void draw()
96     {
97       int end = mCenters.size();
98       glEnable(GL_NORMALIZE);
99       for (int i=0; i<end; ++i)
100       {
101         Affine3f t = Translation3f(mCenters[i]) * Scaling(mRadii[i]);
102         gpu.pushMatrix(GL_MODELVIEW);
103         gpu.multMatrix(t.matrix(),GL_MODELVIEW);
104         mIcoSphere.draw(2);
105         gpu.popMatrix(GL_MODELVIEW);
106       }
107       glDisable(GL_NORMALIZE);
108     }
109   protected:
110     std::vector<Vector3f> mCenters;
111     std::vector<float> mRadii;
112     IcoSphere mIcoSphere;
113 };
114 
115 
116 // generic linear interpolation method
lerp(float t,const T & a,const T & b)117 template<typename T> T lerp(float t, const T& a, const T& b)
118 {
119   return a*(1-t) + b*t;
120 }
121 
122 // quaternion slerp
lerp(float t,const Quaternionf & a,const Quaternionf & b)123 template<> Quaternionf lerp(float t, const Quaternionf& a, const Quaternionf& b)
124 { return a.slerp(t,b); }
125 
126 // linear interpolation of a frame using the type OrientationType
127 // to perform the interpolation of the orientations
128 template<typename OrientationType>
lerpFrame(float alpha,const Frame & a,const Frame & b)129 inline static Frame lerpFrame(float alpha, const Frame& a, const Frame& b)
130 {
131   return Frame(lerp(alpha,a.position,b.position),
132                Quaternionf(lerp(alpha,OrientationType(a.orientation),OrientationType(b.orientation))));
133 }
134 
135 template<typename _Scalar> class EulerAngles
136 {
137 public:
138   enum { Dim = 3 };
139   typedef _Scalar Scalar;
140   typedef Matrix<Scalar,3,3> Matrix3;
141   typedef Matrix<Scalar,3,1> Vector3;
142   typedef Quaternion<Scalar> QuaternionType;
143 
144 protected:
145 
146   Vector3 m_angles;
147 
148 public:
149 
EulerAngles()150   EulerAngles() {}
EulerAngles(Scalar a0,Scalar a1,Scalar a2)151   inline EulerAngles(Scalar a0, Scalar a1, Scalar a2) : m_angles(a0, a1, a2) {}
EulerAngles(const QuaternionType & q)152   inline EulerAngles(const QuaternionType& q) { *this = q; }
153 
coeffs() const154   const Vector3& coeffs() const { return m_angles; }
coeffs()155   Vector3& coeffs() { return m_angles; }
156 
operator =(const QuaternionType & q)157   EulerAngles& operator=(const QuaternionType& q)
158   {
159     Matrix3 m = q.toRotationMatrix();
160     return *this = m;
161   }
162 
operator =(const Matrix3 & m)163   EulerAngles& operator=(const Matrix3& m)
164   {
165     // mat =  cy*cz          -cy*sz           sy
166     //        cz*sx*sy+cx*sz  cx*cz-sx*sy*sz -cy*sx
167     //       -cx*cz*sy+sx*sz  cz*sx+cx*sy*sz  cx*cy
168     m_angles.coeffRef(1) = std::asin(m.coeff(0,2));
169     m_angles.coeffRef(0) = std::atan2(-m.coeff(1,2),m.coeff(2,2));
170     m_angles.coeffRef(2) = std::atan2(-m.coeff(0,1),m.coeff(0,0));
171     return *this;
172   }
173 
toRotationMatrix(void) const174   Matrix3 toRotationMatrix(void) const
175   {
176     Vector3 c = m_angles.array().cos();
177     Vector3 s = m_angles.array().sin();
178     Matrix3 res;
179     res <<  c.y()*c.z(),                    -c.y()*s.z(),                   s.y(),
180             c.z()*s.x()*s.y()+c.x()*s.z(),  c.x()*c.z()-s.x()*s.y()*s.z(),  -c.y()*s.x(),
181             -c.x()*c.z()*s.y()+s.x()*s.z(), c.z()*s.x()+c.x()*s.y()*s.z(),  c.x()*c.y();
182     return res;
183   }
184 
operator QuaternionType()185   operator QuaternionType() { return QuaternionType(toRotationMatrix()); }
186 };
187 
188 // Euler angles slerp
lerp(float t,const EulerAngles<float> & a,const EulerAngles<float> & b)189 template<> EulerAngles<float> lerp(float t, const EulerAngles<float>& a, const EulerAngles<float>& b)
190 {
191   EulerAngles<float> res;
192   res.coeffs() = lerp(t, a.coeffs(), b.coeffs());
193   return res;
194 }
195 
196 
RenderingWidget()197 RenderingWidget::RenderingWidget()
198 {
199   mAnimate = false;
200   mCurrentTrackingMode = TM_NO_TRACK;
201   mNavMode = NavTurnAround;
202   mLerpMode = LerpQuaternion;
203   mRotationMode = RotationStable;
204   mTrackball.setCamera(&mCamera);
205 
206   // required to capture key press events
207   setFocusPolicy(Qt::ClickFocus);
208 }
209 
grabFrame(void)210 void RenderingWidget::grabFrame(void)
211 {
212     // ask user for a time
213     bool ok = false;
214     double t = 0;
215     if (!m_timeline.empty())
216       t = (--m_timeline.end())->first + 1.;
217     t = QInputDialog::getDouble(this, "Eigen's RenderingWidget", "time value: ",
218       t, 0, 1e3, 1, &ok);
219     if (ok)
220     {
221       Frame aux;
222       aux.orientation = mCamera.viewMatrix().linear();
223       aux.position = mCamera.viewMatrix().translation();
224       m_timeline[t] = aux;
225     }
226 }
227 
drawScene()228 void RenderingWidget::drawScene()
229 {
230   static FancySpheres sFancySpheres;
231   float length = 50;
232   gpu.drawVector(Vector3f::Zero(), length*Vector3f::UnitX(), Color(1,0,0,1));
233   gpu.drawVector(Vector3f::Zero(), length*Vector3f::UnitY(), Color(0,1,0,1));
234   gpu.drawVector(Vector3f::Zero(), length*Vector3f::UnitZ(), Color(0,0,1,1));
235 
236   // draw the fractal object
237   float sqrt3 = internal::sqrt(3.);
238   glLightfv(GL_LIGHT0, GL_AMBIENT, Vector4f(0.5,0.5,0.5,1).data());
239   glLightfv(GL_LIGHT0, GL_DIFFUSE, Vector4f(0.5,1,0.5,1).data());
240   glLightfv(GL_LIGHT0, GL_SPECULAR, Vector4f(1,1,1,1).data());
241   glLightfv(GL_LIGHT0, GL_POSITION, Vector4f(-sqrt3,-sqrt3,sqrt3,0).data());
242 
243   glLightfv(GL_LIGHT1, GL_AMBIENT, Vector4f(0,0,0,1).data());
244   glLightfv(GL_LIGHT1, GL_DIFFUSE, Vector4f(1,0.5,0.5,1).data());
245   glLightfv(GL_LIGHT1, GL_SPECULAR, Vector4f(1,1,1,1).data());
246   glLightfv(GL_LIGHT1, GL_POSITION, Vector4f(-sqrt3,sqrt3,-sqrt3,0).data());
247 
248   glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, Vector4f(0.7, 0.7, 0.7, 1).data());
249   glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Vector4f(0.8, 0.75, 0.6, 1).data());
250   glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, Vector4f(1, 1, 1, 1).data());
251   glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 64);
252 
253   glEnable(GL_LIGHTING);
254   glEnable(GL_LIGHT0);
255   glEnable(GL_LIGHT1);
256 
257   sFancySpheres.draw();
258   glVertexPointer(3, GL_FLOAT, 0, mVertices[0].data());
259   glNormalPointer(GL_FLOAT, 0, mNormals[0].data());
260   glEnableClientState(GL_VERTEX_ARRAY);
261   glEnableClientState(GL_NORMAL_ARRAY);
262   glDrawArrays(GL_TRIANGLES, 0, mVertices.size());
263   glDisableClientState(GL_VERTEX_ARRAY);
264   glDisableClientState(GL_NORMAL_ARRAY);
265 
266   glDisable(GL_LIGHTING);
267 }
268 
animate()269 void RenderingWidget::animate()
270 {
271   m_alpha += double(m_timer.interval()) * 1e-3;
272 
273   TimeLine::const_iterator hi = m_timeline.upper_bound(m_alpha);
274   TimeLine::const_iterator lo = hi;
275   --lo;
276 
277   Frame currentFrame;
278 
279   if(hi==m_timeline.end())
280   {
281     // end
282     currentFrame = lo->second;
283     stopAnimation();
284   }
285   else if(hi==m_timeline.begin())
286   {
287     // start
288     currentFrame = hi->second;
289   }
290   else
291   {
292     float s = (m_alpha - lo->first)/(hi->first - lo->first);
293     if (mLerpMode==LerpEulerAngles)
294       currentFrame = ::lerpFrame<EulerAngles<float> >(s, lo->second, hi->second);
295     else if (mLerpMode==LerpQuaternion)
296       currentFrame = ::lerpFrame<Eigen::Quaternionf>(s, lo->second, hi->second);
297     else
298     {
299       std::cerr << "Invalid rotation interpolation mode (abort)\n";
300       exit(2);
301     }
302     currentFrame.orientation.coeffs().normalize();
303   }
304 
305   currentFrame.orientation = currentFrame.orientation.inverse();
306   currentFrame.position = - (currentFrame.orientation * currentFrame.position);
307   mCamera.setFrame(currentFrame);
308 
309   updateGL();
310 }
311 
keyPressEvent(QKeyEvent * e)312 void RenderingWidget::keyPressEvent(QKeyEvent * e)
313 {
314     switch(e->key())
315     {
316       case Qt::Key_Up:
317         mCamera.zoom(2);
318         break;
319       case Qt::Key_Down:
320         mCamera.zoom(-2);
321         break;
322       // add a frame
323       case Qt::Key_G:
324         grabFrame();
325         break;
326       // clear the time line
327       case Qt::Key_C:
328         m_timeline.clear();
329         break;
330       // move the camera to initial pos
331       case Qt::Key_R:
332         resetCamera();
333         break;
334       // start/stop the animation
335       case Qt::Key_A:
336         if (mAnimate)
337         {
338           stopAnimation();
339         }
340         else
341         {
342           m_alpha = 0;
343           connect(&m_timer, SIGNAL(timeout()), this, SLOT(animate()));
344           m_timer.start(1000/30);
345           mAnimate = true;
346         }
347         break;
348       default:
349         break;
350     }
351 
352     updateGL();
353 }
354 
stopAnimation()355 void RenderingWidget::stopAnimation()
356 {
357   disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(animate()));
358   m_timer.stop();
359   mAnimate = false;
360   m_alpha = 0;
361 }
362 
mousePressEvent(QMouseEvent * e)363 void RenderingWidget::mousePressEvent(QMouseEvent* e)
364 {
365   mMouseCoords = Vector2i(e->pos().x(), e->pos().y());
366   bool fly = (mNavMode==NavFly) || (e->modifiers()&Qt::ControlModifier);
367   switch(e->button())
368   {
369     case Qt::LeftButton:
370       if(fly)
371       {
372         mCurrentTrackingMode = TM_LOCAL_ROTATE;
373         mTrackball.start(Trackball::Local);
374       }
375       else
376       {
377         mCurrentTrackingMode = TM_ROTATE_AROUND;
378         mTrackball.start(Trackball::Around);
379       }
380       mTrackball.track(mMouseCoords);
381       break;
382     case Qt::MidButton:
383       if(fly)
384         mCurrentTrackingMode = TM_FLY_Z;
385       else
386         mCurrentTrackingMode = TM_ZOOM;
387       break;
388     case Qt::RightButton:
389         mCurrentTrackingMode = TM_FLY_PAN;
390       break;
391     default:
392       break;
393   }
394 }
mouseReleaseEvent(QMouseEvent *)395 void RenderingWidget::mouseReleaseEvent(QMouseEvent*)
396 {
397     mCurrentTrackingMode = TM_NO_TRACK;
398     updateGL();
399 }
400 
mouseMoveEvent(QMouseEvent * e)401 void RenderingWidget::mouseMoveEvent(QMouseEvent* e)
402 {
403     // tracking
404     if(mCurrentTrackingMode != TM_NO_TRACK)
405     {
406         float dx =   float(e->x() - mMouseCoords.x()) / float(mCamera.vpWidth());
407         float dy = - float(e->y() - mMouseCoords.y()) / float(mCamera.vpHeight());
408 
409         // speedup the transformations
410         if(e->modifiers() & Qt::ShiftModifier)
411         {
412           dx *= 10.;
413           dy *= 10.;
414         }
415 
416         switch(mCurrentTrackingMode)
417         {
418           case TM_ROTATE_AROUND:
419           case TM_LOCAL_ROTATE:
420             if (mRotationMode==RotationStable)
421             {
422               // use the stable trackball implementation mapping
423               // the 2D coordinates to 3D points on a sphere.
424               mTrackball.track(Vector2i(e->pos().x(), e->pos().y()));
425             }
426             else
427             {
428               // standard approach mapping the x and y displacements as rotations
429               // around the camera's X and Y axes.
430               Quaternionf q = AngleAxisf( dx*M_PI, Vector3f::UnitY())
431                             * AngleAxisf(-dy*M_PI, Vector3f::UnitX());
432               if (mCurrentTrackingMode==TM_LOCAL_ROTATE)
433                 mCamera.localRotate(q);
434               else
435                 mCamera.rotateAroundTarget(q);
436             }
437             break;
438           case TM_ZOOM :
439             mCamera.zoom(dy*100);
440             break;
441           case TM_FLY_Z :
442             mCamera.localTranslate(Vector3f(0, 0, -dy*200));
443             break;
444           case TM_FLY_PAN :
445             mCamera.localTranslate(Vector3f(dx*200, dy*200, 0));
446             break;
447           default:
448             break;
449         }
450 
451         updateGL();
452     }
453 
454     mMouseCoords = Vector2i(e->pos().x(), e->pos().y());
455 }
456 
paintGL()457 void RenderingWidget::paintGL()
458 {
459   glEnable(GL_DEPTH_TEST);
460   glDisable(GL_CULL_FACE);
461   glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
462   glDisable(GL_COLOR_MATERIAL);
463   glDisable(GL_BLEND);
464   glDisable(GL_ALPHA_TEST);
465   glDisable(GL_TEXTURE_1D);
466   glDisable(GL_TEXTURE_2D);
467   glDisable(GL_TEXTURE_3D);
468 
469   // Clear buffers
470   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
471 
472   mCamera.activateGL();
473 
474   drawScene();
475 }
476 
initializeGL()477 void RenderingWidget::initializeGL()
478 {
479   glClearColor(1., 1., 1., 0.);
480   glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
481   glDepthMask(GL_TRUE);
482   glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
483 
484   mCamera.setPosition(Vector3f(-200, -200, -200));
485   mCamera.setTarget(Vector3f(0, 0, 0));
486   mInitFrame.orientation = mCamera.orientation().inverse();
487   mInitFrame.position = mCamera.viewMatrix().translation();
488 }
489 
resizeGL(int width,int height)490 void RenderingWidget::resizeGL(int width, int height)
491 {
492     mCamera.setViewport(width,height);
493 }
494 
setNavMode(int m)495 void RenderingWidget::setNavMode(int m)
496 {
497   mNavMode = NavMode(m);
498 }
499 
setLerpMode(int m)500 void RenderingWidget::setLerpMode(int m)
501 {
502   mLerpMode = LerpMode(m);
503 }
504 
setRotationMode(int m)505 void RenderingWidget::setRotationMode(int m)
506 {
507   mRotationMode = RotationMode(m);
508 }
509 
resetCamera()510 void RenderingWidget::resetCamera()
511 {
512   if (mAnimate)
513     stopAnimation();
514   m_timeline.clear();
515   Frame aux0 = mCamera.frame();
516   aux0.orientation = aux0.orientation.inverse();
517   aux0.position = mCamera.viewMatrix().translation();
518   m_timeline[0] = aux0;
519 
520   Vector3f currentTarget = mCamera.target();
521   mCamera.setTarget(Vector3f::Zero());
522 
523   // compute the rotation duration to move the camera to the target
524   Frame aux1 = mCamera.frame();
525   aux1.orientation = aux1.orientation.inverse();
526   aux1.position = mCamera.viewMatrix().translation();
527   float duration = aux0.orientation.angularDistance(aux1.orientation) * 0.9;
528   if (duration<0.1) duration = 0.1;
529 
530   // put the camera at that time step:
531   aux1 = aux0.lerp(duration/2,mInitFrame);
532   // and make it look at the target again
533   aux1.orientation = aux1.orientation.inverse();
534   aux1.position = - (aux1.orientation * aux1.position);
535   mCamera.setFrame(aux1);
536   mCamera.setTarget(Vector3f::Zero());
537 
538   // add this camera keyframe
539   aux1.orientation = aux1.orientation.inverse();
540   aux1.position = mCamera.viewMatrix().translation();
541   m_timeline[duration] = aux1;
542 
543   m_timeline[2] = mInitFrame;
544   m_alpha = 0;
545   animate();
546   connect(&m_timer, SIGNAL(timeout()), this, SLOT(animate()));
547   m_timer.start(1000/30);
548   mAnimate = true;
549 }
550 
createNavigationControlWidget()551 QWidget* RenderingWidget::createNavigationControlWidget()
552 {
553   QWidget* panel = new QWidget();
554   QVBoxLayout* layout = new QVBoxLayout();
555 
556   {
557     QPushButton* but = new QPushButton("reset");
558     but->setToolTip("move the camera to initial position (with animation)");
559     layout->addWidget(but);
560     connect(but, SIGNAL(clicked()), this, SLOT(resetCamera()));
561   }
562   {
563     // navigation mode
564     QGroupBox* box = new QGroupBox("navigation mode");
565     QVBoxLayout* boxLayout = new QVBoxLayout;
566     QButtonGroup* group = new QButtonGroup(panel);
567     QRadioButton* but;
568     but = new QRadioButton("turn around");
569     but->setToolTip("look around an object");
570     group->addButton(but, NavTurnAround);
571     boxLayout->addWidget(but);
572     but = new QRadioButton("fly");
573     but->setToolTip("free navigation like a spaceship\n(this mode can also be enabled pressing the \"shift\" key)");
574     group->addButton(but, NavFly);
575     boxLayout->addWidget(but);
576     group->button(mNavMode)->setChecked(true);
577     connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setNavMode(int)));
578     box->setLayout(boxLayout);
579     layout->addWidget(box);
580   }
581   {
582     // track ball, rotation mode
583     QGroupBox* box = new QGroupBox("rotation mode");
584     QVBoxLayout* boxLayout = new QVBoxLayout;
585     QButtonGroup* group = new QButtonGroup(panel);
586     QRadioButton* but;
587     but = new QRadioButton("stable trackball");
588     group->addButton(but, RotationStable);
589     boxLayout->addWidget(but);
590     but->setToolTip("use the stable trackball implementation mapping\nthe 2D coordinates to 3D points on a sphere");
591     but = new QRadioButton("standard rotation");
592     group->addButton(but, RotationStandard);
593     boxLayout->addWidget(but);
594     but->setToolTip("standard approach mapping the x and y displacements\nas rotations around the camera's X and Y axes");
595     group->button(mRotationMode)->setChecked(true);
596     connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setRotationMode(int)));
597     box->setLayout(boxLayout);
598     layout->addWidget(box);
599   }
600   {
601     // interpolation mode
602     QGroupBox* box = new QGroupBox("spherical interpolation");
603     QVBoxLayout* boxLayout = new QVBoxLayout;
604     QButtonGroup* group = new QButtonGroup(panel);
605     QRadioButton* but;
606     but = new QRadioButton("quaternion slerp");
607     group->addButton(but, LerpQuaternion);
608     boxLayout->addWidget(but);
609     but->setToolTip("use quaternion spherical interpolation\nto interpolate orientations");
610     but = new QRadioButton("euler angles");
611     group->addButton(but, LerpEulerAngles);
612     boxLayout->addWidget(but);
613     but->setToolTip("use Euler angles to interpolate orientations");
614     group->button(mNavMode)->setChecked(true);
615     connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setLerpMode(int)));
616     box->setLayout(boxLayout);
617     layout->addWidget(box);
618   }
619   layout->addItem(new QSpacerItem(0,0,QSizePolicy::Minimum,QSizePolicy::Expanding));
620   panel->setLayout(layout);
621   return panel;
622 }
623 
QuaternionDemo()624 QuaternionDemo::QuaternionDemo()
625 {
626   mRenderingWidget = new RenderingWidget();
627   setCentralWidget(mRenderingWidget);
628 
629   QDockWidget* panel = new QDockWidget("navigation", this);
630   panel->setAllowedAreas((QFlags<Qt::DockWidgetArea>)(Qt::RightDockWidgetArea | Qt::LeftDockWidgetArea));
631   addDockWidget(Qt::RightDockWidgetArea, panel);
632   panel->setWidget(mRenderingWidget->createNavigationControlWidget());
633 }
634 
main(int argc,char * argv[])635 int main(int argc, char *argv[])
636 {
637   std::cout << "Navigation:\n";
638   std::cout << "  left button:           rotate around the target\n";
639   std::cout << "  middle button:         zoom\n";
640   std::cout << "  left button + ctrl     quake rotate (rotate around camera position)\n";
641   std::cout << "  middle button + ctrl   walk (progress along camera's z direction)\n";
642   std::cout << "  left button:           pan (translate in the XY camera's plane)\n\n";
643   std::cout << "R : move the camera to initial position\n";
644   std::cout << "A : start/stop animation\n";
645   std::cout << "C : clear the animation\n";
646   std::cout << "G : add a key frame\n";
647 
648   QApplication app(argc, argv);
649   QuaternionDemo demo;
650   demo.resize(600,500);
651   demo.show();
652   return app.exec();
653 }
654 
655 #include "quaternion_demo.moc"
656 
657