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