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);
140 mTransform.translate(-center);
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::Enum projectionMode()
const
205 return mCamera.projectionMode();
208 void setProjectionMode(Camera<Scalar>::ProjectionMode::Enum 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));
241 Matrix44<Scalar> viewMatrix()
const
243 return mCamera.viewMatrix() * mTransform.matrix();
246 Matrix44<Scalar> projectionMatrix()
const {
return mCamera.projMatrix(); }
248 Matrix44<Scalar> gizmoMatrix()
const
250 Affine3<Scalar> rot_radius = Affine3<Scalar>::Identity();
251 rot_radius.rotate(mTransform.rotation()).scale(mRadius);
252 return mCamera.viewMatrix() * rot_radius.matrix();
255 Matrix44<Scalar> lightGizmoMatrix()
const
257 Affine3<Scalar> rot_radius = Affine3<Scalar>::Identity();
258 rot_radius.rotate(mDirectionalLightTransform).scale(mRadius);
259 return mCamera.viewMatrix() * rot_radius.matrix();
262 bool isDragging()
const {
return mDragging; }
264 MotionType currentMotion()
const {
return mCurrDragMotion; }
292 AtomicMotionArg
step = std::monostate())
294 if (std::holds_alternative<Scalar>(
step)) {
295 Scalar
inc = std::get<Scalar>(
step);
305 else if (std::holds_alternative<TransformArgs>(
step)) {
309 case DIR_LIGHT_ARC: rotateDirLight(
args.axis,
args.scalar);
break;
317 case FOCUS: setCenter(
val); changeScale(FOCUS_SCALE_FACTOR);
327 void applyPan(
const Point3<Scalar>& translation)
332 void applyArc(
const Point3<Scalar>& axis, Scalar angle)
339 void setMousePosition(Scalar x, Scalar y)
341 mPrevMousePosition = mCurrMousePosition;
342 mCurrMousePosition.x() = x;
343 mCurrMousePosition.y() = mScreenSize.y() - y;
346 void setMousePosition(
const Point2<Scalar>& point)
348 setMousePosition(point.x(), point.y());
359 assert(
motion != MOTION_NUMBER &&
"Invalid motion type");
362 if (mCurrDragMotion ==
motion)
366 if (mCurrDragMotion !=
motion)
370 mInitialPoint = pointOnArcball(mCurrMousePosition);
371 mInitialTransform = mTransform;
372 mInitialDirRotation = mDirectionalLightTransform;
396 mDragging != (mCurrDragMotion == MOTION_NUMBER) &&
397 "Invalid state: dragging and no motion");
398 if (mDragging && mCurrMousePosition != mPrevMousePosition)
399 drag(mCurrDragMotion);
407 mCurrDragMotion = value ?
motion : MOTION_NUMBER;
410 void drag(MotionType
motion)
414 case PAN: dragPan();
break;
415 case ZMOVE: dragZmove();
break;
416 case ROLL: dragRoll();
break;
417 case SCALE: dragScale();
break;
423 Scalar trackballToPixelRatio()
const
425 return mCamera.verticalHeight() / mScreenSize.y();
428 Point3<Scalar> pointOnTrackballPlane(Point2<Scalar> screenCoord)
const
433 (screenCoord - mScreenSize / 2.0) * trackballToPixelRatio();
434 return Point3<Scalar>(screenCoord.x(), screenCoord.y(), 0.0);
437 Point3<Scalar> pointOnArcball(Point2<Scalar> screenCoord)
const
441 (screenCoord - mScreenSize / 2.0) * trackballToPixelRatio();
448 const double h = screenCoord.norm();
449 Point2<Scalar> hitPoint;
451 if (mCamera.projectionMode() == Camera<Scalar>::ProjectionMode::ORTHO) {
453 if (h < (M_SQRT1_2 * mRadius)) {
458 Point2<Scalar>(std::sqrt(mRadius * mRadius - h * h), h);
464 hitPoint = Point2<Scalar>(mRadius * mRadius / (2 * h), h);
469 mCamera.projectionMode() ==
470 Camera<Scalar>::ProjectionMode::PERSPECTIVE &&
471 "invalid camera projection value is supported");
496 const double d = (mCamera.eye() - mCamera.center()).norm();
498 const double m = -h / d;
500 bool sphereHit =
false;
501 Point2<Scalar> spherePoint;
519 Scalar a = 1 + m * m;
520 Scalar b = -2 * h * h / d;
521 Scalar c = h * h - mRadius * mRadius;
527 Scalar delta = b * b - 4 * a * c;
528 sphereHit = delta >= 0;
530 spherePoint.x() = (-b + std::sqrt(delta)) / (2 * a);
531 spherePoint.y() = m * spherePoint.x() + h;
535 bool hyperHit =
false;
536 Point2<Scalar> hyperPoint;
558 Scalar b = 2 * d * h;
559 Scalar c = -d * mRadius * mRadius;
566 Scalar delta = b * b - 4 * a * c;
567 hyperHit = delta >= 0;
569 hyperPoint.x() = (-b + std::sqrt(delta)) / (2 * a);
570 hyperPoint.y() = m * hyperPoint.x() + h;
575 const int hitCase = sphereHit + 2 * hyperHit;
581 Point2<Scalar> lineVector = Point2<Scalar>(d, -h).normalized();
582 Point2<Scalar> lineNormal =
583 Point2<Scalar>(-lineVector.y(), lineVector.x());
585 hitPoint = lineNormal * lineNormal.dot(Point2<Scalar>(d, 0));
588 hitPoint = spherePoint;
591 hitPoint = hyperPoint;
596 Scalar angle = std::atan2(spherePoint.y(), spherePoint.x());
598 if (angle > M_PI / 4) {
599 hitPoint = hyperPoint;
602 hitPoint = spherePoint;
605 default: assert(
false &&
"Invalid hit case");
612 assert(hitPoint.x() == hitPoint.x());
614 (h > 0.0 && hitPoint.y() > 0.0) ? hitPoint.y() / h : 0.0;
615 return Point3<Scalar>(
616 screenCoord.x() * factor, screenCoord.y() * factor, hitPoint.x());
638 mDirectionalLightTransform =
rotation * mDirectionalLightTransform;
641 void rotateDirLight(Point3<Scalar> axis, Scalar angle)
643 rotateDirLight(Quaternion<Scalar>(angle, axis));
653 Eigen::AngleAxis<Scalar>
ax(
654 Eigen::Quaternion<Scalar>::FromTwoVectors(mInitialPoint, point));
656 const Scalar
phi = (point - mInitialPoint).
norm() / mRadius;
664 mTransform.prerotate(
665 ax * mInitialTransform.rotation() *
666 mTransform.rotation().inverse());
722 auto pixelDelta = mCurrMousePosition.y() - mPrevMousePosition.y();
738 Scalar
pixelDelta = mCurrMousePosition.y() - mPrevMousePosition.y();
748 Eigen::AngleAxis<Scalar>
ax(
749 Eigen::Quaternion<Scalar>::FromTwoVectors(mInitialPoint, point));
751 const Scalar
phi = (point - mInitialPoint).
norm() / mRadius;
753 mDirectionalLightTransform = mInitialDirRotation;
771 mCamera.projectionMode() =
780 if (mCamera.projectionMode() ==