00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025 #include "quaternion_demo.h"
00026 #include "icosphere.h"
00027
00028 #include <Eigen/Geometry>
00029 #include <Eigen/QR>
00030 #include <Eigen/LU>
00031
00032 #include <iostream>
00033 #include <QEvent>
00034 #include <QMouseEvent>
00035 #include <QInputDialog>
00036 #include <QGridLayout>
00037 #include <QButtonGroup>
00038 #include <QRadioButton>
00039 #include <QDockWidget>
00040 #include <QPushButton>
00041 #include <QGroupBox>
00042
00043 using namespace Eigen;
00044
00045 class FancySpheres
00046 {
00047 public:
00048 EIGEN_MAKE_ALIGNED_OPERATOR_NEW
00049
00050 FancySpheres()
00051 {
00052 const int levels = 4;
00053 const float scale = 0.33;
00054 float radius = 100;
00055 std::vector<int> parents;
00056
00057
00058 mCenters.push_back(Vector3f::Zero());
00059 parents.push_back(-1);
00060 mRadii.push_back(radius);
00061
00062
00063 radius *= 0.45;
00064 {
00065 float dist = mRadii[0]*0.9;
00066 for (int i=0; i<12; ++i)
00067 {
00068 mCenters.push_back(mIcoSphere.vertices()[i] * dist);
00069 mRadii.push_back(radius);
00070 parents.push_back(0);
00071 }
00072 }
00073
00074 static const float angles [10] = {
00075 0, 0,
00076 M_PI, 0.*M_PI,
00077 M_PI, 0.5*M_PI,
00078 M_PI, 1.*M_PI,
00079 M_PI, 1.5*M_PI
00080 };
00081
00082
00083 int start = 1;
00084 for (int l=1; l<levels; l++)
00085 {
00086 radius *= scale;
00087 int end = mCenters.size();
00088 for (int i=start; i<end; ++i)
00089 {
00090 Vector3f c = mCenters[i];
00091 Vector3f ax0 = (c - mCenters[parents[i]]).normalized();
00092 Vector3f ax1 = ax0.unitOrthogonal();
00093 Quaternionf q;
00094 q.setFromTwoVectors(Vector3f::UnitZ(), ax0);
00095 Affine3f t = Translation3f(c) * q * Scaling(mRadii[i]+radius);
00096 for (int j=0; j<5; ++j)
00097 {
00098 Vector3f newC = c + ( (AngleAxisf(angles[j*2+1], ax0)
00099 * AngleAxisf(angles[j*2+0] * (l==1 ? 0.35 : 0.5), ax1)) * ax0)
00100 * (mRadii[i] + radius*0.8);
00101 mCenters.push_back(newC);
00102 mRadii.push_back(radius);
00103 parents.push_back(i);
00104 }
00105 }
00106 start = end;
00107 }
00108 }
00109
00110 void draw()
00111 {
00112 int end = mCenters.size();
00113 glEnable(GL_NORMALIZE);
00114 for (int i=0; i<end; ++i)
00115 {
00116 Affine3f t = Translation3f(mCenters[i]) * Scaling(mRadii[i]);
00117 gpu.pushMatrix(GL_MODELVIEW);
00118 gpu.multMatrix(t.matrix(),GL_MODELVIEW);
00119 mIcoSphere.draw(2);
00120 gpu.popMatrix(GL_MODELVIEW);
00121 }
00122 glDisable(GL_NORMALIZE);
00123 }
00124 protected:
00125 std::vector<Vector3f> mCenters;
00126 std::vector<float> mRadii;
00127 IcoSphere mIcoSphere;
00128 };
00129
00130
00131
00132 template<typename T> T lerp(float t, const T& a, const T& b)
00133 {
00134 return a*(1-t) + b*t;
00135 }
00136
00137
00138 template<> Quaternionf lerp(float t, const Quaternionf& a, const Quaternionf& b)
00139 { return a.slerp(t,b); }
00140
00141
00142
00143 template<typename OrientationType>
00144 inline static Frame lerpFrame(float alpha, const Frame& a, const Frame& b)
00145 {
00146 return Frame(lerp(alpha,a.position,b.position),
00147 Quaternionf(lerp(alpha,OrientationType(a.orientation),OrientationType(b.orientation))));
00148 }
00149
00150 template<typename _Scalar> class EulerAngles
00151 {
00152 public:
00153 enum { Dim = 3 };
00154 typedef _Scalar Scalar;
00155 typedef Matrix<Scalar,3,3> Matrix3;
00156 typedef Matrix<Scalar,3,1> Vector3;
00157 typedef Quaternion<Scalar> QuaternionType;
00158
00159 protected:
00160
00161 Vector3 m_angles;
00162
00163 public:
00164
00165 EulerAngles() {}
00166 inline EulerAngles(Scalar a0, Scalar a1, Scalar a2) : m_angles(a0, a1, a2) {}
00167 inline EulerAngles(const QuaternionType& q) { *this = q; }
00168
00169 const Vector3& coeffs() const { return m_angles; }
00170 Vector3& coeffs() { return m_angles; }
00171
00172 EulerAngles& operator=(const QuaternionType& q)
00173 {
00174 Matrix3 m = q.toRotationMatrix();
00175 return *this = m;
00176 }
00177
00178 EulerAngles& operator=(const Matrix3& m)
00179 {
00180
00181
00182
00183 m_angles.coeffRef(1) = std::asin(m.coeff(0,2));
00184 m_angles.coeffRef(0) = std::atan2(-m.coeff(1,2),m.coeff(2,2));
00185 m_angles.coeffRef(2) = std::atan2(-m.coeff(0,1),m.coeff(0,0));
00186 return *this;
00187 }
00188
00189 Matrix3 toRotationMatrix(void) const
00190 {
00191 Vector3 c = m_angles.array().cos();
00192 Vector3 s = m_angles.array().sin();
00193 Matrix3 res;
00194 res << c.y()*c.z(), -c.y()*s.z(), s.y(),
00195 c.z()*s.x()*s.y()+c.x()*s.z(), c.x()*c.z()-s.x()*s.y()*s.z(), -c.y()*s.x(),
00196 -c.x()*c.z()*s.y()+s.x()*s.z(), c.z()*s.x()+c.x()*s.y()*s.z(), c.x()*c.y();
00197 return res;
00198 }
00199
00200 operator QuaternionType() { return QuaternionType(toRotationMatrix()); }
00201 };
00202
00203
00204 template<> EulerAngles<float> lerp(float t, const EulerAngles<float>& a, const EulerAngles<float>& b)
00205 {
00206 EulerAngles<float> res;
00207 res.coeffs() = lerp(t, a.coeffs(), b.coeffs());
00208 return res;
00209 }
00210
00211
00212 RenderingWidget::RenderingWidget()
00213 {
00214 mAnimate = false;
00215 mCurrentTrackingMode = TM_NO_TRACK;
00216 mNavMode = NavTurnAround;
00217 mLerpMode = LerpQuaternion;
00218 mRotationMode = RotationStable;
00219 mTrackball.setCamera(&mCamera);
00220
00221
00222 setFocusPolicy(Qt::ClickFocus);
00223 }
00224
00225 void RenderingWidget::grabFrame(void)
00226 {
00227
00228 bool ok = false;
00229 double t = 0;
00230 if (!m_timeline.empty())
00231 t = (--m_timeline.end())->first + 1.;
00232 t = QInputDialog::getDouble(this, "Eigen's RenderingWidget", "time value: ",
00233 t, 0, 1e3, 1, &ok);
00234 if (ok)
00235 {
00236 Frame aux;
00237 aux.orientation = mCamera.viewMatrix().linear();
00238 aux.position = mCamera.viewMatrix().translation();
00239 m_timeline[t] = aux;
00240 }
00241 }
00242
00243 void RenderingWidget::drawScene()
00244 {
00245 static FancySpheres sFancySpheres;
00246 float length = 50;
00247 gpu.drawVector(Vector3f::Zero(), length*Vector3f::UnitX(), Color(1,0,0,1));
00248 gpu.drawVector(Vector3f::Zero(), length*Vector3f::UnitY(), Color(0,1,0,1));
00249 gpu.drawVector(Vector3f::Zero(), length*Vector3f::UnitZ(), Color(0,0,1,1));
00250
00251
00252 float sqrt3 = internal::sqrt(3.);
00253 glLightfv(GL_LIGHT0, GL_AMBIENT, Vector4f(0.5,0.5,0.5,1).data());
00254 glLightfv(GL_LIGHT0, GL_DIFFUSE, Vector4f(0.5,1,0.5,1).data());
00255 glLightfv(GL_LIGHT0, GL_SPECULAR, Vector4f(1,1,1,1).data());
00256 glLightfv(GL_LIGHT0, GL_POSITION, Vector4f(-sqrt3,-sqrt3,sqrt3,0).data());
00257
00258 glLightfv(GL_LIGHT1, GL_AMBIENT, Vector4f(0,0,0,1).data());
00259 glLightfv(GL_LIGHT1, GL_DIFFUSE, Vector4f(1,0.5,0.5,1).data());
00260 glLightfv(GL_LIGHT1, GL_SPECULAR, Vector4f(1,1,1,1).data());
00261 glLightfv(GL_LIGHT1, GL_POSITION, Vector4f(-sqrt3,sqrt3,-sqrt3,0).data());
00262
00263 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, Vector4f(0.7, 0.7, 0.7, 1).data());
00264 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Vector4f(0.8, 0.75, 0.6, 1).data());
00265 glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, Vector4f(1, 1, 1, 1).data());
00266 glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 64);
00267
00268 glEnable(GL_LIGHTING);
00269 glEnable(GL_LIGHT0);
00270 glEnable(GL_LIGHT1);
00271
00272 sFancySpheres.draw();
00273 glVertexPointer(3, GL_FLOAT, 0, mVertices[0].data());
00274 glNormalPointer(GL_FLOAT, 0, mNormals[0].data());
00275 glEnableClientState(GL_VERTEX_ARRAY);
00276 glEnableClientState(GL_NORMAL_ARRAY);
00277 glDrawArrays(GL_TRIANGLES, 0, mVertices.size());
00278 glDisableClientState(GL_VERTEX_ARRAY);
00279 glDisableClientState(GL_NORMAL_ARRAY);
00280
00281 glDisable(GL_LIGHTING);
00282 }
00283
00284 void RenderingWidget::animate()
00285 {
00286 m_alpha += double(m_timer.interval()) * 1e-3;
00287
00288 TimeLine::const_iterator hi = m_timeline.upper_bound(m_alpha);
00289 TimeLine::const_iterator lo = hi;
00290 --lo;
00291
00292 Frame currentFrame;
00293
00294 if(hi==m_timeline.end())
00295 {
00296
00297 currentFrame = lo->second;
00298 stopAnimation();
00299 }
00300 else if(hi==m_timeline.begin())
00301 {
00302
00303 currentFrame = hi->second;
00304 }
00305 else
00306 {
00307 float s = (m_alpha - lo->first)/(hi->first - lo->first);
00308 if (mLerpMode==LerpEulerAngles)
00309 currentFrame = ::lerpFrame<EulerAngles<float> >(s, lo->second, hi->second);
00310 else if (mLerpMode==LerpQuaternion)
00311 currentFrame = ::lerpFrame<Eigen::Quaternionf>(s, lo->second, hi->second);
00312 else
00313 {
00314 std::cerr << "Invalid rotation interpolation mode (abort)\n";
00315 exit(2);
00316 }
00317 currentFrame.orientation.coeffs().normalize();
00318 }
00319
00320 currentFrame.orientation = currentFrame.orientation.inverse();
00321 currentFrame.position = - (currentFrame.orientation * currentFrame.position);
00322 mCamera.setFrame(currentFrame);
00323
00324 updateGL();
00325 }
00326
00327 void RenderingWidget::keyPressEvent(QKeyEvent * e)
00328 {
00329 switch(e->key())
00330 {
00331 case Qt::Key_Up:
00332 mCamera.zoom(2);
00333 break;
00334 case Qt::Key_Down:
00335 mCamera.zoom(-2);
00336 break;
00337
00338 case Qt::Key_G:
00339 grabFrame();
00340 break;
00341
00342 case Qt::Key_C:
00343 m_timeline.clear();
00344 break;
00345
00346 case Qt::Key_R:
00347 resetCamera();
00348 break;
00349
00350 case Qt::Key_A:
00351 if (mAnimate)
00352 {
00353 stopAnimation();
00354 }
00355 else
00356 {
00357 m_alpha = 0;
00358 connect(&m_timer, SIGNAL(timeout()), this, SLOT(animate()));
00359 m_timer.start(1000/30);
00360 mAnimate = true;
00361 }
00362 break;
00363 default:
00364 break;
00365 }
00366
00367 updateGL();
00368 }
00369
00370 void RenderingWidget::stopAnimation()
00371 {
00372 disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(animate()));
00373 m_timer.stop();
00374 mAnimate = false;
00375 m_alpha = 0;
00376 }
00377
00378 void RenderingWidget::mousePressEvent(QMouseEvent* e)
00379 {
00380 mMouseCoords = Vector2i(e->pos().x(), e->pos().y());
00381 bool fly = (mNavMode==NavFly) || (e->modifiers()&Qt::ControlModifier);
00382 switch(e->button())
00383 {
00384 case Qt::LeftButton:
00385 if(fly)
00386 {
00387 mCurrentTrackingMode = TM_LOCAL_ROTATE;
00388 mTrackball.start(Trackball::Local);
00389 }
00390 else
00391 {
00392 mCurrentTrackingMode = TM_ROTATE_AROUND;
00393 mTrackball.start(Trackball::Around);
00394 }
00395 mTrackball.track(mMouseCoords);
00396 break;
00397 case Qt::MidButton:
00398 if(fly)
00399 mCurrentTrackingMode = TM_FLY_Z;
00400 else
00401 mCurrentTrackingMode = TM_ZOOM;
00402 break;
00403 case Qt::RightButton:
00404 mCurrentTrackingMode = TM_FLY_PAN;
00405 break;
00406 default:
00407 break;
00408 }
00409 }
00410 void RenderingWidget::mouseReleaseEvent(QMouseEvent*)
00411 {
00412 mCurrentTrackingMode = TM_NO_TRACK;
00413 updateGL();
00414 }
00415
00416 void RenderingWidget::mouseMoveEvent(QMouseEvent* e)
00417 {
00418
00419 if(mCurrentTrackingMode != TM_NO_TRACK)
00420 {
00421 float dx = float(e->x() - mMouseCoords.x()) / float(mCamera.vpWidth());
00422 float dy = - float(e->y() - mMouseCoords.y()) / float(mCamera.vpHeight());
00423
00424
00425 if(e->modifiers() & Qt::ShiftModifier)
00426 {
00427 dx *= 10.;
00428 dy *= 10.;
00429 }
00430
00431 switch(mCurrentTrackingMode)
00432 {
00433 case TM_ROTATE_AROUND:
00434 case TM_LOCAL_ROTATE:
00435 if (mRotationMode==RotationStable)
00436 {
00437
00438
00439 mTrackball.track(Vector2i(e->pos().x(), e->pos().y()));
00440 }
00441 else
00442 {
00443
00444
00445 Quaternionf q = AngleAxisf( dx*M_PI, Vector3f::UnitY())
00446 * AngleAxisf(-dy*M_PI, Vector3f::UnitX());
00447 if (mCurrentTrackingMode==TM_LOCAL_ROTATE)
00448 mCamera.localRotate(q);
00449 else
00450 mCamera.rotateAroundTarget(q);
00451 }
00452 break;
00453 case TM_ZOOM :
00454 mCamera.zoom(dy*100);
00455 break;
00456 case TM_FLY_Z :
00457 mCamera.localTranslate(Vector3f(0, 0, -dy*200));
00458 break;
00459 case TM_FLY_PAN :
00460 mCamera.localTranslate(Vector3f(dx*200, dy*200, 0));
00461 break;
00462 default:
00463 break;
00464 }
00465
00466 updateGL();
00467 }
00468
00469 mMouseCoords = Vector2i(e->pos().x(), e->pos().y());
00470 }
00471
00472 void RenderingWidget::paintGL()
00473 {
00474 glEnable(GL_DEPTH_TEST);
00475 glDisable(GL_CULL_FACE);
00476 glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
00477 glDisable(GL_COLOR_MATERIAL);
00478 glDisable(GL_BLEND);
00479 glDisable(GL_ALPHA_TEST);
00480 glDisable(GL_TEXTURE_1D);
00481 glDisable(GL_TEXTURE_2D);
00482 glDisable(GL_TEXTURE_3D);
00483
00484
00485 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
00486
00487 mCamera.activateGL();
00488
00489 drawScene();
00490 }
00491
00492 void RenderingWidget::initializeGL()
00493 {
00494 glClearColor(1., 1., 1., 0.);
00495 glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
00496 glDepthMask(GL_TRUE);
00497 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
00498
00499 mCamera.setPosition(Vector3f(-200, -200, -200));
00500 mCamera.setTarget(Vector3f(0, 0, 0));
00501 mInitFrame.orientation = mCamera.orientation().inverse();
00502 mInitFrame.position = mCamera.viewMatrix().translation();
00503 }
00504
00505 void RenderingWidget::resizeGL(int width, int height)
00506 {
00507 mCamera.setViewport(width,height);
00508 }
00509
00510 void RenderingWidget::setNavMode(int m)
00511 {
00512 mNavMode = NavMode(m);
00513 }
00514
00515 void RenderingWidget::setLerpMode(int m)
00516 {
00517 mLerpMode = LerpMode(m);
00518 }
00519
00520 void RenderingWidget::setRotationMode(int m)
00521 {
00522 mRotationMode = RotationMode(m);
00523 }
00524
00525 void RenderingWidget::resetCamera()
00526 {
00527 if (mAnimate)
00528 stopAnimation();
00529 m_timeline.clear();
00530 Frame aux0 = mCamera.frame();
00531 aux0.orientation = aux0.orientation.inverse();
00532 aux0.position = mCamera.viewMatrix().translation();
00533 m_timeline[0] = aux0;
00534
00535 Vector3f currentTarget = mCamera.target();
00536 mCamera.setTarget(Vector3f::Zero());
00537
00538
00539 Frame aux1 = mCamera.frame();
00540 aux1.orientation = aux1.orientation.inverse();
00541 aux1.position = mCamera.viewMatrix().translation();
00542 float duration = aux0.orientation.angularDistance(aux1.orientation) * 0.9;
00543 if (duration<0.1) duration = 0.1;
00544
00545
00546 aux1 = aux0.lerp(duration/2,mInitFrame);
00547
00548 aux1.orientation = aux1.orientation.inverse();
00549 aux1.position = - (aux1.orientation * aux1.position);
00550 mCamera.setFrame(aux1);
00551 mCamera.setTarget(Vector3f::Zero());
00552
00553
00554 aux1.orientation = aux1.orientation.inverse();
00555 aux1.position = mCamera.viewMatrix().translation();
00556 m_timeline[duration] = aux1;
00557
00558 m_timeline[2] = mInitFrame;
00559 m_alpha = 0;
00560 animate();
00561 connect(&m_timer, SIGNAL(timeout()), this, SLOT(animate()));
00562 m_timer.start(1000/30);
00563 mAnimate = true;
00564 }
00565
00566 QWidget* RenderingWidget::createNavigationControlWidget()
00567 {
00568 QWidget* panel = new QWidget();
00569 QVBoxLayout* layout = new QVBoxLayout();
00570
00571 {
00572 QPushButton* but = new QPushButton("reset");
00573 but->setToolTip("move the camera to initial position (with animation)");
00574 layout->addWidget(but);
00575 connect(but, SIGNAL(clicked()), this, SLOT(resetCamera()));
00576 }
00577 {
00578
00579 QGroupBox* box = new QGroupBox("navigation mode");
00580 QVBoxLayout* boxLayout = new QVBoxLayout;
00581 QButtonGroup* group = new QButtonGroup(panel);
00582 QRadioButton* but;
00583 but = new QRadioButton("turn around");
00584 but->setToolTip("look around an object");
00585 group->addButton(but, NavTurnAround);
00586 boxLayout->addWidget(but);
00587 but = new QRadioButton("fly");
00588 but->setToolTip("free navigation like a spaceship\n(this mode can also be enabled pressing the \"shift\" key)");
00589 group->addButton(but, NavFly);
00590 boxLayout->addWidget(but);
00591 group->button(mNavMode)->setChecked(true);
00592 connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setNavMode(int)));
00593 box->setLayout(boxLayout);
00594 layout->addWidget(box);
00595 }
00596 {
00597
00598 QGroupBox* box = new QGroupBox("rotation mode");
00599 QVBoxLayout* boxLayout = new QVBoxLayout;
00600 QButtonGroup* group = new QButtonGroup(panel);
00601 QRadioButton* but;
00602 but = new QRadioButton("stable trackball");
00603 group->addButton(but, RotationStable);
00604 boxLayout->addWidget(but);
00605 but->setToolTip("use the stable trackball implementation mapping\nthe 2D coordinates to 3D points on a sphere");
00606 but = new QRadioButton("standard rotation");
00607 group->addButton(but, RotationStandard);
00608 boxLayout->addWidget(but);
00609 but->setToolTip("standard approach mapping the x and y displacements\nas rotations around the camera's X and Y axes");
00610 group->button(mRotationMode)->setChecked(true);
00611 connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setRotationMode(int)));
00612 box->setLayout(boxLayout);
00613 layout->addWidget(box);
00614 }
00615 {
00616
00617 QGroupBox* box = new QGroupBox("spherical interpolation");
00618 QVBoxLayout* boxLayout = new QVBoxLayout;
00619 QButtonGroup* group = new QButtonGroup(panel);
00620 QRadioButton* but;
00621 but = new QRadioButton("quaternion slerp");
00622 group->addButton(but, LerpQuaternion);
00623 boxLayout->addWidget(but);
00624 but->setToolTip("use quaternion spherical interpolation\nto interpolate orientations");
00625 but = new QRadioButton("euler angles");
00626 group->addButton(but, LerpEulerAngles);
00627 boxLayout->addWidget(but);
00628 but->setToolTip("use Euler angles to interpolate orientations");
00629 group->button(mNavMode)->setChecked(true);
00630 connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setLerpMode(int)));
00631 box->setLayout(boxLayout);
00632 layout->addWidget(box);
00633 }
00634 layout->addItem(new QSpacerItem(0,0,QSizePolicy::Minimum,QSizePolicy::Expanding));
00635 panel->setLayout(layout);
00636 return panel;
00637 }
00638
00639 QuaternionDemo::QuaternionDemo()
00640 {
00641 mRenderingWidget = new RenderingWidget();
00642 setCentralWidget(mRenderingWidget);
00643
00644 QDockWidget* panel = new QDockWidget("navigation", this);
00645 panel->setAllowedAreas((QFlags<Qt::DockWidgetArea>)(Qt::RightDockWidgetArea | Qt::LeftDockWidgetArea));
00646 addDockWidget(Qt::RightDockWidgetArea, panel);
00647 panel->setWidget(mRenderingWidget->createNavigationControlWidget());
00648 }
00649
00650 int main(int argc, char *argv[])
00651 {
00652 std::cout << "Navigation:\n";
00653 std::cout << " left button: rotate around the target\n";
00654 std::cout << " middle button: zoom\n";
00655 std::cout << " left button + ctrl quake rotate (rotate around camera position)\n";
00656 std::cout << " middle button + ctrl walk (progress along camera's z direction)\n";
00657 std::cout << " left button: pan (translate in the XY camera's plane)\n\n";
00658 std::cout << "R : move the camera to initial position\n";
00659 std::cout << "A : start/stop animation\n";
00660 std::cout << "C : clear the animation\n";
00661 std::cout << "G : add a key frame\n";
00662
00663 QApplication app(argc, argv);
00664 QuaternionDemo demo;
00665 demo.resize(600,500);
00666 demo.show();
00667 return app.exec();
00668 }
00669
00670 #include "quaternion_demo.moc"
00671