manipulatedFrame.cpp
1 /****************************************************************************
2 
3  Copyright (C) 2002-2008 Gilles Debunne. All rights reserved.
4 
5  This file is part of the QGLViewer library version 2.3.10.
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 "qglviewer.h"
26 #include "camera.h"
27 
28 #include <cstdlib>
29 
30 #if QT_VERSION >= 0x040000
31 # include <QMouseEvent>
32 #endif
33 
34 using namespace qglviewer;
35 using namespace std;
36 
45  : action_(QGLViewer::NO_MOUSE_ACTION), keepsGrabbingMouse_(false)
46 {
47  // #CONNECTION# initFromDOMElement and accessor docs
51  setWheelSensitivity(1.0f);
52 
53  isSpinning_ = false;
54  previousConstraint_ = false;
55 
56  connect(&spinningTimer_, SIGNAL(timeout()), SLOT(spinUpdate()));
57 }
58 
61 {
62  Frame::operator=(mf);
63 
68 
69  mouseSpeed_ = 0.0;
70  dirIsFixed_ = false;
71  keepsGrabbingMouse_ = false;
72 
73  return *this;
74 }
75 
78  : Frame(mf), MouseGrabber()
79 {
80  (*this)=mf;
81 }
82 
84 
91 void ManipulatedFrame::checkIfGrabsMouse(int x, int y, const Camera* const camera)
92 {
93  const int thresold = 10;
94  const Vec proj = camera->projectedCoordinatesOf(position());
95  setGrabsMouse(keepsGrabbingMouse_ || ((fabs(x-proj.x) < thresold) && (fabs(y-proj.y) < thresold)));
96 }
97 
99 // S t a t e s a v i n g a n d r e s t o r i n g //
101 
114 QDomElement ManipulatedFrame::domElement(const QString& name, QDomDocument& document) const
115 {
116  QDomElement e = Frame::domElement(name, document);
117  QDomElement mp = document.createElement("ManipulatedParameters");
118  mp.setAttribute("rotSens", QString::number(rotationSensitivity()));
119  mp.setAttribute("transSens", QString::number(translationSensitivity()));
120  mp.setAttribute("spinSens", QString::number(spinningSensitivity()));
121  mp.setAttribute("wheelSens", QString::number(wheelSensitivity()));
122  e.appendChild(mp);
123  return e;
124 }
125 
135 void ManipulatedFrame::initFromDOMElement(const QDomElement& element)
136 {
137  // Not called since it would set constraint() and referenceFrame() to NULL.
138  // *this = ManipulatedFrame();
139  Frame::initFromDOMElement(element);
140 
141  stopSpinning();
142 
143  QDomElement child=element.firstChild().toElement();
144  while (!child.isNull())
145  {
146  if (child.tagName() == "ManipulatedParameters")
147  {
148  // #CONNECTION# constructor default values and accessor docs
149  setRotationSensitivity (DomUtils::floatFromDom(child, "rotSens", 1.0f));
150  setTranslationSensitivity(DomUtils::floatFromDom(child, "transSens", 1.0f));
151  setSpinningSensitivity (DomUtils::floatFromDom(child, "spinSens", 0.3f));
152  setWheelSensitivity (DomUtils::floatFromDom(child, "wheelSens", 1.0f));
153  }
154  child = child.nextSibling().toElement();
155  }
156 }
157 
158 
160 // M o u s e h a n d l i n g //
162 
171 {
172  return action_ != QGLViewer::NO_MOUSE_ACTION;
173 }
174 
179 void ManipulatedFrame::startSpinning(int updateInterval)
180 {
181  isSpinning_ = true;
182  spinningTimer_.start(updateInterval);
183 }
184 
188 {
190 }
191 
192 /* spin() and spinUpdate() differ since spin can be used by itself (for instance by
193  QGLViewer::SCREEN_ROTATE) without a spun emission. Much nicer to use the spinningQuaternion() and
194  hence spin() for these incremental updates. Nothing special to be done for continuous spinning
195  with this design. */
196 void ManipulatedFrame::spinUpdate()
197 {
198  spin();
199  Q_EMIT spun();
200 }
201 
202 #ifndef DOXYGEN
203 
204 void ManipulatedFrame::startAction(int ma, bool withConstraint)
205 {
206  action_ = (QGLViewer::MouseAction)(ma);
207 
208  // #CONNECTION# manipulatedFrame::wheelEvent, manipulatedCameraFrame::wheelEvent and mouseReleaseEvent()
209  // restore previous constraint
210  if (withConstraint)
211  previousConstraint_ = NULL;
212  else
213  {
214  previousConstraint_ = constraint();
215  setConstraint(NULL);
216  }
217 
218  switch (action_)
219  {
220  case QGLViewer::ROTATE:
221  case QGLViewer::SCREEN_ROTATE:
222  mouseSpeed_ = 0.0;
223  stopSpinning();
224  break;
225 
226  case QGLViewer::SCREEN_TRANSLATE:
227  dirIsFixed_ = false;
228  break;
229 
230  default:
231  break;
232  }
233 }
234 
237 void ManipulatedFrame::computeMouseSpeed(const QMouseEvent* const e)
238 {
239  const QPoint delta = (e->pos() - prevPos_);
240  const float dist = sqrt(static_cast<float>(delta.x()*delta.x() + delta.y()*delta.y()));
241  delay_ = last_move_time.restart();
242  if (delay_ == 0)
243  // Less than a millisecond: assume delay = 1ms
244  mouseSpeed_ = dist;
245  else
246  mouseSpeed_ = dist/delay_;
247 }
248 
251 int ManipulatedFrame::mouseOriginalDirection(const QMouseEvent* const e)
252 {
253  static bool horiz = true; // Two simultaneous manipulatedFrame require two mice !
254 
255  if (!dirIsFixed_)
256  {
257  const QPoint delta = e->pos() - pressPos_;
258  dirIsFixed_ = abs(delta.x()) != abs(delta.y());
259  horiz = abs(delta.x()) > abs(delta.y());
260  }
261 
262  if (dirIsFixed_)
263  if (horiz)
264  return 1;
265  else
266  return -1;
267  else
268  return 0;
269 }
270 #endif // DOXYGEN
271 
278 void ManipulatedFrame::mousePressEvent(QMouseEvent* const event, Camera* const camera)
279 {
280  Q_UNUSED(camera);
281 
282  if (grabsMouse())
283  keepsGrabbingMouse_ = true;
284 
285  // #CONNECTION setMouseBinding
286  // action_ should no longer possibly be NO_MOUSE_ACTION since this value is not inserted in mouseBinding_
287  //#if QT_VERSION >= 0x030000
288  //if (action_ == QGLViewer::NO_MOUSE_ACTION)
289  //event->ignore();
290  //#endif
291 
292  prevPos_ = pressPos_ = event->pos();
293 }
294 
304 void ManipulatedFrame::mouseMoveEvent(QMouseEvent* const event, Camera* const camera)
305 {
306  switch (action_)
307  {
308  case QGLViewer::TRANSLATE:
309  {
310  const QPoint delta = event->pos() - prevPos_;
311  Vec trans(static_cast<float>(delta.x()), static_cast<float>(-delta.y()), 0.0);
312  // Scale to fit the screen mouse displacement
313  switch (camera->type())
314  {
315  case Camera::PERSPECTIVE :
316  trans *= 2.0 * tan(camera->fieldOfView()/2.0) * fabs((camera->frame()->coordinatesOf(position())).z) / camera->screenHeight();
317  break;
318  case Camera::ORTHOGRAPHIC :
319  {
320  GLdouble w,h;
321  camera->getOrthoWidthHeight(w, h);
322  trans[0] *= 2.0 * w / camera->screenWidth();
323  trans[1] *= 2.0 * h / camera->screenHeight();
324  break;
325  }
326  }
327  // Transform to world coordinate system.
328  trans = camera->frame()->orientation().rotate(translationSensitivity()*trans);
329  // And then down to frame
330  if (referenceFrame()) trans = referenceFrame()->transformOf(trans);
331  translate(trans);
332  break;
333  }
334 
335  case QGLViewer::ZOOM:
336  {
337  //#CONNECTION# wheelEvent ZOOM case
338  Vec trans(0.0, 0.0, (camera->position()-position()).norm() * (event->y() - prevPos_.y()) / camera->screenHeight());
339 
340  trans = camera->frame()->orientation().rotate(trans);
341  if (referenceFrame())
342  trans = referenceFrame()->transformOf(trans);
343  translate(trans);
344  break;
345  }
346 
347  case QGLViewer::SCREEN_ROTATE:
348  {
349  Vec trans = camera->projectedCoordinatesOf(position());
350 
351  const double prev_angle = atan2(prevPos_.y()-trans[1], prevPos_.x()-trans[0]);
352  const double angle = atan2(event->y()-trans[1], event->x()-trans[0]);
353 
354  const Vec axis = transformOf(camera->frame()->inverseTransformOf(Vec(0.0, 0.0, -1.0)));
355  Quaternion rot(axis, angle-prev_angle);
356  //#CONNECTION# These two methods should go together (spinning detection and activation)
357  computeMouseSpeed(event);
359  spin();
360  break;
361  }
362 
363  case QGLViewer::SCREEN_TRANSLATE:
364  {
365  Vec trans;
366  int dir = mouseOriginalDirection(event);
367  if (dir == 1)
368  trans.setValue(static_cast<float>(event->x() - prevPos_.x()), 0.0, 0.0);
369  else if (dir == -1)
370  trans.setValue(0.0, static_cast<float>(prevPos_.y() - event->y()), 0.0);
371 
372  switch (camera->type())
373  {
374  case Camera::PERSPECTIVE :
375  trans *= 2.0 * tan(camera->fieldOfView()/2.0) * fabs((camera->frame()->coordinatesOf(position())).z) / camera->screenHeight();
376  break;
377  case Camera::ORTHOGRAPHIC :
378  {
379  GLdouble w,h;
380  camera->getOrthoWidthHeight(w, h);
381  trans[0] *= 2.0 * w / camera->screenWidth();
382  trans[1] *= 2.0 * h / camera->screenHeight();
383  break;
384  }
385  }
386  // Transform to world coordinate system.
387  trans = camera->frame()->orientation().rotate(translationSensitivity()*trans);
388  // And then down to frame
389  if (referenceFrame())
390  trans = referenceFrame()->transformOf(trans);
391 
392  translate(trans);
393  break;
394  }
395 
396  case QGLViewer::ROTATE:
397  {
398  Vec trans = camera->projectedCoordinatesOf(position());
399  Quaternion rot = deformedBallQuaternion(event->x(), event->y(), trans[0], trans[1], camera);
400  trans = Vec(-rot[0], -rot[1], -rot[2]);
401  trans = camera->frame()->orientation().rotate(trans);
402  trans = transformOf(trans);
403  rot[0] = trans[0];
404  rot[1] = trans[1];
405  rot[2] = trans[2];
406  //#CONNECTION# These two methods should go together (spinning detection and activation)
407  computeMouseSpeed(event);
409  spin();
410  break;
411  }
412 
413  case QGLViewer::NO_MOUSE_ACTION:
414  // Possible when the ManipulatedFrame is a MouseGrabber. This method is then called without startAction
415  // because of mouseTracking.
416  break;
417  }
418 
419  if (action_ != QGLViewer::NO_MOUSE_ACTION)
420  {
421  prevPos_ = event->pos();
422  Q_EMIT manipulated();
423  }
424 }
425 
433 void ManipulatedFrame::mouseReleaseEvent(QMouseEvent* const event, Camera* const camera)
434 {
435  Q_UNUSED(event);
436  Q_UNUSED(camera);
437 
438  keepsGrabbingMouse_ = false;
439 
440  if (previousConstraint_)
441  setConstraint(previousConstraint_);
442 
443  if (((action_ == QGLViewer::ROTATE) || (action_ == QGLViewer::SCREEN_ROTATE)) && (mouseSpeed_ >= spinningSensitivity()))
444  startSpinning(delay_);
445 
446  action_ = QGLViewer::NO_MOUSE_ACTION;
447 }
448 
454 void ManipulatedFrame::mouseDoubleClickEvent(QMouseEvent* const event, Camera* const camera)
455 {
456 #if QT_VERSION >= 0x040000
457  if (event->modifiers() == Qt::NoModifier)
458 #else
459  if (event->state() == Qt::NoButton)
460 #endif
461  switch (event->button())
462  {
463  case Qt::LeftButton: alignWithFrame(camera->frame()); break;
464  case Qt::RightButton: projectOnLine(camera->position(), camera->viewDirection()); break;
465  default: break;
466  }
467 }
468 
473 void ManipulatedFrame::wheelEvent(QWheelEvent* const event, Camera* const camera)
474 {
475  //#CONNECTION# QGLViewer::setWheelBinding
476  if (action_ == QGLViewer::ZOOM)
477  {
478  const float wheelSensitivityCoef = 8E-4f;
479  Vec trans(0.0, 0.0, -event->delta()*wheelSensitivity()*wheelSensitivityCoef*(camera->position()-position()).norm());
480 
481  //#CONNECTION# Cut-pasted from the mouseMoveEvent ZOOM case
482  trans = camera->frame()->orientation().rotate(trans);
483  if (referenceFrame())
484  trans = referenceFrame()->transformOf(trans);
485  translate(trans);
486  Q_EMIT manipulated();
487  }
488 
489  // #CONNECTION# startAction should always be called before
490  if (previousConstraint_)
491  setConstraint(previousConstraint_);
492 
493  action_ = QGLViewer::NO_MOUSE_ACTION;
494 }
495 
496 
498 
503 static float projectOnBall(float x, float y)
504 {
505  // If you change the size value, change angle computation in deformedBallQuaternion().
506  const float size = 1.0f;
507  const float size2 = size*size;
508  const float size_limit = size2*0.5;
509 
510  const float d = x*x + y*y;
511  return d < size_limit ? sqrt(size2 - d) : size_limit/sqrt(d);
512 }
513 
514 #ifndef DOXYGEN
515 
517 Quaternion ManipulatedFrame::deformedBallQuaternion(int x, int y, float cx, float cy, const Camera* const camera)
518 {
519  // Points on the deformed ball
520  float px = rotationSensitivity() * (prevPos_.x() - cx) / camera->screenWidth();
521  float py = rotationSensitivity() * (cy - prevPos_.y()) / camera->screenHeight();
522  float dx = rotationSensitivity() * (x - cx) / camera->screenWidth();
523  float dy = rotationSensitivity() * (cy - y) / camera->screenHeight();
524 
525  const Vec p1(px, py, projectOnBall(px, py));
526  const Vec p2(dx, dy, projectOnBall(dx, dy));
527  // Approximation of rotation angle
528  // Should be divided by the projectOnBall size, but it is 1.0
529  const Vec axis = cross(p2,p1);
530  const float angle = 2.0 * asin(sqrt(axis.squaredNorm() / p1.squaredNorm() / p2.squaredNorm()));
531  return Quaternion(axis, angle);
532 }
533 #endif // DOXYGEN