75 using AtomicMotionArg =
76 std::variant<TransformArgs, Scalar, Point3<Scalar>, std::monostate>;
81 static constexpr Scalar ARC_BALL_RADIUS_RATIO = 1.0 / 1.61803398875;
83 static constexpr Scalar FOCUS_SCALE_FACTOR = 1.15;
85 static constexpr Scalar DEFAULT_FOV_DEG = 54.0;
105 Scalar mRadius = ARC_BALL_RADIUS_RATIO;
108 bool mDragging =
false;
109 MotionType mCurrDragMotion = MOTION_NUMBER;
122 TrackBall() { mCamera.setFieldOfViewAdaptingEyeDistance(DEFAULT_FOV_DEG); }
126 auto screenSize = mScreenSize;
127 auto currMousePosition = mCurrMousePosition;
128 auto prevMousePosition = mPrevMousePosition;
130 setScreenSize(screenSize);
131 mCurrMousePosition = currMousePosition;
132 mPrevMousePosition = prevMousePosition;
143 mTransform.scale(scale);
164 cam.up() = y.normalized();
172 Scalar scale = mTransform.linear().col(0).norm();
173 cam.verticalHeight() /= scale;
192 mCamera.projectionMode() =
cam.projectionMode();
194 if (mCamera.projectionMode() ==
196 mCamera.setFieldOfViewAdaptingEyeDistance(
cam.fieldOfView());
202 mCamera.verticalHeight() /
cam.verticalHeight();
205 mCamera.eye() - mCamera.
center();
208 mCamera.nearPlane() =
cam.nearPlane();
209 mCamera.farPlane() =
cam.farPlane();
218 mTransform.linear().col(0) = x;
219 mTransform.linear().col(1) = y;
220 mTransform.linear().col(2) = z;
224 mTransform.pretranslate(
cam.eye());
227 mTransform = mTransform.inverse();
249 if (mCamera.projectionMode() ==
265 mCamera.projectionMode() ==
278 void resetDirectionalLight()
283 Point3<Scalar> center()
const
286 return mTransform.inverse().translation();
289 void setCenter(
const Point3<Scalar>& center)
293 mTransform.pretranslate(-(mTransform * center));
299 return mTransform.linear().colwise().norm().mean();
302 void setScale(Scalar scale)
306 mTransform.prescale(scale);
310 void changeScale(Scalar factor) { mTransform.prescale(factor); }
312 void setRotation(
const Quaternion<Scalar>& rotation)
314 Affine3<Scalar> tx = Affine3<Scalar>::Identity();
315 tx.rotate(rotation).scale(scale());
316 mTransform.linear() = tx.linear();
319 void setRotation(
const Point3<Scalar>& axis, Scalar angle)
321 setRotation(Quaternion<Scalar>(angle, axis));
327 Scalar
fovDeg()
const {
return mCamera.fieldOfView(); }
335 mCamera.setFieldOfViewAdaptingEyeDistance(
fov);
338 Camera<Scalar>::ProjectionMode projectionMode()
const
340 return mCamera.projectionMode();
343 void setProjectionMode(Camera<Scalar>::ProjectionMode mode)
345 mCamera.projectionMode() = mode;
346 mCamera.setFieldOfViewAdaptingEyeDistance(mCamera.fieldOfView());
349 void setScreenSize(Scalar width, Scalar height)
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;
357 mRadius *= mCamera.aspectRatio();
361 void setScreenSize(
const Point2<Scalar>& sz)
363 setScreenSize(sz.x(), sz.y());
366 DirectionalLight<Scalar> light()
const
370 return DirectionalLight<Scalar>(
371 mDirectionalLightTransform * Point3<Scalar>(0, 0, 1));
374 void setLightDirection(
const Point3<Scalar>& direction)
376 mDirectionalLightTransform = Quaternion<Scalar>::FromTwoVectors(
377 Point3<Scalar>(0, 0, 1), direction);
380 Matrix44<Scalar> viewMatrix()
const
382 return mCamera.viewMatrix() * mTransform.matrix();
385 Matrix44<Scalar> projectionMatrix()
const
387 return mCamera.projectionMatrix();
390 Matrix44<Scalar> gizmoMatrix()
const
392 Affine3<Scalar> rot_radius = Affine3<Scalar>::Identity();
393 rot_radius.rotate(mTransform.rotation()).scale(mRadius);
394 return mCamera.viewMatrix() * rot_radius.matrix();
397 Matrix44<Scalar> lightGizmoMatrix()
const
399 Affine3<Scalar> rot_radius = Affine3<Scalar>::Identity();
400 rot_radius.rotate(mDirectionalLightTransform).scale(mRadius);
401 return mCamera.viewMatrix() * rot_radius.matrix();
404 bool isDragging()
const {
return mDragging; }
406 MotionType currentMotion()
const {
return mCurrDragMotion; }
434 AtomicMotionArg step = std::monostate())
436 if (std::holds_alternative<Scalar>(step)) {
437 Scalar
inc = std::get<Scalar>(step);
447 else if (std::holds_alternative<TransformArgs>(step)) {
451 case DIR_LIGHT_ARC: rotateDirLight(
args.axis,
args.scalar);
break;
459 case FOCUS: setCenter(
val); changeScale(FOCUS_SCALE_FACTOR);
469 void applyPan(
const Point3<Scalar>& translation)
474 void applyArc(
const Point3<Scalar>& axis, Scalar angle)
481 void setMousePosition(Scalar x, Scalar y)
483 mPrevMousePosition = mCurrMousePosition;
484 mCurrMousePosition.x() = x;
485 mCurrMousePosition.y() = mScreenSize.y() - y;
488 void setMousePosition(
const Point2<Scalar>& point)
490 setMousePosition(point.x(), point.y());
501 assert(
motion != MOTION_NUMBER &&
"Invalid motion type");
504 if (mCurrDragMotion ==
motion)
508 if (mCurrDragMotion !=
motion)
512 mInitialPoint = pointOnArcball(mCurrMousePosition);
513 mInitialTransform = mTransform;
514 mInitialDirRotation = mDirectionalLightTransform;
538 mDragging != (mCurrDragMotion == MOTION_NUMBER) &&
539 "Invalid state: dragging and no motion");
540 if (mDragging && mCurrMousePosition != mPrevMousePosition)
541 drag(mCurrDragMotion);
549 mCurrDragMotion = value ?
motion : MOTION_NUMBER;
552 void drag(MotionType
motion)
556 case PAN: dragPan();
break;
557 case ZMOVE: dragZmove();
break;
558 case ROLL: dragRoll();
break;
559 case SCALE: dragScale();
break;
565 Scalar trackballToPixelRatio()
const
567 return mCamera.verticalHeight() / mScreenSize.y();
570 Point3<Scalar> pointOnTrackballPlane(Point2<Scalar> screenCoord)
const
575 (screenCoord - mScreenSize / 2.0) * trackballToPixelRatio();
576 return Point3<Scalar>(screenCoord.x(), screenCoord.y(), 0.0);
579 Point3<Scalar> pointOnArcball(Point2<Scalar> screenCoord)
const
583 (screenCoord - mScreenSize / 2.0) * trackballToPixelRatio();
590 const double h = screenCoord.norm();
591 Point2<Scalar> hitPoint;
593 if (mCamera.projectionMode() == Camera<Scalar>::ProjectionMode::ORTHO) {
595 if (h < (M_SQRT1_2 * mRadius)) {
600 Point2<Scalar>(std::sqrt(mRadius * mRadius - h * h), h);
606 hitPoint = Point2<Scalar>(mRadius * mRadius / (2 * h), h);
611 mCamera.projectionMode() ==
612 Camera<Scalar>::ProjectionMode::PERSPECTIVE &&
613 "invalid camera projection value is supported");
638 const double d = (mCamera.eye() - mCamera.
center()).norm();
640 const double m = -h / d;
642 bool sphereHit =
false;
643 Point2<Scalar> spherePoint;
661 Scalar a = 1 + m * m;
662 Scalar b = -2 * h * h / d;
663 Scalar c = h * h - mRadius * mRadius;
669 Scalar delta = b * b - 4 * a * c;
670 sphereHit = delta >= 0;
672 spherePoint.x() = (-b + std::sqrt(delta)) / (2 * a);
673 spherePoint.y() = m * spherePoint.x() + h;
677 bool hyperHit =
false;
678 Point2<Scalar> hyperPoint;
700 Scalar b = 2 * d * h;
701 Scalar c = -d * mRadius * mRadius;
708 Scalar delta = b * b - 4 * a * c;
709 hyperHit = delta >= 0;
711 hyperPoint.x() = (-b + std::sqrt(delta)) / (2 * a);
712 hyperPoint.y() = m * hyperPoint.x() + h;
717 const int hitCase = sphereHit + 2 * hyperHit;
723 Point2<Scalar> lineVector = Point2<Scalar>(d, -h).normalized();
724 Point2<Scalar> lineNormal =
725 Point2<Scalar>(-lineVector.y(), lineVector.x());
727 hitPoint = lineNormal * lineNormal.dot(Point2<Scalar>(d, 0));
730 hitPoint = spherePoint;
733 hitPoint = hyperPoint;
738 Scalar angle = std::atan2(spherePoint.y(), spherePoint.x());
740 if (angle > M_PI / 4) {
741 hitPoint = hyperPoint;
744 hitPoint = spherePoint;
747 default: assert(
false &&
"Invalid hit case");
754 assert(hitPoint.x() == hitPoint.x());
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());
780 mDirectionalLightTransform =
rotation * mDirectionalLightTransform;
783 void rotateDirLight(Point3<Scalar> axis, Scalar angle)
785 rotateDirLight(Quaternion<Scalar>(angle, axis));
795 Eigen::AngleAxis<Scalar>
ax(
796 Eigen::Quaternion<Scalar>::FromTwoVectors(mInitialPoint, point));
798 const Scalar
phi = (point - mInitialPoint).
norm() / mRadius;
806 mTransform.prerotate(
807 ax * mInitialTransform.rotation() *
808 mTransform.rotation().inverse());
864 auto pixelDelta = mCurrMousePosition.y() - mPrevMousePosition.y();
880 Scalar
pixelDelta = mCurrMousePosition.y() - mPrevMousePosition.y();
890 Eigen::AngleAxis<Scalar>
ax(
891 Eigen::Quaternion<Scalar>::FromTwoVectors(mInitialPoint, point));
893 const Scalar
phi = (point - mInitialPoint).
norm() / mRadius;
895 mDirectionalLightTransform = mInitialDirRotation;
913 mCamera.projectionMode() =
922 if (mCamera.projectionMode() ==