manipulatedCameraFrame.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 "manipulatedCameraFrame.h"
25 #include "qglviewer.h"
26 
27 #if QT_VERSION >= 0x040000
28 # include <QMouseEvent>
29 #endif
30 
31 using namespace qglviewer;
32 using namespace std;
33 
40  : driveSpeed_(0.0), flyUpVector_(0.0, 1.0, 0.0)
41 {
42  setFlySpeed(0.0);
44 
45  connect(&flyTimer_, SIGNAL(timeout()), SLOT(flyUpdate()));
46 }
47 
50 {
52 
53  setFlySpeed(mcf.flySpeed());
55 
56  return *this;
57 }
58 
61  : ManipulatedFrame(mcf)
62 {
64  connect(&flyTimer_, SIGNAL(timeout()), SLOT(flyUpdate()));
65  (*this)=(mcf);
66 }
67 
69 
74 {
76 }
77 
78 #ifndef DOXYGEN
79 
81 void ManipulatedCameraFrame::flyUpdate()
82 {
83  static Vec flyDisp(0.0, 0.0, 0.0);
84  switch (action_)
85  {
86  case QGLViewer::MOVE_FORWARD:
87  flyDisp.z = -flySpeed();
89  break;
90  case QGLViewer::MOVE_BACKWARD:
91  flyDisp.z = flySpeed();
93  break;
94  case QGLViewer::DRIVE:
95  flyDisp.z = flySpeed() * driveSpeed_;
97  break;
98  default:
99  break;
100  }
101 
102  // Needs to be out of the switch since ZOOM/fastDraw()/wheelEvent use this callback to trigger a final draw().
103  // #CONNECTION# wheelEvent.
104  Q_EMIT manipulated();
105 }
106 #endif
107 
110 void ManipulatedCameraFrame::updateFlyUpVector()
111 {
112  flyUpVector_ = inverseTransformOf(Vec(0.0, 1.0, 0.0));
113 }
114 
116 // S t a t e s a v i n g a n d r e s t o r i n g //
118 
132 QDomElement ManipulatedCameraFrame::domElement(const QString& name, QDomDocument& document) const
133 {
134  QDomElement e = ManipulatedFrame::domElement(name, document);
135  QDomElement mcp = document.createElement("ManipulatedCameraParameters");
136  mcp.setAttribute("flySpeed", QString::number(flySpeed()));
137  mcp.appendChild(flyUpVector().domElement("flyUpVector", document));
138  e.appendChild(mcp);
139  return e;
140 }
141 
146 void ManipulatedCameraFrame::initFromDOMElement(const QDomElement& element)
147 {
148  // No need to initialize, since default flyUpVector and flySpeed are not meaningful.
149  // It's better to keep current ones. And it would destroy constraint() and referenceFrame().
150  // *this = ManipulatedCameraFrame();
152 
153  QDomElement child=element.firstChild().toElement();
154  while (!child.isNull())
155  {
156  if (child.tagName() == "ManipulatedCameraParameters")
157  {
158  setFlySpeed(DomUtils::floatFromDom(child, "flySpeed", flySpeed()));
159 
160  QDomElement schild=child.firstChild().toElement();
161  while (!schild.isNull())
162  {
163  if (schild.tagName() == "flyUpVector")
164  setFlyUpVector(Vec(schild));
165 
166  schild = schild.nextSibling().toElement();
167  }
168  }
169  child = child.nextSibling().toElement();
170  }
171 }
172 
173 
175 // M o u s e h a n d l i n g //
177 
178 #ifndef DOXYGEN
179 
180 void ManipulatedCameraFrame::startAction(int ma, bool withConstraint)
181 {
182  ManipulatedFrame::startAction(ma, withConstraint);
183 
184  switch (action_)
185  {
186  case QGLViewer::MOVE_FORWARD:
187  case QGLViewer::MOVE_BACKWARD:
188  case QGLViewer::DRIVE:
189 #if QT_VERSION >= 0x040000
190  flyTimer_.setSingleShot(false);
191 #endif
192  flyTimer_.start(10);
193  break;
194  default:
195  break;
196  }
197 }
198 #endif
199 
204 void ManipulatedCameraFrame::mouseMoveEvent(QMouseEvent* const event, Camera* const camera)
205 {
206  // #CONNECTION# QGLViewer::mouseMoveEvent does the updateGL.
207  switch (action_)
208  {
209  case QGLViewer::TRANSLATE:
210  {
211  const QPoint delta = prevPos_ - event->pos();
212  Vec trans(static_cast<float>(delta.x()), static_cast<float>(-delta.y()), 0.0);
213  // Scale to fit the screen mouse displacement
214  switch (camera->type())
215  {
216  case Camera::PERSPECTIVE :
217  trans *= 2.0 * tan(camera->fieldOfView()/2.0) *
218  fabs((camera->frame()->coordinatesOf(revolveAroundPoint())).z) / camera->screenHeight();
219  break;
220  case Camera::ORTHOGRAPHIC :
221  {
222  GLdouble w,h;
223  camera->getOrthoWidthHeight(w, h);
224  trans[0] *= 2.0 * w / camera->screenWidth();
225  trans[1] *= 2.0 * h / camera->screenHeight();
226  break;
227  }
228  }
230  break;
231  }
232 
233  case QGLViewer::MOVE_FORWARD:
234  {
235  Quaternion rot = pitchYawQuaternion(event->x(), event->y(), camera);
236  rotate(rot);
237  //#CONNECTION# wheelEvent MOVE_FORWARD case
238  // actual translation is made in flyUpdate().
239  //translate(inverseTransformOf(Vec(0.0, 0.0, -flySpeed())));
240  break;
241  }
242 
243  case QGLViewer::MOVE_BACKWARD:
244  {
245  Quaternion rot = pitchYawQuaternion(event->x(), event->y(), camera);
246  rotate(rot);
247  // actual translation is made in flyUpdate().
248  //translate(inverseTransformOf(Vec(0.0, 0.0, flySpeed())));
249  break;
250  }
251 
252  case QGLViewer::DRIVE:
253  {
254  Quaternion rot = turnQuaternion(event->x(), camera);
255  rotate(rot);
256  // actual translation is made in flyUpdate().
257  driveSpeed_ = 0.01 * (event->y() - pressPos_.y());
258  break;
259  }
260 
261  case QGLViewer::ZOOM:
262  {
263  //#CONNECTION# wheelEvent() ZOOM case
264  const float coef = qMax(fabsf((camera->frame()->coordinatesOf(camera->revolveAroundPoint())).z), 0.2f*camera->sceneRadius());
265  Vec trans(0.0, 0.0, -coef * (event->y() - prevPos_.y()) / camera->screenHeight());
267  break;
268  }
269 
270  case QGLViewer::LOOK_AROUND:
271  {
272  Quaternion rot = pitchYawQuaternion(event->x(), event->y(), camera);
273  rotate(rot);
274  break;
275  }
276 
277  case QGLViewer::ROTATE:
278  {
279  Vec trans = camera->projectedCoordinatesOf(revolveAroundPoint());
280  Quaternion rot = deformedBallQuaternion(event->x(), event->y(), trans[0], trans[1], camera);
281  //#CONNECTION# These two methods should go together (spinning detection and activation)
282  computeMouseSpeed(event);
284  spin();
285  break;
286  }
287 
288  case QGLViewer::SCREEN_ROTATE:
289  {
290  Vec trans = camera->projectedCoordinatesOf(revolveAroundPoint());
291 
292  const float angle = atan2(event->y() - trans[1], event->x() - trans[0]) - atan2(prevPos_.y()-trans[1], prevPos_.x()-trans[0]);
293 
294  Quaternion rot(Vec(0.0, 0.0, 1.0), angle);
295  //#CONNECTION# These two methods should go together (spinning detection and activation)
296  computeMouseSpeed(event);
298  spin();
299  updateFlyUpVector();
300  break;
301  }
302 
303  case QGLViewer::ROLL:
304  {
305  const float angle = M_PI * (event->x() - prevPos_.x()) / camera->screenWidth();
306  Quaternion rot(Vec(0.0, 0.0, 1.0), angle);
307  rotate(rot);
309  updateFlyUpVector();
310  break;
311  }
312 
313  case QGLViewer::SCREEN_TRANSLATE:
314  {
315  Vec trans;
316  int dir = mouseOriginalDirection(event);
317  if (dir == 1)
318  trans.setValue(static_cast<float>(prevPos_.x() - event->x()), 0.0, 0.0);
319  else if (dir == -1)
320  trans.setValue(0.0, static_cast<float>(event->y() - prevPos_.y()), 0.0);
321 
322  switch (camera->type())
323  {
324  case Camera::PERSPECTIVE :
325  trans *= 2.0 * tan(camera->fieldOfView()/2.0) *
326  fabs((camera->frame()->coordinatesOf(revolveAroundPoint())).z) / camera->screenHeight();
327  break;
328  case Camera::ORTHOGRAPHIC :
329  {
330  GLdouble w,h;
331  camera->getOrthoWidthHeight(w, h);
332  trans[0] *= 2.0 * w / camera->screenWidth();
333  trans[1] *= 2.0 * h / camera->screenHeight();
334  break;
335  }
336  }
337 
339  break;
340  }
341 
342  case QGLViewer::ZOOM_ON_REGION:
343  case QGLViewer::NO_MOUSE_ACTION:
344  break;
345  }
346 
347  if (action_ != QGLViewer::NO_MOUSE_ACTION)
348  {
349  prevPos_ = event->pos();
350  if (action_ != QGLViewer::ZOOM_ON_REGION)
351  // ZOOM_ON_REGION should not emit manipulated().
352  // prevPos_ is used to draw rectangle feedback.
353  Q_EMIT manipulated();
354  }
355 }
356 
357 
360 void ManipulatedCameraFrame::mouseReleaseEvent(QMouseEvent* const event, Camera* const camera)
361 {
362  if ((action_ == QGLViewer::MOVE_FORWARD) || (action_ == QGLViewer::MOVE_BACKWARD) || (action_ == QGLViewer::DRIVE))
363  flyTimer_.stop();
364 
365  if (action_ == QGLViewer::ZOOM_ON_REGION)
366  camera->fitScreenRegion(QRect(pressPos_, event->pos()));
367 
369 }
370 
377 void ManipulatedCameraFrame::wheelEvent(QWheelEvent* const event, Camera* const camera)
378 {
379  //#CONNECTION# QGLViewer::setWheelBinding, ManipulatedFrame::wheelEvent.
380  switch (action_)
381  {
382  case QGLViewer::ZOOM:
383  {
384  const float wheelSensitivityCoef = 8E-4f;
385  //#CONNECTION# mouseMoveEvent() ZOOM case
386  const float coef = qMax(fabsf((camera->frame()->coordinatesOf(camera->revolveAroundPoint())).z), 0.2f*camera->sceneRadius());
387  Vec trans(0.0, 0.0, coef * event->delta() * wheelSensitivity() * wheelSensitivityCoef);
389  Q_EMIT manipulated();
390  break;
391  }
392  case QGLViewer::MOVE_FORWARD:
393  case QGLViewer::MOVE_BACKWARD:
394  //#CONNECTION# mouseMoveEvent() MOVE_FORWARD case
395  translate(inverseTransformOf(Vec(0.0, 0.0, 0.2*flySpeed()*event->delta())));
396  Q_EMIT manipulated();
397  break;
398  default:
399  break;
400  }
401 
402  // #CONNECTION# startAction should always be called before
403  if (previousConstraint_)
404  setConstraint(previousConstraint_);
405 
406  // The wheel triggers a fastDraw. A final updateGL is needed after the last wheel event to
407  // polish the rendering using draw(). Since the last wheel event does not say its name, we use
408  // the flyTimer_ to trigger flyUpdate(), which emits manipulated. Two wheel events
409  // separated by more than this delay milliseconds will trigger a draw().
410  const int finalDrawAfterWheelEventDelay = 400;
411 
412  // Starts (or prolungates) the timer.
413 #if QT_VERSION >= 0x040000
414  flyTimer_.setSingleShot(true);
415  flyTimer_.start(finalDrawAfterWheelEventDelay);
416 #else
417  flyTimer_.start(finalDrawAfterWheelEventDelay, true);
418 #endif
419 
420  // This could also be done *before* manipulated is emitted, so that isManipulated() returns false.
421  // But then fastDraw would not be used with wheel.
422  // Detecting the last wheel event and forcing a final draw() is done using the timer_.
423  action_ = QGLViewer::NO_MOUSE_ACTION;
424 }
425 
427 
429 Quaternion ManipulatedCameraFrame::turnQuaternion(int x, const Camera* const camera)
430 {
431  return Quaternion(Vec(0.0, 1.0, 0.0), rotationSensitivity()*(prevPos_.x()-x)/camera->screenWidth());
432 }
433 
436 Quaternion ManipulatedCameraFrame::pitchYawQuaternion(int x, int y, const Camera* const camera)
437 {
438  const Quaternion rotX(Vec(1.0, 0.0, 0.0), rotationSensitivity()*(prevPos_.y()-y)/camera->screenHeight());
439  const Quaternion rotY(transformOf(flyUpVector()), rotationSensitivity()*(prevPos_.x()-x)/camera->screenWidth());
440  return rotY * rotX;
441 }