manipulatedFrame.cpp
1 /****************************************************************************
2 
3  Copyright (C) 2002-2013 Gilles Debunne. All rights reserved.
4 
5  This file is part of the QGLViewer library version 2.5.2.
6 
7  http://www.libqglviewer.com - contact@libqglviewer.com
8 
9  This file may be used under the terms of the GNU General Public License
10  versions 2.0 or 3.0 as published by the Free Software Foundation and
11  appearing in the LICENSE file included in the packaging of this file.
12  In addition, as a special exception, Gilles Debunne gives you certain
13  additional rights, described in the file GPL_EXCEPTION in this package.
14 
15  libQGLViewer uses dual licensing. Commercial/proprietary software must
16  purchase a libQGLViewer Commercial License.
17 
18  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
19  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20 
21 *****************************************************************************/
22 
23 #include "domUtils.h"
24 #include "manipulatedFrame.h"
25 #include "manipulatedCameraFrame.h"
26 #include "qglviewer.h"
27 #include "camera.h"
28 
29 #include <cstdlib>
30 
31 #include <QMouseEvent>
32 
33 using namespace qglviewer;
34 using namespace std;
35 
44  : action_(QGLViewer::NO_MOUSE_ACTION), keepsGrabbingMouse_(false)
45 {
46  // #CONNECTION# initFromDOMElement and accessor docs
50  setWheelSensitivity(1.0f);
51  setZoomSensitivity(1.0f);
52 
53  isSpinning_ = false;
54  previousConstraint_ = NULL;
55 
56  connect(&spinningTimer_, SIGNAL(timeout()), SLOT(spinUpdate()));
57 }
58 
61 {
62  Frame::operator=(mf);
63 
69 
70  mouseSpeed_ = 0.0;
71  dirIsFixed_ = false;
72  keepsGrabbingMouse_ = false;
73  action_ = QGLViewer::NO_MOUSE_ACTION;
74 
75  return *this;
76 }
77 
80  : Frame(mf), MouseGrabber()
81 {
82  (*this)=mf;
83 }
84 
86 
93 void ManipulatedFrame::checkIfGrabsMouse(int x, int y, const Camera* const camera)
94 {
95  const int thresold = 10;
96  const Vec proj = camera->projectedCoordinatesOf(position());
97  setGrabsMouse(keepsGrabbingMouse_ || ((fabs(x-proj.x) < thresold) && (fabs(y-proj.y) < thresold)));
98 }
99 
101 // S t a t e s a v i n g a n d r e s t o r i n g //
103 
116 QDomElement ManipulatedFrame::domElement(const QString& name, QDomDocument& document) const
117 {
118  QDomElement e = Frame::domElement(name, document);
119  QDomElement mp = document.createElement("ManipulatedParameters");
120  mp.setAttribute("rotSens", QString::number(rotationSensitivity()));
121  mp.setAttribute("transSens", QString::number(translationSensitivity()));
122  mp.setAttribute("spinSens", QString::number(spinningSensitivity()));
123  mp.setAttribute("wheelSens", QString::number(wheelSensitivity()));
124  mp.setAttribute("zoomSens", QString::number(zoomSensitivity()));
125  e.appendChild(mp);
126  return e;
127 }
128 
138 void ManipulatedFrame::initFromDOMElement(const QDomElement& element)
139 {
140  // Not called since it would set constraint() and referenceFrame() to NULL.
141  // *this = ManipulatedFrame();
142  Frame::initFromDOMElement(element);
143 
144  stopSpinning();
145 
146  QDomElement child=element.firstChild().toElement();
147  while (!child.isNull())
148  {
149  if (child.tagName() == "ManipulatedParameters")
150  {
151  // #CONNECTION# constructor default values and accessor docs
152  setRotationSensitivity (DomUtils::floatFromDom(child, "rotSens", 1.0f));
153  setTranslationSensitivity(DomUtils::floatFromDom(child, "transSens", 1.0f));
154  setSpinningSensitivity (DomUtils::floatFromDom(child, "spinSens", 0.3f));
155  setWheelSensitivity (DomUtils::floatFromDom(child, "wheelSens", 1.0f));
156  setZoomSensitivity (DomUtils::floatFromDom(child, "zoomSens", 1.0f));
157  }
158  child = child.nextSibling().toElement();
159  }
160 }
161 
162 
164 // M o u s e h a n d l i n g //
166 
175 {
176  return action_ != QGLViewer::NO_MOUSE_ACTION;
177 }
178 
183 void ManipulatedFrame::startSpinning(int updateInterval)
184 {
185  isSpinning_ = true;
186  spinningTimer_.start(updateInterval);
187 }
188 
192 {
194 }
195 
196 /* spin() and spinUpdate() differ since spin can be used by itself (for instance by
197  QGLViewer::SCREEN_ROTATE) without a spun emission. Much nicer to use the spinningQuaternion() and
198  hence spin() for these incremental updates. Nothing special to be done for continuous spinning
199  with this design. */
200 void ManipulatedFrame::spinUpdate()
201 {
202  spin();
203  Q_EMIT spun();
204 }
205 
206 #ifndef DOXYGEN
207 
208 void ManipulatedFrame::startAction(int ma, bool withConstraint)
209 {
210  action_ = (QGLViewer::MouseAction)(ma);
211 
212  // #CONNECTION# manipulatedFrame::wheelEvent, manipulatedCameraFrame::wheelEvent and mouseReleaseEvent()
213  // restore previous constraint
214  if (withConstraint)
215  previousConstraint_ = NULL;
216  else
217  {
218  previousConstraint_ = constraint();
219  setConstraint(NULL);
220  }
221 
222  switch (action_)
223  {
224  case QGLViewer::ROTATE:
225  case QGLViewer::SCREEN_ROTATE:
226  mouseSpeed_ = 0.0;
227  stopSpinning();
228  break;
229 
230  case QGLViewer::SCREEN_TRANSLATE:
231  dirIsFixed_ = false;
232  break;
233 
234  default:
235  break;
236  }
237 }
238 
241 void ManipulatedFrame::computeMouseSpeed(const QMouseEvent* const e)
242 {
243  const QPoint delta = (e->pos() - prevPos_);
244  const float dist = sqrt(static_cast<float>(delta.x()*delta.x() + delta.y()*delta.y()));
245  delay_ = last_move_time.restart();
246  if (delay_ == 0)
247  // Less than a millisecond: assume delay = 1ms
248  mouseSpeed_ = dist;
249  else
250  mouseSpeed_ = dist/delay_;
251 }
252 
255 int ManipulatedFrame::mouseOriginalDirection(const QMouseEvent* const e)
256 {
257  static bool horiz = true; // Two simultaneous manipulatedFrame require two mice !
258 
259  if (!dirIsFixed_)
260  {
261  const QPoint delta = e->pos() - pressPos_;
262  dirIsFixed_ = abs(delta.x()) != abs(delta.y());
263  horiz = abs(delta.x()) > abs(delta.y());
264  }
265 
266  if (dirIsFixed_)
267  if (horiz)
268  return 1;
269  else
270  return -1;
271  else
272  return 0;
273 }
274 
275 float ManipulatedFrame::deltaWithPrevPos(QMouseEvent* const event, Camera* const camera) const {
276  float dx = float(event->x() - prevPos_.x()) / camera->screenWidth();
277  float dy = float(event->y() - prevPos_.y()) / camera->screenHeight();
278 
279  float value = fabs(dx) > fabs(dy) ? dx : dy;
280  return value * zoomSensitivity();
281 }
282 
283 float ManipulatedFrame::wheelDelta(const QWheelEvent* event) const {
284  static const float WHEEL_SENSITIVITY_COEF = 8E-4f;
285  return event->delta() * wheelSensitivity() * WHEEL_SENSITIVITY_COEF;
286 }
287 
288 void ManipulatedFrame::zoom(float delta, const Camera * const camera) {
289  Vec trans(0.0, 0.0, (camera->position() - position()).norm() * delta);
290 
291  trans = camera->frame()->orientation().rotate(trans);
292  if (referenceFrame())
293  trans = referenceFrame()->transformOf(trans);
294  translate(trans);
295 }
296 
297 #endif // DOXYGEN
298 
305 void ManipulatedFrame::mousePressEvent(QMouseEvent* const event, Camera* const camera)
306 {
307  Q_UNUSED(camera);
308 
309  if (grabsMouse())
310  keepsGrabbingMouse_ = true;
311 
312  // #CONNECTION setMouseBinding
313  // action_ should no longer possibly be NO_MOUSE_ACTION since this value is not inserted in mouseBinding_
314  //if (action_ == QGLViewer::NO_MOUSE_ACTION)
315  //event->ignore();
316 
317  prevPos_ = pressPos_ = event->pos();
318 }
319 
329 void ManipulatedFrame::mouseMoveEvent(QMouseEvent* const event, Camera* const camera)
330 {
331  switch (action_)
332  {
333  case QGLViewer::TRANSLATE:
334  {
335  const QPoint delta = event->pos() - prevPos_;
336  Vec trans(static_cast<float>(delta.x()), static_cast<float>(-delta.y()), 0.0);
337  // Scale to fit the screen mouse displacement
338  switch (camera->type())
339  {
340  case Camera::PERSPECTIVE :
341  trans *= 2.0 * tan(camera->fieldOfView()/2.0) * fabs((camera->frame()->coordinatesOf(position())).z) / camera->screenHeight();
342  break;
343  case Camera::ORTHOGRAPHIC :
344  {
345  GLdouble w,h;
346  camera->getOrthoWidthHeight(w, h);
347  trans[0] *= 2.0 * w / camera->screenWidth();
348  trans[1] *= 2.0 * h / camera->screenHeight();
349  break;
350  }
351  }
352  // Transform to world coordinate system.
353  trans = camera->frame()->orientation().rotate(translationSensitivity()*trans);
354  // And then down to frame
355  if (referenceFrame()) trans = referenceFrame()->transformOf(trans);
356  translate(trans);
357  break;
358  }
359 
360  case QGLViewer::ZOOM:
361  {
362  zoom(deltaWithPrevPos(event, camera), camera);
363  break;
364  }
365 
366  case QGLViewer::SCREEN_ROTATE:
367  {
368  Vec trans = camera->projectedCoordinatesOf(position());
369 
370  const double prev_angle = atan2(prevPos_.y()-trans[1], prevPos_.x()-trans[0]);
371  const double angle = atan2(event->y()-trans[1], event->x()-trans[0]);
372 
373  const Vec axis = transformOf(camera->frame()->inverseTransformOf(Vec(0.0, 0.0, -1.0)));
374  Quaternion rot(axis, angle-prev_angle);
375  //#CONNECTION# These two methods should go together (spinning detection and activation)
376  computeMouseSpeed(event);
378  spin();
379  break;
380  }
381 
382  case QGLViewer::SCREEN_TRANSLATE:
383  {
384  Vec trans;
385  int dir = mouseOriginalDirection(event);
386  if (dir == 1)
387  trans.setValue(static_cast<float>(event->x() - prevPos_.x()), 0.0, 0.0);
388  else if (dir == -1)
389  trans.setValue(0.0, static_cast<float>(prevPos_.y() - event->y()), 0.0);
390 
391  switch (camera->type())
392  {
393  case Camera::PERSPECTIVE :
394  trans *= 2.0 * tan(camera->fieldOfView()/2.0) * fabs((camera->frame()->coordinatesOf(position())).z) / camera->screenHeight();
395  break;
396  case Camera::ORTHOGRAPHIC :
397  {
398  GLdouble w,h;
399  camera->getOrthoWidthHeight(w, h);
400  trans[0] *= 2.0 * w / camera->screenWidth();
401  trans[1] *= 2.0 * h / camera->screenHeight();
402  break;
403  }
404  }
405  // Transform to world coordinate system.
406  trans = camera->frame()->orientation().rotate(translationSensitivity()*trans);
407  // And then down to frame
408  if (referenceFrame())
409  trans = referenceFrame()->transformOf(trans);
410 
411  translate(trans);
412  break;
413  }
414 
415  case QGLViewer::ROTATE:
416  {
417  Vec trans = camera->projectedCoordinatesOf(position());
418  Quaternion rot = deformedBallQuaternion(event->x(), event->y(), trans[0], trans[1], camera);
419  trans = Vec(-rot[0], -rot[1], -rot[2]);
420  trans = camera->frame()->orientation().rotate(trans);
421  trans = transformOf(trans);
422  rot[0] = trans[0];
423  rot[1] = trans[1];
424  rot[2] = trans[2];
425  //#CONNECTION# These two methods should go together (spinning detection and activation)
426  computeMouseSpeed(event);
428  spin();
429  break;
430  }
431 
432  case QGLViewer::MOVE_FORWARD:
433  case QGLViewer::MOVE_BACKWARD:
434  case QGLViewer::LOOK_AROUND:
435  case QGLViewer::ROLL:
436  case QGLViewer::DRIVE:
437  case QGLViewer::ZOOM_ON_REGION:
438  // These MouseAction values make no sense for a manipulatedFrame
439  break;
440 
441  case QGLViewer::NO_MOUSE_ACTION:
442  // Possible when the ManipulatedFrame is a MouseGrabber. This method is then called without startAction
443  // because of mouseTracking.
444  break;
445  }
446 
447  if (action_ != QGLViewer::NO_MOUSE_ACTION)
448  {
449  prevPos_ = event->pos();
450  Q_EMIT manipulated();
451  }
452 }
453 
461 void ManipulatedFrame::mouseReleaseEvent(QMouseEvent* const event, Camera* const camera)
462 {
463  Q_UNUSED(event);
464  Q_UNUSED(camera);
465 
466  keepsGrabbingMouse_ = false;
467 
468  if (previousConstraint_)
469  setConstraint(previousConstraint_);
470 
471  if (((action_ == QGLViewer::ROTATE) || (action_ == QGLViewer::SCREEN_ROTATE)) && (mouseSpeed_ >= spinningSensitivity()))
472  startSpinning(delay_);
473 
474  action_ = QGLViewer::NO_MOUSE_ACTION;
475 }
476 
482 void ManipulatedFrame::mouseDoubleClickEvent(QMouseEvent* const event, Camera* const camera)
483 {
484  if (event->modifiers() == Qt::NoModifier)
485  switch (event->button())
486  {
487  case Qt::LeftButton: alignWithFrame(camera->frame()); break;
488  case Qt::RightButton: projectOnLine(camera->position(), camera->viewDirection()); break;
489  default: break;
490  }
491 }
492 
497 void ManipulatedFrame::wheelEvent(QWheelEvent* const event, Camera* const camera)
498 {
499  //#CONNECTION# QGLViewer::setWheelBinding
500  if (action_ == QGLViewer::ZOOM)
501  {
502  zoom(wheelDelta(event), camera);
503  Q_EMIT manipulated();
504  }
505 
506  // #CONNECTION# startAction should always be called before
507  if (previousConstraint_)
508  setConstraint(previousConstraint_);
509 
510  action_ = QGLViewer::NO_MOUSE_ACTION;
511 }
512 
513 
515 
520 static float projectOnBall(float x, float y)
521 {
522  // If you change the size value, change angle computation in deformedBallQuaternion().
523  const float size = 1.0f;
524  const float size2 = size*size;
525  const float size_limit = size2*0.5;
526 
527  const float d = x*x + y*y;
528  return d < size_limit ? sqrt(size2 - d) : size_limit/sqrt(d);
529 }
530 
531 #ifndef DOXYGEN
532 
534 Quaternion ManipulatedFrame::deformedBallQuaternion(int x, int y, float cx, float cy, const Camera* const camera)
535 {
536  // Points on the deformed ball
537  float px = rotationSensitivity() * (prevPos_.x() - cx) / camera->screenWidth();
538  float py = rotationSensitivity() * (cy - prevPos_.y()) / camera->screenHeight();
539  float dx = rotationSensitivity() * (x - cx) / camera->screenWidth();
540  float dy = rotationSensitivity() * (cy - y) / camera->screenHeight();
541 
542  const Vec p1(px, py, projectOnBall(px, py));
543  const Vec p2(dx, dy, projectOnBall(dx, dy));
544  // Approximation of rotation angle
545  // Should be divided by the projectOnBall size, but it is 1.0
546  const Vec axis = cross(p2,p1);
547  const float angle = 5.0 * asin(sqrt(axis.squaredNorm() / p1.squaredNorm() / p2.squaredNorm()));
548  return Quaternion(axis, angle);
549 }
550 #endif // DOXYGEN
void spun()
This signal is emitted when the ManipulatedFrame isSpinning().
Vec projectedCoordinatesOf(const Vec &src, const Frame *frame=NULL) const
Returns the screen projected coordinates of a point src defined in the frame coordinate system...
Definition: camera.cpp:1584
float deltaWithPrevPos(QMouseEvent *const event, Camera *const camera) const
Returns a screen scaled delta from event's position to prevPos_, along the X or Y direction...
virtual void initFromDOMElement(const QDomElement &element)
Restores the Frame state from a QDomElement created by domElement().
Definition: frame.cpp:1011
virtual void initFromDOMElement(const QDomElement &element)
Restores the ManipulatedFrame state from a QDomElement created by domElement().
virtual QDomElement domElement(const QString &name, QDomDocument &document) const
Returns an XML QDomElement that represents the Frame.
Definition: frame.cpp:994
A ManipulatedFrame is a Frame that can be rotated and translated using the mouse. ...
double squaredNorm() const
Returns the squared norm of the Vec.
Definition: vec.h:332
void translate(Vec &t)
Same as translate(const Vec&) but t may be modified to satisfy the translation constraint().
Definition: frame.cpp:335
MouseAction
Defines the possible actions that can be binded to a mouse action (a click, followed by a mouse displ...
Definition: qglviewer.h:969
Quaternion spinningQuaternion() const
Returns the incremental rotation that is applied by spin() to the ManipulatedFrame orientation when i...
void alignWithFrame(const Frame *const frame, bool move=false, float threshold=0.0f)
Aligns the Frame with frame, so that two of their axis are parallel.
Definition: frame.cpp:1055
float wheelSensitivity() const
Returns the mouse wheel sensitivity.
int screenWidth() const
Returns the width (in pixels) of the Camera screen.
Definition: camera.h:190
virtual void mouseDoubleClickEvent(QMouseEvent *const event, Camera *const camera)
Overloading of MouseGrabber::mouseDoubleClickEvent().
Vec viewDirection() const
Returns the normalized view direction of the Camera, defined in the world coordinate system...
Definition: camera.cpp:1220
ManipulatedCameraFrame * frame() const
Returns the ManipulatedCameraFrame attached to the Camera.
Definition: camera.h:334
Abstract class for objects that grab mouse focus in a QGLViewer.
Definition: mouseGrabber.h:130
float rotationSensitivity() const
Returns the influence of a mouse displacement on the ManipulatedFrame rotation.
virtual void checkIfGrabsMouse(int x, int y, const Camera *const camera)
Implementation of the MouseGrabber main method.
int screenHeight() const
Returns the height (in pixels) of the Camera screen.
Definition: camera.h:195
Vec coordinatesOf(const Vec &src) const
Returns the Frame coordinates of a point src defined in the world coordinate system (converts from wo...
Definition: frame.cpp:702
float fieldOfView() const
Returns the vertical field of view of the Camera (in radians).
Definition: camera.h:170
float wheelDelta(const QWheelEvent *event) const
Returns a normalized wheel delta, proportionnal to wheelSensitivity().
The Vec class represents 3D positions and 3D vectors.
Definition: vec.h:65
void rotate(Quaternion &q)
Same as rotate(const Quaternion&) but q may be modified to satisfy the rotation constraint().
Definition: frame.cpp:376
void setSpinningSensitivity(float sensitivity)
Defines the spinningSensitivity(), in pixels per milliseconds.
virtual void getOrthoWidthHeight(GLdouble &halfWidth, GLdouble &halfHeight) const
Returns the halfWidth and halfHeight of the Camera orthographic frustum.
Definition: camera.cpp:324
virtual void spin()
Rotates the ManipulatedFrame by its spinningQuaternion().
void setRotationSensitivity(float sensitivity)
Defines the rotationSensitivity().
Quaternion orientation() const
Returns the orientation of the Frame, defined in the world coordinate system.
Definition: frame.cpp:546
ManipulatedFrame()
Default constructor.
void setWheelSensitivity(float sensitivity)
Defines the wheelSensitivity().
float translationSensitivity() const
Returns the influence of a mouse displacement on the ManipulatedFrame translation.
virtual void mouseReleaseEvent(QMouseEvent *const event, Camera *const camera)
Stops the ManipulatedFrame mouse manipulation.
virtual void wheelEvent(QWheelEvent *const event, Camera *const camera)
Overloading of MouseGrabber::wheelEvent().
void setValue(double X, double Y, double Z)
Set the current value.
Definition: vec.h:125
virtual void mouseMoveEvent(QMouseEvent *const event, Camera *const camera)
Modifies the ManipulatedFrame according to the mouse motion.
void projectOnLine(const Vec &origin, const Vec &direction)
Translates the Frame so that its position() lies on the line defined by origin and direction (defined...
Definition: frame.cpp:1133
A versatile 3D OpenGL viewer based on QGLWidget.
Definition: qglviewer.h:62
void setSpinningQuaternion(const Quaternion &spinningQuaternion)
Defines the spinningQuaternion().
float zoomSensitivity() const
Returns the zoom sensitivity.
A perspective or orthographic camera.
Definition: camera.h:84
void setGrabsMouse(bool grabs)
Sets the grabsMouse() flag.
Definition: mouseGrabber.h:183
void setZoomSensitivity(float sensitivity)
Defines the zoomSensitivity().
The Quaternion class represents 3D rotations and orientations.
Definition: quaternion.h:66
const Frame * referenceFrame() const
Returns the reference Frame, in which coordinates system the Frame is defined.
Definition: frame.h:262
void manipulated()
This signal is emitted when ever the ManipulatedFrame is manipulated (i.e.
Frame & operator=(const Frame &frame)
Equal operator.
Definition: frame.cpp:54
Type type() const
Returns the Camera::Type of the Camera.
Definition: camera.h:158
bool grabsMouse() const
Returns true when the MouseGrabber grabs the QGLViewer's mouse events.
Definition: mouseGrabber.h:179
void setConstraint(Constraint *const constraint)
Sets the constraint() attached to the Frame.
Definition: frame.h:361
virtual void mousePressEvent(QMouseEvent *const event, Camera *const camera)
Initiates the ManipulatedFrame mouse manipulation.
virtual void stopSpinning()
Stops the spinning motion started using startSpinning().
bool isManipulated() const
Returns true when the ManipulatedFrame is being manipulated with the mouse.
virtual QDomElement domElement(const QString &name, QDomDocument &document) const
Returns an XML QDomElement that represents the ManipulatedFrame.
Quaternion deformedBallQuaternion(int x, int y, float cx, float cy, const Camera *const camera)
Returns a quaternion computed according to the mouse motion.
int mouseOriginalDirection(const QMouseEvent *const e)
Return 1 if mouse motion was started horizontally and -1 if it was more vertical. ...
The Frame class represents a coordinate system, defined by a position and an orientation.
Definition: frame.h:121
void setTranslationSensitivity(float sensitivity)
Defines the translationSensitivity().
virtual void startSpinning(int updateInterval)
Starts the spinning of the ManipulatedFrame.
Vec position() const
Returns the Camera position (the eye), defined in the world coordinate system.
Definition: camera.cpp:1200
ManipulatedFrame & operator=(const ManipulatedFrame &mf)
Equal operator.
Vec transformOf(const Vec &src) const
Returns the Frame transform of a vector src defined in the world coordinate system (converts vectors ...
Definition: frame.cpp:843
Vec rotate(const Vec &v) const
Returns the image of v by the Quaternion rotation.
Definition: quaternion.cpp:76
Vec position() const
Returns the position of the Frame, defined in the world coordinate system.
Definition: frame.cpp:537
float spinningSensitivity() const
Returns the minimum mouse speed required (at button release) to make the ManipulatedFrame spin()...
Vec inverseTransformOf(const Vec &src) const
Returns the world transform of the vector whose coordinates in the Frame coordinate system is src (co...
Definition: frame.cpp:856
void computeMouseSpeed(const QMouseEvent *const e)
Updates mouse speed, measured in pixels/milliseconds.
virtual void startAction(int ma, bool withConstraint=true)
Protected internal method used to handle mouse events.
Constraint * constraint() const
Returns the current constraint applied to the Frame.
Definition: frame.h:356