74 using AtomicMotionArg =
75 std::variant<TransformArgs, Scalar, Point3<Scalar>, std::monostate>;
80 static constexpr Scalar ARC_BALL_RADIUS_RATIO = 1.0 / 1.61803398875;
82 static constexpr Scalar FOCUS_SCALE_FACTOR = 1.15;
84 static constexpr Scalar DEFAULT_FOV_DEG = 54.0;
101 Scalar mRadius = ARC_BALL_RADIUS_RATIO;
104 bool mDragging =
false;
105 MotionType mCurrDragMotion = MOTION_NUMBER;
118 TrackBall() { mCamera.setFieldOfViewAdaptingEyeDistance(DEFAULT_FOV_DEG); }
122 auto screenSize = mScreenSize;
123 auto currMousePosition = mCurrMousePosition;
124 auto prevMousePosition = mPrevMousePosition;
126 setScreenSize(screenSize);
127 mCurrMousePosition = currMousePosition;
128 mPrevMousePosition = prevMousePosition;
139 mTransform.scale(scale);
143 void resetDirectionalLight()
148 Point3<Scalar> center()
const
151 return mTransform.inverse().translation();
154 void setCenter(
const Point3<Scalar>& center)
158 mTransform.pretranslate(-(mTransform * center));
164 return mTransform.linear().colwise().norm().mean();
167 void setScale(Scalar scale)
171 mTransform.prescale(scale);
175 void changeScale(Scalar factor) { mTransform.prescale(factor); }
177 void setRotation(
const Quaternion<Scalar>& rotation)
179 Affine3<Scalar> tx = Affine3<Scalar>::Identity();
180 tx.rotate(rotation).scale(scale());
181 mTransform.linear() = tx.linear();
184 void setRotation(
const Point3<Scalar>& axis, Scalar angle)
186 setRotation(Quaternion<Scalar>(angle, axis));
192 Scalar
fovDeg()
const {
return mCamera.fieldOfView(); }
200 mCamera.setFieldOfViewAdaptingEyeDistance(
fov);
203 Camera<Scalar>::ProjectionMode projectionMode()
const
205 return mCamera.projectionMode();
208 void setProjectionMode(Camera<Scalar>::ProjectionMode mode)
210 mCamera.projectionMode() = mode;
211 mCamera.setFieldOfViewAdaptingEyeDistance(mCamera.fieldOfView());
214 void setScreenSize(Scalar width, Scalar height)
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;
222 mRadius *= mCamera.aspectRatio();
226 void setScreenSize(
const Point2<Scalar>& sz)
228 setScreenSize(sz.x(), sz.y());
231 DirectionalLight<Scalar> light()
const
235 return DirectionalLight<Scalar>(
236 mDirectionalLightTransform * Point3<Scalar>(0, 0, 1));
239 void setLightDirection(
const Point3<Scalar>& direction)
241 mDirectionalLightTransform = Quaternion<Scalar>::FromTwoVectors(
242 Point3<Scalar>(0, 0, 1), direction);
247 Matrix44<Scalar> viewMatrix()
const
249 return mCamera.viewMatrix() * mTransform.matrix();
252 Matrix44<Scalar> projectionMatrix()
const
254 return mCamera.projectionMatrix();
257 Matrix44<Scalar> gizmoMatrix()
const
259 Affine3<Scalar> rot_radius = Affine3<Scalar>::Identity();
260 rot_radius.rotate(mTransform.rotation()).scale(mRadius);
261 return mCamera.viewMatrix() * rot_radius.matrix();
264 Matrix44<Scalar> lightGizmoMatrix()
const
266 Affine3<Scalar> rot_radius = Affine3<Scalar>::Identity();
267 rot_radius.rotate(mDirectionalLightTransform).scale(mRadius);
268 return mCamera.viewMatrix() * rot_radius.matrix();
271 bool isDragging()
const {
return mDragging; }
273 MotionType currentMotion()
const {
return mCurrDragMotion; }
301 AtomicMotionArg step = std::monostate())
303 if (std::holds_alternative<Scalar>(step)) {
304 Scalar
inc = std::get<Scalar>(step);
314 else if (std::holds_alternative<TransformArgs>(step)) {
318 case DIR_LIGHT_ARC: rotateDirLight(
args.axis,
args.scalar);
break;
326 case FOCUS: setCenter(
val); changeScale(FOCUS_SCALE_FACTOR);
336 void applyPan(
const Point3<Scalar>& translation)
341 void applyArc(
const Point3<Scalar>& axis, Scalar angle)
348 void setMousePosition(Scalar x, Scalar y)
350 mPrevMousePosition = mCurrMousePosition;
351 mCurrMousePosition.x() = x;
352 mCurrMousePosition.y() = mScreenSize.y() - y;
355 void setMousePosition(
const Point2<Scalar>& point)
357 setMousePosition(point.x(), point.y());
368 assert(
motion != MOTION_NUMBER &&
"Invalid motion type");
371 if (mCurrDragMotion ==
motion)
375 if (mCurrDragMotion !=
motion)
379 mInitialPoint = pointOnArcball(mCurrMousePosition);
380 mInitialTransform = mTransform;
381 mInitialDirRotation = mDirectionalLightTransform;
405 mDragging != (mCurrDragMotion == MOTION_NUMBER) &&
406 "Invalid state: dragging and no motion");
407 if (mDragging && mCurrMousePosition != mPrevMousePosition)
408 drag(mCurrDragMotion);
416 mCurrDragMotion = value ?
motion : MOTION_NUMBER;
419 void drag(MotionType
motion)
423 case PAN: dragPan();
break;
424 case ZMOVE: dragZmove();
break;
425 case ROLL: dragRoll();
break;
426 case SCALE: dragScale();
break;
432 Scalar trackballToPixelRatio()
const
434 return mCamera.verticalHeight() / mScreenSize.y();
437 Point3<Scalar> pointOnTrackballPlane(Point2<Scalar> screenCoord)
const
442 (screenCoord - mScreenSize / 2.0) * trackballToPixelRatio();
443 return Point3<Scalar>(screenCoord.x(), screenCoord.y(), 0.0);
446 Point3<Scalar> pointOnArcball(Point2<Scalar> screenCoord)
const
450 (screenCoord - mScreenSize / 2.0) * trackballToPixelRatio();
457 const double h = screenCoord.norm();
458 Point2<Scalar> hitPoint;
460 if (mCamera.projectionMode() == Camera<Scalar>::ProjectionMode::ORTHO) {
462 if (h < (M_SQRT1_2 * mRadius)) {
467 Point2<Scalar>(std::sqrt(mRadius * mRadius - h * h), h);
473 hitPoint = Point2<Scalar>(mRadius * mRadius / (2 * h), h);
478 mCamera.projectionMode() ==
479 Camera<Scalar>::ProjectionMode::PERSPECTIVE &&
480 "invalid camera projection value is supported");
505 const double d = (mCamera.eye() - mCamera.
center()).norm();
507 const double m = -h / d;
509 bool sphereHit =
false;
510 Point2<Scalar> spherePoint;
528 Scalar a = 1 + m * m;
529 Scalar b = -2 * h * h / d;
530 Scalar c = h * h - mRadius * mRadius;
536 Scalar delta = b * b - 4 * a * c;
537 sphereHit = delta >= 0;
539 spherePoint.x() = (-b + std::sqrt(delta)) / (2 * a);
540 spherePoint.y() = m * spherePoint.x() + h;
544 bool hyperHit =
false;
545 Point2<Scalar> hyperPoint;
567 Scalar b = 2 * d * h;
568 Scalar c = -d * mRadius * mRadius;
575 Scalar delta = b * b - 4 * a * c;
576 hyperHit = delta >= 0;
578 hyperPoint.x() = (-b + std::sqrt(delta)) / (2 * a);
579 hyperPoint.y() = m * hyperPoint.x() + h;
584 const int hitCase = sphereHit + 2 * hyperHit;
590 Point2<Scalar> lineVector = Point2<Scalar>(d, -h).normalized();
591 Point2<Scalar> lineNormal =
592 Point2<Scalar>(-lineVector.y(), lineVector.x());
594 hitPoint = lineNormal * lineNormal.dot(Point2<Scalar>(d, 0));
597 hitPoint = spherePoint;
600 hitPoint = hyperPoint;
605 Scalar angle = std::atan2(spherePoint.y(), spherePoint.x());
607 if (angle > M_PI / 4) {
608 hitPoint = hyperPoint;
611 hitPoint = spherePoint;
614 default: assert(
false &&
"Invalid hit case");
621 assert(hitPoint.x() == hitPoint.x());
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());
647 mDirectionalLightTransform =
rotation * mDirectionalLightTransform;
650 void rotateDirLight(Point3<Scalar> axis, Scalar angle)
652 rotateDirLight(Quaternion<Scalar>(angle, axis));
662 Eigen::AngleAxis<Scalar>
ax(
663 Eigen::Quaternion<Scalar>::FromTwoVectors(mInitialPoint, point));
665 const Scalar
phi = (point - mInitialPoint).
norm() / mRadius;
673 mTransform.prerotate(
674 ax * mInitialTransform.rotation() *
675 mTransform.rotation().inverse());
731 auto pixelDelta = mCurrMousePosition.y() - mPrevMousePosition.y();
747 Scalar
pixelDelta = mCurrMousePosition.y() - mPrevMousePosition.y();
757 Eigen::AngleAxis<Scalar>
ax(
758 Eigen::Quaternion<Scalar>::FromTwoVectors(mInitialPoint, point));
760 const Scalar
phi = (point - mInitialPoint).
norm() / mRadius;
762 mDirectionalLightTransform = mInitialDirRotation;
780 mCamera.projectionMode() =
789 if (mCamera.projectionMode() ==