Visual Computing Library  devel
Loading...
Searching...
No Matches
trackball.h
1/*****************************************************************************
2 * VCLib *
3 * Visual Computing Library *
4 * *
5 * Copyright(C) 2021-2025 *
6 * Visual Computing Lab *
7 * ISTI - Italian National Research Council *
8 * *
9 * All rights reserved. *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the Mozilla Public License Version 2.0 as published *
13 * by the Mozilla Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * Mozilla Public License Version 2.0 *
20 * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. *
21 ****************************************************************************/
22
23#ifndef VCL_RENDER_VIEWER_TRACKBALL_H
24#define VCL_RENDER_VIEWER_TRACKBALL_H
25
26#include "camera.h"
27#include "lights/directional_light.h"
28
29#include <vclib/space/core/quaternion.h>
30
31#include <variant>
32
33namespace vcl {
34
50template<typename Scalar>
52{
53public:
54 enum MotionType {
55 ARC,
56 PAN,
57 ZMOVE,
58 ROLL,
59 SCALE,
60 FOV,
61 FOCUS,
62 DIR_LIGHT_ARC,
63 MOTION_NUMBER
64 };
65
67 {
68 Point3<Scalar> axis;
69 Scalar scalar; // could be an angle or a distance
70
71 TransformArgs(const Point3<Scalar>& a, Scalar s) : axis(a), scalar(s) {}
72 };
73
74 using AtomicMotionArg =
75 std::variant<TransformArgs, Scalar, Point3<Scalar>, std::monostate>;
76
77private:
78 // arcball radius ratio wrt minimum screen dimension
79 // set as the inverse of the golden ratio
80 static constexpr Scalar ARC_BALL_RADIUS_RATIO = 1.0 / 1.61803398875;
81
82 static constexpr Scalar FOCUS_SCALE_FACTOR = 1.15;
83
84 static constexpr Scalar DEFAULT_FOV_DEG = 54.0;
85
86 Camera<Scalar> mCamera;
87
88 // Similarity holding the manipulator transformation.
89 // The convention is that the transformation is expressed as
90 // TRANSLATION * ROTATION * SCALE
91 // to avoid error accumulation we should split the transformation
92 // into the three components (Point3, Quaternion, Scalar)
93 Affine3<Scalar> mTransform = Affine3<Scalar>::Identity();
94
95 Quaternion<Scalar> mDirectionalLightTransform;
96
97 Point2<Scalar> mScreenSize = {-1, -1};
98
99 // trackball radius in camera space
100 // this value affects the interaction and the visualization of the trackball
101 Scalar mRadius = ARC_BALL_RADIUS_RATIO;
102
103 // trackball interaction state
104 bool mDragging = false;
105 MotionType mCurrDragMotion = MOTION_NUMBER;
106
107 // initial arcball hit point
108 Point3<Scalar> mInitialPoint;
109 // initial transformation
110 Affine3<Scalar> mInitialTransform = Affine3<Scalar>::Identity();
111 // initial light rotation
112 Quaternion<Scalar> mInitialDirRotation = Quaternion<Scalar>::Identity();
113
114 Point2<Scalar> mCurrMousePosition;
115 Point2<Scalar> mPrevMousePosition;
116
117public:
118 TrackBall() { mCamera.setFieldOfViewAdaptingEyeDistance(DEFAULT_FOV_DEG); }
119
120 void reset()
121 {
122 auto screenSize = mScreenSize;
123 auto currMousePosition = mCurrMousePosition;
124 auto prevMousePosition = mPrevMousePosition;
125 *this = TrackBall();
126 setScreenSize(screenSize);
127 mCurrMousePosition = currMousePosition;
128 mPrevMousePosition = prevMousePosition;
129 }
130
136 void reset(const Point3<Scalar>& center, Scalar scale)
137 {
138 reset();
139 mTransform.scale(scale);
140 mTransform.translate(-center);
141 }
142
143 void resetDirectionalLight()
144 {
145 mDirectionalLightTransform = Quaternion<Scalar>();
146 }
147
148 Point3<Scalar> center() const
149 {
150 // obtain the point in world space that maps to zero when transformed
151 return mTransform.inverse().translation();
152 }
153
154 void setCenter(const Point3<Scalar>& center)
155 {
156 // transform the center (world space) using the current transformation
157 // then translate it to the origin
158 mTransform.pretranslate(-(mTransform * center));
159 }
160
161 Scalar scale() const
162 {
163 // return the average of the norm of linear part vectors
164 return mTransform.linear().colwise().norm().mean();
165 }
166
167 void setScale(Scalar scale)
168 {
169 // scale the linear part of the transformation
170 // so the manipulator will be scaled around the origin
171 mTransform.prescale(scale);
172 // TODO: scale also near/far?
173 }
174
175 void changeScale(Scalar factor) { mTransform.prescale(factor); }
176
177 void setRotation(const Quaternion<Scalar>& rotation)
178 {
179 Affine3<Scalar> tx = Affine3<Scalar>::Identity();
180 tx.rotate(rotation).scale(scale());
181 mTransform.linear() = tx.linear();
182 }
183
184 void setRotation(const Point3<Scalar>& axis, Scalar angle)
185 {
186 setRotation(Quaternion<Scalar>(angle, axis));
187 }
188
192 Scalar fovDeg() const { return mCamera.fieldOfView(); }
193
198 void setFovDeg(Scalar fov)
199 {
200 mCamera.setFieldOfViewAdaptingEyeDistance(fov);
201 }
202
203 Camera<Scalar>::ProjectionMode projectionMode() const
204 {
205 return mCamera.projectionMode();
206 }
207
208 void setProjectionMode(Camera<Scalar>::ProjectionMode mode)
209 {
210 mCamera.projectionMode() = mode;
211 mCamera.setFieldOfViewAdaptingEyeDistance(mCamera.fieldOfView());
212 }
213
214 void setScreenSize(Scalar width, Scalar height)
215 {
216 if (width > 1 || height > 1) {
217 mScreenSize.x() = width;
218 mScreenSize.y() = height;
219 mCamera.aspectRatio() = width / height;
220 mRadius = ARC_BALL_RADIUS_RATIO * mCamera.verticalHeight() / 2.0;
221 if (width < height)
222 mRadius *= mCamera.aspectRatio();
223 }
224 }
225
226 void setScreenSize(const Point2<Scalar>& sz)
227 {
228 setScreenSize(sz.x(), sz.y());
229 }
230
231 DirectionalLight<Scalar> light() const
232 {
233 // TODO: return a light direction stored in this class,
234 // in order to store also the light color
235 return DirectionalLight<Scalar>(
236 mDirectionalLightTransform * Point3<Scalar>(0, 0, 1));
237 }
238
239 void setLightDirection(const Point3<Scalar>& direction)
240 {
241 mDirectionalLightTransform = Quaternion<Scalar>::FromTwoVectors(
242 Point3<Scalar>(0, 0, 1), direction);
243 }
244
245 const vcl::Camera<Scalar>& camera() const { return mCamera; }
246
247 Matrix44<Scalar> viewMatrix() const
248 {
249 return mCamera.viewMatrix() * mTransform.matrix();
250 }
251
252 Matrix44<Scalar> projectionMatrix() const
253 {
254 return mCamera.projectionMatrix();
255 }
256
257 Matrix44<Scalar> gizmoMatrix() const
258 {
259 Affine3<Scalar> rot_radius = Affine3<Scalar>::Identity();
260 rot_radius.rotate(mTransform.rotation()).scale(mRadius);
261 return mCamera.viewMatrix() * rot_radius.matrix();
262 }
263
264 Matrix44<Scalar> lightGizmoMatrix() const
265 {
266 Affine3<Scalar> rot_radius = Affine3<Scalar>::Identity();
267 rot_radius.rotate(mDirectionalLightTransform).scale(mRadius);
268 return mCamera.viewMatrix() * rot_radius.matrix();
269 }
270
271 bool isDragging() const { return mDragging; }
272
273 MotionType currentMotion() const { return mCurrDragMotion; }
274
275 // Atomic motions
276
300 MotionType motion,
301 AtomicMotionArg step = std::monostate())
302 {
303 if (std::holds_alternative<Scalar>(step)) {
304 Scalar inc = std::get<Scalar>(step);
305
306 switch (motion) {
307 case ROLL: roll(inc); break;
308 case SCALE: performScale(inc); break;
309 case FOV: performFov(inc); break;
310 case ZMOVE: performZmove(inc); break;
311 default: break;
312 }
313 }
314 else if (std::holds_alternative<TransformArgs>(step)) {
315 const TransformArgs& args = std::get<TransformArgs>(step);
316 switch (motion) {
317 case ARC: rotate(args.axis, args.scalar); break;
318 case DIR_LIGHT_ARC: rotateDirLight(args.axis, args.scalar); break;
319 default: break;
320 }
321 }
322 else if (std::holds_alternative<Point3<Scalar>>(step)) {
323 Point3<Scalar> val = std::get<Point3<Scalar>>(step);
324 switch (motion) {
325 case PAN: translate(val); break;
326 case FOCUS: setCenter(val); changeScale(FOCUS_SCALE_FACTOR);
327 default: break;
328 }
329 }
330 }
331
332 void applyScale(Scalar value) { applyAtomicMotion(SCALE, value); }
333
334 void applyRoll(Scalar angleRad) { applyAtomicMotion(ROLL, angleRad); }
335
336 void applyPan(const Point3<Scalar>& translation)
337 {
338 applyAtomicMotion(PAN, translation);
339 }
340
341 void applyArc(const Point3<Scalar>& axis, Scalar angle)
342 {
343 applyAtomicMotion(ARC, TransformArgs(axis, angle));
344 }
345
346 // Drag motions
347
348 void setMousePosition(Scalar x, Scalar y)
349 {
350 mPrevMousePosition = mCurrMousePosition;
351 mCurrMousePosition.x() = x;
352 mCurrMousePosition.y() = mScreenSize.y() - y;
353 }
354
355 void setMousePosition(const Point2<Scalar>& point)
356 {
357 setMousePosition(point.x(), point.y());
358 }
359
366 void beginDragMotion(MotionType motion)
367 {
368 assert(motion != MOTION_NUMBER && "Invalid motion type");
369
370 // no need to restart?
371 if (mCurrDragMotion == motion)
372 return;
373
374 // end previous motion
375 if (mCurrDragMotion != motion)
376 endDragMotion(mCurrDragMotion);
377
379 mInitialPoint = pointOnArcball(mCurrMousePosition);
380 mInitialTransform = mTransform;
381 mInitialDirRotation = mDirectionalLightTransform;
382 mDragging = true;
383 }
384
391 void endDragMotion(MotionType motion)
392 {
394 mDragging = false;
395 }
396
402 void update() // TODO: rename this function (it just updates the motion)
403 {
404 assert(
405 mDragging != (mCurrDragMotion == MOTION_NUMBER) &&
406 "Invalid state: dragging and no motion");
407 if (mDragging && mCurrMousePosition != mPrevMousePosition)
408 drag(mCurrDragMotion);
409 }
410
411private:
414 void setDragMotionValue(MotionType motion, bool value)
415 {
416 mCurrDragMotion = value ? motion : MOTION_NUMBER;
417 }
418
419 void drag(MotionType motion)
420 {
421 switch (motion) {
422 case ARC: dragArc(); break;
423 case PAN: dragPan(); break;
424 case ZMOVE: dragZmove(); break;
425 case ROLL: dragRoll(); break;
426 case SCALE: dragScale(); break;
427 case DIR_LIGHT_ARC: dragDirLightArc(); break;
428 default: break;
429 }
430 }
431
432 Scalar trackballToPixelRatio() const
433 {
434 return mCamera.verticalHeight() / mScreenSize.y();
435 }
436
437 Point3<Scalar> pointOnTrackballPlane(Point2<Scalar> screenCoord) const
438 {
439 // convert to camera space
440 // range [-1, 1] for Y and [-aspectRatio, aspectRatio] for X
441 screenCoord =
442 (screenCoord - mScreenSize / 2.0) * trackballToPixelRatio();
443 return Point3<Scalar>(screenCoord.x(), screenCoord.y(), 0.0);
444 }
445
446 Point3<Scalar> pointOnArcball(Point2<Scalar> screenCoord) const
447 {
448 // convert to range [-1, 1] for Y and [-aspectRatio, aspectRatio] for X
449 screenCoord =
450 (screenCoord - mScreenSize / 2.0) * trackballToPixelRatio();
451
452 // Solve the equations in 2D on the plane passing through the eye,
453 // the trackball center, and the intersection line.
454 // The X coordinate corresponds to the Z axis, while the Y coordinate
455 // is on the XY trackball plane.
456
457 const double h = screenCoord.norm();
458 Point2<Scalar> hitPoint;
459
460 if (mCamera.projectionMode() == Camera<Scalar>::ProjectionMode::ORTHO) {
461 // in ortho projection we can project the Y coordinate directly
462 if (h < (M_SQRT1_2 * mRadius)) {
463 // hit sphere
464 // Y^2 + X^2 = r^2
465 // X = sqrt(r^2 - Y^2)
466 hitPoint =
467 Point2<Scalar>(std::sqrt(mRadius * mRadius - h * h), h);
468 }
469 else {
470 // hit hyperbola
471 // X*Y = (r^2 / 2)
472 // X = r^2 / (2 * Y)
473 hitPoint = Point2<Scalar>(mRadius * mRadius / (2 * h), h);
474 }
475 }
476 else {
477 assert(
478 mCamera.projectionMode() ==
479 Camera<Scalar>::ProjectionMode::PERSPECTIVE &&
480 "invalid camera projection value is supported");
481 // PERSPECTIVE PROJECTION
482 //
483 // | ^
484 // | h /
485 //--target------------> Y axis
486 // | /
487 // | /
488 // | /
489 // | /
490 // | / intersecting line
491 // d | /
492 // | /
493 // | /
494 // | /
495 // |/
496 // eye
497 // /|
498 // / |
499 // / |
500 // \./ X axis
501
502 // Equation constants:
503 // d = distance from the eye to the target
504 // h = distance(target, line plane inter
505 const double d = (mCamera.eye() - mCamera.center()).norm();
506
507 const double m = -h / d;
508
509 bool sphereHit = false;
510 Point2<Scalar> spherePoint;
511 {
512 // hit point on the sphere
513 //
514 // line equation:
515 // Y = -(h/d) * X + h
516 //
517 // circle equation:
518 // X^2 + Y^2 = r^2
519 //
520 // substitute Y in the circle equation:
521 // X^2 + (-h/d * X + h)^2 = r^2
522 // X^2 + h^2/d^2 * X^2 - 2 * h^2/d * X + h^2 = r^2
523 // (1 + h^2/d^2) * X^2 - 2 * h^2/d * X + h^2 - r^2 = 0
524
525 // a = 1 + h^2/d^2
526 // b = -2 * h^2/d
527 // c = h^2 - r^2
528 Scalar a = 1 + m * m;
529 Scalar b = -2 * h * h / d;
530 Scalar c = h * h - mRadius * mRadius;
531
532 // X = (-b +- sqrt(b^2 - 4ac)) / 2a
533 // Y = -(h/d) * X + h
534 // we take the positive solution (hit point closest to the eye)
535
536 Scalar delta = b * b - 4 * a * c;
537 sphereHit = delta >= 0;
538 if (sphereHit) {
539 spherePoint.x() = (-b + std::sqrt(delta)) / (2 * a);
540 spherePoint.y() = m * spherePoint.x() + h;
541 }
542 }
543
544 bool hyperHit = false;
545 Point2<Scalar> hyperPoint;
546 {
547 // hit point on the hyperbola
548 //
549 // line equation:
550 // Y = -(h/d) * X + h
551 //
552 // hyperbola equation:
553 // Y = 1/X * (r^2 / 2)
554
555 // substitute Y in the hyperbola equation:
556 // -(h/d) * X + h = 1/X * (r^2 / 2)
557 // -(h/d) * X^2 + h * X = r^2 / 2
558 // -h * X^2 + d * h * X = d * r^2 / 2
559 // -2 * h * X^2 + 2 * d * h * X = d * r^2
560 // -2 * h * X^2 + 2 * d * h * X - d * r^2 = 0
561
562 // a = -2 * h
563 // b = 2 * d * h
564 // c = - d * r^2
565
566 Scalar a = -2 * h;
567 Scalar b = 2 * d * h;
568 Scalar c = -d * mRadius * mRadius;
569
570 // X = (-b +- sqrt(b^2 - 4ac)) / 2a
571 // Y = -(h/d) * X + h
572 // we take the solution with smallest x
573 // (hit point farthest from the eye)
574
575 Scalar delta = b * b - 4 * a * c;
576 hyperHit = delta >= 0;
577 if (hyperHit) {
578 hyperPoint.x() = (-b + std::sqrt(delta)) / (2 * a);
579 hyperPoint.y() = m * hyperPoint.x() + h;
580 }
581 }
582
583 // discriminate between sphere and hyperbola hit points
584 const int hitCase = sphereHit + 2 * hyperHit;
585 switch (hitCase) {
586 case 0: // no hit
587 {
588 // get closeset point of the line to the origin
589 // rotate the line vector by 90 degrees counter-clockwise
590 Point2<Scalar> lineVector = Point2<Scalar>(d, -h).normalized();
591 Point2<Scalar> lineNormal =
592 Point2<Scalar>(-lineVector.y(), lineVector.x());
593 // project the eye on the vector
594 hitPoint = lineNormal * lineNormal.dot(Point2<Scalar>(d, 0));
595 } break;
596 case 1: // sphere hit
597 hitPoint = spherePoint;
598 break;
599 case 2: // hyperbola hit
600 hitPoint = hyperPoint;
601 break;
602 case 3: // both hit
603 {
604 // check angle of sphere point
605 Scalar angle = std::atan2(spherePoint.y(), spherePoint.x());
606 // if angle is more than 45 degrees, take the hyperbola point
607 if (angle > M_PI / 4) {
608 hitPoint = hyperPoint;
609 }
610 else {
611 hitPoint = spherePoint;
612 }
613 } break;
614 default: assert(false && "Invalid hit case");
615 }
616 }
617
618 // convert hit point to 3D
619 // rescale in trackball space
620 // FIXME: Avoid cancellation issue with different solution
621 assert(hitPoint.x() == hitPoint.x());
622 double factor =
623 (h > 0.0 && hitPoint.y() > 0.0) ? hitPoint.y() / h : 0.0;
624 return Point3<Scalar>(
625 screenCoord.x() * factor, screenCoord.y() * factor, hitPoint.x());
626 }
627
629 // (general purpose and used for atomic operations)
630
631 void rotate(const Quaternion<Scalar>& q) { mTransform.prerotate(q); }
632
633 void rotate(Point3<Scalar> axis, Scalar angleRad)
634 {
635 mTransform.prerotate(Quaternion<Scalar>(angleRad, axis));
636 }
637
643 void translate(Point3<Scalar> t) { mTransform.pretranslate(t); }
644
645 void rotateDirLight(const Quaternion<Scalar>& rotation)
646 {
647 mDirectionalLightTransform = rotation * mDirectionalLightTransform;
648 }
649
650 void rotateDirLight(Point3<Scalar> axis, Scalar angle)
651 {
652 rotateDirLight(Quaternion<Scalar>(angle, axis));
653 }
654
659 void dragArc()
660 {
661 const Point3<Scalar> point = pointOnArcball(mCurrMousePosition);
662 Eigen::AngleAxis<Scalar> ax(
663 Eigen::Quaternion<Scalar>::FromTwoVectors(mInitialPoint, point));
664 // use angle proportional to the arc length
665 const Scalar phi = (point - mInitialPoint).norm() / mRadius;
666 ax.angle() = phi;
667
668 // rotate from freezed transformation
669 // mTransform = mInitialTransform;
670 // mTransform.prerotate(ax);
671
672 // modify rotation only
673 mTransform.prerotate(
674 ax * mInitialTransform.rotation() *
675 mTransform.rotation().inverse());
676 }
677
680 void roll(Scalar delta) { rotate(Point3<Scalar>(0, 0, 1), delta); }
681
682 void dragRoll()
683 {
684 static constexpr Scalar ROLL_DIST_TO_CENTER_THRESHOLD = 0.025;
685
686 const Point3<Scalar> prev = pointOnTrackballPlane(mPrevMousePosition);
687 const Point3<Scalar> curr = pointOnTrackballPlane(mCurrMousePosition);
688 if (prev.norm() < ROLL_DIST_TO_CENTER_THRESHOLD ||
690 return;
691
692 Scalar angle =
693 std::atan2(curr.y(), curr.x()) - std::atan2(prev.y(), prev.x());
694
695 roll(angle);
696 }
697
705 {
706 const Point2<Scalar> pan = pixelDelta * trackballToPixelRatio();
707 translate(Point3<Scalar>(pan.x(), pan.y(), 0.0));
708 }
709
710 // drag
711 void dragPan()
712 {
713 Point2<Scalar> pixelDelta = mCurrMousePosition - mPrevMousePosition;
715 }
716
722 void performZmove(const Scalar& pixelDelta)
723 {
724 const Scalar translation = pixelDelta * trackballToPixelRatio();
726 }
727
728 // drag
729 void dragZmove()
730 {
731 auto pixelDelta = mCurrMousePosition.y() - mPrevMousePosition.y();
733 }
734
737 // scrolling and scaling are setup with "magic" numbers
739 {
740 pixelDelta /= 60;
741 const auto factor = std::pow(1.2f, -pixelDelta);
742 changeScale(factor);
743 }
744
745 void dragScale()
746 {
747 Scalar pixelDelta = mCurrMousePosition.y() - mPrevMousePosition.y();
749 }
750
753 // drag
755 {
756 const Point3<Scalar> point = pointOnArcball(mCurrMousePosition);
757 Eigen::AngleAxis<Scalar> ax(
758 Eigen::Quaternion<Scalar>::FromTwoVectors(mInitialPoint, point));
759 // use angle proportional to the arc length
760 const Scalar phi = (point - mInitialPoint).norm() / mRadius;
761
762 mDirectionalLightTransform = mInitialDirRotation;
763 rotateDirLight(Point3<Scalar>(ax.axis()), phi);
764 }
765
769 {
770 static constexpr double MIN_FOV_DEG = 5.0;
771 static constexpr double MAX_FOV_DEG = 90.0;
772
773 pixelDelta /= 60.0;
774 double fov = fovDeg();
775
776 // ortho -> perspective
777 if (mCamera.projectionMode() == Camera<Scalar>::ProjectionMode::ORTHO) {
778 if (pixelDelta > 0) {
780 mCamera.projectionMode() =
782 }
783 }
784
785 // update fov
786 fov = std::clamp(fov + 1.2 * pixelDelta, MIN_FOV_DEG, MAX_FOV_DEG);
787
788 // perspective -> ortho
789 if (mCamera.projectionMode() ==
791 fov == MIN_FOV_DEG) {
792 mCamera.projectionMode() = Camera<Scalar>::ProjectionMode::ORTHO;
793 }
794
795 // commit fov
796 setFovDeg(fov);
797 }
798};
799
800} // namespace vcl
801
802#endif // VCL_RENDER_VIEWER_TRACKBALL_H
A class representing a box in N-dimensional space.
Definition box.h:46
void translate(const PointT &p)
Translates the box by summing the values of p.
Definition box.h:456
PointT center() const
Calculates the center point of the box.
Definition box.h:259
Definition camera.h:32
Quaternion class.
Definition quaternion.h:51
The TrackBall class implements a trackball camera.
Definition trackball.h:52
void setFovDeg(Scalar fov)
Set the vertical field of view adapting the eye distance.
Definition trackball.h:198
void roll(Scalar delta)
Definition trackball.h:680
void dragArc()
Definition trackball.h:659
void translate(Point3< Scalar > t)
Translate in the camera space.
Definition trackball.h:643
void applyAtomicMotion(MotionType motion, AtomicMotionArg step=std::monostate())
Applies an atomic motion to the trackball. Atomic motions are motions that are applied atomically to ...
Definition trackball.h:299
void endDragMotion(MotionType motion)
Ends a drag motion.
Definition trackball.h:391
void dragDirLightArc()
Definition trackball.h:754
Scalar fovDeg() const
Get the vertical field of view.
Definition trackball.h:192
void performPan(const Point2< Scalar > &pixelDelta)
perform a pan operation
Definition trackball.h:704
void reset(const Point3< Scalar > &center, Scalar scale)
Reset the manipulator to a given center and scale.
Definition trackball.h:136
void setDragMotionValue(MotionType motion, bool value)
Definition trackball.h:414
void performZmove(const Scalar &pixelDelta)
translate in the camera z direction
Definition trackball.h:722
void performScale(Scalar pixelDelta)
Definition trackball.h:738
void performFov(Scalar pixelDelta)
Definition trackball.h:768
void update()
Updates the state of the trackball during a drag motion.
Definition trackball.h:402
void rotate(const Quaternion< Scalar > &q)
Definition trackball.h:631
void beginDragMotion(MotionType motion)
Starts a drag motion.
Definition trackball.h:366
Definition trackball.h:67