Files
magnum-boostrap-qtquick/src/arc_ball.cpp

135 lines
4.6 KiB
C++
Raw Normal View History

2020-06-18 18:25:03 -06:00
#include <Magnum/Math/Matrix3.h>
#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()};
}