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("
");
+ const QString tdtr("
\n");
+ const QString tdtd("
");
+
+ text += QString("
%1
%2
\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("
%1
\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 += "
";
+
+ 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("
%1
%2
\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("
%1
\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 += QGLViewer::tr("Camera paths are controlled using the %1 keys (noted Fx below):", "Help window key tab camera keys").arg(cpks) + "
\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 += "
";
+
+ 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("
+
+ 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
+
+