123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 |
- // This file is part of Eigen, a lightweight C++ template library
- // for linear algebra.
- //
- // Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr>
- //
- // 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 "quaternion_demo.h"
- #include "icosphere.h"
- #include <Eigen/Geometry>
- #include <Eigen/QR>
- #include <Eigen/LU>
- #include <iostream>
- #include <QEvent>
- #include <QMouseEvent>
- #include <QInputDialog>
- #include <QGridLayout>
- #include <QButtonGroup>
- #include <QRadioButton>
- #include <QDockWidget>
- #include <QPushButton>
- #include <QGroupBox>
- using namespace Eigen;
- class FancySpheres
- {
- public:
- EIGEN_MAKE_ALIGNED_OPERATOR_NEW
-
- FancySpheres()
- {
- const int levels = 4;
- const float scale = 0.33;
- float radius = 100;
- std::vector<int> parents;
- // leval 0
- mCenters.push_back(Vector3f::Zero());
- parents.push_back(-1);
- mRadii.push_back(radius);
- // generate level 1 using icosphere vertices
- radius *= 0.45;
- {
- float dist = mRadii[0]*0.9;
- for (int i=0; i<12; ++i)
- {
- mCenters.push_back(mIcoSphere.vertices()[i] * dist);
- mRadii.push_back(radius);
- parents.push_back(0);
- }
- }
- 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;
- for (int l=1; l<levels; l++)
- {
- radius *= scale;
- int end = mCenters.size();
- for (int i=start; i<end; ++i)
- {
- Vector3f c = mCenters[i];
- Vector3f ax0 = (c - mCenters[parents[i]]).normalized();
- Vector3f ax1 = ax0.unitOrthogonal();
- Quaternionf q;
- q.setFromTwoVectors(Vector3f::UnitZ(), ax0);
- Affine3f t = Translation3f(c) * q * Scaling(mRadii[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)
- * (mRadii[i] + radius*0.8);
- mCenters.push_back(newC);
- mRadii.push_back(radius);
- parents.push_back(i);
- }
- }
- start = end;
- }
- }
- void draw()
- {
- int end = mCenters.size();
- glEnable(GL_NORMALIZE);
- for (int i=0; i<end; ++i)
- {
- Affine3f t = Translation3f(mCenters[i]) * Scaling(mRadii[i]);
- gpu.pushMatrix(GL_MODELVIEW);
- gpu.multMatrix(t.matrix(),GL_MODELVIEW);
- mIcoSphere.draw(2);
- gpu.popMatrix(GL_MODELVIEW);
- }
- glDisable(GL_NORMALIZE);
- }
- protected:
- std::vector<Vector3f> mCenters;
- std::vector<float> mRadii;
- IcoSphere mIcoSphere;
- };
- // generic linear interpolation method
- template<typename T> T lerp(float t, const T& a, const T& b)
- {
- return a*(1-t) + b*t;
- }
- // quaternion slerp
- template<> Quaternionf lerp(float t, const Quaternionf& a, const Quaternionf& b)
- { return a.slerp(t,b); }
- // linear interpolation of a frame using the type OrientationType
- // to perform the interpolation of the orientations
- template<typename OrientationType>
- inline static Frame lerpFrame(float alpha, const Frame& a, const Frame& b)
- {
- return Frame(lerp(alpha,a.position,b.position),
- Quaternionf(lerp(alpha,OrientationType(a.orientation),OrientationType(b.orientation))));
- }
- template<typename _Scalar> class EulerAngles
- {
- public:
- enum { Dim = 3 };
- typedef _Scalar Scalar;
- typedef Matrix<Scalar,3,3> Matrix3;
- typedef Matrix<Scalar,3,1> Vector3;
- typedef Quaternion<Scalar> QuaternionType;
- protected:
- Vector3 m_angles;
- public:
- EulerAngles() {}
- inline EulerAngles(Scalar a0, Scalar a1, Scalar a2) : m_angles(a0, a1, a2) {}
- inline EulerAngles(const QuaternionType& q) { *this = q; }
- const Vector3& coeffs() const { return m_angles; }
- Vector3& coeffs() { return m_angles; }
- EulerAngles& operator=(const QuaternionType& q)
- {
- Matrix3 m = q.toRotationMatrix();
- return *this = m;
- }
- EulerAngles& operator=(const Matrix3& m)
- {
- // mat = cy*cz -cy*sz sy
- // cz*sx*sy+cx*sz cx*cz-sx*sy*sz -cy*sx
- // -cx*cz*sy+sx*sz cz*sx+cx*sy*sz cx*cy
- m_angles.coeffRef(1) = std::asin(m.coeff(0,2));
- m_angles.coeffRef(0) = std::atan2(-m.coeff(1,2),m.coeff(2,2));
- m_angles.coeffRef(2) = std::atan2(-m.coeff(0,1),m.coeff(0,0));
- return *this;
- }
- Matrix3 toRotationMatrix(void) const
- {
- Vector3 c = m_angles.array().cos();
- Vector3 s = m_angles.array().sin();
- Matrix3 res;
- res << c.y()*c.z(), -c.y()*s.z(), s.y(),
- c.z()*s.x()*s.y()+c.x()*s.z(), c.x()*c.z()-s.x()*s.y()*s.z(), -c.y()*s.x(),
- -c.x()*c.z()*s.y()+s.x()*s.z(), c.z()*s.x()+c.x()*s.y()*s.z(), c.x()*c.y();
- return res;
- }
- operator QuaternionType() { return QuaternionType(toRotationMatrix()); }
- };
- // Euler angles slerp
- template<> EulerAngles<float> lerp(float t, const EulerAngles<float>& a, const EulerAngles<float>& b)
- {
- EulerAngles<float> res;
- res.coeffs() = lerp(t, a.coeffs(), b.coeffs());
- return res;
- }
- RenderingWidget::RenderingWidget()
- {
- mAnimate = false;
- mCurrentTrackingMode = TM_NO_TRACK;
- mNavMode = NavTurnAround;
- mLerpMode = LerpQuaternion;
- mRotationMode = RotationStable;
- mTrackball.setCamera(&mCamera);
- // required to capture key press events
- setFocusPolicy(Qt::ClickFocus);
- }
- 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 RenderingWidget", "time value: ",
- t, 0, 1e3, 1, &ok);
- if (ok)
- {
- Frame aux;
- aux.orientation = mCamera.viewMatrix().linear();
- aux.position = mCamera.viewMatrix().translation();
- m_timeline[t] = aux;
- }
- }
- void RenderingWidget::drawScene()
- {
- static FancySpheres sFancySpheres;
- 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 = std::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);
- sFancySpheres.draw();
- 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 RenderingWidget::animate()
- {
- m_alpha += double(m_timer.interval()) * 1e-3;
- TimeLine::const_iterator hi = m_timeline.upper_bound(m_alpha);
- TimeLine::const_iterator lo = hi;
- --lo;
- Frame currentFrame;
- if(hi==m_timeline.end())
- {
- // end
- currentFrame = lo->second;
- stopAnimation();
- }
- else if(hi==m_timeline.begin())
- {
- // start
- currentFrame = hi->second;
- }
- else
- {
- float s = (m_alpha - lo->first)/(hi->first - lo->first);
- if (mLerpMode==LerpEulerAngles)
- currentFrame = ::lerpFrame<EulerAngles<float> >(s, lo->second, hi->second);
- else if (mLerpMode==LerpQuaternion)
- currentFrame = ::lerpFrame<Eigen::Quaternionf>(s, lo->second, hi->second);
- else
- {
- std::cerr << "Invalid rotation interpolation mode (abort)\n";
- exit(2);
- }
- currentFrame.orientation.coeffs().normalize();
- }
- currentFrame.orientation = currentFrame.orientation.inverse();
- currentFrame.position = - (currentFrame.orientation * currentFrame.position);
- mCamera.setFrame(currentFrame);
- updateGL();
- }
- void RenderingWidget::keyPressEvent(QKeyEvent * e)
- {
- switch(e->key())
- {
- case Qt::Key_Up:
- mCamera.zoom(2);
- break;
- case Qt::Key_Down:
- mCamera.zoom(-2);
- break;
- // add a frame
- case Qt::Key_G:
- grabFrame();
- break;
- // clear the time line
- case Qt::Key_C:
- m_timeline.clear();
- break;
- // move the camera to initial pos
- case Qt::Key_R:
- resetCamera();
- break;
- // start/stop the animation
- case Qt::Key_A:
- if (mAnimate)
- {
- stopAnimation();
- }
- else
- {
- m_alpha = 0;
- connect(&m_timer, SIGNAL(timeout()), this, SLOT(animate()));
- m_timer.start(1000/30);
- mAnimate = true;
- }
- break;
- default:
- break;
- }
- updateGL();
- }
- void RenderingWidget::stopAnimation()
- {
- disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(animate()));
- m_timer.stop();
- mAnimate = false;
- m_alpha = 0;
- }
- 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(fly)
- {
- mCurrentTrackingMode = TM_LOCAL_ROTATE;
- mTrackball.start(Trackball::Local);
- }
- else
- {
- mCurrentTrackingMode = TM_ROTATE_AROUND;
- mTrackball.start(Trackball::Around);
- }
- mTrackball.track(mMouseCoords);
- break;
- case Qt::MidButton:
- if(fly)
- mCurrentTrackingMode = TM_FLY_Z;
- else
- mCurrentTrackingMode = TM_ZOOM;
- break;
- case Qt::RightButton:
- mCurrentTrackingMode = TM_FLY_PAN;
- break;
- default:
- break;
- }
- }
- void RenderingWidget::mouseReleaseEvent(QMouseEvent*)
- {
- mCurrentTrackingMode = TM_NO_TRACK;
- updateGL();
- }
- void RenderingWidget::mouseMoveEvent(QMouseEvent* e)
- {
- // tracking
- 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());
- // speedup the transformations
- if(e->modifiers() & Qt::ShiftModifier)
- {
- dx *= 10.;
- dy *= 10.;
- }
- switch(mCurrentTrackingMode)
- {
- case TM_ROTATE_AROUND:
- case TM_LOCAL_ROTATE:
- if (mRotationMode==RotationStable)
- {
- // use the stable trackball implementation mapping
- // the 2D coordinates to 3D points on a sphere.
- mTrackball.track(Vector2i(e->pos().x(), e->pos().y()));
- }
- else
- {
- // standard approach mapping the x and y displacements as rotations
- // around the camera's X and Y axes.
- Quaternionf q = AngleAxisf( dx*M_PI, Vector3f::UnitY())
- * AngleAxisf(-dy*M_PI, Vector3f::UnitX());
- if (mCurrentTrackingMode==TM_LOCAL_ROTATE)
- mCamera.localRotate(q);
- else
- mCamera.rotateAroundTarget(q);
- }
- break;
- case TM_ZOOM :
- mCamera.zoom(dy*100);
- break;
- case TM_FLY_Z :
- mCamera.localTranslate(Vector3f(0, 0, -dy*200));
- break;
- case TM_FLY_PAN :
- mCamera.localTranslate(Vector3f(dx*200, dy*200, 0));
- break;
- default:
- break;
- }
- updateGL();
- }
- mMouseCoords = Vector2i(e->pos().x(), e->pos().y());
- }
- void RenderingWidget::paintGL()
- {
- glEnable(GL_DEPTH_TEST);
- glDisable(GL_CULL_FACE);
- glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
- glDisable(GL_COLOR_MATERIAL);
- glDisable(GL_BLEND);
- glDisable(GL_ALPHA_TEST);
- glDisable(GL_TEXTURE_1D);
- glDisable(GL_TEXTURE_2D);
- glDisable(GL_TEXTURE_3D);
- // Clear buffers
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- mCamera.activateGL();
- drawScene();
- }
- 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);
- mCamera.setPosition(Vector3f(-200, -200, -200));
- mCamera.setTarget(Vector3f(0, 0, 0));
- mInitFrame.orientation = mCamera.orientation().inverse();
- mInitFrame.position = mCamera.viewMatrix().translation();
- }
- 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 duration = aux0.orientation.angularDistance(aux1.orientation) * 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 the 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();
- {
- QPushButton* but = new QPushButton("reset");
- but->setToolTip("move the camera to initial position (with animation)");
- layout->addWidget(but);
- connect(but, SIGNAL(clicked()), this, SLOT(resetCamera()));
- }
- {
- // navigation mode
- QGroupBox* box = new QGroupBox("navigation mode");
- QVBoxLayout* boxLayout = new QVBoxLayout;
- QButtonGroup* group = new QButtonGroup(panel);
- QRadioButton* but;
- but = new QRadioButton("turn around");
- but->setToolTip("look around an object");
- group->addButton(but, NavTurnAround);
- boxLayout->addWidget(but);
- but = new QRadioButton("fly");
- but->setToolTip("free navigation like a spaceship\n(this mode can also be enabled pressing the \"shift\" key)");
- group->addButton(but, NavFly);
- boxLayout->addWidget(but);
- group->button(mNavMode)->setChecked(true);
- connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setNavMode(int)));
- box->setLayout(boxLayout);
- layout->addWidget(box);
- }
- {
- // track ball, rotation mode
- QGroupBox* box = new QGroupBox("rotation mode");
- QVBoxLayout* boxLayout = new QVBoxLayout;
- QButtonGroup* group = new QButtonGroup(panel);
- QRadioButton* but;
- but = new QRadioButton("stable trackball");
- group->addButton(but, RotationStable);
- boxLayout->addWidget(but);
- but->setToolTip("use the stable trackball implementation mapping\nthe 2D coordinates to 3D points on a sphere");
- but = new QRadioButton("standard rotation");
- group->addButton(but, RotationStandard);
- boxLayout->addWidget(but);
- but->setToolTip("standard approach mapping the x and y displacements\nas rotations around the camera's X and Y axes");
- group->button(mRotationMode)->setChecked(true);
- connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setRotationMode(int)));
- box->setLayout(boxLayout);
- layout->addWidget(box);
- }
- {
- // interpolation mode
- QGroupBox* box = new QGroupBox("spherical interpolation");
- QVBoxLayout* boxLayout = new QVBoxLayout;
- QButtonGroup* group = new QButtonGroup(panel);
- QRadioButton* but;
- but = new QRadioButton("quaternion slerp");
- group->addButton(but, LerpQuaternion);
- boxLayout->addWidget(but);
- but->setToolTip("use quaternion spherical interpolation\nto interpolate orientations");
- but = new QRadioButton("euler angles");
- group->addButton(but, LerpEulerAngles);
- boxLayout->addWidget(but);
- but->setToolTip("use Euler angles to interpolate orientations");
- group->button(mNavMode)->setChecked(true);
- connect(group, SIGNAL(buttonClicked(int)), this, SLOT(setLerpMode(int)));
- box->setLayout(boxLayout);
- layout->addWidget(box);
- }
- 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[])
- {
- std::cout << "Navigation:\n";
- std::cout << " left button: rotate around the target\n";
- std::cout << " middle button: zoom\n";
- std::cout << " left button + ctrl quake rotate (rotate around camera position)\n";
- std::cout << " middle button + ctrl walk (progress along camera's z direction)\n";
- std::cout << " left button: pan (translate in the XY camera's plane)\n\n";
- std::cout << "R : move the camera to initial position\n";
- std::cout << "A : start/stop animation\n";
- std::cout << "C : clear the animation\n";
- std::cout << "G : add a key frame\n";
- QApplication app(argc, argv);
- QuaternionDemo demo;
- demo.resize(600,500);
- demo.show();
- return app.exec();
- }
- #include "quaternion_demo.moc"
|