#include #include "arc_ball.h" /* Project a point in NDC onto the arcball sphere */ Quaternion ndcToArcBall(const Vector2 &p) { const Float dist = Math::dot(p, p); /* Point is on sphere */ if (dist <= 1.0f) return {{p.x(), p.y(), Math::sqrt(1.0f - dist)}, 0.0f}; /* Point is outside sphere */ else { const Vector2 proj = p.normalized(); return {{proj.x(), proj.y(), 0.0f}, 0.0f}; } } ArcBall::ArcBall(const Vector3 &eye, const Vector3 &viewCenter, const Vector3 &upDir, Deg fov, const Vector2i &windowSize) : m_fov{fov}, m_windowSize{windowSize} { setViewParameters(eye, viewCenter, upDir); } void ArcBall::setViewParameters(const Vector3 &eye, const Vector3 &viewCenter, const Vector3 &upDir) { const Vector3 dir = viewCenter - eye; Vector3 zAxis = dir.normalized(); Vector3 xAxis = (Math::cross(zAxis, upDir.normalized())).normalized(); Vector3 yAxis = (Math::cross(xAxis, zAxis)).normalized(); xAxis = (Math::cross(zAxis, yAxis)).normalized(); m_targetPosition = -viewCenter; m_targetZooming = -dir.length(); m_targetQRotation = Quaternion::fromMatrix(Matrix3x3{xAxis, yAxis, -zAxis}.transposed()) .normalized(); m_positionT0 = m_currentPosition = m_targetPosition; m_zoomingT0 = m_currentZooming = m_targetZooming; m_qRotationT0 = m_currentQRotation = m_targetQRotation; updateInternalTransformations(); } void ArcBall::reset() { m_targetPosition = m_positionT0; m_targetZooming = m_zoomingT0; m_targetQRotation = m_qRotationT0; } void ArcBall::setLagging(const Float lagging) { CORRADE_INTERNAL_ASSERT(lagging >= 0.0f && lagging < 1.0f); m_lagging = lagging; } void ArcBall::initTransformation(const Vector2i &mousePos) { m_prevMousePosNDC = screenCoordToNDC(mousePos); } void ArcBall::rotate(const Vector2i &mousePos) { const Vector2 mousePosNDC = screenCoordToNDC(mousePos); const Quaternion currentQRotation = ndcToArcBall(mousePosNDC); const Quaternion prevQRotation = ndcToArcBall(m_prevMousePosNDC); m_prevMousePosNDC = mousePosNDC; m_targetQRotation = (currentQRotation * prevQRotation * m_targetQRotation).normalized(); } void ArcBall::translate(const Vector2i &mousePos) { const Vector2 mousePosNDC = screenCoordToNDC(mousePos); const Vector2 translationNDC = mousePosNDC - m_prevMousePosNDC; m_prevMousePosNDC = mousePosNDC; translateDelta(translationNDC); } void ArcBall::translateDelta(const Vector2 &translationNDC) { /* Half size of the screen viewport at the view center and perpendicular with the viewDir */ const Float hh = Math::abs(m_targetZooming) * Math::tan(m_fov * 0.5f); const Float hw = hh * Vector2{m_windowSize}.aspectRatio(); m_targetPosition += m_inverseView.transformVector( {translationNDC.x() * hw, translationNDC.y() * hh, 0.0f}); } void ArcBall::zoom(const Float delta) { m_targetZooming += delta; } bool ArcBall::updateTransformation() { const Vector3 diffViewCenter = m_targetPosition - m_currentPosition; const Quaternion diffRotation = m_targetQRotation - m_currentQRotation; const Float diffZooming = m_targetZooming - m_currentZooming; const Float dViewCenter = Math::dot(diffViewCenter, diffViewCenter); const Float dRotation = Math::dot(diffRotation, diffRotation); const Float dZooming = diffZooming * diffZooming; /* Nothing change */ if (dViewCenter < 1.0e-10f && dRotation < 1.0e-10f && dZooming < 1.0e-10f) { return false; } /* Nearly done: just jump directly to the target */ if (dViewCenter < 1.0e-6f && dRotation < 1.0e-6f && dZooming < 1.0e-6f) { m_currentPosition = m_targetPosition; m_currentQRotation = m_targetQRotation; m_currentZooming = m_targetZooming; /* Interpolate between the current transformation and the target transformation */ } else { const Float t = 1 - m_lagging; m_currentPosition = Math::lerp(m_currentPosition, m_targetPosition, t); m_currentZooming = Math::lerp(m_currentZooming, m_targetZooming, t); m_currentQRotation = Math::slerpShortestPath(m_currentQRotation, m_targetQRotation, t); } updateInternalTransformations(); return true; } void ArcBall::updateInternalTransformations() { m_view = DualQuaternion::translation(Vector3::zAxis(m_currentZooming)) * DualQuaternion{m_currentQRotation} * DualQuaternion::translation(m_currentPosition); m_inverseView = m_view.inverted(); } Vector2 ArcBall::screenCoordToNDC(const Vector2i &mousePos) const { return {mousePos.x() * 2.0f / m_windowSize.x() - 1.0f, 1.0f - 2.0f * mousePos.y() / m_windowSize.y()}; }