diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be80295 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Generated Bin and debug +debug/ +object_script.*.Debug +object_script.*.Release + +# C++ objects and libs + +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.dll +*.dylib + +# Qt-es + +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +qrc_*.cpp +ui_*.h +Makefile* +*build-* + +# QtCreator + +*.autosave + +# QtCtreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCtreator CMake +CMakeLists.txt.user* + diff --git a/QGLViewer/ImageInterface.ui b/QGLViewer/ImageInterface.ui new file mode 100644 index 0000000..db85b69 --- /dev/null +++ b/QGLViewer/ImageInterface.ui @@ -0,0 +1,297 @@ + + + ImageInterface + + + + 0 + 0 + 298 + 204 + + + + Image settings + + + + 6 + + + 9 + + + + + 6 + + + 0 + + + + + Width + + + + + + + Width of the image (in pixels) + + + px + + + 1 + + + 32000 + + + + + + + Qt::Horizontal + + + + 20 + 22 + + + + + + + + Height + + + + + + + Height of the image (in pixels) + + + px + + + 1 + + + 32000 + + + + + + + + + 6 + + + 0 + + + + + Image quality + + + + + + + Between 0 (smallest files) and 100 (highest quality) + + + 0 + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 6 + + + 0 + + + + + Oversampling + + + + + + + Antialiases image (when larger then 1.0) + + + x + + + 1 + + + 0.100000000000000 + + + 64.000000000000000 + + + 1.000000000000000 + + + 1.000000000000000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Use white as background color + + + Use white background + + + + + + + When image aspect ratio differs from viewer's one, expand frustum as needed. Fits inside current frustum otherwise. + + + Expand frustum if needed + + + + + + + Qt::Vertical + + + + 20 + 16 + + + + + + + + 6 + + + 0 + + + + + Qt::Horizontal + + + + 131 + 31 + + + + + + + + OK + + + + + + + Cancel + + + + + + + + + + + okButton + clicked() + ImageInterface + accept() + + + 135 + 184 + + + 96 + 254 + + + + + cancelButton + clicked() + ImageInterface + reject() + + + 226 + 184 + + + 179 + 282 + + + + + diff --git a/QGLViewer/VRenderInterface.ui b/QGLViewer/VRenderInterface.ui new file mode 100644 index 0000000..e6e1a60 --- /dev/null +++ b/QGLViewer/VRenderInterface.ui @@ -0,0 +1,217 @@ + + + VRenderInterface + + + + 0 + 0 + 309 + 211 + + + + Vectorial rendering options + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Hidden polygons are also included in the output (usually twice bigger) + + + Include hidden parts + + + + + + + Back faces (non clockwise point ordering) are removed from the output + + + Cull back faces + + + + + + + Black and white rendering + + + Black and white + + + + + + + Use current background color instead of white + + + Color background + + + + + + + Fit output bounding box to current display + + + Tighten bounding box + + + + + + + 11 + + + 11 + + + 11 + + + 11 + + + + + Polygon depth sorting method + + + Sort method: + + + + + + + Polygon depth sorting method + + + 3 + + + + No sorting + + + + + BSP + + + + + Topological + + + + + Advanced topological + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 31 + 41 + + + + + + + + + + Save + + + + + + + Cancel + + + + + + + + + + SaveButton + CancelButton + includeHidden + cullBackFaces + blackAndWhite + colorBackground + tightenBBox + sortMethod + + + + + SaveButton + released() + VRenderInterface + accept() + + + 16 + 210 + + + 35 + 176 + + + + + CancelButton + released() + VRenderInterface + reject() + + + 225 + 198 + + + 211 + 180 + + + + + diff --git a/QGLViewer/camera.cpp b/QGLViewer/camera.cpp new file mode 100644 index 0000000..62474f4 --- /dev/null +++ b/QGLViewer/camera.cpp @@ -0,0 +1,2073 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "domUtils.h" +#include "camera.h" +#include "qglviewer.h" +#include "manipulatedCameraFrame.h" +#include + +using namespace std; +using namespace qglviewer; + +namespace { + + // Replacement for gluUnProject that uses QVector3D::unproject() + Vec unProject(const Vec& src, + const GLdouble matProjection[], + const GLdouble matModelView[], + const GLint viewport[]) + { + // Convert matrices to QMatrix4x4 + QMatrix4x4 projectionMatrix; + QMatrix4x4 modelViewMatrix; + for (int row=0; row<4; ++row) + { + for (int col=0; col<4; ++col) + { + int index = row + col*4; + projectionMatrix(row, col) = matProjection[index]; + modelViewMatrix(row, col) = matModelView[index]; + } + } + + // Unproject point + QVector3D point(src.x, src.y, src.z); + QRect vp(viewport[0], viewport[1], viewport[2], viewport[3]); + QVector3D result = point.unproject(modelViewMatrix, projectionMatrix, vp); + + return Vec(result.x(), result.y(), result.z()); + } + + // Replacement for gluProject that uses QVector3D::project() + Vec project(const Vec& src, + const GLdouble matProjection[], + const GLdouble matModelView[], + const GLint viewport[]) + { + // Convert matrices to QMatrix4x4 + QMatrix4x4 projectionMatrix; + QMatrix4x4 modelViewMatrix; + for (int row=0; row<4; ++row) + { + for (int col=0; col<4; ++col) + { + int index = row + col*4; + projectionMatrix(row, col) = matProjection[index]; + modelViewMatrix(row, col) = matModelView[index]; + } + } + + // Unproject point + QVector3D point(src.x, src.y, src.z); + QRect vp(viewport[0], viewport[1], viewport[2], viewport[3]); + QVector3D result = point.project(modelViewMatrix, projectionMatrix, vp); + + return Vec(result.x(), result.y(), result.z()); + } +} + +/*! Default constructor. + + sceneCenter() is set to (0,0,0) and sceneRadius() is set to 1.0. type() is Camera::PERSPECTIVE, + with a \c M_PI/4 fieldOfView(). + + See IODistance(), physicalDistanceToScreen(), physicalScreenWidth() and focusDistance() + documentations for default stereo parameter values. */ +Camera::Camera() + : frame_(NULL), fieldOfView_(M_PI/4.0), modelViewMatrixIsUpToDate_(false), projectionMatrixIsUpToDate_(false) +{ + // #CONNECTION# Camera copy constructor + interpolationKfi_ = new KeyFrameInterpolator; + // Requires the interpolationKfi_ + setFrame(new ManipulatedCameraFrame()); + + // #CONNECTION# All these default values identical in initFromDOMElement. + + // Requires fieldOfView() to define focusDistance() + setSceneRadius(1.0); + + // Initial value (only scaled after this) + orthoCoef_ = tan(fieldOfView()/2.0); + + // Also defines the pivotPoint(), which changes orthoCoef_. Requires a frame(). + setSceneCenter(Vec(0.0, 0.0, 0.0)); + + // Requires fieldOfView() when called with ORTHOGRAPHIC. Attention to projectionMatrix_ below. + setType(PERSPECTIVE); + + // #CONNECTION# initFromDOMElement default values + setZNearCoefficient(0.005); + setZClippingCoefficient(sqrt(3.0)); + + // Dummy values + setScreenWidthAndHeight(600, 400); + + // Stereo parameters + setIODistance(0.062); + setPhysicalScreenWidth(0.5); + // focusDistance is set from setFieldOfView() + + // #CONNECTION# Camera copy constructor + for (unsigned short j=0; j<16; ++j) + { + modelViewMatrix_[j] = ((j%5 == 0) ? 1.0 : 0.0); + // #CONNECTION# computeProjectionMatrix() is lazy and assumes 0.0 almost everywhere. + projectionMatrix_[j] = 0.0; + } + computeProjectionMatrix(); +} + +/*! Virtual destructor. + + The frame() is deleted, but the different keyFrameInterpolator() are \e not deleted (in case they + are shared). */ +Camera::~Camera() +{ + delete frame_; + delete interpolationKfi_; +} + + +/*! Copy constructor. Performs a deep copy using operator=(). */ +Camera::Camera(const Camera& camera) + : QObject(), frame_(NULL) +{ + // #CONNECTION# Camera constructor + interpolationKfi_ = new KeyFrameInterpolator; + // Requires the interpolationKfi_ + setFrame(new ManipulatedCameraFrame(*camera.frame())); + + for (unsigned short j=0; j<16; ++j) + { + modelViewMatrix_[j] = ((j%5 == 0) ? 1.0 : 0.0); + // #CONNECTION# computeProjectionMatrix() is lazy and assumes 0.0 almost everywhere. + projectionMatrix_[j] = 0.0; + } + + (*this)=camera; +} + +/*! Equal operator. + + All the parameters of \p camera are copied. The frame() pointer is not modified, but its + Frame::position() and Frame::orientation() are set to those of \p camera. + + \attention The Camera screenWidth() and screenHeight() are set to those of \p camera. If your + Camera is associated with a QGLViewer, you should update these value after the call to this method: + \code + *(camera()) = otherCamera; + camera()->setScreenWidthAndHeight(width(), height()); + \endcode + The same applies to sceneCenter() and sceneRadius(), if needed. */ +Camera& Camera::operator=(const Camera& camera) +{ + setScreenWidthAndHeight(camera.screenWidth(), camera.screenHeight()); + setFieldOfView(camera.fieldOfView()); + setSceneRadius(camera.sceneRadius()); + setSceneCenter(camera.sceneCenter()); + setZNearCoefficient(camera.zNearCoefficient()); + setZClippingCoefficient(camera.zClippingCoefficient()); + setType(camera.type()); + + // Stereo parameters + setIODistance(camera.IODistance()); + setFocusDistance(camera.focusDistance()); + setPhysicalScreenWidth(camera.physicalScreenWidth()); + + orthoCoef_ = camera.orthoCoef_; + projectionMatrixIsUpToDate_ = false; + + // frame_ and interpolationKfi_ pointers are not shared. + frame_->setReferenceFrame(NULL); + frame_->setPosition(camera.position()); + frame_->setOrientation(camera.orientation()); + + interpolationKfi_->resetInterpolation(); + + kfi_ = camera.kfi_; + + computeProjectionMatrix(); + computeModelViewMatrix(); + + return *this; +} + +/*! Sets Camera screenWidth() and screenHeight() (expressed in pixels). + +You should not call this method when the Camera is associated with a QGLViewer, since the +latter automatically updates these values when it is resized (hence overwritting your values). + +Non-positive dimension are silently replaced by a 1 pixel value to ensure frustrum coherence. + +If your Camera is used without a QGLViewer (offscreen rendering, shadow maps), use setAspectRatio() +instead to define the projection matrix. */ +void Camera::setScreenWidthAndHeight(int width, int height) +{ + // Prevent negative and zero dimensions that would cause divisions by zero. + screenWidth_ = width > 0 ? width : 1; + screenHeight_ = height > 0 ? height : 1; + projectionMatrixIsUpToDate_ = false; +} + +/*! Returns the near clipping plane distance used by the Camera projection matrix. + + The clipping planes' positions depend on the sceneRadius() and sceneCenter() rather than being fixed + small-enough and large-enough values. A good scene dimension approximation will hence result in an + optimal precision of the z-buffer. + + The near clipping plane is positioned at a distance equal to zClippingCoefficient() * sceneRadius() + in front of the sceneCenter(): + \code + zNear = distanceToSceneCenter() - zClippingCoefficient()*sceneRadius(); + \endcode + + In order to prevent negative or too small zNear() values (which would degrade the z precision), + zNearCoefficient() is used when the Camera is inside the sceneRadius() sphere: + \code + const qreal zMin = zNearCoefficient() * zClippingCoefficient() * sceneRadius(); + if (zNear < zMin) + zNear = zMin; + // With an ORTHOGRAPHIC type, the value is simply clamped to 0.0 + \endcode + + See also the zFar(), zClippingCoefficient() and zNearCoefficient() documentations. + + If you need a completely different zNear computation, overload the zNear() and zFar() methods in a + new class that publicly inherits from Camera and use QGLViewer::setCamera(): + \code + class myCamera :: public qglviewer::Camera + { + virtual qreal Camera::zNear() const { return 0.001; }; + virtual qreal Camera::zFar() const { return 100.0; }; + } + \endcode + + See the standardCamera example for an application. + + \attention The value is always positive although the clipping plane is positioned at a negative z + value in the Camera coordinate system. This follows the \c gluPerspective standard. */ +qreal Camera::zNear() const +{ + const qreal zNearScene = zClippingCoefficient() * sceneRadius(); + qreal z = distanceToSceneCenter() - zNearScene; + + // Prevents negative or null zNear values. + const qreal zMin = zNearCoefficient() * zNearScene; + if (z < zMin) + switch (type()) + { + case Camera::PERSPECTIVE : z = zMin; break; + case Camera::ORTHOGRAPHIC : z = 0.0; break; + } + return z; +} + +/*! Returns the far clipping plane distance used by the Camera projection matrix. + +The far clipping plane is positioned at a distance equal to zClippingCoefficient() * sceneRadius() +behind the sceneCenter(): +\code +zFar = distanceToSceneCenter() + zClippingCoefficient()*sceneRadius(); +\endcode + +See the zNear() documentation for details. */ +qreal Camera::zFar() const +{ + return distanceToSceneCenter() + zClippingCoefficient() * sceneRadius(); +} + + +/*! Sets the vertical fieldOfView() of the Camera (in radians). + +Note that focusDistance() is set to sceneRadius() / tan(fieldOfView()/2) by this method. */ +void Camera::setFieldOfView(qreal fov) { + fieldOfView_ = fov; + setFocusDistance(sceneRadius() / tan(fov/2.0)); + projectionMatrixIsUpToDate_ = false; +} + +/*! Defines the Camera type(). + +Changing the camera Type alters the viewport and the objects' sizes can be changed. +This method garantees that the two frustum match in a plane normal to viewDirection(), passing through the pivotPoint(). + +Prefix the type with \c Camera if needed, as in: +\code +camera()->setType(Camera::ORTHOGRAPHIC); +// or even qglviewer::Camera::ORTHOGRAPHIC if you do not use namespace +\endcode */ +void Camera::setType(Type type) +{ + // make ORTHOGRAPHIC frustum fit PERSPECTIVE (at least in plane normal to viewDirection(), passing + // through RAP). Done only when CHANGING type since orthoCoef_ may have been changed with a + // setPivotPoint() in the meantime. + if ( (type == Camera::ORTHOGRAPHIC) && (type_ == Camera::PERSPECTIVE) ) + orthoCoef_ = tan(fieldOfView()/2.0); + type_ = type; + projectionMatrixIsUpToDate_ = false; +} + +/*! Sets the Camera frame(). + +If you want to move the Camera, use setPosition() and setOrientation() or one of the Camera +positioning methods (lookAt(), fitSphere(), showEntireScene()...) instead. + +If you want to save the Camera position(), there's no need to call this method either. Use +addKeyFrameToPath() and playPath() instead. + +This method is actually mainly useful if you derive the ManipulatedCameraFrame class and want to +use an instance of your new class to move the Camera. + +A \c NULL \p mcf pointer will silently be ignored. The calling method is responsible for +deleting the previous frame() pointer if needed in order to prevent memory leaks. */ +void Camera::setFrame(ManipulatedCameraFrame* const mcf) +{ + if (!mcf) + return; + + if (frame_) { + disconnect(frame_, SIGNAL(modified()), this, SLOT(onFrameModified())); + } + + frame_ = mcf; + interpolationKfi_->setFrame(frame()); + + connect(frame_, SIGNAL(modified()), this, SLOT(onFrameModified())); + onFrameModified(); +} + +/*! Returns the distance from the Camera center to sceneCenter(), projected along the Camera Z axis. + Used by zNear() and zFar() to optimize the Z range. */ +qreal Camera::distanceToSceneCenter() const +{ + return fabs((frame()->coordinatesOf(sceneCenter())).z); +} + + +/*! Returns the \p halfWidth and \p halfHeight of the Camera orthographic frustum. + + These values are only valid and used when the Camera is of type() Camera::ORTHOGRAPHIC. They are + expressed in OpenGL units and are used by loadProjectionMatrix() to define the projection matrix + using: + \code + glOrtho( -halfWidth, halfWidth, -halfHeight, halfHeight, zNear(), zFar() ) + \endcode + + These values are proportional to the Camera (z projected) distance to the pivotPoint(). + When zooming on the object, the Camera is translated forward \e and its frustum is narrowed, making + the object appear bigger on screen, as intuitively expected. + + Overload this method to change this behavior if desired, as is done in the + standardCamera example. */ +void Camera::getOrthoWidthHeight(GLdouble& halfWidth, GLdouble& halfHeight) const +{ + const qreal dist = orthoCoef_ * fabs(cameraCoordinatesOf(pivotPoint()).z); + //#CONNECTION# fitScreenRegion + halfWidth = dist * ((aspectRatio() < 1.0) ? 1.0 : aspectRatio()); + halfHeight = dist * ((aspectRatio() < 1.0) ? 1.0/aspectRatio() : 1.0); +} + + +/*! Computes the projection matrix associated with the Camera. + + If type() is Camera::PERSPECTIVE, defines a \c GL_PROJECTION matrix similar to what would \c + gluPerspective() do using the fieldOfView(), window aspectRatio(), zNear() and zFar() parameters. + + If type() is Camera::ORTHOGRAPHIC, the projection matrix is as what \c glOrtho() would do. + Frustum's width and height are set using getOrthoWidthHeight(). + + Both types use zNear() and zFar() to place clipping planes. These values are determined from + sceneRadius() and sceneCenter() so that they best fit the scene size. + + Use getProjectionMatrix() to retrieve this matrix. Overload loadProjectionMatrix() if you want your + Camera to use an exotic projection matrix. + + \note You must call this method if your Camera is not associated with a QGLViewer and is used for + offscreen computations (using (un)projectedCoordinatesOf() for instance). loadProjectionMatrix() + does it otherwise. */ +void Camera::computeProjectionMatrix() const +{ + if (projectionMatrixIsUpToDate_) return; + + const qreal ZNear = zNear(); + const qreal ZFar = zFar(); + + switch (type()) + { + case Camera::PERSPECTIVE: + { + // #CONNECTION# all non null coefficients were set to 0.0 in constructor. + const qreal f = 1.0/tan(fieldOfView()/2.0); + projectionMatrix_[0] = f/aspectRatio(); + projectionMatrix_[5] = f; + projectionMatrix_[10] = (ZNear + ZFar) / (ZNear - ZFar); + projectionMatrix_[11] = -1.0; + projectionMatrix_[14] = 2.0 * ZNear * ZFar / (ZNear - ZFar); + projectionMatrix_[15] = 0.0; + // same as gluPerspective( 180.0*fieldOfView()/M_PI, aspectRatio(), zNear(), zFar() ); + break; + } + case Camera::ORTHOGRAPHIC: + { + GLdouble w, h; + getOrthoWidthHeight(w,h); + projectionMatrix_[0] = 1.0/w; + projectionMatrix_[5] = 1.0/h; + projectionMatrix_[10] = -2.0/(ZFar - ZNear); + projectionMatrix_[11] = 0.0; + projectionMatrix_[14] = -(ZFar + ZNear)/(ZFar - ZNear); + projectionMatrix_[15] = 1.0; + // same as glOrtho( -w, w, -h, h, zNear(), zFar() ); + break; + } + } + + projectionMatrixIsUpToDate_ = true; +} + +/*! Computes the modelView matrix associated with the Camera's position() and orientation(). + + This matrix converts from the world coordinates system to the Camera coordinates system, so that + coordinates can then be projected on screen using the projection matrix (see computeProjectionMatrix()). + + Use getModelViewMatrix() to retrieve this matrix. + + \note You must call this method if your Camera is not associated with a QGLViewer and is used for + offscreen computations (using (un)projectedCoordinatesOf() for instance). loadModelViewMatrix() + does it otherwise. */ +void Camera::computeModelViewMatrix() const +{ + if (modelViewMatrixIsUpToDate_) return; + + const Quaternion q = frame()->orientation(); + + const qreal q00 = 2.0 * q[0] * q[0]; + const qreal q11 = 2.0 * q[1] * q[1]; + const qreal q22 = 2.0 * q[2] * q[2]; + + const qreal q01 = 2.0 * q[0] * q[1]; + const qreal q02 = 2.0 * q[0] * q[2]; + const qreal q03 = 2.0 * q[0] * q[3]; + + const qreal q12 = 2.0 * q[1] * q[2]; + const qreal q13 = 2.0 * q[1] * q[3]; + + const qreal q23 = 2.0 * q[2] * q[3]; + + modelViewMatrix_[0] = 1.0 - q11 - q22; + modelViewMatrix_[1] = q01 - q23; + modelViewMatrix_[2] = q02 + q13; + modelViewMatrix_[3] = 0.0; + + modelViewMatrix_[4] = q01 + q23; + modelViewMatrix_[5] = 1.0 - q22 - q00; + modelViewMatrix_[6] = q12 - q03; + modelViewMatrix_[7] = 0.0; + + modelViewMatrix_[8] = q02 - q13; + modelViewMatrix_[9] = q12 + q03; + modelViewMatrix_[10] = 1.0 - q11 - q00; + modelViewMatrix_[11] = 0.0; + + const Vec t = q.inverseRotate(frame()->position()); + + modelViewMatrix_[12] = -t.x; + modelViewMatrix_[13] = -t.y; + modelViewMatrix_[14] = -t.z; + modelViewMatrix_[15] = 1.0; + + modelViewMatrixIsUpToDate_ = true; +} + + +/*! Loads the OpenGL \c GL_PROJECTION matrix with the Camera projection matrix. + + The Camera projection matrix is computed using computeProjectionMatrix(). + + When \p reset is \c true (default), the method clears the previous projection matrix by calling \c + glLoadIdentity before setting the matrix. Setting \p reset to \c false is useful for \c GL_SELECT + mode, to combine the pushed matrix with a picking matrix. See QGLViewer::beginSelection() for details. + + This method is used by QGLViewer::preDraw() (called before user's QGLViewer::draw() method) to + set the \c GL_PROJECTION matrix according to the viewer's QGLViewer::camera() settings. + + Use getProjectionMatrix() to retrieve this matrix. Overload this method if you want your Camera to + use an exotic projection matrix. See also loadModelViewMatrix(). + + \attention \c glMatrixMode is set to \c GL_PROJECTION. + + \attention If you use several OpenGL contexts and bypass the Qt main refresh loop, you should call + QGLWidget::makeCurrent() before this method in order to activate the right OpenGL context. [TODO Update with QOpenGLWidget] */ +void Camera::loadProjectionMatrix(bool /*reset*/) const +{ + computeProjectionMatrix(); +} + +/*! Loads the OpenGL \c GL_MODELVIEW matrix with the modelView matrix corresponding to the Camera. + + Calls computeModelViewMatrix() to compute the Camera's modelView matrix. + + This method is used by QGLViewer::preDraw() (called before user's QGLViewer::draw() method) to + set the \c GL_MODELVIEW matrix according to the viewer's QGLViewer::camera() position() and + orientation(). + + As a result, the vertices used in QGLViewer::draw() can be defined in the so called world + coordinate system. They are multiplied by this matrix to get converted to the Camera coordinate + system, before getting projected using the \c GL_PROJECTION matrix (see loadProjectionMatrix()). + + When \p reset is \c true (default), the method loads (overwrites) the \c GL_MODELVIEW matrix. Setting + \p reset to \c false simply calls \c glMultMatrixd (might be useful for some applications). + + Overload this method or simply call glLoadMatrixd() at the beginning of QGLViewer::draw() if you + want your Camera to use an exotic modelView matrix. See also loadProjectionMatrix(). + + getModelViewMatrix() returns the 4x4 modelView matrix. + + \attention glMatrixMode is set to \c GL_MODELVIEW + + \attention If you use several OpenGL contexts and bypass the Qt main refresh loop, you should call + QGLWidget::makeCurrent() before this method in order to activate the right OpenGL context. [TODO Update with QOpenGLWidget] */ +void Camera::loadModelViewMatrix(bool /*reset*/) const +{ + // WARNING: makeCurrent must be called by every calling method + computeModelViewMatrix(); +} + +/*! Same as loadModelViewMatrix() but for a stereo setup. + + Only the Camera::PERSPECTIVE type() is supported for stereo mode. See + QGLViewer::setStereoDisplay(). + + The modelView matrix is almost identical to the mono-vision one. It is simply translated along its + horizontal axis by a value that depends on stereo parameters (see focusDistance(), + IODistance(), and physicalScreenWidth()). + + When \p leftBuffer is \c true, computes the modelView matrix associated to the left eye (right eye + otherwise). + + loadProjectionMatrixStereo() explains how to retrieve to resulting matrix. + + See the stereoViewer and the anaglyph examples for an illustration. + + \attention glMatrixMode is set to \c GL_MODELVIEW. */ +void Camera::loadModelViewMatrixStereo(bool leftBuffer) const +{ + // WARNING: makeCurrent must be called by every calling method + + qreal halfWidth = focusDistance() * tan(horizontalFieldOfView() / 2.0); + qreal shift = halfWidth * IODistance() / physicalScreenWidth(); // * current window width / full screen width + + computeModelViewMatrix(); + if (leftBuffer) + modelViewMatrix_[12] -= shift; + else + modelViewMatrix_[12] += shift; +} + +/*! Fills \p m with the Camera projection matrix values. + + Based on computeProjectionMatrix() to make sure the Camera projection matrix is up to date. + + This matrix only reflects the Camera's internal parameters and it may differ from the \c + GL_PROJECTION matrix retrieved using \c glGetDoublev(GL_PROJECTION_MATRIX, m). It actually + represents the state of the \c GL_PROJECTION after QGLViewer::preDraw(), at the beginning of + QGLViewer::draw(). If you modified the \c GL_PROJECTION matrix (for instance using + QGLViewer::startScreenCoordinatesSystem()), the two results differ. + + The result is an OpenGL 4x4 matrix, which is given in \e column-major order (see \c glMultMatrix + man page for details). + + See also getModelViewMatrix() and setFromProjectionMatrix(). */ +void Camera::getProjectionMatrix(GLdouble m[16]) const +{ + computeProjectionMatrix(); + for (unsigned short i=0; i<16; ++i) + m[i] = projectionMatrix_[i]; +} + +/*! Overloaded getProjectionMatrix(GLdouble m[16]) method using a \c GLfloat array instead. */ +void Camera::getProjectionMatrix(GLfloat m[16]) const +{ + static GLdouble mat[16]; + getProjectionMatrix(mat); + for (unsigned short i=0; i<16; ++i) + m[i] = float(mat[i]); +} + +/*! Overloaded getProjectionMatrix(GLdouble m[16]) method using a \c QMatrix4x4 instead. */ +void Camera::getProjectionMatrix(QMatrix4x4& m) const +{ + computeProjectionMatrix(); + for (int row=0; row<4; ++row) + for (int col=0; col<4; ++col) + m(row, col) = projectionMatrix_[row + col*4]; +} + +/*! Fills \p m with the Camera modelView matrix values. + + First calls computeModelViewMatrix() to define the Camera modelView matrix. + + Note that this matrix may \e not be the one you would get from a \c + glGetDoublev(GL_MODELVIEW_MATRIX, m). It actually represents the state of the \c + GL_MODELVIEW after QGLViewer::preDraw(), at the \e beginning of QGLViewer::draw(). It converts from + the world to the Camera coordinate system. As soon as you modify the \c GL_MODELVIEW in your + QGLViewer::draw() method (using glTranslate, glRotate... or similar methods), the two matrices differ. + + The result is an OpenGL 4x4 matrix, which is given in \e column-major order (see \c glMultMatrix + man page for details). + + See also getProjectionMatrix() and setFromModelViewMatrix(). */ +void Camera::getModelViewMatrix(GLdouble m[16]) const +{ + // May not be needed, but easier like this. + // Prevents from retrieving matrix in stereo mode -> overwrites shifted value. + computeModelViewMatrix(); + for (unsigned short i=0; i<16; ++i) + m[i] = modelViewMatrix_[i]; +} + + +/*! Overloaded getModelViewMatrix(GLdouble m[16]) method using a \c GLfloat array instead. */ +void Camera::getModelViewMatrix(GLfloat m[16]) const +{ + static GLdouble mat[16]; + getModelViewMatrix(mat); + for (unsigned short i=0; i<16; ++i) + m[i] = float(mat[i]); +} + +/*! Overloaded getModelViewMatrix(GLdouble m[16]) method using a \c QMatrix4x4 instead. */ +void Camera::getModelViewMatrix(QMatrix4x4& m) const +{ + computeModelViewMatrix(); + for (int row=0; row<4; ++row) + for (int col=0; col<4; ++col) + m(row, col) = modelViewMatrix_[row + col*4]; +} + +/*! Fills \p m with the product of the ModelView and Projection matrices. + + Calls getModelViewMatrix() and getProjectionMatrix() and then fills \p m with the product of these two matrices. */ +void Camera::getModelViewProjectionMatrix(GLdouble m[16]) const +{ + GLdouble mv[16]; + GLdouble proj[16]; + getModelViewMatrix(mv); + getProjectionMatrix(proj); + + for (unsigned short i=0; i<4; ++i) + { + for (unsigned short j=0; j<4; ++j) + { + qreal sum = 0.0; + for (unsigned short k=0; k<4; ++k) + sum += proj[i+4*k]*mv[k+4*j]; + m[i+4*j] = sum; + } + } +} + +/*! Overloaded getModelViewProjectionMatrix(GLdouble m[16]) method using a \c GLfloat array instead. */ +void Camera::getModelViewProjectionMatrix(GLfloat m[16]) const +{ + static GLdouble mat[16]; + getModelViewProjectionMatrix(mat); + for (unsigned short i=0; i<16; ++i) + m[i] = float(mat[i]); +} + +/*! Overloaded getModelViewProjectionMatrix(GLdouble m[16]) method using a \c QMatrix4x4 instead. */ +void Camera::getModelViewProjectionMatrix(QMatrix4x4& m) const +{ + static GLdouble mat[16]; + getModelViewProjectionMatrix(mat); + for (int row=0; row<4; ++row) + for (int col=0; col<4; ++col) + m(row, col) = mat[row + col*4]; +} + +/*! Sets the sceneRadius() value. Negative values are ignored. + +\attention This methods also sets focusDistance() to sceneRadius() / tan(fieldOfView()/2) and +flySpeed() to 1% of sceneRadius(). */ +void Camera::setSceneRadius(qreal radius) +{ + if (radius <= 0.0) + { + qWarning("Scene radius must be positive - Ignoring value"); + return; + } + + sceneRadius_ = radius; + projectionMatrixIsUpToDate_ = false; + + setFocusDistance(sceneRadius() / tan(fieldOfView()/2.0)); + + frame()->setFlySpeed(0.01*sceneRadius()); +} + +/*! Similar to setSceneRadius() and setSceneCenter(), but the scene limits are defined by a (world + axis aligned) bounding box. */ +void Camera::setSceneBoundingBox(const Vec& min, const Vec& max) +{ + setSceneCenter((min+max)/2.0); + setSceneRadius(0.5*(max-min).norm()); +} + + +/*! Sets the sceneCenter(). + + \attention This method also sets the pivotPoint() to sceneCenter(). */ +void Camera::setSceneCenter(const Vec& center) +{ + sceneCenter_ = center; + setPivotPoint(sceneCenter()); + projectionMatrixIsUpToDate_ = false; +} + +/*! setSceneCenter() to the result of pointUnderPixel(\p pixel). + + Returns \c true if a pointUnderPixel() was found and sceneCenter() was actually changed. + + See also setPivotPointFromPixel(). See the pointUnderPixel() documentation. */ +bool Camera::setSceneCenterFromPixel(const QPoint& pixel) +{ + bool found; + Vec point = pointUnderPixel(pixel, found); + if (found) + setSceneCenter(point); + return found; +} + +#ifndef DOXYGEN +void Camera::setRevolveAroundPoint(const Vec& point) { + qWarning("setRevolveAroundPoint() is deprecated, use setPivotPoint() instead"); + setPivotPoint(point); +} +bool Camera::setRevolveAroundPointFromPixel(const QPoint& pixel) { + qWarning("setRevolveAroundPointFromPixel() is deprecated, use setPivotPointFromPixel() instead"); + return setPivotPointFromPixel(pixel); +} +Vec Camera::revolveAroundPoint() const { + qWarning("revolveAroundPoint() is deprecated, use pivotPoint() instead"); + return pivotPoint(); +} +#endif + +/*! Changes the pivotPoint() to \p point (defined in the world coordinate system). */ +void Camera::setPivotPoint(const Vec& point) +{ + const qreal prevDist = fabs(cameraCoordinatesOf(pivotPoint()).z); + + // If frame's RAP is set directly, projectionMatrixIsUpToDate_ should also be + // set to false to ensure proper recomputation of the ORTHO projection matrix. + frame()->setPivotPoint(point); + + // orthoCoef_ is used to compensate for changes of the pivotPoint, so that the image does + // not change when the pivotPoint is changed in ORTHOGRAPHIC mode. + const qreal newDist = fabs(cameraCoordinatesOf(pivotPoint()).z); + // Prevents division by zero when rap is set to camera position + if ((prevDist > 1E-9) && (newDist > 1E-9)) + orthoCoef_ *= prevDist / newDist; + projectionMatrixIsUpToDate_ = false; +} + +/*! The pivotPoint() is set to the point located under \p pixel on screen. + +Returns \c true if a pointUnderPixel() was found. If no point was found under \p pixel, the +pivotPoint() is left unchanged. + +\p pixel is expressed in Qt format (origin in the upper left corner of the window). See +pointUnderPixel(). + +See also setSceneCenterFromPixel(). */ +bool Camera::setPivotPointFromPixel(const QPoint& pixel) +{ + bool found; + Vec point = pointUnderPixel(pixel, found); + if (found) + setPivotPoint(point); + return found; +} + +/*! Returns the ratio between pixel and OpenGL units at \p position. + + A line of \c n * pixelGLRatio() OpenGL units, located at \p position in the world coordinates + system, will be projected with a length of \c n pixels on screen. + + Use this method to scale objects so that they have a constant pixel size on screen. The following + code will draw a 20 pixel line, starting at sceneCenter() and always directed along the screen + vertical direction: + \code + glBegin(GL_LINES); + glVertex3fv(sceneCenter()); + glVertex3fv(sceneCenter() + 20 * pixelGLRatio(sceneCenter()) * camera()->upVector()); + glEnd(); + \endcode */ +qreal Camera::pixelGLRatio(const Vec& position) const +{ + switch (type()) + { + case Camera::PERSPECTIVE : + return 2.0 * fabs((frame()->coordinatesOf(position)).z) * tan(fieldOfView()/2.0) / screenHeight(); + case Camera::ORTHOGRAPHIC : + { + GLdouble w, h; + getOrthoWidthHeight(w,h); + return 2.0 * h / screenHeight(); + } + } + // Bad compilers complain + return 1.0; +} + +/*! Changes the Camera fieldOfView() so that the entire scene (defined by QGLViewer::sceneCenter() + and QGLViewer::sceneRadius()) is visible from the Camera position(). + + The position() and orientation() of the Camera are not modified and you first have to orientate the + Camera in order to actually see the scene (see lookAt(), showEntireScene() or fitSphere()). + + This method is especially useful for \e shadow \e maps computation. Use the Camera positioning + tools (setPosition(), lookAt()) to position a Camera at the light position. Then use this method to + define the fieldOfView() so that the shadow map resolution is optimally used: + \code + // The light camera needs size hints in order to optimize its fieldOfView + lightCamera->setSceneRadius(sceneRadius()); + lightCamera->setSceneCenter(sceneCenter()); + + // Place the light camera. + lightCamera->setPosition(lightFrame->position()); + lightCamera->lookAt(sceneCenter()); + lightCamera->setFOVToFitScene(); + \endcode + + See the (soon available) shadowMap contribution example for a practical implementation. + + \attention The fieldOfView() is clamped to M_PI/2.0. This happens when the Camera is at a distance + lower than sqrt(2.0) * sceneRadius() from the sceneCenter(). It optimizes the shadow map + resolution, although it may miss some parts of the scene. */ +void Camera::setFOVToFitScene() +{ + if (distanceToSceneCenter() > sqrt(2.0)*sceneRadius()) + setFieldOfView(2.0 * asin(sceneRadius() / distanceToSceneCenter())); + else + setFieldOfView(M_PI / 2.0); +} + +/*! Makes the Camera smoothly zoom on the pointUnderPixel() \p pixel. + + Nothing happens if no pointUnderPixel() is found. Otherwise a KeyFrameInterpolator is created that + animates the Camera on a one second path that brings the Camera closer to the point under \p pixel. + + See also interpolateToFitScene(). */ +void Camera::interpolateToZoomOnPixel(const QPoint& pixel) +{ + const qreal coef = 0.1; + + bool found; + Vec target = pointUnderPixel(pixel, found); + + if (!found) + return; + + if (interpolationKfi_->interpolationIsStarted()) + interpolationKfi_->stopInterpolation(); + + interpolationKfi_->deletePath(); + interpolationKfi_->addKeyFrame(*(frame())); + + interpolationKfi_->addKeyFrame(Frame(0.3*frame()->position() + 0.7*target, frame()->orientation()), 0.4); + + // Small hack: attach a temporary frame to take advantage of lookAt without modifying frame + static ManipulatedCameraFrame* tempFrame = new ManipulatedCameraFrame(); + ManipulatedCameraFrame* const originalFrame = frame(); + tempFrame->setPosition(coef*frame()->position() + (1.0-coef)*target); + tempFrame->setOrientation(frame()->orientation()); + setFrame(tempFrame); + lookAt(target); + setFrame(originalFrame); + + interpolationKfi_->addKeyFrame(*(tempFrame), 1.0); + + interpolationKfi_->startInterpolation(); +} + +/*! Interpolates the Camera on a one second KeyFrameInterpolator path so that the entire scene fits + the screen at the end. + + The scene is defined by its sceneCenter() and its sceneRadius(). See showEntireScene(). + + The orientation() of the Camera is not modified. See also interpolateToZoomOnPixel(). */ +void Camera::interpolateToFitScene() +{ + if (interpolationKfi_->interpolationIsStarted()) + interpolationKfi_->stopInterpolation(); + + interpolationKfi_->deletePath(); + interpolationKfi_->addKeyFrame(*(frame())); + + // Small hack: attach a temporary frame to take advantage of lookAt without modifying frame + static ManipulatedCameraFrame* tempFrame = new ManipulatedCameraFrame(); + ManipulatedCameraFrame* const originalFrame = frame(); + tempFrame->setPosition(frame()->position()); + tempFrame->setOrientation(frame()->orientation()); + setFrame(tempFrame); + showEntireScene(); + setFrame(originalFrame); + + interpolationKfi_->addKeyFrame(*(tempFrame)); + + interpolationKfi_->startInterpolation(); +} + + +/*! Smoothly interpolates the Camera on a KeyFrameInterpolator path so that it goes to \p fr. + + \p fr is expressed in world coordinates. \p duration tunes the interpolation speed (default is + 1 second). + + See also interpolateToFitScene() and interpolateToZoomOnPixel(). */ +void Camera::interpolateTo(const Frame& fr, qreal duration) +{ + if (interpolationKfi_->interpolationIsStarted()) + interpolationKfi_->stopInterpolation(); + + interpolationKfi_->deletePath(); + interpolationKfi_->addKeyFrame(*(frame())); + interpolationKfi_->addKeyFrame(fr, duration); + + interpolationKfi_->startInterpolation(); +} + + +/*! Returns the coordinates of the 3D point located at pixel (x,y) on screen. + + Calls a \c glReadPixel to get the pixel depth and applies an unprojectedCoordinatesOf() to the + result. \p found indicates whether a point was found or not (i.e. background pixel, result's depth + is zFar() in that case). + + \p x and \p y are expressed in pixel units with an origin in the upper left corner. Use + screenHeight() - y to convert to OpenGL standard. + + \attention This method assumes that a GL context is available, and that its content was drawn using + the Camera (i.e. using its projection and modelview matrices). This method hence cannot be used for + offscreen Camera computations. Use cameraCoordinatesOf() and worldCoordinatesOf() to perform + similar operations in that case. + + \note The precision of the z-Buffer highly depends on how the zNear() and zFar() values are fitted + to your scene. Loose boundaries will result in imprecision along the viewing direction. */ +Vec Camera::pointUnderPixel(const QPoint& pixel, bool& found) const +{ + float depth; + // Qt uses upper corner for its origin while GL uses the lower corner. + glReadPixels(pixel.x(), screenHeight()-1-pixel.y(), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth); + found = depth < 1.0; + Vec point(pixel.x(), pixel.y(), depth); + point = unprojectedCoordinatesOf(point); + return point; +} + +/*! Moves the Camera so that the entire scene is visible. + + Simply calls fitSphere() on a sphere defined by sceneCenter() and sceneRadius(). + + You will typically use this method in QGLViewer::init() after you defined a new sceneRadius(). */ +void Camera::showEntireScene() +{ + fitSphere(sceneCenter(), sceneRadius()); +} + +/*! Moves the Camera so that its sceneCenter() is projected on the center of the window. The + orientation() and fieldOfView() are unchanged. + + Simply projects the current position on a line passing through sceneCenter(). See also + showEntireScene().*/ +void Camera::centerScene() +{ + frame()->projectOnLine(sceneCenter(), viewDirection()); +} + +/*! Sets the Camera orientation(), so that it looks at point \p target (defined in the world + coordinate system). + + The Camera position() is not modified. Simply setViewDirection(). + + See also setUpVector(), setOrientation(), showEntireScene(), fitSphere() and fitBoundingBox(). */ +void Camera::lookAt(const Vec& target) +{ + setViewDirection(target - position()); +} + +/*! Moves the Camera so that the sphere defined by (\p center, \p radius) is visible and fits in the frustum. + + The Camera is simply translated to center the sphere in the screen and make it fit the frustum. Its + orientation() and its fieldOfView() are unchanged. + + You should therefore orientate the Camera before you call this method. See lookAt(), + setOrientation() and setUpVector(). */ +void Camera::fitSphere(const Vec& center, qreal radius) +{ + qreal distance = 0.0; + switch (type()) + { + case Camera::PERSPECTIVE : + { + const qreal yview = radius / sin(fieldOfView() / 2.0); + const qreal xview = radius / sin(horizontalFieldOfView() / 2.0); + distance = qMax(xview, yview); + break; + } + case Camera::ORTHOGRAPHIC : + { + distance = ((center-pivotPoint()) * viewDirection()) + (radius / orthoCoef_); + break; + } + } + Vec newPos(center - distance * viewDirection()); + frame()->setPositionWithConstraint(newPos); +} + +/*! Moves the Camera so that the (world axis aligned) bounding box (\p min, \p max) is entirely + visible, using fitSphere(). */ +void Camera::fitBoundingBox(const Vec& min, const Vec& max) +{ + qreal diameter = qMax(fabs(max[1]-min[1]), fabs(max[0]-min[0])); + diameter = qMax(fabs(max[2]-min[2]), diameter); + fitSphere(0.5*(min+max), 0.5*diameter); +} + +/*! Moves the Camera so that the rectangular screen region defined by \p rectangle (pixel units, + with origin in the upper left corner) fits the screen. + + The Camera is translated (its orientation() is unchanged) so that \p rectangle is entirely + visible. Since the pixel coordinates only define a \e frustum in 3D, it's the intersection of this + frustum with a plane (orthogonal to the viewDirection() and passing through the sceneCenter()) + that is used to define the 3D rectangle that is eventually fitted. */ +void Camera::fitScreenRegion(const QRect& rectangle) +{ + const Vec vd = viewDirection(); + const qreal distToPlane = distanceToSceneCenter(); + const QPoint center = rectangle.center(); + + Vec orig, dir; + convertClickToLine( center, orig, dir ); + Vec newCenter = orig + distToPlane / (dir*vd) * dir; + + convertClickToLine( QPoint(rectangle.x(), center.y()), orig, dir ); + const Vec pointX = orig + distToPlane / (dir*vd) * dir; + + convertClickToLine( QPoint(center.x(), rectangle.y()), orig, dir ); + const Vec pointY = orig + distToPlane / (dir*vd) * dir; + + qreal distance = 0.0; + switch (type()) + { + case Camera::PERSPECTIVE : + { + const qreal distX = (pointX-newCenter).norm() / sin(horizontalFieldOfView()/2.0); + const qreal distY = (pointY-newCenter).norm() / sin(fieldOfView()/2.0); + distance = qMax(distX, distY); + break; + } + case Camera::ORTHOGRAPHIC : + { + const qreal dist = ((newCenter-pivotPoint()) * vd); + //#CONNECTION# getOrthoWidthHeight + const qreal distX = (pointX-newCenter).norm() / orthoCoef_ / ((aspectRatio() < 1.0) ? 1.0 : aspectRatio()); + const qreal distY = (pointY-newCenter).norm() / orthoCoef_ / ((aspectRatio() < 1.0) ? 1.0/aspectRatio() : 1.0); + distance = dist + qMax(distX, distY); + break; + } + } + + Vec newPos(newCenter - distance * vd); + frame()->setPositionWithConstraint(newPos); +} + +/*! Rotates the Camera so that its upVector() becomes \p up (defined in the world coordinate + system). + + The Camera is rotated around an axis orthogonal to \p up and to the current upVector() direction. + Use this method in order to define the Camera horizontal plane. + + When \p noMove is set to \c false, the orientation modification is compensated by a translation, so + that the pivotPoint() stays projected at the same position on screen. This is especially + useful when the Camera is used as an observer of the scene (default mouse binding). + + When \p noMove is \c true (default), the Camera position() is left unchanged, which is an intuitive + behavior when the Camera is in a walkthrough fly mode (see the QGLViewer::MOVE_FORWARD and + QGLViewer::MOVE_BACKWARD QGLViewer::MouseAction). + + The frame()'s ManipulatedCameraFrame::sceneUpVector() is set accordingly. + + See also setViewDirection(), lookAt() and setOrientation(). */ +void Camera::setUpVector(const Vec& up, bool noMove) +{ + Quaternion q(Vec(0.0, 1.0, 0.0), frame()->transformOf(up)); + + if (!noMove) + frame()->setPosition(pivotPoint() - (frame()->orientation()*q).rotate(frame()->coordinatesOf(pivotPoint()))); + + frame()->rotate(q); + + // Useful in fly mode to keep the horizontal direction. + frame()->updateSceneUpVector(); +} + +/*! Sets the orientation() of the Camera using polar coordinates. + + \p theta rotates the Camera around its Y axis, and \e then \p phi rotates it around its X axis. + The polar coordinates are defined in the world coordinates system: \p theta = \p phi = 0 means + that the Camera is directed towards the world Z axis. Both angles are expressed in radians. + + See also setUpVector(). The position() of the Camera is unchanged, you may want to call showEntireScene() + after this method to move the Camera. + + This method can be useful to create Quicktime VR panoramic sequences, see the + QGLViewer::saveSnapshot() documentation for details. */ +void Camera::setOrientation(qreal theta, qreal phi) +{ + Vec axis(0.0, 1.0, 0.0); + const Quaternion rot1(axis, theta); + axis = Vec(-cos(theta), 0.0, sin(theta)); + const Quaternion rot2(axis, phi); + setOrientation(rot1 * rot2); +} + +/*! Sets the Camera orientation(), defined in the world coordinate system. */ +void Camera::setOrientation(const Quaternion& q) +{ + frame()->setOrientation(q); + frame()->updateSceneUpVector(); +} + +/*! Rotates the Camera so that its viewDirection() is \p direction (defined in the world coordinate + system). + + The Camera position() is not modified. The Camera is rotated so that the horizon (defined by its + upVector()) is preserved. See also lookAt() and setUpVector(). */ +void Camera::setViewDirection(const Vec& direction) +{ + if (direction.squaredNorm() < 1E-10) + return; + + Vec xAxis = direction ^ upVector(); + if (xAxis.squaredNorm() < 1E-10) + { + // target is aligned with upVector, this means a rotation around X axis + // X axis is then unchanged, let's keep it ! + xAxis = frame()->inverseTransformOf(Vec(1.0, 0.0, 0.0)); + } + + Quaternion q; + q.setFromRotatedBasis(xAxis, xAxis^direction, -direction); + frame()->setOrientationWithConstraint(q); +} + +// Compute a 3 by 3 determinant. +static qreal det(qreal m00,qreal m01,qreal m02, + qreal m10,qreal m11,qreal m12, + qreal m20,qreal m21,qreal m22) +{ + return m00*m11*m22 + m01*m12*m20 + m02*m10*m21 - m20*m11*m02 - m10*m01*m22 - m00*m21*m12; +} + +// Computes the index of element [i][j] in a \c qreal matrix[3][4]. +static inline unsigned int ind(unsigned int i, unsigned int j) +{ + return (i*4+j); +} + +/*! Returns the Camera position (the eye), defined in the world coordinate system. + +Use setPosition() to set the Camera position. Other convenient methods are showEntireScene() or +fitSphere(). Actually returns \c frame()->position(). + +This position corresponds to the projection center of a Camera::PERSPECTIVE Camera. It is not +located in the image plane, which is at a zNear() distance ahead. */ +Vec Camera::position() const { return frame()->position(); } + +/*! Returns the normalized up vector of the Camera, defined in the world coordinate system. + +Set using setUpVector() or setOrientation(). It is orthogonal to viewDirection() and to +rightVector(). + +It corresponds to the Y axis of the associated frame() (actually returns +frame()->inverseTransformOf(Vec(0.0, 1.0, 0.0)) ). */ +Vec Camera::upVector() const +{ + return frame()->inverseTransformOf(Vec(0.0, 1.0, 0.0)); +} +/*! Returns the normalized view direction of the Camera, defined in the world coordinate system. + +Change this value using setViewDirection(), lookAt() or setOrientation(). It is orthogonal to +upVector() and to rightVector(). + +This corresponds to the negative Z axis of the frame() ( frame()->inverseTransformOf(Vec(0.0, +0.0, -1.0)) ). */ +Vec Camera::viewDirection() const { return frame()->inverseTransformOf(Vec(0.0, 0.0, -1.0)); } + +/*! Returns the normalized right vector of the Camera, defined in the world coordinate system. + +This vector lies in the Camera horizontal plane, directed along the X axis (orthogonal to +upVector() and to viewDirection()). Set using setUpVector(), lookAt() or setOrientation(). + +Simply returns frame()->inverseTransformOf(Vec(1.0, 0.0, 0.0)). */ +Vec Camera::rightVector() const +{ + return frame()->inverseTransformOf(Vec(1.0, 0.0, 0.0)); +} + +/*! Returns the Camera orientation, defined in the world coordinate system. + +Actually returns \c frame()->orientation(). Use setOrientation(), setUpVector() or lookAt() to +set the Camera orientation. */ +Quaternion Camera::orientation() const { return frame()->orientation(); } + +/*! Sets the Camera position() (the eye), defined in the world coordinate system. */ +void Camera::setPosition(const Vec& pos) { frame()->setPosition(pos); } + +/*! Returns the Camera frame coordinates of a point \p src defined in world coordinates. + +worldCoordinatesOf() performs the inverse transformation. + +Note that the point coordinates are simply converted in a different coordinate system. They are +not projected on screen. Use projectedCoordinatesOf() for that. */ +Vec Camera::cameraCoordinatesOf(const Vec& src) const { return frame()->coordinatesOf(src); } + +/*! Returns the world coordinates of the point whose position \p src is defined in the Camera +coordinate system. + +cameraCoordinatesOf() performs the inverse transformation. */ +Vec Camera::worldCoordinatesOf(const Vec& src) const { return frame()->inverseCoordinatesOf(src); } + +/*! Returns the fly speed of the Camera. + +Simply returns frame()->flySpeed(). See the ManipulatedCameraFrame::flySpeed() documentation. +This value is only meaningful when the MouseAction bindings is QGLViewer::MOVE_FORWARD or +QGLViewer::MOVE_BACKWARD. + +Set to 1% of the sceneRadius() by setSceneRadius(). See also setFlySpeed(). */ +qreal Camera::flySpeed() const { return frame()->flySpeed(); } + +/*! Sets the Camera flySpeed(). + +\attention This value is modified by setSceneRadius(). */ +void Camera::setFlySpeed(qreal speed) { frame()->setFlySpeed(speed); } + +/*! The point the Camera pivots around with the QGLViewer::ROTATE mouse binding. Defined in world coordinate system. + +Default value is the sceneCenter(). + +\attention setSceneCenter() changes this value. */ +Vec Camera::pivotPoint() const { return frame()->pivotPoint(); } + +/*! Sets the Camera's position() and orientation() from an OpenGL ModelView matrix. + +This enables a Camera initialisation from an other OpenGL application. \p modelView is a 16 GLdouble +vector representing a valid OpenGL ModelView matrix, such as one can get using: +\code +GLdouble mvm[16]; +glGetDoublev(GL_MODELVIEW_MATRIX, mvm); +myCamera->setFromModelViewMatrix(mvm); +\endcode + +After this method has been called, getModelViewMatrix() returns a matrix equivalent to \p +modelView. + +Only the orientation() and position() of the Camera are modified. + +\note If you defined your matrix as \c GLdouble \c mvm[4][4], pass \c &(mvm[0][0]) as a +parameter. */ +void Camera::setFromModelViewMatrix(const GLdouble* const modelViewMatrix) +{ + // Get upper left (rotation) matrix + qreal upperLeft[3][3]; + for (int i=0; i<3; ++i) + for (int j=0; j<3; ++j) + upperLeft[i][j] = modelViewMatrix[i*4+j]; + + // Transform upperLeft into the associated Quaternion + Quaternion q; + q.setFromRotationMatrix(upperLeft); + + setOrientation(q); + setPosition(-q.rotate(Vec(modelViewMatrix[12], modelViewMatrix[13], modelViewMatrix[14]))); +} + +/*! Defines the Camera position(), orientation() and fieldOfView() from a projection matrix. + + \p matrix has to be given in the format used by vision algorithm. It has 3 lines and 4 columns. It + transforms a point from the world homogeneous coordinate system (4 coordinates: \c sx, \c sy, \c sz + and \c s) into a point in the screen homogeneous coordinate system (3 coordinates: \c sx, \c sy, + and \c s, where \c x and \c y are the pixel coordinates on the screen). + + Its three lines correspond to the homogeneous coordinates of the normals to the planes x=0, y=0 and + z=0, defined in the Camera coordinate system. + + The elements of the matrix are ordered in line major order: you can call \c + setFromProjectionMatrix(&(matrix[0][0])) if you defined your matrix as a \c qreal \c matrix[3][4]. + + \attention Passing the result of getProjectionMatrix() or getModelViewMatrix() to this method is + not possible (purposefully incompatible matrix dimensions). \p matrix is more likely to be the + product of these two matrices, without the last line. + + Use setFromModelViewMatrix() to set position() and orientation() from a \c GL_MODELVIEW matrix. + fieldOfView() can also be retrieved from a \e perspective \c GL_PROJECTION matrix using 2.0 * + atan(1.0/projectionMatrix[5]). + + This code was written by Sylvain Paris. */ +void Camera::setFromProjectionMatrix(const qreal matrix[12]) +{ + // The 3 lines of the matrix are the normals to the planes x=0, y=0, z=0 + // in the camera CS. As we normalize them, we do not need the 4th coordinate. + Vec line_0(matrix[ind(0,0)],matrix[ind(0,1)],matrix[ind(0,2)]); + Vec line_1(matrix[ind(1,0)],matrix[ind(1,1)],matrix[ind(1,2)]); + Vec line_2(matrix[ind(2,0)],matrix[ind(2,1)],matrix[ind(2,2)]); + + line_0.normalize(); + line_1.normalize(); + line_2.normalize(); + + // The camera position is at (0,0,0) in the camera CS so it is the + // intersection of the 3 planes. It can be seen as the kernel + // of the 3x4 projection matrix. We calculate it through 4 dimensional + // vectorial product. We go directly into 3D that is to say we directly + // divide the first 3 coordinates by the 4th one. + + // We derive the 4 dimensional vectorial product formula from the + // computation of a 4x4 determinant that is developped according to + // its 4th column. This implies some 3x3 determinants. + const Vec cam_pos = Vec(det(matrix[ind(0,1)],matrix[ind(0,2)],matrix[ind(0,3)], + matrix[ind(1,1)],matrix[ind(1,2)],matrix[ind(1,3)], + matrix[ind(2,1)],matrix[ind(2,2)],matrix[ind(2,3)]), + + -det(matrix[ind(0,0)],matrix[ind(0,2)],matrix[ind(0,3)], + matrix[ind(1,0)],matrix[ind(1,2)],matrix[ind(1,3)], + matrix[ind(2,0)],matrix[ind(2,2)],matrix[ind(2,3)]), + + det(matrix[ind(0,0)],matrix[ind(0,1)],matrix[ind(0,3)], + matrix[ind(1,0)],matrix[ind(1,1)],matrix[ind(1,3)], + matrix[ind(2,0)],matrix[ind(2,1)],matrix[ind(2,3)])) / + + (-det(matrix[ind(0,0)],matrix[ind(0,1)],matrix[ind(0,2)], + matrix[ind(1,0)],matrix[ind(1,1)],matrix[ind(1,2)], + matrix[ind(2,0)],matrix[ind(2,1)],matrix[ind(2,2)])); + + // We compute the rotation matrix column by column. + + // GL Z axis is front facing. + Vec column_2 = -line_2; + + // X-axis is almost like line_0 but should be orthogonal to the Z axis. + Vec column_0 = ((column_2^line_0)^column_2); + column_0.normalize(); + + // Y-axis is almost like line_1 but should be orthogonal to the Z axis. + // Moreover line_1 is downward oriented as the screen CS. + Vec column_1 = -((column_2^line_1)^column_2); + column_1.normalize(); + + qreal rot[3][3]; + rot[0][0] = column_0[0]; + rot[1][0] = column_0[1]; + rot[2][0] = column_0[2]; + + rot[0][1] = column_1[0]; + rot[1][1] = column_1[1]; + rot[2][1] = column_1[2]; + + rot[0][2] = column_2[0]; + rot[1][2] = column_2[1]; + rot[2][2] = column_2[2]; + + // We compute the field of view + + // line_1^column_0 -> vector of intersection line between + // y_screen=0 and x_camera=0 plane. + // column_2*(...) -> cos of the angle between Z vector et y_screen=0 plane + // * 2 -> field of view = 2 * half angle + + // We need some intermediate values. + Vec dummy = line_1^column_0; + dummy.normalize(); + qreal fov = acos(column_2*dummy) * 2.0; + + // We set the camera. + Quaternion q; + q.setFromRotationMatrix(rot); + setOrientation(q); + setPosition(cam_pos); + setFieldOfView(fov); +} + + +/* + // persp : projectionMatrix_[0] = f/aspectRatio(); +void Camera::setFromProjectionMatrix(const GLdouble* projectionMatrix) +{ + QString message; + if ((fabs(projectionMatrix[1]) > 1E-3) || + (fabs(projectionMatrix[2]) > 1E-3) || + (fabs(projectionMatrix[3]) > 1E-3) || + (fabs(projectionMatrix[4]) > 1E-3) || + (fabs(projectionMatrix[6]) > 1E-3) || + (fabs(projectionMatrix[7]) > 1E-3) || + (fabs(projectionMatrix[8]) > 1E-3) || + (fabs(projectionMatrix[9]) > 1E-3)) + message = "Non null coefficient in projection matrix - Aborting"; + else + if ((fabs(projectionMatrix[11]+1.0) < 1E-5) && (fabs(projectionMatrix[15]) < 1E-5)) + { + if (projectionMatrix[5] < 1E-4) + message="Negative field of view in Camera::setFromProjectionMatrix"; + else + setType(Camera::PERSPECTIVE); + } + else + if ((fabs(projectionMatrix[11]) < 1E-5) && (fabs(projectionMatrix[15]-1.0) < 1E-5)) + setType(Camera::ORTHOGRAPHIC); + else + message = "Unable to determine camera type in setFromProjectionMatrix - Aborting"; + + if (!message.isEmpty()) + { + qWarning(message); + return; + } + + switch (type()) + { + case Camera::PERSPECTIVE: + { + setFieldOfView(2.0 * atan(1.0/projectionMatrix[5])); + const qreal far = projectionMatrix[14] / (2.0 * (1.0 + projectionMatrix[10])); + const qreal near = (projectionMatrix[10]+1.0) / (projectionMatrix[10]-1.0) * far; + setSceneRadius((far-near)/2.0); + setSceneCenter(position() + (near + sceneRadius())*viewDirection()); + break; + } + case Camera::ORTHOGRAPHIC: + { + GLdouble w, h; + getOrthoWidthHeight(w,h); + projectionMatrix_[0] = 1.0/w; + projectionMatrix_[5] = 1.0/h; + projectionMatrix_[10] = -2.0/(ZFar - ZNear); + projectionMatrix_[11] = 0.0; + projectionMatrix_[14] = -(ZFar + ZNear)/(ZFar - ZNear); + projectionMatrix_[15] = 1.0; + // same as glOrtho( -w, w, -h, h, zNear(), zFar() ); + break; + } + } +} +*/ + +///////////////////////// Camera to world transform /////////////////////// + +/*! Same as cameraCoordinatesOf(), but with \c qreal[3] parameters (\p src and \p res may be identical pointers). */ +void Camera::getCameraCoordinatesOf(const qreal src[3], qreal res[3]) const +{ + Vec r = cameraCoordinatesOf(Vec(src)); + for (int i=0; i<3; ++i) + res[i] = r[i]; +} + +/*! Same as worldCoordinatesOf(), but with \c qreal[3] parameters (\p src and \p res may be identical pointers). */ +void Camera::getWorldCoordinatesOf(const qreal src[3], qreal res[3]) const +{ + Vec r = worldCoordinatesOf(Vec(src)); + for (int i=0; i<3; ++i) + res[i] = r[i]; +} + +/*! Fills \p viewport with the Camera OpenGL viewport. + +This method is mainly used in conjunction with \c gluProject, which requires such a viewport. +Returned values are (0, screenHeight(), screenWidth(), - screenHeight()), so that the origin is +located in the \e upper left corner of the window (Qt style coordinate system). */ +void Camera::getViewport(GLint viewport[4]) const +{ + viewport[0] = 0; + viewport[1] = screenHeight(); + viewport[2] = screenWidth(); + viewport[3] = -screenHeight(); +} + +/*! Returns the screen projected coordinates of a point \p src defined in the \p frame coordinate + system. + + When \p frame in \c NULL (default), \p src is expressed in the world coordinate system. + + The x and y coordinates of the returned Vec are expressed in pixel, (0,0) being the \e upper left + corner of the window. The z coordinate ranges between 0.0 (near plane) and 1.0 (excluded, far + plane). See the \c gluProject man page for details. + + unprojectedCoordinatesOf() performs the inverse transformation. + + See the screenCoordSystem example. + + This method only uses the intrinsic Camera parameters (see getModelViewMatrix(), + getProjectionMatrix() and getViewport()) and is completely independent of the OpenGL \c + GL_MODELVIEW, \c GL_PROJECTION and viewport matrices. You can hence define a virtual Camera and use + this method to compute projections out of a classical rendering context. + + \attention However, if your Camera is not attached to a QGLViewer (used for offscreen computations + for instance), make sure the Camera matrices are updated before calling this method. Call + computeModelViewMatrix() and computeProjectionMatrix() to do so. + + If you call this method several times with no change in the matrices, consider precomputing the + projection times modelview matrix to save computation time if required (\c P x \c M in the \c + gluProject man page). + + Here is the code corresponding to what this method does (kindly submitted by Robert W. Kuhn) : + \code + Vec project(Vec point) + { + GLint Viewport[4]; + GLdouble Projection[16], Modelview[16]; + GLdouble matrix[16]; + + // Precomputation begin + glGetIntegerv(GL_VIEWPORT , Viewport); + glGetDoublev (GL_MODELVIEW_MATRIX , Modelview); + glGetDoublev (GL_PROJECTION_MATRIX, Projection); + + for (unsigned short m=0; m<4; ++m) + { + for (unsigned short l=0; l<4; ++l) + { + qreal sum = 0.0; + for (unsigned short k=0; k<4; ++k) + sum += Projection[l+4*k]*Modelview[k+4*m]; + matrix[l+4*m] = sum; + } + } + // Precomputation end + + GLdouble v[4], vs[4]; + v[0]=point[0]; v[1]=point[1]; v[2]=point[2]; v[3]=1.0; + + vs[0]=matrix[0 ]*v[0] + matrix[4 ]*v[1] + matrix[8 ]*v[2] + matrix[12 ]*v[3]; + vs[1]=matrix[1 ]*v[0] + matrix[5 ]*v[1] + matrix[9 ]*v[2] + matrix[13 ]*v[3]; + vs[2]=matrix[2 ]*v[0] + matrix[6 ]*v[1] + matrix[10]*v[2] + matrix[14 ]*v[3]; + vs[3]=matrix[3 ]*v[0] + matrix[7 ]*v[1] + matrix[11]*v[2] + matrix[15 ]*v[3]; + + vs[0] /= vs[3]; + vs[1] /= vs[3]; + vs[2] /= vs[3]; + + vs[0] = vs[0] * 0.5 + 0.5; + vs[1] = vs[1] * 0.5 + 0.5; + vs[2] = vs[2] * 0.5 + 0.5; + + vs[0] = vs[0] * Viewport[2] + Viewport[0]; + vs[1] = vs[1] * Viewport[3] + Viewport[1]; + + return Vec(vs[0], Viewport[3]-vs[1], vs[2]); + } + \endcode + */ +Vec Camera::projectedCoordinatesOf(const Vec& src, const Frame* frame) const +{ + static GLint viewport[4]; + getViewport(viewport); + + Vec result; + if (frame) + { + const Vec tmp = frame->inverseCoordinatesOf(src); + result = project(tmp, projectionMatrix_, modelViewMatrix_, viewport); + } + else + result = project(src, projectionMatrix_, modelViewMatrix_, viewport); + + return result; +} + +/*! Returns the world unprojected coordinates of a point \p src defined in the screen coordinate + system. + + The \p src.x and \p src.y input values are expressed in pixels, (0,0) being the \e upper left corner + of the window. \p src.z is a depth value ranging in [0..1[ (respectively corresponding to the near + and far planes). Note that src.z is \e not a linear interpolation between zNear and zFar. + /code + src.z = zFar() / (zFar() - zNear()) * (1.0 - zNear() / z); + /endcode + Where z is the distance from the point you project to the camera, along the viewDirection(). + See the \c gluUnProject man page for details. + + The result is expressed in the \p frame coordinate system. When \p frame is \c NULL (default), the + result is expressed in the world coordinates system. The possible \p frame Frame::referenceFrame() + are taken into account. + + projectedCoordinatesOf() performs the inverse transformation. + + This method only uses the intrinsic Camera parameters (see getModelViewMatrix(), + getProjectionMatrix() and getViewport()) and is completely independent of the OpenGL \c + GL_MODELVIEW, \c GL_PROJECTION and viewport matrices. You can hence define a virtual Camera and use + this method to compute un-projections out of a classical rendering context. + + \attention However, if your Camera is not attached to a QGLViewer (used for offscreen computations + for instance), make sure the Camera matrices are updated before calling this method (use + computeModelViewMatrix(), computeProjectionMatrix()). See also setScreenWidthAndHeight(). + + This method is not computationally optimized. If you call it several times with no change in the + matrices, you should buffer the entire inverse projection matrix (modelview, projection and then + viewport) to speed-up the queries. See the \c gluUnProject man page for details. */ +Vec Camera::unprojectedCoordinatesOf(const Vec& src, const Frame* frame) const +{ + static GLint viewport[4]; + getViewport(viewport); + Vec point = unProject(src, projectionMatrix_, modelViewMatrix_, viewport); + if (frame) + return frame->coordinatesOf(point); + else + return point; +} + +/*! Same as projectedCoordinatesOf(), but with \c qreal parameters (\p src and \p res can be identical pointers). */ +void Camera::getProjectedCoordinatesOf(const qreal src[3], qreal res[3], const Frame* frame) const +{ + Vec r = projectedCoordinatesOf(Vec(src), frame); + for (int i=0; i<3; ++i) + res[i] = r[i]; +} + +/*! Same as unprojectedCoordinatesOf(), but with \c qreal parameters (\p src and \p res can be identical pointers). */ +void Camera::getUnprojectedCoordinatesOf(const qreal src[3], qreal res[3], const Frame* frame) const +{ + Vec r = unprojectedCoordinatesOf(Vec(src), frame); + for (int i=0; i<3; ++i) + res[i] = r[i]; +} + +///////////////////////////////////// KFI ///////////////////////////////////////// + +/*! Returns the KeyFrameInterpolator that defines the Camera path number \p i. + +If path \p i is not defined for this index, the method returns a \c NULL pointer. */ +KeyFrameInterpolator* Camera::keyFrameInterpolator(unsigned int i) const +{ + if (kfi_.contains(i)) + return kfi_[i]; + else + return NULL; +} + +/*! Sets the KeyFrameInterpolator that defines the Camera path of index \p i. + + The previous keyFrameInterpolator() is lost and should be deleted by the calling method if + needed. + + The KeyFrameInterpolator::interpolated() signal of \p kfi probably needs to be connected to the + Camera's associated QGLViewer::update() slot, so that when the Camera position is interpolated + using \p kfi, every interpolation step updates the display: + \code + myViewer.camera()->deletePath(3); + myViewer.camera()->setKeyFrameInterpolator(3, myKeyFrameInterpolator); + connect(myKeyFrameInterpolator, SIGNAL(interpolated()), myViewer, SLOT(update()); + \endcode + + \note These connections are done automatically when a Camera is attached to a QGLViewer, or when a + new KeyFrameInterpolator is defined using the QGLViewer::addKeyFrameKeyboardModifiers() and + QGLViewer::pathKey() (default is Alt+F[1-12]). See the keyboard page + for details. */ +void Camera::setKeyFrameInterpolator(unsigned int i, KeyFrameInterpolator* const kfi) +{ + if (kfi) + kfi_[i] = kfi; + else + kfi_.remove(i); +} + +/*! Adds the current Camera position() and orientation() as a keyFrame to the path number \p i. + +This method can also be used if you simply want to save a Camera point of view (a path made of a +single keyFrame). Use playPath() to make the Camera play the keyFrame path (resp. restore +the point of view). Use deletePath() to clear the path. + +The default keyboard shortcut for this method is Alt+F[1-12]. Set QGLViewer::pathKey() and +QGLViewer::addKeyFrameKeyboardModifiers(). + +If you use directly this method and the keyFrameInterpolator(i) does not exist, a new one is +created. Its KeyFrameInterpolator::interpolated() signal should then be connected to the +QGLViewer::update() slot (see setKeyFrameInterpolator()). */ +void Camera::addKeyFrameToPath(unsigned int i) +{ + if (!kfi_.contains(i)) + setKeyFrameInterpolator(i, new KeyFrameInterpolator(frame())); + + kfi_[i]->addKeyFrame(*(frame())); +} + +/*! Makes the Camera follow the path of keyFrameInterpolator() number \p i. + + If the interpolation is started, it stops it instead. + + This method silently ignores undefined (empty) paths (see keyFrameInterpolator()). + + The default keyboard shortcut for this method is F[1-12]. Set QGLViewer::pathKey() and + QGLViewer::playPathKeyboardModifiers(). */ +void Camera::playPath(unsigned int i) +{ + if (kfi_.contains(i)) { + if (kfi_[i]->interpolationIsStarted()) + kfi_[i]->stopInterpolation(); + else + kfi_[i]->startInterpolation(); + } +} + +/*! Resets the path of the keyFrameInterpolator() number \p i. + +If this path is \e not being played (see playPath() and +KeyFrameInterpolator::interpolationIsStarted()), resets it to its starting position (see +KeyFrameInterpolator::resetInterpolation()). If the path is played, simply stops interpolation. */ +void Camera::resetPath(unsigned int i) +{ + if (kfi_.contains(i)) { + if ((kfi_[i]->interpolationIsStarted())) + kfi_[i]->stopInterpolation(); + else + { + kfi_[i]->resetInterpolation(); + kfi_[i]->interpolateAtTime(kfi_[i]->interpolationTime()); + } + } +} + +/*! Deletes the keyFrameInterpolator() of index \p i. + +Disconnect the keyFrameInterpolator() KeyFrameInterpolator::interpolated() signal before deleting the +keyFrameInterpolator() if needed: +\code +disconnect(camera()->keyFrameInterpolator(i), SIGNAL(interpolated()), this, SLOT(update())); +camera()->deletePath(i); +\endcode */ +void Camera::deletePath(unsigned int i) +{ + if (kfi_.contains(i)) + { + kfi_[i]->stopInterpolation(); + delete kfi_[i]; + kfi_.remove(i); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +/*! Returns an XML \c QDomElement that represents the Camera. + + \p name is the name of the QDomElement tag. \p doc is the \c QDomDocument factory used to create + QDomElement. + + Concatenates the Camera parameters, the ManipulatedCameraFrame::domElement() and the paths' + KeyFrameInterpolator::domElement(). + + Use initFromDOMElement() to restore the Camera state from the resulting \c QDomElement. + + If you want to save the Camera state in a file, use: + \code + QDomDocument document("myCamera"); + doc.appendChild( myCamera->domElement("Camera", document) ); + + QFile f("myCamera.xml"); + if (f.open(IO_WriteOnly)) + { + QTextStream out(&f); + document.save(out, 2); + } + \endcode + + Note that the QGLViewer::camera() is automatically saved by QGLViewer::saveStateToFile() when a + QGLViewer is closed. Use QGLViewer::restoreStateFromFile() to restore it back. */ +QDomElement Camera::domElement(const QString& name, QDomDocument& document) const +{ + QDomElement de = document.createElement(name); + QDomElement paramNode = document.createElement("Parameters"); + paramNode.setAttribute("fieldOfView", QString::number(fieldOfView())); + paramNode.setAttribute("zNearCoefficient", QString::number(zNearCoefficient())); + paramNode.setAttribute("zClippingCoefficient", QString::number(zClippingCoefficient())); + paramNode.setAttribute("orthoCoef", QString::number(orthoCoef_)); + paramNode.setAttribute("sceneRadius", QString::number(sceneRadius())); + paramNode.appendChild(sceneCenter().domElement("SceneCenter", document)); + + switch (type()) + { + case Camera::PERSPECTIVE : paramNode.setAttribute("Type", "PERSPECTIVE"); break; + case Camera::ORTHOGRAPHIC : paramNode.setAttribute("Type", "ORTHOGRAPHIC"); break; + } + de.appendChild(paramNode); + + QDomElement stereoNode = document.createElement("Stereo"); + stereoNode.setAttribute("IODist", QString::number(IODistance())); + stereoNode.setAttribute("focusDistance", QString::number(focusDistance())); + stereoNode.setAttribute("physScreenWidth", QString::number(physicalScreenWidth())); + de.appendChild(stereoNode); + + de.appendChild(frame()->domElement("ManipulatedCameraFrame", document)); + + // KeyFrame paths + for (QMap::ConstIterator it = kfi_.begin(), end=kfi_.end(); it != end; ++it) + { + QDomElement kfNode = (it.value())->domElement("KeyFrameInterpolator", document); + kfNode.setAttribute("index", QString::number(it.key())); + de.appendChild(kfNode); + } + + return de; +} + +/*! Restores the Camera state from a \c QDomElement created by domElement(). + + Use the following code to retrieve a Camera state from a file created using domElement(): + \code + // Load DOM from file + QDomDocument document; + QFile f("myCamera.xml"); + if (f.open(IO_ReadOnly)) + { + document.setContent(&f); + f.close(); + } + + // Parse the DOM tree + QDomElement main = document.documentElement(); + myCamera->initFromDOMElement(main); + \endcode + + The frame() pointer is not modified by this method. The frame() state is however modified. + + \attention The original keyFrameInterpolator() are deleted and should be copied first if they are shared. */ +void Camera::initFromDOMElement(const QDomElement& element) +{ + QDomElement child=element.firstChild().toElement(); + + QMutableMapIterator it(kfi_); + while (it.hasNext()) { + it.next(); + deletePath(it.key()); + } + + while (!child.isNull()) + { + if (child.tagName() == "Parameters") + { + // #CONNECTION# Default values set in constructor + setFieldOfView(DomUtils::qrealFromDom(child, "fieldOfView", M_PI/4.0)); + setZNearCoefficient(DomUtils::qrealFromDom(child, "zNearCoefficient", 0.005)); + setZClippingCoefficient(DomUtils::qrealFromDom(child, "zClippingCoefficient", sqrt(3.0))); + orthoCoef_ = DomUtils::qrealFromDom(child, "orthoCoef", tan(fieldOfView()/2.0)); + setSceneRadius(DomUtils::qrealFromDom(child, "sceneRadius", sceneRadius())); + + setType(PERSPECTIVE); + QString type = child.attribute("Type", "PERSPECTIVE"); + if (type == "PERSPECTIVE") setType(Camera::PERSPECTIVE); + if (type == "ORTHOGRAPHIC") setType(Camera::ORTHOGRAPHIC); + + QDomElement child2=child.firstChild().toElement(); + while (!child2.isNull()) + { + /* Although the scene does not change when a camera is loaded, restore the saved center and radius values. + Mainly useful when a the viewer is restored on startup, with possible additional cameras. */ + if (child2.tagName() == "SceneCenter") + setSceneCenter(Vec(child2)); + + child2 = child2.nextSibling().toElement(); + } + } + + if (child.tagName() == "ManipulatedCameraFrame") + frame()->initFromDOMElement(child); + + if (child.tagName() == "Stereo") + { + setIODistance(DomUtils::qrealFromDom(child, "IODist", 0.062)); + setFocusDistance(DomUtils::qrealFromDom(child, "focusDistance", focusDistance())); + setPhysicalScreenWidth(DomUtils::qrealFromDom(child, "physScreenWidth", 0.5)); + } + + if (child.tagName() == "KeyFrameInterpolator") + { + unsigned int index = DomUtils::uintFromDom(child, "index", 0); + setKeyFrameInterpolator(index, new KeyFrameInterpolator(frame())); + if (keyFrameInterpolator(index)) + keyFrameInterpolator(index)->initFromDOMElement(child); + } + + child = child.nextSibling().toElement(); + } +} + +/*! Gives the coefficients of a 3D half-line passing through the Camera eye and pixel (x,y). + + The origin of the half line (eye position) is stored in \p orig, while \p dir contains the properly + oriented and normalized direction of the half line. + + \p x and \p y are expressed in Qt format (origin in the upper left corner). Use screenHeight() - y + to convert to OpenGL units. + + This method is useful for analytical intersection in a selection method. + + See the select example for an illustration. */ +void Camera::convertClickToLine(const QPoint& pixel, Vec& orig, Vec& dir) const +{ + switch (type()) + { + case Camera::PERSPECTIVE: + orig = position(); + dir = Vec( ((2.0 * pixel.x() / screenWidth()) - 1.0) * tan(fieldOfView()/2.0) * aspectRatio(), + ((2.0 * (screenHeight()-pixel.y()) / screenHeight()) - 1.0) * tan(fieldOfView()/2.0), + -1.0 ); + dir = worldCoordinatesOf(dir) - orig; + dir.normalize(); + break; + + case Camera::ORTHOGRAPHIC: + { + GLdouble w,h; + getOrthoWidthHeight(w,h); + orig = Vec((2.0 * pixel.x() / screenWidth() - 1.0)*w, -(2.0 * pixel.y() / screenHeight() - 1.0)*h, 0.0); + orig = worldCoordinatesOf(orig); + dir = viewDirection(); + break; + } + } +} + + +/*! Returns the 6 plane equations of the Camera frustum. + +The six 4-component vectors of \p coef respectively correspond to the left, right, near, far, top +and bottom Camera frustum planes. Each vector holds a plane equation of the form: +\code +a*x + b*y + c*z + d = 0 +\endcode +where \c a, \c b, \c c and \c d are the 4 components of each vector, in that order. + +See the frustumCulling example for an application. + +This format is compatible with the \c glClipPlane() function. One camera frustum plane can hence be +applied in an other viewer to visualize the culling results: +\code + // Retrieve plane equations + GLdouble coef[6][4]; + mainViewer->camera()->getFrustumPlanesCoefficients(coef); + + // These two additional clipping planes (which must have been enabled) + // will reproduce the mainViewer's near and far clipping. + glClipPlane(GL_CLIP_PLANE0, coef[2]); + glClipPlane(GL_CLIP_PLANE1, coef[3]); +\endcode */ +void Camera::getFrustumPlanesCoefficients(GLdouble coef[6][4]) const +{ + // Computed once and for all + const Vec pos = position(); + const Vec viewDir = viewDirection(); + const Vec up = upVector(); + const Vec right = rightVector(); + const qreal posViewDir = pos * viewDir; + + static Vec normal[6]; + static GLdouble dist[6]; + + switch (type()) + { + case Camera::PERSPECTIVE : + { + const qreal hhfov = horizontalFieldOfView() / 2.0; + const qreal chhfov = cos(hhfov); + const qreal shhfov = sin(hhfov); + normal[0] = - shhfov * viewDir; + normal[1] = normal[0] + chhfov * right; + normal[0] = normal[0] - chhfov * right; + + normal[2] = -viewDir; + normal[3] = viewDir; + + const qreal hfov = fieldOfView() / 2.0; + const qreal chfov = cos(hfov); + const qreal shfov = sin(hfov); + normal[4] = - shfov * viewDir; + normal[5] = normal[4] - chfov * up; + normal[4] = normal[4] + chfov * up; + + for (int i=0; i<2; ++i) + dist[i] = pos * normal[i]; + for (int j=4; j<6; ++j) + dist[j] = pos * normal[j]; + + // Natural equations are: + // dist[0,1,4,5] = pos * normal[0,1,4,5]; + // dist[2] = (pos + zNear() * viewDir) * normal[2]; + // dist[3] = (pos + zFar() * viewDir) * normal[3]; + + // 2 times less computations using expanded/merged equations. Dir vectors are normalized. + const qreal posRightCosHH = chhfov * pos * right; + dist[0] = -shhfov * posViewDir; + dist[1] = dist[0] + posRightCosHH; + dist[0] = dist[0] - posRightCosHH; + const qreal posUpCosH = chfov * pos * up; + dist[4] = - shfov * posViewDir; + dist[5] = dist[4] - posUpCosH; + dist[4] = dist[4] + posUpCosH; + + break; + } + case Camera::ORTHOGRAPHIC : + normal[0] = -right; + normal[1] = right; + normal[4] = up; + normal[5] = -up; + + GLdouble hw, hh; + getOrthoWidthHeight(hw, hh); + dist[0] = (pos - hw * right) * normal[0]; + dist[1] = (pos + hw * right) * normal[1]; + dist[4] = (pos + hh * up) * normal[4]; + dist[5] = (pos - hh * up) * normal[5]; + break; + } + + // Front and far planes are identical for both camera types. + normal[2] = -viewDir; + normal[3] = viewDir; + dist[2] = -posViewDir - zNear(); + dist[3] = posViewDir + zFar(); + + for (int i=0; i<6; ++i) + { + coef[i][0] = GLdouble(normal[i].x); + coef[i][1] = GLdouble(normal[i].y); + coef[i][2] = GLdouble(normal[i].z); + coef[i][3] = dist[i]; + } +} + +void Camera::onFrameModified() { + projectionMatrixIsUpToDate_ = false; + modelViewMatrixIsUpToDate_ = false; +} diff --git a/QGLViewer/camera.h b/QGLViewer/camera.h new file mode 100644 index 0000000..f5d5ae1 --- /dev/null +++ b/QGLViewer/camera.h @@ -0,0 +1,505 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#ifndef QGLVIEWER_CAMERA_H +#define QGLVIEWER_CAMERA_H + +#include +#include "keyFrameInterpolator.h" +class QGLViewer; + +namespace qglviewer { + +class ManipulatedCameraFrame; + +/*! \brief A perspective or orthographic camera. + \class Camera camera.h QGLViewer/camera.h + + A Camera defines some intrinsic parameters (fieldOfView(), position(), viewDirection(), + upVector()...) and useful positioning tools that ease its placement (showEntireScene(), + fitSphere(), lookAt()...). It exports its associated OpenGL projection and modelview matrices and + can interactively be modified using the mouse. + +

Mouse manipulation

+ + The position() and orientation() of the Camera are defined by a ManipulatedCameraFrame (retrieved + using frame()). These methods are just convenient wrappers to the equivalent Frame methods. This + also means that the Camera frame() can be attached to a Frame::referenceFrame() which enables + complex Camera setups. + + Different displacements can be performed using the mouse. The list of possible actions is defined + by the QGLViewer::MouseAction enum. Use QGLViewer::setMouseBinding() to attach a specific action + to an arbitrary mouse button-state key binding. These actions are detailed in the mouse page. + + The default button binding are: QGLViewer::ROTATE (left), QGLViewer::ZOOM (middle) and + QGLViewer::TRANSLATE (right). With this configuration, the Camera \e observes a scene and rotates + around its pivotPoint(). You can switch between this mode and a fly mode using the + QGLViewer::CAMERA_MODE (see QGLViewer::toggleCameraMode()) keyboard shortcut (default is 'Space'). + +

Other functionalities

+ + The type() of the Camera can be Camera::ORTHOGRAPHIC or Camera::PERSPECTIVE (see Type()). + fieldOfView() is meaningless with Camera::ORTHOGRAPHIC. + + The near and far planes of the Camera are fitted to the scene and determined from + QGLViewer::sceneRadius(), QGLViewer::sceneCenter() and zClippingCoefficient() by the zNear() and + zFar() methods. Reasonable values on the scene extends hence have to be provided to the QGLViewer + in order for the Camera to correctly display the scene. High level positioning methods also use + this information (showEntireScene(), centerScene()...). + + A Camera holds KeyFrameInterpolator that can be used to save Camera positions and paths. You can + interactively addKeyFrameToPath() to a given path using the default \c Alt+F[1-12] shortcuts. Use + playPath() to make the Camera follow the path (default shortcut is F[1-12]). See the keyboard page for details on key customization. + + Use cameraCoordinatesOf() and worldCoordinatesOf() to convert to and from the Camera frame() + coordinate system. projectedCoordinatesOf() and unprojectedCoordinatesOf() will convert from + screen to 3D coordinates. convertClickToLine() is very useful for analytical object selection. + + Stereo display is possible on machines with quad buffer capabilities (with Camera::PERSPECTIVE + type() only). Test the stereoViewer example to check. + + A Camera can also be used outside of a QGLViewer or even without OpenGL for its coordinate system + conversion capabilities. Note however that some of them explicitly rely on the presence of a + Z-buffer. \nosubgrouping */ +class QGLVIEWER_EXPORT Camera : public QObject +{ +#ifndef DOXYGEN + friend class ::QGLViewer; +#endif + + Q_OBJECT + +public: + Camera(); + virtual ~Camera(); + + Camera(const Camera& camera); + Camera& operator=(const Camera& camera); + + + /*! Enumerates the two possible types of Camera. + + See type() and setType(). This type mainly defines different Camera projection matrix (see + loadProjectionMatrix()). Many other methods (pointUnderPixel(), convertClickToLine(), + projectedCoordinatesOf(), pixelGLRatio()...) are affected by this Type. */ + enum Type { PERSPECTIVE, ORTHOGRAPHIC }; + + /*! @name Position and orientation */ + //@{ +public: + Vec position() const; + Vec upVector() const; + Vec viewDirection() const; + Vec rightVector() const; + Quaternion orientation() const; + + void setFromModelViewMatrix(const GLdouble* const modelViewMatrix); + void setFromProjectionMatrix(const qreal matrix[12]); + +public Q_SLOTS: + void setPosition(const Vec& pos); + void setOrientation(const Quaternion& q); + void setOrientation(qreal theta, qreal phi); + void setUpVector(const Vec& up, bool noMove=true); + void setViewDirection(const Vec& direction); + //@} + + + /*! @name Positioning tools */ + //@{ +public Q_SLOTS: + void lookAt(const Vec& target); + void showEntireScene(); + void fitSphere(const Vec& center, qreal radius); + void fitBoundingBox(const Vec& min, const Vec& max); + void fitScreenRegion(const QRect& rectangle); + void centerScene(); + void interpolateToZoomOnPixel(const QPoint& pixel); + void interpolateToFitScene(); + void interpolateTo(const Frame& fr, qreal duration); + //@} + + + /*! @name Frustum */ + //@{ +public: + /*! Returns the Camera::Type of the Camera. + + Set by setType(). Mainly used by loadProjectionMatrix(). + + A Camera::PERSPECTIVE Camera uses a classical projection mainly defined by its fieldOfView(). + + With a Camera::ORTHOGRAPHIC type(), the fieldOfView() is meaningless and the width and height of + the Camera frustum are inferred from the distance to the pivotPoint() using + getOrthoWidthHeight(). + + Both types use zNear() and zFar() (to define their clipping planes) and aspectRatio() (for + frustum shape). */ + Type type() const { return type_; } + + /*! Returns the vertical field of view of the Camera (in radians). + + Value is set using setFieldOfView(). Default value is pi/4 radians. This value is meaningless if + the Camera type() is Camera::ORTHOGRAPHIC. + + The field of view corresponds the one used in \c gluPerspective (see manual). It sets the Y + (vertical) aperture of the Camera. The X (horizontal) angle is inferred from the window aspect + ratio (see aspectRatio() and horizontalFieldOfView()). + + Use setFOVToFitScene() to adapt the fieldOfView() to a given scene. */ + qreal fieldOfView() const { return fieldOfView_; } + + /*! Returns the horizontal field of view of the Camera (in radians). + + Value is set using setHorizontalFieldOfView() or setFieldOfView(). These values + are always linked by: + \code + horizontalFieldOfView() = 2.0 * atan ( tan(fieldOfView()/2.0) * aspectRatio() ). + \endcode */ + qreal horizontalFieldOfView() const { return 2.0 * atan ( tan(fieldOfView()/2.0) * aspectRatio() ); } + + /*! Returns the Camera aspect ratio defined by screenWidth() / screenHeight(). + + When the Camera is attached to a QGLViewer, these values and hence the aspectRatio() are + automatically fitted to the viewer's window aspect ratio using setScreenWidthAndHeight(). */ + qreal aspectRatio() const { return screenWidth_ / static_cast(screenHeight_); } + /*! Returns the width (in pixels) of the Camera screen. + + Set using setScreenWidthAndHeight(). This value is automatically fitted to the QGLViewer's + window dimensions when the Camera is attached to a QGLViewer. See also QGLWidget::width() [TODO Update with QOpenGLWidget] */ + int screenWidth() const { return screenWidth_; } + /*! Returns the height (in pixels) of the Camera screen. + + Set using setScreenWidthAndHeight(). This value is automatically fitted to the QGLViewer's + window dimensions when the Camera is attached to a QGLViewer. See also QGLWidget::height() [TODO Update with QOpenGLWidget] */ + int screenHeight() const { return screenHeight_; } + void getViewport(GLint viewport[4]) const; + qreal pixelGLRatio(const Vec& position) const; + + /*! Returns the coefficient which is used to set zNear() when the Camera is inside the sphere + defined by sceneCenter() and zClippingCoefficient() * sceneRadius(). + + In that case, the zNear() value is set to zNearCoefficient() * zClippingCoefficient() * + sceneRadius(). See the zNear() documentation for details. + + Default value is 0.005, which is appropriate for most applications. In case you need a high + dynamic ZBuffer precision, you can increase this value (~0.1). A lower value will prevent + clipping of very close objects at the expense of a worst Z precision. + + Only meaningful when Camera type is Camera::PERSPECTIVE. */ + qreal zNearCoefficient() const { return zNearCoef_; } + /*! Returns the coefficient used to position the near and far clipping planes. + + The near (resp. far) clipping plane is positioned at a distance equal to zClippingCoefficient() * + sceneRadius() in front of (resp. behind) the sceneCenter(). This garantees an optimal use of + the z-buffer range and minimizes aliasing. See the zNear() and zFar() documentations. + + Default value is square root of 3.0 (so that a cube of size sceneRadius() is not clipped). + + However, since the sceneRadius() is used for other purposes (see showEntireScene(), flySpeed(), + ...) and you may want to change this value to define more precisely the location of the clipping + planes. See also zNearCoefficient(). + + For a total control on clipping planes' positions, an other option is to overload the zNear() + and zFar() methods. See the standardCamera example. + + \attention When QGLViewer::cameraPathAreEdited(), this value is set to 5.0 so that the Camera + paths are not clipped. The previous zClippingCoefficient() value is restored back when you leave + this mode. */ + qreal zClippingCoefficient() const { return zClippingCoef_; } + + virtual qreal zNear() const; + virtual qreal zFar() const; + virtual void getOrthoWidthHeight(GLdouble& halfWidth, GLdouble& halfHeight) const; + void getFrustumPlanesCoefficients(GLdouble coef[6][4]) const; + +public Q_SLOTS: + void setType(Type type); + + void setFieldOfView(qreal fov); + + /*! Sets the horizontalFieldOfView() of the Camera (in radians). + + horizontalFieldOfView() and fieldOfView() are linked by the aspectRatio(). This method actually + calls setFieldOfView(( 2.0 * atan (tan(hfov / 2.0) / aspectRatio()) )) so that a call to + horizontalFieldOfView() returns the expected value. */ + void setHorizontalFieldOfView(qreal hfov) { setFieldOfView( 2.0 * atan (tan(hfov / 2.0) / aspectRatio()) ); } + + void setFOVToFitScene(); + + /*! Defines the Camera aspectRatio(). + + This value is actually inferred from the screenWidth() / screenHeight() ratio. You should use + setScreenWidthAndHeight() instead. + + This method might however be convenient when the Camera is not associated with a QGLViewer. It + actually sets the screenHeight() to 100 and the screenWidth() accordingly. See also + setFOVToFitScene(). + + \note If you absolutely need an aspectRatio() that does not correspond to your viewer's window + dimensions, overload loadProjectionMatrix() or multiply the created GL_PROJECTION matrix by a + scaled diagonal matrix in your QGLViewer::draw() method. */ + void setAspectRatio(qreal aspect) { setScreenWidthAndHeight(int(100.0*aspect), 100); } + + void setScreenWidthAndHeight(int width, int height); + /*! Sets the zNearCoefficient() value. */ + void setZNearCoefficient(qreal coef) { zNearCoef_ = coef; projectionMatrixIsUpToDate_ = false; } + /*! Sets the zClippingCoefficient() value. */ + void setZClippingCoefficient(qreal coef) { zClippingCoef_ = coef; projectionMatrixIsUpToDate_ = false; } + //@} + + + /*! @name Scene radius and center */ + //@{ +public: + /*! Returns the radius of the scene observed by the Camera. + + You need to provide such an approximation of the scene dimensions so that the Camera can adapt + its zNear() and zFar() values. See the sceneCenter() documentation. + + See also setSceneBoundingBox(). + + Note that QGLViewer::sceneRadius() (resp. QGLViewer::setSceneRadius()) simply call this method + (resp. setSceneRadius()) on its associated QGLViewer::camera(). */ + qreal sceneRadius() const { return sceneRadius_; } + + /*! Returns the position of the scene center, defined in the world coordinate system. + + The scene observed by the Camera should be roughly centered on this position, and included in a + sceneRadius() sphere. This approximate description of the scene permits a zNear() and zFar() + clipping planes definition, and allows convenient positioning methods such as showEntireScene(). + + Default value is (0,0,0) (world origin). Use setSceneCenter() to change it. See also + setSceneBoundingBox(). + + Note that QGLViewer::sceneCenter() (resp. QGLViewer::setSceneCenter()) simply calls this method + (resp. setSceneCenter()) on its associated QGLViewer::camera(). */ + Vec sceneCenter() const { return sceneCenter_; } + qreal distanceToSceneCenter() const; + +public Q_SLOTS: + void setSceneRadius(qreal radius); + void setSceneCenter(const Vec& center); + bool setSceneCenterFromPixel(const QPoint& pixel); + void setSceneBoundingBox(const Vec& min, const Vec& max); + //@} + + + /*! @name Pivot Point */ + //@{ +public Q_SLOTS: + void setPivotPoint(const Vec& point); + bool setPivotPointFromPixel(const QPoint& pixel); + +public: + Vec pivotPoint() const; + +#ifndef DOXYGEN +public Q_SLOTS: + void setRevolveAroundPoint(const Vec& point); + bool setRevolveAroundPointFromPixel(const QPoint& pixel); +public: + Vec revolveAroundPoint() const; +#endif + //@} + + + /*! @name Associated frame */ + //@{ +public: + /*! Returns the ManipulatedCameraFrame attached to the Camera. + + This ManipulatedCameraFrame defines its position() and orientation() and can translate mouse + events into Camera displacement. Set using setFrame(). */ + ManipulatedCameraFrame* frame() const { return frame_; } +public Q_SLOTS: + void setFrame(ManipulatedCameraFrame* const mcf); + //@} + + + /*! @name KeyFramed paths */ + //@{ +public: + KeyFrameInterpolator* keyFrameInterpolator(unsigned int i) const; + +public Q_SLOTS: + void setKeyFrameInterpolator(unsigned int i, KeyFrameInterpolator* const kfi); + + virtual void addKeyFrameToPath(unsigned int i); + virtual void playPath(unsigned int i); + virtual void deletePath(unsigned int i); + virtual void resetPath(unsigned int i); + //@} + + + /*! @name OpenGL matrices */ + //@{ +public: + virtual void loadProjectionMatrix(bool reset=true) const; + virtual void loadModelViewMatrix(bool reset=true) const; + void computeProjectionMatrix() const; + void computeModelViewMatrix() const; + + virtual void loadModelViewMatrixStereo(bool leftBuffer=true) const; + + void getProjectionMatrix(GLfloat m[16]) const; + void getProjectionMatrix(GLdouble m[16]) const; + void getProjectionMatrix(QMatrix4x4& m) const; + + void getModelViewMatrix(GLfloat m[16]) const; + void getModelViewMatrix(GLdouble m[16]) const; + void getModelViewMatrix(QMatrix4x4& m) const; + + void getModelViewProjectionMatrix(GLfloat m[16]) const; + void getModelViewProjectionMatrix(GLdouble m[16]) const; + void getModelViewProjectionMatrix(QMatrix4x4& m) const; + //@} + + + /*! @name World to Camera coordinate systems conversions */ + //@{ +public: + Vec cameraCoordinatesOf(const Vec& src) const; + Vec worldCoordinatesOf(const Vec& src) const; + void getCameraCoordinatesOf(const qreal src[3], qreal res[3]) const; + void getWorldCoordinatesOf(const qreal src[3], qreal res[3]) const; + //@} + + + /*! @name 2D screen to 3D world coordinate systems conversions */ + //@{ +public: + Vec projectedCoordinatesOf(const Vec& src, const Frame* frame=NULL) const; + Vec unprojectedCoordinatesOf(const Vec& src, const Frame* frame=NULL) const; + void getProjectedCoordinatesOf(const qreal src[3], qreal res[3], const Frame* frame=NULL) const; + void getUnprojectedCoordinatesOf(const qreal src[3], qreal res[3], const Frame* frame=NULL) const; + void convertClickToLine(const QPoint& pixel, Vec& orig, Vec& dir) const; + Vec pointUnderPixel(const QPoint& pixel, bool& found) const; + //@} + + + /*! @name Fly speed */ + //@{ +public: + qreal flySpeed() const; +public Q_SLOTS: + void setFlySpeed(qreal speed); + //@} + + + /*! @name Stereo parameters */ + //@{ +public: + /*! Returns the user's inter-ocular distance (in meters). Default value is 0.062m, which fits most people. + + loadProjectionMatrixStereo() uses this value to define the Camera offset and frustum. See + setIODistance(). */ + qreal IODistance() const { return IODistance_; } + + /*! Returns the physical distance between the user's eyes and the screen (in meters). + + physicalDistanceToScreen() and focusDistance() represent the same distance. The former is + expressed in physical real world units, while the latter is expressed in OpenGL virtual world + units. + + This is a helper function. It simply returns physicalScreenWidth() / 2.0 / tan(horizontalFieldOfView() / 2.0); */ + qreal physicalDistanceToScreen() const { return physicalScreenWidth() / 2.0 / tan(horizontalFieldOfView() / 2.0); } + + /*! Returns the physical screen width, in meters. Default value is 0.5m (average monitor width). + + Used for stereo display only (see loadModelViewMatrixStereo() and loadProjectionMatrixStereo()). + Set using setPhysicalScreenWidth(). */ + qreal physicalScreenWidth() const { return physicalScreenWidth_; } + + /*! Returns the focus distance used by stereo display, expressed in OpenGL units. + + This is the distance in the virtual world between the Camera and the plane where the horizontal + stereo parallax is null (the stereo left and right cameras' lines of sigth cross at this distance). + + This distance is the virtual world equivalent of the real-world physicalDistanceToScreen(). + + \attention This value is modified by QGLViewer::setSceneRadius(), setSceneRadius() and + setFieldOfView(). When one of these values is modified, focusDistance() is set to sceneRadius() + / tan(fieldOfView()/2), which provides good results. */ + qreal focusDistance() const { return focusDistance_; } +public Q_SLOTS: + /*! Sets the IODistance(). */ + void setIODistance(qreal distance) { IODistance_ = distance; } + +#ifndef DOXYGEN + /*! This method is deprecated. Use setPhysicalScreenWidth() instead. */ + void setPhysicalDistanceToScreen(qreal distance) { Q_UNUSED(distance); qWarning("setPhysicalDistanceToScreen is deprecated, use setPhysicalScreenWidth instead"); } +#endif + + /*! Sets the physical screen (monitor or projected wall) width (in meters). */ + void setPhysicalScreenWidth(qreal width) { physicalScreenWidth_ = width; } + + /*! Sets the focusDistance(), in OpenGL scene units. */ + void setFocusDistance(qreal distance) { focusDistance_ = distance; } + //@} + + + /*! @name XML representation */ + //@{ +public: + virtual QDomElement domElement(const QString& name, QDomDocument& document) const; +public Q_SLOTS: + virtual void initFromDOMElement(const QDomElement& element); + //@} + + +private Q_SLOTS: + void onFrameModified(); + +private: + // F r a m e + ManipulatedCameraFrame* frame_; + + // C a m e r a p a r a m e t e r s + int screenWidth_, screenHeight_; // size of the window, in pixels + qreal fieldOfView_; // in radians + Vec sceneCenter_; + qreal sceneRadius_; // OpenGL units + qreal zNearCoef_; + qreal zClippingCoef_; + qreal orthoCoef_; + Type type_; // PERSPECTIVE or ORTHOGRAPHIC + mutable GLdouble modelViewMatrix_[16]; // Buffered model view matrix. + mutable bool modelViewMatrixIsUpToDate_; + mutable GLdouble projectionMatrix_[16]; // Buffered projection matrix. + mutable bool projectionMatrixIsUpToDate_; + + // S t e r e o p a r a m e t e r s + qreal IODistance_; // inter-ocular distance, in meters + qreal focusDistance_; // in scene units + qreal physicalScreenWidth_; // in meters + + // P o i n t s o f V i e w s a n d K e y F r a m e s + QMap kfi_; + KeyFrameInterpolator* interpolationKfi_; +}; + +} // namespace qglviewer + +#endif // QGLVIEWER_CAMERA_H diff --git a/QGLViewer/config.h b/QGLViewer/config.h new file mode 100644 index 0000000..c288242 --- /dev/null +++ b/QGLViewer/config.h @@ -0,0 +1,97 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +/////////////////////////////////////////////////////////////////// +// libQGLViewer configuration file // +// Modify these settings according to your local configuration // +/////////////////////////////////////////////////////////////////// + +#ifndef QGLVIEWER_CONFIG_H +#define QGLVIEWER_CONFIG_H + +#define QGLVIEWER_VERSION 0x020603 + +// Needed for Qt < 4 (?) +#ifndef QT_CLEAN_NAMESPACE +# define QT_CLEAN_NAMESPACE +#endif + +// Get QT_VERSION and other Qt flags +#include + +#if QT_VERSION < 0x040000 +Error : libQGLViewer requires a minimum Qt version of 4.0 +#endif + +// Win 32 DLL export macros +#ifdef Q_OS_WIN32 +# ifndef M_PI +# define M_PI 3.14159265358979323846 +# endif +# ifndef QGLVIEWER_STATIC +# ifdef CREATE_QGLVIEWER_DLL +# if QT_VERSION >= 0x040500 +# define QGLVIEWER_EXPORT Q_DECL_EXPORT +# else +# define QGLVIEWER_EXPORT __declspec(dllexport) +# endif +# else +# if QT_VERSION >= 0x040500 +# define QGLVIEWER_EXPORT Q_DECL_IMPORT +# else +# define QGLVIEWER_EXPORT __declspec(dllimport) +# endif +# endif +# endif +# ifndef __MINGW32__ +# pragma warning( disable : 4251 ) // DLL interface, needed with Visual 6 +# pragma warning( disable : 4786 ) // identifier truncated to 255 in browser information (Visual 6). +# endif +#endif // Q_OS_WIN32 + +// For other architectures, this macro is empty +#ifndef QGLVIEWER_EXPORT +# define QGLVIEWER_EXPORT +#endif + +// OpenGL includes - Included here and hence shared by all the files that need OpenGL headers. +# include + +// Container classes interfaces changed a lot in Qt. +// Compatibility patches are all grouped here. +#include +#include + +// For deprecated methods +// #define __WHERE__ "In file "<<__FILE__<<", line "<<__LINE__<<": " +// #define orientationAxisAngle(x,y,z,a) { std::cout << __WHERE__ << "getOrientationAxisAngle()." << std::endl; exit(0); } + +// Patch for gcc version <= 2.95. Seems to no longer be needed with recent Qt versions. +// Uncomment these lines if you have error message dealing with operator << on QStrings +// #if defined(__GNUC__) && defined(__GNUC_MINOR__) && (__GNUC__ < 3) && (__GNUC_MINOR__ < 96) +// # include +// # include +// std::ostream& operator<<(std::ostream& out, const QString& str) +// { out << str.latin1(); return out; } +// #endif + +#endif // QGLVIEWER_CONFIG_H diff --git a/QGLViewer/constraint.cpp b/QGLViewer/constraint.cpp new file mode 100644 index 0000000..fbb9a6c --- /dev/null +++ b/QGLViewer/constraint.cpp @@ -0,0 +1,291 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "constraint.h" +#include "frame.h" +#include "camera.h" +#include "manipulatedCameraFrame.h" + +using namespace qglviewer; +using namespace std; + +//////////////////////////////////////////////////////////////////////////////// +// Constraint // +//////////////////////////////////////////////////////////////////////////////// + +/*! Default constructor. + +translationConstraintType() and rotationConstraintType() are set to AxisPlaneConstraint::FREE. +translationConstraintDirection() and rotationConstraintDirection() are set to (0,0,0). */ +AxisPlaneConstraint::AxisPlaneConstraint() + : translationConstraintType_(FREE), rotationConstraintType_(FREE) +{ + // Do not use set since setRotationConstraintType needs a read. +} + +/*! Simply calls setTranslationConstraintType() and setTranslationConstraintDirection(). */ +void AxisPlaneConstraint::setTranslationConstraint(Type type, const Vec& direction) +{ + setTranslationConstraintType(type); + setTranslationConstraintDirection(direction); +} + +/*! Defines the translationConstraintDirection(). The coordinate system where \p direction is expressed depends on your class implementation. */ +void AxisPlaneConstraint::setTranslationConstraintDirection(const Vec& direction) +{ + if ((translationConstraintType()!=AxisPlaneConstraint::FREE) && (translationConstraintType()!=AxisPlaneConstraint::FORBIDDEN)) + { + const qreal norm = direction.norm(); + if (norm < 1E-8) + { + qWarning("AxisPlaneConstraint::setTranslationConstraintDir: null vector for translation constraint"); + translationConstraintType_ = AxisPlaneConstraint::FREE; + } + else + translationConstraintDir_ = direction/norm; + } +} + +/*! Simply calls setRotationConstraintType() and setRotationConstraintDirection(). */ +void AxisPlaneConstraint::setRotationConstraint(Type type, const Vec& direction) +{ + setRotationConstraintType(type); + setRotationConstraintDirection(direction); +} + +/*! Defines the rotationConstraintDirection(). The coordinate system where \p direction is expressed depends on your class implementation. */ +void AxisPlaneConstraint::setRotationConstraintDirection(const Vec& direction) +{ + if ((rotationConstraintType()!=AxisPlaneConstraint::FREE) && (rotationConstraintType()!=AxisPlaneConstraint::FORBIDDEN)) + { + const qreal norm = direction.norm(); + if (norm < 1E-8) + { + qWarning("AxisPlaneConstraint::setRotationConstraintDir: null vector for rotation constraint"); + rotationConstraintType_ = AxisPlaneConstraint::FREE; + } + else + rotationConstraintDir_ = direction/norm; + } +} + +/*! Set the Type() of the rotationConstraintType(). Default is AxisPlaneConstraint::FREE. + + Depending on this value, the Frame will freely rotate (AxisPlaneConstraint::FREE), will only be able + to rotate around an axis (AxisPlaneConstraint::AXIS), or will not able to rotate at all + (AxisPlaneConstraint::FORBIDDEN). + + Use Frame::setOrientation() to define the orientation of the constrained Frame before it gets + constrained. + + \attention An AxisPlaneConstraint::PLANE Type() is not meaningful for rotational constraints and + will be ignored. */ +void AxisPlaneConstraint::setRotationConstraintType(Type type) +{ + if (rotationConstraintType() == AxisPlaneConstraint::PLANE) + { + qWarning("AxisPlaneConstraint::setRotationConstraintType: the PLANE type cannot be used for a rotation constraints"); + return; + } + + rotationConstraintType_ = type; +} + + +//////////////////////////////////////////////////////////////////////////////// +// LocalConstraint // +//////////////////////////////////////////////////////////////////////////////// + +/*! Depending on translationConstraintType(), constrain \p translation to be along an axis or + limited to a plane defined in the Frame local coordinate system by + translationConstraintDirection(). */ +void LocalConstraint::constrainTranslation(Vec& translation, Frame* const frame) +{ + Vec proj; + switch (translationConstraintType()) + { + case AxisPlaneConstraint::FREE: + break; + case AxisPlaneConstraint::PLANE: + proj = frame->rotation().rotate(translationConstraintDirection()); + translation.projectOnPlane(proj); + break; + case AxisPlaneConstraint::AXIS: + proj = frame->rotation().rotate(translationConstraintDirection()); + translation.projectOnAxis(proj); + break; + case AxisPlaneConstraint::FORBIDDEN: + translation = Vec(0.0, 0.0, 0.0); + break; + } +} + +/*! When rotationConstraintType() is AxisPlaneConstraint::AXIS, constrain \p rotation to be a rotation + around an axis whose direction is defined in the Frame local coordinate system by + rotationConstraintDirection(). */ +void LocalConstraint::constrainRotation(Quaternion& rotation, Frame* const) +{ + switch (rotationConstraintType()) + { + case AxisPlaneConstraint::FREE: + break; + case AxisPlaneConstraint::PLANE: + break; + case AxisPlaneConstraint::AXIS: + { + Vec axis = rotationConstraintDirection(); + Vec quat = Vec(rotation[0], rotation[1], rotation[2]); + quat.projectOnAxis(axis); + rotation = Quaternion(quat, 2.0*acos(rotation[3])); + } + break; + case AxisPlaneConstraint::FORBIDDEN: + rotation = Quaternion(); // identity + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// WorldConstraint // +//////////////////////////////////////////////////////////////////////////////// + +/*! Depending on translationConstraintType(), constrain \p translation to be along an axis or + limited to a plane defined in the world coordinate system by + translationConstraintDirection(). */ +void WorldConstraint::constrainTranslation(Vec& translation, Frame* const frame) +{ + Vec proj; + switch (translationConstraintType()) + { + case AxisPlaneConstraint::FREE: + break; + case AxisPlaneConstraint::PLANE: + if (frame->referenceFrame()) + { + proj = frame->referenceFrame()->transformOf(translationConstraintDirection()); + translation.projectOnPlane(proj); + } + else + translation.projectOnPlane(translationConstraintDirection()); + break; + case AxisPlaneConstraint::AXIS: + if (frame->referenceFrame()) + { + proj = frame->referenceFrame()->transformOf(translationConstraintDirection()); + translation.projectOnAxis(proj); + } + else + translation.projectOnAxis(translationConstraintDirection()); + break; + case AxisPlaneConstraint::FORBIDDEN: + translation = Vec(0.0, 0.0, 0.0); + break; + } +} + +/*! When rotationConstraintType() is AxisPlaneConstraint::AXIS, constrain \p rotation to be a rotation + around an axis whose direction is defined in the world coordinate system by + rotationConstraintDirection(). */ +void WorldConstraint::constrainRotation(Quaternion& rotation, Frame* const frame) +{ + switch (rotationConstraintType()) + { + case AxisPlaneConstraint::FREE: + break; + case AxisPlaneConstraint::PLANE: + break; + case AxisPlaneConstraint::AXIS: + { + Vec quat(rotation[0], rotation[1], rotation[2]); + Vec axis = frame->transformOf(rotationConstraintDirection()); + quat.projectOnAxis(axis); + rotation = Quaternion(quat, 2.0*acos(rotation[3])); + break; + } + case AxisPlaneConstraint::FORBIDDEN: + rotation = Quaternion(); // identity + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// CameraConstraint // +//////////////////////////////////////////////////////////////////////////////// + +/*! Creates a CameraConstraint, whose constrained directions are defined in the \p camera coordinate + system. */ +CameraConstraint::CameraConstraint(const Camera* const camera) + : AxisPlaneConstraint(), camera_(camera) +{} + +/*! Depending on translationConstraintType(), constrain \p translation to be along an axis or + limited to a plane defined in the camera() coordinate system by + translationConstraintDirection(). */ +void CameraConstraint::constrainTranslation(Vec& translation, Frame* const frame) +{ + Vec proj; + switch (translationConstraintType()) + { + case AxisPlaneConstraint::FREE: + break; + case AxisPlaneConstraint::PLANE: + proj = camera()->frame()->inverseTransformOf(translationConstraintDirection()); + if (frame->referenceFrame()) + proj = frame->referenceFrame()->transformOf(proj); + translation.projectOnPlane(proj); + break; + case AxisPlaneConstraint::AXIS: + proj = camera()->frame()->inverseTransformOf(translationConstraintDirection()); + if (frame->referenceFrame()) + proj = frame->referenceFrame()->transformOf(proj); + translation.projectOnAxis(proj); + break; + case AxisPlaneConstraint::FORBIDDEN: + translation = Vec(0.0, 0.0, 0.0); + break; + } +} + +/*! When rotationConstraintType() is AxisPlaneConstraint::AXIS, constrain \p rotation to be a rotation + around an axis whose direction is defined in the camera() coordinate system by + rotationConstraintDirection(). */ +void CameraConstraint::constrainRotation(Quaternion& rotation, Frame* const frame) +{ + switch (rotationConstraintType()) + { + case AxisPlaneConstraint::FREE: + break; + case AxisPlaneConstraint::PLANE: + break; + case AxisPlaneConstraint::AXIS: + { + Vec axis = frame->transformOf(camera()->frame()->inverseTransformOf(rotationConstraintDirection())); + Vec quat = Vec(rotation[0], rotation[1], rotation[2]); + quat.projectOnAxis(axis); + rotation = Quaternion(quat, 2.0*acos(rotation[3])); + } + break; + case AxisPlaneConstraint::FORBIDDEN: + rotation = Quaternion(); // identity + break; + } +} diff --git a/QGLViewer/constraint.h b/QGLViewer/constraint.h new file mode 100644 index 0000000..d90d820 --- /dev/null +++ b/QGLViewer/constraint.h @@ -0,0 +1,338 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#ifndef QGLVIEWER_CONSTRAINT_H +#define QGLVIEWER_CONSTRAINT_H + +#include "vec.h" +#include "quaternion.h" + +namespace qglviewer { +class Frame; +class Camera; + +/*! \brief An interface class for Frame constraints. + \class Constraint constraint.h QGLViewer/constraint.h + + This class defines the interface for the Constraints that can be applied to a Frame to limit its + motion. Use Frame::setConstraint() to associate a Constraint to a Frame (default is a \c NULL + Frame::constraint()). + +

How does it work ?

+ + The Constraint acts as a filter on the translation and rotation Frame increments. + constrainTranslation() and constrainRotation() should be overloaded to specify the constraint + behavior: the desired displacement is given as a parameter that can optionally be modified. + + Here is how the Frame::translate() and Frame::rotate() methods use the Constraint: + \code + Frame::translate(Vec& T) + { + if (constraint()) + constraint()->constrainTranslation(T, this); + t += T; + } + + Frame::rotate(Quaternion& Q) + { + if (constraint()) + constraint()->constrainRotation(Q, this); + q *= Q; + } + \endcode + + The default behavior of constrainTranslation() and constrainRotation() is empty (meaning no + filtering). + + The Frame which uses the Constraint is passed as a parameter to the constrainTranslation() and + constrainRotation() methods, so that they can have access to its current state (mainly + Frame::position() and Frame::orientation()). It is not \c const for versatility reasons, but + directly modifying it should be avoided. + + \attention Frame::setTranslation(), Frame::setRotation() and similar methods will actually indeed + set the frame position and orientation, without taking the constraint into account. Use the \e + WithConstraint versions of these methods to enforce the Constraint. + +

Implemented Constraints

+ + Classical axial and plane Constraints are provided for convenience: see the LocalConstraint, + WorldConstraint and CameraConstraint classes' documentations. + + Try the constrainedFrame and constrainedCamera examples for an illustration. + +

Creating new Constraints

+ + The implementation of a new Constraint class simply consists in overloading the filtering methods: + \code + // This Constraint enforces that the Frame cannot have a negative z world coordinate. + class myConstraint : public Constraint + { + public: + virtual void constrainTranslation(Vec& t, Frame * const fr) + { + // Express t in the world coordinate system. + const Vec tWorld = fr->inverseTransformOf(t); + if (fr->position().z + tWorld.z < 0.0) // check the new fr z coordinate + t.z = fr->transformOf(-fr->position().z); // t.z is clamped so that next z position is 0.0 + } + }; + \endcode + + Note that the translation (resp. rotation) parameter passed to constrainTranslation() (resp. + constrainRotation()) is expressed in the \e local Frame coordinate system. Here, we use the + Frame::transformOf() and Frame::inverseTransformOf() method to convert it to and from the world + coordinate system. + + Combined constraints can easily be achieved by creating a new class that applies the different + constraint filters: + \code + myConstraint::constrainTranslation(Vec& v, Frame* const fr) + { + constraint1->constrainTranslation(v, fr); + constraint2->constrainTranslation(v, fr); + // and so on, with possible branches, tests, loops... + } + \endcode + */ +class QGLVIEWER_EXPORT Constraint +{ +public: + /*! Virtual destructor. Empty. */ + virtual ~Constraint() {} + + /*! Filters the translation applied to the \p frame. This default implementation is empty (no + filtering). + + Overload this method in your own Constraint class to define a new translation constraint. \p + frame is the Frame to which is applied the translation. It is not defined \c const, but you + should refrain from directly changing its value in the constraint. Use its Frame::position() and + update the \p translation accordingly instead. + + \p translation is expressed in local frame coordinate system. Use Frame::inverseTransformOf() to + express it in the world coordinate system if needed. */ + virtual void constrainTranslation(Vec& translation, Frame* const frame) { Q_UNUSED(translation); Q_UNUSED(frame); } + /*! Filters the rotation applied to the \p frame. This default implementation is empty (no + filtering). + + Overload this method in your own Constraint class to define a new rotation constraint. See + constrainTranslation() for details. + + Use Frame::inverseTransformOf() on the \p rotation Quaternion::axis() to express \p rotation in + the world coordinate system if needed. */ + virtual void constrainRotation(Quaternion& rotation, Frame* const frame) { Q_UNUSED(rotation); Q_UNUSED(frame); } +}; + +/*! + \brief An abstract class for Frame Constraints defined by an axis or a plane. + \class AxisPlaneConstraint constraint.h QGLViewer/constraint.h + + AxisPlaneConstraint is an interface for (translation and/or rotation) Constraint that are defined + by a direction. translationConstraintType() and rotationConstraintType() define how this + direction should be interpreted: as an axis (AxisPlaneConstraint::AXIS) or as a plane normal + (AxisPlaneConstraint::PLANE). See the Type() documentation for details. + + The three implementations of this class: LocalConstraint, WorldConstraint and CameraConstraint + differ by the coordinate system in which this direction is expressed. + + Different implementations of this class are illustrated in the + contrainedCamera and + constrainedFrame examples. + + \attention When applied, the rotational Constraint may not intuitively follow the mouse + displacement. A solution would be to directly measure the rotation angle in screen coordinates, + but that would imply to know the QGLViewer::camera(), so that we can compute the projected + coordinates of the rotation center (as is done with the QGLViewer::SCREEN_ROTATE binding). + However, adding an extra pointer to the QGLViewer::camera() in all the AxisPlaneConstraint + derived classes (which the user would have to update in a multi-viewer application) was judged as + an overkill. */ +class QGLVIEWER_EXPORT AxisPlaneConstraint : public Constraint +{ +public: + AxisPlaneConstraint(); + /*! Virtual destructor. Empty. */ + virtual ~AxisPlaneConstraint() {} + + /*! Type lists the different types of translation and rotation constraints that are available. + + It specifies the meaning of the constraint direction (see translationConstraintDirection() and + rotationConstraintDirection()): as an axis direction (AxisPlaneConstraint::AXIS) or a plane + normal (AxisPlaneConstraint::PLANE). AxisPlaneConstraint::FREE means no constraint while + AxisPlaneConstraint::FORBIDDEN completely forbids the translation and/or the rotation. + + See translationConstraintType() and rotationConstraintType(). + + \attention The AxisPlaneConstraint::PLANE Type is not valid for rotational constraint. + + New derived classes can use their own extended enum for specific constraints: + \code + class MyAxisPlaneConstraint : public AxisPlaneConstraint + { + public: + enum MyType { FREE, AXIS, PLANE, FORBIDDEN, CUSTOM }; + virtual void constrainTranslation(Vec &translation, Frame *const frame) + { + // translationConstraintType() is simply an int. CUSTOM Type is handled seamlessly. + switch (translationConstraintType()) + { + case MyAxisPlaneConstraint::FREE: ... break; + case MyAxisPlaneConstraint::CUSTOM: ... break; + } + }; + + MyAxisPlaneConstraint* c = new MyAxisPlaneConstraint(); + // Note the Type conversion + c->setTranslationConstraintType(AxisPlaneConstraint::Type(MyAxisPlaneConstraint::CUSTOM)); + }; + \endcode */ + enum Type { FREE, AXIS, PLANE, FORBIDDEN }; + + /*! @name Translation constraint */ + //@{ + /*! Overloading of Constraint::constrainTranslation(). Empty */ + virtual void constrainTranslation(Vec& translation, Frame* const frame) { Q_UNUSED(translation); Q_UNUSED(frame); }; + + void setTranslationConstraint(Type type, const Vec& direction); + /*! Sets the Type() of the translationConstraintType(). Default is AxisPlaneConstraint::FREE. */ + void setTranslationConstraintType(Type type) { translationConstraintType_ = type; }; + void setTranslationConstraintDirection(const Vec& direction); + + /*! Returns the translation constraint Type(). + + Depending on this value, the Frame will freely translate (AxisPlaneConstraint::FREE), will only + be able to translate along an axis direction (AxisPlaneConstraint::AXIS), will be forced to stay + into a plane (AxisPlaneConstraint::PLANE) or will not able to translate at all + (AxisPlaneConstraint::FORBIDDEN). + + Use Frame::setPosition() to define the position of the constrained Frame before it gets + constrained. */ + Type translationConstraintType() const { return translationConstraintType_; }; + /*! Returns the direction used by the translation constraint. + + It represents the axis direction (AxisPlaneConstraint::AXIS) or the plane normal + (AxisPlaneConstraint::PLANE) depending on the translationConstraintType(). It is undefined for + AxisPlaneConstraint::FREE or AxisPlaneConstraint::FORBIDDEN. + + The AxisPlaneConstraint derived classes express this direction in different coordinate system + (camera for CameraConstraint, local for LocalConstraint, and world for WorldConstraint). This + value can be modified with setTranslationConstraintDirection(). */ + Vec translationConstraintDirection() const { return translationConstraintDir_; }; + //@} + + /*! @name Rotation constraint */ + //@{ + /*! Overloading of Constraint::constrainRotation(). Empty. */ + virtual void constrainRotation(Quaternion& rotation, Frame* const frame) { Q_UNUSED(rotation); Q_UNUSED(frame); }; + + void setRotationConstraint(Type type, const Vec& direction); + void setRotationConstraintType(Type type); + void setRotationConstraintDirection(const Vec& direction); + + /*! Returns the rotation constraint Type(). */ + Type rotationConstraintType() const { return rotationConstraintType_; }; + /*! Returns the axis direction used by the rotation constraint. + + This direction is defined only when rotationConstraintType() is AxisPlaneConstraint::AXIS. + + The AxisPlaneConstraint derived classes express this direction in different coordinate system + (camera for CameraConstraint, local for LocalConstraint, and world for WorldConstraint). This + value can be modified with setRotationConstraintDirection(). */ + Vec rotationConstraintDirection() const { return rotationConstraintDir_; }; + //@} + +private: + // int and not Type to allow for overloading and new types definition. + Type translationConstraintType_; + Type rotationConstraintType_; + + Vec translationConstraintDir_; + Vec rotationConstraintDir_; +}; + + +/*! \brief An AxisPlaneConstraint defined in the Frame local coordinate system. + \class LocalConstraint constraint.h QGLViewer/constraint.h + + The translationConstraintDirection() and rotationConstraintDirection() are expressed in the Frame + local coordinate system (see Frame::referenceFrame()). + + See the constrainedFrame example for an illustration. */ +class QGLVIEWER_EXPORT LocalConstraint : public AxisPlaneConstraint +{ +public: + /*! Virtual destructor. Empty. */ + virtual ~LocalConstraint() {}; + + virtual void constrainTranslation(Vec& translation, Frame* const frame); + virtual void constrainRotation (Quaternion& rotation, Frame* const frame); +}; + + + +/*! \brief An AxisPlaneConstraint defined in the world coordinate system. + \class WorldConstraint constraint.h QGLViewer/constraint.h + + The translationConstraintDirection() and rotationConstraintDirection() are expressed in world + coordinate system. + + See the constrainedFrame and multiView examples for an illustration. */ +class QGLVIEWER_EXPORT WorldConstraint : public AxisPlaneConstraint +{ +public: + /*! Virtual destructor. Empty. */ + virtual ~WorldConstraint() {}; + + virtual void constrainTranslation(Vec& translation, Frame* const frame); + virtual void constrainRotation (Quaternion& rotation, Frame* const frame); +}; + + + +/*! \brief An AxisPlaneConstraint defined in the camera coordinate system. + \class CameraConstraint constraint.h QGLViewer/constraint.h + + The translationConstraintDirection() and rotationConstraintDirection() are expressed in the + associated camera() coordinate system. + + See the constrainedFrame and constrainedCamera examples for an illustration. */ +class QGLVIEWER_EXPORT CameraConstraint : public AxisPlaneConstraint +{ +public: + explicit CameraConstraint(const Camera* const camera); + /*! Virtual destructor. Empty. */ + virtual ~CameraConstraint() {}; + + virtual void constrainTranslation(Vec& translation, Frame* const frame); + virtual void constrainRotation (Quaternion& rotation, Frame* const frame); + + /*! Returns the associated Camera. Set using the CameraConstraint constructor. */ + const Camera* camera() const { return camera_; }; + +private: + const Camera* const camera_; +}; + +} // namespace qglviewer + +#endif // QGLVIEWER_CONSTRAINT_H diff --git a/QGLViewer/domUtils.h b/QGLViewer/domUtils.h new file mode 100644 index 0000000..3994468 --- /dev/null +++ b/QGLViewer/domUtils.h @@ -0,0 +1,161 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "config.h" + +#include +#include +#include +#include + +#include + +#ifndef DOXYGEN + +// QDomElement loading with syntax checking. +class DomUtils +{ +private: + static void warning(const QString& message) + { + qWarning("%s", message.toLatin1().constData()); + } + +public: + static qreal qrealFromDom(const QDomElement& e, const QString& attribute, qreal defValue) + { + qreal value = defValue; + if (e.hasAttribute(attribute)) { + const QString s = e.attribute(attribute); + bool ok; + value = s.toDouble(&ok); + if (!ok) { + warning(QString("'%1' is not a valid qreal syntax for attribute \"%2\" in initialization of \"%3\". Setting value to %4.") + .arg(s).arg(attribute).arg(e.tagName()).arg(QString::number(defValue))); + value = defValue; + } + } else { + warning(QString("\"%1\" attribute missing in initialization of \"%2\". Setting value to %3.") + .arg(attribute).arg(e.tagName()).arg(QString::number(value))); + } + +#if defined(isnan) + // The "isnan" method may not be available on all platforms. + // Find its equivalent or simply remove these two lines + if (isnan(value)) + warning(QString("Warning, attribute \"%1\" initialized to Not a Number in \"%2\"") + .arg(attribute).arg(e.tagName())); +#endif + + return value; + } + + static int intFromDom(const QDomElement& e, const QString& attribute, int defValue) + { + int value = defValue; + if (e.hasAttribute(attribute)) + { + const QString s = e.attribute(attribute); + bool ok; + value = s.toInt(&ok); + if (!ok) { + warning(QString("'%1' is not a valid integer syntax for attribute \"%2\" in initialization of \"%3\". Setting value to %4.") + .arg(s).arg(attribute).arg(e.tagName()).arg(QString::number(defValue))); + value = defValue; + } + } else { + warning(QString("\"%1\" attribute missing in initialization of \"%2\". Setting value to %3.") + .arg(attribute).arg(e.tagName()).arg(QString::number(value))); + } + + return value; + } + + static unsigned int uintFromDom(const QDomElement& e, const QString& attribute, unsigned int defValue) + { + unsigned int value = defValue; + if (e.hasAttribute(attribute)) + { + const QString s = e.attribute(attribute); + bool ok; + value = s.toUInt(&ok); + if (!ok) { + warning(QString("'%1' is not a valid unsigned integer syntax for attribute \"%2\" in initialization of \"%3\". Setting value to %4.") + .arg(s).arg(attribute).arg(e.tagName()).arg(QString::number(defValue))); + value = defValue; + } + } else { + warning(QString("\"%1\" attribute missing in initialization of \"%2\". Setting value to %3.") + .arg(attribute).arg(e.tagName()).arg(QString::number(value))); + } + + return value; + } + + static bool boolFromDom(const QDomElement& e, const QString& attribute, bool defValue) + { + bool value = defValue; + if (e.hasAttribute(attribute)) + { + const QString s = e.attribute(attribute); + if (s.toLower() == QString("true")) + value = true; + else if (s.toLower() == QString("false")) + value = false; + else + { + warning(QString("'%1' is not a valid boolean syntax for attribute \"%2\" in initialization of \"%3\". Setting value to %4.") + .arg(s).arg(attribute).arg(e.tagName()).arg(defValue?"true":"false")); + } + } else { + warning(QString("\"%1\" attribute missing in initialization of \"%2\". Setting value to %3.") + .arg(attribute).arg(e.tagName()).arg(value?"true":"false")); + } + + return value; + } + + static void setBoolAttribute(QDomElement& element, const QString& attribute, bool value) { + element.setAttribute(attribute, (value ? "true" : "false")); + } + + static QDomElement QColorDomElement(const QColor& color, const QString& name, QDomDocument& doc) + { + QDomElement de = doc.createElement(name); + de.setAttribute("red", QString::number(color.red())); + de.setAttribute("green", QString::number(color.green())); + de.setAttribute("blue", QString::number(color.blue())); + return de; + } + + static QColor QColorFromDom(const QDomElement& e) + { + int color[3]; + QStringList attribute; + attribute << "red" << "green" << "blue"; + for (int i=0; i + +using namespace qglviewer; +using namespace std; + + +/*! Creates a default Frame. + + Its position() is (0,0,0) and it has an identity orientation() Quaternion. The referenceFrame() + and the constraint() are \c NULL. */ +Frame::Frame() + : constraint_(NULL), referenceFrame_(NULL) +{} + +/*! Creates a Frame with a position() and an orientation(). + + See the Vec and Quaternion documentations for convenient constructors and methods. + + The Frame is defined in the world coordinate system (its referenceFrame() is \c NULL). It + has a \c NULL associated constraint(). */ +Frame::Frame(const Vec& position, const Quaternion& orientation) + : t_(position), q_(orientation), constraint_(NULL), referenceFrame_(NULL) +{} + +/*! Equal operator. + + The referenceFrame() and constraint() pointers are copied. + + \attention Signal and slot connections are not copied. */ +Frame& Frame::operator=(const Frame& frame) +{ + // Automatic compiler generated version would not emit the modified() signals as is done in + // setTranslationAndRotation. + setTranslationAndRotation(frame.translation(), frame.rotation()); + setConstraint(frame.constraint()); + setReferenceFrame(frame.referenceFrame()); + return *this; +} + +/*! Copy constructor. + + The translation() and rotation() as well as constraint() and referenceFrame() pointers are + copied. */ +Frame::Frame(const Frame& frame) + : QObject() +{ + (*this) = frame; +} + +/////////////////////////////// MATRICES ////////////////////////////////////// + +/*! Returns the 4x4 OpenGL transformation matrix represented by the Frame. + + This method should be used in conjunction with \c glMultMatrixd() to modify the OpenGL modelview + matrix from a Frame hierarchy. With this Frame hierarchy: + \code + Frame* body = new Frame(); + Frame* leftArm = new Frame(); + Frame* rightArm = new Frame(); + leftArm->setReferenceFrame(body); + rightArm->setReferenceFrame(body); + \endcode + + The associated OpenGL drawing code should look like: + \code + void Viewer::draw() + { + glPushMatrix(); + glMultMatrixd(body->matrix()); + drawBody(); + + glPushMatrix(); + glMultMatrixd(leftArm->matrix()); + drawArm(); + glPopMatrix(); + + glPushMatrix(); + glMultMatrixd(rightArm->matrix()); + drawArm(); + glPopMatrix(); + + glPopMatrix(); + } + \endcode + Note the use of nested \c glPushMatrix() and \c glPopMatrix() blocks to represent the frame hierarchy: \c + leftArm and \c rightArm are both correctly drawn with respect to the \c body coordinate system. + + This matrix only represents the local Frame transformation (i.e. with respect to the + referenceFrame()). Use worldMatrix() to get the full Frame transformation matrix (i.e. from the + world to the Frame coordinate system). These two match when the referenceFrame() is \c NULL. + + The result is only valid until the next call to matrix(), getMatrix(), worldMatrix() or + getWorldMatrix(). Use it immediately (as above) or use getMatrix() instead. + + \attention The OpenGL format of the result is the transpose of the actual mathematical European + representation (translation is on the last \e line instead of the last \e column). + + \note The scaling factor of the 4x4 matrix is 1.0. */ +const GLdouble* Frame::matrix() const +{ + static GLdouble m[4][4]; + getMatrix(m); + return (const GLdouble*)(m); +} + +/*! \c GLdouble[4][4] version of matrix(). See also getWorldMatrix() and matrix(). */ +void Frame::getMatrix(GLdouble m[4][4]) const +{ + q_.getMatrix(m); + + m[3][0] = t_[0]; + m[3][1] = t_[1]; + m[3][2] = t_[2]; +} + +/*! \c GLdouble[16] version of matrix(). See also getWorldMatrix() and matrix(). */ +void Frame::getMatrix(GLdouble m[16]) const +{ + q_.getMatrix(m); + + m[12] = t_[0]; + m[13] = t_[1]; + m[14] = t_[2]; +} + +/*! Returns a Frame representing the inverse of the Frame space transformation. + + The rotation() of the new Frame is the Quaternion::inverse() of the original rotation. + Its translation() is the negated inverse rotated image of the original translation. + + If a Frame is considered as a space rigid transformation (translation and rotation), the inverse() + Frame performs the inverse transformation. + + Only the local Frame transformation (i.e. defined with respect to the referenceFrame()) is inverted. + Use worldInverse() for a global inverse. + + The resulting Frame has the same referenceFrame() as the Frame and a \c NULL constraint(). + + \note The scaling factor of the 4x4 matrix is 1.0. */ +Frame Frame::inverse() const +{ + Frame fr(-(q_.inverseRotate(t_)), q_.inverse()); + fr.setReferenceFrame(referenceFrame()); + return fr; +} + +/*! Returns the 4x4 OpenGL transformation matrix represented by the Frame. + + This method should be used in conjunction with \c glMultMatrixd() to modify + the OpenGL modelview matrix from a Frame: + \code + // The modelview here corresponds to the world coordinate system. + Frame fr(pos, Quaternion(from, to)); + glPushMatrix(); + glMultMatrixd(fr.worldMatrix()); + // draw object in the fr coordinate system. + glPopMatrix(); + \endcode + + This matrix represents the global Frame transformation: the entire referenceFrame() hierarchy is + taken into account to define the Frame transformation from the world coordinate system. Use + matrix() to get the local Frame transformation matrix (i.e. defined with respect to the + referenceFrame()). These two match when the referenceFrame() is \c NULL. + + The OpenGL format of the result is the transpose of the actual mathematical European + representation (translation is on the last \e line instead of the last \e column). + + \attention The result is only valid until the next call to matrix(), getMatrix(), worldMatrix() or + getWorldMatrix(). Use it immediately (as above) or use getWorldMatrix() instead. + + \note The scaling factor of the 4x4 matrix is 1.0. */ +const GLdouble* Frame::worldMatrix() const +{ + // This test is done for efficiency reasons (creates lots of temp objects otherwise). + if (referenceFrame()) + { + static Frame fr; + fr.setTranslation(position()); + fr.setRotation(orientation()); + return fr.matrix(); + } + else + return matrix(); +} + +/*! qreal[4][4] parameter version of worldMatrix(). See also getMatrix() and matrix(). */ +void Frame::getWorldMatrix(GLdouble m[4][4]) const +{ + const GLdouble* mat = worldMatrix(); + for (int i=0; i<4; ++i) + for (int j=0; j<4; ++j) + m[i][j] = mat[i*4+j]; +} + +/*! qreal[16] parameter version of worldMatrix(). See also getMatrix() and matrix(). */ +void Frame::getWorldMatrix(GLdouble m[16]) const +{ + const GLdouble* mat = worldMatrix(); + for (int i=0; i<16; ++i) + m[i] = mat[i]; +} + +/*! This is an overloaded method provided for convenience. Same as setFromMatrix(). */ +void Frame::setFromMatrix(const GLdouble m[4][4]) +{ + if (fabs(m[3][3]) < 1E-8) + { + qWarning("Frame::setFromMatrix: Null homogeneous coefficient"); + return; + } + + qreal rot[3][3]; + for (int i=0; i<3; ++i) + { + t_[i] = m[3][i] / m[3][3]; + for (int j=0; j<3; ++j) + // Beware of the transposition (OpenGL to European math) + rot[i][j] = m[j][i] / m[3][3]; + } + q_.setFromRotationMatrix(rot); + Q_EMIT modified(); +} + +/*! Sets the Frame from an OpenGL matrix representation (rotation in the upper left 3x3 matrix and + translation on the last line). + + Hence, if a code fragment looks like: + \code + GLdouble m[16]={...}; + glMultMatrixd(m); + \endcode + It is equivalent to write: + \code + Frame fr; + fr.setFromMatrix(m); + glMultMatrixd(fr.matrix()); + \endcode + + Using this conversion, you can benefit from the powerful Frame transformation methods to translate + points and vectors to and from the Frame coordinate system to any other Frame coordinate system + (including the world coordinate system). See coordinatesOf() and transformOf(). + + Emits the modified() signal. See also matrix(), getMatrix() and + Quaternion::setFromRotationMatrix(). + + \attention A Frame does not contain a scale factor. The possible scaling in \p m will not be + converted into the Frame by this method. */ +void Frame::setFromMatrix(const GLdouble m[16]) +{ + GLdouble mat[4][4]; + for (int i=0; i<4; ++i) + for (int j=0; j<4; ++j) + mat[i][j] = m[i*4+j]; + setFromMatrix(mat); +} + +//////////////////// SET AND GET LOCAL TRANSLATION AND ROTATION /////////////////////////////// + + +/*! Same as setTranslation(), but with \p qreal parameters. */ +void Frame::setTranslation(qreal x, qreal y, qreal z) +{ + setTranslation(Vec(x, y, z)); +} + +/*! Fill \c x, \c y and \c z with the translation() of the Frame. */ +void Frame::getTranslation(qreal& x, qreal& y, qreal& z) const +{ + const Vec t = translation(); + x = t[0]; + y = t[1]; + z = t[2]; +} + +/*! Same as setRotation() but with \c qreal Quaternion parameters. */ +void Frame::setRotation(qreal q0, qreal q1, qreal q2, qreal q3) +{ + setRotation(Quaternion(q0, q1, q2, q3)); +} + +/*! The \p q are set to the rotation() of the Frame. + +See Quaternion::Quaternion(qreal, qreal, qreal, qreal) for details on \c q. */ +void Frame::getRotation(qreal& q0, qreal& q1, qreal& q2, qreal& q3) const +{ + const Quaternion q = rotation(); + q0 = q[0]; + q1 = q[1]; + q2 = q[2]; + q3 = q[3]; +} + +//////////////////////////////////////////////////////////////////////////////// + +/*! Translates the Frame of \p t (defined in the Frame coordinate system). + + The translation actually applied to the Frame may differ from \p t since it can be filtered by the + constraint(). Use translate(Vec&) or setTranslationWithConstraint() to retrieve the filtered + translation value. Use setTranslation() to directly translate the Frame without taking the + constraint() into account. + + See also rotate(const Quaternion&). Emits the modified() signal. */ +void Frame::translate(const Vec& t) +{ + Vec tbis = t; + translate(tbis); +} + +/*! Same as translate(const Vec&) but \p t may be modified to satisfy the translation constraint(). + Its new value corresponds to the translation that has actually been applied to the Frame. */ +void Frame::translate(Vec& t) +{ + if (constraint()) + constraint()->constrainTranslation(t, this); + t_ += t; + Q_EMIT modified(); +} + +/*! Same as translate(const Vec&) but with \c qreal parameters. */ +void Frame::translate(qreal x, qreal y, qreal z) +{ + Vec t(x,y,z); + translate(t); +} + +/*! Same as translate(Vec&) but with \c qreal parameters. */ +void Frame::translate(qreal& x, qreal& y, qreal& z) +{ + Vec t(x,y,z); + translate(t); + x = t[0]; + y = t[1]; + z = t[2]; +} + +/*! Rotates the Frame by \p q (defined in the Frame coordinate system): R = R*q. + + The rotation actually applied to the Frame may differ from \p q since it can be filtered by the + constraint(). Use rotate(Quaternion&) or setRotationWithConstraint() to retrieve the filtered + rotation value. Use setRotation() to directly rotate the Frame without taking the constraint() + into account. + + See also translate(const Vec&). Emits the modified() signal. */ +void Frame::rotate(const Quaternion& q) +{ + Quaternion qbis = q; + rotate(qbis); +} + +/*! Same as rotate(const Quaternion&) but \p q may be modified to satisfy the rotation constraint(). + Its new value corresponds to the rotation that has actually been applied to the Frame. */ +void Frame::rotate(Quaternion& q) +{ + if (constraint()) + constraint()->constrainRotation(q, this); + q_ *= q; + q_.normalize(); // Prevents numerical drift + Q_EMIT modified(); +} + +/*! Same as rotate(Quaternion&) but with \c qreal Quaternion parameters. */ +void Frame::rotate(qreal& q0, qreal& q1, qreal& q2, qreal& q3) +{ + Quaternion q(q0,q1,q2,q3); + rotate(q); + q0 = q[0]; + q1 = q[1]; + q2 = q[2]; + q3 = q[3]; +} + +/*! Same as rotate(const Quaternion&) but with \c qreal Quaternion parameters. */ +void Frame::rotate(qreal q0, qreal q1, qreal q2, qreal q3) +{ + Quaternion q(q0,q1,q2,q3); + rotate(q); +} + +/*! Makes the Frame rotate() by \p rotation around \p point. + + \p point is defined in the world coordinate system, while the \p rotation axis is defined in the + Frame coordinate system. + + If the Frame has a constraint(), \p rotation is first constrained using + Constraint::constrainRotation(). The translation which results from the filtered rotation around + \p point is then computed and filtered using Constraint::constrainTranslation(). The new \p + rotation value corresponds to the rotation that has actually been applied to the Frame. + + Emits the modified() signal. */ +void Frame::rotateAroundPoint(Quaternion& rotation, const Vec& point) +{ + if (constraint()) + constraint()->constrainRotation(rotation, this); + q_ *= rotation; + q_.normalize(); // Prevents numerical drift + Vec trans = point + Quaternion(inverseTransformOf(rotation.axis()), rotation.angle()).rotate(position()-point) - t_; + if (constraint()) + constraint()->constrainTranslation(trans, this); + t_ += trans; + Q_EMIT modified(); +} + +/*! Same as rotateAroundPoint(), but with a \c const \p rotation Quaternion. Note that the actual + rotation may differ since it can be filtered by the constraint(). */ +void Frame::rotateAroundPoint(const Quaternion& rotation, const Vec& point) +{ + Quaternion rot = rotation; + rotateAroundPoint(rot, point); +} + +//////////////////// SET AND GET WORLD POSITION AND ORIENTATION /////////////////////////////// + +/*! Sets the position() of the Frame, defined in the world coordinate system. Emits the modified() + signal. + +Use setTranslation() to define the \e local frame translation (with respect to the +referenceFrame()). The potential constraint() of the Frame is not taken into account, use +setPositionWithConstraint() instead. */ +void Frame::setPosition(const Vec& position) +{ + if (referenceFrame()) + setTranslation(referenceFrame()->coordinatesOf(position)); + else + setTranslation(position); +} + +/*! Same as setPosition(), but with \c qreal parameters. */ +void Frame::setPosition(qreal x, qreal y, qreal z) +{ + setPosition(Vec(x, y, z)); +} + +/*! Same as successive calls to setPosition() and then setOrientation(). + +Only one modified() signal is emitted, which is convenient if this signal is connected to a +QGLViewer::update() slot. See also setTranslationAndRotation() and +setPositionAndOrientationWithConstraint(). */ +void Frame::setPositionAndOrientation(const Vec& position, const Quaternion& orientation) +{ + if (referenceFrame()) + { + t_ = referenceFrame()->coordinatesOf(position); + q_ = referenceFrame()->orientation().inverse() * orientation; + } + else + { + t_ = position; + q_ = orientation; + } + Q_EMIT modified(); +} + + +/*! Same as successive calls to setTranslation() and then setRotation(). + +Only one modified() signal is emitted, which is convenient if this signal is connected to a +QGLViewer::update() slot. See also setPositionAndOrientation() and +setTranslationAndRotationWithConstraint(). */ +void Frame::setTranslationAndRotation(const Vec& translation, const Quaternion& rotation) +{ + t_ = translation; + q_ = rotation; + Q_EMIT modified(); +} + + +/*! \p x, \p y and \p z are set to the position() of the Frame. */ +void Frame::getPosition(qreal& x, qreal& y, qreal& z) const +{ + Vec p = position(); + x = p.x; + y = p.y; + z = p.z; +} + +/*! Sets the orientation() of the Frame, defined in the world coordinate system. Emits the modified() signal. + +Use setRotation() to define the \e local frame rotation (with respect to the referenceFrame()). The +potential constraint() of the Frame is not taken into account, use setOrientationWithConstraint() +instead. */ +void Frame::setOrientation(const Quaternion& orientation) +{ + if (referenceFrame()) + setRotation(referenceFrame()->orientation().inverse() * orientation); + else + setRotation(orientation); +} + +/*! Same as setOrientation(), but with \c qreal parameters. */ +void Frame::setOrientation(qreal q0, qreal q1, qreal q2, qreal q3) +{ + setOrientation(Quaternion(q0, q1, q2, q3)); +} + +/*! Get the current orientation of the frame (same as orientation()). + Parameters are the orientation Quaternion values. + See also setOrientation(). */ + +/*! The \p q are set to the orientation() of the Frame. + +See Quaternion::Quaternion(qreal, qreal, qreal, qreal) for details on \c q. */ +void Frame::getOrientation(qreal& q0, qreal& q1, qreal& q2, qreal& q3) const +{ + Quaternion o = orientation(); + q0 = o[0]; + q1 = o[1]; + q2 = o[2]; + q3 = o[3]; +} + +/*! Returns the position of the Frame, defined in the world coordinate system. See also + orientation(), setPosition() and translation(). */ +Vec Frame::position() const { + if (referenceFrame_) + return inverseCoordinatesOf(Vec(0.0,0.0,0.0)); + else + return t_; +} + +/*! Returns the orientation of the Frame, defined in the world coordinate system. See also + position(), setOrientation() and rotation(). */ +Quaternion Frame::orientation() const +{ + Quaternion res = rotation(); + const Frame* fr = referenceFrame(); + while (fr != NULL) + { + res = fr->rotation() * res; + fr = fr->referenceFrame(); + } + return res; +} + + +////////////////////// C o n s t r a i n t V e r s i o n s ////////////////////////// + +/*! Same as setTranslation(), but \p translation is modified so that the potential constraint() of the + Frame is satisfied. + + Emits the modified() signal. See also setRotationWithConstraint() and setPositionWithConstraint(). */ +void Frame::setTranslationWithConstraint(Vec& translation) +{ + Vec deltaT = translation - this->translation(); + if (constraint()) + constraint()->constrainTranslation(deltaT, this); + + setTranslation(this->translation() + deltaT); + translation = this->translation(); +} + +/*! Same as setRotation(), but \p rotation is modified so that the potential constraint() of the + Frame is satisfied. + + Emits the modified() signal. See also setTranslationWithConstraint() and setOrientationWithConstraint(). */ +void Frame::setRotationWithConstraint(Quaternion& rotation) +{ + Quaternion deltaQ = this->rotation().inverse() * rotation; + if (constraint()) + constraint()->constrainRotation(deltaQ, this); + + // Prevent numerical drift + deltaQ.normalize(); + + setRotation(this->rotation() * deltaQ); + q_.normalize(); + rotation = this->rotation(); +} + +/*! Same as setTranslationAndRotation(), but \p translation and \p orientation are modified to + satisfy the constraint(). Emits the modified() signal. */ +void Frame::setTranslationAndRotationWithConstraint(Vec& translation, Quaternion& rotation) +{ + Vec deltaT = translation - this->translation(); + Quaternion deltaQ = this->rotation().inverse() * rotation; + + if (constraint()) + { + constraint()->constrainTranslation(deltaT, this); + constraint()->constrainRotation(deltaQ, this); + } + + // Prevent numerical drift + deltaQ.normalize(); + + t_ += deltaT; + q_ *= deltaQ; + q_.normalize(); + + translation = this->translation(); + rotation = this->rotation(); + + Q_EMIT modified(); +} + +/*! Same as setPosition(), but \p position is modified so that the potential constraint() of the + Frame is satisfied. See also setOrientationWithConstraint() and setTranslationWithConstraint(). */ +void Frame::setPositionWithConstraint(Vec& position) +{ + if (referenceFrame()) + position = referenceFrame()->coordinatesOf(position); + + setTranslationWithConstraint(position); +} + +/*! Same as setOrientation(), but \p orientation is modified so that the potential constraint() of the Frame + is satisfied. See also setPositionWithConstraint() and setRotationWithConstraint(). */ +void Frame::setOrientationWithConstraint(Quaternion& orientation) +{ + if (referenceFrame()) + orientation = referenceFrame()->orientation().inverse() * orientation; + + setRotationWithConstraint(orientation); +} + +/*! Same as setPositionAndOrientation() but \p position and \p orientation are modified to satisfy +the constraint. Emits the modified() signal. */ +void Frame::setPositionAndOrientationWithConstraint(Vec& position, Quaternion& orientation) +{ + if (referenceFrame()) + { + position = referenceFrame()->coordinatesOf(position); + orientation = referenceFrame()->orientation().inverse() * orientation; + } + setTranslationAndRotationWithConstraint(position, orientation); +} + + +///////////////////////////// REFERENCE FRAMES /////////////////////////////////////// + +/*! Sets the referenceFrame() of the Frame. + +The Frame translation() and rotation() are then defined in the referenceFrame() coordinate system. +Use position() and orientation() to express these in the world coordinate system. + +Emits the modified() signal if \p refFrame differs from the current referenceFrame(). + +Using this method, you can create a hierarchy of Frames. This hierarchy needs to be a tree, which +root is the world coordinate system (i.e. a \c NULL referenceFrame()). A warning is printed and no +action is performed if setting \p refFrame as the referenceFrame() would create a loop in the Frame +hierarchy (see settingAsReferenceFrameWillCreateALoop()). */ +void Frame::setReferenceFrame(const Frame* const refFrame) +{ + if (settingAsReferenceFrameWillCreateALoop(refFrame)) + qWarning("Frame::setReferenceFrame would create a loop in Frame hierarchy"); + else + { + bool identical = (referenceFrame_ == refFrame); + referenceFrame_ = refFrame; + if (!identical) + Q_EMIT modified(); + } +} + +/*! Returns \c true if setting \p frame as the Frame's referenceFrame() would create a loop in the + Frame hierarchy. */ +bool Frame::settingAsReferenceFrameWillCreateALoop(const Frame* const frame) +{ + const Frame* f = frame; + while (f != NULL) + { + if (f == this) + return true; + f = f->referenceFrame(); + } + return false; +} + +///////////////////////// FRAME TRANSFORMATIONS OF 3D POINTS ////////////////////////////// + +/*! Returns the Frame coordinates of a point \p src defined in the world coordinate system (converts + from world to Frame). + + inverseCoordinatesOf() performs the inverse convertion. transformOf() converts 3D vectors instead + of 3D coordinates. + + See the frameTransform example for an + illustration. */ +Vec Frame::coordinatesOf(const Vec& src) const +{ + if (referenceFrame()) + return localCoordinatesOf(referenceFrame()->coordinatesOf(src)); + else + return localCoordinatesOf(src); +} + +/*! Returns the world coordinates of the point whose position in the Frame coordinate system is \p + src (converts from Frame to world). + + coordinatesOf() performs the inverse convertion. Use inverseTransformOf() to transform 3D vectors + instead of 3D coordinates. */ +Vec Frame::inverseCoordinatesOf(const Vec& src) const +{ + const Frame* fr = this; + Vec res = src; + while (fr != NULL) + { + res = fr->localInverseCoordinatesOf(res); + fr = fr->referenceFrame(); + } + return res; +} + +/*! Returns the Frame coordinates of a point \p src defined in the referenceFrame() coordinate + system (converts from referenceFrame() to Frame). + + localInverseCoordinatesOf() performs the inverse convertion. See also localTransformOf(). */ +Vec Frame::localCoordinatesOf(const Vec& src) const +{ + return rotation().inverseRotate(src - translation()); +} + +/*! Returns the referenceFrame() coordinates of a point \p src defined in the Frame coordinate + system (converts from Frame to referenceFrame()). + + localCoordinatesOf() performs the inverse convertion. See also localInverseTransformOf(). */ +Vec Frame::localInverseCoordinatesOf(const Vec& src) const +{ + return rotation().rotate(src) + translation(); +} + +/*! Returns the Frame coordinates of the point whose position in the \p from coordinate system is \p + src (converts from \p from to Frame). + + coordinatesOfIn() performs the inverse transformation. */ +Vec Frame::coordinatesOfFrom(const Vec& src, const Frame* const from) const +{ + if (this == from) + return src; + else + if (referenceFrame()) + return localCoordinatesOf(referenceFrame()->coordinatesOfFrom(src, from)); + else + return localCoordinatesOf(from->inverseCoordinatesOf(src)); +} + +/*! Returns the \p in coordinates of the point whose position in the Frame coordinate system is \p + src (converts from Frame to \p in). + + coordinatesOfFrom() performs the inverse transformation. */ +Vec Frame::coordinatesOfIn(const Vec& src, const Frame* const in) const +{ + const Frame* fr = this; + Vec res = src; + while ((fr != NULL) && (fr != in)) + { + res = fr->localInverseCoordinatesOf(res); + fr = fr->referenceFrame(); + } + + if (fr != in) + // in was not found in the branch of this, res is now expressed in the world + // coordinate system. Simply convert to in coordinate system. + res = in->coordinatesOf(res); + + return res; +} + +////// qreal[3] versions + +/*! Same as coordinatesOf(), but with \c qreal parameters. */ +void Frame::getCoordinatesOf(const qreal src[3], qreal res[3]) const +{ + const Vec r = coordinatesOf(Vec(src)); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + +/*! Same as inverseCoordinatesOf(), but with \c qreal parameters. */ +void Frame::getInverseCoordinatesOf(const qreal src[3], qreal res[3]) const +{ + const Vec r = inverseCoordinatesOf(Vec(src)); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + +/*! Same as localCoordinatesOf(), but with \c qreal parameters. */ +void Frame::getLocalCoordinatesOf(const qreal src[3], qreal res[3]) const +{ + const Vec r = localCoordinatesOf(Vec(src)); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + +/*! Same as localInverseCoordinatesOf(), but with \c qreal parameters. */ +void Frame::getLocalInverseCoordinatesOf(const qreal src[3], qreal res[3]) const +{ + const Vec r = localInverseCoordinatesOf(Vec(src)); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + +/*! Same as coordinatesOfIn(), but with \c qreal parameters. */ +void Frame::getCoordinatesOfIn(const qreal src[3], qreal res[3], const Frame* const in) const +{ + const Vec r = coordinatesOfIn(Vec(src), in); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + +/*! Same as coordinatesOfFrom(), but with \c qreal parameters. */ +void Frame::getCoordinatesOfFrom(const qreal src[3], qreal res[3], const Frame* const from) const +{ + const Vec r = coordinatesOfFrom(Vec(src), from); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + + +///////////////////////// FRAME TRANSFORMATIONS OF VECTORS ////////////////////////////// + +/*! Returns the Frame transform of a vector \p src defined in the world coordinate system (converts + vectors from world to Frame). + + inverseTransformOf() performs the inverse transformation. coordinatesOf() converts 3D coordinates + instead of 3D vectors (here only the rotational part of the transformation is taken into account). + + See the frameTransform example for an + illustration. */ +Vec Frame::transformOf(const Vec& src) const +{ + if (referenceFrame()) + return localTransformOf(referenceFrame()->transformOf(src)); + else + return localTransformOf(src); +} + +/*! Returns the world transform of the vector whose coordinates in the Frame coordinate + system is \p src (converts vectors from Frame to world). + + transformOf() performs the inverse transformation. Use inverseCoordinatesOf() to transform 3D + coordinates instead of 3D vectors. */ +Vec Frame::inverseTransformOf(const Vec& src) const +{ + const Frame* fr = this; + Vec res = src; + while (fr != NULL) + { + res = fr->localInverseTransformOf(res); + fr = fr->referenceFrame(); + } + return res; +} + +/*! Returns the Frame transform of a vector \p src defined in the referenceFrame() coordinate system + (converts vectors from referenceFrame() to Frame). + + localInverseTransformOf() performs the inverse transformation. See also localCoordinatesOf(). */ +Vec Frame::localTransformOf(const Vec& src) const +{ + return rotation().inverseRotate(src); +} + +/*! Returns the referenceFrame() transform of a vector \p src defined in the Frame coordinate + system (converts vectors from Frame to referenceFrame()). + + localTransformOf() performs the inverse transformation. See also localInverseCoordinatesOf(). */ +Vec Frame::localInverseTransformOf(const Vec& src) const +{ + return rotation().rotate(src); +} + +/*! Returns the Frame transform of the vector whose coordinates in the \p from coordinate system is \p + src (converts vectors from \p from to Frame). + + transformOfIn() performs the inverse transformation. */ +Vec Frame::transformOfFrom(const Vec& src, const Frame* const from) const +{ + if (this == from) + return src; + else + if (referenceFrame()) + return localTransformOf(referenceFrame()->transformOfFrom(src, from)); + else + return localTransformOf(from->inverseTransformOf(src)); +} + +/*! Returns the \p in transform of the vector whose coordinates in the Frame coordinate system is \p + src (converts vectors from Frame to \p in). + + transformOfFrom() performs the inverse transformation. */ +Vec Frame::transformOfIn(const Vec& src, const Frame* const in) const +{ + const Frame* fr = this; + Vec res = src; + while ((fr != NULL) && (fr != in)) + { + res = fr->localInverseTransformOf(res); + fr = fr->referenceFrame(); + } + + if (fr != in) + // in was not found in the branch of this, res is now expressed in the world + // coordinate system. Simply convert to in coordinate system. + res = in->transformOf(res); + + return res; +} + +///////////////// qreal[3] versions ////////////////////// + +/*! Same as transformOf(), but with \c qreal parameters. */ +void Frame::getTransformOf(const qreal src[3], qreal res[3]) const +{ + Vec r = transformOf(Vec(src)); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + +/*! Same as inverseTransformOf(), but with \c qreal parameters. */ +void Frame::getInverseTransformOf(const qreal src[3], qreal res[3]) const +{ + Vec r = inverseTransformOf(Vec(src)); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + +/*! Same as localTransformOf(), but with \c qreal parameters. */ +void Frame::getLocalTransformOf(const qreal src[3], qreal res[3]) const +{ + Vec r = localTransformOf(Vec(src)); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + +/*! Same as localInverseTransformOf(), but with \c qreal parameters. */ +void Frame::getLocalInverseTransformOf(const qreal src[3], qreal res[3]) const +{ + Vec r = localInverseTransformOf(Vec(src)); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + +/*! Same as transformOfIn(), but with \c qreal parameters. */ +void Frame::getTransformOfIn(const qreal src[3], qreal res[3], const Frame* const in) const +{ + Vec r = transformOfIn(Vec(src), in); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + +/*! Same as transformOfFrom(), but with \c qreal parameters. */ +void Frame::getTransformOfFrom(const qreal src[3], qreal res[3], const Frame* const from) const +{ + Vec r = transformOfFrom(Vec(src), from); + for (int i=0; i<3 ; ++i) + res[i] = r[i]; +} + +//////////////////////////// STATE ////////////////////////////// + +/*! Returns an XML \c QDomElement that represents the Frame. + + \p name is the name of the QDomElement tag. \p doc is the \c QDomDocument factory used to create + QDomElement. + + The resulting QDomElement looks like: + \code + + + + + \endcode + + Use initFromDOMElement() to restore the Frame state from the resulting \c QDomElement. + + See Vec::domElement() for a complete example. See also Quaternion::domElement(), + Camera::domElement()... + + \attention The constraint() and referenceFrame() are not saved in the QDomElement. */ +QDomElement Frame::domElement(const QString& name, QDomDocument& document) const +{ + // TODO: use translation and rotation instead when referenceFrame is coded... + QDomElement e = document.createElement(name); + e.appendChild(position().domElement("position", document)); + e.appendChild(orientation().domElement("orientation", document)); + return e; +} + +/*! Restores the Frame state from a \c QDomElement created by domElement(). + + See domElement() for the \c QDomElement syntax. See the Vec::initFromDOMElement() and + Quaternion::initFromDOMElement() documentations for details on default values if an argument is + missing. + + \attention The constraint() and referenceFrame() are not restored by this method and are left + unchanged. */ +void Frame::initFromDOMElement(const QDomElement& element) +{ + // TODO: use translation and rotation instead when referenceFrame is coded... + + // Reset default values. Attention: destroys constraint. + // *this = Frame(); + // This instead ? Better : what is not set is not changed. + // setPositionAndOrientation(Vec(), Quaternion()); + + QDomElement child=element.firstChild().toElement(); + while (!child.isNull()) + { + if (child.tagName() == "position") + setPosition(Vec(child)); + if (child.tagName() == "orientation") + setOrientation(Quaternion(child).normalized()); + + child = child.nextSibling().toElement(); + } +} + +///////////////////////////////// ALIGN ///////////////////////////////// + +/*! Aligns the Frame with \p frame, so that two of their axis are parallel. + +If one of the X, Y and Z axis of the Frame is almost parallel to any of the X, Y, or Z axis of \p +frame, the Frame is rotated so that these two axis actually become parallel. + +If, after this first rotation, two other axis are also almost parallel, a second alignment is +performed. The two frames then have identical orientations, up to 90 degrees rotations. + +\p threshold measures how close two axis must be to be considered parallel. It is compared with the +absolute values of the dot product of the normalized axis. As a result, useful range is sqrt(2)/2 +(systematic alignment) to 1 (no alignment). + +When \p move is set to \c true, the Frame position() is also affected by the alignment. The new +Frame's position() is such that the \p frame position (computed with coordinatesOf(), in the Frame +coordinates system) does not change. + +\p frame may be \c NULL and then represents the world coordinate system (same convention than for +the referenceFrame()). + +The rotation (and translation when \p move is \c true) applied to the Frame are filtered by the +possible constraint(). */ +void Frame::alignWithFrame(const Frame* const frame, bool move, qreal threshold) +{ + Vec directions[2][3]; + for (unsigned short d=0; d<3; ++d) + { + Vec dir((d==0)? 1.0 : 0.0, (d==1)? 1.0 : 0.0, (d==2)? 1.0 : 0.0); + if (frame) + directions[0][d] = frame->inverseTransformOf(dir); + else + directions[0][d] = dir; + directions[1][d] = inverseTransformOf(dir); + } + + qreal maxProj = 0.0; + qreal proj; + unsigned short index[2]; + index[0] = index[1] = 0; + for (unsigned short i=0; i<3; ++i) + for (unsigned short j=0; j<3; ++j) + if ( (proj=fabs(directions[0][i]*directions[1][j])) >= maxProj ) + { + index[0] = i; + index[1] = j; + maxProj = proj; + } + + Frame old; + old=*this; + + qreal coef = directions[0][index[0]] * directions[1][index[1]]; + if (fabs(coef) >= threshold) + { + const Vec axis = cross(directions[0][index[0]], directions[1][index[1]]); + qreal angle = asin(axis.norm()); + if (coef >= 0.0) + angle = -angle; + rotate(rotation().inverse() * Quaternion(axis, angle) * orientation()); + + // Try to align an other axis direction + unsigned short d = (index[1]+1) % 3; + Vec dir((d==0)? 1.0 : 0.0, (d==1)? 1.0 : 0.0, (d==2)? 1.0 : 0.0); + dir = inverseTransformOf(dir); + + qreal max = 0.0; + for (unsigned short i=0; i<3; ++i) + { + qreal proj = fabs(directions[0][i]*dir); + if (proj > max) + { + index[0] = i; + max = proj; + } + } + + if (max >= threshold) + { + const Vec axis = cross(directions[0][index[0]], dir); + qreal angle = asin(axis.norm()); + if (directions[0][index[0]] * dir >= 0.0) + angle = -angle; + rotate(rotation().inverse() * Quaternion(axis, angle) * orientation()); + } + } + + if (move) + { + Vec center; + if (frame) + center = frame->position(); + + translate(center - orientation().rotate(old.coordinatesOf(center)) - translation()); + } +} + +/*! Translates the Frame so that its position() lies on the line defined by \p origin and \p + direction (defined in the world coordinate system). + +Simply uses an orthogonal projection. \p direction does not need to be normalized. */ +void Frame::projectOnLine(const Vec& origin, const Vec& direction) +{ + // If you are trying to find a bug here, because of memory problems, you waste your time. + // This is a bug in the gcc 3.3 compiler. Compile the library in debug mode and test. + // Uncommenting this line also seems to solve the problem. Horrible. + // cout << "position = " << position() << endl; + // If you found a problem or are using a different compiler, please let me know. + const Vec shift = origin - position(); + Vec proj = shift; + proj.projectOnAxis(direction); + translate(shift-proj); +} diff --git a/QGLViewer/frame.h b/QGLViewer/frame.h new file mode 100644 index 0000000..1513283 --- /dev/null +++ b/QGLViewer/frame.h @@ -0,0 +1,415 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#ifndef QGLVIEWER_FRAME_H +#define QGLVIEWER_FRAME_H + +#include +#include + +#include "constraint.h" +// #include "GL/gl.h" is now included in config.h for ease of configuration + +namespace qglviewer { +/*! \brief The Frame class represents a coordinate system, defined by a position and an + orientation. \class Frame frame.h QGLViewer/frame.h + + A Frame is a 3D coordinate system, represented by a position() and an orientation(). The order of + these transformations is important: the Frame is first translated \e and \e then rotated around + the new translated origin. + + A Frame is useful to define the position and orientation of a 3D rigid object, using its matrix() + method, as shown below: + \code + // Builds a Frame at position (0.5,0,0) and oriented such that its Y axis is along the (1,1,1) + // direction. One could also have used setPosition() and setOrientation(). + Frame fr(Vec(0.5,0,0), Quaternion(Vec(0,1,0), Vec(1,1,1))); + glPushMatrix(); + glMultMatrixd(fr.matrix()); + // Draw your object here, in the local fr coordinate system. + glPopMatrix(); + \endcode + + Many functions are provided to transform a 3D point from one coordinate system (Frame) to an + other: see coordinatesOf(), inverseCoordinatesOf(), coordinatesOfIn(), coordinatesOfFrom()... + + You may also want to transform a 3D vector (such as a normal), which corresponds to applying only + the rotational part of the frame transformation: see transformOf() and inverseTransformOf(). See + the frameTransform example for an illustration. + + The translation() and the rotation() that are encapsulated in a Frame can also be used to + represent a \e rigid \e transformation of space. Such a transformation can also be interpreted as + a change of coordinate system, and the coordinate system conversion functions actually allow you + to use a Frame as a rigid transformation. Use inverseCoordinatesOf() (resp. coordinatesOf()) to + apply the transformation (resp. its inverse). Note the inversion. + +

Hierarchy of Frames

+ + The position and the orientation of a Frame are actually defined with respect to a + referenceFrame(). The default referenceFrame() is the world coordinate system (represented by a \c + NULL referenceFrame()). If you setReferenceFrame() to a different Frame, you must then + differentiate: + + \arg the \e local translation() and rotation(), defined with respect to the referenceFrame(), + + \arg the \e global position() and orientation(), always defined with respect to the world + coordinate system. + + A Frame is actually defined by its translation() with respect to its referenceFrame(), and then by + a rotation() of the coordinate system around the new translated origin. + + This terminology for \e local (translation() and rotation()) and \e global (position() and + orientation()) definitions is used in all the methods' names and should be sufficient to prevent + ambiguities. These notions are obviously identical when the referenceFrame() is \c NULL, i.e. when + the Frame is defined in the world coordinate system (the one you are in at the beginning of the + QGLViewer::draw() method, see the introduction page). + + Frames can hence easily be organized in a tree hierarchy, which root is the world coordinate + system. A loop in the hierarchy would result in an inconsistent (multiple) Frame definition. + settingAsReferenceFrameWillCreateALoop() checks this and prevents setReferenceFrame() from + creating such a loop. + + This frame hierarchy is used in methods like coordinatesOfIn(), coordinatesOfFrom()... which allow + coordinates (or vector) conversions from a Frame to any other one (including the world coordinate + system). + + However, one must note that this hierarchical representation is internal to the Frame classes. + When the Frames represent OpenGL coordinates system, one should map this hierarchical + representation to the OpenGL GL_MODELVIEW matrix stack. See the matrix() documentation for + details. + +

Constraints

+ + An interesting feature of Frames is that their displacements can be constrained. When a Constraint + is attached to a Frame, it filters the input of translate() and rotate(), and only the resulting + filtered motion is applied to the Frame. The default constraint() is \c NULL resulting in no + filtering. Use setConstraint() to attach a Constraint to a frame. + + Constraints are especially usefull for the ManipulatedFrame instances, in order to forbid some + mouse motions. See the constrainedFrame, constrainedCamera and luxo examples for an illustration. + + Classical constraints are provided for convenience (see LocalConstraint, WorldConstraint and + CameraConstraint) and new constraints can very easily be implemented. + +

Derived classes

+ + The ManipulatedFrame class inherits Frame and implements a mouse motion convertion, so that a + Frame (and hence an object) can be manipulated in the scene with the mouse. + + \nosubgrouping */ +class QGLVIEWER_EXPORT Frame : public QObject +{ + Q_OBJECT + +public: + Frame(); + + /*! Virtual destructor. Empty. */ + virtual ~Frame() {} + + Frame(const Frame& frame); + Frame& operator=(const Frame& frame); + +Q_SIGNALS: + /*! This signal is emitted whenever the position() or the orientation() of the Frame is modified. + + Connect this signal to any object that must be notified: + \code + QObject::connect(myFrame, SIGNAL(modified()), myObject, SLOT(update())); + \endcode + Use the QGLViewer::QGLViewerPool() to connect the signal to all the viewers. + + \note If your Frame is part of a Frame hierarchy (see referenceFrame()), a modification of one + of the parents of this Frame will \e not emit this signal. Use code like this to change this + behavior (you can do this recursively for all the referenceFrame() until the \c NULL world root + frame is encountered): + \code + // Emits the Frame modified() signal when its referenceFrame() is modified(). + connect(myFrame->referenceFrame(), SIGNAL(modified()), myFrame, SIGNAL(modified())); + \endcode + + \attention Connecting this signal to a QGLWidget::update() slot (or a method that calls it) [TODO Update with QOpenGLWidget] + will prevent you from modifying the Frame \e inside your QGLViewer::draw() method as it would + result in an infinite loop. However, QGLViewer::draw() should not modify the scene. + + \note Note that this signal might be emitted even if the Frame is not actually modified, for + instance after a translate(Vec(0,0,0)) or a setPosition(position()). */ + void modified(); + + /*! This signal is emitted when the Frame is interpolated by a KeyFrameInterpolator. + + See the KeyFrameInterpolator documentation for details. + + If a KeyFrameInterpolator is used to successively interpolate several Frames in your scene, + connect the KeyFrameInterpolator::interpolated() signal instead (identical, but independent of + the interpolated Frame). */ + void interpolated(); + +public: + /*! @name World coordinates position and orientation */ + //@{ + Frame(const Vec& position, const Quaternion& orientation); + + void setPosition(const Vec& position); + void setPosition(qreal x, qreal y, qreal z); + void setPositionWithConstraint(Vec& position); + + void setOrientation(const Quaternion& orientation); + void setOrientation(qreal q0, qreal q1, qreal q2, qreal q3); + void setOrientationWithConstraint(Quaternion& orientation); + + void setPositionAndOrientation(const Vec& position, const Quaternion& orientation); + void setPositionAndOrientationWithConstraint(Vec& position, Quaternion& orientation); + + Vec position() const; + Quaternion orientation() const; + + void getPosition(qreal& x, qreal& y, qreal& z) const; + void getOrientation(qreal& q0, qreal& q1, qreal& q2, qreal& q3) const; + //@} + + +public: + /*! @name Local translation and rotation w/r reference Frame */ + //@{ + /*! Sets the translation() of the frame, locally defined with respect to the referenceFrame(). + Emits the modified() signal. + + Use setPosition() to define the world coordinates position(). Use + setTranslationWithConstraint() to take into account the potential constraint() of the Frame. */ + void setTranslation(const Vec& translation) { t_ = translation; Q_EMIT modified(); } + void setTranslation(qreal x, qreal y, qreal z); + void setTranslationWithConstraint(Vec& translation); + + /*! Set the current rotation Quaternion. See rotation() and the different Quaternion + constructors. Emits the modified() signal. See also setTranslation() and + setRotationWithConstraint(). */ + + /*! Sets the rotation() of the Frame, locally defined with respect to the referenceFrame(). + Emits the modified() signal. + + Use setOrientation() to define the world coordinates orientation(). The potential + constraint() of the Frame is not taken into account, use setRotationWithConstraint() + instead. */ + void setRotation(const Quaternion& rotation) { q_ = rotation; Q_EMIT modified(); } + void setRotation(qreal q0, qreal q1, qreal q2, qreal q3); + void setRotationWithConstraint(Quaternion& rotation); + + void setTranslationAndRotation(const Vec& translation, const Quaternion& rotation); + void setTranslationAndRotationWithConstraint(Vec& translation, Quaternion& rotation); + + /*! Returns the Frame translation, defined with respect to the referenceFrame(). + + Use position() to get the result in the world coordinates. These two values are identical + when the referenceFrame() is \c NULL (default). + + See also setTranslation() and setTranslationWithConstraint(). */ + Vec translation() const { return t_; } + /*! Returns the Frame rotation, defined with respect to the referenceFrame(). + + Use orientation() to get the result in the world coordinates. These two values are identical + when the referenceFrame() is \c NULL (default). + + See also setRotation() and setRotationWithConstraint(). */ + + /*! Returns the current Quaternion orientation. See setRotation(). */ + Quaternion rotation() const { return q_; } + + void getTranslation(qreal& x, qreal& y, qreal& z) const; + void getRotation(qreal& q0, qreal& q1, qreal& q2, qreal& q3) const; + //@} + +public: + /*! @name Frame hierarchy */ + //@{ + /*! Returns the reference Frame, in which coordinates system the Frame is defined. + + The translation() and rotation() of the Frame are defined with respect to the referenceFrame() + coordinate system. A \c NULL referenceFrame() (default value) means that the Frame is defined in + the world coordinate system. + + Use position() and orientation() to recursively convert values along the referenceFrame() chain + and to get values expressed in the world coordinate system. The values match when the + referenceFrame() is \c NULL. + + Use setReferenceFrame() to set this value and create a Frame hierarchy. Convenient functions + allow you to convert 3D coordinates from one Frame to an other: see coordinatesOf(), + localCoordinatesOf(), coordinatesOfIn() and their inverse functions. + + Vectors can also be converted using transformOf(), transformOfIn, localTransformOf() and their + inverse functions. */ + const Frame* referenceFrame() const { return referenceFrame_; } + void setReferenceFrame(const Frame* const refFrame); + bool settingAsReferenceFrameWillCreateALoop(const Frame* const frame); + //@} + + + /*! @name Frame modification */ + //@{ + void translate(Vec& t); + void translate(const Vec& t); + // Some compilers complain about "overloading cannot distinguish from previous declaration" + // Simply comment out the following method and its associated implementation + void translate(qreal x, qreal y, qreal z); + void translate(qreal& x, qreal& y, qreal& z); + + void rotate(Quaternion& q); + void rotate(const Quaternion& q); + // Some compilers complain about "overloading cannot distinguish from previous declaration" + // Simply comment out the following method and its associated implementation + void rotate(qreal q0, qreal q1, qreal q2, qreal q3); + void rotate(qreal& q0, qreal& q1, qreal& q2, qreal& q3); + + void rotateAroundPoint(Quaternion& rotation, const Vec& point); + void rotateAroundPoint(const Quaternion& rotation, const Vec& point); + + void alignWithFrame(const Frame* const frame, bool move=false, qreal threshold=0.0); + void projectOnLine(const Vec& origin, const Vec& direction); + //@} + + + /*! @name Coordinate system transformation of 3D coordinates */ + //@{ + Vec coordinatesOf(const Vec& src) const; + Vec inverseCoordinatesOf(const Vec& src) const; + Vec localCoordinatesOf(const Vec& src) const; + Vec localInverseCoordinatesOf(const Vec& src) const; + Vec coordinatesOfIn(const Vec& src, const Frame* const in) const; + Vec coordinatesOfFrom(const Vec& src, const Frame* const from) const; + + void getCoordinatesOf(const qreal src[3], qreal res[3]) const; + void getInverseCoordinatesOf(const qreal src[3], qreal res[3]) const; + void getLocalCoordinatesOf(const qreal src[3], qreal res[3]) const; + void getLocalInverseCoordinatesOf(const qreal src[3], qreal res[3]) const; + void getCoordinatesOfIn(const qreal src[3], qreal res[3], const Frame* const in) const; + void getCoordinatesOfFrom(const qreal src[3], qreal res[3], const Frame* const from) const; + //@} + + /*! @name Coordinate system transformation of vectors */ + // A frame is as a new coordinate system, defined with respect to a reference frame (the world + // coordinate system by default, see the "Composition of frame" section). + + // The transformOf() (resp. inverseTransformOf()) functions transform a 3D vector from (resp. + // to) the world coordinates system. This section defines the 3D vector transformation + // functions. See the Coordinate system transformation of 3D points above for the transformation + // of 3D points. The difference between the two sets of functions is simple: for vectors, only + // the rotational part of the transformations is taken into account, while translation is also + // considered for 3D points. + + // The length of the resulting transformed vector is identical to the one of the source vector + // for all the described functions. + + // When local is prepended to the names of the functions, the functions simply transform from + // (and to) the reference frame. + + // When In (resp. From) is appended to the names, the functions transform from (resp. To) the + // frame that is given as an argument. The frame does not need to be in the same branch or the + // hierarchical tree, and can be \c NULL (the world coordinates system). + + // Combining any of these functions with its inverse (in any order) leads to the identity. + //@{ + Vec transformOf(const Vec& src) const; + Vec inverseTransformOf(const Vec& src) const; + Vec localTransformOf(const Vec& src) const; + Vec localInverseTransformOf(const Vec& src) const; + Vec transformOfIn(const Vec& src, const Frame* const in) const; + Vec transformOfFrom(const Vec& src, const Frame* const from) const; + + void getTransformOf(const qreal src[3], qreal res[3]) const; + void getInverseTransformOf(const qreal src[3], qreal res[3]) const; + void getLocalTransformOf(const qreal src[3], qreal res[3]) const; + void getLocalInverseTransformOf(const qreal src[3], qreal res[3]) const; + void getTransformOfIn(const qreal src[3], qreal res[3], const Frame* const in) const; + void getTransformOfFrom(const qreal src[3], qreal res[3], const Frame* const from) const; + //@} + + + /*! @name Constraint on the displacement */ + //@{ + /*! Returns the current constraint applied to the Frame. + + A \c NULL value (default) means that no Constraint is used to filter Frame translation and + rotation. See the Constraint class documentation for details. + + You may have to use a \c dynamic_cast to convert the result to a Constraint derived class. */ + Constraint* constraint() const { return constraint_; } + /*! Sets the constraint() attached to the Frame. + + A \c NULL value means no constraint. The previous constraint() should be deleted by the calling + method if needed. */ + void setConstraint(Constraint* const constraint) { constraint_ = constraint; } + //@} + + /*! @name Associated matrices */ + //@{ +public: + const GLdouble* matrix() const; + void getMatrix(GLdouble m[4][4]) const; + void getMatrix(GLdouble m[16]) const; + + const GLdouble* worldMatrix() const; + void getWorldMatrix(GLdouble m[4][4]) const; + void getWorldMatrix(GLdouble m[16]) const; + + void setFromMatrix(const GLdouble m[4][4]); + void setFromMatrix(const GLdouble m[16]); + //@} + + /*! @name Inversion of the transformation */ + //@{ + Frame inverse() const; + /*! Returns the inverse() of the Frame world transformation. + + The orientation() of the new Frame is the Quaternion::inverse() of the original orientation. + Its position() is the negated and inverse rotated image of the original position. + + The result Frame has a \c NULL referenceFrame() and a \c NULL constraint(). + + Use inverse() for a local (i.e. with respect to referenceFrame()) transformation inverse. */ + Frame worldInverse() const { return Frame(-(orientation().inverseRotate(position())), orientation().inverse()); } + //@} + + /*! @name XML representation */ + //@{ +public: + virtual QDomElement domElement(const QString& name, QDomDocument& document) const; +public Q_SLOTS: + virtual void initFromDOMElement(const QDomElement& element); + //@} + +private: + // P o s i t i o n a n d o r i e n t a t i o n + Vec t_; + Quaternion q_; + + // C o n s t r a i n t s + Constraint* constraint_; + + // F r a m e c o m p o s i t i o n + const Frame* referenceFrame_; +}; + +} // namespace qglviewer + +#endif // QGLVIEWER_FRAME_H diff --git a/QGLViewer/keyFrameInterpolator.cpp b/QGLViewer/keyFrameInterpolator.cpp new file mode 100644 index 0000000..5b96c63 --- /dev/null +++ b/QGLViewer/keyFrameInterpolator.cpp @@ -0,0 +1,549 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "domUtils.h" +#include "qglviewer.h" // for QGLViewer::drawAxis and Camera::drawCamera + +using namespace qglviewer; +using namespace std; + +/*! Creates a KeyFrameInterpolator, with \p frame as associated frame(). + + The frame() can be set or changed using setFrame(). + + interpolationTime(), interpolationSpeed() and interpolationPeriod() are set to their default + values. */ +KeyFrameInterpolator::KeyFrameInterpolator(Frame* frame) + : frame_(NULL), period_(40), interpolationTime_(0.0), interpolationSpeed_(1.0), interpolationStarted_(false), + closedPath_(false), loopInterpolation_(false), pathIsValid_(false), valuesAreValid_(true), currentFrameValid_(false) + // #CONNECTION# Values cut pasted initFromDOMElement() +{ + setFrame(frame); + for (int i=0; i<4; ++i) + currentFrame_[i] = new QMutableListIterator(keyFrame_); + connect(&timer_, SIGNAL(timeout()), SLOT(update())); +} + +/*! Virtual destructor. Clears the keyFrame path. */ +KeyFrameInterpolator::~KeyFrameInterpolator() +{ + deletePath(); + for (int i=0; i<4; ++i) + delete currentFrame_[i]; +} + +/*! Sets the frame() associated to the KeyFrameInterpolator. */ +void KeyFrameInterpolator::setFrame(Frame* const frame) +{ + if (this->frame()) + disconnect(this, SIGNAL( interpolated() ), this->frame(), SIGNAL( interpolated() )); + + frame_ = frame; + + if (this->frame()) + connect(this, SIGNAL( interpolated() ), this->frame(), SIGNAL( interpolated() )); +} + +/*! Updates frame() state according to current interpolationTime(). Then adds + interpolationPeriod()*interpolationSpeed() to interpolationTime(). + + This internal method is called by a timer when interpolationIsStarted(). It can be used for + debugging purpose. stopInterpolation() is called when interpolationTime() reaches firstTime() or + lastTime(), unless loopInterpolation() is \c true. */ +void KeyFrameInterpolator::update() +{ + interpolateAtTime(interpolationTime()); + + interpolationTime_ += interpolationSpeed() * interpolationPeriod() / 1000.0; + + if (interpolationTime() > keyFrame_.last()->time()) + { + if (loopInterpolation()) + setInterpolationTime(keyFrame_.first()->time() + interpolationTime_ - keyFrame_.last()->time()); + else + { + // Make sure last KeyFrame is reached and displayed + interpolateAtTime(keyFrame_.last()->time()); + stopInterpolation(); + } + Q_EMIT endReached(); + } + else + if (interpolationTime() < keyFrame_.first()->time()) + { + if (loopInterpolation()) + setInterpolationTime(keyFrame_.last()->time() - keyFrame_.first()->time() + interpolationTime_); + else + { + // Make sure first KeyFrame is reached and displayed + interpolateAtTime(keyFrame_.first()->time()); + stopInterpolation(); + } + Q_EMIT endReached(); + } +} + + +/*! Starts the interpolation process. + + A timer is started with an interpolationPeriod() period that updates the frame()'s position and + orientation. interpolationIsStarted() will return \c true until stopInterpolation() or + toggleInterpolation() is called. + + If \p period is positive, it is set as the new interpolationPeriod(). The previous + interpolationPeriod() is used otherwise (default). + + If interpolationTime() is larger than lastTime(), interpolationTime() is reset to firstTime() + before interpolation starts (and inversely for negative interpolationSpeed()). + + Use setInterpolationTime() before calling this method to change the starting interpolationTime(). + + See the keyFrames example for an illustration. + + You may also be interested in QGLViewer::animate() and QGLViewer::startAnimation(). + + \attention The keyFrames must be defined (see addKeyFrame()) \e before you startInterpolation(), + or else the interpolation will naturally immediately stop. */ +void KeyFrameInterpolator::startInterpolation(int period) +{ + if (period >= 0) + setInterpolationPeriod(period); + + if (!keyFrame_.isEmpty()) + { + if ((interpolationSpeed() > 0.0) && (interpolationTime() >= keyFrame_.last()->time())) + setInterpolationTime(keyFrame_.first()->time()); + if ((interpolationSpeed() < 0.0) && (interpolationTime() <= keyFrame_.first()->time())) + setInterpolationTime(keyFrame_.last()->time()); + timer_.start(interpolationPeriod()); + interpolationStarted_ = true; + update(); + } +} + + +/*! Stops an interpolation started with startInterpolation(). See interpolationIsStarted() and toggleInterpolation(). */ +void KeyFrameInterpolator::stopInterpolation() +{ + timer_.stop(); + interpolationStarted_ = false; +} + + +/*! Stops the interpolation and resets interpolationTime() to the firstTime(). + +If desired, call interpolateAtTime() after this method to actually move the frame() to +firstTime(). */ +void KeyFrameInterpolator::resetInterpolation() +{ + stopInterpolation(); + setInterpolationTime(firstTime()); +} + +/*! Appends a new keyFrame to the path, with its associated \p time (in seconds). + + The keyFrame is given as a pointer to a Frame, which will be connected to the + KeyFrameInterpolator: when \p frame is modified, the KeyFrameInterpolator path is updated + accordingly. This allows for dynamic paths, where keyFrame can be edited, even during the + interpolation. See the keyFrames example for an + illustration. + + \c NULL \p frame pointers are silently ignored. The keyFrameTime() has to be monotonously + increasing over keyFrames. + + Use addKeyFrame(const Frame&, qreal) to add keyFrame by values. */ +void KeyFrameInterpolator::addKeyFrame(const Frame* const frame, qreal time) +{ + if (!frame) + return; + + if (keyFrame_.isEmpty()) + interpolationTime_ = time; + + if ( (!keyFrame_.isEmpty()) && (keyFrame_.last()->time() > time) ) + qWarning("Error in KeyFrameInterpolator::addKeyFrame: time is not monotone"); + else + keyFrame_.append(new KeyFrame(frame, time)); + connect(frame, SIGNAL(modified()), SLOT(invalidateValues())); + valuesAreValid_ = false; + pathIsValid_ = false; + currentFrameValid_ = false; + resetInterpolation(); +} + +/*! Appends a new keyFrame to the path, with its associated \p time (in seconds). + + The path will use the current \p frame state. If you want the path to change when \p frame is + modified, you need to pass a \e pointer to the Frame instead (see addKeyFrame(const Frame*, + qreal)). + + The keyFrameTime() have to be monotonously increasing over keyFrames. */ +void KeyFrameInterpolator::addKeyFrame(const Frame& frame, qreal time) +{ + if (keyFrame_.isEmpty()) + interpolationTime_ = time; + + if ( (!keyFrame_.isEmpty()) && (keyFrame_.last()->time() > time) ) + qWarning("Error in KeyFrameInterpolator::addKeyFrame: time is not monotone"); + else + keyFrame_.append(new KeyFrame(frame, time)); + + valuesAreValid_ = false; + pathIsValid_ = false; + currentFrameValid_ = false; + resetInterpolation(); +} + + +/*! Appends a new keyFrame to the path. + + Same as addKeyFrame(const Frame* frame, qreal), except that the keyFrameTime() is set to the + previous keyFrameTime() plus one second (or 0.0 if there is no previous keyFrame). */ +void KeyFrameInterpolator::addKeyFrame(const Frame* const frame) +{ + qreal time; + if (keyFrame_.isEmpty()) + time = 0.0; + else + time = lastTime() + 1.0; + + addKeyFrame(frame, time); +} + +/*! Appends a new keyFrame to the path. + + Same as addKeyFrame(const Frame& frame, qreal), except that the keyFrameTime() is automatically set + to previous keyFrameTime() plus one second (or 0.0 if there is no previous keyFrame). */ +void KeyFrameInterpolator::addKeyFrame(const Frame& frame) +{ + qreal time; + if (keyFrame_.isEmpty()) + time = 0.0; + else + time = keyFrame_.last()->time() + 1.0; + + addKeyFrame(frame, time); +} + +/*! Removes all keyFrames from the path. The numberOfKeyFrames() is set to 0. */ +void KeyFrameInterpolator::deletePath() +{ + stopInterpolation(); + qDeleteAll(keyFrame_); + keyFrame_.clear(); + pathIsValid_ = false; + valuesAreValid_ = false; + currentFrameValid_ = false; +} + +void KeyFrameInterpolator::updateModifiedFrameValues() +{ + Quaternion prevQ = keyFrame_.first()->orientation(); + KeyFrame* kf; + for (int i=0; iframe()) + kf->updateValuesFromPointer(); + kf->flipOrientationIfNeeded(prevQ); + prevQ = kf->orientation(); + } + + KeyFrame* prev = keyFrame_.first(); + kf = keyFrame_.first(); + int index = 1; + while (kf) + { + KeyFrame* next = (index < keyFrame_.size()) ? keyFrame_.at(index) : NULL; + index++; + if (next) + kf->computeTangent(prev, next); + else + kf->computeTangent(prev, kf); + prev = kf; + kf = next; + } + valuesAreValid_ = true; +} + +/*! Returns the Frame associated with the keyFrame at index \p index. + + See also keyFrameTime(). \p index has to be in the range 0..numberOfKeyFrames()-1. + + \note If this keyFrame was defined using a pointer to a Frame (see addKeyFrame(const Frame* + const)), the \e current pointed Frame state is returned. */ +Frame KeyFrameInterpolator::keyFrame(int index) const +{ + const KeyFrame* const kf = keyFrame_.at(index); + return Frame(kf->position(), kf->orientation()); +} + +/*! Returns the time corresponding to the \p index keyFrame. + + See also keyFrame(). \p index has to be in the range 0..numberOfKeyFrames()-1. */ +qreal KeyFrameInterpolator::keyFrameTime(int index) const +{ + return keyFrame_.at(index)->time(); +} + +/*! Returns the duration of the KeyFrameInterpolator path, expressed in seconds. + + Simply corresponds to lastTime() - firstTime(). Returns 0.0 if the path has less than 2 keyFrames. + See also keyFrameTime(). */ +qreal KeyFrameInterpolator::duration() const +{ + return lastTime() - firstTime(); +} + +/*! Returns the time corresponding to the first keyFrame, expressed in seconds. + +Returns 0.0 if the path is empty. See also lastTime(), duration() and keyFrameTime(). */ +qreal KeyFrameInterpolator::firstTime() const +{ + if (keyFrame_.isEmpty()) + return 0.0; + else + return keyFrame_.first()->time(); +} + +/*! Returns the time corresponding to the last keyFrame, expressed in seconds. + +Returns 0.0 if the path is empty. See also firstTime(), duration() and keyFrameTime(). */ +qreal KeyFrameInterpolator::lastTime() const +{ + if (keyFrame_.isEmpty()) + return 0.0; + else + return keyFrame_.last()->time(); +} + +void KeyFrameInterpolator::updateCurrentKeyFrameForTime(qreal time) +{ + // Assertion: times are sorted in monotone order. + // Assertion: keyFrame_ is not empty + + // TODO: Special case for loops when closed path is implemented !! + if (!currentFrameValid_) + // Recompute everything from scrach + currentFrame_[1]->toFront(); + + while (currentFrame_[1]->peekNext()->time() > time) + { + currentFrameValid_ = false; + if (!currentFrame_[1]->hasPrevious()) + break; + currentFrame_[1]->previous(); + } + + if (!currentFrameValid_) + *currentFrame_[2] = *currentFrame_[1]; + + while (currentFrame_[2]->peekNext()->time() < time) + { + currentFrameValid_ = false; + if (!currentFrame_[2]->hasNext()) + break; + currentFrame_[2]->next(); + } + + if (!currentFrameValid_) + { + *currentFrame_[1] = *currentFrame_[2]; + if ((currentFrame_[1]->hasPrevious()) && (time < currentFrame_[2]->peekNext()->time())) + currentFrame_[1]->previous(); + + *currentFrame_[0] = *currentFrame_[1]; + if (currentFrame_[0]->hasPrevious()) + currentFrame_[0]->previous(); + + *currentFrame_[3] = *currentFrame_[2]; + if (currentFrame_[3]->hasNext()) + currentFrame_[3]->next(); + + currentFrameValid_ = true; + splineCacheIsValid_ = false; + } + + // cout << "Time = " << time << " : " << currentFrame_[0]->peekNext()->time() << " , " << + // currentFrame_[1]->peekNext()->time() << " , " << currentFrame_[2]->peekNext()->time() << " , " << currentFrame_[3]->peekNext()->time() << endl; +} + +void KeyFrameInterpolator::updateSplineCache() +{ + Vec delta = currentFrame_[2]->peekNext()->position() - currentFrame_[1]->peekNext()->position(); + v1 = 3.0 * delta - 2.0 * currentFrame_[1]->peekNext()->tgP() - currentFrame_[2]->peekNext()->tgP(); + v2 = -2.0 * delta + currentFrame_[1]->peekNext()->tgP() + currentFrame_[2]->peekNext()->tgP(); + splineCacheIsValid_ = true; +} + +/*! Interpolate frame() at time \p time (expressed in seconds). interpolationTime() is set to \p + time and frame() is set accordingly. + + If you simply want to change interpolationTime() but not the frame() state, use + setInterpolationTime() instead. + + Emits the interpolated() signal and makes the frame() emit the Frame::interpolated() signal. */ +void KeyFrameInterpolator::interpolateAtTime(qreal time) +{ + setInterpolationTime(time); + + if ((keyFrame_.isEmpty()) || (!frame())) + return; + + if (!valuesAreValid_) + updateModifiedFrameValues(); + + updateCurrentKeyFrameForTime(time); + + if (!splineCacheIsValid_) + updateSplineCache(); + + qreal alpha; + qreal dt = currentFrame_[2]->peekNext()->time() - currentFrame_[1]->peekNext()->time(); + if (dt == 0.0) + alpha = 0.0; + else + alpha = (time - currentFrame_[1]->peekNext()->time()) / dt; + + // Linear interpolation - debug + // Vec pos = alpha*(currentFrame_[2]->peekNext()->position()) + (1.0-alpha)*(currentFrame_[1]->peekNext()->position()); + Vec pos = currentFrame_[1]->peekNext()->position() + alpha * (currentFrame_[1]->peekNext()->tgP() + alpha * (v1+alpha*v2)); + Quaternion q = Quaternion::squad(currentFrame_[1]->peekNext()->orientation(), currentFrame_[1]->peekNext()->tgQ(), + currentFrame_[2]->peekNext()->tgQ(), currentFrame_[2]->peekNext()->orientation(), alpha); + frame()->setPositionAndOrientationWithConstraint(pos, q); + + Q_EMIT interpolated(); +} + +/*! Returns an XML \c QDomElement that represents the KeyFrameInterpolator. + + The resulting QDomElement holds the KeyFrameInterpolator parameters as well as the path keyFrames + (if the keyFrame is defined by a pointer to a Frame, use its current value). + + \p name is the name of the QDomElement tag. \p doc is the \c QDomDocument factory used to create + QDomElement. + + Use initFromDOMElement() to restore the ManipulatedFrame state from the resulting QDomElement. + + See Vec::domElement() for a complete example. See also Quaternion::domElement(), + Camera::domElement()... + + Note that the Camera::keyFrameInterpolator() are automatically saved by QGLViewer::saveStateToFile() + when a QGLViewer is closed. */ +QDomElement KeyFrameInterpolator::domElement(const QString& name, QDomDocument& document) const +{ + QDomElement de = document.createElement(name); + int count = 0; + Q_FOREACH (KeyFrame* kf, keyFrame_) + { + Frame fr(kf->position(), kf->orientation()); + QDomElement kfNode = fr.domElement("KeyFrame", document); + kfNode.setAttribute("index", QString::number(count)); + kfNode.setAttribute("time", QString::number(kf->time())); + de.appendChild(kfNode); + ++count; + } + de.setAttribute("nbKF", QString::number(keyFrame_.count())); + de.setAttribute("time", QString::number(interpolationTime())); + de.setAttribute("speed", QString::number(interpolationSpeed())); + de.setAttribute("period", QString::number(interpolationPeriod())); + DomUtils::setBoolAttribute(de, "closedPath", closedPath()); + DomUtils::setBoolAttribute(de, "loop", loopInterpolation()); + return de; +} + +/*! Restores the KeyFrameInterpolator state from a \c QDomElement created by domElement(). + + Note that the frame() pointer is not included in the domElement(): you need to setFrame() after + this method to attach a Frame to the KeyFrameInterpolator. + + See Vec::initFromDOMElement() for a complete code example. + + See also Camera::initFromDOMElement() and Frame::initFromDOMElement(). */ +void KeyFrameInterpolator::initFromDOMElement(const QDomElement& element) +{ + qDeleteAll(keyFrame_); + keyFrame_.clear(); + QDomElement child=element.firstChild().toElement(); + while (!child.isNull()) + { + if (child.tagName() == "KeyFrame") + { + Frame fr; + fr.initFromDOMElement(child); + qreal time = DomUtils::qrealFromDom(child, "time", 0.0); + addKeyFrame(fr, time); + } + + child = child.nextSibling().toElement(); + } + + // #CONNECTION# Values cut pasted from constructor + setInterpolationTime(DomUtils::qrealFromDom(element, "time", 0.0)); + setInterpolationSpeed(DomUtils::qrealFromDom(element, "speed", 1.0)); + setInterpolationPeriod(DomUtils::intFromDom(element, "period", 40)); + setClosedPath(DomUtils::boolFromDom(element, "closedPath", false)); + setLoopInterpolation(DomUtils::boolFromDom(element, "loop", false)); + + // setFrame(NULL); + pathIsValid_ = false; + valuesAreValid_ = false; + currentFrameValid_ = false; + + stopInterpolation(); +} + +#ifndef DOXYGEN + +//////////// KeyFrame private class implementation ///////// +KeyFrameInterpolator::KeyFrame::KeyFrame(const Frame& fr, qreal t) + : time_(t), frame_(NULL) +{ + p_ = fr.position(); + q_ = fr.orientation(); +} + +KeyFrameInterpolator::KeyFrame::KeyFrame(const Frame* fr, qreal t) + : time_(t), frame_(fr) +{ + updateValuesFromPointer(); +} + +void KeyFrameInterpolator::KeyFrame::updateValuesFromPointer() +{ + p_ = frame()->position(); + q_ = frame()->orientation(); +} + +void KeyFrameInterpolator::KeyFrame::computeTangent(const KeyFrame* const prev, const KeyFrame* const next) +{ + tgP_ = 0.5 * (next->position() - prev->position()); + tgQ_ = Quaternion::squadTangent(prev->orientation(), q_, next->orientation()); +} + +void KeyFrameInterpolator::KeyFrame::flipOrientationIfNeeded(const Quaternion& prev) +{ + if (Quaternion::dot(prev, q_) < 0.0) + q_.negate(); +} + +#endif //DOXYGEN diff --git a/QGLViewer/keyFrameInterpolator.h b/QGLViewer/keyFrameInterpolator.h new file mode 100644 index 0000000..1df8d45 --- /dev/null +++ b/QGLViewer/keyFrameInterpolator.h @@ -0,0 +1,351 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#ifndef QGLVIEWER_KEY_FRAME_INTERPOLATOR_H +#define QGLVIEWER_KEY_FRAME_INTERPOLATOR_H + +#include +#include + +#include "quaternion.h" +// Not actually needed, but some bad compilers (Microsoft VS6) complain. +#include "frame.h" + +// If you compiler complains about incomplete type, uncomment the next line +// #include "frame.h" +// and comment "class Frame;" 3 lines below + +namespace qglviewer { +class Camera; +class Frame; +/*! \brief A keyFrame Catmull-Rom Frame interpolator. + \class KeyFrameInterpolator keyFrameInterpolator.h QGLViewer/keyFrameInterpolator.h + + A KeyFrameInterpolator holds keyFrames (that define a path) and a pointer to a Frame of your + application (which will be interpolated). When the user startInterpolation(), the + KeyFrameInterpolator regularly updates the frame() position and orientation along the path. + + Here is a typical utilization example (see also the keyFrames + example): + \code + + + init() + { + // The KeyFrameInterpolator kfi is given the Frame that it will drive over time. + kfi = new KeyFrameInterpolator( new Frame() ); + kfi->addKeyFrame( Frame( Vec(1,0,0), Quaternion() ) ); + kfi->addKeyFrame( new Frame( Vec(2,1,0), Quaternion() ) ); + // ...and so on for all the keyFrames. + + // Ask for a display update after each update of the KeyFrameInterpolator + connect(kfi, SIGNAL(interpolated()), SLOT(update())); + + kfi->startInterpolation(); + } + + draw() + { + glPushMatrix(); + glMultMatrixd( kfi->frame()->matrix() ); + // Draw your object here. Its position and orientation are interpolated. + glPopMatrix(); + } + \endcode + + The keyFrames are defined by a Frame and a time, expressed in seconds. The Frame can be provided + as a const reference or as a pointer to a Frame (see the addKeyFrame() methods). In the latter + case, the path will automatically be updated when the Frame is modified (using the + Frame::modified() signal). + + The time has to be monotonously increasing over keyFrames. When interpolationSpeed() equals 1.0 + (default value), these times correspond to actual user's seconds during interpolation (provided + that your main loop is fast enough). The interpolation is then real-time: the keyFrames will be + reached at their keyFrameTime(). + +

Interpolation details

+ + When the user startInterpolation(), a timer is started which will update the frame()'s position + and orientation every interpolationPeriod() milliseconds. This update increases the + interpolationTime() by interpolationPeriod() * interpolationSpeed() milliseconds. + + Note that this mechanism ensures that the number of interpolation steps is constant and equal to + the total path duration() divided by the interpolationPeriod() * interpolationSpeed(). This is + especially useful for benchmarking or movie creation (constant number of snapshots). + + During the interpolation, the KeyFrameInterpolator emits an interpolated() signal, which will + usually be connected to the QGLViewer::update() slot. The interpolation is stopped when + interpolationTime() is greater than the lastTime() (unless loopInterpolation() is \c true) and the + endReached() signal is then emitted. + + Note that a Camera has Camera::keyFrameInterpolator(), that can be used to drive the Camera along a + path, or to restore a saved position (a path made of a single keyFrame). Press Alt+Fx to define a + new keyFrame for path x. Pressing Fx plays/pauses path interpolation. See QGLViewer::pathKey() and + the keyboard page for details. + + \attention If a Constraint is attached to the frame() (see Frame::constraint()), it should be + deactivated before interpolationIsStarted(), otherwise the interpolated motion (computed as if + there was no constraint) will probably be erroneous. + +

Retrieving interpolated values

+ + This code defines a KeyFrameInterpolator, and displays the positions that will be followed by the + frame() along the path: + \code + KeyFrameInterpolator kfi( new Frame() ); + // calls to kfi.addKeyFrame() to define the path. + + const qreal deltaTime = 0.04; // output a position every deltaTime seconds + for (qreal time=kfi.firstTime(); time<=kfi.lastTime(); time += deltaTime) + { + kfi.interpolateAtTime(time); + cout << "t=" << time << "\tpos=" << kfi.frame()->position() << endl; + } + \endcode + You may want to temporally disconnect the \c kfi interpolated() signal from the + QGLViewer::update() slot before calling this code. \nosubgrouping */ +class QGLVIEWER_EXPORT KeyFrameInterpolator : public QObject +{ + // todo closedPath, insertKeyFrames, deleteKeyFrame, replaceKeyFrame + Q_OBJECT + +public: + KeyFrameInterpolator(Frame* fr=NULL); + virtual ~KeyFrameInterpolator(); + +Q_SIGNALS: + /*! This signal is emitted whenever the frame() state is interpolated. + + The emission of this signal triggers the synchronous emission of the frame() + Frame::interpolated() signal, which may also be useful. + + This signal should especially be connected to your QGLViewer::update() slot, so that the display + is updated after every update of the KeyFrameInterpolator frame(): + \code + connect(myKeyFrameInterpolator, SIGNAL(interpolated()), SLOT(update())); + \endcode + Use the QGLViewer::QGLViewerPool() to connect the signal to all the viewers. + + Note that the QGLViewer::camera() Camera::keyFrameInterpolator() created using QGLViewer::pathKey() + have their interpolated() signals automatically connected to the QGLViewer::update() slot. */ + void interpolated(); + + /*! This signal is emitted when the interpolation reaches the first (when interpolationSpeed() + is negative) or the last keyFrame. + + When loopInterpolation() is \c true, interpolationTime() is reset and the interpolation + continues. It otherwise stops. */ + void endReached(); + + /*! @name Path creation */ + //@{ +public Q_SLOTS: + void addKeyFrame(const Frame& frame); + void addKeyFrame(const Frame& frame, qreal time); + + void addKeyFrame(const Frame* const frame); + void addKeyFrame(const Frame* const frame, qreal time); + + void deletePath(); + //@} + + /*! @name Associated Frame */ + //@{ +public: + /*! Returns the associated Frame and that is interpolated by the KeyFrameInterpolator. + + When interpolationIsStarted(), this Frame's position and orientation will regularly be updated + by a timer, so that they follow the KeyFrameInterpolator path. + + Set using setFrame() or with the KeyFrameInterpolator constructor. */ + Frame* frame() const { return frame_; } + +public Q_SLOTS: + void setFrame(Frame* const frame); + //@} + + /*! @name Path parameters */ + //@{ +public: + Frame keyFrame(int index) const; + qreal keyFrameTime(int index) const; + /*! Returns the number of keyFrames used by the interpolation. Use addKeyFrame() to add new keyFrames. */ + int numberOfKeyFrames() const { return keyFrame_.count(); } + qreal duration() const; + qreal firstTime() const; + qreal lastTime() const; + //@} + + /*! @name Interpolation parameters */ + //@{ +public: + /*! Returns the current interpolation time (in seconds) along the KeyFrameInterpolator path. + + This time is regularly updated when interpolationIsStarted(). Can be set directly with + setInterpolationTime() or interpolateAtTime(). */ + qreal interpolationTime() const { return interpolationTime_; } + /*! Returns the current interpolation speed. + + Default value is 1.0, which means keyFrameTime() will be matched during the interpolation + (provided that your main loop is fast enough). + + A negative value will result in a reverse interpolation of the keyFrames. See also + interpolationPeriod(). */ + qreal interpolationSpeed() const { return interpolationSpeed_; } + /*! Returns the current interpolation period, expressed in milliseconds. + + The update of the frame() state will be done by a timer at this period when + interpolationIsStarted(). + + This period (multiplied by interpolationSpeed()) is added to the interpolationTime() at each + update, and the frame() state is modified accordingly (see interpolateAtTime()). Default value + is 40 milliseconds. */ + int interpolationPeriod() const { return period_; } + /*! Returns \c true when the interpolation is played in an infinite loop. + + When \c false (default), the interpolation stops when interpolationTime() reaches firstTime() + (with negative interpolationSpeed()) or lastTime(). + + interpolationTime() is otherwise reset to firstTime() (+ interpolationTime() - lastTime()) (and + inversely for negative interpolationSpeed()) and interpolation continues. + + In both cases, the endReached() signal is emitted. */ + bool loopInterpolation() const { return loopInterpolation_; } +#ifndef DOXYGEN + /*! Whether or not (default) the path defined by the keyFrames is a closed loop. When \c true, + the last and the first KeyFrame are linked by a new spline segment. + + Use setLoopInterpolation() to create a continuous animation over the entire path. + \attention The closed path feature is not yet implemented. */ + bool closedPath() const { return closedPath_; } +#endif +public Q_SLOTS: + /*! Sets the interpolationTime(). + + \attention The frame() state is not affected by this method. Use this function to define the + starting time of a future interpolation (see startInterpolation()). Use interpolateAtTime() to + actually interpolate at a given time. */ + void setInterpolationTime(qreal time) { interpolationTime_ = time; } + /*! Sets the interpolationSpeed(). Negative or null values are allowed. */ + void setInterpolationSpeed(qreal speed) { interpolationSpeed_ = speed; } + /*! Sets the interpolationPeriod(). */ + void setInterpolationPeriod(int period) { period_ = period; } + /*! Sets the loopInterpolation() value. */ + void setLoopInterpolation(bool loop=true) { loopInterpolation_ = loop; } +#ifndef DOXYGEN + /*! Sets the closedPath() value. \attention The closed path feature is not yet implemented. */ + void setClosedPath(bool closed=true) { closedPath_ = closed; } +#endif + //@} + + + /*! @name Interpolation */ + //@{ +public: + /*! Returns \c true when the interpolation is being performed. Use startInterpolation(), + stopInterpolation() or toggleInterpolation() to modify this state. */ + bool interpolationIsStarted() const { return interpolationStarted_; } +public Q_SLOTS: + void startInterpolation(int period = -1); + void stopInterpolation(); + void resetInterpolation(); + /*! Calls startInterpolation() or stopInterpolation(), depending on interpolationIsStarted(). */ + void toggleInterpolation() { if (interpolationIsStarted()) stopInterpolation(); else startInterpolation(); } + virtual void interpolateAtTime(qreal time); + //@} + + /*! @name XML representation */ + //@{ +public: + virtual QDomElement domElement(const QString& name, QDomDocument& document) const; + virtual void initFromDOMElement(const QDomElement& element); + //@} + +private Q_SLOTS: + virtual void update(); + virtual void invalidateValues() { valuesAreValid_ = false; pathIsValid_ = false; splineCacheIsValid_ = false; } + +private: + // Copy constructor and opertor= are declared private and undefined + // Prevents everyone from trying to use them + // KeyFrameInterpolator(const KeyFrameInterpolator& kfi); + // KeyFrameInterpolator& operator=(const KeyFrameInterpolator& kfi); + + void updateCurrentKeyFrameForTime(qreal time); + void updateModifiedFrameValues(); + void updateSplineCache(); + +#ifndef DOXYGEN + // Internal private KeyFrame representation + class KeyFrame + { + public: + KeyFrame(const Frame& fr, qreal t); + KeyFrame(const Frame* fr, qreal t); + + Vec position() const { return p_; } + Quaternion orientation() const { return q_; } + Vec tgP() const { return tgP_; } + Quaternion tgQ() const { return tgQ_; } + qreal time() const { return time_; } + const Frame* frame() const { return frame_; } + void updateValuesFromPointer(); + void flipOrientationIfNeeded(const Quaternion& prev); + void computeTangent(const KeyFrame* const prev, const KeyFrame* const next); + private: + Vec p_, tgP_; + Quaternion q_, tgQ_; + qreal time_; + const Frame* const frame_; + }; +#endif + + // K e y F r a m e s + mutable QList keyFrame_; + QMutableListIterator* currentFrame_[4]; + QList path_; + + // A s s o c i a t e d f r a m e + Frame* frame_; + + // R h y t h m + QTimer timer_; + int period_; + qreal interpolationTime_; + qreal interpolationSpeed_; + bool interpolationStarted_; + + // M i s c + bool closedPath_; + bool loopInterpolation_; + + // C a c h e d v a l u e s a n d f l a g s + bool pathIsValid_; + bool valuesAreValid_; + bool currentFrameValid_; + bool splineCacheIsValid_; + Vec v1, v2; +}; + +} // namespace qglviewer + +#endif // QGLVIEWER_KEY_FRAME_INTERPOLATOR_H diff --git a/QGLViewer/manipulatedCameraFrame.cpp b/QGLViewer/manipulatedCameraFrame.cpp new file mode 100644 index 0000000..723b0f7 --- /dev/null +++ b/QGLViewer/manipulatedCameraFrame.cpp @@ -0,0 +1,469 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "domUtils.h" +#include "manipulatedCameraFrame.h" +#include "qglviewer.h" + +#include + +using namespace qglviewer; +using namespace std; + +/*! Default constructor. + + flySpeed() is set to 0.0 and sceneUpVector() is (0,1,0). The pivotPoint() is set to (0,0,0). + + \attention Created object is removeFromMouseGrabberPool(). */ +ManipulatedCameraFrame::ManipulatedCameraFrame() + : driveSpeed_(0.0), sceneUpVector_(0.0, 1.0, 0.0), rotatesAroundUpVector_(false), zoomsOnPivotPoint_(false) +{ + setFlySpeed(0.0); + removeFromMouseGrabberPool(); + connect(&flyTimer_, SIGNAL(timeout()), SLOT(flyUpdate())); +} + +/*! Equal operator. Calls ManipulatedFrame::operator=() and then copy attributes. */ +ManipulatedCameraFrame& ManipulatedCameraFrame::operator=(const ManipulatedCameraFrame& mcf) +{ + ManipulatedFrame::operator=(mcf); + + setFlySpeed(mcf.flySpeed()); + setSceneUpVector(mcf.sceneUpVector()); + setRotatesAroundUpVector(mcf.rotatesAroundUpVector_); + setZoomsOnPivotPoint(mcf.zoomsOnPivotPoint_); + + return *this; +} + +/*! Copy constructor. Performs a deep copy of all members using operator=(). */ +ManipulatedCameraFrame::ManipulatedCameraFrame(const ManipulatedCameraFrame& mcf) + : ManipulatedFrame(mcf) +{ + removeFromMouseGrabberPool(); + connect(&flyTimer_, SIGNAL(timeout()), SLOT(flyUpdate())); + (*this)=(mcf); +} + +//////////////////////////////////////////////////////////////////////////////// + +/*! Overloading of ManipulatedFrame::spin(). + +Rotates the ManipulatedCameraFrame around its pivotPoint() instead of its origin. */ +void ManipulatedCameraFrame::spin() +{ + rotateAroundPoint(spinningQuaternion(), pivotPoint()); +} + +#ifndef DOXYGEN +/*! Called for continuous frame motion in fly mode (see QGLViewer::MOVE_FORWARD). Emits + manipulated(). */ +void ManipulatedCameraFrame::flyUpdate() +{ + static Vec flyDisp(0.0, 0.0, 0.0); + switch (action_) + { + case QGLViewer::MOVE_FORWARD: + flyDisp.z = -flySpeed(); + translate(localInverseTransformOf(flyDisp)); + break; + case QGLViewer::MOVE_BACKWARD: + flyDisp.z = flySpeed(); + translate(localInverseTransformOf(flyDisp)); + break; + case QGLViewer::DRIVE: + flyDisp.z = flySpeed() * driveSpeed_; + translate(localInverseTransformOf(flyDisp)); + break; + default: + break; + } + + // Needs to be out of the switch since ZOOM/fastDraw()/wheelEvent use this callback to trigger a final draw(). + // #CONNECTION# wheelEvent. + Q_EMIT manipulated(); +} + +Vec ManipulatedCameraFrame::flyUpVector() const { + qWarning("flyUpVector() is deprecated. Use sceneUpVector() instead."); + return sceneUpVector(); +} + +void ManipulatedCameraFrame::setFlyUpVector(const Vec& up) { + qWarning("setFlyUpVector() is deprecated. Use setSceneUpVector() instead."); + setSceneUpVector(up); +} + +#endif + +/*! This method will be called by the Camera when its orientation is changed, so that the +sceneUpVector (private) is changed accordingly. You should not need to call this method. */ +void ManipulatedCameraFrame::updateSceneUpVector() +{ + sceneUpVector_ = inverseTransformOf(Vec(0.0, 1.0, 0.0)); +} + +//////////////////////////////////////////////////////////////////////////////// +// S t a t e s a v i n g a n d r e s t o r i n g // +//////////////////////////////////////////////////////////////////////////////// + +/*! Returns an XML \c QDomElement that represents the ManipulatedCameraFrame. + + Adds to the ManipulatedFrame::domElement() the ManipulatedCameraFrame specific informations in a \c + ManipulatedCameraParameters child QDomElement. + + \p name is the name of the QDomElement tag. \p doc is the \c QDomDocument factory used to create + QDomElement. + + Use initFromDOMElement() to restore the ManipulatedCameraFrame state from the resulting + \c QDomElement. + + See Vec::domElement() for a complete example. See also Quaternion::domElement(), + Frame::domElement(), Camera::domElement()... */ +QDomElement ManipulatedCameraFrame::domElement(const QString& name, QDomDocument& document) const +{ + QDomElement e = ManipulatedFrame::domElement(name, document); + QDomElement mcp = document.createElement("ManipulatedCameraParameters"); + mcp.setAttribute("flySpeed", QString::number(flySpeed())); + DomUtils::setBoolAttribute(mcp, "rotatesAroundUpVector", rotatesAroundUpVector()); + DomUtils::setBoolAttribute(mcp, "zoomsOnPivotPoint", zoomsOnPivotPoint()); + mcp.appendChild(sceneUpVector().domElement("sceneUpVector", document)); + e.appendChild(mcp); + return e; +} + +/*! Restores the ManipulatedCameraFrame state from a \c QDomElement created by domElement(). + +First calls ManipulatedFrame::initFromDOMElement() and then initializes ManipulatedCameraFrame +specific parameters. */ +void ManipulatedCameraFrame::initFromDOMElement(const QDomElement& element) +{ + // No need to initialize, since default sceneUpVector and flySpeed are not meaningful. + // It's better to keep current ones. And it would destroy constraint() and referenceFrame(). + // *this = ManipulatedCameraFrame(); + ManipulatedFrame::initFromDOMElement(element); + + QDomElement child=element.firstChild().toElement(); + while (!child.isNull()) + { + if (child.tagName() == "ManipulatedCameraParameters") + { + setFlySpeed(DomUtils::qrealFromDom(child, "flySpeed", flySpeed())); + setRotatesAroundUpVector(DomUtils::boolFromDom(child, "rotatesAroundUpVector", false)); + setZoomsOnPivotPoint(DomUtils::boolFromDom(child, "zoomsOnPivotPoint", false)); + + QDomElement schild=child.firstChild().toElement(); + while (!schild.isNull()) + { + if (schild.tagName() == "sceneUpVector") + setSceneUpVector(Vec(schild)); + + schild = schild.nextSibling().toElement(); + } + } + child = child.nextSibling().toElement(); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// M o u s e h a n d l i n g // +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DOXYGEN +/*! Protected internal method used to handle mouse events. */ +void ManipulatedCameraFrame::startAction(int ma, bool withConstraint) +{ + ManipulatedFrame::startAction(ma, withConstraint); + + switch (action_) + { + case QGLViewer::MOVE_FORWARD: + case QGLViewer::MOVE_BACKWARD: + case QGLViewer::DRIVE: + flyTimer_.setSingleShot(false); + flyTimer_.start(10); + break; + case QGLViewer::ROTATE: + constrainedRotationIsReversed_ = transformOf(sceneUpVector_).y < 0.0; + break; + default: + break; + } +} + +void ManipulatedCameraFrame::zoom(qreal delta, const Camera * const camera) { + const qreal sceneRadius = camera->sceneRadius(); + if (zoomsOnPivotPoint_) { + Vec direction = position() - camera->pivotPoint(); + if (direction.norm() > 0.02 * sceneRadius || delta > 0.0) + translate(delta * direction); + } else { + const qreal coef = qMax(fabs((camera->frame()->coordinatesOf(camera->pivotPoint())).z), 0.2 * sceneRadius); + Vec trans(0.0, 0.0, -coef * delta); + translate(inverseTransformOf(trans)); + } +} + +#endif + +/*! Overloading of ManipulatedFrame::mouseMoveEvent(). + +Motion depends on mouse binding (see mouse page for details). The +resulting displacements are basically inverted from those of a ManipulatedFrame. */ +void ManipulatedCameraFrame::mouseMoveEvent(QMouseEvent* const event, Camera* const camera) +{ + // #CONNECTION# QGLViewer::mouseMoveEvent does the update(). + switch (action_) + { + case QGLViewer::TRANSLATE: + { + const QPoint delta = prevPos_ - event->pos(); + Vec trans(delta.x(), -delta.y(), 0.0); + // Scale to fit the screen mouse displacement + switch (camera->type()) + { + case Camera::PERSPECTIVE : + trans *= 2.0 * tan(camera->fieldOfView()/2.0) * + fabs((camera->frame()->coordinatesOf(pivotPoint())).z) / camera->screenHeight(); + break; + case Camera::ORTHOGRAPHIC : + { + GLdouble w,h; + camera->getOrthoWidthHeight(w, h); + trans[0] *= 2.0 * w / camera->screenWidth(); + trans[1] *= 2.0 * h / camera->screenHeight(); + break; + } + } + translate(inverseTransformOf(translationSensitivity()*trans)); + break; + } + + case QGLViewer::MOVE_FORWARD: + { + Quaternion rot = pitchYawQuaternion(event->x(), event->y(), camera); + rotate(rot); + //#CONNECTION# wheelEvent MOVE_FORWARD case + // actual translation is made in flyUpdate(). + //translate(inverseTransformOf(Vec(0.0, 0.0, -flySpeed()))); + break; + } + + case QGLViewer::MOVE_BACKWARD: + { + Quaternion rot = pitchYawQuaternion(event->x(), event->y(), camera); + rotate(rot); + // actual translation is made in flyUpdate(). + //translate(inverseTransformOf(Vec(0.0, 0.0, flySpeed()))); + break; + } + + case QGLViewer::DRIVE: + { + Quaternion rot = turnQuaternion(event->x(), camera); + rotate(rot); + // actual translation is made in flyUpdate(). + driveSpeed_ = 0.01 * (event->y() - pressPos_.y()); + break; + } + + case QGLViewer::ZOOM: + { + zoom(deltaWithPrevPos(event, camera), camera); + break; + } + + case QGLViewer::LOOK_AROUND: + { + Quaternion rot = pitchYawQuaternion(event->x(), event->y(), camera); + rotate(rot); + break; + } + + case QGLViewer::ROTATE: + { + Quaternion rot; + if (rotatesAroundUpVector_) { + // Multiply by 2.0 to get on average about the same speed as with the deformed ball + qreal dx = 2.0 * rotationSensitivity() * (prevPos_.x() - event->x()) / camera->screenWidth(); + qreal dy = 2.0 * rotationSensitivity() * (prevPos_.y() - event->y()) / camera->screenHeight(); + if (constrainedRotationIsReversed_) dx = -dx; + Vec verticalAxis = transformOf(sceneUpVector_); + rot = Quaternion(verticalAxis, dx) * Quaternion(Vec(1.0, 0.0, 0.0), dy); + } else { + Vec trans = camera->projectedCoordinatesOf(pivotPoint()); + rot = deformedBallQuaternion(event->x(), event->y(), trans[0], trans[1], camera); + } + //#CONNECTION# These two methods should go together (spinning detection and activation) + computeMouseSpeed(event); + setSpinningQuaternion(rot); + spin(); + break; + } + + case QGLViewer::SCREEN_ROTATE: + { + Vec trans = camera->projectedCoordinatesOf(pivotPoint()); + + const qreal angle = atan2(event->y() - trans[1], event->x() - trans[0]) - atan2(prevPos_.y()-trans[1], prevPos_.x()-trans[0]); + + Quaternion rot(Vec(0.0, 0.0, 1.0), angle); + //#CONNECTION# These two methods should go together (spinning detection and activation) + computeMouseSpeed(event); + setSpinningQuaternion(rot); + spin(); + updateSceneUpVector(); + break; + } + + case QGLViewer::ROLL: + { + const qreal angle = M_PI * (event->x() - prevPos_.x()) / camera->screenWidth(); + Quaternion rot(Vec(0.0, 0.0, 1.0), angle); + rotate(rot); + setSpinningQuaternion(rot); + updateSceneUpVector(); + break; + } + + case QGLViewer::SCREEN_TRANSLATE: + { + Vec trans; + int dir = mouseOriginalDirection(event); + if (dir == 1) + trans.setValue(prevPos_.x() - event->x(), 0.0, 0.0); + else if (dir == -1) + trans.setValue(0.0, event->y() - prevPos_.y(), 0.0); + + switch (camera->type()) + { + case Camera::PERSPECTIVE : + trans *= 2.0 * tan(camera->fieldOfView()/2.0) * + fabs((camera->frame()->coordinatesOf(pivotPoint())).z) / camera->screenHeight(); + break; + case Camera::ORTHOGRAPHIC : + { + GLdouble w,h; + camera->getOrthoWidthHeight(w, h); + trans[0] *= 2.0 * w / camera->screenWidth(); + trans[1] *= 2.0 * h / camera->screenHeight(); + break; + } + } + + translate(inverseTransformOf(translationSensitivity()*trans)); + break; + } + + case QGLViewer::ZOOM_ON_REGION: + case QGLViewer::NO_MOUSE_ACTION: + break; + } + + if (action_ != QGLViewer::NO_MOUSE_ACTION) + { + prevPos_ = event->pos(); + if (action_ != QGLViewer::ZOOM_ON_REGION) + // ZOOM_ON_REGION should not emit manipulated(). + // prevPos_ is used to draw rectangle feedback. + Q_EMIT manipulated(); + } +} + + +/*! This is an overload of ManipulatedFrame::mouseReleaseEvent(). The QGLViewer::MouseAction is + terminated. */ +void ManipulatedCameraFrame::mouseReleaseEvent(QMouseEvent* const event, Camera* const camera) +{ + if ((action_ == QGLViewer::MOVE_FORWARD) || (action_ == QGLViewer::MOVE_BACKWARD) || (action_ == QGLViewer::DRIVE)) + flyTimer_.stop(); + + if (action_ == QGLViewer::ZOOM_ON_REGION) + camera->fitScreenRegion(QRect(pressPos_, event->pos())); + + ManipulatedFrame::mouseReleaseEvent(event, camera); +} + +/*! This is an overload of ManipulatedFrame::wheelEvent(). + +The wheel behavior depends on the wheel binded action. Current possible actions are QGLViewer::ZOOM, +QGLViewer::MOVE_FORWARD, QGLViewer::MOVE_BACKWARD. QGLViewer::ZOOM speed depends on +wheelSensitivity() while QGLViewer::MOVE_FORWARD and QGLViewer::MOVE_BACKWARD depend on flySpeed(). +See QGLViewer::setWheelBinding() to customize the binding. */ +void ManipulatedCameraFrame::wheelEvent(QWheelEvent* const event, Camera* const camera) +{ + //#CONNECTION# QGLViewer::setWheelBinding, ManipulatedFrame::wheelEvent. + switch (action_) + { + case QGLViewer::ZOOM: + { + zoom(wheelDelta(event), camera); + Q_EMIT manipulated(); + break; + } + case QGLViewer::MOVE_FORWARD: + case QGLViewer::MOVE_BACKWARD: + //#CONNECTION# mouseMoveEvent() MOVE_FORWARD case + translate(inverseTransformOf(Vec(0.0, 0.0, 0.2*flySpeed()*event->delta()))); + Q_EMIT manipulated(); + break; + default: + break; + } + + // #CONNECTION# startAction should always be called before + if (previousConstraint_) + setConstraint(previousConstraint_); + + // The wheel triggers a fastDraw. A final update() is needed after the last wheel event to + // polish the rendering using draw(). Since the last wheel event does not say its name, we use + // the flyTimer_ to trigger flyUpdate(), which emits manipulated. Two wheel events + // separated by more than this delay milliseconds will trigger a draw(). + const int finalDrawAfterWheelEventDelay = 400; + + // Starts (or prolungates) the timer. + flyTimer_.setSingleShot(true); + flyTimer_.start(finalDrawAfterWheelEventDelay); + + // This could also be done *before* manipulated is emitted, so that isManipulated() returns false. + // But then fastDraw would not be used with wheel. + // Detecting the last wheel event and forcing a final draw() is done using the timer_. + action_ = QGLViewer::NO_MOUSE_ACTION; +} + +//////////////////////////////////////////////////////////////////////////////// + +/*! Returns a Quaternion that is a rotation around current camera Y, proportionnal to the horizontal mouse position. */ +Quaternion ManipulatedCameraFrame::turnQuaternion(int x, const Camera* const camera) +{ + return Quaternion(Vec(0.0, 1.0, 0.0), rotationSensitivity()*(prevPos_.x()-x)/camera->screenWidth()); +} + +/*! Returns a Quaternion that is the composition of two rotations, inferred from the + mouse pitch (X axis) and yaw (sceneUpVector() axis). */ +Quaternion ManipulatedCameraFrame::pitchYawQuaternion(int x, int y, const Camera* const camera) +{ + const Quaternion rotX(Vec(1.0, 0.0, 0.0), rotationSensitivity()*(prevPos_.y()-y)/camera->screenHeight()); + const Quaternion rotY(transformOf(sceneUpVector()), rotationSensitivity()*(prevPos_.x()-x)/camera->screenWidth()); + return rotY * rotX; +} diff --git a/QGLViewer/manipulatedCameraFrame.h b/QGLViewer/manipulatedCameraFrame.h new file mode 100644 index 0000000..1f500ad --- /dev/null +++ b/QGLViewer/manipulatedCameraFrame.h @@ -0,0 +1,233 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#ifndef QGLVIEWER_MANIPULATED_CAMERA_FRAME_H +#define QGLVIEWER_MANIPULATED_CAMERA_FRAME_H + +#include "manipulatedFrame.h" + +namespace qglviewer { +/*! \brief The ManipulatedCameraFrame class represents a ManipulatedFrame with Camera specific mouse bindings. + \class ManipulatedCameraFrame manipulatedCameraFrame.h QGLViewer/manipulatedCameraFrame.h + + A ManipulatedCameraFrame is a specialization of a ManipulatedFrame, designed to be set as the + Camera::frame(). Mouse motions are basically interpreted in a negated way: when the mouse goes to + the right, the ManipulatedFrame translation goes to the right, while the ManipulatedCameraFrame + has to go to the \e left, so that the \e scene seems to move to the right. + + A ManipulatedCameraFrame rotates around its pivotPoint(), which corresponds to the + associated Camera::pivotPoint(). + + A ManipulatedCameraFrame can also "fly" in the scene. It basically moves forward, and turns + according to the mouse motion. See flySpeed(), sceneUpVector() and the QGLViewer::MOVE_FORWARD and + QGLViewer::MOVE_BACKWARD QGLViewer::MouseAction. + + See the mouse page for a description of the possible actions that can + be performed using the mouse and their bindings. + \nosubgrouping */ +class QGLVIEWER_EXPORT ManipulatedCameraFrame : public ManipulatedFrame +{ +#ifndef DOXYGEN + friend class Camera; + friend class ::QGLViewer; +#endif + + Q_OBJECT + +public: + ManipulatedCameraFrame(); + /*! Virtual destructor. Empty. */ + virtual ~ManipulatedCameraFrame() {} + + ManipulatedCameraFrame(const ManipulatedCameraFrame& mcf); + ManipulatedCameraFrame& operator=(const ManipulatedCameraFrame& mcf); + + /*! @name Pivot point */ + //@{ +public: + /*! Returns the point the ManipulatedCameraFrame pivot point, around which the camera rotates. + + It is defined in the world coordinate system. Default value is (0,0,0). + + When the ManipulatedCameraFrame is associated to a Camera, Camera::pivotPoint() also + returns this value. This point can interactively be changed using the mouse (see + Camera::setPivotPointFromPixel() and QGLViewer::RAP_FROM_PIXEL and QGLViewer::RAP_IS_CENTER + in the mouse page). */ + Vec pivotPoint() const { return pivotPoint_; } + /*! Sets the pivotPoint(), defined in the world coordinate system. */ + void setPivotPoint(const Vec& point) { pivotPoint_ = point; } + +#ifndef DOXYGEN + Vec revolveAroundPoint() const { qWarning("revolveAroundPoint() is deprecated, use pivotPoint() instead"); return pivotPoint(); } + void setRevolveArountPoint(const Vec& point) { qWarning("setRevolveAroundPoint() is deprecated, use setPivotPoint() instead"); setPivotPoint(point); } +#endif + //@} + + /*! @name Camera manipulation */ + //@{ +public: + /*! Returns \c true when the frame's rotation is constrained around the sceneUpVector(), + and \c false otherwise, when the rotation is completely free (default). + + In free mode, the associated camera can be arbitrarily rotated in the scene, along its + three axis, thus possibly leading to any arbitrary orientation. + + When you setRotatesAroundUpVector() to \c true, the sceneUpVector() defines a + 'vertical' direction around which the camera rotates. The camera can rotate left + or right, around this axis. It can also be moved up or down to show the 'top' and + 'bottom' views of the scene. As a result, the sceneUpVector() will always appear vertical + in the scene, and the horizon is preserved and stays projected along the camera's + horizontal axis. + + Note that setting this value to \c true when the sceneUpVector() is not already + vertically projected will break these invariants. It will also limit the possible movement + of the camera, possibly up to a lock when the sceneUpVector() is projected horizontally. + Use Camera::setUpVector() to define the sceneUpVector() and align the camera before calling + this method to ensure this does not happen. */ + bool rotatesAroundUpVector() const { return rotatesAroundUpVector_; } + /*! Sets the value of rotatesAroundUpVector(). + + Default value is false (free rotation). */ + void setRotatesAroundUpVector(bool constrained) { rotatesAroundUpVector_ = constrained; } + + /*! Returns whether or not the QGLViewer::ZOOM action zooms on the pivot point. + + When set to \c false (default), a zoom action will move the camera along its Camera::viewDirection(), + i.e. back and forth along a direction perpendicular to the projection screen. + + setZoomsOnPivotPoint() to \c true will move the camera along an axis defined by the + Camera::pivotPoint() and its current position instead. As a result, the projected position of the + pivot point on screen will stay the same during a zoom. */ + bool zoomsOnPivotPoint() const { return zoomsOnPivotPoint_; } + /*! Sets the value of zoomsOnPivotPoint(). + + Default value is false. */ + void setZoomsOnPivotPoint(bool enabled) { zoomsOnPivotPoint_ = enabled; } + +private: +#ifndef DOXYGEN + void zoom(qreal delta, const Camera * const camera); +#endif + //@} + + /*! @name Fly parameters */ + //@{ +public Q_SLOTS: + /*! Sets the flySpeed(), defined in OpenGL units. + + Default value is 0.0, but it is modified according to the QGLViewer::sceneRadius() when the + ManipulatedCameraFrame is set as the Camera::frame(). */ + void setFlySpeed(qreal speed) { flySpeed_ = speed; } + + /*! Sets the sceneUpVector(), defined in the world coordinate system. + + Default value is (0,1,0), but it is updated by the Camera when this object is set as its Camera::frame(). + Using Camera::setUpVector() instead is probably a better solution. */ + void setSceneUpVector(const Vec& up) { sceneUpVector_ = up; } + +public: + /*! Returns the fly speed, expressed in OpenGL units. + + It corresponds to the incremental displacement that is periodically applied to the + ManipulatedCameraFrame position when a QGLViewer::MOVE_FORWARD or QGLViewer::MOVE_BACKWARD + QGLViewer::MouseAction is proceeded. + + \attention When the ManipulatedCameraFrame is set as the Camera::frame(), this value is set + according to the QGLViewer::sceneRadius() by QGLViewer::setSceneRadius(). */ + qreal flySpeed() const { return flySpeed_; } + + /*! Returns the up vector of the scene, expressed in the world coordinate system. + + In 'fly mode' (corresponding to the QGLViewer::MOVE_FORWARD and QGLViewer::MOVE_BACKWARD + QGLViewer::MouseAction bindings), horizontal displacements of the mouse rotate + the ManipulatedCameraFrame around this vector. Vertical displacements rotate always around the + Camera \c X axis. + + This value is also used when setRotationIsConstrained() is set to \c true to define the up vector + (and incidentally the 'horizon' plane) around which the camera will rotate. + + Default value is (0,1,0), but it is updated by the Camera when this object is set as its Camera::frame(). + Camera::setOrientation() and Camera::setUpVector()) direclty modify this value and should be used + instead. */ + Vec sceneUpVector() const { return sceneUpVector_; } + +#ifndef DOXYGEN + Vec flyUpVector() const; + void setFlyUpVector(const Vec& up); +#endif + //@} + + /*! @name Mouse event handlers */ + //@{ +protected: + virtual void mouseReleaseEvent(QMouseEvent* const event, Camera* const camera); + virtual void mouseMoveEvent (QMouseEvent* const event, Camera* const camera); + virtual void wheelEvent (QWheelEvent* const event, Camera* const camera); + //@} + + /*! @name Spinning */ + //@{ +protected Q_SLOTS: + virtual void spin(); + //@} + + /*! @name XML representation */ + //@{ +public: + virtual QDomElement domElement(const QString& name, QDomDocument& document) const; +public Q_SLOTS: + virtual void initFromDOMElement(const QDomElement& element); + //@} + +#ifndef DOXYGEN +protected: + virtual void startAction(int ma, bool withConstraint=true); // int is really a QGLViewer::MouseAction +#endif + +private Q_SLOTS: + virtual void flyUpdate(); + +private: + void updateSceneUpVector(); + Quaternion turnQuaternion(int x, const Camera* const camera); + Quaternion pitchYawQuaternion(int x, int y, const Camera* const camera); + +private: + // Fly mode data + qreal flySpeed_; + qreal driveSpeed_; + Vec sceneUpVector_; + QTimer flyTimer_; + + bool rotatesAroundUpVector_; + // Inverse the direction of an horizontal mouse motion. Depends on the projected + // screen orientation of the vertical axis when the mouse button is pressed. + bool constrainedRotationIsReversed_; + + bool zoomsOnPivotPoint_; + + Vec pivotPoint_; +}; + +} // namespace qglviewer + +#endif // QGLVIEWER_MANIPULATED_CAMERA_FRAME_H diff --git a/QGLViewer/manipulatedFrame.cpp b/QGLViewer/manipulatedFrame.cpp new file mode 100644 index 0000000..b6b2d72 --- /dev/null +++ b/QGLViewer/manipulatedFrame.cpp @@ -0,0 +1,550 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "domUtils.h" +#include "manipulatedFrame.h" +#include "manipulatedCameraFrame.h" +#include "qglviewer.h" +#include "camera.h" + +#include + +#include + +using namespace qglviewer; +using namespace std; + +/*! Default constructor. + + The translation is set to (0,0,0), with an identity rotation (0,0,0,1) (see Frame constructor + for details). + + The different sensitivities are set to their default values (see rotationSensitivity(), + translationSensitivity(), spinningSensitivity() and wheelSensitivity()). */ +ManipulatedFrame::ManipulatedFrame() + : action_(QGLViewer::NO_MOUSE_ACTION), keepsGrabbingMouse_(false) +{ + // #CONNECTION# initFromDOMElement and accessor docs + setRotationSensitivity(1.0); + setTranslationSensitivity(1.0); + setSpinningSensitivity(0.3); + setWheelSensitivity(1.0); + setZoomSensitivity(1.0); + + isSpinning_ = false; + previousConstraint_ = NULL; + + connect(&spinningTimer_, SIGNAL(timeout()), SLOT(spinUpdate())); +} + +/*! Equal operator. Calls Frame::operator=() and then copy attributes. */ +ManipulatedFrame& ManipulatedFrame::operator=(const ManipulatedFrame& mf) +{ + Frame::operator=(mf); + + setRotationSensitivity(mf.rotationSensitivity()); + setTranslationSensitivity(mf.translationSensitivity()); + setSpinningSensitivity(mf.spinningSensitivity()); + setWheelSensitivity(mf.wheelSensitivity()); + setZoomSensitivity(mf.zoomSensitivity()); + + mouseSpeed_ = 0.0; + dirIsFixed_ = false; + keepsGrabbingMouse_ = false; + action_ = QGLViewer::NO_MOUSE_ACTION; + + return *this; +} + +/*! Copy constructor. Performs a deep copy of all attributes using operator=(). */ +ManipulatedFrame::ManipulatedFrame(const ManipulatedFrame& mf) + : Frame(mf), MouseGrabber() +{ + (*this)=mf; +} + +//////////////////////////////////////////////////////////////////////////////// + +/*! Implementation of the MouseGrabber main method. + +The ManipulatedFrame grabsMouse() when the mouse is within a 10 pixels region around its +Camera::projectedCoordinatesOf() position(). + +See the mouseGrabber example for an illustration. */ +void ManipulatedFrame::checkIfGrabsMouse(int x, int y, const Camera* const camera) +{ + const int thresold = 10; + const Vec proj = camera->projectedCoordinatesOf(position()); + setGrabsMouse(keepsGrabbingMouse_ || ((fabs(x-proj.x) < thresold) && (fabs(y-proj.y) < thresold))); +} + +//////////////////////////////////////////////////////////////////////////////// +// S t a t e s a v i n g a n d r e s t o r i n g // +//////////////////////////////////////////////////////////////////////////////// + +/*! Returns an XML \c QDomElement that represents the ManipulatedFrame. + + Adds to the Frame::domElement() the ManipulatedFrame specific informations in a \c + ManipulatedParameters child QDomElement. + + \p name is the name of the QDomElement tag. \p doc is the \c QDomDocument factory used to create + QDomElement. + + Use initFromDOMElement() to restore the ManipulatedFrame state from the resulting \c QDomElement. + + See Vec::domElement() for a complete example. See also Quaternion::domElement(), + Camera::domElement()... */ +QDomElement ManipulatedFrame::domElement(const QString& name, QDomDocument& document) const +{ + QDomElement e = Frame::domElement(name, document); + QDomElement mp = document.createElement("ManipulatedParameters"); + mp.setAttribute("rotSens", QString::number(rotationSensitivity())); + mp.setAttribute("transSens", QString::number(translationSensitivity())); + mp.setAttribute("spinSens", QString::number(spinningSensitivity())); + mp.setAttribute("wheelSens", QString::number(wheelSensitivity())); + mp.setAttribute("zoomSens", QString::number(zoomSensitivity())); + e.appendChild(mp); + return e; +} + +/*! Restores the ManipulatedFrame state from a \c QDomElement created by domElement(). + +Fields that are not described in \p element are set to their default values (see +ManipulatedFrame()). + +First calls Frame::initFromDOMElement() and then initializes ManipulatedFrame specific parameters. +Note that constraint() and referenceFrame() are not restored and are left unchanged. + +See Vec::initFromDOMElement() for a complete code example. */ +void ManipulatedFrame::initFromDOMElement(const QDomElement& element) +{ + // Not called since it would set constraint() and referenceFrame() to NULL. + // *this = ManipulatedFrame(); + Frame::initFromDOMElement(element); + + stopSpinning(); + + QDomElement child=element.firstChild().toElement(); + while (!child.isNull()) + { + if (child.tagName() == "ManipulatedParameters") + { + // #CONNECTION# constructor default values and accessor docs + setRotationSensitivity (DomUtils::qrealFromDom(child, "rotSens", 1.0)); + setTranslationSensitivity(DomUtils::qrealFromDom(child, "transSens", 1.0)); + setSpinningSensitivity (DomUtils::qrealFromDom(child, "spinSens", 0.3)); + setWheelSensitivity (DomUtils::qrealFromDom(child, "wheelSens", 1.0)); + setZoomSensitivity (DomUtils::qrealFromDom(child, "zoomSens", 1.0)); + } + child = child.nextSibling().toElement(); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// M o u s e h a n d l i n g // +//////////////////////////////////////////////////////////////////////////////// + +/*! Returns \c true when the ManipulatedFrame is being manipulated with the mouse. + + Can be used to change the display of the manipulated object during manipulation. + + When Camera::frame() of the QGLViewer::camera() isManipulated(), QGLViewer::fastDraw() is used in + place of QGLViewer::draw() for scene rendering. A simplified drawing will then allow for + interactive camera displacements. */ +bool ManipulatedFrame::isManipulated() const +{ + return action_ != QGLViewer::NO_MOUSE_ACTION; +} + +/*! Starts the spinning of the ManipulatedFrame. + +This method starts a timer that will call spin() every \p updateInterval milliseconds. The +ManipulatedFrame isSpinning() until you call stopSpinning(). */ +void ManipulatedFrame::startSpinning(int updateInterval) +{ + isSpinning_ = true; + spinningTimer_.start(updateInterval); +} + +/*! Rotates the ManipulatedFrame by its spinningQuaternion(). Called by a timer when the + ManipulatedFrame isSpinning(). */ +void ManipulatedFrame::spin() +{ + rotate(spinningQuaternion()); +} + +/* spin() and spinUpdate() differ since spin can be used by itself (for instance by + QGLViewer::SCREEN_ROTATE) without a spun emission. Much nicer to use the spinningQuaternion() and + hence spin() for these incremental updates. Nothing special to be done for continuous spinning + with this design. */ +void ManipulatedFrame::spinUpdate() +{ + spin(); + Q_EMIT spun(); +} + +#ifndef DOXYGEN +/*! Protected internal method used to handle mouse events. */ +void ManipulatedFrame::startAction(int ma, bool withConstraint) +{ + action_ = (QGLViewer::MouseAction)(ma); + + // #CONNECTION# manipulatedFrame::wheelEvent, manipulatedCameraFrame::wheelEvent and mouseReleaseEvent() + // restore previous constraint + if (withConstraint) + previousConstraint_ = NULL; + else + { + previousConstraint_ = constraint(); + setConstraint(NULL); + } + + switch (action_) + { + case QGLViewer::ROTATE: + case QGLViewer::SCREEN_ROTATE: + mouseSpeed_ = 0.0; + stopSpinning(); + break; + + case QGLViewer::SCREEN_TRANSLATE: + dirIsFixed_ = false; + break; + + default: + break; + } +} + +/*! Updates mouse speed, measured in pixels/milliseconds. Should be called by any method which wants to +use mouse speed. Currently used to trigger spinning in mouseReleaseEvent(). */ +void ManipulatedFrame::computeMouseSpeed(const QMouseEvent* const e) +{ + const QPoint delta = (e->pos() - prevPos_); + const qreal dist = sqrt(qreal(delta.x()*delta.x() + delta.y()*delta.y())); + delay_ = last_move_time.restart(); + if (delay_ == 0) + // Less than a millisecond: assume delay = 1ms + mouseSpeed_ = dist; + else + mouseSpeed_ = dist/delay_; +} + +/*! Return 1 if mouse motion was started horizontally and -1 if it was more vertical. Returns 0 if +this could not be determined yet (perfect diagonal motion, rare). */ +int ManipulatedFrame::mouseOriginalDirection(const QMouseEvent* const e) +{ + static bool horiz = true; // Two simultaneous manipulatedFrame require two mice ! + + if (!dirIsFixed_) + { + const QPoint delta = e->pos() - pressPos_; + dirIsFixed_ = abs(delta.x()) != abs(delta.y()); + horiz = abs(delta.x()) > abs(delta.y()); + } + + if (dirIsFixed_) + if (horiz) + return 1; + else + return -1; + else + return 0; +} + +qreal ManipulatedFrame::deltaWithPrevPos(QMouseEvent* const event, Camera* const camera) const { + qreal dx = qreal(event->x() - prevPos_.x()) / camera->screenWidth(); + qreal dy = qreal(event->y() - prevPos_.y()) / camera->screenHeight(); + + qreal value = fabs(dx) > fabs(dy) ? dx : dy; + return value * zoomSensitivity(); +} + +qreal ManipulatedFrame::wheelDelta(const QWheelEvent* event) const { + static const qreal WHEEL_SENSITIVITY_COEF = 8E-4; + return event->delta() * wheelSensitivity() * WHEEL_SENSITIVITY_COEF; +} + +void ManipulatedFrame::zoom(qreal delta, const Camera * const camera) { + Vec trans(0.0, 0.0, (camera->position() - position()).norm() * delta); + + trans = camera->frame()->orientation().rotate(trans); + if (referenceFrame()) + trans = referenceFrame()->transformOf(trans); + translate(trans); +} + +#endif // DOXYGEN + +/*! Initiates the ManipulatedFrame mouse manipulation. + +Overloading of MouseGrabber::mousePressEvent(). See also mouseMoveEvent() and mouseReleaseEvent(). + +The mouse behavior depends on which button is pressed. See the QGLViewer +mouse page for details. */ +void ManipulatedFrame::mousePressEvent(QMouseEvent* const event, Camera* const camera) +{ + Q_UNUSED(camera); + + if (grabsMouse()) + keepsGrabbingMouse_ = true; + + // #CONNECTION setMouseBinding + // action_ should no longer possibly be NO_MOUSE_ACTION since this value is not inserted in mouseBinding_ + //if (action_ == QGLViewer::NO_MOUSE_ACTION) + //event->ignore(); + + prevPos_ = pressPos_ = event->pos(); +} + +/*! Modifies the ManipulatedFrame according to the mouse motion. + +Actual behavior depends on mouse bindings. See the QGLViewer::MouseAction enum and the QGLViewer mouse page for details. + +The \p camera is used to fit the mouse motion with the display parameters (see +Camera::screenWidth(), Camera::screenHeight(), Camera::fieldOfView()). + +Emits the manipulated() signal. */ +void ManipulatedFrame::mouseMoveEvent(QMouseEvent* const event, Camera* const camera) +{ + switch (action_) + { + case QGLViewer::TRANSLATE: + { + const QPoint delta = event->pos() - prevPos_; + Vec trans(delta.x(), -delta.y(), 0.0); + // Scale to fit the screen mouse displacement + switch (camera->type()) + { + case Camera::PERSPECTIVE : + trans *= 2.0 * tan(camera->fieldOfView()/2.0) * fabs((camera->frame()->coordinatesOf(position())).z) / camera->screenHeight(); + break; + case Camera::ORTHOGRAPHIC : + { + GLdouble w,h; + camera->getOrthoWidthHeight(w, h); + trans[0] *= 2.0 * w / camera->screenWidth(); + trans[1] *= 2.0 * h / camera->screenHeight(); + break; + } + } + // Transform to world coordinate system. + trans = camera->frame()->orientation().rotate(translationSensitivity()*trans); + // And then down to frame + if (referenceFrame()) trans = referenceFrame()->transformOf(trans); + translate(trans); + break; + } + + case QGLViewer::ZOOM: + { + zoom(deltaWithPrevPos(event, camera), camera); + break; + } + + case QGLViewer::SCREEN_ROTATE: + { + Vec trans = camera->projectedCoordinatesOf(position()); + + const qreal prev_angle = atan2(prevPos_.y()-trans[1], prevPos_.x()-trans[0]); + const qreal angle = atan2(event->y()-trans[1], event->x()-trans[0]); + + const Vec axis = transformOf(camera->frame()->inverseTransformOf(Vec(0.0, 0.0, -1.0))); + Quaternion rot(axis, angle-prev_angle); + //#CONNECTION# These two methods should go together (spinning detection and activation) + computeMouseSpeed(event); + setSpinningQuaternion(rot); + spin(); + break; + } + + case QGLViewer::SCREEN_TRANSLATE: + { + Vec trans; + int dir = mouseOriginalDirection(event); + if (dir == 1) + trans.setValue(event->x() - prevPos_.x(), 0.0, 0.0); + else if (dir == -1) + trans.setValue(0.0, prevPos_.y() - event->y(), 0.0); + + switch (camera->type()) + { + case Camera::PERSPECTIVE : + trans *= 2.0 * tan(camera->fieldOfView()/2.0) * fabs((camera->frame()->coordinatesOf(position())).z) / camera->screenHeight(); + break; + case Camera::ORTHOGRAPHIC : + { + GLdouble w,h; + camera->getOrthoWidthHeight(w, h); + trans[0] *= 2.0 * w / camera->screenWidth(); + trans[1] *= 2.0 * h / camera->screenHeight(); + break; + } + } + // Transform to world coordinate system. + trans = camera->frame()->orientation().rotate(translationSensitivity()*trans); + // And then down to frame + if (referenceFrame()) + trans = referenceFrame()->transformOf(trans); + + translate(trans); + break; + } + + case QGLViewer::ROTATE: + { + Vec trans = camera->projectedCoordinatesOf(position()); + Quaternion rot = deformedBallQuaternion(event->x(), event->y(), trans[0], trans[1], camera); + trans = Vec(-rot[0], -rot[1], -rot[2]); + trans = camera->frame()->orientation().rotate(trans); + trans = transformOf(trans); + rot[0] = trans[0]; + rot[1] = trans[1]; + rot[2] = trans[2]; + //#CONNECTION# These two methods should go together (spinning detection and activation) + computeMouseSpeed(event); + setSpinningQuaternion(rot); + spin(); + break; + } + + case QGLViewer::MOVE_FORWARD: + case QGLViewer::MOVE_BACKWARD: + case QGLViewer::LOOK_AROUND: + case QGLViewer::ROLL: + case QGLViewer::DRIVE: + case QGLViewer::ZOOM_ON_REGION: + // These MouseAction values make no sense for a manipulatedFrame + break; + + case QGLViewer::NO_MOUSE_ACTION: + // Possible when the ManipulatedFrame is a MouseGrabber. This method is then called without startAction + // because of mouseTracking. + break; + } + + if (action_ != QGLViewer::NO_MOUSE_ACTION) + { + prevPos_ = event->pos(); + Q_EMIT manipulated(); + } +} + +/*! Stops the ManipulatedFrame mouse manipulation. + +Overloading of MouseGrabber::mouseReleaseEvent(). + +If the action was a QGLViewer::ROTATE QGLViewer::MouseAction, a continuous spinning is possible if +the speed of the mouse cursor is larger than spinningSensitivity() when the button is released. +Press the rotate button again to stop spinning. See startSpinning() and isSpinning(). */ +void ManipulatedFrame::mouseReleaseEvent(QMouseEvent* const event, Camera* const camera) +{ + Q_UNUSED(event); + Q_UNUSED(camera); + + keepsGrabbingMouse_ = false; + + if (previousConstraint_) + setConstraint(previousConstraint_); + + if (((action_ == QGLViewer::ROTATE) || (action_ == QGLViewer::SCREEN_ROTATE)) && (mouseSpeed_ >= spinningSensitivity())) + startSpinning(delay_); + + action_ = QGLViewer::NO_MOUSE_ACTION; +} + +/*! Overloading of MouseGrabber::mouseDoubleClickEvent(). + +Left button double click aligns the ManipulatedFrame with the \p camera axis (see alignWithFrame() + and QGLViewer::ALIGN_FRAME). Right button projects the ManipulatedFrame on the \p camera view + direction. */ +void ManipulatedFrame::mouseDoubleClickEvent(QMouseEvent* const event, Camera* const camera) +{ + if (event->modifiers() == Qt::NoModifier) + switch (event->button()) + { + case Qt::LeftButton: alignWithFrame(camera->frame()); break; + case Qt::RightButton: projectOnLine(camera->position(), camera->viewDirection()); break; + default: break; + } +} + +/*! Overloading of MouseGrabber::wheelEvent(). + +Using the wheel is equivalent to a QGLViewer::ZOOM QGLViewer::MouseAction. See + QGLViewer::setWheelBinding(), setWheelSensitivity(). */ +void ManipulatedFrame::wheelEvent(QWheelEvent* const event, Camera* const camera) +{ + //#CONNECTION# QGLViewer::setWheelBinding + if (action_ == QGLViewer::ZOOM) + { + zoom(wheelDelta(event), camera); + Q_EMIT manipulated(); + } + + // #CONNECTION# startAction should always be called before + if (previousConstraint_) + setConstraint(previousConstraint_); + + action_ = QGLViewer::NO_MOUSE_ACTION; +} + + +//////////////////////////////////////////////////////////////////////////////// + +/*! Returns "pseudo-distance" from (x,y) to ball of radius size. +\arg for a point inside the ball, it is proportional to the euclidean distance to the ball +\arg for a point outside the ball, it is proportional to the inverse of this distance (tends to +zero) on the ball, the function is continuous. */ +static qreal projectOnBall(qreal x, qreal y) +{ + // If you change the size value, change angle computation in deformedBallQuaternion(). + const qreal size = 1.0; + const qreal size2 = size*size; + const qreal size_limit = size2*0.5; + + const qreal d = x*x + y*y; + return d < size_limit ? sqrt(size2 - d) : size_limit/sqrt(d); +} + +#ifndef DOXYGEN +/*! Returns a quaternion computed according to the mouse motion. Mouse positions are projected on a +deformed ball, centered on (\p cx,\p cy). */ +Quaternion ManipulatedFrame::deformedBallQuaternion(int x, int y, qreal cx, qreal cy, const Camera* const camera) +{ + // Points on the deformed ball + qreal px = rotationSensitivity() * (prevPos_.x() - cx) / camera->screenWidth(); + qreal py = rotationSensitivity() * (cy - prevPos_.y()) / camera->screenHeight(); + qreal dx = rotationSensitivity() * (x - cx) / camera->screenWidth(); + qreal dy = rotationSensitivity() * (cy - y) / camera->screenHeight(); + + const Vec p1(px, py, projectOnBall(px, py)); + const Vec p2(dx, dy, projectOnBall(dx, dy)); + // Approximation of rotation angle + // Should be divided by the projectOnBall size, but it is 1.0 + const Vec axis = cross(p2,p1); + const qreal angle = 5.0 * asin(sqrt(axis.squaredNorm() / p1.squaredNorm() / p2.squaredNorm())); + return Quaternion(axis, angle); +} +#endif // DOXYGEN diff --git a/QGLViewer/manipulatedFrame.h b/QGLViewer/manipulatedFrame.h new file mode 100644 index 0000000..b27fb25 --- /dev/null +++ b/QGLViewer/manipulatedFrame.h @@ -0,0 +1,335 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#ifndef QGLVIEWER_MANIPULATED_FRAME_H +#define QGLVIEWER_MANIPULATED_FRAME_H + +#include "frame.h" +#include "mouseGrabber.h" +#include "qglviewer.h" + +#include +#include +#include + +namespace qglviewer { +/*! \brief A ManipulatedFrame is a Frame that can be rotated and translated using the mouse. + \class ManipulatedFrame manipulatedFrame.h QGLViewer/manipulatedFrame.h + + It converts the mouse motion into a translation and an orientation updates. A ManipulatedFrame is + used to move an object in the scene. Combined with object selection, its MouseGrabber properties + and a dynamic update of the scene, the ManipulatedFrame introduces a great reactivity in your + applications. + + A ManipulatedFrame is attached to a QGLViewer using QGLViewer::setManipulatedFrame(): + \code + init() { setManipulatedFrame( new ManipulatedFrame() ); } + + draw() + { + glPushMatrix(); + glMultMatrixd(manipulatedFrame()->matrix()); + // draw the manipulated object here + glPopMatrix(); + } + \endcode + See the manipulatedFrame example for a complete + application. + + Mouse events are normally sent to the QGLViewer::camera(). You have to press the QGLViewer::FRAME + state key (default is \c Control) to move the QGLViewer::manipulatedFrame() instead. See the mouse page for a description of mouse button bindings. + +

Inherited functionalities

+ + A ManipulatedFrame is an overloaded instance of a Frame. The powerful coordinate system + transformation functions (Frame::coordinatesOf(), Frame::transformOf(), ...) can hence be applied + to a ManipulatedFrame. + + A ManipulatedFrame is also a MouseGrabber. If the mouse cursor gets within a distance of 10 pixels + from the projected position of the ManipulatedFrame, the ManipulatedFrame becomes the new + QGLViewer::mouseGrabber(). It can then be manipulated directly, without any specific state key, + object selection or GUI intervention. This is very convenient to directly move some objects in the + scene (typically a light). See the mouseGrabber + example as an illustration. Note that QWidget::setMouseTracking() needs to be enabled in order + to use this feature (see the MouseGrabber documentation). + +

Advanced functionalities

+ + A QGLViewer can handle at most one ManipulatedFrame at a time. If you want to move several objects + in the scene, you simply have to keep a list of the different ManipulatedFrames, and to activate + the right one (using QGLViewer::setManipulatedFrame()) when needed. This can for instance be done + according to an object selection: see the luxo example for an + illustration. + + When the ManipulatedFrame is being manipulated using the mouse (mouse pressed and not yet + released), isManipulated() returns \c true. This might be used to trigger a specific action or + display (as is done with QGLViewer::fastDraw()). + + The ManipulatedFrame also emits a manipulated() signal each time its state is modified by the + mouse. This signal is automatically connected to the QGLViewer::update() slot when the + ManipulatedFrame is attached to a viewer using QGLViewer::setManipulatedFrame(). + + You can make the ManipulatedFrame spin() if you release the rotation mouse button while moving the + mouse fast enough (see spinningSensitivity()). See also translationSensitivity() and + rotationSensitivity() for sensitivity tuning. \nosubgrouping */ +class QGLVIEWER_EXPORT ManipulatedFrame : public Frame, public MouseGrabber +{ +#ifndef DOXYGEN + friend class Camera; + friend class ::QGLViewer; +#endif + + Q_OBJECT + +public: + ManipulatedFrame(); + /*! Virtual destructor. Empty. */ + virtual ~ManipulatedFrame() {} + + ManipulatedFrame(const ManipulatedFrame& mf); + ManipulatedFrame& operator=(const ManipulatedFrame& mf); + +Q_SIGNALS: + /*! This signal is emitted when ever the ManipulatedFrame is manipulated (i.e. rotated or + translated) using the mouse. Connect this signal to any object that should be notified. + + Note that this signal is automatically connected to the QGLViewer::update() slot, when the + ManipulatedFrame is attached to a viewer using QGLViewer::setManipulatedFrame(), which is + probably all you need. + + Use the QGLViewer::QGLViewerPool() if you need to connect this signal to all the viewers. + + See also the spun(), modified(), interpolated() and KeyFrameInterpolator::interpolated() + signals' documentations. */ + void manipulated(); + + /*! This signal is emitted when the ManipulatedFrame isSpinning(). + + Note that for the QGLViewer::manipulatedFrame(), this signal is automatically connected to the + QGLViewer::update() slot. + + Connect this signal to any object that should be notified. Use the QGLViewer::QGLViewerPool() if + you need to connect this signal to all the viewers. + + See also the manipulated(), modified(), interpolated() and KeyFrameInterpolator::interpolated() + signals' documentations. */ + void spun(); + + /*! @name Manipulation sensitivity */ + //@{ +public Q_SLOTS: + /*! Defines the rotationSensitivity(). */ + void setRotationSensitivity(qreal sensitivity) { rotationSensitivity_ = sensitivity; } + /*! Defines the translationSensitivity(). */ + void setTranslationSensitivity(qreal sensitivity) { translationSensitivity_ = sensitivity; } + /*! Defines the spinningSensitivity(), in pixels per milliseconds. */ + void setSpinningSensitivity(qreal sensitivity) { spinningSensitivity_ = sensitivity; } + /*! Defines the wheelSensitivity(). */ + void setWheelSensitivity(qreal sensitivity) { wheelSensitivity_ = sensitivity; } + /*! Defines the zoomSensitivity(). */ + void setZoomSensitivity(qreal sensitivity) { zoomSensitivity_ = sensitivity; } + +public: + /*! Returns the influence of a mouse displacement on the ManipulatedFrame rotation. + + Default value is 1.0. With an identical mouse displacement, a higher value will generate a + larger rotation (and inversely for lower values). A 0.0 value will forbid ManipulatedFrame mouse + rotation (see also constraint()). + + See also setRotationSensitivity(), translationSensitivity(), spinningSensitivity() and + wheelSensitivity(). */ + qreal rotationSensitivity() const { return rotationSensitivity_; } + /*! Returns the influence of a mouse displacement on the ManipulatedFrame translation. + + Default value is 1.0. You should not have to modify this value, since with 1.0 the + ManipulatedFrame precisely stays under the mouse cursor. + + With an identical mouse displacement, a higher value will generate a larger translation (and + inversely for lower values). A 0.0 value will forbid ManipulatedFrame mouse translation (see + also constraint()). + + \note When the ManipulatedFrame is used to move a \e Camera (see the ManipulatedCameraFrame + class documentation), after zooming on a small region of your scene, the camera may translate + too fast. For a camera, it is the Camera::pivotPoint() that exactly matches the mouse + displacement. Hence, instead of changing the translationSensitivity(), solve the problem by + (temporarily) setting the Camera::pivotPoint() to a point on the zoomed region (see the + QGLViewer::RAP_FROM_PIXEL mouse binding in the mouse page). + + See also setTranslationSensitivity(), rotationSensitivity(), spinningSensitivity() and + wheelSensitivity(). */ + qreal translationSensitivity() const { return translationSensitivity_; } + /*! Returns the minimum mouse speed required (at button release) to make the ManipulatedFrame + spin(). + + See spin(), spinningQuaternion() and startSpinning() for details. + + Mouse speed is expressed in pixels per milliseconds. Default value is 0.3 (300 pixels per + second). Use setSpinningSensitivity() to tune this value. A higher value will make spinning more + difficult (a value of 100.0 forbids spinning in practice). + + See also setSpinningSensitivity(), translationSensitivity(), rotationSensitivity() and + wheelSensitivity(). */ + qreal spinningSensitivity() const { return spinningSensitivity_; } + + /*! Returns the zoom sensitivity. + + Default value is 1.0. A higher value will make the zoom faster. + Use a negative value to invert the zoom in and out directions. + + See also setZoomSensitivity(), translationSensitivity(), rotationSensitivity() wheelSensitivity() + and spinningSensitivity(). */ + qreal zoomSensitivity() const { return zoomSensitivity_; } + /*! Returns the mouse wheel sensitivity. + + Default value is 1.0. A higher value will make the wheel action more efficient (usually meaning + a faster zoom). Use a negative value to invert the zoom in and out directions. + + See also setWheelSensitivity(), translationSensitivity(), rotationSensitivity() zoomSensitivity() + and spinningSensitivity(). */ + qreal wheelSensitivity() const { return wheelSensitivity_; } + //@} + + + /*! @name Spinning */ + //@{ +public: + /*! Returns \c true when the ManipulatedFrame is spinning. + + During spinning, spin() rotates the ManipulatedFrame by its spinningQuaternion() at a frequency + defined when the ManipulatedFrame startSpinning(). + + Use startSpinning() and stopSpinning() to change this state. Default value is \c false. */ + bool isSpinning() const { return isSpinning_; } + /*! Returns the incremental rotation that is applied by spin() to the ManipulatedFrame + orientation when it isSpinning(). + + Default value is a null rotation (identity Quaternion). Use setSpinningQuaternion() to change + this value. + + The spinningQuaternion() axis is defined in the ManipulatedFrame coordinate system. You can use + Frame::transformOfFrom() to convert this axis from an other Frame coordinate system. */ + Quaternion spinningQuaternion() const { return spinningQuaternion_; } +public Q_SLOTS: + /*! Defines the spinningQuaternion(). Its axis is defined in the ManipulatedFrame coordinate + system. */ + void setSpinningQuaternion(const Quaternion& spinningQuaternion) { spinningQuaternion_ = spinningQuaternion; } + virtual void startSpinning(int updateInterval); + /*! Stops the spinning motion started using startSpinning(). isSpinning() will return \c false + after this call. */ + virtual void stopSpinning() { spinningTimer_.stop(); isSpinning_ = false; } +protected Q_SLOTS: + virtual void spin(); +private Q_SLOTS: + void spinUpdate(); + //@} + + /*! @name Mouse event handlers */ + //@{ +protected: + virtual void mousePressEvent (QMouseEvent* const event, Camera* const camera); + virtual void mouseMoveEvent (QMouseEvent* const event, Camera* const camera); + virtual void mouseReleaseEvent (QMouseEvent* const event, Camera* const camera); + virtual void mouseDoubleClickEvent(QMouseEvent* const event, Camera* const camera); + virtual void wheelEvent (QWheelEvent* const event, Camera* const camera); + //@} + +public: + /*! @name Current state */ + //@{ + bool isManipulated() const; + /*! Returns the \c MouseAction currently applied to this ManipulatedFrame. + + Will return QGLViewer::NO_MOUSE_ACTION unless a mouse button is being pressed + and has been bound to this QGLViewer::MouseHandler. + + The binding between mouse buttons and key modifiers and MouseAction is set using + QGLViewer::setMouseBinding(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton buttons, MouseHandler handler, MouseAction action, bool withConstraint). + */ + QGLViewer::MouseAction currentMouseAction() const { return action_; } + //@} + + /*! @name MouseGrabber implementation */ + //@{ +public: + virtual void checkIfGrabsMouse(int x, int y, const Camera* const camera); + //@} + + /*! @name XML representation */ + //@{ +public: + virtual QDomElement domElement(const QString& name, QDomDocument& document) const; +public Q_SLOTS: + virtual void initFromDOMElement(const QDomElement& element); + //@} + +#ifndef DOXYGEN +protected: + Quaternion deformedBallQuaternion(int x, int y, qreal cx, qreal cy, const Camera* const camera); + + QGLViewer::MouseAction action_; + Constraint* previousConstraint_; // When manipulation is without Contraint. + + virtual void startAction(int ma, bool withConstraint=true); // int is really a QGLViewer::MouseAction + void computeMouseSpeed(const QMouseEvent* const e); + int mouseOriginalDirection(const QMouseEvent* const e); + + /*! Returns a screen scaled delta from event's position to prevPos_, along the + X or Y direction, whichever has the largest magnitude. */ + qreal deltaWithPrevPos(QMouseEvent* const event, Camera* const camera) const; + /*! Returns a normalized wheel delta, proportionnal to wheelSensitivity(). */ + qreal wheelDelta(const QWheelEvent* event) const; + + // Previous mouse position (used for incremental updates) and mouse press position. + QPoint prevPos_, pressPos_; + +private: + void zoom(qreal delta, const Camera * const camera); + +#endif // DOXYGEN + +private: + // Sensitivity + qreal rotationSensitivity_; + qreal translationSensitivity_; + qreal spinningSensitivity_; + qreal wheelSensitivity_; + qreal zoomSensitivity_; + + // Mouse speed and spinning + QTime last_move_time; + qreal mouseSpeed_; + int delay_; + bool isSpinning_; + QTimer spinningTimer_; + Quaternion spinningQuaternion_; + + // Whether the SCREEN_TRANS direction (horizontal or vertical) is fixed or not. + bool dirIsFixed_; + + // MouseGrabber + bool keepsGrabbingMouse_; +}; + +} // namespace qglviewer + +#endif // QGLVIEWER_MANIPULATED_FRAME_H diff --git a/QGLViewer/mouseGrabber.cpp b/QGLViewer/mouseGrabber.cpp new file mode 100644 index 0000000..38cfdd0 --- /dev/null +++ b/QGLViewer/mouseGrabber.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "mouseGrabber.h" + +using namespace qglviewer; + +// Static private variable +QList MouseGrabber::MouseGrabberPool_; + +/*! Default constructor. + +Adds the created MouseGrabber in the MouseGrabberPool(). grabsMouse() is set to \c false. */ +MouseGrabber::MouseGrabber() + : grabsMouse_(false) +{ + addInMouseGrabberPool(); +} + +/*! Adds the MouseGrabber in the MouseGrabberPool(). + +All created MouseGrabber are automatically added in the MouseGrabberPool() by the constructor. +Trying to add a MouseGrabber that already isInMouseGrabberPool() has no effect. + +Use removeFromMouseGrabberPool() to remove the MouseGrabber from the list, so that it is no longer +tested with checkIfGrabsMouse() by the QGLViewer, and hence can no longer grab mouse focus. Use +isInMouseGrabberPool() to know the current state of the MouseGrabber. */ +void MouseGrabber::addInMouseGrabberPool() +{ + if (!isInMouseGrabberPool()) + MouseGrabber::MouseGrabberPool_.append(this); +} + +/*! Removes the MouseGrabber from the MouseGrabberPool(). + +See addInMouseGrabberPool() for details. Removing a MouseGrabber that is not in MouseGrabberPool() +has no effect. */ +void MouseGrabber::removeFromMouseGrabberPool() +{ + if (isInMouseGrabberPool()) + MouseGrabber::MouseGrabberPool_.removeAll(const_cast(this)); +} + +/*! Clears the MouseGrabberPool(). + + Use this method only if it is faster to clear the MouseGrabberPool() and then to add back a few + MouseGrabbers than to remove each one independently. Use QGLViewer::setMouseTracking(false) instead + if you want to disable mouse grabbing. + + When \p autoDelete is \c true, the MouseGrabbers of the MouseGrabberPool() are actually deleted + (use this only if you're sure of what you do). */ +void MouseGrabber::clearMouseGrabberPool(bool autoDelete) +{ + if (autoDelete) + qDeleteAll(MouseGrabber::MouseGrabberPool_); + MouseGrabber::MouseGrabberPool_.clear(); +} diff --git a/QGLViewer/mouseGrabber.h b/QGLViewer/mouseGrabber.h new file mode 100644 index 0000000..d704d1c --- /dev/null +++ b/QGLViewer/mouseGrabber.h @@ -0,0 +1,264 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#ifndef QGLVIEWER_MOUSE_GRABBER_H +#define QGLVIEWER_MOUSE_GRABBER_H + +#include "config.h" + +#include + +class QGLViewer; + +namespace qglviewer { +class Camera; + +/*! \brief Abstract class for objects that grab mouse focus in a QGLViewer. + \class MouseGrabber mouseGrabber.h QGLViewer/mouseGrabber.h + + MouseGrabber are objects which react to the mouse cursor, usually when it hovers over them. This + abstract class only provides an interface for all these objects: their actual behavior has to be + defined in a derived class. + +

How does it work ?

+ + All the created MouseGrabber are grouped in a MouseGrabberPool(). The QGLViewers parse this pool, + calling all the MouseGrabbers' checkIfGrabsMouse() methods that setGrabsMouse() if desired. + + When a MouseGrabber grabsMouse(), it becomes the QGLViewer::mouseGrabber(). All the mouse events + (mousePressEvent(), mouseReleaseEvent(), mouseMoveEvent(), mouseDoubleClickEvent() and + wheelEvent()) are then transmitted to the QGLViewer::mouseGrabber() instead of being normally + processed. This continues while grabsMouse() (updated using checkIfGrabsMouse()) returns \c true. + + If you want to (temporarily) disable a specific MouseGrabbers, you can remove it from this pool + using removeFromMouseGrabberPool(). You can also disable a MouseGrabber in a specific QGLViewer + using QGLViewer::setMouseGrabberIsEnabled(). + +

Implementation details

+ + In order to make MouseGrabber react to mouse events, mouse tracking has to be activated in the + QGLViewer which wants to use MouseGrabbers: + \code + init() { setMouseTracking(true); } + \endcode + Call \c QGLWidget::hasMouseTracking() to get the current state of this flag. [TODO Update with QOpenGLWidget] + + The \p camera parameter of the different mouse event methods is a pointer to the + QGLViewer::camera() of the QGLViewer that uses the MouseGrabber. It can be used to compute 2D to + 3D coordinates conversion using Camera::projectedCoordinatesOf() and + Camera::unprojectedCoordinatesOf(). + + Very complex behaviors can be implemented using this framework: auto-selected objects (no need to + press a key to use them), automatic drop-down menus, 3D GUI, spinners using the wheelEvent(), and + whatever your imagination creates. See the mouseGrabber + example for an illustration. + + Note that ManipulatedFrame are MouseGrabber: see the keyFrame + example for an illustration. Every created ManipulatedFrame is hence present in the + MouseGrabberPool() (note however that ManipulatedCameraFrame are not inserted). + +

Example

+ + Here is for instance a draft version of a MovableObject class. Instances of these class can freely + be moved on screen using the mouse, as movable post-it-like notes: + \code + class MovableObject : public MouseGrabber + { + public: + MovableObject() : pos(0,0), moved(false) {} + + void checkIfGrabsMouse(int x, int y, const qglviewer::Camera* const) + { + // MovableObject is active in a region of 5 pixels around its pos. + // May depend on the actual shape of the object. Customize as desired. + // Once clicked (moved = true), it keeps grabbing mouse until button is released. + setGrabsMouse( moved || ((pos-QPoint(x,y)).manhattanLength() < 5) ); + } + + void mousePressEvent( QMouseEvent* const e, Camera* const) { prevPos = e->pos(); moved = true; } + + void mouseMoveEvent(QMouseEvent* const e, const Camera* const) + { + if (moved) + { + // Add position delta to current pos + pos += e->pos() - prevPos; + prevPos = e->pos(); + } + } + + void mouseReleaseEvent(QMouseEvent* const, Camera* const) { moved = false; } + + void draw() + { + // The object is drawn centered on its pos, with different possible aspects: + if (grabsMouse()) + if (moved) + // Object being moved, maybe a transparent display + else + // Object ready to be moved, maybe a highlighted visual feedback + else + // Normal display + } + + private: + QPoint pos, prevPos; + bool moved; + }; + \endcode + Note that the different event callback methods are called only once the MouseGrabber grabsMouse(). + \nosubgrouping */ +class QGLVIEWER_EXPORT MouseGrabber +{ +#ifndef DOXYGEN + friend class ::QGLViewer; +#endif + +public: + MouseGrabber(); + /*! Virtual destructor. Removes the MouseGrabber from the MouseGrabberPool(). */ + virtual ~MouseGrabber() { MouseGrabber::MouseGrabberPool_.removeAll(this); } + + /*! @name Mouse grabbing detection */ + //@{ +public: + /*! Pure virtual method, called by the QGLViewers before they test if the MouseGrabber + grabsMouse(). Should setGrabsMouse() according to the mouse position. + + This is the core method of the MouseGrabber. It has to be overloaded in your derived class. + Its goal is to update the grabsMouse() flag according to the mouse and MouseGrabber current + positions, using setGrabsMouse(). + + grabsMouse() is usually set to \c true when the mouse cursor is close enough to the MouseGrabber + position. It should also be set to \c false when the mouse cursor leaves this region in order to + release the mouse focus. + + \p x and \p y are the mouse cursor coordinates (Qt coordinate system: (0,0) corresponds to the upper + left corner). + + A typical implementation will look like: + \code + // (posX,posY) is the position of the MouseGrabber on screen. + // Here, distance to mouse must be less than 10 pixels to activate the MouseGrabber. + setGrabsMouse( sqrt((x-posX)*(x-posX) + (y-posY)*(y-posY)) < 10); + \endcode + + If the MouseGrabber position is defined in 3D, use the \p camera parameter, corresponding to + the calling QGLViewer Camera. Project on screen and then compare the projected coordinates: + \code + Vec proj = camera->projectedCoordinatesOf(myMouseGrabber->frame()->position()); + setGrabsMouse((fabs(x-proj.x) < 5) && (fabs(y-proj.y) < 2)); // Rectangular region + \endcode + + See examples in the detailed description section and in the mouseGrabber example. */ + virtual void checkIfGrabsMouse(int x, int y, const Camera* const camera) = 0; + + /*! Returns \c true when the MouseGrabber grabs the QGLViewer's mouse events. + + This flag is set with setGrabsMouse() by the checkIfGrabsMouse() method. */ + bool grabsMouse() const { return grabsMouse_; } + +protected: + /*! Sets the grabsMouse() flag. Normally used by checkIfGrabsMouse(). */ + void setGrabsMouse(bool grabs) { grabsMouse_ = grabs; } + //@} + + + /*! @name MouseGrabber pool */ + //@{ +public: + /*! Returns a list containing pointers to all the active MouseGrabbers. + + Used by the QGLViewer to parse all the MouseGrabbers and to check if any of them grabsMouse() + using checkIfGrabsMouse(). + + You should not have to directly use this list. Use removeFromMouseGrabberPool() and + addInMouseGrabberPool() to modify this list. + + \attention This method returns a \c QPtrList with Qt 3 and a \c QList with Qt 2. */ + static const QList& MouseGrabberPool() { return MouseGrabber::MouseGrabberPool_; } + + /*! Returns \c true if the MouseGrabber is currently in the MouseGrabberPool() list. + + Default value is \c true. When set to \c false using removeFromMouseGrabberPool(), the + QGLViewers no longer checkIfGrabsMouse() on this MouseGrabber. Use addInMouseGrabberPool() to + insert it back. */ + bool isInMouseGrabberPool() const { return MouseGrabber::MouseGrabberPool_.contains(const_cast(this)); } + void addInMouseGrabberPool(); + void removeFromMouseGrabberPool(); + void clearMouseGrabberPool(bool autoDelete=false); + //@} + + + /*! @name Mouse event handlers */ + //@{ +protected: + /*! Callback method called when the MouseGrabber grabsMouse() and a mouse button is pressed. + + + The MouseGrabber will typically start an action or change its state when a mouse button is + pressed. mouseMoveEvent() (called at each mouse displacement) will then update the MouseGrabber + accordingly and mouseReleaseEvent() (called when the mouse button is released) will terminate + this action. + + Use the \p event QMouseEvent::state() and QMouseEvent::button() to test the keyboard + and button state and possibly change the MouseGrabber behavior accordingly. + + See the detailed description section and the mouseGrabber example for examples. + + See the \c QGLWidget::mousePressEvent() and the \c QMouseEvent documentations for details. [TODO Update with QOpenGLWidget] */ + virtual void mousePressEvent(QMouseEvent* const event, Camera* const camera) { Q_UNUSED(event); Q_UNUSED(camera); } + /*! Callback method called when the MouseGrabber grabsMouse() and a mouse button is double clicked. + + See the \c QGLWidget::mouseDoubleClickEvent() and the \c QMouseEvent documentations for details. [TODO Update with QOpenGLWidget] */ + virtual void mouseDoubleClickEvent(QMouseEvent* const event, Camera* const camera) { Q_UNUSED(event); Q_UNUSED(camera); } + /*! Mouse release event callback method. See mousePressEvent(). */ + virtual void mouseReleaseEvent(QMouseEvent* const event, Camera* const camera) { Q_UNUSED(event); Q_UNUSED(camera); } + /*! Callback method called when the MouseGrabber grabsMouse() and the mouse is moved while a + button is pressed. + + This method will typically update the state of the MouseGrabber from the mouse displacement. See + the mousePressEvent() documentation for details. */ + virtual void mouseMoveEvent(QMouseEvent* const event, Camera* const camera) { Q_UNUSED(event); Q_UNUSED(camera); } + /*! Callback method called when the MouseGrabber grabsMouse() and the mouse wheel is used. + + See the \c QGLWidget::wheelEvent() and the \c QWheelEvent documentations for details. [TODO Update with QOpenGLWidget] */ + virtual void wheelEvent(QWheelEvent* const event, Camera* const camera) { Q_UNUSED(event); Q_UNUSED(camera); } + //@} + +private: + // Copy constructor and opertor= are declared private and undefined + // Prevents everyone from trying to use them + MouseGrabber(const MouseGrabber&); + MouseGrabber& operator=(const MouseGrabber&); + + bool grabsMouse_; + + // Q G L V i e w e r p o o l + static QList MouseGrabberPool_; +}; + +} // namespace qglviewer + +#endif // QGLVIEWER_MOUSE_GRABBER_H diff --git a/QGLViewer/qglviewer-icon.xpm b/QGLViewer/qglviewer-icon.xpm new file mode 100644 index 0000000..5c7e72a --- /dev/null +++ b/QGLViewer/qglviewer-icon.xpm @@ -0,0 +1,359 @@ +/* XPM */ +static const char * qglviewer_icon[] = { +"100 100 256 2", +" c None", +". c #0A0B27", +"+ c #090B2C", +"@ c #150C12", +"# c #080F34", +"$ c #1A0E1A", +"% c #220D0B", +"& c #260B0B", +"* c #230E1C", +"= c #12113E", +"- c #2C0D10", +"; c #2F0D09", +"> c #310D14", +", c #1A123A", +"' c #261025", +") c #17134B", +"! c #151453", +"~ c #1B1733", +"{ c #14135E", +"] c #281430", +"^ c #0E1867", +"/ c #3B1215", +"( c #32132A", +"_ c #1D1849", +": c #121C58", +"< c #431014", +"[ c #141974", +"} c #3A152E", +"| c #201A5E", +"1 c #1D204C", +"2 c #401431", +"3 c #261960", +"4 c #48171C", +"5 c #411B21", +"6 c #371B45", +"7 c #2A1C6B", +"8 c #0E229B", +"9 c #52171D", +"0 c #441A35", +"a c #1B2582", +"b c #65150F", +"c c #2D2076", +"d c #4D1B3A", +"e c #57182F", +"f c #3E1F54", +"g c #5E1924", +"h c #2E2567", +"i c #002BD1", +"j c #002AD9", +"k c #6D1A13", +"l c #36237A", +"m c #002FCD", +"n c #701A0D", +"o c #462060", +"p c #651C23", +"q c #322581", +"r c #552429", +"s c #581E41", +"t c #671E1B", +"u c #771911", +"v c #6C1B2B", +"w c #562341", +"x c #342A79", +"y c #3C2484", +"z c #062FE6", +"A c #68212B", +"B c #4D236B", +"C c #612045", +"D c #42267B", +"E c #811B10", +"F c #7C1E0E", +"G c #0033F1", +"H c #0032F9", +"I c #3A298E", +"J c #472872", +"K c #72202A", +"L c #4F2774", +"M c #652449", +"N c #442890", +"O c #1F37A8", +"P c #87200E", +"Q c #852014", +"R c #402B98", +"S c #6C244D", +"T c #7D2230", +"U c #8F1F12", +"V c #70254A", +"W c #542A7E", +"X c #212FF2", +"Y c #6E2E28", +"Z c #442E9B", +"` c #772834", +" . c #971E15", +".. c #552B86", +"+. c #693132", +"@. c #702A46", +"#. c #7B2645", +"$. c #862435", +"%. c #79264E", +"&. c #5A2B88", +"*. c #1B3AE9", +"=. c #9A2211", +"-. c #4E2EA0", +";. c #1E3BDE", +">. c #722F3D", +",. c #5E2C91", +"'. c #592F91", +"). c #7A322A", +"!. c #1D42D7", +"~. c #902539", +"{. c #7D2A52", +"]. c #862E26", +"^. c #822853", +"/. c #922B20", +"(. c #4E34AA", +"_. c #A62411", +":. c #5D309A", +"<. c #59387B", +"[. c #5631AB", +"}. c #5533A6", +"|. c #912840", +"1. c #8C2B3F", +"2. c #AE2215", +"3. c #862C57", +"4. c #5D32A8", +"5. c #882C53", +"6. c #5C34A2", +"7. c #6231A2", +"8. c #A32A18", +"9. c #5C398B", +"0. c #4D4480", +"a. c #982A3F", +"b. c #8D2B5A", +"c. c #962C44", +"d. c #902C56", +"e. c #7A3B43", +"f. c #3249C3", +"g. c #B42812", +"h. c #583E9B", +"i. c #BA2516", +"j. c #524297", +"k. c #55448B", +"l. c #5E3BA3", +"m. c #9E2C47", +"n. c #5A4676", +"o. c #873847", +"p. c #404E94", +"q. c #932F59", +"r. c #BE2810", +"s. c #992D5B", +"t. c #5941A4", +"u. c #8A3F36", +"v. c #9D2F58", +"w. c #78415D", +"x. c #A72D4D", +"y. c #A2304B", +"z. c #C72815", +"A. c #C12C13", +"B. c #654298", +"C. c #654780", +"D. c #8F385F", +"E. c #98345D", +"F. c #2F51E3", +"G. c #A93724", +"H. c #A0325B", +"I. c #CA2B10", +"J. c #A93054", +"K. c #A6305D", +"L. c #D42A00", +"M. c #D22914", +"N. c #89415D", +"O. c #6546A5", +"P. c #AB3255", +"Q. c #DC2806", +"R. c #A3355D", +"S. c #AA325A", +"T. c #A13F2F", +"U. c #6A4A8E", +"V. c #A53754", +"W. c #DE2A00", +"X. c #CF300A", +"Y. c #D62C0E", +"Z. c #A8385B", +"`. c #4B54C3", +" + c #964356", +".+ c #E82903", +"++ c #8F4757", +"@+ c #DF2C14", +"#+ c #D93011", +"$+ c #A23D62", +"%+ c #8C4E44", +"&+ c #E32F00", +"*+ c #6E4CA5", +"=+ c #6E4F9B", +"-+ c #A04259", +";+ c #9D4266", +">+ c #D03716", +",+ c #E3300D", +"'+ c #6A51A7", +")+ c #BA3F2F", +"!+ c #EC2E07", +"~+ c #A9405F", +"{+ c #EB2E13", +"]+ c #EF3100", +"^+ c #4A5CDC", +"/+ c #984A68", +"(+ c #B54536", +"_+ c #4563C9", +":+ c #F62E03", +"<+ c #EF310B", +"[+ c #E73411", +"}+ c #AA4B3E", +"|+ c #F62E10", +"1+ c #CE401F", +"2+ c #D33D23", +"3+ c #7455A7", +"4+ c #C54426", +"5+ c #A5496A", +"6+ c #F93106", +"7+ c #A45048", +"8+ c #CD412C", +"9+ c #F2350E", +"0+ c #AA4A64", +"a+ c #5266C7", +"b+ c #DE411E", +"c+ c #EF3C18", +"d+ c #755FAD", +"e+ c #C94C34", +"f+ c #C14F3F", +"g+ c #DB452E", +"h+ c #AB526E", +"i+ c #EB421F", +"j+ c #AA566A", +"k+ c #A55B6B", +"l+ c #AA5873", +"m+ c #E94824", +"n+ c #D94D35", +"o+ c #DB4F31", +"p+ c #5B71DE", +"q+ c #A85F76", +"r+ c #EC4C31", +"s+ c #E2522E", +"t+ c #E84F30", +"u+ c #CD5D42", +"v+ c #6976CD", +"w+ c #D9583F", +"x+ c #E55537", +"y+ c #E7573E", +"z+ c #D65F4C", +"A+ c #CA6457", +"B+ c #E35C3E", +"C+ c #D1634E", +"D+ c #E26447", +"E+ c #E1674E", +"F+ c #DE6F57", +"G+ c #DE705E", +" ", +" ", +" C+ ", +" ` >.o. 1+X.u+ ", +" e.A K K T $.T ++ 1+L.L.X. ", +" A A v ` T T T 1.~.++ X.L.L.L.W.1+ ", +" g A K K T T $.$.1.$.1. >+L.L.L.L.L.L. ", +" r g A v v T T $.1.$.$.|.|. + u+X.L.L.L.L.W.W.W.2+ ", +" f r g g K K v T T T |.|.1.1.c.-+ u+X.L.L.L.Q.L.Q.Q.W.W. ", +" 6 o B C. 4 9 p p v T T T 1.~.$.1.~.a.~.1. u+X.L.L.L.Q.L.W.L.W.W.W.o+ ", +" f o B C. 4 9 9 g v v T T $.1.1.~.|.c.c.c.m. 4+L.L.L.Q.L.Q.Q.W.&+W.W.,+b+ ", +" f o J L U. 9 9 A v K T $.1.1.$.|.|.c.c.c.a.a.q+ 4+L.L.L.L.L.Q.W.W.&+L.&+&+&+W. ", +" f o B W L <. < 9 g A e.` ` $.$.|.$.1.~.a.a.c.c.a.k+ 1+L.L.Q.Q.W.L.W.L.&+&+&+,+&+&+w+ ", +" 6 f B L L .. 4 4 9 o.$.|.$.c.a.c.c.m.m.y.c.q+ u+L.W.Q.L.W.&+&+W.&+,+&+&+&+.+s+ ", +" p. f o L W W .. / < r 1.|.|.c.c.c.c.y.m.y.a.k+ X.L.W.L.,+,+&+,+&+&+&+[+&+&+b+ ", +" 3 6 B L W W &.9. / o.|.|.c.a.m.y.a.a.y.y.q+ e+W.W.&+W.W.,+&+&+&+&+.+&+!+b+ ", +" a : f o L L ....&.B. / ++a.a.m.c.a.y.y.a.y.y.q+ u+W.W.W.&+.+&+{+&+[+&+!+&+]+&+ ", +" O ^ 1 f J W ....&.'.U. / 5 k+c.m.c.m.y.m.y.y.y.y. L.&+&+&+&+&+&+!+]+!+[+!+!+!+ ", +" f.[ ) $ <.L W ..&.'.'. / k+c.m.a.m.y.a.y.y.y.y. b+&+,+&+.+&+&+&+[+]+&+!+&+!+E+ ", +" `.8 ! = $ <.W ..&.'.'.B. - k+y.a.y.y.a.y.x.y.x.V. b+.+&+!+&+[+!+!+]+!+!+!+9+!+B+ ", +" a+m { = # $ ....'.'.'.,.=+ - k+m.y.y.y.V.y.x.y.x.V. w+!+&+!+]+&+[+&+!+[+<+!+]+&+B+ ", +" v+m 8 ) # $ U...'.'.'.,.:. - -+m.m.y.V.x.y.y.x.m.0+ s+&+!+[+&+!+]+<+!+]+]+[+<+9+B+ ", +" ;.m ) = + $ 9.&.'.l.l.,.*+ & -+y.y.x.x.J.x.y.x.y.0+ B+&+]+!+<+!+!+[+]+!+]+<+]+<+B+ ", +" ;.i [ = + . $ &.'.,.,.,.:.*+ & 0+y.y.x.y.y.y.J.V.x.l+ s+!+!+!+[+<+]+]+!+]+]+]+!+]+B+ ", +" ;.z ^+1 # + $ =+l.'.l.7.:.'. % y.x.y.x.x.P.y.x.P.y. B+.+[+]+!+[+]+]+<+]+]+]+]+<+B+ ", +" ;.z ;. + + . $ '.:.:.:.:.:.3+ & V.y.x.y.x.J.P.P.V.Z. x+<+]+]+]+:+]+]+]+9+<+9+9+]+ ", +" p+*.G G p+ + . @ U.:.:.7.:.6.7. % h+P.V.P.P.y.J.P.x.y.0+ s+]+9+]+]+9+9+]+]+]+]+]+9+<+ ", +" f. p+F.H H G F. . . . $ l.:.:.:.6.:.O. & 0+x.J.J.V.P.x.x.P.P.h+ x+<+]+<+]+]+]+9+]+]+]+9+]+<+ ", +" i G X X X G H ;. . . $ =+:.:.6.l.6.:.3+ % V.y.y.x.P.V.V.P.P.J. m+!+]+]+]+9+]+|+9+|+]+9+]+c+ ", +" m z G H G H G p+ . . * O.6.6.:.7.6.:. & V.P.P.P.x.P.P.V.V.V. i+]+]+<+|+:+]+]+:+]+:+:+]+m+ ", +" i z G G G G F. . . * 3+7.7.6.6.6.6.3+ % 0+x.P.x.V.x.x.x.x.P.0+ [+9+]+]+]+|+]+]+]+9+9+9+]+m+ ", +" i z G G H *. . . * :.6.6.7.6.7.:. & 0+P.P.P.P.P.P.P.P.V. :+:+]+]+9+]+9+9+|+:+:+:+]+B+ ", +" i z G G G p+ . * *+7.}.6.4.6.6.*+ % V.y.P.x.P.x.P.P.P.V. F+]+|+9+:+]+:+:+]+]+:+|+|+:+B+ ", +" i z G G ^+ . ' n.4.6.7.4.7.4.6. & 0+x.P.P.P.P.P.P.P.P.~+ s+9+]+:+]+|+6+]+]+|+9+]+]+9+ ", +" i z G F. . * O.4.6.6.6.6.4.*+ & ~+P.P.P.P.P.P.P.P.Z.l+ s+6+9+|+]+9+|+|+]+6+:+6+6+9+ ", +" j z !. . ] J 4.6.4.[.4.[.l. & P.P.P.P.P.P.P.P.P.V. c+9+6+]+|+6+9+]+6+9+|+9+|+i+ ", +" a+i ;. . ' l.[.(.6.6.6.4.*+ & h+P.P.S.P.S.P.P.P.P.0+ :+|+]+]+|+6+|+9+6+]+6+]+9+t+ ", +" a+!. . ] t.(.6.7.4.4.[.6. & S.P.P.P.P.P.S.S.S.S.q+ D+9+9+|+6+]+9+]+6+|+6+9+6+:+E+ ", +" . 3+7.[.6.(.6.6.6.*+ & h+S.S.P.S.~+P.P.P.P.~+ r+6+]+9+6+6+6+9+]+9+6+9+6+9+ ", +" . 6.(.6.4.4.4.(.6. & ~+P.P.P.P.P.P.P.P.P.h+ c+6+|+6+9+6+9+6+|+6+|+6+9+9+ ", +" . l.[.[.(.6.(.4.(.3+ % l+S.V.~+P.P.P.~+P.P.S. 9+]+|+6+9+6+|+9+9+]+|+6+9+t+ ", +" ~ *+(.6.4.4.4.6.4.l. % ~+P.S.P.S.P.S.P.S.S.0+ B+9+6+9+9+6+|+9+6+6+|+]+]+|+D+ ", +" ~ '+(.[.(.(.(.4.4.6.d+ & S.P.S.P.S.P.S.P.P.~+ i+9+6+6+9+6+6+:+9+:+6+|+6+c+ ", +" = '+(.[.(.(.[.4.[.(.O. % ~+~+S.Z.S.Z.S.P.Z.S.~+ 9+6+6+9+6+9+:+9+6+6+9+6+]+m+ ", +" _ '+Z }.}.4.6.(.(.[.4. & q+S.S.S.~+S.~+S.S.S.S.l+ x+]+9+6+9+6+9+6+9+9+6+9+|+6+B+ ", +" ) h h.Z -.-.}.(.(.[.[.(.'+ & R.Z.Z.S.S.S.S.~+Z.Z.R. r+|+6+|+6+9+6+|+6+6+9+6+9+]+F+ ", +" _ 3 x k.j.I Z -.Z Z }.4.(.(.4.t. % l+S.S.Z.Z.Z.S.S.S.S.Z.h+ F+]+9+6+9+6+9+6+9+9+6+9+6+6+c+ ", +" ) 3 7 q I I Z N }.}.}.(.}.}.(. & Z.Z.S.Z.Z.S.Z.Z.Z.Z.~+ B+6+6+9+6+|+6+9+6+6+|+6+6+9+y+ ", +" = ! 7 c q I N Z Z Z Z -.}.}.}.'+ % l+S.Z.S.S.Z.S.S.S.S.Z.h+ c+9+6+9+6+9+6+|+9+6+9+9+6+6+ ", +" _ ) 3 l q N N Z N -.}.Z -.(.O. & R.R.R.S.K.S.Z.Z.Z.V.$+ D+6+6+9+6+9+6+9+6+6+9+6+6+9+m+ ", +" _ 3 3 l q I N Z Z -.-.}.}.Z & $+S.S.Z.Z.Z.K.K.K.K.S.h+ i+9+6+9+6+9+6+9+9+6+9+9+6+9+B+ ", +" _ | 7 l y I I N N Z -.(.-.d+ % l+R.R.R.R.S.K.Z.S.S.S.~+ F+9+6+9+|+6+6+9+6+6+9+|+6+6+9+ ", +" _ 3 7 c q I R Z :.Z Z -.'+ & R.K.K.K.R.Z.S.Z.Z.Z.K.l+ r+9+6+6+9+6+6+6+9+6+6+9+9+6+t+ ", +" ) | 7 l y y I N R Z -.D & ;+R.Z.Z.S.K.K.R.Z.S.S.5+ G+6+6+9+9+6+9+9+9+6+6+9+6+6+]+E+ ", +" 3 x c I y I Z Z Z h.' & 5+K.K.R.R.S.K.S.K.R.R.S. t+9+6+|+9+6+6+6+6+9+6+9+|+|+i+ ", +" | c l q I R I Z h. $ & $+R.s.K.R.R.Z.R.K.K.K.5+ 9+6+9+6+6+6+c+6+9+6+9+|+6+6+x+ ", +" h 7 l q y N N j. * & H.H.R.s.K.H.H.K.Z.K.S.Z. r+9+6+9+6+6+6+6+6+6+6+9+9+9+c+ ", +" 0.l q y I y '+ ' ; ;+v.v.K.R.H.K.K.v.H.Z.R.5+ G+9+|+6+c+6+c+6+c+6+6+9+6+|+6+B+ ", +" 0.x D j. ' & ;+s.R.s.K.H.s.s.R.K.K.K.$+ m+9+9+|+9+6+9+6+6+c+6+6+9+9+6+ ", +" ( ( ; ;+v.$+s.R.v.H.R.K.K.R.s.R. E+9+|+9+6+6+c+6+c+6+6+9+9+6+6+t+ ", +" ( } & ; /+5.v.v.v.s.H.R.H.H.s.K.R.5+ i+9+|+9+9+6+6+6+c+6+6+6+9+6+9+ ", +" ( } - ; /+q.s.s.s.E.H.v.K.v.R.K.R.$+ y+|+|+9+9+|+9+c+6+6+c+9+9+|+9+x+ ", +" ( 2 0 - ; D.d.q.q.E.v.v.s.s.H.s.R.v.$+ 9+9+|+|+9+6+6+9+c+6+6+6+c+6+9+ ", +" } 0 0 ; ; 3.q.b.q.q.s.E.s.H.H.v.K.v.H.5+ r+9+|+9+9+6+c+9+6+6+c+9+9+|+|+x+ ", +" } 0 d d ; < N.5.3.q.q.q.q.s.E.E.E.s.v.s.H.5+ D+9+9+|+9+9+6+c+9+6+|+6+9+9+9+9+ ", +" } 2 s s s M w.w.e @.{.^.3.3.b.d.q.q.q.s.v.v.s.H.s.$+ i+9+|+9+|+c+6+|+6+9+9+9+9+6+|+y+ ", +" 0 0 d s C M V V V ^.^.5.5.5.3.d.q.q.q.q.s.v.E.R.E. r+9+c+|+9+|+9+c+c+|+|+9+6+9+6+i+ ", +" 2 2 s w C C V V %.{.{.5.b.5.q.3.d.q.q.s.E.E.v.s.q+ w+9+9+9+9+9+9+|+|+|+9+9+|+9+|+9+E+ ", +" 0 0 d w C C S V V {.%.#.3.5.d.d.d.q.q.s.s.E.E.q+ E+c+9+9+c+9+c+9+|+9+|+9+9+9+9+|+i+ ", +" } d s s C M V S %.%.^.3.3.3.3.3.d.d.q.d.q.s./+ i+!+c+9+9+9+|+9+9+|+9+|+9+|+|+9+G+ ", +" 0 d d e M M V S {.{.^.3.3.d.d.D.d.d.q.q.v./+ r+[+|+{+9+c+9+9+9+9+9+9+|+9+9+9+B+ ", +" d s s s V S V S %.{.{.5.3.3.d.d.q.q.q./+ o+[+!+c+9+|+9+c+9+c+9+c+9+9+|+9+m+ ", +" 0 s C s V V %.V {.^.{.5.5.3.3.D.d.q./+ n+{+[+{+[+c+c+|+9+9+9+|+9+c+9+|+|+G+ ", +" d w s M M M V %.%.{.3.{.3.d.d.d.q./+ w+[+[+{+&+{+{+{+9+c+9+c+9+c+|+9+c+B+ ", +" w s @.S S S %.%.{.5.5.^.3.b.D. G+@+[+[+[+c+[+<+[+9+c+|+c+9+9+c+|+m+ ", +" w s C M V V S %.^.{.{.3.5.N. C+@+[+{+&+{+&+<+[+{+{+{+9+9+9+c+9+c+G+ ", +" w M M S V {.V ^.5.5.{. w+,+,+@+[+[+{+[+{+c+9+<+[+9+9+9+9+{+D+ ", +" M @.V V {.{.#.++ w+@+@+@+[+{+[+[+{+[+&+[+[+{+c+{+{+{+x+ ", +" w.>.p t b ). 8+@+,+[+,+@+{+[+[+&+{+{+{+[+!+[+9+9+b+ ", +" +.b b n u %+ 2+#+,+@+Q.@+[+@+,+[+[+[+[+[+<+[+<+{+i+ ", +" b b k u u u+M.M.#+#+@+,+@+[+@+[+{+[+[+{+[+[+[+[+{+z+ ", +" b b n u u /. A+2+M.#+#+#+,+@+@+,+,+[+@+,+[+{+[+{+{+[+<+w+ ", +" t k k F u P /.7+ A+e+z.#+#+@+M.#+#+,+#+@+Q.[+@+@+,+{+&+[+{+[+B+ ", +" Y n u u E Q U U T.7+ f+4+I.M.>+M.M.Y.@+M.@+#+@+,+@+@+[+[+[+[+[+[+[+n+ ", +" k n F Q Q U .=._.8.G.(+(+)+g.i.z.r.I.M.I.>+I.#+#+#+#+#+#+,+,+Q.,+@+,+@+{+{+o+ ", +" k u u E U U U =.=._.2.g.i.i.i.A.r.z.z.I.M.Y.#+>+#+#+#+Q.@+@+@+@+,+[+@+[+,+s+ ", +" ).n E E P U =.=.=.8._._.g.g.i.i.A.A.z.I.z.M.Y.M.M.M.#+#+#+,+@+@+@+[+,+@+g+ ", +" u u Q U U U =._._.2.g.g.g.A.A.A.z.A.z.M.I.I.Y.#+#+#+#+@+#+,+,+,+,+@+o+ ", +" ).F Q U U =.=.=.8._.2.2.g.r.r.r.I.I.I.z.M.I.M.#+M.#+#+#+#+@+@+@+,+w+ ", +" Q Q P U U =._._.2._.g.g.g.i.A.A.A.A.I.I.M.X.M.#+@+#+@+@+@+,+#+z+ ", +" u.U /.U U 8._.8._.2.2.g.g.i.A.A.I.z.I.I.M.I.Y.>+@+#+#+#+#+#+A+ ", +" ].U U =.=.=.2.2._.g.i.i.A.i.A.A.I.z.I.>+Y.>+Y.Y.#+#+#+n+ ", +" /.U .=.=._._.2.2.g.i.A.A.z.A.I.I.M.I.M.M.M.M.Y.M.z+ ", +" /.=.8._._.2._.g.g.g.g.i.A.z.A.z.I.I.Y.I.X.#+8+ ", +" /.8.8._._.2.2.i.i.A.i.A.z.z.I.I.I.M.M.8+A+ ", +" }+8._.2.2.g.2.A.i.A.A.A.>+z.z.z.4+A+ ", +" T.)+_.g.2.A.r.A.A.r.A.4+z+ ", +" }+}+(+(+f+f+A+ ", +" ", +" "}; diff --git a/QGLViewer/qglviewer.cpp b/QGLViewer/qglviewer.cpp new file mode 100644 index 0000000..6452e52 --- /dev/null +++ b/QGLViewer/qglviewer.cpp @@ -0,0 +1,3171 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "domUtils.h" +#include "qglviewer.h" +#include "camera.h" +#include "keyFrameInterpolator.h" +#include "manipulatedCameraFrame.h" + +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#include +# include +# include +# include +# include + +using namespace std; +using namespace qglviewer; + +// Static private variable +QList QGLViewer::QGLViewerPool_; + + +/*! \mainpage + +libQGLViewer is a free C++ library based on Qt that enables the quick creation of OpenGL 3D viewers. +It features a powerful camera trackball and simple applications simply require an implementation of +the draw() method. This makes it a tool of choice for OpenGL beginners and +assignments. It provides screenshot saving, mouse manipulated frames, stereo display, interpolated +keyFrames, object selection, and much more. It is fully +customizable and easy to extend to create complex applications, with a possible Qt GUI. + +libQGLViewer is not a 3D viewer that can be used directly to view 3D scenes in various +formats. It is more likely to be the starting point for the coding of such a viewer. + +libQGLViewer is based on the Qt toolkit and hence compiles on any architecture (Unix-Linux, Mac, +Windows, ...). Full reference documentation and many examples are provided. + +See the project main page for details on the project and installation steps. */ + +void QGLViewer::defaultConstructor() +{ + // Test OpenGL context + // if (glGetString(GL_VERSION) == 0) + // qWarning("Unable to get OpenGL version, context may not be available - Check your configuration"); + + std::cout << "OpenGL version: " << format().majorVersion() + << "." << format().minorVersion() << std::endl; + + int poolIndex = QGLViewer::QGLViewerPool_.indexOf(NULL); + setFocusPolicy(Qt::StrongFocus); + + if (poolIndex >= 0) + QGLViewer::QGLViewerPool_.replace(poolIndex, this); + else + QGLViewer::QGLViewerPool_.append(this); + + camera_ = new Camera(); + setCamera(camera()); + + setDefaultShortcuts(); + setDefaultMouseBindings(); + + setSnapshotFileName(tr("snapshot", "Default snapshot file name")); + initializeSnapshotFormats(); + setSnapshotCounter(0); + setSnapshotQuality(95); + + fpsTime_.start(); + fpsCounter_ = 0; + f_p_s_ = 0.0; + fpsString_ = tr("%1Hz", "Frames per seconds, in Hertz").arg("?"); + visualHint_ = 0; + previousPathId_ = 0; + // prevPos_ is not initialized since pos() is not meaningful here. + // It will be set when setFullScreen(false) is called after setFullScreen(true) + + // #CONNECTION# default values in initFromDOMElement() + manipulatedFrame_ = NULL; + manipulatedFrameIsACamera_ = false; + mouseGrabberIsAManipulatedFrame_ = false; + mouseGrabberIsAManipulatedCameraFrame_ = false; + displayMessage_ = false; + connect(&messageTimer_, SIGNAL(timeout()), SLOT(hideMessage())); + messageTimer_.setSingleShot(true); + helpWidget_ = NULL; + setMouseGrabber(NULL); + + setSceneRadius(1.0); + showEntireScene(); + setStateFileName(".qglviewer.xml"); + + // #CONNECTION# default values in initFromDOMElement() + setAxisIsDrawn(false); + setGridIsDrawn(false); + setFPSIsDisplayed(false); + setCameraIsEdited(false); + setTextIsEnabled(true); + setStereoDisplay(false); + // Make sure move() is not called, which would call initializeGL() + fullScreen_ = false; + setFullScreen(false); + + animationTimerId_ = 0; + stopAnimation(); + setAnimationPeriod(40); // 25Hz + + selectBuffer_ = NULL; + + bufferTextureId_ = 0; + bufferTextureMaxU_ = 0.0; + bufferTextureMaxV_ = 0.0; + bufferTextureWidth_ = 0; + bufferTextureHeight_ = 0; + previousBufferTextureFormat_ = 0; + previousBufferTextureInternalFormat_ = 0; + currentlyPressedKey_ = Qt::Key(0); + + setAttribute(Qt::WA_NoSystemBackground); + + tileRegion_ = NULL; +} + +#if !defined QT3_SUPPORT +/*! Constructor. See \c QGLWidget documentation for details. + +All viewer parameters (display flags, scene parameters, associated objects...) are set to their default values. See +the associated documentation. + +If the \p shareWidget parameter points to a valid \c QGLWidget, the QGLViewer will share the OpenGL +context with \p shareWidget (see isSharing()). */ +QGLViewer::QGLViewer(QWidget* parent, Qt::WindowFlags flags) + : QOpenGLWidget(parent, flags) +{ defaultConstructor(); } +#endif // QT3_SUPPORT + +/*! Virtual destructor. + +The viewer is replaced by \c NULL in the QGLViewerPool() (in order to preserve other viewer's indexes) and allocated +memory is released. The camera() is deleted and should be copied before if it is shared by an other viewer. */ +QGLViewer::~QGLViewer() +{ + // See closeEvent comment. Destructor is called (and not closeEvent) only when the widget is embedded. + // Hence we saveToFile here. It is however a bad idea if virtual domElement() has been overloaded ! + // if (parent()) + // saveStateToFileForAllViewers(); + + QGLViewer::QGLViewerPool_.replace(QGLViewer::QGLViewerPool_.indexOf(this), NULL); + + delete camera(); + delete[] selectBuffer_; + if (helpWidget()) + { + // Needed for Qt 4 which has no main widget. + helpWidget()->close(); + delete helpWidget_; + } +} + + +static QString QGLViewerVersionString() +{ + return QString::number((QGLVIEWER_VERSION & 0xff0000) >> 16) + "." + + QString::number((QGLVIEWER_VERSION & 0x00ff00) >> 8) + "." + + QString::number(QGLVIEWER_VERSION & 0x0000ff); +} + +static Qt::KeyboardModifiers keyboardModifiersFromState(unsigned int state) { + // Convertion of keyboard modifiers and mouse buttons as an int is no longer supported : emulate + return Qt::KeyboardModifiers(int(state & 0xFF000000)); +} + + +static Qt::MouseButton mouseButtonFromState(unsigned int state) { + // Convertion of keyboard modifiers and mouse buttons as an int is no longer supported : emulate + return Qt::MouseButton(state & 0xFFFF); +} + +/*! Initializes the QGLViewer OpenGL context and then calls user-defined init(). + +This method is automatically called once, before the first call to paintGL(). + +Overload init() instead of this method to modify viewer specific OpenGL state or to create display +lists. + +To make beginners' life easier and to simplify the examples, this method slightly modifies the +standard OpenGL state: +\code +glEnable(GL_LIGHT0); +glEnable(GL_LIGHTING); +glEnable(GL_DEPTH_TEST); +glEnable(GL_COLOR_MATERIAL); +\endcode + +If you port an existing application to QGLViewer and your display changes, you probably want to +disable these flags in init() to get back to a standard OpenGL state. */ +void QGLViewer::initializeGL() +{ + glEnable(GL_DEPTH_TEST); + + // Default colors + setForegroundColor(QColor(180, 180, 180)); + setBackgroundColor(QColor(51, 51, 51)); + + // Clear the buffer where we're going to draw + if (format().stereo()) + { + glDrawBuffer(GL_BACK_RIGHT); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDrawBuffer(GL_BACK_LEFT); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + else + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Calls user defined method. Default emits a signal. + init(); + + // Give time to glInit to finish and then call setFullScreen(). + if (isFullScreen()) + QTimer::singleShot( 100, this, SLOT(delayedFullScreen()) ); +} + +/*! Main paint method, inherited from \c QGLWidget. + +Calls the following methods, in that order: +\arg preDraw() (or preDrawStereo() if viewer displaysInStereo()) : places the camera in the world coordinate system. +\arg draw() (or fastDraw() when the camera is manipulated) : main drawing method. Should be overloaded. +\arg postDraw() : display of visual hints (world axis, FPS...) */ +void QGLViewer::paintGL() +{ + if (displaysInStereo()) + { + for (int view=1; view>=0; --view) + { + // Clears screen, set model view matrix with shifted matrix for ith buffer + preDrawStereo(view); + // Used defined method. Default is empty + if (camera()->frame()->isManipulated()) + fastDraw(); + else + draw(); + postDraw(); + } + } + else + { + // Clears screen, set model view matrix... + preDraw(); + // Used defined method. Default calls draw() + if (camera()->frame()->isManipulated()) + fastDraw(); + else + draw(); + // Add visual hints: axis, camera, grid... + postDraw(); + } + Q_EMIT drawFinished(true); +} + +/*! Sets OpenGL state before draw(). + +Default behavior clears screen and sets the projection and modelView matrices: +\code +glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + +camera()->loadProjectionMatrix(); +camera()->loadModelViewMatrix(); +\endcode + +Emits the drawNeeded() signal once this is done (see the callback example). */ +void QGLViewer::preDraw() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // GL_PROJECTION matrix + camera()->loadProjectionMatrix(); + // GL_MODELVIEW matrix + camera()->loadModelViewMatrix(); + + Q_EMIT drawNeeded(); +} + +/*! Called after draw() to draw viewer visual hints. + +Default implementation displays axis, grid, FPS... when the respective flags are sets. + +See the multiSelect and thumbnail examples for an overloading illustration. + +The GLContext (color, LIGHTING, BLEND...) is \e not modified by this method, so that in +draw(), the user can rely on the OpenGL context he defined. Respect this convention (by pushing/popping the +different attributes) if you overload this method. */ +void QGLViewer::postDraw() +{ + // Reset model view matrix to world coordinates origin + camera()->loadModelViewMatrix(); + // TODO restore model loadProjectionMatrixStereo + + // FPS computation + const unsigned int maxCounter = 20; + if (++fpsCounter_ == maxCounter) + { + f_p_s_ = 1000.0 * maxCounter / fpsTime_.restart(); + fpsString_ = tr("%1Hz", "Frames per seconds, in Hertz").arg(f_p_s_, 0, 'f', ((f_p_s_ < 10.0)?1:0)); + fpsCounter_ = 0; + } + + bool depthTestWasEnabled = glIsEnabled(GL_DEPTH_TEST); + glDisable(GL_DEPTH_TEST); + + // Restore GL state + if (depthTestWasEnabled) + glEnable(GL_DEPTH_TEST); +} + +/*! Called before draw() (instead of preDraw()) when viewer displaysInStereo(). + +Same as preDraw() except that the glDrawBuffer() is set to \c GL_BACK_LEFT or \c GL_BACK_RIGHT +depending on \p leftBuffer, and it uses qglviewer::Camera::loadProjectionMatrixStereo() and +qglviewer::Camera::loadModelViewMatrixStereo() instead. */ +void QGLViewer::preDrawStereo(bool leftBuffer) +{ + // Set buffer to draw in + // Seems that SGI and Crystal Eyes are not synchronized correctly ! + // That's why we don't draw in the appropriate buffer... + if (!leftBuffer) + glDrawBuffer(GL_BACK_LEFT); + else + glDrawBuffer(GL_BACK_RIGHT); + + // Clear the buffer where we're going to draw + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // GL_PROJECTION matrix + //camera()->loadProjectionMatrixStereo(leftBuffer); + // GL_MODELVIEW matrix + camera()->loadModelViewMatrixStereo(leftBuffer); + + Q_EMIT drawNeeded(); +} + +/*! Draws a simplified version of the scene to guarantee interactive camera displacements. + +This method is called instead of draw() when the qglviewer::Camera::frame() is +qglviewer::ManipulatedCameraFrame::isManipulated(). Default implementation simply calls draw(). + +Overload this method if your scene is too complex to allow for interactive camera manipulation. See +the fastDraw example for an illustration. */ +void QGLViewer::fastDraw() +{ + draw(); +} + +/*! Starts (\p edit = \c true, default) or stops (\p edit=\c false) the edition of the camera(). + +Current implementation is limited to paths display. Get current state using cameraIsEdited(). + +\attention This method sets the qglviewer::Camera::zClippingCoefficient() to 5.0 when \p edit is \c +true, so that the Camera paths (see qglviewer::Camera::keyFrameInterpolator()) are not clipped. It +restores the previous value when \p edit is \c false. */ +void QGLViewer::setCameraIsEdited(bool edit) +{ + cameraIsEdited_ = edit; + if (edit) + { + previousCameraZClippingCoefficient_ = camera()->zClippingCoefficient(); + // #CONNECTION# 5.0 also used in domElement() and in initFromDOMElement(). + camera()->setZClippingCoefficient(5.0); + } + else + camera()->setZClippingCoefficient(previousCameraZClippingCoefficient_); + + Q_EMIT cameraIsEditedChanged(edit); + + update(); +} + +// Key bindings. 0 means not defined +void QGLViewer::setDefaultShortcuts() +{ + // D e f a u l t a c c e l e r a t o r s + setShortcut(DRAW_AXIS, Qt::Key_A); + setShortcut(DRAW_GRID, Qt::Key_G); + setShortcut(DISPLAY_FPS, Qt::Key_F); + setShortcut(ENABLE_TEXT, Qt::SHIFT+Qt::Key_Question); + setShortcut(EXIT_VIEWER, Qt::Key_Escape); + setShortcut(SAVE_SCREENSHOT, Qt::CTRL+Qt::Key_S); + setShortcut(CAMERA_MODE, Qt::Key_Space); + setShortcut(FULL_SCREEN, Qt::ALT+Qt::Key_Return); + setShortcut(STEREO, Qt::Key_S); + setShortcut(ANIMATION, Qt::Key_Return); + setShortcut(HELP, Qt::Key_H); + setShortcut(EDIT_CAMERA, Qt::Key_C); + setShortcut(MOVE_CAMERA_LEFT, Qt::Key_Left); + setShortcut(MOVE_CAMERA_RIGHT,Qt::Key_Right); + setShortcut(MOVE_CAMERA_UP, Qt::Key_Up); + setShortcut(MOVE_CAMERA_DOWN, Qt::Key_Down); + setShortcut(INCREASE_FLYSPEED, Qt::Key_Plus); + setShortcut(DECREASE_FLYSPEED, Qt::Key_Minus); + setShortcut(SNAPSHOT_TO_CLIPBOARD, Qt::CTRL+Qt::Key_C); + + keyboardActionDescription_[DISPLAY_FPS] = tr("Toggles the display of the FPS", "DISPLAY_FPS action description"); + keyboardActionDescription_[SAVE_SCREENSHOT] = tr("Saves a screenshot", "SAVE_SCREENSHOT action description"); + keyboardActionDescription_[FULL_SCREEN] = tr("Toggles full screen display", "FULL_SCREEN action description"); + keyboardActionDescription_[DRAW_AXIS] = tr("Toggles the display of the world axis", "DRAW_AXIS action description"); + keyboardActionDescription_[DRAW_GRID] = tr("Toggles the display of the XY grid", "DRAW_GRID action description"); + keyboardActionDescription_[CAMERA_MODE] = tr("Changes camera mode (observe or fly)", "CAMERA_MODE action description"); + keyboardActionDescription_[STEREO] = tr("Toggles stereo display", "STEREO action description"); + keyboardActionDescription_[HELP] = tr("Opens this help window", "HELP action description"); + keyboardActionDescription_[ANIMATION] = tr("Starts/stops the animation", "ANIMATION action description"); + keyboardActionDescription_[EDIT_CAMERA] = tr("Toggles camera paths display", "EDIT_CAMERA action description"); // TODO change + keyboardActionDescription_[ENABLE_TEXT] = tr("Toggles the display of the text", "ENABLE_TEXT action description"); + keyboardActionDescription_[EXIT_VIEWER] = tr("Exits program", "EXIT_VIEWER action description"); + keyboardActionDescription_[MOVE_CAMERA_LEFT] = tr("Moves camera left", "MOVE_CAMERA_LEFT action description"); + keyboardActionDescription_[MOVE_CAMERA_RIGHT] = tr("Moves camera right", "MOVE_CAMERA_RIGHT action description"); + keyboardActionDescription_[MOVE_CAMERA_UP] = tr("Moves camera up", "MOVE_CAMERA_UP action description"); + keyboardActionDescription_[MOVE_CAMERA_DOWN] = tr("Moves camera down", "MOVE_CAMERA_DOWN action description"); + keyboardActionDescription_[INCREASE_FLYSPEED] = tr("Increases fly speed", "INCREASE_FLYSPEED action description"); + keyboardActionDescription_[DECREASE_FLYSPEED] = tr("Decreases fly speed", "DECREASE_FLYSPEED action description"); + keyboardActionDescription_[SNAPSHOT_TO_CLIPBOARD] = tr("Copies a snapshot to clipboard", "SNAPSHOT_TO_CLIPBOARD action description"); + + // K e y f r a m e s s h o r t c u t k e y s + setPathKey(Qt::Key_F1, 1); + setPathKey(Qt::Key_F2, 2); + setPathKey(Qt::Key_F3, 3); + setPathKey(Qt::Key_F4, 4); + setPathKey(Qt::Key_F5, 5); + setPathKey(Qt::Key_F6, 6); + setPathKey(Qt::Key_F7, 7); + setPathKey(Qt::Key_F8, 8); + setPathKey(Qt::Key_F9, 9); + setPathKey(Qt::Key_F10, 10); + setPathKey(Qt::Key_F11, 11); + setPathKey(Qt::Key_F12, 12); + + setAddKeyFrameKeyboardModifiers(Qt::AltModifier); + setPlayPathKeyboardModifiers(Qt::NoModifier); +} + +// M o u s e b e h a v i o r +void QGLViewer::setDefaultMouseBindings() +{ + const Qt::KeyboardModifiers cameraKeyboardModifiers = Qt::NoModifier; + const Qt::KeyboardModifiers frameKeyboardModifiers = Qt::ControlModifier; + + //#CONNECTION# toggleCameraMode() + for (int handler=0; handler<2; ++handler) + { + MouseHandler mh = (MouseHandler)(handler); + Qt::KeyboardModifiers modifiers = (mh == FRAME) ? frameKeyboardModifiers : cameraKeyboardModifiers; + + setMouseBinding(modifiers, Qt::LeftButton, mh, ROTATE); + setMouseBinding(modifiers, Qt::MidButton, mh, ZOOM); + setMouseBinding(modifiers, Qt::RightButton, mh, TRANSLATE); + + setMouseBinding(Qt::Key_R, modifiers, Qt::LeftButton, mh, SCREEN_ROTATE); + + setWheelBinding(modifiers, mh, ZOOM); + } + + // Z o o m o n r e g i o n + setMouseBinding(Qt::ShiftModifier, Qt::MidButton, CAMERA, ZOOM_ON_REGION); + + // S e l e c t + setMouseBinding(Qt::ShiftModifier, Qt::LeftButton, SELECT); + + setMouseBinding(Qt::ShiftModifier, Qt::RightButton, RAP_FROM_PIXEL); + // D o u b l e c l i c k + setMouseBinding(Qt::NoModifier, Qt::LeftButton, ALIGN_CAMERA, true); + setMouseBinding(Qt::NoModifier, Qt::MidButton, SHOW_ENTIRE_SCENE, true); + setMouseBinding(Qt::NoModifier, Qt::RightButton, CENTER_SCENE, true); + + setMouseBinding(frameKeyboardModifiers, Qt::LeftButton, ALIGN_FRAME, true); + // middle double click makes no sense for manipulated frame + setMouseBinding(frameKeyboardModifiers, Qt::RightButton, CENTER_FRAME, true); + + // A c t i o n s w i t h k e y m o d i f i e r s + setMouseBinding(Qt::Key_Z, Qt::NoModifier, Qt::LeftButton, ZOOM_ON_PIXEL); + setMouseBinding(Qt::Key_Z, Qt::NoModifier, Qt::RightButton, ZOOM_TO_FIT); + +#ifdef Q_OS_MAC + // Specific Mac bindings for touchpads. Two fingers emulate a wheelEvent which zooms. + // There is no right button available : make Option key + left emulate the right button. + // A Control+Left indeed emulates a right click (OS X system configuration), but it does + // no seem to support dragging. + // Done at the end to override previous settings. + const Qt::KeyboardModifiers macKeyboardModifiers = Qt::AltModifier; + + setMouseBinding(macKeyboardModifiers, Qt::LeftButton, CAMERA, TRANSLATE); + setMouseBinding(macKeyboardModifiers, Qt::LeftButton, CENTER_SCENE, true); + setMouseBinding(frameKeyboardModifiers | macKeyboardModifiers, Qt::LeftButton, CENTER_FRAME, true); + setMouseBinding(frameKeyboardModifiers | macKeyboardModifiers, Qt::LeftButton, FRAME, TRANSLATE); +#endif +} + +/*! Associates a new qglviewer::Camera to the viewer. + +You should only use this method when you derive a new class from qglviewer::Camera and want to use +one of its instances instead of the original class. + +It you simply want to save and restore Camera positions, use qglviewer::Camera::addKeyFrameToPath() +and qglviewer::Camera::playPath() instead. + +This method silently ignores \c NULL \p camera pointers. The calling method is responsible for deleting +the previous camera pointer in order to prevent memory leaks if needed. + +The sceneRadius() and sceneCenter() of \p camera are set to the \e current QGLViewer values. + +All the \p camera qglviewer::Camera::keyFrameInterpolator() +qglviewer::KeyFrameInterpolator::interpolated() signals are connected to the viewer update() slot. +The connections with the previous viewer's camera are removed. */ +void QGLViewer::setCamera(Camera* const camera) +{ + if (!camera) + return; + + camera->setSceneRadius(sceneRadius()); + camera->setSceneCenter(sceneCenter()); + camera->setScreenWidthAndHeight(width(), height()); + + // Disconnect current camera from this viewer. + disconnect(this->camera()->frame(), SIGNAL(manipulated()), this, SLOT(update())); + disconnect(this->camera()->frame(), SIGNAL(spun()), this, SLOT(update())); + + // Connect camera frame to this viewer. + connect(camera->frame(), SIGNAL(manipulated()), SLOT(update())); + connect(camera->frame(), SIGNAL(spun()), SLOT(update())); + + connectAllCameraKFIInterpolatedSignals(false); + camera_ = camera; + connectAllCameraKFIInterpolatedSignals(); + + previousCameraZClippingCoefficient_ = this->camera()->zClippingCoefficient(); +} + +void QGLViewer::connectAllCameraKFIInterpolatedSignals(bool connection) +{ + for (QMap::ConstIterator it = camera()->kfi_.begin(), end=camera()->kfi_.end(); it != end; ++it) + { + if (connection) + connect(camera()->keyFrameInterpolator(it.key()), SIGNAL(interpolated()), SLOT(update())); + else + disconnect(camera()->keyFrameInterpolator(it.key()), SIGNAL(interpolated()), this, SLOT(update())); + } + + if (connection) + connect(camera()->interpolationKfi_, SIGNAL(interpolated()), SLOT(update())); + else + disconnect(camera()->interpolationKfi_, SIGNAL(interpolated()), this, SLOT(update())); +} + +/*! Briefly displays a message in the lower left corner of the widget. Convenient to provide +feedback to the user. + +\p message is displayed during \p delay milliseconds (default is 2 seconds) using drawText(). + +This method should not be called in draw(). If you want to display a text in each draw(), use +drawText() instead. + +If this method is called when a message is already displayed, the new message replaces the old one. +Use setTextIsEnabled() (default shortcut is '?') to enable or disable text (and hence messages) +display. */ +void QGLViewer::displayMessage(const QString& message, int delay) +{ + message_ = message; + displayMessage_ = true; + // Was set to single shot in defaultConstructor. + messageTimer_.start(delay); + if (textIsEnabled()) + update(); +} + +void QGLViewer::hideMessage() +{ + displayMessage_ = false; + if (textIsEnabled()) + update(); +} + + +/*! Overloading of the \c QObject method. + +If animationIsStarted(), calls animate() and draw(). */ +void QGLViewer::timerEvent(QTimerEvent *) +{ + if (animationIsStarted()) + { + animate(); + update(); + } +} + +/*! Starts the animation loop. See animationIsStarted(). */ +void QGLViewer::startAnimation() +{ + animationTimerId_ = startTimer(animationPeriod()); + animationStarted_ = true; +} + +/*! Stops animation. See animationIsStarted(). */ +void QGLViewer::stopAnimation() +{ + animationStarted_ = false; + if (animationTimerId_ != 0) + killTimer(animationTimerId_); +} + +/*! Overloading of the \c QWidget method. + +Saves the viewer state using saveStateToFile() and then calls QGLWidget::closeEvent(). */ +void QGLViewer::closeEvent(QCloseEvent *e) +{ + // When the user clicks on the window close (x) button: + // - If the viewer is a top level window, closeEvent is called and then saves to file. + // - Otherwise, nothing happen s:( + // When the user press the EXIT_VIEWER keyboard shortcut: + // - If the viewer is a top level window, saveStateToFile() is also called + // - Otherwise, closeEvent is NOT called and keyPressEvent does the job. + + /* After tests: + E : Embedded widget + N : Widget created with new + C : closeEvent called + D : destructor called + + E N C D + y y + y n y + n y y + n n y y + + closeEvent is called iif the widget is NOT embedded. + + Destructor is called iif the widget is created on the stack + or if widget (resp. parent if embedded) is created with WDestructiveClose flag. + + closeEvent always before destructor. + + Close using qApp->closeAllWindows or (x) is identical. + */ + + // #CONNECTION# Also done for EXIT_VIEWER in keyPressEvent(). + saveStateToFile(); + QOpenGLWidget::closeEvent(e); +} + +static QString mouseButtonsString(Qt::MouseButtons b) +{ + QString result(""); + bool addAmpersand = false; + if (b & Qt::LeftButton) { result += QGLViewer::tr("Left", "left mouse button"); addAmpersand=true; } + if (b & Qt::MidButton) { if (addAmpersand) result += " & "; result += QGLViewer::tr("Middle", "middle mouse button"); addAmpersand=true; } + if (b & Qt::RightButton) { if (addAmpersand) result += " & "; result += QGLViewer::tr("Right", "right mouse button"); } + return result; +} + +void QGLViewer::performClickAction(ClickAction ca, const QMouseEvent* const e) +{ + // Note: action that need it should call update(). + switch (ca) + { + // # CONNECTION setMouseBinding prevents adding NO_CLICK_ACTION in clickBinding_ + // This case should hence not be possible. Prevents unused case warning. + case NO_CLICK_ACTION : + break; + case ZOOM_ON_PIXEL : + camera()->interpolateToZoomOnPixel(e->pos()); + break; + case ZOOM_TO_FIT : + camera()->interpolateToFitScene(); + break; + case SELECT : + //select(e); DEPRECATED! + update(); + break; + case RAP_FROM_PIXEL : + if (! camera()->setPivotPointFromPixel(e->pos())) + camera()->setPivotPoint(sceneCenter()); + setVisualHintsMask(1); + update(); + break; + case RAP_IS_CENTER : + camera()->setPivotPoint(sceneCenter()); + setVisualHintsMask(1); + update(); + break; + case CENTER_FRAME : + if (manipulatedFrame()) + manipulatedFrame()->projectOnLine(camera()->position(), camera()->viewDirection()); + break; + case CENTER_SCENE : + camera()->centerScene(); + break; + case SHOW_ENTIRE_SCENE : + camera()->showEntireScene(); + break; + case ALIGN_FRAME : + if (manipulatedFrame()) + manipulatedFrame()->alignWithFrame(camera()->frame()); + break; + case ALIGN_CAMERA : + Frame * frame = new Frame(); + frame->setTranslation(camera()->pivotPoint()); + camera()->frame()->alignWithFrame(frame, true); + delete frame; + break; + } +} + +/*! Overloading of the \c QWidget method. + +When the user clicks on the mouse: +\arg if a mouseGrabber() is defined, qglviewer::MouseGrabber::mousePressEvent() is called, +\arg otherwise, the camera() or the manipulatedFrame() interprets the mouse displacements, +depending on mouse bindings. + +Mouse bindings customization can be achieved using setMouseBinding() and setWheelBinding(). See the +mouse page for a complete description of mouse bindings. + +See the mouseMoveEvent() documentation for an example of more complex mouse behavior customization +using overloading. + +\note When the mouseGrabber() is a manipulatedFrame(), the modifier keys are not taken into +account. This allows for a direct manipulation of the manipulatedFrame() when the mouse hovers, +which is probably what is expected. */ +void QGLViewer::mousePressEvent(QMouseEvent* e) +{ + //#CONNECTION# mouseDoubleClickEvent has the same structure + //#CONNECTION# mouseString() concatenates bindings description in inverse order. + ClickBindingPrivate cbp(e->modifiers(), e->button(), false, (Qt::MouseButtons)(e->buttons() & ~(e->button())), currentlyPressedKey_); + + if (clickBinding_.contains(cbp)) { + performClickAction(clickBinding_[cbp], e); + } else + if (mouseGrabber()) + { + if (mouseGrabberIsAManipulatedFrame_) + { + for (QMap::ConstIterator it=mouseBinding_.begin(), end=mouseBinding_.end(); it!=end; ++it) + if ((it.value().handler == FRAME) && (it.key().button == e->button())) + { + ManipulatedFrame* mf = dynamic_cast(mouseGrabber()); + if (mouseGrabberIsAManipulatedCameraFrame_) + { + mf->ManipulatedFrame::startAction(it.value().action, it.value().withConstraint); + mf->ManipulatedFrame::mousePressEvent(e, camera()); + } + else + { + mf->startAction(it.value().action, it.value().withConstraint); + mf->mousePressEvent(e, camera()); + } + break; + } + } + else + mouseGrabber()->mousePressEvent(e, camera()); + update(); + } + else + { + //#CONNECTION# wheelEvent has the same structure + const MouseBindingPrivate mbp(e->modifiers(), e->button(), currentlyPressedKey_); + + if (mouseBinding_.contains(mbp)) + { + MouseActionPrivate map = mouseBinding_[mbp]; + switch (map.handler) + { + case CAMERA : + camera()->frame()->startAction(map.action, map.withConstraint); + camera()->frame()->mousePressEvent(e, camera()); + break; + case FRAME : + if (manipulatedFrame()) + { + if (manipulatedFrameIsACamera_) + { + manipulatedFrame()->ManipulatedFrame::startAction(map.action, map.withConstraint); + manipulatedFrame()->ManipulatedFrame::mousePressEvent(e, camera()); + } + else + { + manipulatedFrame()->startAction(map.action, map.withConstraint); + manipulatedFrame()->mousePressEvent(e, camera()); + } + } + break; + } + if (map.action == SCREEN_ROTATE) + // Display visual hint line + update(); + } + else + e->ignore(); + } +} + +/*! Overloading of the \c QWidget method. + +Mouse move event is sent to the mouseGrabber() (if any) or to the camera() or the +manipulatedFrame(), depending on mouse bindings (see setMouseBinding()). + +If you want to define your own mouse behavior, do something like this: +\code +void Viewer::mousePressEvent(QMouseEvent* e) +{ + +if ((e->button() == myButton) && (e->modifiers() == myModifiers)) + myMouseBehavior = true; +else + QGLViewer::mousePressEvent(e); +} + +void Viewer::mouseMoveEvent(QMouseEvent *e) +{ +if (myMouseBehavior) + // Use e->x() and e->y() as you want... +else + QGLViewer::mouseMoveEvent(e); +} + +void Viewer::mouseReleaseEvent(QMouseEvent* e) +{ +if (myMouseBehavior) + myMouseBehavior = false; +else + QGLViewer::mouseReleaseEvent(e); +} +\endcode */ +void QGLViewer::mouseMoveEvent(QMouseEvent* e) +{ + if (mouseGrabber()) + { + mouseGrabber()->checkIfGrabsMouse(e->x(), e->y(), camera()); + if (mouseGrabber()->grabsMouse()) + if (mouseGrabberIsAManipulatedCameraFrame_) + (dynamic_cast(mouseGrabber()))->ManipulatedFrame::mouseMoveEvent(e, camera()); + else + mouseGrabber()->mouseMoveEvent(e, camera()); + else + setMouseGrabber(NULL); + update(); + } + + if (!mouseGrabber()) + { + //#CONNECTION# mouseReleaseEvent has the same structure + if (camera()->frame()->isManipulated()) + { + camera()->frame()->mouseMoveEvent(e, camera()); + // #CONNECTION# manipulatedCameraFrame::mouseMoveEvent specific if at the beginning + if (camera()->frame()->action_ == ZOOM_ON_REGION) + update(); + } + else // ! + if ((manipulatedFrame()) && (manipulatedFrame()->isManipulated())) + if (manipulatedFrameIsACamera_) + manipulatedFrame()->ManipulatedFrame::mouseMoveEvent(e, camera()); + else + manipulatedFrame()->mouseMoveEvent(e, camera()); + else + if (hasMouseTracking()) + { + Q_FOREACH (MouseGrabber* mg, MouseGrabber::MouseGrabberPool()) + { + mg->checkIfGrabsMouse(e->x(), e->y(), camera()); + if (mg->grabsMouse()) + { + setMouseGrabber(mg); + // Check that MouseGrabber is not disabled + if (mouseGrabber() == mg) + { + update(); + break; + } + } + } + } + } +} + +/*! Overloading of the \c QWidget method. + +Calls the mouseGrabber(), camera() or manipulatedFrame \c mouseReleaseEvent method. + +See the mouseMoveEvent() documentation for an example of mouse behavior customization. */ +void QGLViewer::mouseReleaseEvent(QMouseEvent* e) +{ + if (mouseGrabber()) + { + if (mouseGrabberIsAManipulatedCameraFrame_) + (dynamic_cast(mouseGrabber()))->ManipulatedFrame::mouseReleaseEvent(e, camera()); + else + mouseGrabber()->mouseReleaseEvent(e, camera()); + mouseGrabber()->checkIfGrabsMouse(e->x(), e->y(), camera()); + if (!(mouseGrabber()->grabsMouse())) + setMouseGrabber(NULL); + // update(); + } + else + //#CONNECTION# mouseMoveEvent has the same structure + if (camera()->frame()->isManipulated()) + { + camera()->frame()->mouseReleaseEvent(e, camera()); + } + else + if ((manipulatedFrame()) && (manipulatedFrame()->isManipulated())) + { + if (manipulatedFrameIsACamera_) + manipulatedFrame()->ManipulatedFrame::mouseReleaseEvent(e, camera()); + else + manipulatedFrame()->mouseReleaseEvent(e, camera()); + } + else + e->ignore(); + + // Not absolutely needed (see above commented code for the optimal version), but may reveal + // useful for specific applications. + update(); +} + +/*! Overloading of the \c QWidget method. + +If defined, the wheel event is sent to the mouseGrabber(). It is otherwise sent according to wheel +bindings (see setWheelBinding()). */ +void QGLViewer::wheelEvent(QWheelEvent* e) +{ + if (mouseGrabber()) + { + if (mouseGrabberIsAManipulatedFrame_) + { + for (QMap::ConstIterator it=wheelBinding_.begin(), end=wheelBinding_.end(); it!=end; ++it) + if (it.value().handler == FRAME) + { + ManipulatedFrame* mf = dynamic_cast(mouseGrabber()); + if (mouseGrabberIsAManipulatedCameraFrame_) + { + mf->ManipulatedFrame::startAction(it.value().action, it.value().withConstraint); + mf->ManipulatedFrame::wheelEvent(e, camera()); + } + else + { + mf->startAction(it.value().action, it.value().withConstraint); + mf->wheelEvent(e, camera()); + } + break; + } + } + else + mouseGrabber()->wheelEvent(e, camera()); + update(); + } + else + { + //#CONNECTION# mousePressEvent has the same structure + WheelBindingPrivate wbp(e->modifiers(), currentlyPressedKey_); + + if (wheelBinding_.contains(wbp)) + { + MouseActionPrivate map = wheelBinding_[wbp]; + switch (map.handler) + { + case CAMERA : + camera()->frame()->startAction(map.action, map.withConstraint); + camera()->frame()->wheelEvent(e, camera()); + break; + case FRAME : + if (manipulatedFrame()) { + if (manipulatedFrameIsACamera_) + { + manipulatedFrame()->ManipulatedFrame::startAction(map.action, map.withConstraint); + manipulatedFrame()->ManipulatedFrame::wheelEvent(e, camera()); + } + else + { + manipulatedFrame()->startAction(map.action, map.withConstraint); + manipulatedFrame()->wheelEvent(e, camera()); + } + } + break; + } + } + else + e->ignore(); + } +} + +/*! Overloading of the \c QWidget method. + +The behavior of the mouse double click depends on the mouse binding. See setMouseBinding() and the +mouse page. */ +void QGLViewer::mouseDoubleClickEvent(QMouseEvent* e) +{ + //#CONNECTION# mousePressEvent has the same structure + ClickBindingPrivate cbp(e->modifiers(), e->button(), true, (Qt::MouseButtons)(e->buttons() & ~(e->button())), currentlyPressedKey_); + if (clickBinding_.contains(cbp)) + performClickAction(clickBinding_[cbp], e); + else + if (mouseGrabber()) + mouseGrabber()->mouseDoubleClickEvent(e, camera()); + else + e->ignore(); +} + +/*! Sets the state of displaysInStereo(). See also toggleStereoDisplay(). + +First checks that the display is able to handle stereovision using QGLWidget::format(). Opens a +warning message box in case of failure. Emits the stereoChanged() signal otherwise. */ +void QGLViewer::setStereoDisplay(bool stereo) +{ + if (format().stereo()) + { + stereo_ = stereo; + if (!displaysInStereo()) + { + glDrawBuffer(GL_BACK_LEFT); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDrawBuffer(GL_BACK_RIGHT); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + Q_EMIT stereoChanged(stereo_); + + update(); + } + else + if (stereo) + QMessageBox::warning(this, tr("Stereo not supported", "Message box window title"), tr("Stereo is not supported on this display.")); + else + stereo_ = false; +} + +/*! Sets the isFullScreen() state. + +If the QGLViewer is embedded in an other QWidget (see QWidget::topLevelWidget()), this widget is +displayed in full screen instead. */ +void QGLViewer::setFullScreen(bool fullScreen) +{ + if (fullScreen_ == fullScreen) return; + + fullScreen_ = fullScreen; + + QWidget* tlw = topLevelWidget(); + + if (isFullScreen()) + { + prevPos_ = topLevelWidget()->pos(); + tlw->showFullScreen(); + tlw->move(0,0); + } + else + { + tlw->showNormal(); + tlw->move(prevPos_); + } +} + +/*! Directly defines the mouseGrabber(). + +You should not call this method directly as it bypasses the +qglviewer::MouseGrabber::checkIfGrabsMouse() test performed by mouseMoveEvent(). + +If the MouseGrabber is disabled (see mouseGrabberIsEnabled()), this method silently does nothing. */ +void QGLViewer::setMouseGrabber(MouseGrabber* mouseGrabber) +{ + if (!mouseGrabberIsEnabled(mouseGrabber)) + return; + + mouseGrabber_ = mouseGrabber; + + mouseGrabberIsAManipulatedFrame_ = (dynamic_cast(mouseGrabber) != NULL); + mouseGrabberIsAManipulatedCameraFrame_ = ((dynamic_cast(mouseGrabber) != NULL) && + (mouseGrabber != camera()->frame())); + Q_EMIT mouseGrabberChanged(mouseGrabber); +} + +/*! Sets the mouseGrabberIsEnabled() state. */ +void QGLViewer::setMouseGrabberIsEnabled(const qglviewer::MouseGrabber* const mouseGrabber, bool enabled) +{ + if (enabled) + disabledMouseGrabbers_.remove(reinterpret_cast(mouseGrabber)); + else + disabledMouseGrabbers_[reinterpret_cast(mouseGrabber)]; +} + +QString QGLViewer::mouseActionString(QGLViewer::MouseAction ma) +{ + switch (ma) + { + case QGLViewer::NO_MOUSE_ACTION : return QString::null; + case QGLViewer::ROTATE : return QGLViewer::tr("Rotates", "ROTATE mouse action"); + case QGLViewer::ZOOM : return QGLViewer::tr("Zooms", "ZOOM mouse action"); + case QGLViewer::TRANSLATE : return QGLViewer::tr("Translates", "TRANSLATE mouse action"); + case QGLViewer::MOVE_FORWARD : return QGLViewer::tr("Moves forward", "MOVE_FORWARD mouse action"); + case QGLViewer::LOOK_AROUND : return QGLViewer::tr("Looks around", "LOOK_AROUND mouse action"); + case QGLViewer::MOVE_BACKWARD : return QGLViewer::tr("Moves backward", "MOVE_BACKWARD mouse action"); + case QGLViewer::SCREEN_ROTATE : return QGLViewer::tr("Rotates in screen plane", "SCREEN_ROTATE mouse action"); + case QGLViewer::ROLL : return QGLViewer::tr("Rolls", "ROLL mouse action"); + case QGLViewer::DRIVE : return QGLViewer::tr("Drives", "DRIVE mouse action"); + case QGLViewer::SCREEN_TRANSLATE : return QGLViewer::tr("Horizontally/Vertically translates", "SCREEN_TRANSLATE mouse action"); + case QGLViewer::ZOOM_ON_REGION : return QGLViewer::tr("Zooms on region for", "ZOOM_ON_REGION mouse action"); + } + return QString::null; +} + +QString QGLViewer::clickActionString(QGLViewer::ClickAction ca) +{ + switch (ca) + { + case QGLViewer::NO_CLICK_ACTION : return QString::null; + case QGLViewer::ZOOM_ON_PIXEL : return QGLViewer::tr("Zooms on pixel", "ZOOM_ON_PIXEL click action"); + case QGLViewer::ZOOM_TO_FIT : return QGLViewer::tr("Zooms to fit scene", "ZOOM_TO_FIT click action"); + case QGLViewer::SELECT : return QGLViewer::tr("Selects", "SELECT click action"); + case QGLViewer::RAP_FROM_PIXEL : return QGLViewer::tr("Sets pivot point", "RAP_FROM_PIXEL click action"); + case QGLViewer::RAP_IS_CENTER : return QGLViewer::tr("Resets pivot point", "RAP_IS_CENTER click action"); + case QGLViewer::CENTER_FRAME : return QGLViewer::tr("Centers manipulated frame", "CENTER_FRAME click action"); + case QGLViewer::CENTER_SCENE : return QGLViewer::tr("Centers scene", "CENTER_SCENE click action"); + case QGLViewer::SHOW_ENTIRE_SCENE : return QGLViewer::tr("Shows entire scene", "SHOW_ENTIRE_SCENE click action"); + case QGLViewer::ALIGN_FRAME : return QGLViewer::tr("Aligns manipulated frame", "ALIGN_FRAME click action"); + case QGLViewer::ALIGN_CAMERA : return QGLViewer::tr("Aligns camera", "ALIGN_CAMERA click action"); + } + return QString::null; +} + +static QString keyString(unsigned int key) +{ +# if QT_VERSION >= 0x040100 + return QKeySequence(int(key)).toString(QKeySequence::NativeText); +# else + return QString(QKeySequence(key)); +# endif +} + +QString QGLViewer::formatClickActionPrivate(ClickBindingPrivate cbp) +{ + bool buttonsBefore = cbp.buttonsBefore != Qt::NoButton; + QString keyModifierString = keyString(cbp.modifiers + cbp.key); + if (!keyModifierString.isEmpty()) { +#ifdef Q_OS_MAC + // modifiers never has a '+' sign. Add one space to clearly separate modifiers (and possible key) from button + keyModifierString += " "; +#else + // modifiers might be of the form : 'S' or 'Ctrl+S' or 'Ctrl+'. For consistency, add an other '+' if needed, no spaces + if (!keyModifierString.endsWith('+')) + keyModifierString += "+"; +#endif + } + + return tr("%1%2%3%4%5%6", "Modifier / button or wheel / double click / with / button / pressed") + .arg(keyModifierString) + .arg(mouseButtonsString(cbp.button)+(cbp.button == Qt::NoButton ? tr("Wheel", "Mouse wheel") : "")) + .arg(cbp.doubleClick ? tr(" double click", "Suffix after mouse button") : "") + .arg(buttonsBefore ? tr(" with ", "As in : Left button with Ctrl pressed") : "") + .arg(buttonsBefore ? mouseButtonsString(cbp.buttonsBefore) : "") + .arg(buttonsBefore ? tr(" pressed", "As in : Left button with Ctrl pressed") : ""); +} + +bool QGLViewer::isValidShortcutKey(int key) { + return (key >= Qt::Key_Any && key < Qt::Key_Escape) || (key >= Qt::Key_F1 && key <= Qt::Key_F35); +} + +#ifndef DOXYGEN +/*! This method is deprecated since version 2.5.0 + + Use setMouseBindingDescription(Qt::KeyboardModifiers, Qt::MouseButtons, QString, bool, Qt::MouseButtons) instead. +*/ +void QGLViewer::setMouseBindingDescription(unsigned int state, QString description, bool doubleClick, Qt::MouseButtons buttonsBefore) { + qWarning("setMouseBindingDescription(int state,...) is deprecated. Use the modifier/button equivalent"); + setMouseBindingDescription(keyboardModifiersFromState(state), + mouseButtonFromState(state), + description, + doubleClick, + buttonsBefore); +} +#endif + +/*! Defines a custom mouse binding description, displayed in the help() window's Mouse tab. + + Same as calling setMouseBindingDescription(Qt::Key, Qt::KeyboardModifiers, Qt::MouseButton, QString, bool, Qt::MouseButtons), + with a key value of Qt::Key(0) (i.e. binding description when no regular key needs to be pressed). */ +void QGLViewer::setMouseBindingDescription(Qt::KeyboardModifiers modifiers, Qt::MouseButton button, QString description, bool doubleClick, Qt::MouseButtons buttonsBefore) +{ + setMouseBindingDescription(Qt::Key(0), modifiers, button, description, doubleClick, buttonsBefore); +} + +/*! Defines a custom mouse binding description, displayed in the help() window's Mouse tab. + +\p modifiers is a combination of Qt::KeyboardModifiers (\c Qt::ControlModifier, \c Qt::AltModifier, \c +Qt::ShiftModifier, \c Qt::MetaModifier). Possibly combined using the \c "|" operator. + +\p button is one of the Qt::MouseButtons (\c Qt::LeftButton, \c Qt::MidButton, +\c Qt::RightButton...). + +\p doubleClick indicates whether or not the user has to double click this button to perform the +described action. \p buttonsBefore lists the buttons that need to be pressed before the double click. + +Set an empty \p description to \e remove a mouse binding description. + +\code +// The R key combined with the Left mouse button rotates the camera in the screen plane. +setMouseBindingDescription(Qt::Key_R, Qt::NoModifier, Qt::LeftButton, "Rotates camera in screen plane"); + +// A left button double click toggles full screen +setMouseBindingDescription(Qt::NoModifier, Qt::LeftButton, "Toggles full screen mode", true); + +// Removes the description of Ctrl+Right button +setMouseBindingDescription(Qt::ControlModifier, Qt::RightButton, ""); +\endcode + +Overload mouseMoveEvent() and friends to implement your custom mouse behavior (see the +mouseMoveEvent() documentation for an example). See the keyboardAndMouse example for an illustration. + +Use setMouseBinding() and setWheelBinding() to change the standard mouse action bindings. */ +void QGLViewer::setMouseBindingDescription(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button, QString description, bool doubleClick, Qt::MouseButtons buttonsBefore) +{ + ClickBindingPrivate cbp(modifiers, button, doubleClick, buttonsBefore, key); + + if (description.isEmpty()) + mouseDescription_.remove(cbp); + else + mouseDescription_[cbp] = description; +} + +static QString tableLine(const QString& left, const QString& right) +{ + static bool even = false; + const QString tdtd(""); + const QString tdtr("\n"); + + QString res(""; + else + res += "#ffffff\">"; + res += "" + left + tdtd + right + tdtr; + even = !even; + + return res; +} + +/*! Returns a QString that describes the application mouse bindings, displayed in the help() window +\c Mouse tab. + +Result is a table that describes custom application mouse binding descriptions defined using +setMouseBindingDescription() as well as standard mouse bindings (defined using setMouseBinding() +and setWheelBinding()). See the mouse page for details on mouse +bindings. + +See also helpString() and keyboardString(). */ +QString QGLViewer::mouseString() const +{ + QString text("
\n"); + const QString trtd("\n"); + const QString tdtd("\n"). + arg(tr("Button(s)", "Buttons column header in help window mouse tab")).arg(tr("Description", "Description column header in help window mouse tab")); + + QMap mouseBinding; + + // User-defined mouse bindings come first. + for (QMap::ConstIterator itm=mouseDescription_.begin(), endm=mouseDescription_.end(); itm!=endm; ++itm) + mouseBinding[itm.key()] = itm.value(); + + for (QMap::ConstIterator it=mouseBinding.begin(), end=mouseBinding.end(); it != end; ++it) + { + // Should not be needed (see setMouseBindingDescription()) + if (it.value().isNull()) + continue; + + text += tableLine(formatClickActionPrivate(it.key()), it.value()); + } + + // Optional separator line + if (!mouseBinding.isEmpty()) + { + mouseBinding.clear(); + text += QString("\n").arg(tr("Standard mouse bindings", "In help window mouse tab")); + } + + // Then concatenates the descriptions of wheelBinding_, mouseBinding_ and clickBinding_. + // The order is significant and corresponds to the priorities set in mousePressEvent() (reverse priority order, last one overwrites previous) + // #CONNECTION# mousePressEvent() order + for (QMap::ConstIterator itmb=mouseBinding_.begin(), endmb=mouseBinding_.end(); + itmb != endmb; ++itmb) + { + ClickBindingPrivate cbp(itmb.key().modifiers, itmb.key().button, false, Qt::NoButton, itmb.key().key); + + QString text = mouseActionString(itmb.value().action); + + if (!text.isNull()) + { + switch (itmb.value().handler) + { + case CAMERA: text += " " + tr("camera", "Suffix after action"); break; + case FRAME: text += " " + tr("manipulated frame", "Suffix after action"); break; + } + if (!(itmb.value().withConstraint)) + text += "*"; + } + mouseBinding[cbp] = text; + } + + for (QMap::ConstIterator itw=wheelBinding_.begin(), endw=wheelBinding_.end(); itw != endw; ++itw) + { + ClickBindingPrivate cbp(itw.key().modifiers, Qt::NoButton, false, Qt::NoButton, itw.key().key); + + QString text = mouseActionString(itw.value().action); + + if (!text.isNull()) + { + switch (itw.value().handler) + { + case CAMERA: text += " " + tr("camera", "Suffix after action"); break; + case FRAME: text += " " + tr("manipulated frame", "Suffix after action"); break; + } + if (!(itw.value().withConstraint)) + text += "*"; + } + + mouseBinding[cbp] = text; + } + + for (QMap::ConstIterator itcb=clickBinding_.begin(), endcb=clickBinding_.end(); itcb!=endcb; ++itcb) + mouseBinding[itcb.key()] = clickActionString(itcb.value()); + + for (QMap::ConstIterator it2=mouseBinding.begin(), end2=mouseBinding.end(); it2 != end2; ++it2) + { + if (it2.value().isNull()) + continue; + + text += tableLine(formatClickActionPrivate(it2.key()), it2.value()); + } + + text += "
"); + const QString tdtr("
"); + + text += QString("
%1%2
%1
"; + + return text; +} + +/*! Defines a custom keyboard shortcut description, that will be displayed in the help() window \c +Keyboard tab. + +The \p key definition is given as an \c int using Qt enumerated values. Set an empty \p description +to remove a shortcut description: +\code +setKeyDescription(Qt::Key_W, "Toggles wireframe display"); +setKeyDescription(Qt::CTRL+Qt::Key_L, "Loads a new scene"); +// Removes a description +setKeyDescription(Qt::CTRL+Qt::Key_C, ""); +\endcode + +See the keyboardAndMouse example for illustration +and the keyboard page for details. */ +void QGLViewer::setKeyDescription(unsigned int key, QString description) +{ + if (description.isEmpty()) + keyDescription_.remove(key); + else + keyDescription_[key] = description; +} + +QString QGLViewer::cameraPathKeysString() const +{ + if (pathIndex_.isEmpty()) + return QString::null; + + QVector keys; + keys.reserve(pathIndex_.count()); + for (QMap::ConstIterator i = pathIndex_.begin(), endi=pathIndex_.end(); i != endi; ++i) + keys.push_back(i.key()); + qSort(keys); + + QVector::const_iterator it = keys.begin(), end = keys.end(); + QString res = keyString(*it); + + const int maxDisplayedKeys = 6; + int nbDisplayedKeys = 0; + Qt::Key previousKey = (*it); + int state = 0; + ++it; + while ((it != end) && (nbDisplayedKeys < maxDisplayedKeys-1)) + { + switch (state) + { + case 0 : + if ((*it) == previousKey + 1) + state++; + else + { + res += ", " + keyString(*it); + nbDisplayedKeys++; + } + break; + case 1 : + if ((*it) == previousKey + 1) + state++; + else + { + res += ", " + keyString(previousKey); + res += ", " + keyString(*it); + nbDisplayedKeys += 2; + state = 0; + } + break; + default : + if ((*it) != previousKey + 1) + { + res += ".." + keyString(previousKey); + res += ", " + keyString(*it); + nbDisplayedKeys += 2; + state = 0; + } + break; + } + previousKey = *it; + ++it; + } + + if (state == 1) + res += ", " + keyString(previousKey); + if (state == 2) + res += ".." + keyString(previousKey); + if (it != end) + res += "..."; + + return res; +} + +/*! Returns a QString that describes the application keyboard shortcut bindings, and that will be +displayed in the help() window \c Keyboard tab. + +Default value is a table that describes the custom shortcuts defined using setKeyDescription() as +well as the \e standard QGLViewer::KeyboardAction shortcuts (defined using setShortcut()). See the +keyboard page for details on key customization. + +See also helpString() and mouseString(). */ +QString QGLViewer::keyboardString() const +{ + QString text("
\n"); + text += QString("\n"). + arg(QGLViewer::tr("Key(s)", "Keys column header in help window mouse tab")).arg(QGLViewer::tr("Description", "Description column header in help window mouse tab")); + + QMap keyDescription; + + // 1 - User defined key descriptions + for (QMap::ConstIterator kd=keyDescription_.begin(), kdend=keyDescription_.end(); kd!=kdend; ++kd) + keyDescription[kd.key()] = kd.value(); + + // Add to text in sorted order + for (QMap::ConstIterator kb=keyDescription.begin(), endb=keyDescription.end(); kb!=endb; ++kb) + text += tableLine(keyString(kb.key()), kb.value()); + + + // 2 - Optional separator line + if (!keyDescription.isEmpty()) + { + keyDescription.clear(); + text += QString("\n").arg(QGLViewer::tr("Standard viewer keys", "In help window keys tab")); + } + + + // 3 - KeyboardAction bindings description + for (QMap::ConstIterator it=keyboardBinding_.begin(), end=keyboardBinding_.end(); it != end; ++it) + if ((it.value() != 0) && ((!cameraIsInRotateMode()) || ((it.key() != INCREASE_FLYSPEED) && (it.key() != DECREASE_FLYSPEED)))) + keyDescription[it.value()] = keyboardActionDescription_[it.key()]; + + // Add to text in sorted order + for (QMap::ConstIterator kb2=keyDescription.begin(), endb2=keyDescription.end(); kb2!=endb2; ++kb2) + text += tableLine(keyString(kb2.key()), kb2.value()); + + + // 4 - Camera paths keys description + const QString cpks = cameraPathKeysString(); + if (!cpks.isNull()) + { + text += "\n"; + text += tableLine(keyString(playPathKeyboardModifiers()) + "" + QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "", + QGLViewer::tr("Plays path (or resets saved position)")); + text += tableLine(keyString(addKeyFrameKeyboardModifiers()) + "" + QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "", + QGLViewer::tr("Adds a key frame to path (or defines a position)")); + text += tableLine(keyString(addKeyFrameKeyboardModifiers()) + "" + QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "+" + QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "", + QGLViewer::tr("Deletes path (or saved position)")); + } + text += "
%1%2
%1
\n"; + text += QGLViewer::tr("Camera paths are controlled using the %1 keys (noted Fx below):", "Help window key tab camera keys").arg(cpks) + "
"; + + return text; +} + +/*! Displays the help window "About" tab. See help() for details. */ +void QGLViewer::aboutQGLViewer() { + help(); + helpWidget()->setCurrentIndex(3); +} + + +/*! Opens a modal help window that includes four tabs, respectively filled with helpString(), +keyboardString(), mouseString() and about libQGLViewer. + +Rich html-like text can be used (see the QStyleSheet documentation). This method is called when the +user presses the QGLViewer::HELP key (default is 'H'). + +You can use helpWidget() to access to the help widget (to add/remove tabs, change layout...). + +The helpRequired() signal is emitted. */ +void QGLViewer::help() +{ + Q_EMIT helpRequired(); + + bool resize = false; + int width=600; + int height=400; + + static QString label[] = {tr("&Help", "Help window tab title"), tr("&Keyboard", "Help window tab title"), tr("&Mouse", "Help window tab title"), tr("&About", "Help window about title")}; + + if (!helpWidget()) + { + // Qt4 requires a NULL parent... + helpWidget_ = new QTabWidget(NULL); + helpWidget()->setWindowTitle(tr("Help", "Help window title")); + + resize = true; + for (int i=0; i<4; ++i) + { + QTextEdit* tab = new QTextEdit(NULL); + tab->setReadOnly(true); + + helpWidget()->insertTab(i, tab, label[i]); + if (i==3) { +# include "qglviewer-icon.xpm" + QPixmap pixmap(qglviewer_icon); + tab->document()->addResource(QTextDocument::ImageResource, + QUrl("mydata://qglviewer-icon.xpm"), QVariant(pixmap)); + } + } + } + + for (int i=0; i<4; ++i) + { + QString text; + switch (i) + { + case 0 : text = helpString(); break; + case 1 : text = keyboardString(); break; + case 2 : text = mouseString(); break; + case 3 : text = QString("

") + tr( + "

libQGLViewer

" + "

Version %1


" + "A versatile 3D viewer based on OpenGL and Qt
" + "Copyright 2002-%2 Gilles Debunne
" + "%3").arg(QGLViewerVersionString()).arg("2014").arg("http://www.libqglviewer.com") + + QString("
"); + break; + default : break; + } + + QTextEdit* textEdit = (QTextEdit*)(helpWidget()->widget(i)); + textEdit->setHtml(text); + textEdit->setText(text); + + if (resize && (textEdit->height() > height)) + height = textEdit->height(); + } + + if (resize) + helpWidget()->resize(width, height+40); // 40 pixels is ~ tabs' height + helpWidget()->show(); + helpWidget()->raise(); +} + +/*! Overloading of the \c QWidget method. + +Default keyboard shortcuts are defined using setShortcut(). Overload this method to implement a +specific keyboard binding. Call the original method if you do not catch the event to preserve the +viewer default key bindings: +\code +void Viewer::keyPressEvent(QKeyEvent *e) +{ + // Defines the Alt+R shortcut. + if ((e->key() == Qt::Key_R) && (e->modifiers() == Qt::AltModifier)) + { + myResetFunction(); + update(); // Refresh display + } + else + QGLViewer::keyPressEvent(e); +} + +// With Qt 2 or 3, you would retrieve modifiers keys using : +// const Qt::ButtonState modifiers = (Qt::ButtonState)(e->state() & Qt::KeyButtonMask); +\endcode +When you define a new keyboard shortcut, use setKeyDescription() to provide a short description +which is displayed in the help() window Keyboard tab. See the keyboardAndMouse example for an illustration. + +See also QGLWidget::keyReleaseEvent(). */ +void QGLViewer::keyPressEvent(QKeyEvent *e) +{ + if (e->key() == 0) + { + e->ignore(); + return; + } + + const Qt::Key key = Qt::Key(e->key()); + + const Qt::KeyboardModifiers modifiers = e->modifiers(); + + QMap::ConstIterator it=keyboardBinding_.begin(), end=keyboardBinding_.end(); + const unsigned int target = key | modifiers; + while ((it != end) && (it.value() != target)) + ++it; + + if (it != end) + handleKeyboardAction(it.key()); + else + if (pathIndex_.contains(Qt::Key(key))) + { + // Camera paths + unsigned int index = pathIndex_[Qt::Key(key)]; + + // not safe, but try to double press on two viewers at the same time ! + static QTime doublePress; + + if (modifiers == playPathKeyboardModifiers()) + { + int elapsed = doublePress.restart(); + if ((elapsed < 250) && (index==previousPathId_)) + camera()->resetPath(index); + else + { + // Stop previous interpolation before starting a new one. + if (index != previousPathId_) + { + KeyFrameInterpolator* previous = camera()->keyFrameInterpolator(previousPathId_); + if ((previous) && (previous->interpolationIsStarted())) + previous->resetInterpolation(); + } + camera()->playPath(index); + } + previousPathId_ = index; + } + else if (modifiers == addKeyFrameKeyboardModifiers()) + { + int elapsed = doublePress.restart(); + if ((elapsed < 250) && (index==previousPathId_)) + { + if (camera()->keyFrameInterpolator(index)) + { + disconnect(camera()->keyFrameInterpolator(index), SIGNAL(interpolated()), this, SLOT(update())); + if (camera()->keyFrameInterpolator(index)->numberOfKeyFrames() > 1) + displayMessage(tr("Path %1 deleted", "Feedback message").arg(index)); + else + displayMessage(tr("Position %1 deleted", "Feedback message").arg(index)); + camera()->deletePath(index); + } + } + else + { + bool nullBefore = (camera()->keyFrameInterpolator(index) == NULL); + camera()->addKeyFrameToPath(index); + if (nullBefore) + connect(camera()->keyFrameInterpolator(index), SIGNAL(interpolated()), SLOT(update())); + int nbKF = camera()->keyFrameInterpolator(index)->numberOfKeyFrames(); + if (nbKF > 1) + displayMessage(tr("Path %1, position %2 added", "Feedback message").arg(index).arg(nbKF)); + else + displayMessage(tr("Position %1 saved", "Feedback message").arg(index)); + } + previousPathId_ = index; + } + update(); + } else { + if (isValidShortcutKey(key)) currentlyPressedKey_ = key; + e->ignore(); + } +} + +void QGLViewer::keyReleaseEvent(QKeyEvent * e) { + if (isValidShortcutKey(e->key())) currentlyPressedKey_ = Qt::Key(0); +} + +void QGLViewer::handleKeyboardAction(KeyboardAction id) +{ + switch (id) + { + case DRAW_AXIS : toggleAxisIsDrawn(); break; + case DRAW_GRID : toggleGridIsDrawn(); break; + case DISPLAY_FPS : toggleFPSIsDisplayed(); break; + case ENABLE_TEXT : toggleTextIsEnabled(); break; + case EXIT_VIEWER : saveStateToFileForAllViewers(); qApp->closeAllWindows(); break; + case SAVE_SCREENSHOT : saveSnapshot(false, false); break; + case FULL_SCREEN : toggleFullScreen(); break; + case STEREO : toggleStereoDisplay(); break; + case ANIMATION : toggleAnimation(); break; + case HELP : help(); break; + case EDIT_CAMERA : toggleCameraIsEdited(); break; + case SNAPSHOT_TO_CLIPBOARD : snapshotToClipboard(); break; + case CAMERA_MODE : + toggleCameraMode(); + displayMessage(cameraIsInRotateMode()?tr("Camera in observer mode", "Feedback message"):tr("Camera in fly mode", "Feedback message")); + break; + + case MOVE_CAMERA_LEFT : + camera()->frame()->translate(camera()->frame()->inverseTransformOf(Vec(-10.0*camera()->flySpeed(), 0.0, 0.0))); + update(); + break; + case MOVE_CAMERA_RIGHT : + camera()->frame()->translate(camera()->frame()->inverseTransformOf(Vec( 10.0*camera()->flySpeed(), 0.0, 0.0))); + update(); + break; + case MOVE_CAMERA_UP : + camera()->frame()->translate(camera()->frame()->inverseTransformOf(Vec(0.0, 10.0*camera()->flySpeed(), 0.0))); + update(); + break; + case MOVE_CAMERA_DOWN : + camera()->frame()->translate(camera()->frame()->inverseTransformOf(Vec(0.0, -10.0*camera()->flySpeed(), 0.0))); + update(); + break; + + case INCREASE_FLYSPEED : camera()->setFlySpeed(camera()->flySpeed() * 1.5); break; + case DECREASE_FLYSPEED : camera()->setFlySpeed(camera()->flySpeed() / 1.5); break; + } +} + +/*! Callback method used when the widget size is modified. + +If you overload this method, first call the inherited method. Also called when the widget is +created, before its first display. */ +void QGLViewer::resizeGL(int width, int height) +{ + QOpenGLWidget::resizeGL(width, height); + glViewport( 0, 0, GLint(width), GLint(height) ); + camera()->setScreenWidthAndHeight(this->width(), this->height()); +} + +////////////////////////////////////////////////////////////////////////// +// K e y b o a r d s h o r t c u t s // +////////////////////////////////////////////////////////////////////////// + +/*! Defines the shortcut() that triggers a given QGLViewer::KeyboardAction. + +Here are some examples: +\code +// Press 'Q' to exit application +setShortcut(EXIT_VIEWER, Qt::Key_Q); + +// Alt+M toggles camera mode +setShortcut(CAMERA_MODE, Qt::ALT + Qt::Key_M); + +// The DISPLAY_FPS action is disabled +setShortcut(DISPLAY_FPS, 0); +\endcode + +Only one shortcut can be assigned to a given QGLViewer::KeyboardAction (new bindings replace +previous ones). If several KeyboardAction are binded to the same shortcut, only one of them is +active. */ +void QGLViewer::setShortcut(KeyboardAction action, unsigned int key) +{ + keyboardBinding_[action] = key; +} + +/*! Returns the keyboard shortcut associated to a given QGLViewer::KeyboardAction. + +Result is an \c unsigned \c int defined using Qt enumerated values, as in \c Qt::Key_Q or +\c Qt::CTRL + Qt::Key_X. Use Qt::MODIFIER_MASK to separate the key from the state keys. Returns \c 0 if +the KeyboardAction is disabled (not binded). Set using setShortcut(). + +If you want to define keyboard shortcuts for custom actions (say, open a scene file), overload +keyPressEvent() and then setKeyDescription(). + +These shortcuts and their descriptions are automatically included in the help() window \c Keyboard +tab. + +See the keyboard page for details and default values and the keyboardAndMouse example for a practical +illustration. */ +unsigned int QGLViewer::shortcut(KeyboardAction action) const +{ + if (keyboardBinding_.contains(action)) + return keyboardBinding_[action]; + else + return 0; +} + +#ifndef DOXYGEN +void QGLViewer::setKeyboardAccelerator(KeyboardAction action, unsigned int key) +{ + qWarning("setKeyboardAccelerator is deprecated. Use setShortcut instead."); + setShortcut(action, key); +} + +unsigned int QGLViewer::keyboardAccelerator(KeyboardAction action) const +{ + qWarning("keyboardAccelerator is deprecated. Use shortcut instead."); + return shortcut(action); +} +#endif + +/////// Key Frames associated keys /////// + +/*! Returns the keyboard key associated to camera Key Frame path \p index. + +Default values are F1..F12 for indexes 1..12. + +addKeyFrameKeyboardModifiers() (resp. playPathKeyboardModifiers()) define the state key(s) that +must be pressed with this key to add a KeyFrame to (resp. to play) the associated Key Frame path. +If you quickly press twice the pathKey(), the path is reset (resp. deleted). + +Use camera()->keyFrameInterpolator( \p index ) to retrieve the KeyFrameInterpolator that defines +the path. + +If several keys are binded to a given \p index (see setPathKey()), one of them is returned. +Returns \c 0 if no key is associated with this index. + +See also the keyboard page. */ +Qt::Key QGLViewer::pathKey(unsigned int index) const +{ + for (QMap::ConstIterator it = pathIndex_.begin(), end=pathIndex_.end(); it != end; ++it) + if (it.value() == index) + return it.key(); + return Qt::Key(0); +} + +/*! Sets the pathKey() associated with the camera Key Frame path \p index. + +Several keys can be binded to the same \p index. Use a negated \p key value to delete the binding +(the \p index value is then ignored): +\code +// Press 'space' to play/pause/add/delete camera path of index 0. +setPathKey(Qt::Key_Space, 0); + +// Remove this binding +setPathKey(-Qt::Key_Space); +\endcode */ +void QGLViewer::setPathKey(int key, unsigned int index) +{ + Qt::Key k = Qt::Key(abs(key)); + if (key < 0) + pathIndex_.remove(k); + else + pathIndex_[k] = index; +} + +/*! Sets the playPathKeyboardModifiers() value. */ +void QGLViewer::setPlayPathKeyboardModifiers(Qt::KeyboardModifiers modifiers) +{ + playPathKeyboardModifiers_ = modifiers; +} + +/*! Sets the addKeyFrameKeyboardModifiers() value. */ +void QGLViewer::setAddKeyFrameKeyboardModifiers(Qt::KeyboardModifiers modifiers) +{ + addKeyFrameKeyboardModifiers_ = modifiers; +} + +/*! Returns the keyboard modifiers that must be pressed with a pathKey() to add the current camera +position to a KeyFrame path. + +It can be \c Qt::NoModifier, \c Qt::ControlModifier, \c Qt::ShiftModifier, \c Qt::AltModifier, \c +Qt::MetaModifier or a combination of these (using the bitwise '|' operator). + +Default value is Qt::AltModifier. Defined using setAddKeyFrameKeyboardModifiers(). + +See also playPathKeyboardModifiers(). */ +Qt::KeyboardModifiers QGLViewer::addKeyFrameKeyboardModifiers() const +{ + return addKeyFrameKeyboardModifiers_; +} + +/*! Returns the keyboard modifiers that must be pressed with a pathKey() to play a camera KeyFrame path. + +It can be \c Qt::NoModifier, \c Qt::ControlModifier, \c Qt::ShiftModifier, \c Qt::AltModifier, \c +Qt::MetaModifier or a combination of these (using the bitwise '|' operator). + +Default value is Qt::NoModifier. Defined using setPlayPathKeyboardModifiers(). + +See also addKeyFrameKeyboardModifiers(). */ +Qt::KeyboardModifiers QGLViewer::playPathKeyboardModifiers() const +{ + return playPathKeyboardModifiers_; +} + +#ifndef DOXYGEN +// Deprecated methods +Qt::KeyboardModifiers QGLViewer::addKeyFrameStateKey() const +{ + qWarning("addKeyFrameStateKey has been renamed addKeyFrameKeyboardModifiers"); + return addKeyFrameKeyboardModifiers(); } + +Qt::KeyboardModifiers QGLViewer::playPathStateKey() const +{ + qWarning("playPathStateKey has been renamed playPathKeyboardModifiers"); + return playPathKeyboardModifiers(); +} + +void QGLViewer::setAddKeyFrameStateKey(unsigned int buttonState) +{ + qWarning("setAddKeyFrameStateKey has been renamed setAddKeyFrameKeyboardModifiers"); + setAddKeyFrameKeyboardModifiers(keyboardModifiersFromState(buttonState)); +} + +void QGLViewer::setPlayPathStateKey(unsigned int buttonState) +{ + qWarning("setPlayPathStateKey has been renamed setPlayPathKeyboardModifiers"); + setPlayPathKeyboardModifiers(keyboardModifiersFromState(buttonState)); +} + +Qt::Key QGLViewer::keyFrameKey(unsigned int index) const +{ + qWarning("keyFrameKey has been renamed pathKey."); + return pathKey(index); +} + +Qt::KeyboardModifiers QGLViewer::playKeyFramePathStateKey() const +{ + qWarning("playKeyFramePathStateKey has been renamed playPathKeyboardModifiers."); + return playPathKeyboardModifiers(); +} + +void QGLViewer::setKeyFrameKey(unsigned int index, int key) +{ + qWarning("setKeyFrameKey is deprecated, use setPathKey instead, with swapped parameters."); + setPathKey(key, index); +} + +void QGLViewer::setPlayKeyFramePathStateKey(unsigned int buttonState) +{ + qWarning("setPlayKeyFramePathStateKey has been renamed setPlayPathKeyboardModifiers."); + setPlayPathKeyboardModifiers(keyboardModifiersFromState(buttonState)); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// M o u s e b e h a v i o r s t a t e k e y s // +//////////////////////////////////////////////////////////////////////////////// +#ifndef DOXYGEN +/*! This method has been deprecated since version 2.5.0 + +Associates keyboard modifiers to MouseHandler \p handler. + +The \p modifiers parameter is \c Qt::AltModifier, \c Qt::ShiftModifier, \c Qt::ControlModifier, \c +Qt::MetaModifier or a combination of these using the '|' bitwise operator. + +\e All the \p handler's associated bindings will then need the specified \p modifiers key(s) to be +activated. + +With this code, +\code +setHandlerKeyboardModifiers(QGLViewer::CAMERA, Qt::AltModifier); +setHandlerKeyboardModifiers(QGLViewer::FRAME, Qt::NoModifier); +\endcode +you will have to press the \c Alt key while pressing mouse buttons in order to move the camera(), +while no key will be needed to move the manipulatedFrame(). + +This method has a very basic implementation: every action binded to \p handler has its keyboard +modifier replaced by \p modifiers. If \p handler had some actions binded to different modifiers, +these settings will be lost. You should hence consider using setMouseBinding() for finer tuning. + +The default binding associates \c Qt::ControlModifier to all the QGLViewer::FRAME actions and \c +Qt::NoModifier to all QGLViewer::CAMERA actions. See mouse page for +details. + +\attention This method calls setMouseBinding(), which ensures that only one action is binded to a +given modifiers. If you want to \e swap the QGLViewer::CAMERA and QGLViewer::FRAME keyboard +modifiers, you have to use a temporary dummy modifier (as if you were swapping two variables) or +else the first call will overwrite the previous settings: +\code +// Associate FRAME with Alt (temporary value) +setHandlerKeyboardModifiers(QGLViewer::FRAME, Qt::AltModifier); +// Control is associated with CAMERA +setHandlerKeyboardModifiers(QGLViewer::CAMERA, Qt::ControlModifier); +// And finally, FRAME can be associated with NoModifier +setHandlerKeyboardModifiers(QGLViewer::FRAME, Qt::NoModifier); +\endcode */ +void QGLViewer::setHandlerKeyboardModifiers(MouseHandler handler, Qt::KeyboardModifiers modifiers) +{ + qWarning("setHandlerKeyboardModifiers is deprecated, call setMouseBinding() instead"); + + QMap newMouseBinding; + QMap newWheelBinding; + QMap newClickBinding_; + + QMap::Iterator mit; + QMap::Iterator wit; + + // First copy unchanged bindings. + for (mit = mouseBinding_.begin(); mit != mouseBinding_.end(); ++mit) + if ((mit.value().handler != handler) || (mit.value().action == ZOOM_ON_REGION)) + newMouseBinding[mit.key()] = mit.value(); + + for (wit = wheelBinding_.begin(); wit != wheelBinding_.end(); ++wit) + if (wit.value().handler != handler) + newWheelBinding[wit.key()] = wit.value(); + + // Then, add modified bindings, that can overwrite the previous ones. + for (mit = mouseBinding_.begin(); mit != mouseBinding_.end(); ++mit) + if ((mit.value().handler == handler) && (mit.value().action != ZOOM_ON_REGION)) + { + MouseBindingPrivate mbp(modifiers, mit.key().button, mit.key().key); + newMouseBinding[mbp] = mit.value(); + } + + for (wit = wheelBinding_.begin(); wit != wheelBinding_.end(); ++wit) + if (wit.value().handler == handler) + { + WheelBindingPrivate wbp(modifiers, wit.key().key); + newWheelBinding[wbp] = wit.value(); + } + + // Same for button bindings + for (QMap::ConstIterator cb=clickBinding_.begin(), end=clickBinding_.end(); cb != end; ++cb) + if (((handler==CAMERA) && ((cb.value() == CENTER_SCENE) || (cb.value() == ALIGN_CAMERA))) || + ((handler==FRAME) && ((cb.value() == CENTER_FRAME) || (cb.value() == ALIGN_FRAME)))) + { + ClickBindingPrivate cbp(modifiers, cb.key().button, cb.key().doubleClick, cb.key().buttonsBefore, cb.key().key); + newClickBinding_[cbp] = cb.value(); + } + else + newClickBinding_[cb.key()] = cb.value(); + + mouseBinding_ = newMouseBinding; + wheelBinding_ = newWheelBinding; + clickBinding_ = newClickBinding_; +} + +void QGLViewer::setHandlerStateKey(MouseHandler handler, unsigned int buttonState) +{ + qWarning("setHandlerStateKey has been renamed setHandlerKeyboardModifiers"); + setHandlerKeyboardModifiers(handler, keyboardModifiersFromState(buttonState)); +} + +void QGLViewer::setMouseStateKey(MouseHandler handler, unsigned int buttonState) +{ + qWarning("setMouseStateKey has been renamed setHandlerKeyboardModifiers."); + setHandlerKeyboardModifiers(handler, keyboardModifiersFromState(buttonState)); +} + +/*! This method is deprecated since version 2.5.0 + + Use setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButtons, MouseHandler, MouseAction, bool) instead. +*/ +void QGLViewer::setMouseBinding(unsigned int state, MouseHandler handler, MouseAction action, bool withConstraint) +{ + qWarning("setMouseBinding(int state, MouseHandler...) is deprecated. Use the modifier/button equivalent"); + setMouseBinding(keyboardModifiersFromState(state), + mouseButtonFromState(state), + handler, + action, + withConstraint); +} +#endif + +/*! Defines a MouseAction binding. + + Same as calling setMouseBinding(Qt::Key, Qt::KeyboardModifiers, Qt::MouseButton, MouseHandler, MouseAction, bool), + with a key value of Qt::Key(0) (i.e. no regular extra key needs to be pressed to perform this action). */ +void QGLViewer::setMouseBinding(Qt::KeyboardModifiers modifiers, Qt::MouseButton button, MouseHandler handler, MouseAction action, bool withConstraint) { + setMouseBinding(Qt::Key(0), modifiers, button, handler, action, withConstraint); +} + +/*! Associates a MouseAction to any mouse \p button, while keyboard \p modifiers and \p key are pressed. +The receiver of the mouse events is a MouseHandler (QGLViewer::CAMERA or QGLViewer::FRAME). + +The parameters should read: when the mouse \p button is pressed, while the keyboard \p modifiers and \p key are down, +activate \p action on \p handler. Use Qt::NoModifier to indicate that no modifier +key is needed, and a \p key value of 0 if no regular key has to be pressed +(or simply use setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButton, MouseHandler, MouseAction, bool)). + +Use the '|' operator to combine modifiers: +\code +// The R key combined with the Left mouse button rotates the camera in the screen plane. +setMouseBinding(Qt::Key_R, Qt::NoModifier, Qt::LeftButton, CAMERA, SCREEN_ROTATE); + +// Alt + Shift and Left button rotates the manipulatedFrame(). +setMouseBinding(Qt::AltModifier | Qt::ShiftModifier, Qt::LeftButton, FRAME, ROTATE); +\endcode + +If \p withConstraint is \c true (default), the possible +qglviewer::Frame::constraint() of the associated Frame will be enforced during motion. + +The list of all possible MouseAction, some binding examples and default bindings are provided in +the mouse page. + +See the keyboardAndMouse example for an illustration. + +If no mouse button is specified, the binding is ignored. If an action was previously +associated with this keyboard and button combination, it is silently overwritten (call mouseAction() +before to check). + +To remove a specific mouse binding, use \p NO_MOUSE_ACTION as the \p action. + +See also setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButtons, ClickAction, bool, int), setWheelBinding() and clearMouseBindings(). */ +void QGLViewer::setMouseBinding(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button, MouseHandler handler, MouseAction action, bool withConstraint) +{ + if ((handler == FRAME) && ((action == MOVE_FORWARD) || (action == MOVE_BACKWARD) || + (action == ROLL) || (action == LOOK_AROUND) || + (action == ZOOM_ON_REGION))) { + qWarning("Cannot bind %s to FRAME", mouseActionString(action).toLatin1().constData()); + return; + } + + if (button == Qt::NoButton) { + qWarning("No mouse button specified in setMouseBinding"); + return; + } + + MouseActionPrivate map; + map.handler = handler; + map.action = action; + map.withConstraint = withConstraint; + + MouseBindingPrivate mbp(modifiers, button, key); + if (action == NO_MOUSE_ACTION) + mouseBinding_.remove(mbp); + else + mouseBinding_.insert(mbp, map); + + ClickBindingPrivate cbp(modifiers, button, false, Qt::NoButton, key); + clickBinding_.remove(cbp); +} + +#ifndef DOXYGEN +/*! This method is deprecated since version 2.5.0 + + Use setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButtons, MouseHandler, MouseAction, bool) instead. +*/ +void QGLViewer::setMouseBinding(unsigned int state, ClickAction action, bool doubleClick, Qt::MouseButtons buttonsBefore) { + qWarning("setMouseBinding(int state, ClickAction...) is deprecated. Use the modifier/button equivalent"); + setMouseBinding(keyboardModifiersFromState(state), + mouseButtonFromState(state), + action, + doubleClick, + buttonsBefore); +} +#endif + +/*! Defines a ClickAction binding. + + Same as calling setMouseBinding(Qt::Key, Qt::KeyboardModifiers, Qt::MouseButton, ClickAction, bool, Qt::MouseButtons), + with a key value of Qt::Key(0) (i.e. no regular key needs to be pressed to activate this action). */ +void QGLViewer::setMouseBinding(Qt::KeyboardModifiers modifiers, Qt::MouseButton button, ClickAction action, bool doubleClick, Qt::MouseButtons buttonsBefore) +{ + setMouseBinding(Qt::Key(0), modifiers, button, action, doubleClick, buttonsBefore); +} + +/*! Associates a ClickAction to a button and keyboard key and modifier(s) combination. + +The parameters should read: when \p button is pressed, while the \p modifiers and \p key keys are down, +and possibly as a \p doubleClick, then perform \p action. Use Qt::NoModifier to indicate that no modifier +key is needed, and a \p key value of 0 if no regular key has to be pressed (or simply use +setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButton, ClickAction, bool, Qt::MouseButtons)). + +If \p buttonsBefore is specified (valid only when \p doubleClick is \c true), then this (or these) other mouse +button(s) has (have) to be pressed \e before the double click occurs in order to execute \p action. + +The list of all possible ClickAction, some binding examples and default bindings are listed in the +mouse page. See also the setMouseBinding() documentation. + +See the keyboardAndMouse example for an +illustration. + +The binding is ignored if Qt::NoButton is specified as \p buttons. + +See also setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButtons, MouseHandler, MouseAction, bool), setWheelBinding() and clearMouseBindings(). +*/ +void QGLViewer::setMouseBinding(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button, ClickAction action, bool doubleClick, Qt::MouseButtons buttonsBefore) +{ + if ((buttonsBefore != Qt::NoButton) && !doubleClick) { + qWarning("Buttons before is only meaningful when doubleClick is true in setMouseBinding()."); + return; + } + + if (button == Qt::NoButton) { + qWarning("No mouse button specified in setMouseBinding"); + return; + } + + ClickBindingPrivate cbp(modifiers, button, doubleClick, buttonsBefore, key); + + // #CONNECTION performClickAction comment on NO_CLICK_ACTION + if (action == NO_CLICK_ACTION) + clickBinding_.remove(cbp); + else + clickBinding_.insert(cbp, action); + + if ((!doubleClick) && (buttonsBefore == Qt::NoButton)) { + MouseBindingPrivate mbp(modifiers, button, key); + mouseBinding_.remove(mbp); + } +} + +/*! Defines a mouse wheel binding. + + Same as calling setWheelBinding(Qt::Key, Qt::KeyboardModifiers, MouseHandler, MouseAction, bool), + with a key value of Qt::Key(0) (i.e. no regular key needs to be pressed to activate this action). */ +void QGLViewer::setWheelBinding(Qt::KeyboardModifiers modifiers, MouseHandler handler, MouseAction action, bool withConstraint) { + setWheelBinding(Qt::Key(0), modifiers, handler, action, withConstraint); +} + +/*! Associates a MouseAction and a MouseHandler to a mouse wheel event. + +This method is very similar to setMouseBinding(), but specific to the wheel. + +In the current implementation only QGLViewer::ZOOM can be associated with QGLViewer::FRAME, while +QGLViewer::CAMERA can receive QGLViewer::ZOOM and QGLViewer::MOVE_FORWARD. + +The difference between QGLViewer::ZOOM and QGLViewer::MOVE_FORWARD is that QGLViewer::ZOOM speed +depends on the distance to the object, while QGLViewer::MOVE_FORWARD moves at a constant speed +defined by qglviewer::Camera::flySpeed(). */ +void QGLViewer::setWheelBinding(Qt::Key key, Qt::KeyboardModifiers modifiers, MouseHandler handler, MouseAction action, bool withConstraint) +{ + //#CONNECTION# ManipulatedFrame::wheelEvent and ManipulatedCameraFrame::wheelEvent switches + if ((action != ZOOM) && (action != MOVE_FORWARD) && (action != MOVE_BACKWARD) && (action != NO_MOUSE_ACTION)) { + qWarning("Cannot bind %s to wheel", mouseActionString(action).toLatin1().constData()); + return; + } + + if ((handler == FRAME) && (action != ZOOM) && (action != NO_MOUSE_ACTION)) { + qWarning("Cannot bind %s to FRAME wheel", mouseActionString(action).toLatin1().constData()); + return; + } + + MouseActionPrivate map; + map.handler = handler; + map.action = action; + map.withConstraint = withConstraint; + + WheelBindingPrivate wbp(modifiers, key); + if (action == NO_MOUSE_ACTION) + wheelBinding_.remove(wbp); + else + wheelBinding_[wbp] = map; +} + +/*! Clears all the default mouse bindings. + +After this call, you will have to use setMouseBinding() and setWheelBinding() to restore the mouse bindings you are interested in. +*/ +void QGLViewer::clearMouseBindings() { + mouseBinding_.clear(); + clickBinding_.clear(); + wheelBinding_.clear(); +} + +/*! Clears all the default keyboard shortcuts. + +After this call, you will have to use setShortcut() to define your own keyboard shortcuts. +*/ +void QGLViewer::clearShortcuts() { + keyboardBinding_.clear(); + pathIndex_.clear(); +} + +/*! This method is deprecated since version 2.5.0 + + Use mouseAction(Qt::Key, Qt::KeyboardModifiers, Qt::MouseButtons) instead. +*/ +QGLViewer::MouseAction QGLViewer::mouseAction(unsigned int state) const { + qWarning("mouseAction(int state,...) is deprecated. Use the modifier/button equivalent"); + return mouseAction(Qt::Key(0), keyboardModifiersFromState(state), mouseButtonFromState(state)); +} + +/*! Returns the MouseAction the will be triggered when the mouse \p button is pressed, +while the keyboard \p modifiers and \p key are pressed. + +Returns QGLViewer::NO_MOUSE_ACTION if no action is associated with this combination. Use 0 for \p key +to indicate that no regular key needs to be pressed. + +For instance, to know which motion corresponds to Alt+LeftButton, do: +\code +QGLViewer::MouseAction ma = mouseAction(0, Qt::AltModifier, Qt::LeftButton); +if (ma != QGLViewer::NO_MOUSE_ACTION) ... +\endcode + +Use mouseHandler() to know which object (QGLViewer::CAMERA or QGLViewer::FRAME) will execute this +action. */ +QGLViewer::MouseAction QGLViewer::mouseAction(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button) const +{ + MouseBindingPrivate mbp(modifiers, button, key); + if (mouseBinding_.contains(mbp)) + return mouseBinding_[mbp].action; + else + return NO_MOUSE_ACTION; +} + +/*! This method is deprecated since version 2.5.0 + + Use mouseHanler(Qt::Key, Qt::KeyboardModifiers, Qt::MouseButtons) instead. +*/ +int QGLViewer::mouseHandler(unsigned int state) const { + qWarning("mouseHandler(int state,...) is deprecated. Use the modifier/button equivalent"); + return mouseHandler(Qt::Key(0), keyboardModifiersFromState(state), mouseButtonFromState(state)); +} + +/*! Returns the MouseHandler which will be activated when the mouse \p button is pressed, while the \p modifiers and \p key are pressed. + +If no action is associated with this combination, returns \c -1. Use 0 for \p key and Qt::NoModifier for \p modifiers +to represent the lack of a key press. + +For instance, to know which handler receives the Alt+LeftButton, do: +\code +int mh = mouseHandler(0, Qt::AltModifier, Qt::LeftButton); +if (mh == QGLViewer::CAMERA) ... +\endcode + +Use mouseAction() to know which action (see the MouseAction enum) will be performed on this handler. */ +int QGLViewer::mouseHandler(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button) const +{ + MouseBindingPrivate mbp(modifiers, button, key); + if (mouseBinding_.contains(mbp)) + return mouseBinding_[mbp].handler; + else + return -1; +} + + +#ifndef DOXYGEN +/*! This method is deprecated since version 2.5.0 + + Use mouseButtons() and keyboardModifiers() instead. +*/ +int QGLViewer::mouseButtonState(MouseHandler handler, MouseAction action, bool withConstraint) const { + qWarning("mouseButtonState() is deprecated. Use mouseButtons() and keyboardModifiers() instead"); + for (QMap::ConstIterator it=mouseBinding_.begin(), end=mouseBinding_.end(); it != end; ++it) + if ( (it.value().handler == handler) && (it.value().action == action) && (it.value().withConstraint == withConstraint) ) + return (int) it.key().modifiers | (int) it.key().button; + + return Qt::NoButton; +} +#endif + +/*! Returns the keyboard state that triggers \p action on \p handler \p withConstraint using the mouse wheel. + +If such a binding exists, results are stored in the \p key and \p modifiers +parameters. If the MouseAction \p action is not bound, \p key is set to the illegal -1 value. +If several keyboard states trigger the MouseAction, one of them is returned. + +See also setMouseBinding(), getClickActionBinding() and getMouseActionBinding(). */ +void QGLViewer::getWheelActionBinding(MouseHandler handler, MouseAction action, bool withConstraint, + Qt::Key& key, Qt::KeyboardModifiers& modifiers) const +{ + for (QMap::ConstIterator it=wheelBinding_.begin(), end=wheelBinding_.end(); it != end; ++it) + if ( (it.value().handler == handler) && (it.value().action == action) && (it.value().withConstraint == withConstraint) ) { + key = it.key().key; + modifiers = it.key().modifiers; + return; + } + + key = Qt::Key(-1); + modifiers = Qt::NoModifier; +} + +/*! Returns the mouse and keyboard state that triggers \p action on \p handler \p withConstraint. + +If such a binding exists, results are stored in the \p key, \p modifiers and \p button +parameters. If the MouseAction \p action is not bound, \p button is set to \c Qt::NoButton. +If several mouse and keyboard states trigger the MouseAction, one of them is returned. + +See also setMouseBinding(), getClickActionBinding() and getWheelActionBinding(). */ +void QGLViewer::getMouseActionBinding(MouseHandler handler, MouseAction action, bool withConstraint, + Qt::Key& key, Qt::KeyboardModifiers& modifiers, Qt::MouseButton& button) const +{ + for (QMap::ConstIterator it=mouseBinding_.begin(), end=mouseBinding_.end(); it != end; ++it) { + if ( (it.value().handler == handler) && (it.value().action == action) && (it.value().withConstraint == withConstraint) ) { + key = it.key().key; + modifiers = it.key().modifiers; + button = it.key().button; + return; + } + } + + key = Qt::Key(0); + modifiers = Qt::NoModifier; + button = Qt::NoButton; +} + +/*! Returns the MouseAction (if any) that is performed when using the wheel, when the \p modifiers and \p key keyboard keys are pressed. + +Returns NO_MOUSE_ACTION if no such binding has been defined using setWheelBinding(). + +Same as mouseAction(), but for the wheel action. See also wheelHandler(). +*/ +QGLViewer::MouseAction QGLViewer::wheelAction(Qt::Key key, Qt::KeyboardModifiers modifiers) const +{ + WheelBindingPrivate wbp(modifiers, key); + if (wheelBinding_.contains(wbp)) + return wheelBinding_[wbp].action; + else + return NO_MOUSE_ACTION; +} + +/*! Returns the MouseHandler (if any) that receives wheel events when the \p modifiers and \p key keyboard keys are pressed. + + Returns -1 if no no such binding has been defined using setWheelBinding(). See also wheelAction(). +*/ +int QGLViewer::wheelHandler(Qt::Key key, Qt::KeyboardModifiers modifiers) const +{ + WheelBindingPrivate wbp(modifiers, key); + if (wheelBinding_.contains(wbp)) + return wheelBinding_[wbp].handler; + else + return -1; +} + +/*! Same as mouseAction(), but for the ClickAction set using setMouseBinding(). + +Returns NO_CLICK_ACTION if no click action is associated with this keyboard and mouse buttons combination. */ +QGLViewer::ClickAction QGLViewer::clickAction(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button, + bool doubleClick, Qt::MouseButtons buttonsBefore) const { + ClickBindingPrivate cbp(modifiers, button, doubleClick, buttonsBefore, key); + if (clickBinding_.contains(cbp)) + return clickBinding_[cbp]; + else + return NO_CLICK_ACTION; +} + +#ifndef DOXYGEN +/*! This method is deprecated since version 2.5.0 + + Use wheelAction(Qt::Key key, Qt::KeyboardModifiers modifiers) instead. */ +QGLViewer::MouseAction QGLViewer::wheelAction(Qt::KeyboardModifiers modifiers) const { + qWarning("wheelAction() is deprecated. Use the new wheelAction() method with a key parameter instead"); + return wheelAction(Qt::Key(0), modifiers); +} + +/*! This method is deprecated since version 2.5.0 + + Use wheelHandler(Qt::Key key, Qt::KeyboardModifiers modifiers) instead. */ +int QGLViewer::wheelHandler(Qt::KeyboardModifiers modifiers) const { + qWarning("wheelHandler() is deprecated. Use the new wheelHandler() method with a key parameter instead"); + return wheelHandler(Qt::Key(0), modifiers); +} + +/*! This method is deprecated since version 2.5.0 + + Use wheelAction() and wheelHandler() instead. */ +unsigned int QGLViewer::wheelButtonState(MouseHandler handler, MouseAction action, bool withConstraint) const +{ + qWarning("wheelButtonState() is deprecated. Use the wheelAction() and wheelHandler() instead"); + for (QMap::ConstIterator it=wheelBinding_.begin(), end=wheelBinding_.end(); it!=end; ++it) + if ( (it.value().handler == handler) && (it.value().action == action) && (it.value().withConstraint == withConstraint) ) + return it.key().key + it.key().modifiers; + + return -1; +} + +/*! This method is deprecated since version 2.5.0 + + Use clickAction(Qt::KeyboardModifiers, Qt::MouseButtons, bool, Qt::MouseButtons) instead. +*/ +QGLViewer::ClickAction QGLViewer::clickAction(unsigned int state, bool doubleClick, Qt::MouseButtons buttonsBefore) const { + qWarning("clickAction(int state,...) is deprecated. Use the modifier/button equivalent"); + return clickAction(Qt::Key(0), + keyboardModifiersFromState(state), + mouseButtonFromState(state), + doubleClick, + buttonsBefore); +} + +/*! This method is deprecated since version 2.5.0 + + Use getClickActionState(ClickAction, Qt::Key, Qt::KeyboardModifiers, Qt::MouseButton, bool, Qt::MouseButtons) instead. +*/ +void QGLViewer::getClickButtonState(ClickAction action, unsigned int& state, bool& doubleClick, Qt::MouseButtons& buttonsBefore) const { + qWarning("getClickButtonState(int state,...) is deprecated. Use the modifier/button equivalent"); + Qt::KeyboardModifiers modifiers; + Qt::MouseButton button; + Qt::Key key; + getClickActionBinding(action, key, modifiers, button, doubleClick, buttonsBefore); + state = (unsigned int) modifiers | (unsigned int) button | (unsigned int) key; +} +#endif + +/*! Returns the mouse and keyboard state that triggers \p action. + +If such a binding exists, results are stored in the \p key, \p modifiers, \p button, \p doubleClick and \p buttonsBefore +parameters. If the ClickAction \p action is not bound, \p button is set to \c Qt::NoButton. +If several mouse buttons trigger in the ClickAction, one of them is returned. + +See also setMouseBinding(), getMouseActionBinding() and getWheelActionBinding(). */ +void QGLViewer::getClickActionBinding(ClickAction action, Qt::Key& key, Qt::KeyboardModifiers& modifiers, Qt::MouseButton &button, bool& doubleClick, Qt::MouseButtons& buttonsBefore) const +{ + for (QMap::ConstIterator it=clickBinding_.begin(), end=clickBinding_.end(); it != end; ++it) + if (it.value() == action) { + modifiers = it.key().modifiers; + button = it.key().button; + doubleClick = it.key().doubleClick; + buttonsBefore = it.key().buttonsBefore; + key = it.key().key; + return; + } + + modifiers = Qt::NoModifier; + button = Qt::NoButton; + doubleClick = false; + buttonsBefore = Qt::NoButton; + key = Qt::Key(0); +} + +/*! This function should be used in conjunction with toggleCameraMode(). It returns \c true when at +least one mouse button is binded to the \c ROTATE mouseAction. This is crude way of determining +which "mode" the camera is in. */ +bool QGLViewer::cameraIsInRotateMode() const +{ + //#CONNECTION# used in toggleCameraMode() and keyboardString() + Qt::Key key; + Qt::KeyboardModifiers modifiers; + Qt::MouseButton button; + getMouseActionBinding(CAMERA, ROTATE, true /*constraint*/, key, modifiers, button); + return button != Qt::NoButton; +} + +/*! Swaps between two predefined camera mouse bindings. + +The first mode makes the camera observe the scene while revolving around the +qglviewer::Camera::pivotPoint(). The second mode is designed for walkthrough applications +and simulates a flying camera. + +Practically, the three mouse buttons are respectively binded to: +\arg In rotate mode: QGLViewer::ROTATE, QGLViewer::ZOOM, QGLViewer::TRANSLATE. +\arg In fly mode: QGLViewer::MOVE_FORWARD, QGLViewer::LOOK_AROUND, QGLViewer::MOVE_BACKWARD. + +The current mode is determined by checking if a mouse button is binded to QGLViewer::ROTATE for +the QGLViewer::CAMERA. The state key that was previously used to move the camera is preserved. */ +void QGLViewer::toggleCameraMode() +{ + Qt::Key key; + Qt::KeyboardModifiers modifiers; + Qt::MouseButton button; + getMouseActionBinding(CAMERA, ROTATE, true /*constraint*/, key, modifiers, button); + bool rotateMode = button != Qt::NoButton; + + if (!rotateMode) { + getMouseActionBinding(CAMERA, MOVE_FORWARD, true /*constraint*/, key, modifiers, button); + } + + //#CONNECTION# setDefaultMouseBindings() + if (rotateMode) + { + camera()->frame()->updateSceneUpVector(); + camera()->frame()->stopSpinning(); + + setMouseBinding(modifiers, Qt::LeftButton, CAMERA, MOVE_FORWARD); + setMouseBinding(modifiers, Qt::MidButton, CAMERA, LOOK_AROUND); + setMouseBinding(modifiers, Qt::RightButton, CAMERA, MOVE_BACKWARD); + + setMouseBinding(Qt::Key_R, modifiers, Qt::LeftButton, CAMERA, ROLL); + + setMouseBinding(Qt::NoModifier, Qt::LeftButton, NO_CLICK_ACTION, true); + setMouseBinding(Qt::NoModifier, Qt::MidButton, NO_CLICK_ACTION, true); + setMouseBinding(Qt::NoModifier, Qt::RightButton, NO_CLICK_ACTION, true); + + setWheelBinding(modifiers, CAMERA, MOVE_FORWARD); + } + else + { + // Should stop flyTimer. But unlikely and not easy. + setMouseBinding(modifiers, Qt::LeftButton, CAMERA, ROTATE); + setMouseBinding(modifiers, Qt::MidButton, CAMERA, ZOOM); + setMouseBinding(modifiers, Qt::RightButton, CAMERA, TRANSLATE); + + setMouseBinding(Qt::Key_R, modifiers, Qt::LeftButton, CAMERA, SCREEN_ROTATE); + + setMouseBinding(Qt::NoModifier, Qt::LeftButton, ALIGN_CAMERA, true); + setMouseBinding(Qt::NoModifier, Qt::MidButton, SHOW_ENTIRE_SCENE, true); + setMouseBinding(Qt::NoModifier, Qt::RightButton, CENTER_SCENE, true); + + setWheelBinding(modifiers, CAMERA, ZOOM); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// M a n i p u l a t e d f r a m e s // +//////////////////////////////////////////////////////////////////////////////// + +/*! Sets the viewer's manipulatedFrame(). + +Several objects can be manipulated simultaneously, as is done the multiSelect example. + +Defining the \e own viewer's camera()->frame() as the manipulatedFrame() is possible and will result +in a classical camera manipulation. See the luxo example for an +illustration. + +Note that a qglviewer::ManipulatedCameraFrame can be set as the manipulatedFrame(): it is possible +to manipulate the camera of a first viewer in a second viewer. */ +void QGLViewer::setManipulatedFrame(ManipulatedFrame* frame) +{ + if (manipulatedFrame()) + { + manipulatedFrame()->stopSpinning(); + + if (manipulatedFrame() != camera()->frame()) + { + disconnect(manipulatedFrame(), SIGNAL(manipulated()), this, SLOT(update())); + disconnect(manipulatedFrame(), SIGNAL(spun()), this, SLOT(update())); + } + } + + manipulatedFrame_ = frame; + + manipulatedFrameIsACamera_ = ((manipulatedFrame() != camera()->frame()) && + (dynamic_cast(manipulatedFrame()) != NULL)); + + if (manipulatedFrame()) + { + // Prevent multiple connections, that would result in useless display updates + if (manipulatedFrame() != camera()->frame()) + { + connect(manipulatedFrame(), SIGNAL(manipulated()), SLOT(update())); + connect(manipulatedFrame(), SIGNAL(spun()), SLOT(update())); + } + } +} + +#ifndef DOXYGEN +//////////////////////////////////////////////////////////////////////////////// +// V i s u a l H i n t s // +//////////////////////////////////////////////////////////////////////////////// +/*! Defines the mask that will be used to drawVisualHints(). The only available mask is currently 1, +corresponding to the display of the qglviewer::Camera::pivotPoint(). resetVisualHints() is +automatically called after \p delay milliseconds (default is 2 seconds). */ +void QGLViewer::setVisualHintsMask(int mask, int delay) +{ + visualHint_ = visualHint_ | mask; + QTimer::singleShot(delay, this, SLOT(resetVisualHints())); +} + +/*! Reset the mask used by drawVisualHints(). Called by setVisualHintsMask() after 2 seconds to reset the display. */ +void QGLViewer::resetVisualHints() +{ + visualHint_ = 0; +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// S t a t i c m e t h o d s : Q G L V i e w e r P o o l // +//////////////////////////////////////////////////////////////////////////////// + +/*! saveStateToFile() is called on all the QGLViewers using the QGLViewerPool(). */ +void QGLViewer::saveStateToFileForAllViewers() +{ + Q_FOREACH (QGLViewer* viewer, QGLViewer::QGLViewerPool()) + { + if (viewer) + viewer->saveStateToFile(); + } +} + +////////////////////////////////////////////////////////////////////////// +// S a v e s t a t e b e t w e e n s e s s i o n s // +////////////////////////////////////////////////////////////////////////// + +/*! Returns the state file name. Default value is \c .qglviewer.xml. + +This is the name of the XML file where saveStateToFile() saves the viewer state (camera state, +widget geometry, display flags... see domElement()) on exit. Use restoreStateFromFile() to restore +this state later (usually in your init() method). + +Setting this value to \c QString::null will disable the automatic state file saving that normally +occurs on exit. + +If more than one viewer are created by the application, this function will return a numbered file +name (as in ".qglviewer1.xml", ".qglviewer2.xml"... using QGLViewer::QGLViewerIndex()) for extra +viewers. Each viewer will then read back its own information in restoreStateFromFile(), provided +that the viewers are created in the same order, which is usually the case. */ +QString QGLViewer::stateFileName() const +{ + QString name = stateFileName_; + + if (!name.isEmpty() && QGLViewer::QGLViewerIndex(this) > 0) + { + QFileInfo fi(name); + if (fi.suffix().isEmpty()) + name += QString::number(QGLViewer::QGLViewerIndex(this)); + else + name = fi.absolutePath() + '/' + fi.completeBaseName() + QString::number(QGLViewer::QGLViewerIndex(this)) + "." + fi.suffix(); + } + + return name; +} + +/*! Saves in stateFileName() an XML representation of the QGLViewer state, obtained from +domElement(). + +Use restoreStateFromFile() to restore this viewer state. + +This method is automatically called when a viewer is closed (using Escape or using the window's +upper right \c x close button). setStateFileName() to \c QString::null to prevent this. */ +void QGLViewer::saveStateToFile() +{ + QString name = stateFileName(); + + if (name.isEmpty()) + return; + + QFileInfo fileInfo(name); + + if (fileInfo.isDir()) + { + QMessageBox::warning(this, tr("Save to file error", "Message box window title"), tr("State file name (%1) references a directory instead of a file.").arg(name)); + return; + } + + const QString dirName = fileInfo.absolutePath(); + if (!QFileInfo(dirName).exists()) + { + QDir dir; + if (!(dir.mkdir(dirName))) + { + QMessageBox::warning(this, tr("Save to file error", "Message box window title"), tr("Unable to create directory %1").arg(dirName)); + return; + } + } + + // Write the DOM tree to file + QFile f(name); + if (f.open(QIODevice::WriteOnly)) + { + QTextStream out(&f); + QDomDocument doc("QGLVIEWER"); + doc.appendChild(domElement("QGLViewer", doc)); + doc.save(out, 2); + f.flush(); + f.close(); + } + else + QMessageBox::warning(this, tr("Save to file error", "Message box window title"), tr("Unable to save to file %1").arg(name) + ":\n" + f.errorString()); +} + +/*! Restores the QGLViewer state from the stateFileName() file using initFromDOMElement(). + +States are saved using saveStateToFile(), which is automatically called on viewer exit. + +Returns \c true when the restoration is successful. Possible problems are an non existing or +unreadable stateFileName() file, an empty stateFileName() or an XML syntax error. + +A manipulatedFrame() should be defined \e before calling this method, so that its state can be +restored. Initialization code put \e after this function will override saved values: +\code +void Viewer::init() +{ +// Default initialization goes here (including the declaration of a possible manipulatedFrame). + +if (!restoreStateFromFile()) +showEntireScene(); // Previous state cannot be restored: fit camera to scene. + +// Specific initialization that overrides file savings goes here. +} +\endcode */ +bool QGLViewer::restoreStateFromFile() +{ + QString name = stateFileName(); + + if (name.isEmpty()) + return false; + + QFileInfo fileInfo(name); + + if (!fileInfo.isFile()) + // No warning since it would be displayed at first start. + return false; + + if (!fileInfo.isReadable()) + { + QMessageBox::warning(this, tr("Problem in state restoration", "Message box window title"), tr("File %1 is not readable.").arg(name)); + return false; + } + + // Read the DOM tree form file + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + { + QDomDocument doc; + doc.setContent(&f); + f.close(); + QDomElement main = doc.documentElement(); + initFromDOMElement(main); + } + else + { + QMessageBox::warning(this, tr("Open file error", "Message box window title"), tr("Unable to open file %1").arg(name) + ":\n" + f.errorString()); + return false; + } + + return true; +} + +/*! Returns an XML \c QDomElement that represents the QGLViewer. + +Used by saveStateToFile(). restoreStateFromFile() uses initFromDOMElement() to restore the +QGLViewer state from the resulting \c QDomElement. + +\p name is the name of the QDomElement tag. \p doc is the \c QDomDocument factory used to create +QDomElement. + +The created QDomElement contains state values (axisIsDrawn(), FPSIsDisplayed(), isFullScreen()...), +viewer geometry, as well as camera() (see qglviewer::Camera::domElement()) and manipulatedFrame() +(if defined, see qglviewer::ManipulatedFrame::domElement()) states. + +Overload this method to add your own attributes to the state file: +\code +QDomElement Viewer::domElement(const QString& name, QDomDocument& document) const +{ +// Creates a custom node for a light +QDomElement de = document.createElement("Light"); +de.setAttribute("state", (lightIsOn()?"on":"off")); +// Note the include of the ManipulatedFrame domElement method. +de.appendChild(lightManipulatedFrame()->domElement("LightFrame", document)); + +// Get default state domElement and append custom node +QDomElement res = QGLViewer::domElement(name, document); +res.appendChild(de); +return res; +} +\endcode +See initFromDOMElement() for the associated restoration code. + +\attention For the manipulatedFrame(), qglviewer::Frame::constraint() and +qglviewer::Frame::referenceFrame() are not saved. See qglviewer::Frame::domElement(). */ +QDomElement QGLViewer::domElement(const QString& name, QDomDocument& document) const +{ + QDomElement de = document.createElement(name); + de.setAttribute("version", QGLViewerVersionString()); + + QDomElement stateNode = document.createElement("State"); + // hasMouseTracking() is not saved + stateNode.appendChild(DomUtils::QColorDomElement(foregroundColor(), "foregroundColor", document)); + stateNode.appendChild(DomUtils::QColorDomElement(backgroundColor(), "backgroundColor", document)); + DomUtils::setBoolAttribute(stateNode, "stereo", displaysInStereo()); + // Revolve or fly camera mode is not saved + de.appendChild(stateNode); + + QDomElement displayNode = document.createElement("Display"); + DomUtils::setBoolAttribute(displayNode, "axisIsDrawn", axisIsDrawn()); + DomUtils::setBoolAttribute(displayNode, "gridIsDrawn", gridIsDrawn()); + DomUtils::setBoolAttribute(displayNode, "FPSIsDisplayed", FPSIsDisplayed()); + DomUtils::setBoolAttribute(displayNode, "cameraIsEdited", cameraIsEdited()); + // textIsEnabled() is not saved + de.appendChild(displayNode); + + QDomElement geometryNode = document.createElement("Geometry"); + DomUtils::setBoolAttribute(geometryNode, "fullScreen", isFullScreen()); + if (isFullScreen()) + { + geometryNode.setAttribute("prevPosX", QString::number(prevPos_.x())); + geometryNode.setAttribute("prevPosY", QString::number(prevPos_.y())); + } + else + { + QWidget* tlw = topLevelWidget(); + geometryNode.setAttribute("width", QString::number(tlw->width())); + geometryNode.setAttribute("height", QString::number(tlw->height())); + geometryNode.setAttribute("posX", QString::number(tlw->pos().x())); + geometryNode.setAttribute("posY", QString::number(tlw->pos().y())); + } + de.appendChild(geometryNode); + + // Restore original Camera zClippingCoefficient before saving. + if (cameraIsEdited()) + camera()->setZClippingCoefficient(previousCameraZClippingCoefficient_); + de.appendChild(camera()->domElement("Camera", document)); + if (cameraIsEdited()) + // #CONNECTION# 5.0 from setCameraIsEdited() + camera()->setZClippingCoefficient(5.0); + + if (manipulatedFrame()) + de.appendChild(manipulatedFrame()->domElement("ManipulatedFrame", document)); + + return de; +} + +/*! Restores the QGLViewer state from a \c QDomElement created by domElement(). + +Used by restoreStateFromFile() to restore the QGLViewer state from a file. + +Overload this method to retrieve custom attributes from the QGLViewer state file. This code +corresponds to the one given in the domElement() documentation: +\code +void Viewer::initFromDOMElement(const QDomElement& element) +{ +// Restore standard state +QGLViewer::initFromDOMElement(element); + +QDomElement child=element.firstChild().toElement(); +while (!child.isNull()) +{ +if (child.tagName() == "Light") +{ +if (child.hasAttribute("state")) +setLightOn(child.attribute("state").lower() == "on"); + +// Assumes there is only one child. Otherwise you need to parse child's children recursively. +QDomElement lf = child.firstChild().toElement(); +if (!lf.isNull() && lf.tagName() == "LightFrame") +lightManipulatedFrame()->initFromDomElement(lf); +} +child = child.nextSibling().toElement(); +} +} +\endcode + +See also qglviewer::Camera::initFromDOMElement(), qglviewer::ManipulatedFrame::initFromDOMElement(). + +\note The manipulatedFrame() \e pointer is not modified by this method. If defined, its state is +simply set from the \p element values. */ +void QGLViewer::initFromDOMElement(const QDomElement& element) +{ + const QString version = element.attribute("version"); + // if (version != QGLViewerVersionString()) + if (version[0] != '2') + // Patches for previous versions should go here when the state file syntax is modified. + qWarning("State file created using QGLViewer version %s may not be correctly read.", version.toLatin1().constData()); + + QDomElement child=element.firstChild().toElement(); + bool tmpCameraIsEdited = cameraIsEdited(); + while (!child.isNull()) + { + if (child.tagName() == "State") + { + // #CONNECTION# default values from defaultConstructor() + // setMouseTracking(DomUtils::boolFromDom(child, "mouseTracking", false)); + setStereoDisplay(DomUtils::boolFromDom(child, "stereo", false)); + //if ((child.attribute("cameraMode", "revolve") == "fly") && (cameraIsInRevolveMode())) + // toggleCameraMode(); + + QDomElement ch=child.firstChild().toElement(); + while (!ch.isNull()) + { + if (ch.tagName() == "foregroundColor") + setForegroundColor(DomUtils::QColorFromDom(ch)); + if (ch.tagName() == "backgroundColor") + setBackgroundColor(DomUtils::QColorFromDom(ch)); + ch = ch.nextSibling().toElement(); + } + } + + if (child.tagName() == "Display") + { + // #CONNECTION# default values from defaultConstructor() + setAxisIsDrawn(DomUtils::boolFromDom(child, "axisIsDrawn", false)); + setGridIsDrawn(DomUtils::boolFromDom(child, "gridIsDrawn", false)); + setFPSIsDisplayed(DomUtils::boolFromDom(child, "FPSIsDisplayed", false)); + // See comment below. + tmpCameraIsEdited = DomUtils::boolFromDom(child, "cameraIsEdited", false); + // setTextIsEnabled(DomUtils::boolFromDom(child, "textIsEnabled", true)); + } + + if (child.tagName() == "Geometry") + { + setFullScreen(DomUtils::boolFromDom(child, "fullScreen", false)); + + if (isFullScreen()) + { + prevPos_.setX(DomUtils::intFromDom(child, "prevPosX", 0)); + prevPos_.setY(DomUtils::intFromDom(child, "prevPosY", 0)); + } + else + { + int width = DomUtils::intFromDom(child, "width", 600); + int height = DomUtils::intFromDom(child, "height", 400); + topLevelWidget()->resize(width, height); + camera()->setScreenWidthAndHeight(this->width(), this->height()); + + QPoint pos; + pos.setX(DomUtils::intFromDom(child, "posX", 0)); + pos.setY(DomUtils::intFromDom(child, "posY", 0)); + topLevelWidget()->move(pos); + } + } + + if (child.tagName() == "Camera") + { + connectAllCameraKFIInterpolatedSignals(false); + camera()->initFromDOMElement(child); + connectAllCameraKFIInterpolatedSignals(); + } + + if ((child.tagName() == "ManipulatedFrame") && (manipulatedFrame())) + manipulatedFrame()->initFromDOMElement(child); + + child = child.nextSibling().toElement(); + } + + // The Camera always stores its "real" zClippingCoef in domElement(). If it is edited, + // its "real" coef must be saved and the coef set to 5.0, as is done in setCameraIsEdited(). + // BUT : Camera and Display are read in an arbitrary order. We must initialize Camera's + // "real" coef BEFORE calling setCameraIsEdited. Hence this temp cameraIsEdited and delayed call + cameraIsEdited_ = tmpCameraIsEdited; + if (cameraIsEdited_) + { + previousCameraZClippingCoefficient_ = camera()->zClippingCoefficient(); + // #CONNECTION# 5.0 from setCameraIsEdited. + camera()->setZClippingCoefficient(5.0); + } +} + +#ifndef DOXYGEN +/*! This method is deprecated since version 1.3.9-5. Use saveStateToFile() and setStateFileName() +instead. */ +void QGLViewer::saveToFile(const QString& fileName) +{ + if (!fileName.isEmpty()) + setStateFileName(fileName); + + qWarning("saveToFile() is deprecated, use saveStateToFile() instead."); + saveStateToFile(); +} + +/*! This function is deprecated since version 1.3.9-5. Use restoreStateFromFile() and +setStateFileName() instead. */ +bool QGLViewer::restoreFromFile(const QString& fileName) +{ + if (!fileName.isEmpty()) + setStateFileName(fileName); + + qWarning("restoreFromFile() is deprecated, use restoreStateFromFile() instead."); + return restoreStateFromFile(); +} +#endif + +/*! Makes a copy of the current buffer into a texture. + +Creates a texture (when needed) and uses glCopyTexSubImage2D() to directly copy the buffer in it. + +Use \p internalFormat and \p format to define the texture format and hence which and how components +of the buffer are copied into the texture. See the glTexImage2D() documentation for details. + +When \p format is c GL_NONE (default), its value is set to \p internalFormat, which fits most +cases. Typical \p internalFormat (and \p format) values are \c GL_DEPTH_COMPONENT and \c GL_RGBA. +Use \c GL_LUMINANCE as the \p internalFormat and \c GL_RED, \c GL_GREEN or \c GL_BLUE as \p format +to capture a single color component as a luminance (grey scaled) value. Note that \c GL_STENCIL is +not supported as a format. + +The texture has dimensions which are powers of two. It is as small as possible while always being +larger or equal to the current size of the widget. The buffer image hence does not entirely fill +the texture: it is stuck to the lower left corner (corresponding to the (0,0) texture coordinates). +Use bufferTextureMaxU() and bufferTextureMaxV() to get the upper right corner maximum u and v +texture coordinates. Use bufferTextureId() to retrieve the id of the created texture. + +Here is how to display a grey-level image of the z-buffer: +\code +copyBufferToTexture(GL_DEPTH_COMPONENT); + +glMatrixMode(GL_TEXTURE); +glLoadIdentity(); + +glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +glEnable(GL_TEXTURE_2D); + +startScreenCoordinatesSystem(true); + +glBegin(GL_QUADS); +glTexCoord2f(0.0, 0.0); glVertex2i(0, 0); +glTexCoord2f(bufferTextureMaxU(), 0.0); glVertex2i(width(), 0); +glTexCoord2f(bufferTextureMaxU(), bufferTextureMaxV()); glVertex2i(width(), height()); +glTexCoord2f(0.0, bufferTextureMaxV()); glVertex2i(0, height()); +glEnd(); + +stopScreenCoordinatesSystem(); + +glDisable(GL_TEXTURE_2D); +\endcode + +Use glReadBuffer() to select which buffer is copied into the texture. See also \c +glPixelTransfer(), \c glPixelZoom() and \c glCopyPixel() for pixel color transformations during +copy. + +Call makeCurrent() before this method to make the OpenGL context active if needed. + +\note The \c GL_DEPTH_COMPONENT format may not be supported by all hardware. It may sometimes be +emulated in software, resulting in poor performances. + +\note The bufferTextureId() texture is binded at the end of this method. */ +void QGLViewer::copyBufferToTexture(GLint internalFormat, GLenum format) +{ + int h = 16; + int w = 16; + // Todo compare performance with qt code. + while (w < width()) + w <<= 1; + while (h < height()) + h <<= 1; + + bool init = false; + + if ((w != bufferTextureWidth_) || (h != bufferTextureHeight_)) + { + bufferTextureWidth_ = w; + bufferTextureHeight_ = h; + bufferTextureMaxU_ = width() / qreal(bufferTextureWidth_); + bufferTextureMaxV_ = height() / qreal(bufferTextureHeight_); + init = true; + } + + if (bufferTextureId() == 0) + { + glGenTextures(1, &bufferTextureId_); + glBindTexture(GL_TEXTURE_2D, bufferTextureId_); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + init = true; + } + else + glBindTexture(GL_TEXTURE_2D, bufferTextureId_); + + if ((format != previousBufferTextureFormat_) || + (internalFormat != previousBufferTextureInternalFormat_)) + { + previousBufferTextureFormat_ = format; + previousBufferTextureInternalFormat_ = internalFormat; + init = true; + } + + if (init) + { + if (format == GL_NONE) + format = GLenum(internalFormat); + + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, bufferTextureWidth_, bufferTextureHeight_, 0, format, GL_UNSIGNED_BYTE, NULL); + } + + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width(), height()); +} + +/*! Returns the texture id of the texture created by copyBufferToTexture(). + +Use glBindTexture() to use this texture. Note that this is already done by copyBufferToTexture(). + +Returns \c 0 is copyBufferToTexture() was never called or if the texure was deleted using +glDeleteTextures() since then. */ +GLuint QGLViewer::bufferTextureId() const +{ + if (glIsTexture(bufferTextureId_)) + return bufferTextureId_; + else + return 0; +} diff --git a/QGLViewer/qglviewer.h b/QGLViewer/qglviewer.h new file mode 100644 index 0000000..c5fc3df --- /dev/null +++ b/QGLViewer/qglviewer.h @@ -0,0 +1,1278 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#ifndef QGLVIEWER_QGLVIEWER_H +#define QGLVIEWER_QGLVIEWER_H + +#include "camera.h" + +#include +#include +#include + +class QTabWidget; + +namespace qglviewer { +class MouseGrabber; +class ManipulatedFrame; +class ManipulatedCameraFrame; +} + +/*! \brief A versatile 3D OpenGL viewer based on QOpenGLWidget. +\class QGLViewer qglviewer.h QGLViewer/qglviewer.h + +It features many classical viewer functionalities, such as a camera trackball, manipulated objects, +snapshot saving and much more. Its main goal is to ease the development +of new 3D applications. + +New users should read the introduction page to get familiar with +important notions such as sceneRadius(), sceneCenter() and the world coordinate system. Try the +numerous simple examples to discover the possibilities and +understand how it works. + +

Usage

+ +To use a QGLViewer, derive you viewer class from the QGLViewer and overload its draw() virtual +method. See the simpleViewer example for an illustration. + +An other option is to connect your drawing methods to the signals emitted by the QGLViewer (Qt's +callback mechanism). See the callback example for a +complete implementation. + +\nosubgrouping */ +class QGLVIEWER_EXPORT QGLViewer : public QOpenGLWidget +{ + Q_OBJECT + +public: + // Complete implementation is provided so that the constructor is defined with QT3_SUPPORT when .h is included. + // (Would not be available otherwise since lib is compiled without QT3_SUPPORT). +#ifdef QT3_SUPPORT + explicit QGLViewer(QWidget* parent=NULL, const char* name=0, const QOpenGLWidget* shareWidget=0, Qt::WindowFlags flags=0) + : QOpenGLWidget(parent, name, shareWidget, flags) + { defaultConstructor(); } + + explicit QGLViewer(const QSurfaceFormat& format, QWidget* parent=0, const char* name=0, const QOpenGLWidget* shareWidget=0,Qt::WindowFlags flags=0) + : QOpenGLWidget(format, parent, name, shareWidget, flags) + { defaultConstructor(); } + + QGLViewer(QOpenGLContext* context, QWidget* parent, const char* name=0, const QOpenGLWidget* shareWidget=0, Qt::WindowFlags flags=0) + : QOpenGLWidget(context, parent, name, shareWidget, flags) { + defaultConstructor(); } + +#else + + explicit QGLViewer(QWidget* parent=0, Qt::WindowFlags flags=0); + explicit QGLViewer(QOpenGLContext *context, QWidget* parent=0, const QOpenGLWidget* shareWidget=0, Qt::WindowFlags flags=0); + explicit QGLViewer(const QSurfaceFormat& format, QWidget* parent=0, const QOpenGLWidget* shareWidget=0, Qt::WindowFlags flags=0); +#endif + + virtual ~QGLViewer(); + + /*! @name Display of visual hints */ + //@{ +public: + /*! Returns \c true if the world axis is drawn by the viewer. + + Set by setAxisIsDrawn() or toggleAxisIsDrawn(). Default value is \c false. */ + bool axisIsDrawn() const { return axisIsDrawn_; } + /*! Returns \c true if a XY grid is drawn by the viewer. + + Set by setGridIsDrawn() or toggleGridIsDrawn(). Default value is \c false. */ + bool gridIsDrawn() const { return gridIsDrawn_; } + /*! Returns \c true if the viewer displays the current frame rate (Frames Per Second). + + Use QApplication::setFont() to define the display font (see drawText()). + + Set by setFPSIsDisplayed() or toggleFPSIsDisplayed(). Use currentFPS() to get the current FPS. + Default value is \c false. */ + bool FPSIsDisplayed() const { return FPSIsDisplayed_; } + /*! Returns \c true if text display (see drawText()) is enabled. + + Set by setTextIsEnabled() or toggleTextIsEnabled(). This feature conveniently removes all the + possibly displayed text, cleaning display. Default value is \c true. */ + bool textIsEnabled() const { return textIsEnabled_; } + + /*! Returns \c true if the camera() is being edited in the viewer. + + Set by setCameraIsEdited() or toggleCameraIsEdited(). Default value is \p false. + + The current implementation is limited: the defined camera() paths (see + qglviewer::Camera::keyFrameInterpolator()) are simply displayed using + qglviewer::Camera::drawAllPaths(). Actual camera and path edition will be implemented in the + future. */ + bool cameraIsEdited() const { return cameraIsEdited_; } + + +public Q_SLOTS: + /*! Sets the state of axisIsDrawn(). Emits the axisIsDrawnChanged() signal. See also toggleAxisIsDrawn(). */ + void setAxisIsDrawn(bool draw=true) { axisIsDrawn_ = draw; Q_EMIT axisIsDrawnChanged(draw); update(); } + /*! Sets the state of gridIsDrawn(). Emits the gridIsDrawnChanged() signal. See also toggleGridIsDrawn(). */ + void setGridIsDrawn(bool draw=true) { gridIsDrawn_ = draw; Q_EMIT gridIsDrawnChanged(draw); update(); } + /*! Sets the state of FPSIsDisplayed(). Emits the FPSIsDisplayedChanged() signal. See also toggleFPSIsDisplayed(). */ + void setFPSIsDisplayed(bool display=true) { FPSIsDisplayed_ = display; Q_EMIT FPSIsDisplayedChanged(display); update(); } + /*! Sets the state of textIsEnabled(). Emits the textIsEnabledChanged() signal. See also toggleTextIsEnabled(). */ + void setTextIsEnabled(bool enable=true) { textIsEnabled_ = enable; Q_EMIT textIsEnabledChanged(enable); update(); } + void setCameraIsEdited(bool edit=true); + + /*! Toggles the state of axisIsDrawn(). See also setAxisIsDrawn(). */ + void toggleAxisIsDrawn() { setAxisIsDrawn(!axisIsDrawn()); } + /*! Toggles the state of gridIsDrawn(). See also setGridIsDrawn(). */ + void toggleGridIsDrawn() { setGridIsDrawn(!gridIsDrawn()); } + /*! Toggles the state of FPSIsDisplayed(). See also setFPSIsDisplayed(). */ + void toggleFPSIsDisplayed() { setFPSIsDisplayed(!FPSIsDisplayed()); } + /*! Toggles the state of textIsEnabled(). See also setTextIsEnabled(). */ + void toggleTextIsEnabled() { setTextIsEnabled(!textIsEnabled()); } + /*! Toggles the state of cameraIsEdited(). See also setCameraIsEdited(). */ + void toggleCameraIsEdited() { setCameraIsEdited(!cameraIsEdited()); } + //@} + + + /*! @name Viewer's colors */ + //@{ +public: + /*! Returns the background color of the viewer. + + This method is provided for convenience since the background color is an OpenGL state variable + set with \c glClearColor(). However, this internal representation has the advantage that it is + saved (resp. restored) with saveStateToFile() (resp. restoreStateFromFile()). + + Use setBackgroundColor() to define and activate a background color. + + \attention Each QColor component is an integer ranging from 0 to 255. This differs from the qreal + values used by \c glClearColor() which are in the 0.0-1.0 range. Default value is (51, 51, 51) + (dark gray). You may have to change foregroundColor() accordingly. + + \attention This method does not return the current OpenGL clear color as \c glGet() does. Instead, + it returns the QGLViewer internal variable. If you directly use \c glClearColor() or \c + qglClearColor() instead of setBackgroundColor(), the two results will differ. */ + QColor backgroundColor() const { return backgroundColor_; } + + /*! Returns the foreground color used by the viewer. + + This color is used when FPSIsDisplayed(), gridIsDrawn(), to display the camera paths when the + cameraIsEdited(). + + \attention Each QColor component is an integer in the range 0-255. This differs from the qreal + values used by \c glColor3f() which are in the range 0-1. Default value is (180, 180, 180) (light + gray). + + Use \c qglColor(foregroundColor()) to set the current OpenGL color to the foregroundColor(). + + See also backgroundColor(). */ + QColor foregroundColor() const { return foregroundColor_; } +public Q_SLOTS: + /*! Sets the backgroundColor() of the viewer and calls \c qglClearColor(). See also + setForegroundColor(). */ + void setBackgroundColor(const QColor& color) + { + backgroundColor_=color; + glClearColor( // FORMERLY qglClearColor(color) + static_cast(color.redF()), + static_cast(color.greenF()), + static_cast(color.blueF()), + static_cast(color.alphaF())); + } + + /*! Sets the foregroundColor() of the viewer, used to draw visual hints. See also setBackgroundColor(). */ + void setForegroundColor(const QColor& color) { foregroundColor_ = color; } + //@} + + + /*! @name Scene dimensions */ + //@{ +public: + /*! Returns the scene radius. + + The entire displayed scene should be included in a sphere of radius sceneRadius(), centered on + sceneCenter(). + + This approximate value is used by the camera() to set qglviewer::Camera::zNear() and + qglviewer::Camera::zFar(). It is also used to showEntireScene() or to scale the world axis + display.. + + Default value is 1.0. This method is equivalent to camera()->sceneRadius(). See + setSceneRadius(). */ + qreal sceneRadius() const { return camera()->sceneRadius(); } + /*! Returns the scene center, defined in world coordinates. + + See sceneRadius() for details. + + Default value is (0,0,0). Simply a wrapper for camera()->sceneCenter(). Set using + setSceneCenter(). + + Do not mismatch this value (that only depends on the scene) with the qglviewer::Camera::pivotPoint(). */ + qglviewer::Vec sceneCenter() const { return camera()->sceneCenter(); } + +public Q_SLOTS: + /*! Sets the sceneRadius(). + + The camera() qglviewer::Camera::flySpeed() is set to 1% of this value by this method. Simple + wrapper around camera()->setSceneRadius(). */ + virtual void setSceneRadius(qreal radius) { camera()->setSceneRadius(radius); } + + /*! Sets the sceneCenter(), defined in world coordinates. + + \attention The qglviewer::Camera::pivotPoint() is set to the sceneCenter() value by this + method. */ + virtual void setSceneCenter(const qglviewer::Vec& center) { camera()->setSceneCenter(center); } + + /*! Convenient way to call setSceneCenter() and setSceneRadius() from a (world axis aligned) bounding box of the scene. + + This is equivalent to: + \code + setSceneCenter((min+max) / 2.0); + setSceneRadius((max-min).norm() / 2.0); + \endcode */ + void setSceneBoundingBox(const qglviewer::Vec& min, const qglviewer::Vec& max) { camera()->setSceneBoundingBox(min,max); } + + /*! Moves the camera so that the entire scene is visible. + + Simple wrapper around qglviewer::Camera::showEntireScene(). */ + void showEntireScene() { camera()->showEntireScene(); update(); } + //@} + + + /*! @name Associated objects */ + //@{ +public: + /*! Returns the associated qglviewer::Camera, never \c NULL. */ + qglviewer::Camera* camera() const { return camera_; } + + /*! Returns the viewer's qglviewer::ManipulatedFrame. + + This qglviewer::ManipulatedFrame can be moved with the mouse when the associated mouse bindings + are used (default is when pressing the \c Control key with any mouse button). Use + setMouseBinding() to define new bindings. + + See the manipulatedFrame example for a complete + implementation. + + Default value is \c NULL, meaning that no qglviewer::ManipulatedFrame is set. */ + qglviewer::ManipulatedFrame* manipulatedFrame() const { return manipulatedFrame_; } + +public Q_SLOTS: + void setCamera(qglviewer::Camera* const camera); + void setManipulatedFrame(qglviewer::ManipulatedFrame* frame); + //@} + + + /*! @name Mouse grabbers */ + //@{ +public: + /*! Returns the current qglviewer::MouseGrabber, or \c NULL if no qglviewer::MouseGrabber + currently grabs mouse events. + + When qglviewer::MouseGrabber::grabsMouse(), the different mouse events are sent to the + mouseGrabber() instead of their usual targets (camera() or manipulatedFrame()). + + See the qglviewer::MouseGrabber documentation for details on MouseGrabber's mode of operation. + + In order to use MouseGrabbers, you need to enable mouse tracking (so that mouseMoveEvent() is + called even when no mouse button is pressed). Add this line in init() or in your viewer + constructor: + \code + setMouseTracking(true); + \endcode + Note that mouse tracking is disabled by default. Use QWidget::hasMouseTracking() to + retrieve current state. */ + qglviewer::MouseGrabber* mouseGrabber() const { return mouseGrabber_; } + + void setMouseGrabberIsEnabled(const qglviewer::MouseGrabber* const mouseGrabber, bool enabled=true); + /*! Returns \c true if \p mouseGrabber is enabled. + + Default value is \c true for all MouseGrabbers. When set to \c false using + setMouseGrabberIsEnabled(), the specified \p mouseGrabber will never become the mouseGrabber() of + this QGLViewer. This is useful when you use several viewers: some MouseGrabbers may only have a + meaning for some specific viewers and should not be selectable in others. + + You can also use qglviewer::MouseGrabber::removeFromMouseGrabberPool() to completely disable a + MouseGrabber in all the QGLViewers. */ + bool mouseGrabberIsEnabled(const qglviewer::MouseGrabber* const mouseGrabber) { return !disabledMouseGrabbers_.contains(reinterpret_cast(mouseGrabber)); } +public Q_SLOTS: + void setMouseGrabber(qglviewer::MouseGrabber* mouseGrabber); + //@} + + + /*! @name State of the viewer */ + //@{ +public: + /*! Returns the aspect ratio of the viewer's widget (width() / height()). */ + qreal aspectRatio() const { return width() / static_cast(height()); } + /*! Returns the current averaged viewer frame rate. + + This value is computed and averaged over 20 successive frames. It only changes every 20 draw() + (previously computed value is otherwise returned). + + This method is useful for true real-time applications that may adapt their computational load + accordingly in order to maintain a given frequency. + + This value is meaningful only when draw() is regularly called, either using a \c QTimer, when + animationIsStarted() or when the camera is manipulated with the mouse. */ + qreal currentFPS() { return f_p_s_; } + /*! Returns \c true if the viewer is in fullScreen mode. + + Default value is \c false. Set by setFullScreen() or toggleFullScreen(). + + Note that if the QGLViewer is embedded in an other QWidget, it returns \c true when the top level + widget is in full screen mode. */ + bool isFullScreen() const { return fullScreen_; } + /*! Returns \c true if the viewer displays in stereo. + + The QGLViewer object must be created with a stereo format to handle stereovision: + \code + QGLFormat format; + format.setStereoDisplay( TRUE ); + QGLViewer viewer(format); + \endcode + The hardware needs to support stereo display. Try the stereoViewer example to check. + + Set by setStereoDisplay() or toggleStereoDisplay(). Default value is \c false. + + Stereo is performed using the Parallel axis asymmetric frustum perspective projection method. + See Camera::loadProjectionMatrixStereo() and Camera::loadModelViewMatrixStereo(). + + The stereo parameters are defined by the camera(). See qglviewer::Camera::setIODistance(), + qglviewer::Camera::setPhysicalScreenWidth() and + qglviewer::Camera::setFocusDistance(). */ + bool displaysInStereo() const { return stereo_; } + /*! Returns the recommended size for the QGLViewer. Default value is 600x400 pixels. */ + virtual QSize sizeHint() const { return QSize(600, 400); } + +public Q_SLOTS: + void setFullScreen(bool fullScreen=true); + void setStereoDisplay(bool stereo=true); + /*! Toggles the state of isFullScreen(). See also setFullScreen(). */ + void toggleFullScreen() { setFullScreen(!isFullScreen()); } + /*! Toggles the state of displaysInStereo(). See setStereoDisplay(). */ + void toggleStereoDisplay() { setStereoDisplay(!stereo_); } + void toggleCameraMode(); + +private: + bool cameraIsInRotateMode() const; + //@} + + + /*! @name Display methods */ + //@{ +public: + void displayMessage(const QString& message, int delay=2000); + // void draw3DText(const qglviewer::Vec& pos, const qglviewer::Vec& normal, const QString& string, GLfloat height=0.1); + +private: + void displayFPS(); + /*! Vectorial rendering callback method. */ + void drawVectorial() { paintGL(); } + +#ifndef DOXYGEN + friend void drawVectorial(void* param); +#endif + //@} + + +#ifdef DOXYGEN + /*! @name Useful inherited methods */ + //@{ +public: + /*! Returns viewer's widget width (in pixels). See QGLWidget documentation. */ + int width() const; + /*! Returns viewer's widget height (in pixels). See QGLWidget documentation. */ + int height() const; + /*! Updates the display. Do not call draw() directly, use this method instead. See QGLWidget documentation. */ + virtual void updateGL(); + /*! Converts \p image into the unnamed format expected by OpenGL methods such as glTexImage2D(). + See QGLWidget documentation. */ + static QImage convertToGLFormat(const QImage & image); + /*! Calls \c glColor3. See QGLWidget::qglColor(). */ + void qglColor(const QColor& color) const; + /*! Calls \c glClearColor. See QGLWidget documentation. */ + void qglClearColor(const QColor& color) const; + /*! Returns \c true if the widget has a valid GL rendering context. See QGLWidget + documentation. */ + bool isValid() const; + /*! Returns \c true if display list sharing with another QGLWidget was requested in the + constructor. See QGLWidget documentation. */ + bool isSharing() const; + /*! Makes this widget's rendering context the current OpenGL rendering context. Useful with + several viewers. See QGLWidget documentation. */ + virtual void makeCurrent(); + /*! Returns \c true if mouseMoveEvent() is called even when no mouse button is pressed. + + You need to setMouseTracking() to \c true in order to use MouseGrabber (see mouseGrabber()). See + details in the QWidget documentation. */ + bool hasMouseTracking () const; +public Q_SLOTS: + /*! Resizes the widget to size \p width by \p height pixels. See also width() and height(). */ + virtual void resize(int width, int height); + /*! Sets the hasMouseTracking() value. */ + virtual void setMouseTracking(bool enable); +protected: + /*! Returns \c true when buffers are automatically swapped (default). See details in the QGLWidget + documentation. */ + bool autoBufferSwap() const; +protected Q_SLOTS: + /*! Sets the autoBufferSwap() value. */ + void setAutoBufferSwap(bool on); + //@} +#endif + + + /*! @name Snapshots */ + //@{ +public: + /*! Returns the snapshot file name used by saveSnapshot(). + + This value is used in \p automatic mode (see saveSnapshot()). A dialog is otherwise popped-up to + set it. + + You can also directly provide a file name using saveSnapshot(const QString&, bool). + + If the file name is relative, the current working directory at the moment of the method call is + used. Set using setSnapshotFileName(). */ + const QString& snapshotFileName() const { return snapshotFileName_; } +#ifndef DOXYGEN + const QString& snapshotFilename() const; +#endif + /*! Returns the snapshot file format used by saveSnapshot(). + + This value is used when saveSnapshot() is passed the \p automatic flag. It is defined using a + saveAs pop-up dialog otherwise. + + The available formats are those handled by Qt. Classical values are \c "JPEG", \c "PNG", + \c "PPM", \c "BMP". Use the following code to get the actual list: + \code + QList formatList = QImageReader::supportedImageFormats(); + // or with Qt version 2 or 3: + QStringList formatList = QImage::outputFormatList(); + \endcode + + If the library was compiled with the vectorial rendering option (default), three additional + vectorial formats are available: \c "EPS", \c "PS" and \c "XFIG". \c "SVG" and \c "PDF" formats + should soon be available. The VRender library + was created by Cyril Soler. + + Note that the VRender library has some limitations: vertex shader effects are not reproduced and + \c PASS_THROUGH tokens are not handled so one can not change point and line size in the middle of + a drawing. + + Default value is the first supported among "JPEG, PNG, EPS, PS, PPM, BMP", in that order. + + This value is set using setSnapshotFormat() or with openSnapshotFormatDialog(). + + \attention No verification is performed on the provided format validity. The next call to + saveSnapshot() may fail if the format string is not supported. */ + const QString& snapshotFormat() const { return snapshotFormat_; } + /*! Returns the value of the counter used to name snapshots in saveSnapshot() when \p automatic is + \c true. + + Set using setSnapshotCounter(). Default value is 0, and it is incremented after each \p automatic + snapshot. See saveSnapshot() for details. */ + int snapshotCounter() const { return snapshotCounter_; } + /*! Defines the image quality of the snapshots produced with saveSnapshot(). + + Values must be in the range -1..100. Use 0 for lowest quality and 100 for highest quality (and + larger files). -1 means use Qt default quality. Default value is 95. + + Set using setSnapshotQuality(). See also the QImage::save() documentation. + + \note This value has no impact on the images produced in vectorial format. */ + int snapshotQuality() { return snapshotQuality_; } + + // Qt 2.3 does not support qreal default value parameters in slots. + // Remove "Q_SLOTS" from the following line to compile with Qt 2.3 +public Q_SLOTS: + void saveSnapshot(bool automatic=true, bool overwrite=false); + +public Q_SLOTS: + void saveSnapshot(const QString& fileName, bool overwrite=false); + void setSnapshotFileName(const QString& name); + + /*! Sets the snapshotFormat(). */ + void setSnapshotFormat(const QString& format) { snapshotFormat_ = format; } + /*! Sets the snapshotCounter(). */ + void setSnapshotCounter(int counter) { snapshotCounter_ = counter; } + /*! Sets the snapshotQuality(). */ + void setSnapshotQuality(int quality) { snapshotQuality_ = quality; } + bool openSnapshotFormatDialog(); + void snapshotToClipboard(); + +private: + bool saveImageSnapshot(const QString& fileName); + +#ifndef DOXYGEN + /* This class is used internally for screenshot that require tiling (image size size different + from window size). Only in that case, is the private tileRegion_ pointer non null. + It then contains the current tiled region, which is used by startScreenCoordinatesSystem + to adapt the coordinate system. Not using it would result in a tiled drawing of the parts + that use startScreenCoordinatesSystem. Also used by scaledFont for same purposes. */ + class TileRegion { public : qreal xMin, yMin, xMax, yMax, textScale; }; +#endif + +public: + /*! Return a possibly scaled version of \p font, used for snapshot rendering. + + From a user's point of view, this method simply returns \p font and can be used transparently. + + However when internally rendering a screen snapshot using saveSnapshot(), it returns a scaled version + of the font, so that the size of the rendered text on the snapshot is identical to what is displayed on screen, + even if the snapshot uses image tiling to create an image of dimensions different from those of the + current window. This scaled version will only be used when saveSnapshot() calls your draw() method + to generate the snapshot. + + All your calls to QGLWidget::renderText() function hence should use this method. + \code + renderText(x, y, z, "My Text", scaledFont(QFont())); + \endcode + will guarantee that this text will be properly displayed on arbitrary sized snapshots. + + Note that this method is not needed if you use drawText() which already calls it internally. */ + QFont scaledFont(const QFont& font) const { + if (tileRegion_ == NULL) + return font; + else { + QFont f(font); + if (f.pixelSize() == -1) + f.setPointSizeF(f.pointSizeF() * tileRegion_->textScale); + else + f.setPixelSize(int(f.pixelSize() * tileRegion_->textScale)); + return f; + } + } + //@} + + + /*! @name Buffer to texture */ + //@{ +public: + GLuint bufferTextureId() const; + /*! Returns the texture coordinate corresponding to the u extremum of the bufferTexture. + + The bufferTexture is created by copyBufferToTexture(). The texture size has powers of two + dimensions and the buffer image hence only fills a part of it. This value corresponds to the u + coordinate of the extremum right side of the buffer image. + + Use (0,0) to (bufferTextureMaxU(), bufferTextureMaxV()) texture coordinates to map the entire + texture on a quad. */ + qreal bufferTextureMaxU() const { return bufferTextureMaxU_; } + /*! Same as bufferTextureMaxU(), but for the v texture coordinate. */ + qreal bufferTextureMaxV() const { return bufferTextureMaxV_; } +public Q_SLOTS: + void copyBufferToTexture(GLint internalFormat, GLenum format=GL_NONE); + //@} + + /*! @name Animation */ + //@{ +public: + /*! Return \c true when the animation loop is started. + + During animation, an infinite loop calls animate() and draw() and then waits for animationPeriod() + milliseconds before calling animate() and draw() again. And again. + + Use startAnimation(), stopAnimation() or toggleAnimation() to change this value. + + See the animation example for illustration. */ + bool animationIsStarted() const { return animationStarted_; } + /*! The animation loop period, in milliseconds. + + When animationIsStarted(), this is delay waited after draw() to call animate() and draw() again. + Default value is 40 milliseconds (25 Hz). + + This value will define the currentFPS() when animationIsStarted() (provided that your animate() + and draw() methods are fast enough). + + If you want to know the maximum possible frame rate of your machine on a given scene, + setAnimationPeriod() to \c 0, and startAnimation() (keyboard shortcut is \c Enter). The display + will then be updated as often as possible, and the frame rate will be meaningful. + + \note This value is taken into account only the next time you call startAnimation(). If + animationIsStarted(), you should stopAnimation() first. */ + int animationPeriod() const { return animationPeriod_; } + +public Q_SLOTS: + /*! Sets the animationPeriod(), in milliseconds. */ + void setAnimationPeriod(int period) { animationPeriod_ = period; } + virtual void startAnimation(); + virtual void stopAnimation(); + /*! Scene animation method. + + When animationIsStarted(), this method is in charge of the scene update before each draw(). + Overload it to define how your scene evolves over time. The time should either be regularly + incremented in this method (frame-rate independent animation) or computed from actual time (for + instance using QTime::elapsed()) for real-time animations. + + Note that KeyFrameInterpolator (which regularly updates a Frame) does not use this method + to animate a Frame, but rather rely on a QTimer signal-slot mechanism. + + See the animation example for an illustration. */ + virtual void animate() { Q_EMIT animateNeeded(); } + /*! Calls startAnimation() or stopAnimation(), depending on animationIsStarted(). */ + void toggleAnimation() { if (animationIsStarted()) stopAnimation(); else startAnimation(); } + //@} + +public: +Q_SIGNALS: + /*! Signal emitted by the default init() method. + + Connect this signal to the methods that need to be called to initialize your viewer or overload init(). */ + void viewerInitialized(); + + /*! Signal emitted by the default draw() method. + + Connect this signal to your main drawing method or overload draw(). See the callback example for an illustration. */ + void drawNeeded(); + + /*! Signal emitted at the end of the QGLViewer::paintGL() method, when frame is drawn. + + Can be used to notify an image grabbing process that the image is ready. A typical example is to + connect this signal to the saveSnapshot() method, so that a (numbered) snapshot is generated after + each new display, in order to create a movie: + \code + connect(viewer, SIGNAL(drawFinished(bool)), SLOT(saveSnapshot(bool))); + \endcode + + The \p automatic bool variable is always \c true and has been added so that the signal can be + connected to saveSnapshot() with an \c automatic value set to \c true. */ + void drawFinished(bool automatic); + + /*! Signal emitted by the default animate() method. + + Connect this signal to your scene animation method or overload animate(). */ + void animateNeeded(); + + /*! Signal emitted by the default QGLViewer::help() method. + + Connect this signal to your own help method or overload help(). */ + void helpRequired(); + + /*! This signal is emitted whenever axisIsDrawn() changes value. */ + void axisIsDrawnChanged(bool drawn); + /*! This signal is emitted whenever gridIsDrawn() changes value. */ + void gridIsDrawnChanged(bool drawn); + /*! This signal is emitted whenever FPSIsDisplayed() changes value. */ + void FPSIsDisplayedChanged(bool displayed); + /*! This signal is emitted whenever textIsEnabled() changes value. */ + void textIsEnabledChanged(bool enabled); + /*! This signal is emitted whenever cameraIsEdited() changes value.. */ + void cameraIsEditedChanged(bool edited); + /*! This signal is emitted whenever displaysInStereo() changes value. */ + void stereoChanged(bool on); + /*! Signal emitted by select(). + + Connect this signal to your selection method or overload select(), or more probably simply + drawWithNames(). */ + void pointSelected(const QMouseEvent* e); + + /*! Signal emitted by setMouseGrabber() when the mouseGrabber() is changed. + + \p mouseGrabber is a pointer to the new MouseGrabber. Note that this signal is emitted with a \c + NULL parameter each time a MouseGrabber stops grabbing mouse. */ + void mouseGrabberChanged(qglviewer::MouseGrabber* mouseGrabber); + + /*! @name Help window */ + //@{ +public: + /*! Returns the QString displayed in the help() window main tab. + + Overload this method to define your own help string, which should shortly describe your + application and explain how it works. Rich-text (HTML) tags can be used (see QStyleSheet() + documentation for available tags): + \code + QString myViewer::helpString() const + { + QString text("

M y V i e w e r

"); + text += "Displays a Scene using OpenGL. Move the camera using the mouse."; + return text; + } + \endcode + + See also mouseString() and keyboardString(). */ + virtual QString helpString() const { return tr("No help available."); } + + virtual QString mouseString() const; + virtual QString keyboardString() const; + +#ifndef DOXYGEN + /*! This method is deprecated, use mouseString() instead. */ + virtual QString mouseBindingsString () const { return mouseString(); } + /*! This method is deprecated, use keyboardString() instead. */ + virtual QString shortcutBindingsString () const { return keyboardString(); } +#endif + +public Q_SLOTS: + virtual void help(); + virtual void aboutQGLViewer(); + +protected: + /*! Returns a pointer to the help widget. + + Use this only if you want to directly modify the help widget. Otherwise use helpString(), + setKeyDescription() and setMouseBindingDescription() to customize the text displayed in the help + window tabs. */ + QTabWidget* helpWidget() { return helpWidget_; } + //@} + + + /*! @name Drawing methods */ + //@{ +protected: + virtual void resizeGL(int width, int height); + virtual void initializeGL(); + + /*! Initializes the viewer OpenGL context. + + This method is called before the first drawing and should be overloaded to initialize some of the + OpenGL flags. The default implementation is empty. See initializeGL(). + + Typical usage include camera() initialization (showEntireScene()), previous viewer state + restoration (restoreStateFromFile()), OpenGL state modification and display list creation. + + Note that initializeGL() modifies the standard OpenGL context. These values can be restored back + in this method. + + \attention You should not call updateGL() (or any method that calls it) in this method, as it will + result in an infinite loop. The different QGLViewer set methods (setAxisIsDrawn(), + setFPSIsDisplayed()...) are protected against this problem and can safely be called. + + \note All the OpenGL specific initializations must be done in this method: the OpenGL context is + not yet available in your viewer constructor. */ + virtual void init() { Q_EMIT viewerInitialized(); } + + virtual void paintGL(); + virtual void preDraw(); + virtual void preDrawStereo(bool leftBuffer=true); + + /*! The core method of the viewer, that draws the scene. + + If you build a class that inherits from QGLViewer, this is the method you want to overload. See + the simpleViewer example for an illustration. + + The camera modelView matrix set in preDraw() converts from the world to the camera coordinate + systems. Vertices given in draw() can then be considered as being given in the world coordinate + system. The camera is moved in this world using the mouse. This representation is much more + intuitive than the default camera-centric OpenGL standard. + + \attention The \c GL_PROJECTION matrix should not be modified by this method, to correctly display + visual hints (axis, grid, FPS...) in postDraw(). Use push/pop or call + camera()->loadProjectionMatrix() at the end of draw() if you need to change the projection matrix + (unlikely). On the other hand, the \c GL_MODELVIEW matrix can be modified and left in a arbitrary + state. */ + virtual void draw() {} + virtual void fastDraw(); + virtual void postDraw(); + //@} + + /*! @name Mouse, keyboard and event handlers */ + //@{ +protected: + virtual void mousePressEvent(QMouseEvent *); + virtual void mouseMoveEvent(QMouseEvent *); + virtual void mouseReleaseEvent(QMouseEvent *); + virtual void mouseDoubleClickEvent(QMouseEvent *); + virtual void wheelEvent(QWheelEvent *); + virtual void keyPressEvent(QKeyEvent *); + virtual void keyReleaseEvent(QKeyEvent *); + virtual void timerEvent(QTimerEvent *); + virtual void closeEvent(QCloseEvent *); + //@} + + /*! @name Object selection */ + //@{ +public: + /*! Returns the name (an integer value) of the entity that was last selected by select(). This + value is set by endSelection(). See the select() documentation for details. + + As a convention, this method returns -1 if the selectBuffer() was empty, meaning that no object + was selected. + + Return value is -1 before the first call to select(). This value is modified using setSelectedName(). */ + int selectedName() const { return selectedObjectId_; } + /*! Returns the selectBuffer() size. + + See the select() documentation for details. Use setSelectBufferSize() to change this value. + + Default value is 4000 (i.e. 1000 objects in selection region, since each object pushes 4 values). + This size should be over estimated to prevent a buffer overflow when many objects are drawn under + the mouse cursor. */ + int selectBufferSize() const { return selectBufferSize_; } + + /*! Returns the width (in pixels) of a selection frustum, centered on the mouse cursor, that is + used to select objects. + + The height of the selection frustum is defined by selectRegionHeight(). + + The objects that will be drawn in this region by drawWithNames() will be recorded in the + selectBuffer(). endSelection() then analyzes this buffer and setSelectedName() to the name of the + closest object. See the gluPickMatrix() documentation for details. + + The default value is 3, which is adapted to standard applications. A smaller value results in a + more precise selection but the user has to be careful for small feature selection. + + See the multiSelect example for an illustration. */ + int selectRegionWidth() const { return selectRegionWidth_; } + /*! See the selectRegionWidth() documentation. Default value is 3 pixels. */ + int selectRegionHeight() const { return selectRegionHeight_; } + + /*! Returns a pointer to an array of \c GLuint. + + This buffer is used by the \c GL_SELECT mode in select() to perform object selection. The buffer + size can be modified using setSelectBufferSize(). If you overload endSelection(), you will analyze + the content of this buffer. See the \c glSelectBuffer() man page for details. */ + GLuint* selectBuffer() { return selectBuffer_; } + +public Q_SLOTS: +protected: + /*! This method is called by select() and should draw selectable entities. + + Default implementation is empty. Overload and draw the different elements of your scene you want + to be able to select. The default select() implementation relies on the \c GL_SELECT, and requires + that each selectable element is drawn within a \c glPushName() - \c glPopName() block. A typical + usage would be (see the select example): +\code +void Viewer::drawWithNames() { + for (int i=0; idraw(); + glPopName(); + } +} +\endcode + + The resulting selected name is computed by endSelection(), which setSelectedName() to the integer + id pushed by this method (a value of -1 means no selection). Use selectedName() to update your + selection, probably in the postSelection() method. + + \attention If your selected objects are points, do not use \c glBegin(GL_POINTS); and \c glVertex3fv() + in the above \c draw() method (not compatible with raster mode): use \c glRasterPos3fv() instead. */ + virtual void drawWithNames() {} + /*! This method is called at the end of the select() procedure. It should finalize the selection + process and update the data structure/interface/computation/display... according to the newly + selected entity. + + The default implementation is empty. Overload this method if needed, and use selectedName() to + retrieve the selected entity name (returns -1 if no object was selected). See the select example for an illustration. */ + virtual void postSelection(const QPoint& point) { Q_UNUSED(point); } + //@} + + + /*! @name Keyboard customization */ + //@{ +public: + /*! Defines the different actions that can be associated with a keyboard shortcut using + setShortcut(). + + See the keyboard page for details. */ + enum KeyboardAction { DRAW_AXIS, DRAW_GRID, DISPLAY_FPS, ENABLE_TEXT, EXIT_VIEWER, + SAVE_SCREENSHOT, CAMERA_MODE, FULL_SCREEN, STEREO, ANIMATION, HELP, EDIT_CAMERA, + MOVE_CAMERA_LEFT, MOVE_CAMERA_RIGHT, MOVE_CAMERA_UP, MOVE_CAMERA_DOWN, + INCREASE_FLYSPEED, DECREASE_FLYSPEED, SNAPSHOT_TO_CLIPBOARD }; + + unsigned int shortcut(KeyboardAction action) const; +#ifndef DOXYGEN + // QGLViewer 1.x + unsigned int keyboardAccelerator(KeyboardAction action) const; + Qt::Key keyFrameKey(unsigned int index) const; + Qt::KeyboardModifiers playKeyFramePathStateKey() const; + // QGLViewer 2.0 without Qt4 support + Qt::KeyboardModifiers addKeyFrameStateKey() const; + Qt::KeyboardModifiers playPathStateKey() const; +#endif + Qt::Key pathKey(unsigned int index) const; + Qt::KeyboardModifiers addKeyFrameKeyboardModifiers() const; + Qt::KeyboardModifiers playPathKeyboardModifiers() const; + +public Q_SLOTS: + void setShortcut(KeyboardAction action, unsigned int key); +#ifndef DOXYGEN + void setKeyboardAccelerator(KeyboardAction action, unsigned int key); +#endif + void setKeyDescription(unsigned int key, QString description); + void clearShortcuts(); + + // Key Frames shortcut keys +#ifndef DOXYGEN + // QGLViewer 1.x compatibility methods + virtual void setKeyFrameKey(unsigned int index, int key); + virtual void setPlayKeyFramePathStateKey(unsigned int buttonState); + // QGLViewer 2.0 without Qt4 support + virtual void setPlayPathStateKey(unsigned int buttonState); + virtual void setAddKeyFrameStateKey(unsigned int buttonState); +#endif + virtual void setPathKey(int key, unsigned int index = 0); + virtual void setPlayPathKeyboardModifiers(Qt::KeyboardModifiers modifiers); + virtual void setAddKeyFrameKeyboardModifiers(Qt::KeyboardModifiers modifiers); + //@} + + +public: + /*! @name Mouse customization */ + //@{ + /*! Defines the different mouse handlers: camera() or manipulatedFrame(). + + Used by setMouseBinding(), setMouseBinding(Qt::KeyboardModifiers modifiers, Qt::MouseButtons, ClickAction, bool, int) + and setWheelBinding() to define which handler receives the mouse events. */ + enum MouseHandler { CAMERA, FRAME }; + + /*! Defines the possible actions that can be binded to a mouse click using + setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButtons, ClickAction, bool, int). + + See the mouse page for details. */ + enum ClickAction { NO_CLICK_ACTION, ZOOM_ON_PIXEL, ZOOM_TO_FIT, SELECT, RAP_FROM_PIXEL, RAP_IS_CENTER, + CENTER_FRAME, CENTER_SCENE, SHOW_ENTIRE_SCENE, ALIGN_FRAME, ALIGN_CAMERA }; + + + /*! Defines the possible actions that can be binded to a mouse action (a click, followed by a + mouse displacement). + + These actions may be binded to the camera() or to the manipulatedFrame() (see QGLViewer::MouseHandler) using + setMouseBinding(). */ + enum MouseAction { NO_MOUSE_ACTION, + ROTATE, ZOOM, TRANSLATE, + MOVE_FORWARD, LOOK_AROUND, MOVE_BACKWARD, + SCREEN_ROTATE, ROLL, DRIVE, + SCREEN_TRANSLATE, ZOOM_ON_REGION }; + +#ifndef DOXYGEN + MouseAction mouseAction(unsigned int state) const; + int mouseHandler(unsigned int state) const; + int mouseButtonState(MouseHandler handler, MouseAction action, bool withConstraint=true) const; + ClickAction clickAction(unsigned int state, bool doubleClick, Qt::MouseButtons buttonsBefore) const; + void getClickButtonState(ClickAction action, unsigned int & state, bool& doubleClick, Qt::MouseButtons& buttonsBefore) const; + unsigned int wheelButtonState(MouseHandler handler, MouseAction action, bool withConstraint=true) const; +#endif + + MouseAction mouseAction(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button) const; + int mouseHandler(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button) const; + + void getMouseActionBinding(MouseHandler handler, MouseAction action, bool withConstraint, + Qt::Key& key, Qt::KeyboardModifiers& modifiers, Qt::MouseButton& button) const; + + ClickAction clickAction(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button, + bool doubleClick=false, Qt::MouseButtons buttonsBefore=Qt::NoButton) const; + + void getClickActionBinding(ClickAction action, Qt::Key& key, Qt::KeyboardModifiers& modifiers, + Qt::MouseButton& button, bool& doubleClick, Qt::MouseButtons& buttonsBefore) const; + + MouseAction wheelAction(Qt::Key key, Qt::KeyboardModifiers modifiers) const; + int wheelHandler(Qt::Key key, Qt::KeyboardModifiers modifiers) const; + + void getWheelActionBinding(MouseHandler handler, MouseAction action, bool withConstraint, + Qt::Key& key, Qt::KeyboardModifiers& modifiers) const; + +public Q_SLOTS: +#ifndef DOXYGEN + void setMouseBinding(unsigned int state, MouseHandler handler, MouseAction action, bool withConstraint=true); + void setMouseBinding(unsigned int state, ClickAction action, bool doubleClick=false, Qt::MouseButtons buttonsBefore=Qt::NoButton); + void setMouseBindingDescription(unsigned int state, QString description, bool doubleClick=false, Qt::MouseButtons buttonsBefore=Qt::NoButton); +#endif + + void setMouseBinding(Qt::KeyboardModifiers modifiers, Qt::MouseButton buttons, MouseHandler handler, MouseAction action, bool withConstraint=true); + void setMouseBinding(Qt::KeyboardModifiers modifiers, Qt::MouseButton button, ClickAction action, bool doubleClick=false, Qt::MouseButtons buttonsBefore=Qt::NoButton); + void setWheelBinding(Qt::KeyboardModifiers modifiers, MouseHandler handler, MouseAction action, bool withConstraint=true); + void setMouseBindingDescription(Qt::KeyboardModifiers modifiers, Qt::MouseButton button, QString description, bool doubleClick=false, Qt::MouseButtons buttonsBefore=Qt::NoButton); + + void setMouseBinding(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton buttons, MouseHandler handler, MouseAction action, bool withConstraint=true); + void setMouseBinding(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button, ClickAction action, bool doubleClick=false, Qt::MouseButtons buttonsBefore=Qt::NoButton); + void setWheelBinding(Qt::Key key, Qt::KeyboardModifiers modifiers, MouseHandler handler, MouseAction action, bool withConstraint=true); + void setMouseBindingDescription(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button, QString description, bool doubleClick=false, Qt::MouseButtons buttonsBefore=Qt::NoButton); + + void clearMouseBindings(); + +#ifndef DOXYGEN + MouseAction wheelAction(Qt::KeyboardModifiers modifiers) const; + int wheelHandler(Qt::KeyboardModifiers modifiers) const; + + void setHandlerKeyboardModifiers(MouseHandler handler, Qt::KeyboardModifiers modifiers); + void setHandlerStateKey(MouseHandler handler, unsigned int buttonState); + void setMouseStateKey(MouseHandler handler, unsigned int buttonState); +#endif + +private: + static QString mouseActionString(QGLViewer::MouseAction ma); + static QString clickActionString(QGLViewer::ClickAction ca); + //@} + + + /*! @name State persistence */ + //@{ +public: + QString stateFileName() const; + virtual QDomElement domElement(const QString& name, QDomDocument& document) const; + +public Q_SLOTS: + virtual void initFromDOMElement(const QDomElement& element); + virtual void saveStateToFile(); // cannot be const because of QMessageBox + virtual bool restoreStateFromFile(); + + /*! Defines the stateFileName() used by saveStateToFile() and restoreStateFromFile(). + + The file name can have an optional prefix directory (no prefix meaning current directory). If the + directory does not exist, it will be created by saveStateToFile(). + + \code + // Name depends on the displayed 3D model. Saved in current directory. + setStateFileName(3DModelName() + ".xml"); + + // Files are stored in a dedicated directory under user's home directory. + setStateFileName(QDir::homeDirPath + "/.config/myApp.xml"); + \endcode */ + void setStateFileName(const QString& name) { stateFileName_ = name; } + +#ifndef DOXYGEN + void saveToFile(const QString& fileName=QString::null); + bool restoreFromFile(const QString& fileName=QString::null); +#endif + +private: + static void saveStateToFileForAllViewers(); + //@} + + + /*! @name QGLViewer pool */ + //@{ +public: + /*! Returns a \c QList that contains pointers to all the created QGLViewers. + Note that this list may contain \c NULL pointers if the associated viewer has been deleted. + + Can be useful to apply a method or to connect a signal to all the viewers: + \code + foreach (QGLViewer* viewer, QGLViewer::QGLViewerPool()) + connect(myObject, SIGNAL(IHaveChangedSignal()), viewer, SLOT(update())); + \endcode + + \attention With Qt version 3, this method returns a \c QPtrList instead. Use a \c QPtrListIterator + to iterate on the list instead.*/ + static const QList& QGLViewerPool() { return QGLViewer::QGLViewerPool_; } + + + /*! Returns the index of the QGLViewer \p viewer in the QGLViewerPool(). This index in unique and + can be used to identify the different created QGLViewers (see stateFileName() for an application + example). + + When a QGLViewer is deleted, the QGLViewers' indexes are preserved and NULL is set for that index. + When a QGLViewer is created, it is placed in the first available position in that list. + Returns -1 if the QGLViewer could not be found (which should not be possible). */ + static int QGLViewerIndex(const QGLViewer* const viewer) { return QGLViewer::QGLViewerPool_.indexOf(const_cast(viewer)); } + //@} + +#ifndef DOXYGEN + /*! @name Visual hints */ + //@{ +public: + virtual void setVisualHintsMask(int mask, int delay = 2000); + +public Q_SLOTS: + virtual void resetVisualHints(); + //@} +#endif + +private Q_SLOTS: + // Patch for a Qt bug with fullScreen on startup + void delayedFullScreen() { move(prevPos_); setFullScreen(); } + void hideMessage(); + +private: + // Copy constructor and operator= are declared private and undefined + // Prevents everyone from trying to use them + QGLViewer(const QGLViewer& v); + QGLViewer& operator=(const QGLViewer& v); + + // Set parameters to their default values. Called by the constructors. + void defaultConstructor(); + + void handleKeyboardAction(KeyboardAction id); + + // C a m e r a + qglviewer::Camera* camera_; + bool cameraIsEdited_; + qreal previousCameraZClippingCoefficient_; + unsigned int previousPathId_; // double key press recognition + void connectAllCameraKFIInterpolatedSignals(bool connection=true); + + // C o l o r s + QColor backgroundColor_, foregroundColor_; + + // D i s p l a y f l a g s + bool axisIsDrawn_; // world axis + bool gridIsDrawn_; // world XY grid + bool FPSIsDisplayed_; // Frame Per Seconds + bool textIsEnabled_; // drawText() actually draws text or not + bool stereo_; // stereo display + bool fullScreen_; // full screen mode + QPoint prevPos_; // Previous window position, used for full screen mode + + // A n i m a t i o n + bool animationStarted_; // animation mode started + int animationPeriod_; // period in msecs + int animationTimerId_; + + // F P S d i s p l a y + QTime fpsTime_; + unsigned int fpsCounter_; + QString fpsString_; + qreal f_p_s_; + + // M e s s a g e s + QString message_; + bool displayMessage_; + QTimer messageTimer_; + + // M a n i p u l a t e d f r a m e + qglviewer::ManipulatedFrame* manipulatedFrame_; + bool manipulatedFrameIsACamera_; + + // M o u s e G r a b b e r + qglviewer::MouseGrabber* mouseGrabber_; + bool mouseGrabberIsAManipulatedFrame_; + bool mouseGrabberIsAManipulatedCameraFrame_; + QMap disabledMouseGrabbers_; + + // S e l e c t i o n + int selectRegionWidth_, selectRegionHeight_; + int selectBufferSize_; + GLuint* selectBuffer_; + int selectedObjectId_; + + // V i s u a l h i n t s + int visualHint_; + + // S h o r t c u t k e y s + void setDefaultShortcuts(); + QString cameraPathKeysString() const; + QMap keyboardActionDescription_; + QMap keyboardBinding_; + QMap keyDescription_; + + // K e y F r a m e s s h o r t c u t s + QMap pathIndex_; + Qt::KeyboardModifiers addKeyFrameKeyboardModifiers_, playPathKeyboardModifiers_; + + // B u f f e r T e x t u r e + GLuint bufferTextureId_; + qreal bufferTextureMaxU_, bufferTextureMaxV_; + int bufferTextureWidth_, bufferTextureHeight_; + unsigned int previousBufferTextureFormat_; + int previousBufferTextureInternalFormat_; + +#ifndef DOXYGEN + // M o u s e a c t i o n s + struct MouseActionPrivate { + MouseHandler handler; + MouseAction action; + bool withConstraint; + }; + + // M o u s e b i n d i n g s + struct MouseBindingPrivate { + const Qt::KeyboardModifiers modifiers; + const Qt::MouseButton button; + const Qt::Key key; + + MouseBindingPrivate(Qt::KeyboardModifiers m, Qt::MouseButton b, Qt::Key k) + : modifiers(m), button(b), key(k) {} + + // This sort order is used in mouseString() to display sorted mouse bindings + bool operator<(const MouseBindingPrivate& mbp) const + { + if (key != mbp.key) + return key < mbp.key; + if (modifiers != mbp.modifiers) + return modifiers < mbp.modifiers; + return button < mbp.button; + } + }; + + // W h e e l b i n d i n g s + struct WheelBindingPrivate { + const Qt::KeyboardModifiers modifiers; + const Qt::Key key; + + WheelBindingPrivate(Qt::KeyboardModifiers m, Qt::Key k) + : modifiers(m), key(k) {} + + // This sort order is used in mouseString() to display sorted wheel bindings + bool operator<(const WheelBindingPrivate& wbp) const + { + if (key != wbp.key) + return key < wbp.key; + return modifiers < wbp.modifiers; + } + }; + + // C l i c k b i n d i n g s + struct ClickBindingPrivate { + const Qt::KeyboardModifiers modifiers; + const Qt::MouseButton button; + const bool doubleClick; + const Qt::MouseButtons buttonsBefore; // only defined when doubleClick is true + const Qt::Key key; + + ClickBindingPrivate(Qt::KeyboardModifiers m, Qt::MouseButton b, bool dc, Qt::MouseButtons bb, Qt::Key k) + : modifiers(m), button(b), doubleClick(dc), buttonsBefore(bb), key(k) {} + + // This sort order is used in mouseString() to display sorted mouse bindings + bool operator<(const ClickBindingPrivate& cbp) const + { + if (key != cbp.key) + return key < cbp.key; + if (buttonsBefore != cbp.buttonsBefore) + return buttonsBefore < cbp.buttonsBefore; + if (modifiers != cbp.modifiers) + return modifiers < cbp.modifiers; + if (button != cbp.button) + return button < cbp.button; + return doubleClick != cbp.doubleClick; + } + }; +#endif + static QString formatClickActionPrivate(ClickBindingPrivate cbp); + static bool isValidShortcutKey(int key); + + QMap mouseDescription_; + + void setDefaultMouseBindings(); + void performClickAction(ClickAction ca, const QMouseEvent* const e); + QMap mouseBinding_; + QMap wheelBinding_; + QMap clickBinding_; + Qt::Key currentlyPressedKey_; + + // S n a p s h o t s + void initializeSnapshotFormats(); + QImage frameBufferSnapshot(); + QString snapshotFileName_, snapshotFormat_; + int snapshotCounter_, snapshotQuality_; + TileRegion* tileRegion_; + + // Q G L V i e w e r p o o l + static QList QGLViewerPool_; + + // S t a t e F i l e + QString stateFileName_; + + // H e l p w i n d o w + QTabWidget* helpWidget_; +}; + +#endif // QGLVIEWER_QGLVIEWER_H diff --git a/QGLViewer/qglviewer.icns b/QGLViewer/qglviewer.icns new file mode 100644 index 0000000..ab03127 Binary files /dev/null and b/QGLViewer/qglviewer.icns differ diff --git a/QGLViewer/qglviewer_fr.qm b/QGLViewer/qglviewer_fr.qm new file mode 100644 index 0000000..4e9253f Binary files /dev/null and b/QGLViewer/qglviewer_fr.qm differ diff --git a/QGLViewer/qglviewer_fr.ts b/QGLViewer/qglviewer_fr.ts new file mode 100644 index 0000000..72372d1 --- /dev/null +++ b/QGLViewer/qglviewer_fr.ts @@ -0,0 +1,608 @@ + + + + + ImageInterface + + Image settings + RĂ©glages d'image + + + Width + Largeur + + + px + px + + + Width of the image (in pixels) + Largeur de l'image (en pixels) + + + Height + Hauteur + + + Height of the image (in pixels) + Hauteur de l'image (en pixels) + + + Image quality + QualitĂ© d'image + + + Between 0 (smallest files) and 100 (highest quality) + Entre 0 (taille de fichier minimale) et 100 (qualitĂ© maximale) + + + Oversampling + SurĂ©chantillonage + + + x + x + + + Antialiases image (when larger then 1.0) + Anti-alliassage de l'image (si supĂ©rieur Ă  1.0) + + + Use white background + Fond blanc + + + Use white as background color + Mettre du blanc en couleur de fond + + + Expand frustum if needed + Etendre la pyramide de vue ( frustum) si nĂ©cessaire + + + When image aspect ratio differs from viewer's one, expand frustum as needed. Fits inside current frustum otherwise. + Lorsque le rapport de dimensions de l'image diffère de celui de la fenĂªtre, Ă©tendre la pyramide de vue (frustum) en consĂ©quence. L'image est ajustĂ©e Ă  l'intĂ©rieur de la vue actuelle sinon. + + + OK + Ok + + + Cancel + Annuler + + + + QGLViewer + + snapshot + Default snapshot file name + capture + + + %1Hz + Frames per seconds, in Hertz + %1Hz + + + Toggles the display of the FPS + DISPLAY_FPS action description + Active ou non l'affichage de la frĂ©quence d'affiichage + + + Saves a screenshot + SAVE_SCREENSHOT action description + Sauvegarde une capture d'Ă©cran + + + Toggles full screen display + FULL_SCREEN action description + Passe ou non en mode plein Ă©cran + + + Toggles the display of the world axis + DRAW_AXIS action description + Affiche ou non le repère du monde + + + Toggles the display of the XY grid + DRAW_GRID action description + Affiche ou non la grille XY + + + Changes camera mode (observe or fly) + CAMERA_MODE action description + Change le mode de la camĂ©ra (observateur ou vol) + + + Toggles stereo display + STEREO action description + Affiche ou non en stĂ©rĂ©o + + + Opens this help window + HELP action description + Ouvre la fenĂªtre d'aide + + + Starts/stops the animation + ANIMATION action description + DĂ©marre/arrĂªte l'animation + + + Toggles camera paths display + EDIT_CAMERA action description + Affiche ou non les chemins de camĂ©ra + + + Toggles the display of the text + ENABLE_TEXT action description + Affiche ou non les textes + + + Exits program + EXIT_VIEWER action description + Quitte l'application + + + Moves camera left + MOVE_CAMERA_LEFT action description + DĂ©place la camĂ©ra sur la gauche + + + Moves camera right + MOVE_CAMERA_RIGHT action description + DĂ©place la camĂ©ra sur la droite + + + Moves camera up + MOVE_CAMERA_UP action description + DĂ©place la camĂ©ra vers le haut + + + Moves camera down + MOVE_CAMERA_DOWN action description + DĂ©place la camĂ©ra vers le bas + + + Increases fly speed + INCREASE_FLYSPEED action description + Augmente la vitesse de vol + + + Decreases fly speed + DECREASE_FLYSPEED action description + Diminue la vitesse de vol + + + Copies a snapshot to clipboard + SNAPSHOT_TO_CLIPBOARD action description + Place une capture d'Ă©cran dans le presse-papier + + + Stereo not supported + Message box window title + StĂ©rĂ©o non supportĂ©e + + + Stereo is not supported on this display. + Affichage en stĂ©rĂ©o non supportĂ© sur cette machine. + + + Rotates + ROTATE mouse action + Tourne + + + Zooms + ZOOM mouse action + Zoome + + + Translates + TRANSLATE mouse action + Translate + + + Moves backward + MOVE_BACKWARD mouse action + Recule + + + Horizontally/Vertically translates + SCREEN_TRANSLATE mouse action + Translate horizontalement/verticalement + + + Moves forward + MOVE_FORWARD mouse action + Avance + + + Looks around + LOOK_AROUND mouse action + Regarde + + + Rotates in screen plane + SCREEN_ROTATE mouse action + Pivote dans le plan Ă©cran + + + Rolls + ROLL mouse action + Pivote + + + Drives + DRIVE mouse action + Avance + + + Zooms on region for + ZOOM_ON_REGION mouse action + Zoome sur la rĂ©gion pour + + + Zooms on pixel + ZOOM_ON_PIXEL click action + Zoome sur le pixel + + + Zooms to fit scene + ZOOM_TO_FIT click action + Zoome pour ajuster Ă  la scène + + + Selects + SELECT click action + SĂ©lectionne + + + Sets pivot point + RAP_FROM_PIXEL click action + DĂ©finit le point de rotation + + + Resets pivot point + RAP_IS_CENTER click action + Restaure le point de rotation + + + Centers manipulated frame + CENTER_FRAME click action + Centre le repère manipulĂ© + + + Centers scene + CENTER_SCENE click action + Centre la scène + + + Shows entire scene + SHOW_ENTIRE_SCENE click action + Affiche toute la scène + + + Aligns manipulated frame + ALIGN_FRAME click action + Aligne le repère manipulĂ© + + + Aligns camera + ALIGN_CAMERA click action + Aligne la camĂ©ra + + + Camera paths are controlled using the %1 keys (noted <i>Fx</i> below): + Help window key tab camera keys + Les chemins de camĂ©ra sont contrĂ´lĂ©s avec les touches %1 (notĂ©es <i>Fx</i> ci-dessous) : + + + Key(s) + Keys column header in help window mouse tab + Touche(s) + + + Description + Description column header in help window mouse tab + Description + + + Standard viewer keys + In help window keys tab + Raccourcis standards + + + Fx + Generic function key (F1..F12) + Fx + + + Plays path (or resets saved position) + Joue le chemin (ou restaure la position sauvegardĂ©e) + + + Adds a key frame to path (or defines a position) + Ajoute une position clef au chemin (ou dĂ©finit une position) + + + Deletes path (or saved position) + Supprime le chemin (ou la position) + + + Button(s) + Buttons column header in help window mouse tab + Bouton(s) + + + Standard mouse bindings + In help window mouse tab + Actions souris standards + + + Wheel + Mouse wheel + Molette + + + &Help + Help window tab title + &Aide + + + &Keyboard + Help window tab title + &Clavier + + + &Mouse + Help window tab title + &Souris + + + Help + Help window title + Aide + + + Path %1 deleted + Feedback message + Chemin %1 supprimĂ© + + + Position %1 deleted + Feedback message + Position %1 supprimĂ©e + + + Path %1, position %2 added + Feedback message + Chemin %1, position %2 ajoutĂ©e + + + Position %1 saved + Feedback message + Position %1 sauvegardĂ©e + + + Camera in observer mode + Feedback message + CamĂ©ra en mode observateur + + + Camera in fly mode + Feedback message + CamĂ©ra en mode vol + + + Save to file error + Message box window title + Erreur lors de la sauvegarde + + + State file name (%1) references a directory instead of a file. + Le nom du fichier d'Ă©tat (%1) rĂ©fĂ©rence un rĂ©pĂ©rtoire et non un fichier. + + + Unable to create directory %1 + Le rĂ©pĂ©rtoire %1 ne peut Ăªtre créé + + + Unable to save to file %1 + Impossible de sauvegarder le fichier %1 + + + Problem in state restoration + Message box window title + Problème lors de la restauration de l'Ă©tat + + + File %1 is not readable. + Le fichier %1 n'est pas lisible. + + + Open file error + Message box window title + Erreur d'ouverture de fichier + + + Unable to open file %1 + Le fichier %1 ne peut Ăªtre ouvert + + + Left + left mouse button + Gauche + + + Middle + middle mouse button + Milieu + + + Right + right mouse button + Droit + + + double click + Suffix after mouse button + Double clic + + + camera + Suffix after action + la camĂ©ra + + + manipulated frame + Suffix after action + le repère manipulĂ© + + + with + As in : Left button with Ctrl pressed + avec + + + pressed + As in : Left button with Ctrl pressed + enfoncĂ© + + + %1%2%3%4%5%6 + Modifier / button or wheel / double click / with / button / pressed + %1%3%2%4%5%6 + + + No help available. + Pas d'aide disponible. + + + Exporter error + Message box window title + Erreur d'export + + + Unable to open file %1. + Impossible d'ouvrir le ficher %1. + + + BSP Construction + Construction du BSP + + + Exporting to file %1 + Export vers le fichier %1 + + + Parsing feedback buffer. + Parcours du feedback buffer. + + + Topological sort + Tri topologique + + + Advanced topological sort + Tri topologique avancĂ© + + + Rendering... + Rendu... + + + Visibility optimization + Optimisation de la visibilitĂ© + + + &About + Help window about title + Ă€ &propos + + + <h1>libQGLViewer</h1><h3>Version %1</h3><br>A versatile 3D viewer based on OpenGL and Qt<br>Copyright 2002-%2 Gilles Debunne<br><code>%3</code> + <h1>libQGLViewer</h1><h3>Version %1</h3><br>Un afficheur 3D gĂ©nĂ©raliste basĂ© sur OpenGL et Qt<br>Copyright 2002-%2 Gilles Debunne<br><code>%3</code> + + + + VRenderInterface + + Vectorial rendering options + Options de rendu vectoriel + + + Include hidden parts + Inclure les parties cachĂ©es + + + Cull back faces + Supprimer les faces arrières + + + Back faces (non clockwise point ordering) are removed from the output + Les faces orientĂ©es vers l'arrière (points ordonnĂ©s dans le sens anti-horaire) sont supprimĂ©es du rĂ©sultat (Back Face Culling) + + + Black and white + Noir et blanc + + + Black and white rendering + Rendu en noir et blanc + + + Color background + Fond avec une couleur + + + Use current background color instead of white + Utiliser la couleur de fond actuelle Ă  la place du blanc + + + Tighten bounding box + Ajuster la boĂ®te englobante + + + Fit output bounding box to current display + Ajuster la boĂ®te englobante de la sortie Ă  ce qui est actuellement affichĂ© + + + Polygon depth sorting method + MĂ©thode de tri de la profondeur des polygĂ´nes + + + No sorting + Pas de tri + + + Topological + Topologique + + + Advanced topological + Topologique avancĂ© + + + Save + Sauvegarder + + + Cancel + Annuler + + + Hidden polygons are also included in the output (usually twice bigger) + Inclure les polygĂ´nes cachĂ©s dans le rĂ©sultat (alors habituellement deux fois plus gros) + + + Sort method: + MĂ©thode de tri : + + + BSP + BSP + + + diff --git a/QGLViewer/quaternion.cpp b/QGLViewer/quaternion.cpp new file mode 100644 index 0000000..0c30809 --- /dev/null +++ b/QGLViewer/quaternion.cpp @@ -0,0 +1,552 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "domUtils.h" +#include "quaternion.h" +#include // RAND_MAX + +// All the methods are declared inline in Quaternion.h +using namespace qglviewer; +using namespace std; + +/*! Constructs a Quaternion that will rotate from the \p from direction to the \p to direction. + +Note that this rotation is not uniquely defined. The selected axis is usually orthogonal to \p from +and \p to, minimizing the rotation angle. This method is robust and can handle small or almost identical vectors. */ +Quaternion::Quaternion(const Vec& from, const Vec& to) +{ + const qreal epsilon = 1E-10; + + const qreal fromSqNorm = from.squaredNorm(); + const qreal toSqNorm = to.squaredNorm(); + // Identity Quaternion when one vector is null + if ((fromSqNorm < epsilon) || (toSqNorm < epsilon)) + { + q[0]=q[1]=q[2]=0.0; + q[3]=1.0; + } + else + { + Vec axis = cross(from, to); + const qreal axisSqNorm = axis.squaredNorm(); + + // Aligned vectors, pick any axis, not aligned with from or to + if (axisSqNorm < epsilon) + axis = from.orthogonalVec(); + + qreal angle = asin(sqrt(axisSqNorm / (fromSqNorm * toSqNorm))); + + if (from*to < 0.0) + angle = M_PI-angle; + + setAxisAngle(axis, angle); + } +} + +/*! Returns the image of \p v by the Quaternion inverse() rotation. + +rotate() performs an inverse transformation. Same as inverse().rotate(v). */ +Vec Quaternion::inverseRotate(const Vec& v) const +{ + return inverse().rotate(v); +} + +/*! Returns the image of \p v by the Quaternion rotation. + +See also inverseRotate() and operator*(const Quaternion&, const Vec&). */ +Vec Quaternion::rotate(const Vec& v) const +{ + const qreal q00 = 2.0 * q[0] * q[0]; + const qreal q11 = 2.0 * q[1] * q[1]; + const qreal q22 = 2.0 * q[2] * q[2]; + + const qreal q01 = 2.0 * q[0] * q[1]; + const qreal q02 = 2.0 * q[0] * q[2]; + const qreal q03 = 2.0 * q[0] * q[3]; + + const qreal q12 = 2.0 * q[1] * q[2]; + const qreal q13 = 2.0 * q[1] * q[3]; + + const qreal q23 = 2.0 * q[2] * q[3]; + + return Vec((1.0 - q11 - q22)*v[0] + ( q01 - q23)*v[1] + ( q02 + q13)*v[2], + ( q01 + q23)*v[0] + (1.0 - q22 - q00)*v[1] + ( q12 - q03)*v[2], + ( q02 - q13)*v[0] + ( q12 + q03)*v[1] + (1.0 - q11 - q00)*v[2] ); +} + +/*! Set the Quaternion from a (supposedly correct) 3x3 rotation matrix. + + The matrix is expressed in European format: its three \e columns are the images by the rotation of + the three vectors of an orthogonal basis. Note that OpenGL uses a symmetric representation for its + matrices. + + setFromRotatedBasis() sets a Quaternion from the three axis of a rotated frame. It actually fills + the three columns of a matrix with these rotated basis vectors and calls this method. */ +void Quaternion::setFromRotationMatrix(const qreal m[3][3]) +{ + // Compute one plus the trace of the matrix + const qreal onePlusTrace = 1.0 + m[0][0] + m[1][1] + m[2][2]; + + if (onePlusTrace > 1E-5) + { + // Direct computation + const qreal s = sqrt(onePlusTrace) * 2.0; + q[0] = (m[2][1] - m[1][2]) / s; + q[1] = (m[0][2] - m[2][0]) / s; + q[2] = (m[1][0] - m[0][1]) / s; + q[3] = 0.25 * s; + } + else + { + // Computation depends on major diagonal term + if ((m[0][0] > m[1][1])&(m[0][0] > m[2][2])) + { + const qreal s = sqrt(1.0 + m[0][0] - m[1][1] - m[2][2]) * 2.0; + q[0] = 0.25 * s; + q[1] = (m[0][1] + m[1][0]) / s; + q[2] = (m[0][2] + m[2][0]) / s; + q[3] = (m[1][2] - m[2][1]) / s; + } + else + if (m[1][1] > m[2][2]) + { + const qreal s = sqrt(1.0 + m[1][1] - m[0][0] - m[2][2]) * 2.0; + q[0] = (m[0][1] + m[1][0]) / s; + q[1] = 0.25 * s; + q[2] = (m[1][2] + m[2][1]) / s; + q[3] = (m[0][2] - m[2][0]) / s; + } + else + { + const qreal s = sqrt(1.0 + m[2][2] - m[0][0] - m[1][1]) * 2.0; + q[0] = (m[0][2] + m[2][0]) / s; + q[1] = (m[1][2] + m[2][1]) / s; + q[2] = 0.25 * s; + q[3] = (m[0][1] - m[1][0]) / s; + } + } + normalize(); +} + +#ifndef DOXYGEN +void Quaternion::setFromRotationMatrix(const float m[3][3]) +{ + qWarning("setFromRotationMatrix now expects a double[3][3] parameter"); + + qreal mat[3][3]; + for (int i=0; i<3; ++i) + for (int j=0; j<3; ++j) + mat[i][j] = qreal(m[i][j]); + + setFromRotationMatrix(mat); +} + +void Quaternion::setFromRotatedBase(const Vec& X, const Vec& Y, const Vec& Z) +{ + qWarning("setFromRotatedBase is deprecated, use setFromRotatedBasis instead"); + setFromRotatedBasis(X,Y,Z); +} +#endif + +/*! Sets the Quaternion from the three rotated vectors of an orthogonal basis. + + The three vectors do not have to be normalized but must be orthogonal and direct (X^Y=k*Z, with k>0). + + \code + Quaternion q; + q.setFromRotatedBasis(X, Y, Z); + // Now q.rotate(Vec(1,0,0)) == X and q.inverseRotate(X) == Vec(1,0,0) + // Same goes for Y and Z with Vec(0,1,0) and Vec(0,0,1). + \endcode + + See also setFromRotationMatrix() and Quaternion(const Vec&, const Vec&). */ +void Quaternion::setFromRotatedBasis(const Vec& X, const Vec& Y, const Vec& Z) +{ + qreal m[3][3]; + qreal normX = X.norm(); + qreal normY = Y.norm(); + qreal normZ = Z.norm(); + + for (int i=0; i<3; ++i) + { + m[i][0] = X[i] / normX; + m[i][1] = Y[i] / normY; + m[i][2] = Z[i] / normZ; + } + + setFromRotationMatrix(m); +} + +/*! Returns the axis vector and the angle (in radians) of the rotation represented by the Quaternion. + See the axis() and angle() documentations. */ +void Quaternion::getAxisAngle(Vec& axis, qreal& angle) const +{ + angle = 2.0 * acos(q[3]); + axis = Vec(q[0], q[1], q[2]); + const qreal sinus = axis.norm(); + if (sinus > 1E-8) + axis /= sinus; + + if (angle > M_PI) + { + angle = 2.0 * qreal(M_PI) - angle; + axis = -axis; + } +} + +/*! Returns the normalized axis direction of the rotation represented by the Quaternion. + +It is null for an identity Quaternion. See also angle() and getAxisAngle(). */ +Vec Quaternion::axis() const +{ + Vec res = Vec(q[0], q[1], q[2]); + const qreal sinus = res.norm(); + if (sinus > 1E-8) + res /= sinus; + return (acos(q[3]) <= M_PI/2.0) ? res : -res; +} + +/*! Returns the angle (in radians) of the rotation represented by the Quaternion. + + This value is always in the range [0-pi]. Larger rotational angles are obtained by inverting the + axis() direction. + + See also axis() and getAxisAngle(). */ +qreal Quaternion::angle() const +{ + const qreal angle = 2.0 * acos(q[3]); + return (angle <= M_PI) ? angle : 2.0*M_PI - angle; +} + +/*! Returns an XML \c QDomElement that represents the Quaternion. + + \p name is the name of the QDomElement tag. \p doc is the \c QDomDocument factory used to create + QDomElement. + + When output to a file, the resulting QDomElement will look like: + \code + + \endcode + + Use initFromDOMElement() to restore the Quaternion state from the resulting \c QDomElement. See + also the Quaternion(const QDomElement&) constructor. + + See the Vec::domElement() documentation for a complete QDomDocument creation and saving example. + + See also Frame::domElement(), Camera::domElement(), KeyFrameInterpolator::domElement()... */ +QDomElement Quaternion::domElement(const QString& name, QDomDocument& document) const +{ + QDomElement de = document.createElement(name); + de.setAttribute("q0", QString::number(q[0])); + de.setAttribute("q1", QString::number(q[1])); + de.setAttribute("q2", QString::number(q[2])); + de.setAttribute("q3", QString::number(q[3])); + return de; +} + +/*! Restores the Quaternion state from a \c QDomElement created by domElement(). + + The \c QDomElement should contain the \c q0, \c q1 , \c q2 and \c q3 attributes. If one of these + attributes is missing or is not a number, a warning is displayed and these fields are respectively + set to 0.0, 0.0, 0.0 and 1.0 (identity Quaternion). + + See also the Quaternion(const QDomElement&) constructor. */ +void Quaternion::initFromDOMElement(const QDomElement& element) +{ + Quaternion q(element); + *this = q; +} + +/*! Constructs a Quaternion from a \c QDomElement representing an XML code of the form + \code< anyTagName q0=".." q1=".." q2=".." q3=".." />\endcode + + If one of these attributes is missing or is not a number, a warning is displayed and the associated + value is respectively set to 0, 0, 0 and 1 (identity Quaternion). + + See also domElement() and initFromDOMElement(). */ +Quaternion::Quaternion(const QDomElement& element) +{ + QStringList attribute; + attribute << "q0" << "q1" << "q2" << "q3"; + for (int i=0; i +#include + +namespace qglviewer { +/*! \brief The Quaternion class represents 3D rotations and orientations. + \class Quaternion quaternion.h QGLViewer/quaternion.h + + The Quaternion is an appropriate (although not very intuitive) representation for 3D rotations and + orientations. Many tools are provided to ease the definition of a Quaternion: see constructors, + setAxisAngle(), setFromRotationMatrix(), setFromRotatedBasis(). + + You can apply the rotation represented by the Quaternion to 3D points using rotate() and + inverseRotate(). See also the Frame class that represents a coordinate system and provides other + conversion functions like Frame::coordinatesOf() and Frame::transformOf(). + + You can apply the Quaternion \c q rotation to the OpenGL matrices using: + \code + glMultMatrixd(q.matrix()); + // equvalent to glRotate(q.angle()*180.0/M_PI, q.axis().x, q.axis().y, q.axis().z); + \endcode + + Quaternion is part of the \c qglviewer namespace, specify \c qglviewer::Quaternion or use the qglviewer + namespace: \code using namespace qglviewer; \endcode + +

Internal representation

+ + The internal representation of a Quaternion corresponding to a rotation around axis \c axis, with an angle + \c alpha is made of four qreals (i.e. doubles) q[i]: + \code + {q[0],q[1],q[2]} = sin(alpha/2) * {axis[0],axis[1],axis[2]} + q[3] = cos(alpha/2) + \endcode + + Note that certain implementations place the cosine term in first position (instead of last here). + + The Quaternion is always normalized, so that its inverse() is actually its conjugate. + + See also the Vec and Frame classes' documentations. + \nosubgrouping */ +class QGLVIEWER_EXPORT Quaternion +{ +public: + /*! @name Defining a Quaternion */ + //@{ + /*! Default constructor, builds an identity rotation. */ + Quaternion() + { q[0]=q[1]=q[2]=0.0; q[3]=1.0; } + + /*! Constructor from rotation axis (non null) and angle (in radians). See also setAxisAngle(). */ + Quaternion(const Vec& axis, qreal angle) + { + setAxisAngle(axis, angle); + } + + Quaternion(const Vec& from, const Vec& to); + + /*! Constructor from the four values of a Quaternion. First three values are axis*sin(angle/2) and + last one is cos(angle/2). + + \attention The identity Quaternion is Quaternion(0,0,0,1) and \e not Quaternion(0,0,0,0) (which is + not unitary). The default Quaternion() creates such identity Quaternion. */ + Quaternion(qreal q0, qreal q1, qreal q2, qreal q3) + { q[0]=q0; q[1]=q1; q[2]=q2; q[3]=q3; } + + /*! Copy constructor. */ + Quaternion(const Quaternion& Q) + { for (int i=0; i<4; ++i) q[i] = Q.q[i]; } + + /*! Equal operator. */ + Quaternion& operator=(const Quaternion& Q) + { + for (int i=0; i<4; ++i) + q[i] = Q.q[i]; + return (*this); + } + + /*! Sets the Quaternion as a rotation of axis \p axis and angle \p angle (in radians). + + \p axis does not need to be normalized. A null \p axis will result in an identity Quaternion. */ + void setAxisAngle(const Vec& axis, qreal angle) + { + const qreal norm = axis.norm(); + if (norm < 1E-8) + { + // Null rotation + q[0] = 0.0; q[1] = 0.0; q[2] = 0.0; q[3] = 1.0; + } + else + { + const qreal sin_half_angle = sin(angle / 2.0); + q[0] = sin_half_angle*axis[0]/norm; + q[1] = sin_half_angle*axis[1]/norm; + q[2] = sin_half_angle*axis[2]/norm; + q[3] = cos(angle / 2.0); + } + } + + /*! Sets the Quaternion value. See the Quaternion(qreal, qreal, qreal, qreal) constructor documentation. */ + void setValue(qreal q0, qreal q1, qreal q2, qreal q3) + { q[0]=q0; q[1]=q1; q[2]=q2; q[3]=q3; } + +#ifndef DOXYGEN + void setFromRotationMatrix(const float m[3][3]); + void setFromRotatedBase(const Vec& X, const Vec& Y, const Vec& Z); +#endif + void setFromRotationMatrix(const qreal m[3][3]); + void setFromRotatedBasis(const Vec& X, const Vec& Y, const Vec& Z); + //@} + + + /*! @name Accessing values */ + //@{ + Vec axis() const; + qreal angle() const; + void getAxisAngle(Vec& axis, qreal& angle) const; + + /*! Bracket operator, with a constant return value. \p i must range in [0..3]. See the Quaternion(qreal, qreal, qreal, qreal) documentation. */ + qreal operator[](int i) const { return q[i]; } + + /*! Bracket operator returning an l-value. \p i must range in [0..3]. See the Quaternion(qreal, qreal, qreal, qreal) documentation. */ + qreal& operator[](int i) { return q[i]; } + //@} + + + /*! @name Rotation computations */ + //@{ + /*! Returns the composition of the \p a and \p b rotations. + + The order is important. When applied to a Vec \c v (see operator*(const Quaternion&, const Vec&) + and rotate()) the resulting Quaternion acts as if \p b was applied first and then \p a was + applied. This is obvious since the image \c v' of \p v by the composited rotation satisfies: \code + v'= (a*b) * v = a * (b*v) \endcode + + Note that a*b usually differs from b*a. + + \attention For efficiency reasons, the resulting Quaternion is not normalized. Use normalize() in + case of numerical drift with small rotation composition. */ + friend Quaternion operator*(const Quaternion& a, const Quaternion& b) + { + return Quaternion(a.q[3]*b.q[0] + b.q[3]*a.q[0] + a.q[1]*b.q[2] - a.q[2]*b.q[1], + a.q[3]*b.q[1] + b.q[3]*a.q[1] + a.q[2]*b.q[0] - a.q[0]*b.q[2], + a.q[3]*b.q[2] + b.q[3]*a.q[2] + a.q[0]*b.q[1] - a.q[1]*b.q[0], + a.q[3]*b.q[3] - b.q[0]*a.q[0] - a.q[1]*b.q[1] - a.q[2]*b.q[2]); + } + + /*! Quaternion rotation is composed with \p q. + + See operator*(), since this is equivalent to \c this = \c this * \p q. + + \note For efficiency reasons, the resulting Quaternion is not normalized. + You may normalize() it after each application in case of numerical drift. */ + Quaternion& operator*=(const Quaternion &q) + { + *this = (*this)*q; + return *this; + } + + /*! Returns the image of \p v by the rotation \p q. + + Same as q.rotate(v). See rotate() and inverseRotate(). */ + friend Vec operator*(const Quaternion& q, const Vec& v) + { + return q.rotate(v); + } + + Vec rotate(const Vec& v) const; + Vec inverseRotate(const Vec& v) const; + //@} + + + /*! @name Inversion */ + //@{ + /*! Returns the inverse Quaternion (inverse rotation). + + Result has a negated axis() direction and the same angle(). A composition (see operator*()) of a + Quaternion and its inverse() results in an identity function. + + Use invert() to actually modify the Quaternion. */ + Quaternion inverse() const { return Quaternion(-q[0], -q[1], -q[2], q[3]); } + + /*! Inverses the Quaternion (same rotation angle(), but negated axis()). + + See also inverse(). */ + void invert() { q[0] = -q[0]; q[1] = -q[1]; q[2] = -q[2]; } + + /*! Negates all the coefficients of the Quaternion. + + This results in an other representation of the \e same rotation (opposite rotation angle, but with + a negated axis direction: the two cancel out). However, note that the results of axis() and + angle() are unchanged after a call to this method since angle() always returns a value in [0,pi]. + + This method is mainly useful for Quaternion interpolation, so that the spherical + interpolation takes the shortest path on the unit sphere. See slerp() for details. */ + void negate() { invert(); q[3] = -q[3]; } + + /*! Normalizes the Quaternion coefficients. + + This method should not need to be called since we only deal with unit Quaternions. This is however + useful to prevent numerical drifts, especially with small rotational increments. See also + normalized(). */ + qreal normalize() + { + const qreal norm = sqrt(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]); + for (int i=0; i<4; ++i) + q[i] /= norm; + return norm; + } + + /*! Returns a normalized version of the Quaternion. + + See also normalize(). */ + Quaternion normalized() const + { + qreal Q[4]; + const qreal norm = sqrt(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]); + for (int i=0; i<4; ++i) + Q[i] = q[i] / norm; + return Quaternion(Q[0], Q[1], Q[2], Q[3]); + } + //@} + + + /*! @name Associated matrix */ + //@{ + const GLdouble* matrix() const; + void getMatrix(GLdouble m[4][4]) const; + void getMatrix(GLdouble m[16]) const; + + void getRotationMatrix(qreal m[3][3]) const; + + const GLdouble* inverseMatrix() const; + void getInverseMatrix(GLdouble m[4][4]) const; + void getInverseMatrix(GLdouble m[16]) const; + + void getInverseRotationMatrix(qreal m[3][3]) const; + //@} + + + /*! @name Slerp interpolation */ + //@{ + static Quaternion slerp(const Quaternion& a, const Quaternion& b, qreal t, bool allowFlip=true); + static Quaternion squad(const Quaternion& a, const Quaternion& tgA, const Quaternion& tgB, const Quaternion& b, qreal t); + /*! Returns the "dot" product of \p a and \p b: a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3]. */ + static qreal dot(const Quaternion& a, const Quaternion& b) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3]; } + + Quaternion log(); + Quaternion exp(); + static Quaternion lnDif(const Quaternion& a, const Quaternion& b); + static Quaternion squadTangent(const Quaternion& before, const Quaternion& center, const Quaternion& after); + //@} + + /*! @name Random Quaternion */ + //@{ + static Quaternion randomQuaternion(); + //@} + + /*! @name XML representation */ + //@{ + explicit Quaternion(const QDomElement& element); + QDomElement domElement(const QString& name, QDomDocument& document) const; + void initFromDOMElement(const QDomElement& element); + //@} + +#ifdef DOXYGEN + /*! @name Output stream */ + //@{ + /*! Output stream operator. Enables debugging code like: + \code + Quaternion rot(...); + cout << "Rotation=" << rot << endl; + \endcode */ + std::ostream& operator<<(std::ostream& o, const qglviewer::Vec&); + //@} +#endif + +private: + /*! The internal data representation is private, use operator[] to access values. */ + qreal q[4]; +}; + +} // namespace + +std::ostream& operator<<(std::ostream& o, const qglviewer::Quaternion&); + +#endif // QGLVIEWER_QUATERNION_H diff --git a/QGLViewer/saveSnapshot.cpp b/QGLViewer/saveSnapshot.cpp new file mode 100644 index 0000000..5290010 --- /dev/null +++ b/QGLViewer/saveSnapshot.cpp @@ -0,0 +1,494 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "qglviewer.h" + +#include "ui_ImageInterface.h" + +// Output format list +# include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +////// Static global variables - local to this file ////// +// List of available output file formats, formatted for QFileDialog. +static QString formats; +// Converts QFileDialog resulting format to Qt snapshotFormat. +static QMap Qtformat; +// Converts Qt snapshotFormat to QFileDialog menu string. +static QMap FDFormatString; +// Converts snapshotFormat to file extension +static QMap extension; + + +/*! Sets snapshotFileName(). */ +void QGLViewer::setSnapshotFileName(const QString& name) +{ + snapshotFileName_ = QFileInfo(name).absoluteFilePath(); +} + +#ifndef DOXYGEN +const QString& QGLViewer::snapshotFilename() const +{ + qWarning("snapshotFilename is deprecated. Use snapshotFileName() (uppercase N) instead."); + return snapshotFileName(); +} +#endif + + +/*! Opens a dialog that displays the different available snapshot formats. + +Then calls setSnapshotFormat() with the selected one (unless the user cancels). + +Returns \c false if the user presses the Cancel button and \c true otherwise. */ +bool QGLViewer::openSnapshotFormatDialog() +{ + bool ok = false; + QStringList list = formats.split(";;", QString::SkipEmptyParts); + int current = list.indexOf(FDFormatString[snapshotFormat()]); + QString format = QInputDialog::getItem(this, "Snapshot format", "Select a snapshot format", list, current, false, &ok); + if (ok) + setSnapshotFormat(Qtformat[format]); + return ok; +} + + +// Finds all available Qt output formats, so that they can be available in +// saveSnapshot dialog. Initialize snapshotFormat() to the first one. +void QGLViewer::initializeSnapshotFormats() +{ + QList list = QImageWriter::supportedImageFormats(); + QStringList formatList; + for (int i=0; i < list.size(); ++i) + formatList << QString(list.at(i).toUpper()); + // qWarning("Available image formats: "); + // QStringList::Iterator it = formatList.begin(); + // while( it != formatList.end() ) + // qWarning((*it++).); QT4 change this. qWarning no longer accepts QString + + // Check that the interesting formats are available and add them in "formats" + // Unused formats: XPM XBM PBM PGM + QStringList QtText, MenuText, Ext; + QtText += "JPEG"; MenuText += "JPEG (*.jpg)"; Ext += "jpg"; + QtText += "PNG"; MenuText += "PNG (*.png)"; Ext += "png"; + QtText += "EPS"; MenuText += "Encapsulated Postscript (*.eps)"; Ext += "eps"; + QtText += "PS"; MenuText += "Postscript (*.ps)"; Ext += "ps"; + QtText += "PPM"; MenuText += "24bit RGB Bitmap (*.ppm)"; Ext += "ppm"; + QtText += "BMP"; MenuText += "Windows Bitmap (*.bmp)"; Ext += "bmp"; + QtText += "XFIG"; MenuText += "XFig (*.fig)"; Ext += "fig"; + + QStringList::iterator itText = QtText.begin(); + QStringList::iterator itMenu = MenuText.begin(); + QStringList::iterator itExt = Ext.begin(); + + while (itText != QtText.end()) + { + //QMessageBox::information(this, "Snapshot ", "Trying format\n"+(*itText)); + if (formatList.contains((*itText))) + { + //QMessageBox::information(this, "Snapshot ", "Recognized format\n"+(*itText)); + if (formats.isEmpty()) + setSnapshotFormat(*itText); + else + formats += ";;"; + formats += (*itMenu); + Qtformat[(*itMenu)] = (*itText); + FDFormatString[(*itText)] = (*itMenu); + extension[(*itText)] = (*itExt); + } + // Synchronize parsing + itText++; + itMenu++; + itExt++; + } +} + +// Returns false if the user refused to use the fileName +static bool checkFileName(QString& fileName, QWidget* widget, const QString& snapshotFormat) +{ + if (fileName.isEmpty()) + return false; + + // Check that extension has been provided + QFileInfo info(fileName); + + if (info.suffix().isEmpty()) + { + // No extension given. Silently add one + if (fileName.right(1) != ".") + fileName += "."; + fileName += extension[snapshotFormat]; + info.setFile(fileName); + } + else if (info.suffix() != extension[snapshotFormat]) + { + // Extension is not appropriate. Propose a modification + QString modifiedName = info.absolutePath() + '/' + info.baseName() + "." + extension[snapshotFormat]; + QFileInfo modifInfo(modifiedName); + int i=(QMessageBox::warning(widget,"Wrong extension", + info.fileName()+" has a wrong extension.\nSave as "+modifInfo.fileName()+" instead ?", + QMessageBox::Yes, + QMessageBox::No, + QMessageBox::Cancel)); + if (i==QMessageBox::Cancel) + return false; + + if (i==QMessageBox::Yes) + { + fileName = modifiedName; + info.setFile(fileName); + } + } + + return true; +} + +class ImageInterface: public QDialog, public Ui::ImageInterface +{ +public: ImageInterface(QWidget *parent) : QDialog(parent) { setupUi(this); } +}; + + +// Pops-up an image settings dialog box and save to fileName. +// Returns false in case of problem. +bool QGLViewer::saveImageSnapshot(const QString& fileName) +{ + static ImageInterface* imageInterface = NULL; + + if (!imageInterface) + imageInterface = new ImageInterface(this); + + imageInterface->imgWidth->setValue(width()); + imageInterface->imgHeight->setValue(height()); + + imageInterface->imgQuality->setValue(snapshotQuality()); + + if (imageInterface->exec() == QDialog::Rejected) + return true; + + // Hide closed dialog + qApp->processEvents(); + + setSnapshotQuality(imageInterface->imgQuality->value()); + + QColor previousBGColor = backgroundColor(); + if (imageInterface->whiteBackground->isChecked()) + setBackgroundColor(Qt::white); + + QSize finalSize(imageInterface->imgWidth->value(), imageInterface->imgHeight->value()); + + qreal oversampling = imageInterface->oversampling->value(); + QSize subSize(int(this->width()/oversampling), int(this->height()/oversampling)); + + qreal aspectRatio = width() / static_cast(height()); + qreal newAspectRatio = finalSize.width() / static_cast(finalSize.height()); + + qreal zNear = camera()->zNear(); + //qreal zFar = camera()->zFar(); + + qreal xMin, yMin; + bool expand = imageInterface->expandFrustum->isChecked(); + if (camera()->type() == qglviewer::Camera::PERSPECTIVE) + if ((expand && (newAspectRatio>aspectRatio)) || (!expand && (newAspectRatiofieldOfView() / 2.0); + xMin = newAspectRatio * yMin; + } + else + { + xMin = zNear * tan(camera()->fieldOfView() / 2.0) * aspectRatio; + yMin = xMin / newAspectRatio; + } + else + { + camera()->getOrthoWidthHeight(xMin, yMin); + if ((expand && (newAspectRatio>aspectRatio)) || (!expand && (newAspectRatio(finalSize.width()); + qreal scaleY = subSize.height() / static_cast(finalSize.height()); + + //qreal deltaX = 2.0 * xMin * scaleX; + //qreal deltaY = 2.0 * yMin * scaleY; + + int nbX = finalSize.width() / subSize.width(); + int nbY = finalSize.height() / subSize.height(); + + // Extra subimage on the right/bottom border(s) if needed + if (nbX * subSize.width() < finalSize.width()) + nbX++; + if (nbY * subSize.height() < finalSize.height()) + nbY++; + + makeCurrent(); + + // tileRegion_ is used by startScreenCoordinatesSystem to appropriately set the local + // coordinate system when tiling + tileRegion_ = new TileRegion(); + qreal tileXMin, tileWidth, tileYMin, tileHeight; + if ((expand && (newAspectRatio>aspectRatio)) || (!expand && (newAspectRatiotextScale = 1.0 / scaleY; + } + else + { + qreal tileTotalHeight = width() / newAspectRatio; + tileYMin = (height() - tileTotalHeight) / 2.0; + tileHeight = tileTotalHeight * scaleY; + tileXMin = 0.0; + tileWidth = width() * scaleX; + tileRegion_->textScale = 1.0 / scaleX; + } + + int count=0; + for (int i=0; ixMin = tileXMin + i * tileWidth; + tileRegion_->xMax = tileXMin + (i+1) * tileWidth; + tileRegion_->yMin = tileYMin + j * tileHeight; + tileRegion_->yMax = tileYMin + (j+1) * tileHeight; + + draw(); + postDraw(); + + // ProgressDialog::hideProgressDialog(); + // qApp->processEvents(); + + QImage snapshot = grabFramebuffer(); + + // ProgressDialog::showProgressDialog(this); + // ProgressDialog::updateProgress(count / (qreal)(nbX*nbY), + // "Generating image ["+QString::number(count)+"/"+QString::number(nbX*nbY)+"]"); + // qApp->processEvents(); + + QImage subImage = snapshot.scaled(subSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + + // Copy subImage in image + for (int ii=0; iiwhiteBackground->isChecked()) + setBackgroundColor(previousBGColor); + + return saveOK; +} + + +/*! Saves a snapshot of the current image displayed by the widget. + + Options are set using snapshotFormat(), snapshotFileName() and snapshotQuality(). For non vectorial + image formats, the image size is equal to the current viewer's dimensions (see width() and + height()). See snapshotFormat() for details on supported formats. + + If \p automatic is \c false (or if snapshotFileName() is empty), a file dialog is opened to ask for + the file name. + + When \p automatic is \c true, the file name is set to \c NAME-NUMBER, where \c NAME is + snapshotFileName() and \c NUMBER is snapshotCounter(). The snapshotCounter() is automatically + incremented after each snapshot saving. This is useful to create videos from your application: + \code + void Viewer::init() + { + resize(720, 576); // PAL DV format (use 720x480 for NTSC DV) + connect(this, SIGNAL(drawFinished(bool)), SLOT(saveSnapshot(bool))); + } + \endcode + Then call draw() in a loop (for instance using animate() and/or a camera() KeyFrameInterpolator + replay) to create your image sequence. + + If you want to create a Quicktime VR panoramic sequence, simply use code like this: + \code + void Viewer::createQuicktime() + { + const int nbImages = 36; + for (int i=0; isetOrientation(2.0*M_PI/nbImages, 0.0); // Theta-Phi orientation + showEntireScene(); + update(); // calls draw(), which emits drawFinished(), which calls saveSnapshot() + } + } + \endcode + + If snapshotCounter() is negative, no number is appended to snapshotFileName() and the + snapshotCounter() is not incremented. This is useful to force the creation of a file, overwriting + the previous one. + + When \p overwrite is set to \c false (default), a window asks for confirmation if the file already + exists. In \p automatic mode, the snapshotCounter() is incremented (if positive) until a + non-existing file name is found instead. Otherwise the file is overwritten without confirmation. + + The VRender library was written by Cyril Soler (Cyril dot Soler at imag dot fr). If the generated + PS or EPS file is not properly displayed, remove the anti-aliasing option in your postscript viewer. + + \note In order to correctly grab the frame buffer, the QGLViewer window is raised in front of + other windows by this method. */ +void QGLViewer::saveSnapshot(bool automatic, bool overwrite) +{ + // Ask for file name + if (snapshotFileName().isEmpty() || !automatic) + { + QString fileName; + QString selectedFormat = FDFormatString[snapshotFormat()]; + fileName = QFileDialog::getSaveFileName(this, "Choose a file name to save under", snapshotFileName(), formats, &selectedFormat, + overwrite?QFileDialog::DontConfirmOverwrite:QFlags(0)); + setSnapshotFormat(Qtformat[selectedFormat]); + + if (checkFileName(fileName, this, snapshotFormat())) + setSnapshotFileName(fileName); + else + return; + } + + QFileInfo fileInfo(snapshotFileName()); + + if ((automatic) && (snapshotCounter() >= 0)) + { + // In automatic mode, names have a number appended + const QString baseName = fileInfo.baseName(); + QString count; + count.sprintf("%.04d", snapshotCounter_++); + QString suffix; + suffix = fileInfo.suffix(); + if (suffix.isEmpty()) + suffix = extension[snapshotFormat()]; + fileInfo.setFile(fileInfo.absolutePath()+ '/' + baseName + '-' + count + '.' + suffix); + + if (!overwrite) + while (fileInfo.exists()) + { + count.sprintf("%.04d", snapshotCounter_++); + fileInfo.setFile(fileInfo.absolutePath() + '/' +baseName + '-' + count + '.' + fileInfo.suffix()); + } + } + + bool saveOK; + if (automatic) + { + QImage snapshot = frameBufferSnapshot(); + saveOK = snapshot.save(fileInfo.filePath(), snapshotFormat().toLatin1().constData(), snapshotQuality()); + } + else + saveOK = saveImageSnapshot(fileInfo.filePath()); + + if (!saveOK) + QMessageBox::warning(this, "Snapshot problem", "Unable to save snapshot in\n"+fileInfo.filePath()); +} + +QImage QGLViewer::frameBufferSnapshot() +{ + // Viewer must be on top of other windows. + makeCurrent(); + raise(); + // Hack: Qt has problems if the frame buffer is grabbed after QFileDialog is displayed. + // We grab the frame buffer before, even if it might be not necessary (vectorial rendering). + // The problem could not be reproduced on a simple example to submit a Qt bug. + // However, only grabs the backgroundImage in the eponym example. May come from the driver. + return grabFramebuffer(); +} + +/*! Same as saveSnapshot(), except that it uses \p fileName instead of snapshotFileName(). + + If \p fileName is empty, opens a file dialog to select the name. + + Snapshot settings are set from snapshotFormat() and snapshotQuality(). + + Asks for confirmation when \p fileName already exists and \p overwrite is \c false (default). + + \attention If \p fileName is a char* (as is "myFile.jpg"), it may be casted into a \c bool, and the + other saveSnapshot() method may be used instead. Pass QString("myFile.jpg") as a parameter to + prevent this. */ +void QGLViewer::saveSnapshot(const QString& fileName, bool overwrite) +{ + const QString previousName = snapshotFileName(); + const int previousCounter = snapshotCounter(); + setSnapshotFileName(fileName); + setSnapshotCounter(-1); + saveSnapshot(true, overwrite); + setSnapshotFileName(previousName); + setSnapshotCounter(previousCounter); +} + +/*! Takes a snapshot of the current display and pastes it to the clipboard. + +This action is activated by the KeyboardAction::SNAPSHOT_TO_CLIPBOARD enum, binded to \c Ctrl+C by default. +*/ +void QGLViewer::snapshotToClipboard() +{ + QClipboard *cb = QApplication::clipboard(); + cb->setImage(frameBufferSnapshot()); +} + diff --git a/QGLViewer/vec.cpp b/QGLViewer/vec.cpp new file mode 100644 index 0000000..669d6f7 --- /dev/null +++ b/QGLViewer/vec.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "domUtils.h" +#include "vec.h" + +// Most of the methods are declared inline in vec.h + +using namespace qglviewer; +using namespace std; + +/*! Projects the Vec on the axis of direction \p direction that passes through the origin. + +\p direction does not need to be normalized (but must be non null). */ +void Vec::projectOnAxis(const Vec& direction) +{ +#ifndef QT_NO_DEBUG + if (direction.squaredNorm() < 1.0E-10) + qWarning("Vec::projectOnAxis: axis direction is not normalized (norm=%f).", direction.norm()); +#endif + + *this = (((*this)*direction) / direction.squaredNorm()) * direction; +} + +/*! Projects the Vec on the plane whose normal is \p normal that passes through the origin. + +\p normal does not need to be normalized (but must be non null). */ +void Vec::projectOnPlane(const Vec& normal) +{ +#ifndef QT_NO_DEBUG + if (normal.squaredNorm() < 1.0E-10) + qWarning("Vec::projectOnPlane: plane normal is not normalized (norm=%f).", normal.norm()); +#endif + + *this -= (((*this)*normal) / normal.squaredNorm()) * normal; +} + +/*! Returns a Vec orthogonal to the Vec. Its norm() depends on the Vec, but is zero only for a + null Vec. Note that the function that associates an orthogonalVec() to a Vec is not continous. */ +Vec Vec::orthogonalVec() const +{ + // Find smallest component. Keep equal case for null values. + if ((fabs(y) >= 0.9*fabs(x)) && (fabs(z) >= 0.9*fabs(x))) + return Vec(0.0, -z, y); + else + if ((fabs(x) >= 0.9*fabs(y)) && (fabs(z) >= 0.9*fabs(y))) + return Vec(-z, 0.0, x); + else + return Vec(-y, x, 0.0); +} + +/*! Constructs a Vec from a \c QDomElement representing an XML code of the form + \code< anyTagName x=".." y=".." z=".." />\endcode + +If one of these attributes is missing or is not a number, a warning is displayed and the associated +value is set to 0.0. + +See also domElement() and initFromDOMElement(). */ +Vec::Vec(const QDomElement& element) +{ + QStringList attribute; + attribute << "x" << "y" << "z"; + for (int i=0; ioperator[](i) = DomUtils::qrealFromDom(element, attribute[i], 0.0); +#else + v_[i] = DomUtils::qrealFromDom(element, attribute[i], 0.0); +#endif +} + +/*! Returns an XML \c QDomElement that represents the Vec. + + \p name is the name of the QDomElement tag. \p doc is the \c QDomDocument factory used to create + QDomElement. + + When output to a file, the resulting QDomElement will look like: + \code + + \endcode + + Use initFromDOMElement() to restore the Vec state from the resulting \c QDomElement. See also the + Vec(const QDomElement&) constructor. + + Here is complete example that creates a QDomDocument and saves it into a file: + \code + Vec sunPos; + QDomDocument document("myDocument"); + QDomElement sunElement = document.createElement("Sun"); + document.appendChild(sunElement); + sunElement.setAttribute("brightness", sunBrightness()); + sunElement.appendChild(sunPos.domElement("sunPosition", document)); + // Other additions to the document hierarchy... + + // Save doc document + QFile f("myFile.xml"); + if (f.open(IO_WriteOnly)) + { + QTextStream out(&f); + document.save(out, 2); + f.close(); + } + \endcode + + See also Quaternion::domElement(), Frame::domElement(), Camera::domElement()... */ +QDomElement Vec::domElement(const QString& name, QDomDocument& document) const +{ + QDomElement de = document.createElement(name); + de.setAttribute("x", QString::number(x)); + de.setAttribute("y", QString::number(y)); + de.setAttribute("z", QString::number(z)); + return de; +} + +/*! Restores the Vec state from a \c QDomElement created by domElement(). + + The \c QDomElement should contain \c x, \c y and \c z attributes. If one of these attributes is + missing or is not a number, a warning is displayed and the associated value is set to 0.0. + + To restore the Vec state from an xml file, use: + \code + // Load DOM from file + QDomDocument doc; + QFile f("myFile.xml"); + if (f.open(IO_ReadOnly)) + { + doc.setContent(&f); + f.close(); + } + // Parse the DOM tree and initialize + QDomElement main=doc.documentElement(); + myVec.initFromDOMElement(main); + \endcode + + See also the Vec(const QDomElement&) constructor. */ +void Vec::initFromDOMElement(const QDomElement& element) +{ + const Vec v(element); + *this = v; +} + +ostream& operator<<(ostream& o, const Vec& v) +{ + return o << v.x << '\t' << v.y << '\t' << v.z; +} + diff --git a/QGLViewer/vec.h b/QGLViewer/vec.h new file mode 100644 index 0000000..429ab62 --- /dev/null +++ b/QGLViewer/vec.h @@ -0,0 +1,390 @@ +/**************************************************************************** + + Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.6.3. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#ifndef QGLVIEWER_VEC_H +#define QGLVIEWER_VEC_H + +#include +#include + +# include + +// Included by all files as vec.h is at the end of the include hierarchy +#include "config.h" // Specific configuration options. + +namespace qglviewer { + +/*! \brief The Vec class represents 3D positions and 3D vectors. + \class Vec vec.h QGLViewer/vec.h + + Vec is used as a parameter and return type by many methods of the library. It provides classical + algebraic computational methods and is compatible with OpenGL: + + \code + // Draws a point located at 3.0 OpenGL units in front of the camera + Vec pos = camera()->position() + 3.0 * camera()->viewDirection(); + glBegin(GL_POINTS); + glVertex3fv(pos); + glEnd(); + \endcode + + This makes of Vec a good candidate for representing positions and vectors in your programs. Since + it is part of the \c qglviewer namespace, specify \c qglviewer::Vec or use the qglviewer + namespace: + \code + using namespace qglviewer; + \endcode + +

Interface with other vector classes

+ + Vec implements a universal explicit converter, based on the \c [] \c operator. + Everywhere a \c const \c Vec& argument is expected, you can use your own vector type + instead, as long as it implements this operator (see the Vec(const C& c) documentation). + + See also the Quaternion and the Frame documentations. + \nosubgrouping */ +class QGLVIEWER_EXPORT Vec +{ + + // If your compiler complains the "The class "qglviewer::Vec" has no member "x"." + // Add your architecture Q_OS_XXXX flag (see qglobal.h) in this list. +#if defined (Q_OS_IRIX) || defined (Q_OS_AIX) || defined (Q_OS_HPUX) +# define QGLVIEWER_UNION_NOT_SUPPORTED +#endif + +public: + /*! The internal data representation is public. One can use v.x, v.y, v.z. See also operator[](). */ +#if defined (DOXYGEN) || defined (QGLVIEWER_UNION_NOT_SUPPORTED) + qreal x, y, z; +#else + union + { + struct { qreal x, y, z; }; + qreal v_[3]; + }; +#endif + + /*! @name Setting the value */ + //@{ + /*! Default constructor. Value is set to (0,0,0). */ + Vec() : x(0.0), y(0.0), z(0.0) {} + + /*! Standard constructor with the x, y and z values. */ + Vec(qreal X, qreal Y, qreal Z) : x(X), y(Y), z(Z) {} + + /*! Universal explicit converter from any class to Vec. You can use your own vector class everywhere + a \c const \c Vec& parameter is required, as long as it implements the \c operator[ ]: + + \code + class MyVec + { + // ... + qreal operator[](int i) const { returns x, y or z when i=0, 1 or 2; } + } + + MyVec v(...); + camera()->setPosition(v); + \endcode + + Note that standard vector types (STL, \c qreal[3], ...) implement this operator and can hence + be used in place of Vec. See also operator const qreal*() .*/ + template + explicit Vec(const C& c) : x(c[0]), y(c[1]), z(c[2]) {} + // Should NOT be explicit to prevent conflicts with operator<<. + + // ! Copy constructor + // Vec(const Vec& v) : x(v.x), y(v.y), z(v.z) {} + + /*! Equal operator. */ + Vec& operator=(const Vec& v) + { + x = v.x; y = v.y; z = v.z; + return *this; + } + + /*! Set the current value. May be faster than using operator=() with a temporary Vec(x,y,z). */ + void setValue(qreal X, qreal Y, qreal Z) + { x=X; y=Y; z=Z; } + + // Universal equal operator which allows the use of any type in place of Vec, + // as long as the [] operator is implemented (v[0]=v.x, v[1]=v.y, v[2]=v.z). + // template + // Vec& operator=(const C& c) + // { + // x=c[0]; y=c[1]; z=c[2]; + // return *this; + // } + //@} + + /*! @name Accessing the value */ + //@{ + /*! Bracket operator, with a constant return value. \p i must range in [0..2]. */ + qreal operator[](int i) const { +#ifdef QGLVIEWER_UNION_NOT_SUPPORTED + return (&x)[i]; +#else + return v_[i]; +#endif + } + + /*! Bracket operator returning an l-value. \p i must range in [0..2]. */ + qreal& operator[](int i) { +#ifdef QGLVIEWER_UNION_NOT_SUPPORTED + return (&x)[i]; +#else + return v_[i]; +#endif + } + +#ifndef DOXYGEN + /*! This method is deprecated since version 2.0. Use operator const double* instead. */ + const double* address() const { qWarning("Vec::address() is deprecated, use operator const double* instead."); return operator const double*(); } +#endif + + /*! Conversion operator returning the memory address of the vector. + + Very convenient to pass a Vec pointer as a parameter to \c GLdouble OpenGL functions: + \code + Vec pos, normal; + glNormal3dv(normal); + glVertex3dv(pos); + \endcode */ + operator const double*() const { +#ifdef QGLVIEWER_UNION_NOT_SUPPORTED + return &x; +#else + return v_; +#endif + } + + /*! Non const conversion operator returning the memory address of the vector. + + Useful to pass a Vec to a method that requires and fills a \c double*, as provided by certain libraries. */ + operator double*() { +#ifdef QGLVIEWER_UNION_NOT_SUPPORTED + return &x; +#else + return v_; +#endif + } + + /*! Conversion operator returning the memory address of the vector. + + Very convenient to pass a Vec pointer as a \c float parameter to OpenGL functions: + \code + Vec pos, normal; + glNormal3fv(normal); + glVertex3fv(pos); + \endcode + \note The returned float array is a static shared by all \c Vec instances. */ + operator const float*() const { + static float* const result = new float[3]; + result[0] = (float)x; + result[1] = (float)y; + result[2] = (float)z; + return result; + } + //@} + + /*! @name Algebraic computations */ + //@{ + /*! Returns the sum of the two vectors. */ + friend Vec operator+(const Vec &a, const Vec &b) + { + return Vec(a.x+b.x, a.y+b.y, a.z+b.z); + } + + /*! Returns the difference of the two vectors. */ + friend Vec operator-(const Vec &a, const Vec &b) + { + return Vec(a.x-b.x, a.y-b.y, a.z-b.z); + } + + /*! Unary minus operator. */ + friend Vec operator-(const Vec &a) + { + return Vec(-a.x, -a.y, -a.z); + } + + /*! Returns the product of the vector with a scalar. */ + friend Vec operator*(const Vec &a, qreal k) + { + return Vec(a.x*k, a.y*k, a.z*k); + } + + /*! Returns the product of a scalar with the vector. */ + friend Vec operator*(qreal k, const Vec &a) + { + return a*k; + } + + /*! Returns the division of the vector with a scalar. + + Too small \p k values are \e not tested (unless the library was compiled with the "debug" Qt \c + CONFIG flag) and may result in \c NaN values. */ + friend Vec operator/(const Vec &a, qreal k) + { +#ifndef QT_NO_DEBUG + if (fabs(k) < 1.0E-10) + qWarning("Vec::operator / : dividing by a null value (%f)", k); +#endif + return Vec(a.x/k, a.y/k, a.z/k); + } + + /*! Returns \c true only when the two vector are not equal (see operator==()). */ + friend bool operator!=(const Vec &a, const Vec &b) + { + return !(a==b); + } + + /*! Returns \c true when the squaredNorm() of the difference vector is lower than 1E-10. */ + friend bool operator==(const Vec &a, const Vec &b) + { + const qreal epsilon = 1.0E-10; + return (a-b).squaredNorm() < epsilon; + } + + /*! Adds \p a to the vector. */ + Vec& operator+=(const Vec &a) + { + x += a.x; y += a.y; z += a.z; + return *this; + } + + /*! Subtracts \p a to the vector. */ + Vec& operator-=(const Vec &a) + { + x -= a.x; y -= a.y; z -= a.z; + return *this; + } + + /*! Multiply the vector by a scalar \p k. */ + Vec& operator*=(qreal k) + { + x *= k; y *= k; z *= k; + return *this; + } + + /*! Divides the vector by a scalar \p k. + + An absolute \p k value lower than 1E-10 will print a warning if the library was compiled with the + "debug" Qt \c CONFIG flag. Otherwise, no test is performed for efficiency reasons. */ + Vec& operator/=(qreal k) + { +#ifndef QT_NO_DEBUG + if (fabs(k)<1.0E-10) + qWarning("Vec::operator /= : dividing by a null value (%f)", k); +#endif + x /= k; y /= k; z /= k; + return *this; + } + + /*! Dot product of the two Vec. */ + friend qreal operator*(const Vec &a, const Vec &b) + { + return a.x*b.x + a.y*b.y + a.z*b.z; + } + + /*! Cross product of the two vectors. Same as cross(). */ + friend Vec operator^(const Vec &a, const Vec &b) + { + return cross(a,b); + } + + /*! Cross product of the two Vec. Mind the order ! */ + friend Vec cross(const Vec &a, const Vec &b) + { + return Vec(a.y*b.z - a.z*b.y, + a.z*b.x - a.x*b.z, + a.x*b.y - a.y*b.x); + } + + Vec orthogonalVec() const; + //@} + + /*! @name Norm of the vector */ + //@{ +#ifndef DOXYGEN + /*! This method is deprecated since version 2.0. Use squaredNorm() instead. */ + qreal sqNorm() const { return x*x + y*y + z*z; } +#endif + + /*! Returns the \e squared norm of the Vec. */ + qreal squaredNorm() const { return x*x + y*y + z*z; } + + /*! Returns the norm of the vector. */ + qreal norm() const { return sqrt(x*x + y*y + z*z); } + + /*! Normalizes the Vec and returns its original norm. + + Normalizing a null vector will result in \c NaN values. */ + qreal normalize() + { + const qreal n = norm(); +#ifndef QT_NO_DEBUG + if (n < 1.0E-10) + qWarning("Vec::normalize: normalizing a null vector (norm=%f)", n); +#endif + *this /= n; + return n; + } + + /*! Returns a unitary (normalized) \e representation of the vector. The original Vec is not modified. */ + Vec unit() const + { + Vec v = *this; + v.normalize(); + return v; + } + //@} + + /*! @name Projection */ + //@{ + void projectOnAxis(const Vec& direction); + void projectOnPlane(const Vec& normal); + //@} + + /*! @name XML representation */ + //@{ + explicit Vec(const QDomElement& element); + QDomElement domElement(const QString& name, QDomDocument& document) const; + void initFromDOMElement(const QDomElement& element); + //@} + +#ifdef DOXYGEN + /*! @name Output stream */ + //@{ + /*! Output stream operator. Enables debugging code like: + \code + Vec pos(...); + cout << "Position=" << pos << endl; + \endcode */ + std::ostream& operator<<(std::ostream& o, const qglviewer::Vec&); + //@} +#endif +}; + +} // namespace + +std::ostream& operator<<(std::ostream& o, const qglviewer::Vec&); + +#endif // QGLVIEWER_VEC_H diff --git a/log750-lab.pro b/log750-lab.pro new file mode 100644 index 0000000..6d7ba5b --- /dev/null +++ b/log750-lab.pro @@ -0,0 +1,73 @@ +#------------------------------------------------- +# +# Project created by QtCreator +# +#------------------------------------------------- + +QT += core gui xml opengl + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = simpleViewer +TEMPLATE = app + +QMAKE_CXXFLAGS += -std=c++0x + +SOURCES += QGLViewer/camera.cpp \ + QGLViewer/constraint.cpp \ + QGLViewer/frame.cpp \ + QGLViewer/keyFrameInterpolator.cpp \ + QGLViewer/manipulatedCameraFrame.cpp \ + QGLViewer/manipulatedFrame.cpp \ + QGLViewer/mouseGrabber.cpp \ + QGLViewer/qglviewer.cpp \ + QGLViewer/quaternion.cpp \ + QGLViewer/saveSnapshot.cpp \ + QGLViewer/vec.cpp \ + src/main.cpp \ + src/window/mainwindow.cpp \ + src/viewer/simpleViewer.cpp \ + src/glnodes/glnode.cpp \ + src/glnodes/shapes.cpp \ + src/glnodes/scenegroup.cpp + +HEADERS += QGLViewer/camera.h \ + QGLViewer/config.h \ + QGLViewer/constraint.h \ + QGLViewer/domUtils.h \ + QGLViewer/frame.h \ + QGLViewer/keyFrameInterpolator.h \ + QGLViewer/manipulatedCameraFrame.h \ + QGLViewer/manipulatedFrame.h \ + QGLViewer/mouseGrabber.h \ + QGLViewer/qglviewer.h \ + QGLViewer/quaternion.h \ + QGLViewer/vec.h \ + src/window/mainwindow.h \ + src/viewer/simpleViewer.h \ + src/glnodes/glnode.h \ + src/glnodes/shapes.h \ + src/interfaces/ivisitable.h \ + src/interfaces/visitor.h \ + src/glnodes/scenegroup.h + +DISTFILES += src/shaders/basicShader.vert \ + src/shaders/basicShader.frag + +FORMS += QGLViewer/ImageInterface.ui mainwindow.ui + + +CONFIG *= debug_and_release console qt opengl warn_on thread create_prl rtti + +DEFINES *= QGLVIEWER_STATIC +win32 { + DEFINES *= NOMINMAX +} + +win32 { + contains ( QT_MAJOR_VERSION, 5 ) { + greaterThan( QT_MINOR_VERSION, 4) { + LIBS *= -lopengl32 + } + } +} diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..3283213 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,176 @@ + + + MainWindow + + + + 0 + 0 + 588 + 499 + + + + MainWindow + + + + + + + + 1 + 1 + + + + + 500 + 500 + + + + + 500 + 500 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + Forme + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Couleur + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Vide + + + + + Triangle + + + + + Carré + + + + + Cercle + + + + + + + + Couleur + + + false + + + false + + + + + + + + + + + 0 + 0 + 588 + 20 + + + + + &File + + + + + + + + + &Quit + + + Esc + + + + + + + + action_Quit + triggered() + MainWindow + close() + + + -1 + -1 + + + 199 + 149 + + + + + diff --git a/src/glnodes/glnode.cpp b/src/glnodes/glnode.cpp new file mode 100644 index 0000000..022ede8 --- /dev/null +++ b/src/glnodes/glnode.cpp @@ -0,0 +1,17 @@ +#include "glnode.h" +#include + + +//GlNode& GlNode::getChild() { +// GlNode* tempNode = new GlNode(); +// return *tempNode; +//} + +//bool GlNode::hasNext(){ +// return false; +//} + +//void GlNode::accept(Visitor &v) { + +// //v.visit(this); +//} diff --git a/src/glnodes/glnode.h b/src/glnodes/glnode.h new file mode 100644 index 0000000..6d88ffc --- /dev/null +++ b/src/glnodes/glnode.h @@ -0,0 +1,14 @@ +#ifndef GLNODE +#define GLNODE +#include +#include "../interfaces/ivisitable.h" +#include "../interfaces/visitor.h" +class Viewer; +class GlNode : public IVisitable { +public: + + QMatrix4x4 transform; +}; + +#endif // GLNODE + diff --git a/src/glnodes/scenegroup.cpp b/src/glnodes/scenegroup.cpp new file mode 100644 index 0000000..551339d --- /dev/null +++ b/src/glnodes/scenegroup.cpp @@ -0,0 +1,42 @@ +#include "scenegroup.h" + +SceneGroup::SceneGroup() +{ + +} + +std::vector* SceneGroup::getChildren() { + return &children; +} + +GlNode* SceneGroup::childAt(int i) { + return children.at(i); +} + +GlNode* SceneGroup::getChild(){ + // Automatically loops + if(childIndex >= children.size() || childIndex < 0){ //just in case + childIndex = 0; + } + return children.at(childIndex++); +} + +bool SceneGroup::hasNext() +{ + if(childIndex >= children.size() || childIndex < 0) { + childIndex = 0; + return false; + } + + return true; +} + +void SceneGroup::addChild(GlNode* child){ + + children.push_back(child); + //children.push_back(std::shared_ptr(child)); +} + +void SceneGroup::accept(Visitor &v) { + v.visit(*this); +} diff --git a/src/glnodes/scenegroup.h b/src/glnodes/scenegroup.h new file mode 100644 index 0000000..9296bed --- /dev/null +++ b/src/glnodes/scenegroup.h @@ -0,0 +1,26 @@ +#ifndef SCENEGROUP_H +#define SCENEGROUP_H + +#include "glnode.h" +#include "../interfaces/ivisitable.h" +#include "../interfaces/visitor.h" + +class SceneGroup : public GlNode +{ +private: + std::vector children; + int childIndex = 0; +public: + void addChild(GlNode* c); + GlNode* getChild(); + bool hasNext(); + + std::vector* getChildren(); + + GlNode* childAt(int i); + + void accept(Visitor& v) override; + SceneGroup(); +}; + +#endif // SCENEGROUP_H diff --git a/src/glnodes/shapes.cpp b/src/glnodes/shapes.cpp new file mode 100644 index 0000000..55de8f2 --- /dev/null +++ b/src/glnodes/shapes.cpp @@ -0,0 +1,44 @@ +#include "shapes.h" +#include +#include + +void Square::accept(Visitor &v) { + v.visit(*this); +} + +void Circle::accept(Visitor &v) { + v.visit(*this); +} + +void Triangle::accept(Visitor &v) { + v.visit(*this); +} + +// *** + +void Square::setColor(QColor& c) { + color = QColor(c); +} + +void Circle::setColor(QColor& c) { + color = QColor(c); +} + +void Triangle::setColor(QColor& c) { + color = QColor(c); +} + + +QColor Triangle::getColor(){ + return color; +}; + + +QColor Circle::getColor(){ + return color; +}; + + +QColor Square::getColor(){ + return color; +}; diff --git a/src/glnodes/shapes.h b/src/glnodes/shapes.h new file mode 100644 index 0000000..2f92c5b --- /dev/null +++ b/src/glnodes/shapes.h @@ -0,0 +1,41 @@ +#ifndef SHAPES_H +#define SHAPES_H +#include "glnode.h" +#include +#include +#include "../interfaces/visitor.h" +class Shape : public GlNode +{ +public: + virtual void setColor(QColor& c) = 0; + virtual QColor getColor() = 0; +}; + +class Circle : public Shape +{ +public: + Circle(){} + QColor color; + void accept(Visitor& v) override; + void setColor(QColor& c); + QColor getColor(); +}; +class Triangle : public Shape +{ +public: + Triangle(){} + QColor color; + void accept(Visitor& v) override; + void setColor(QColor& c); + QColor getColor(); +}; +class Square : public Shape +{ +public: + Square(){} + QColor color; + void accept(Visitor& v) override; + void setColor(QColor& c); + QColor getColor(); +}; +#endif // SHAPES_H diff --git a/src/interfaces/ivisitable.h b/src/interfaces/ivisitable.h new file mode 100644 index 0000000..804baf7 --- /dev/null +++ b/src/interfaces/ivisitable.h @@ -0,0 +1,10 @@ +#ifndef IVISITABLE_H +#define IVISITABLE_H +class Visitor; + +class IVisitable { +public: + virtual void accept(Visitor& v) = 0; +}; + +#endif // IVISITABLE_H diff --git a/src/interfaces/visitor.h b/src/interfaces/visitor.h new file mode 100644 index 0000000..9786fb9 --- /dev/null +++ b/src/interfaces/visitor.h @@ -0,0 +1,15 @@ +#ifndef VISITOR_H +#define VISITOR_H +class SceneGroup; +class Square; +class Circle; +class Triangle; + +class Visitor { +public: + virtual void visit(SceneGroup &n) = 0; + virtual void visit(Square &s) = 0; + virtual void visit(Circle &s) = 0; + virtual void visit(Triangle &s) = 0; +}; +#endif // VISITOR_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..2c5842c --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,29 @@ +#include "src/viewer/simpleViewer.h" +#include "src/window/mainwindow.h" +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + QDesktopWidget dw; + + // Set the core profile and version of OpenGL shaders + QSurfaceFormat fmt; + fmt.setVersion(4, 0); + fmt.setProfile(QSurfaceFormat::CoreProfile); + QSurfaceFormat::setDefaultFormat(fmt); + + MainWindow w; + + // Instantiate and layout the viewer. + Viewer *v = new Viewer(); + w.addViewer(v); + + //w.setFixedSize(dw.width() * 0.7, dw.height() * 0.7); + + w.show(); + + return a.exec(); +} diff --git a/src/shaders/basicShader.frag b/src/shaders/basicShader.frag new file mode 100644 index 0000000..7252e9d --- /dev/null +++ b/src/shaders/basicShader.frag @@ -0,0 +1,9 @@ +#version 400 core +in vec4 ifColor; +out vec4 fColor; + +void +main() +{ + fColor = ifColor; +} diff --git a/src/shaders/basicShader.vert b/src/shaders/basicShader.vert new file mode 100644 index 0000000..8edff8e --- /dev/null +++ b/src/shaders/basicShader.vert @@ -0,0 +1,14 @@ +#version 400 core +uniform mat4 mvMatrix; +uniform mat4 projMatrix; +uniform vec4 color; +in vec4 vPosition; +out vec4 ifColor; + +void +main() +{ + gl_Position = projMatrix * mvMatrix * vPosition; + ifColor = color; +} + diff --git a/src/viewer/simpleViewer.cpp b/src/viewer/simpleViewer.cpp new file mode 100644 index 0000000..e982567 --- /dev/null +++ b/src/viewer/simpleViewer.cpp @@ -0,0 +1,444 @@ +/**************************************************************************** + + Copyright (C) 2002-2008 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.3.6. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#include "simpleViewer.h" +#include "../interfaces/visitor.h" +#include "../glnodes/scenegroup.h" +#include "../glnodes/shapes.h" + +#include +#include + +#include + +#include +#include +#include +#include +using namespace std; + +#define BUFFER_OFFSET(i) ((char *)NULL + (i)) +#define GRID_SIZE 10; + +namespace +{ +const int numVerticesSquare = 5; +const int numVerticesTriangle = 3; +const int numVerticesCircle = 26; +const double frame_limit = 5; +const double inc_mult = 5; +const double inc_offset = 1.05; +} + +Viewer::Viewer() +{ + activeColor = new QColor(255, 255, 255, 255); + activeCell = nullptr; + activeShape = 0; +} + +Viewer::~Viewer() +{ + cleanup(); +} + +void Viewer::cleanup() +{ + makeCurrent(); + + // Delete shaders + delete m_program; + m_program = 0; + + // Delete buffers + glDeleteBuffers(NumBuffers, m_Buffers); + glDeleteVertexArrays(NumVAOs, m_VAOs); + + doneCurrent(); +} + +void Viewer::draw() +{ + // Bind our vertex/fragment shaders + m_program->bind(); + + // Get projection and camera transformations + QMatrix4x4 projectionMatrix; + QMatrix4x4 modelViewMatrix; + camera()->getProjectionMatrix(projectionMatrix); + camera()->getModelViewMatrix(modelViewMatrix); + + // Prepare a transformation stack + + // stack modelStack; + + modelViewMatrix.translate(-4.5, -4.5); + modelViewMatrix.scale(0.95); + + m_program->setUniformValue(m_projMatrixLocation, projectionMatrix); + m_program->setUniformValue(m_mvMatrixLocation, modelViewMatrix); + + // Traverse the Scene in order to draw its components + + modelStack.push(modelViewMatrix); + root.accept(*this); +} + +void Viewer::mouseMoveEvent(QMouseEvent* e) { + cout << "Viewer::mouseMoveEvent(QMouseEvent* e)" << endl; + // Normal QGLViewer behavior. + //QGLViewer::mouseMoveEvent(e); +} + +void Viewer::mousePressEvent(QMouseEvent* e) { + + cout << "Viewer::mouseMoveEvent(QMouseEvent* e) : " << e->button() << endl; + + if(e->button() == 1){ // LMB + + int truY = 10 - (e->y()) / 500.0 * GRID_SIZE; + int truX = (e->x() / 500.0) * GRID_SIZE; + + cout << " -->Getting cell at " << truX << " : " << truY << endl; + + SceneGroup* row = dynamic_cast (root.childAt(truY)); + cout << " -->" << row << endl; + SceneGroup* cell = dynamic_cast (row->childAt(truX)); + cout << " -->" << cell << endl; + + if(e->modifiers() & Qt::ShiftModifier){ + // add a shape + if(!cell->getChildren()->size()){ + + // WARNING: CODE DEGEULASSE + + Shape* s = nullptr; + if(activeShape == 1){ + s = new Triangle(); + }else if(activeShape == 2){ + s = new Square(); + }else if(activeShape == 3){ + s = new Circle(); + } + + // WARNING: END OF CODE DEGEULASSE + + //activeCell->getChildren()->at(0) = s; + if(s != nullptr){ + s->setColor(*activeColor); + cell->addChild(s); + this->update(); + deselect(); + activeCell = cell; + } + } + } + + if(e->modifiers() & Qt::ControlModifier){ + // select a shape + deselect(); + activeCell = cell; + if(activeCell != nullptr && activeCell->getChildren()->size()){ + std::cout << "Cell has children..." << endl; + Shape* shape = dynamic_cast (activeCell->childAt(0)); + QColor newColor = shape->getColor(); + std::cout << newColor.Rgb << endl; + newColor.setAlpha(255); + shape->setColor(newColor); + //emit shapeSelected(getTypeIndex(typeof activeCell->childAt(0))); + int shapeId = 0; + if(typeid(*(activeCell->getChildren()->at(0))) == typeid(Triangle)) + shapeId = 1; + if(typeid(*(activeCell->getChildren()->at(0))) == typeid(Square)) + shapeId = 2; + if(typeid(*(activeCell->getChildren()->at(0))) == typeid(Circle)) + shapeId = 3; + emit shapeSelected(shapeId); + }else{ + emit shapeSelected(0); + } + } + } +} + +void Viewer::deselect(){ + std::cout << "Deselecting cell " << activeCell << endl; + if(activeCell != nullptr && activeCell->getChildren()->size()){ + std::cout << "Cell has children..." << endl; + Shape* shape = dynamic_cast (activeCell->childAt(0)); + QColor newColor = shape->getColor(); + std::cout << newColor.Rgb << endl; + newColor.setAlpha(180); + shape->setColor(newColor); + }else{ + std::cout << "Cell has no children, moving on" << endl; + } + this->update(); +} + +void Viewer::mouseReleaseEvent(QMouseEvent* e) { + cout << "Viewer::mouseReleaseEvent(QMouseEvent* e)" << endl; + //QGLViewer::mouseReleaseEvent(e); +} + +void Viewer::init() +{ + // We want to restrict ourselves to a 2D viewer. + camera()->setType(qglviewer::Camera::ORTHOGRAPHIC); + /*setMouseBinding(Qt::NoModifier, Qt::LeftButton, CAMERA, SCREEN_ROTATE); + setMouseBinding(Qt::AltModifier, Qt::LeftButton, CAMERA, NO_MOUSE_ACTION);*/ + setMouseBinding(Qt::NoModifier, Qt::MouseButton(Qt::LeftButton + Qt::MidButton), CAMERA, NO_MOUSE_ACTION); + setMouseBinding(Qt::ControlModifier, Qt::MouseButton(Qt::LeftButton + Qt::MidButton), CAMERA, NO_MOUSE_ACTION); + setMouseBinding(Qt::ShiftModifier, Qt::MouseButton(Qt::LeftButton + Qt::MidButton), CAMERA, NO_MOUSE_ACTION); + + // Our scene will be from -5 to 5 in X and Y (the grid will be 10x10). + setSceneRadius(5); + showEntireScene(); + + // Init OpenGL objects + connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &Viewer::cleanup); + initializeOpenGLFunctions(); + + // Init shaders & geometry + initShaders(); + initGeometries(); + initGrid(); +} + +void Viewer::initShaders() +{ + // Load vertex and fragment shaders + m_program = new QOpenGLShaderProgram; + if (!m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "src/shaders/basicShader.vert")) { + cerr << "Unable to load Shader" << endl + << "Log file:" << endl; + qDebug() << m_program->log(); + } + if (!m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "src/shaders/basicShader.frag")) { + cerr << "Unable to load Shader" << endl + << "Log file:" << endl; + qDebug() << m_program->log(); + } + m_program->link(); + m_program->bind(); // Note: This is equivalent to glUseProgram(programId()); + + // Specify shader input paramters + // The strings "vPosition", "mvMatrix", etc. have to match an attribute name in the vertex shader. + if ((m_vPositionLocation = m_program->attributeLocation("vPosition")) < 0) + qDebug() << "Unable to find shader location for " << "vPosition"; + + if ((m_colorLocation = m_program->uniformLocation("color")) < 0) + qDebug() << "Unable to find shader location for " << "color"; + + if ((m_mvMatrixLocation = m_program->uniformLocation("mvMatrix")) < 0) + qDebug() << "Unable to find shader location for " << "mvMatrix"; + + if ((m_projMatrixLocation = m_program->uniformLocation("projMatrix")) < 0) + qDebug() << "Unable to find shader location for " << "projMatrix"; +} + +// Creates the basic shapes in memory. We only have 3, so we just prep them all in advance. +void Viewer::initGeometries() +{ + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable( GL_BLEND ); glClearColor(0.0,0.0,0.0,0.0); + + // Create our VertexArrays Objects and VertexBuffer Objects + glGenVertexArrays(NumVAOs, m_VAOs); + glGenBuffers(NumBuffers, m_Buffers); + + // Create our pentagone object, store its vertices on the graphic card, and + // bind the data to the vPosition attribute of the shader + GLfloat verticesSquare[numVerticesSquare][3] = { + { -0.5, 0.5, 0 }, + { -0.5, -0.5, 0 }, + { 0.5, -0.5, 0 }, + { 0.5, 0.5, 0 }, + { -0.5, 0.5, 0 } + }; + + glBindVertexArray(m_VAOs[VAO_Square]); + glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[VBO_Square]); + glBufferData(GL_ARRAY_BUFFER, sizeof(verticesSquare), verticesSquare, GL_STATIC_DRAW); + + glVertexAttribPointer(m_vPositionLocation, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0)); + glEnableVertexAttribArray(m_vPositionLocation); + + // Create our triangle object, store its vertices on the graphic card, and + // bind the data to the vPosition attribute of the shader + GLfloat verticesTriangle[numVerticesTriangle][3] = { + { -0.5, -0.5, 0.0 }, + { 0.5, -0.5, 0.0 }, + { 0.0, 0.5, 0.0 } + }; + + glBindVertexArray(m_VAOs[VAO_Triangle]); + glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[VBO_Triangle]); + glBufferData(GL_ARRAY_BUFFER, sizeof(verticesTriangle), verticesTriangle, GL_STATIC_DRAW); + + glVertexAttribPointer(m_vPositionLocation, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0)); + glEnableVertexAttribArray(m_vPositionLocation); + + // Create our Circle Object, and store its vertices with its bindings + + GLfloat verticesCircle[numVerticesCircle][3]; + + const float PI = 3.1415926f; + double increment = 2.0f * PI / (numVerticesCircle - 2); + + verticesCircle[0][0] = 0; + verticesCircle[0][1] = 0; + verticesCircle[0][2] = 0; + + for(int i = 1; i < numVerticesCircle; i++) { + double angle = increment * (i); + + verticesCircle[i][0] = 0.5 * cos(angle); + verticesCircle[i][1] = 0.5 * sin(angle); + verticesCircle[i][2] = 0; + } + + glBindVertexArray(m_VAOs[VAO_Circle]); + glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[VBO_Circle]); + glBufferData(GL_ARRAY_BUFFER, sizeof(verticesCircle), verticesCircle, GL_STATIC_DRAW); + + glVertexAttribPointer(m_vPositionLocation, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0)); + glEnableVertexAttribArray(m_vPositionLocation); +} + +void Viewer::initGrid() +{ + // Prepare construction loop. Also contains necessary translations + int i,j, limit; + limit = GRID_SIZE; + + for(i = 0; i < limit ; i++) { + SceneGroup *row = new SceneGroup(); + + row->transform.translate(0, inc_offset * i); + + + for(j = 0; j < limit ; j++) { + SceneGroup *cell = new SceneGroup(); + + cell->transform.translate(inc_offset * j, 0); + + row->addChild(cell); + } + + root.addChild(row); + } +} + +void Viewer::visit(Square &s) +{ + QMatrix4x4 modelViewMatrix = modelStack.top() * QMatrix4x4(s.transform); + + glBindVertexArray(m_VAOs[VAO_Square]); + m_program->setUniformValue(m_mvMatrixLocation, modelViewMatrix); + m_program->setUniformValue(m_colorLocation, s.color); + + glDrawArrays(GL_TRIANGLE_FAN, 0, numVerticesSquare); +} +void Viewer::visit(Circle &s) +{ + QMatrix4x4 modelViewMatrix = modelStack.top() * QMatrix4x4(s.transform); + + glBindVertexArray(m_VAOs[VAO_Circle]); + m_program->setUniformValue(m_mvMatrixLocation, modelViewMatrix); + m_program->setUniformValue(m_colorLocation, s.color); + + glDrawArrays(GL_TRIANGLE_FAN, 0, numVerticesCircle); +} +void Viewer::visit(Triangle &s) +{ + QMatrix4x4 modelViewMatrix = modelStack.top() * QMatrix4x4(s.transform); + + glBindVertexArray(m_VAOs[VAO_Triangle]); + m_program->setUniformValue(m_mvMatrixLocation, modelViewMatrix); + m_program->setUniformValue(m_colorLocation, s.color); + + glDrawArrays(GL_TRIANGLES, 0, numVerticesTriangle); +} + +void Viewer::visit(SceneGroup &s) +{ + // Build compound transformation matrix + QMatrix4x4 currentMatrix = modelStack.top() * QMatrix4x4(s.transform); + modelStack.push(currentMatrix); + + while(s.hasNext()) { + // Get next leaf + GlNode* current = s.getChild(); + + // Draw/Traverse child + current->accept(*this); + } + + // Return model matrix to previous state + modelStack.pop(); +} + +void Viewer::changeColor(QColor c){ + if(activeCell->getChildren()->size()){ + Shape* shape = dynamic_cast (activeCell->childAt(0)); + shape->setColor(c); + } + activeColor = new QColor(c); + this->update(); +} + +void Viewer::changeShape(int s){ + std::cout << "Chaging active shape from " << activeShape << " to " << s << endl; + + if(activeCell != nullptr && activeCell->getChildren()->size()){ + std::cout << "Chaging cell-bound shape... " << endl; + Shape* shape = dynamic_cast (activeCell->childAt(0)); + QColor c = shape->getColor(); + + // WARNING: CODE DEGEULASSE + + Shape* newShape = nullptr; + if(s == 1){ + newShape = new Triangle(); + }else if(s == 2){ + newShape = new Square(); + }else if(s == 3){ + newShape = new Circle(); + } + + if(newShape != nullptr){ + newShape->setColor(c); + activeCell->getChildren()->at(0) = newShape; + }else{ + activeCell->getChildren()->clear(); + } + + // WARNING: END OF CODE DEGEULASSE + + this->update(); + }else{ + std::cout << "Cell has no children, ignoring change." << endl; + } + + activeShape = s; +}; diff --git a/src/viewer/simpleViewer.h b/src/viewer/simpleViewer.h new file mode 100644 index 0000000..9f5c882 --- /dev/null +++ b/src/viewer/simpleViewer.h @@ -0,0 +1,95 @@ +/**************************************************************************** + + Copyright (C) 2002-2008 Gilles Debunne. All rights reserved. + + This file is part of the QGLViewer library version 2.3.6. + + http://www.libqglviewer.com - contact@libqglviewer.com + + This file may be used under the terms of the GNU General Public License + versions 2.0 or 3.0 as published by the Free Software Foundation and + appearing in the LICENSE file included in the packaging of this file. + In addition, as a special exception, Gilles Debunne gives you certain + additional rights, described in the file GPL_EXCEPTION in this package. + + libQGLViewer uses dual licensing. Commercial/proprietary software must + purchase a libQGLViewer Commercial License. + + This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*****************************************************************************/ + +#ifndef SIMPLEVIEWER_H +#define SIMPLEVIEWER_H + +#include +#include +#include +#include +#include +#include "../interfaces/visitor.h" +#include "../glnodes/glnode.h" +#include "../glnodes/scenegroup.h" +#include "../glnodes/shapes.h" +#include + +QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram) + +class Viewer : public QGLViewer, protected QOpenGLFunctions_4_0_Core, public Visitor +{ +Q_OBJECT +public: + Viewer(); + ~Viewer(); + + virtual void visit(SceneGroup &s); + virtual void visit(Square &s); + virtual void visit(Circle &s); + virtual void visit(Triangle &s); + +public slots: + void cleanup(); + void changeColor(QColor); + void changeShape(int); + +signals: + int shapeSelected(int); + +protected : + virtual void draw(); + virtual void init(); + + virtual void mouseMoveEvent(QMouseEvent* e); + virtual void mouseReleaseEvent(QMouseEvent* e); + virtual void mousePressEvent(QMouseEvent *e); + + SceneGroup root; + std::stack modelStack; + +private: + void initShaders(); + void initGeometries(); + void initGrid(); + void deselect(); + + QOpenGLShaderProgram *m_program; + int m_vPositionLocation; + int m_colorLocation; + int m_projMatrixLocation; + int m_mvMatrixLocation; + + SceneGroup* activeCell; + QColor* activeColor; + int activeShape; + + enum VAO_IDs { VAO_Square, VAO_Triangle, VAO_Circle, NumVAOs }; + enum Buffer_IDs { VBO_Square, VBO_Triangle, VBO_Circle, NumBuffers }; + + GLuint m_VAOs[NumVAOs]; + GLuint m_Buffers[NumBuffers]; + + Shape* generateShapeFromIndex(int); +}; + +#endif // SIMPLEVIEWER_H diff --git a/src/window/mainwindow.cpp b/src/window/mainwindow.cpp new file mode 100644 index 0000000..47302fb --- /dev/null +++ b/src/window/mainwindow.cpp @@ -0,0 +1,35 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow), + colordialog(new QColorDialog) +{ + ui->setupUi(this); + + connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(onColorPickerActivate())); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::addViewer(Viewer* viewer) +{ + QLayout *layout = new QHBoxLayout; + layout->addWidget(viewer); + ui->frame->setLayout(layout); + + connect(colordialog, SIGNAL(colorSelected(QColor)), viewer, SLOT(changeColor(QColor))); + connect(ui->comboBox, SIGNAL(currentIndexChanged(int)), viewer, SLOT(changeShape(int))); + connect(viewer, SIGNAL(shapeSelected(int)), ui->comboBox, SLOT(setCurrentIndex(int))); + +} + +void MainWindow::onColorPickerActivate(){ + colordialog->open(); +} diff --git a/src/window/mainwindow.h b/src/window/mainwindow.h new file mode 100644 index 0000000..8ec7803 --- /dev/null +++ b/src/window/mainwindow.h @@ -0,0 +1,30 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include "src/viewer/simpleViewer.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + + void addViewer(Viewer* viewer); + +public slots: + void onColorPickerActivate(); + +private: + Ui::MainWindow *ui; + QColorDialog *colordialog; +}; + +#endif // MAINWINDOW_H