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
51template<typename Scalar>
53{
54public:
55 enum MotionType {
56 ARC,
57 PAN,
58 ZMOVE,
59 ROLL,
60 SCALE,
61 FOV,
62 FOCUS,
63 DIR_LIGHT_ARC,
64 MOTION_NUMBER
65 };
66
68 {
69 Point3<Scalar> axis;
70 Scalar scalar; // could be an angle or a distance
71
72 TransformArgs(const Point3<Scalar>& a, Scalar s) : axis(a), scalar(s) {}
73 };
74
75 using AtomicMotionArg =
76 std::variant<TransformArgs, Scalar, Point3<Scalar>, std::monostate>;
77
78private:
79 // arcball radius ratio wrt minimum screen dimension
80 // set as the inverse of the golden ratio
81 static constexpr Scalar ARC_BALL_RADIUS_RATIO = 1.0 / 1.61803398875;
82
83 static constexpr Scalar FOCUS_SCALE_FACTOR = 1.15;
84
85 static constexpr Scalar DEFAULT_FOV_DEG = 54.0;
86
87 // the camera used by the trackball (in front of the model centered in
88 // the origin)
89 Camera<Scalar> mCamera;
90
91 // Similarity holding the manipulator transformation.
92 // The convention is that the transformation is expressed as
93 // TRANSLATION * ROTATION * SCALE
94 // to avoid error accumulation we should split the transformation
95 // into the three components (Point3, Quaternion, Scalar)
96 Affine3<Scalar> mTransform = Affine3<Scalar>::Identity();
97
98 Quaternion<Scalar> mDirectionalLightTransform;
99
100 // screen size in 'pixels' (or generic units for high-DPI screens)
101 Point2<Scalar> mScreenSize = {-1, -1};
102
103 // trackball radius in camera space
104 // this value affects the interaction and the visualization of the trackball
105 Scalar mRadius = ARC_BALL_RADIUS_RATIO;
106
107 // trackball interaction state
108 bool mDragging = false;
109 MotionType mCurrDragMotion = MOTION_NUMBER;
110
111 // initial arcball hit point
112 Point3<Scalar> mInitialPoint;
113 // initial transformation
114 Affine3<Scalar> mInitialTransform = Affine3<Scalar>::Identity();
115 // initial light rotation
116 Quaternion<Scalar> mInitialDirRotation = Quaternion<Scalar>::Identity();
117
118 Point2<Scalar> mCurrMousePosition;
119 Point2<Scalar> mPrevMousePosition;
120
121public:
122 TrackBall() { mCamera.setFieldOfViewAdaptingEyeDistance(DEFAULT_FOV_DEG); }
123
124 void reset()
125 {
126 auto screenSize = mScreenSize;
127 auto currMousePosition = mCurrMousePosition;
128 auto prevMousePosition = mPrevMousePosition;
129 *this = TrackBall();
130 setScreenSize(screenSize);
131 mCurrMousePosition = currMousePosition;
132 mPrevMousePosition = prevMousePosition;
133 }
134
140 void reset(const Point3<Scalar>& center, Scalar scale)
141 {
142 reset();
143 mTransform.scale(scale);
144 mTransform.translate(-center);
145 }
146
154 {
155 Camera<Scalar> cam = mCamera;
156
157 // compute the camera properties from the trackball camere and
158 // the model transformation
159 const Affine3<Scalar> modelToCamera = mTransform.inverse();
160
161 Point3<Scalar> y = modelToCamera.linear().col(1);
162 Point3<Scalar> z = modelToCamera.linear().col(2);
163
164 cam.up() = y.normalized();
165 cam.eye() = modelToCamera * cam.eye();
166 cam.center() = cam.eye() - z.normalized();
167
168 if (cam.projectionMode() == Camera<Scalar>::ProjectionMode::ORTHO) {
169 // ortho camera always uses the vertical height = 2.0
170 // correct it using a scale factor when the model is scaled
171 // (assume uniform scale)
172 Scalar scale = mTransform.linear().col(0).norm();
173 cam.verticalHeight() /= scale;
174 }
175
176 return cam;
177 }
178
189 {
190 // FIXME? check what to do with the aspect ratio (if != 1.0)
191 this->reset();
192 mCamera.projectionMode() = cam.projectionMode();
193 Scalar correctiveScaleFactor = 1.0;
194 if (mCamera.projectionMode() ==
196 mCamera.setFieldOfViewAdaptingEyeDistance(cam.fieldOfView());
197 }
198 else {
199 // ortho camera always uses the vertical height = 2.0
200 // correct it using a scale factor
202 mCamera.verticalHeight() / cam.verticalHeight();
203 }
205 mCamera.eye() - mCamera.center();
206
207 // FIXME? near and far planes should be corrected too but we don't care
208 mCamera.nearPlane() = cam.nearPlane();
209 mCamera.farPlane() = cam.farPlane();
210 // generate a transformation that will place the model
211 // in front of the camera as if view from the the provided camera
212 const Point3<Scalar> y = cam.up();
213 const Point3<Scalar> z = (cam.eye() - cam.center()).normalized();
214 const Point3<Scalar> x = y.cross(z).normalized();
215
216 // set the transformation of the trackball such that the frame of the
217 // model is aligned with the inverse of the computed camera frame
218 mTransform.linear().col(0) = x;
219 mTransform.linear().col(1) = y;
220 mTransform.linear().col(2) = z;
221
222 // set the translation of the transformation such that the center
223 // of the camera is mapped to the origin
224 mTransform.pretranslate(cam.eye());
225
226 // invert the transformation
227 mTransform = mTransform.inverse();
228
229 // apply the corrective scale factor
230 mTransform.prescale(correctiveScaleFactor);
231
232 // correct the translation to account for the position of the
233 // trackball camera (mCamera)
234 mTransform.pretranslate(camTrackballTransl);
235 }
236
248 {
249 if (mCamera.projectionMode() ==
251 Point3<Scalar> transformedCenter = mTransform * center;
252 Point3<Scalar> toCenter = transformedCenter - mCamera.eye();
253 Point3<Scalar> eyeToCenter = (mCamera.center() - mCamera.eye());
254 Scalar scaleRatio =
255 toCenter.dot(eyeToCenter.normalized()) / eyeToCenter.norm();
256 if (scaleRatio < 0)
257 return; // center is behind the camera
258
259 mTransform.pretranslate(eyeToCenter);
260 mTransform.prescale(1.0 / scaleRatio);
261 mTransform.pretranslate(-eyeToCenter);
262 }
263 else {
264 assert(
265 mCamera.projectionMode() ==
267 // for ortho camera just translate the model along camera view
268 // direction
269 Point3<Scalar> transformedCenter = mTransform * center;
272 (mCamera.center() - mCamera.eye()).normalized();
273 Scalar distance = toCenter.dot(viewDir);
274 mTransform.pretranslate(-viewDir * distance);
275 }
276 }
277
278 void resetDirectionalLight()
279 {
280 mDirectionalLightTransform = Quaternion<Scalar>();
281 }
282
283 Point3<Scalar> center() const
284 {
285 // obtain the point in world space that maps to zero when transformed
286 return mTransform.inverse().translation();
287 }
288
289 void setCenter(const Point3<Scalar>& center)
290 {
291 // transform the center (world space) using the current transformation
292 // then translate it to the origin
293 mTransform.pretranslate(-(mTransform * center));
294 }
295
296 Scalar scale() const
297 {
298 // return the average of the norm of linear part vectors
299 return mTransform.linear().colwise().norm().mean();
300 }
301
302 void setScale(Scalar scale)
303 {
304 // scale the linear part of the transformation
305 // so the manipulator will be scaled around the origin
306 mTransform.prescale(scale);
307 // TODO: scale also near/far?
308 }
309
310 void changeScale(Scalar factor) { mTransform.prescale(factor); }
311
312 void setRotation(const Quaternion<Scalar>& rotation)
313 {
314 Affine3<Scalar> tx = Affine3<Scalar>::Identity();
315 tx.rotate(rotation).scale(scale());
316 mTransform.linear() = tx.linear();
317 }
318
319 void setRotation(const Point3<Scalar>& axis, Scalar angle)
320 {
321 setRotation(Quaternion<Scalar>(angle, axis));
322 }
323
327 Scalar fovDeg() const { return mCamera.fieldOfView(); }
328
333 void setFovDeg(Scalar fov)
334 {
335 mCamera.setFieldOfViewAdaptingEyeDistance(fov);
336 }
337
338 Camera<Scalar>::ProjectionMode projectionMode() const
339 {
340 return mCamera.projectionMode();
341 }
342
343 void setProjectionMode(Camera<Scalar>::ProjectionMode mode)
344 {
345 mCamera.projectionMode() = mode;
346 mCamera.setFieldOfViewAdaptingEyeDistance(mCamera.fieldOfView());
347 }
348
349 void setScreenSize(Scalar width, Scalar height)
350 {
351 if (width > 1 || height > 1) {
352 mScreenSize.x() = width;
353 mScreenSize.y() = height;
354 mCamera.aspectRatio() = width / height;
355 mRadius = ARC_BALL_RADIUS_RATIO * mCamera.verticalHeight() / 2.0;
356 if (width < height)
357 mRadius *= mCamera.aspectRatio();
358 }
359 }
360
361 void setScreenSize(const Point2<Scalar>& sz)
362 {
363 setScreenSize(sz.x(), sz.y());
364 }
365
366 DirectionalLight<Scalar> light() const
367 {
368 // TODO: return a light direction stored in this class,
369 // in order to store also the light color
370 return DirectionalLight<Scalar>(
371 mDirectionalLightTransform * Point3<Scalar>(0, 0, 1));
372 }
373
374 void setLightDirection(const Point3<Scalar>& direction)
375 {
376 mDirectionalLightTransform = Quaternion<Scalar>::FromTwoVectors(
377 Point3<Scalar>(0, 0, 1), direction);
378 }
379
380 Matrix44<Scalar> viewMatrix() const
381 {
382 return mCamera.viewMatrix() * mTransform.matrix();
383 }
384
385 Matrix44<Scalar> projectionMatrix() const
386 {
387 return mCamera.projectionMatrix();
388 }
389
390 Matrix44<Scalar> gizmoMatrix() const
391 {
392 Affine3<Scalar> rot_radius = Affine3<Scalar>::Identity();
393 rot_radius.rotate(mTransform.rotation()).scale(mRadius);
394 return mCamera.viewMatrix() * rot_radius.matrix();
395 }
396
397 Matrix44<Scalar> lightGizmoMatrix() const
398 {
399 Affine3<Scalar> rot_radius = Affine3<Scalar>::Identity();
400 rot_radius.rotate(mDirectionalLightTransform).scale(mRadius);
401 return mCamera.viewMatrix() * rot_radius.matrix();
402 }
403
404 bool isDragging() const { return mDragging; }
405
406 MotionType currentMotion() const { return mCurrDragMotion; }
407
408 // Atomic motions
409
433 MotionType motion,
434 AtomicMotionArg step = std::monostate())
435 {
436 if (std::holds_alternative<Scalar>(step)) {
437 Scalar inc = std::get<Scalar>(step);
438
439 switch (motion) {
440 case ROLL: roll(inc); break;
441 case SCALE: performScale(inc); break;
442 case FOV: performFov(inc); break;
443 case ZMOVE: performZmove(inc); break;
444 default: break;
445 }
446 }
447 else if (std::holds_alternative<TransformArgs>(step)) {
448 const TransformArgs& args = std::get<TransformArgs>(step);
449 switch (motion) {
450 case ARC: rotate(args.axis, args.scalar); break;
451 case DIR_LIGHT_ARC: rotateDirLight(args.axis, args.scalar); break;
452 default: break;
453 }
454 }
455 else if (std::holds_alternative<Point3<Scalar>>(step)) {
456 Point3<Scalar> val = std::get<Point3<Scalar>>(step);
457 switch (motion) {
458 case PAN: translate(val); break;
459 case FOCUS: setCenter(val); changeScale(FOCUS_SCALE_FACTOR);
460 default: break;
461 }
462 }
463 }
464
465 void applyScale(Scalar value) { applyAtomicMotion(SCALE, value); }
466
467 void applyRoll(Scalar angleRad) { applyAtomicMotion(ROLL, angleRad); }
468
469 void applyPan(const Point3<Scalar>& translation)
470 {
471 applyAtomicMotion(PAN, translation);
472 }
473
474 void applyArc(const Point3<Scalar>& axis, Scalar angle)
475 {
476 applyAtomicMotion(ARC, TransformArgs(axis, angle));
477 }
478
479 // Drag motions
480
481 void setMousePosition(Scalar x, Scalar y)
482 {
483 mPrevMousePosition = mCurrMousePosition;
484 mCurrMousePosition.x() = x;
485 mCurrMousePosition.y() = mScreenSize.y() - y;
486 }
487
488 void setMousePosition(const Point2<Scalar>& point)
489 {
490 setMousePosition(point.x(), point.y());
491 }
492
499 void beginDragMotion(MotionType motion)
500 {
501 assert(motion != MOTION_NUMBER && "Invalid motion type");
502
503 // no need to restart?
504 if (mCurrDragMotion == motion)
505 return;
506
507 // end previous motion
508 if (mCurrDragMotion != motion)
509 endDragMotion(mCurrDragMotion);
510
512 mInitialPoint = pointOnArcball(mCurrMousePosition);
513 mInitialTransform = mTransform;
514 mInitialDirRotation = mDirectionalLightTransform;
515 mDragging = true;
516 }
517
524 void endDragMotion(MotionType motion)
525 {
527 mDragging = false;
528 }
529
535 void update() // TODO: rename this function (it just updates the motion)
536 {
537 assert(
538 mDragging != (mCurrDragMotion == MOTION_NUMBER) &&
539 "Invalid state: dragging and no motion");
540 if (mDragging && mCurrMousePosition != mPrevMousePosition)
541 drag(mCurrDragMotion);
542 }
543
544private:
547 void setDragMotionValue(MotionType motion, bool value)
548 {
549 mCurrDragMotion = value ? motion : MOTION_NUMBER;
550 }
551
552 void drag(MotionType motion)
553 {
554 switch (motion) {
555 case ARC: dragArc(); break;
556 case PAN: dragPan(); break;
557 case ZMOVE: dragZmove(); break;
558 case ROLL: dragRoll(); break;
559 case SCALE: dragScale(); break;
560 case DIR_LIGHT_ARC: dragDirLightArc(); break;
561 default: break;
562 }
563 }
564
565 Scalar trackballToPixelRatio() const
566 {
567 return mCamera.verticalHeight() / mScreenSize.y();
568 }
569
570 Point3<Scalar> pointOnTrackballPlane(Point2<Scalar> screenCoord) const
571 {
572 // convert to camera space
573 // range [-1, 1] for Y and [-aspectRatio, aspectRatio] for X
574 screenCoord =
575 (screenCoord - mScreenSize / 2.0) * trackballToPixelRatio();
576 return Point3<Scalar>(screenCoord.x(), screenCoord.y(), 0.0);
577 }
578
579 Point3<Scalar> pointOnArcball(Point2<Scalar> screenCoord) const
580 {
581 // convert to range [-1, 1] for Y and [-aspectRatio, aspectRatio] for X
582 screenCoord =
583 (screenCoord - mScreenSize / 2.0) * trackballToPixelRatio();
584
585 // Solve the equations in 2D on the plane passing through the eye,
586 // the trackball center, and the intersection line.
587 // The X coordinate corresponds to the Z axis, while the Y coordinate
588 // is on the XY trackball plane.
589
590 const double h = screenCoord.norm();
591 Point2<Scalar> hitPoint;
592
593 if (mCamera.projectionMode() == Camera<Scalar>::ProjectionMode::ORTHO) {
594 // in ortho projection we can project the Y coordinate directly
595 if (h < (M_SQRT1_2 * mRadius)) {
596 // hit sphere
597 // Y^2 + X^2 = r^2
598 // X = sqrt(r^2 - Y^2)
599 hitPoint =
600 Point2<Scalar>(std::sqrt(mRadius * mRadius - h * h), h);
601 }
602 else {
603 // hit hyperbola
604 // X*Y = (r^2 / 2)
605 // X = r^2 / (2 * Y)
606 hitPoint = Point2<Scalar>(mRadius * mRadius / (2 * h), h);
607 }
608 }
609 else {
610 assert(
611 mCamera.projectionMode() ==
612 Camera<Scalar>::ProjectionMode::PERSPECTIVE &&
613 "invalid camera projection value is supported");
614 // PERSPECTIVE PROJECTION
615 //
616 // | ^
617 // | h /
618 //--target------------> Y axis
619 // | /
620 // | /
621 // | /
622 // | /
623 // | / intersecting line
624 // d | /
625 // | /
626 // | /
627 // | /
628 // |/
629 // eye
630 // /|
631 // / |
632 // / |
633 // \./ X axis
634
635 // Equation constants:
636 // d = distance from the eye to the target
637 // h = distance(target, line plane inter
638 const double d = (mCamera.eye() - mCamera.center()).norm();
639
640 const double m = -h / d;
641
642 bool sphereHit = false;
643 Point2<Scalar> spherePoint;
644 {
645 // hit point on the sphere
646 //
647 // line equation:
648 // Y = -(h/d) * X + h
649 //
650 // circle equation:
651 // X^2 + Y^2 = r^2
652 //
653 // substitute Y in the circle equation:
654 // X^2 + (-h/d * X + h)^2 = r^2
655 // X^2 + h^2/d^2 * X^2 - 2 * h^2/d * X + h^2 = r^2
656 // (1 + h^2/d^2) * X^2 - 2 * h^2/d * X + h^2 - r^2 = 0
657
658 // a = 1 + h^2/d^2
659 // b = -2 * h^2/d
660 // c = h^2 - r^2
661 Scalar a = 1 + m * m;
662 Scalar b = -2 * h * h / d;
663 Scalar c = h * h - mRadius * mRadius;
664
665 // X = (-b +- sqrt(b^2 - 4ac)) / 2a
666 // Y = -(h/d) * X + h
667 // we take the positive solution (hit point closest to the eye)
668
669 Scalar delta = b * b - 4 * a * c;
670 sphereHit = delta >= 0;
671 if (sphereHit) {
672 spherePoint.x() = (-b + std::sqrt(delta)) / (2 * a);
673 spherePoint.y() = m * spherePoint.x() + h;
674 }
675 }
676
677 bool hyperHit = false;
678 Point2<Scalar> hyperPoint;
679 {
680 // hit point on the hyperbola
681 //
682 // line equation:
683 // Y = -(h/d) * X + h
684 //
685 // hyperbola equation:
686 // Y = 1/X * (r^2 / 2)
687
688 // substitute Y in the hyperbola equation:
689 // -(h/d) * X + h = 1/X * (r^2 / 2)
690 // -(h/d) * X^2 + h * X = r^2 / 2
691 // -h * X^2 + d * h * X = d * r^2 / 2
692 // -2 * h * X^2 + 2 * d * h * X = d * r^2
693 // -2 * h * X^2 + 2 * d * h * X - d * r^2 = 0
694
695 // a = -2 * h
696 // b = 2 * d * h
697 // c = - d * r^2
698
699 Scalar a = -2 * h;
700 Scalar b = 2 * d * h;
701 Scalar c = -d * mRadius * mRadius;
702
703 // X = (-b +- sqrt(b^2 - 4ac)) / 2a
704 // Y = -(h/d) * X + h
705 // we take the solution with smallest x
706 // (hit point farthest from the eye)
707
708 Scalar delta = b * b - 4 * a * c;
709 hyperHit = delta >= 0;
710 if (hyperHit) {
711 hyperPoint.x() = (-b + std::sqrt(delta)) / (2 * a);
712 hyperPoint.y() = m * hyperPoint.x() + h;
713 }
714 }
715
716 // discriminate between sphere and hyperbola hit points
717 const int hitCase = sphereHit + 2 * hyperHit;
718 switch (hitCase) {
719 case 0: // no hit
720 {
721 // get closeset point of the line to the origin
722 // rotate the line vector by 90 degrees counter-clockwise
723 Point2<Scalar> lineVector = Point2<Scalar>(d, -h).normalized();
724 Point2<Scalar> lineNormal =
725 Point2<Scalar>(-lineVector.y(), lineVector.x());
726 // project the eye on the vector
727 hitPoint = lineNormal * lineNormal.dot(Point2<Scalar>(d, 0));
728 } break;
729 case 1: // sphere hit
730 hitPoint = spherePoint;
731 break;
732 case 2: // hyperbola hit
733 hitPoint = hyperPoint;
734 break;
735 case 3: // both hit
736 {
737 // check angle of sphere point
738 Scalar angle = std::atan2(spherePoint.y(), spherePoint.x());
739 // if angle is more than 45 degrees, take the hyperbola point
740 if (angle > M_PI / 4) {
741 hitPoint = hyperPoint;
742 }
743 else {
744 hitPoint = spherePoint;
745 }
746 } break;
747 default: assert(false && "Invalid hit case");
748 }
749 }
750
751 // convert hit point to 3D
752 // rescale in trackball space
753 // FIXME: Avoid cancellation issue with different solution
754 assert(hitPoint.x() == hitPoint.x());
755 double factor =
756 (h > 0.0 && hitPoint.y() > 0.0) ? hitPoint.y() / h : 0.0;
757 return Point3<Scalar>(
758 screenCoord.x() * factor, screenCoord.y() * factor, hitPoint.x());
759 }
760
762 // (general purpose and used for atomic operations)
763
764 void rotate(const Quaternion<Scalar>& q) { mTransform.prerotate(q); }
765
766 void rotate(Point3<Scalar> axis, Scalar angleRad)
767 {
768 mTransform.prerotate(Quaternion<Scalar>(angleRad, axis));
769 }
770
776 void translate(Point3<Scalar> t) { mTransform.pretranslate(t); }
777
778 void rotateDirLight(const Quaternion<Scalar>& rotation)
779 {
780 mDirectionalLightTransform = rotation * mDirectionalLightTransform;
781 }
782
783 void rotateDirLight(Point3<Scalar> axis, Scalar angle)
784 {
785 rotateDirLight(Quaternion<Scalar>(angle, axis));
786 }
787
792 void dragArc()
793 {
794 const Point3<Scalar> point = pointOnArcball(mCurrMousePosition);
795 Eigen::AngleAxis<Scalar> ax(
796 Eigen::Quaternion<Scalar>::FromTwoVectors(mInitialPoint, point));
797 // use angle proportional to the arc length
798 const Scalar phi = (point - mInitialPoint).norm() / mRadius;
799 ax.angle() = phi;
800
801 // rotate from freezed transformation
802 // mTransform = mInitialTransform;
803 // mTransform.prerotate(ax);
804
805 // modify rotation only
806 mTransform.prerotate(
807 ax * mInitialTransform.rotation() *
808 mTransform.rotation().inverse());
809 }
810
813 void roll(Scalar delta) { rotate(Point3<Scalar>(0, 0, 1), delta); }
814
815 void dragRoll()
816 {
817 static constexpr Scalar ROLL_DIST_TO_CENTER_THRESHOLD = 0.025;
818
819 const Point3<Scalar> prev = pointOnTrackballPlane(mPrevMousePosition);
820 const Point3<Scalar> curr = pointOnTrackballPlane(mCurrMousePosition);
821 if (prev.norm() < ROLL_DIST_TO_CENTER_THRESHOLD ||
823 return;
824
825 Scalar angle =
826 std::atan2(curr.y(), curr.x()) - std::atan2(prev.y(), prev.x());
827
828 roll(angle);
829 }
830
838 {
839 const Point2<Scalar> pan = pixelDelta * trackballToPixelRatio();
840 translate(Point3<Scalar>(pan.x(), pan.y(), 0.0));
841 }
842
843 // drag
844 void dragPan()
845 {
846 Point2<Scalar> pixelDelta = mCurrMousePosition - mPrevMousePosition;
848 }
849
855 void performZmove(const Scalar& pixelDelta)
856 {
857 const Scalar translation = pixelDelta * trackballToPixelRatio();
859 }
860
861 // drag
862 void dragZmove()
863 {
864 auto pixelDelta = mCurrMousePosition.y() - mPrevMousePosition.y();
866 }
867
870 // scrolling and scaling are setup with "magic" numbers
872 {
873 pixelDelta /= 60;
874 const auto factor = std::pow(1.2f, -pixelDelta);
875 changeScale(factor);
876 }
877
878 void dragScale()
879 {
880 Scalar pixelDelta = mCurrMousePosition.y() - mPrevMousePosition.y();
882 }
883
886 // drag
888 {
889 const Point3<Scalar> point = pointOnArcball(mCurrMousePosition);
890 Eigen::AngleAxis<Scalar> ax(
891 Eigen::Quaternion<Scalar>::FromTwoVectors(mInitialPoint, point));
892 // use angle proportional to the arc length
893 const Scalar phi = (point - mInitialPoint).norm() / mRadius;
894
895 mDirectionalLightTransform = mInitialDirRotation;
896 rotateDirLight(Point3<Scalar>(ax.axis()), phi);
897 }
898
902 {
903 static constexpr double MIN_FOV_DEG = 5.0;
904 static constexpr double MAX_FOV_DEG = 90.0;
905
906 pixelDelta /= 60.0;
907 double fov = fovDeg();
908
909 // ortho -> perspective
910 if (mCamera.projectionMode() == Camera<Scalar>::ProjectionMode::ORTHO) {
911 if (pixelDelta > 0) {
913 mCamera.projectionMode() =
915 }
916 }
917
918 // update fov
919 fov = std::clamp(fov + 1.2 * pixelDelta, MIN_FOV_DEG, MAX_FOV_DEG);
920
921 // perspective -> ortho
922 if (mCamera.projectionMode() ==
924 fov == MIN_FOV_DEG) {
925 mCamera.projectionMode() = Camera<Scalar>::ProjectionMode::ORTHO;
926 }
927
928 // commit fov
929 setFovDeg(fov);
930 }
931};
932
933} // namespace vcl
934
935#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
A Pinhole camera model.
Definition camera.h:35
Quaternion class.
Definition quaternion.h:51
The TrackBall class implements a trackball (a camera combined with model transformation).
Definition trackball.h:53
void setFovDeg(Scalar fov)
Set the vertical field of view adapting the eye distance.
Definition trackball.h:333
void roll(Scalar delta)
Definition trackball.h:813
void dragArc()
Definition trackball.h:792
void translate(Point3< Scalar > t)
Translate in the camera space.
Definition trackball.h:776
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:432
void endDragMotion(MotionType motion)
Ends a drag motion.
Definition trackball.h:524
void dragDirLightArc()
Definition trackball.h:887
Scalar fovDeg() const
Get the vertical field of view.
Definition trackball.h:327
void performPan(const Point2< Scalar > &pixelDelta)
perform a pan operation
Definition trackball.h:837
void reset(const Point3< Scalar > &center, Scalar scale)
Reset the manipulator to a given center and scale.
Definition trackball.h:140
void setDragMotionValue(MotionType motion, bool value)
Definition trackball.h:547
void setCamera(const Camera< Scalar > &cam)
Set the camera of the trackball.
Definition trackball.h:188
void performZmove(const Scalar &pixelDelta)
translate in the camera z direction
Definition trackball.h:855
void performScale(Scalar pixelDelta)
Definition trackball.h:871
Camera< Scalar > camera() const
return the camera containing the current view point of the trackball.
Definition trackball.h:153
void performFov(Scalar pixelDelta)
Definition trackball.h:901
void adaptCurrentViewToCenter(const Point3< Scalar > &center)
Adapt the current view to a given center. The function adapts the trackball transformation in order t...
Definition trackball.h:247
void update()
Updates the state of the trackball during a drag motion.
Definition trackball.h:535
void rotate(const Quaternion< Scalar > &q)
Definition trackball.h:764
void beginDragMotion(MotionType motion)
Starts a drag motion.
Definition trackball.h:499
auto distance(const PointType &point0, const PointType &point1)
Compute the distance between two Points of any dimension.
Definition distance.h:45
Definition trackball.h:68