|
|
// 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 = internal::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"
|