453 lines
13 KiB
C++
453 lines
13 KiB
C++
#include "Camera.h"
|
|
#include "ViewportWidget.h"
|
|
#include <QApplication>
|
|
#include <QWheelEvent>
|
|
#include <QPropertyAnimation>
|
|
#include <QParallelAnimationGroup>
|
|
#include <QtMath>
|
|
|
|
Camera::Camera(QObject *parent) : QObject(parent)
|
|
{
|
|
m_lastPos = QPoint();
|
|
// Set initial view to an isometric angle on the XY plane
|
|
m_xRot = 30 * 16;
|
|
m_yRot = -45 * 16;
|
|
m_zoom = -20.0f;
|
|
m_panX = 0.0f;
|
|
m_panY = 0.0f;
|
|
}
|
|
|
|
void Camera::mousePressEvent(QMouseEvent* event)
|
|
{
|
|
m_lastPos = event->pos();
|
|
}
|
|
|
|
void Camera::mouseMoveEvent(QMouseEvent* event, int viewportHeight)
|
|
{
|
|
int dx = event->pos().x() - m_lastPos.x();
|
|
int dy = event->pos().y() - m_lastPos.y();
|
|
|
|
if ((event->buttons() & Qt::MiddleButton) || (event->buttons() == (Qt::LeftButton | Qt::RightButton))) {
|
|
if (QApplication::keyboardModifiers() & Qt::ShiftModifier) {
|
|
// Pan
|
|
if (viewportHeight == 0) viewportHeight = 1;
|
|
|
|
// This logic is based on a 45-degree field of view.
|
|
float fov = 45.0f;
|
|
float dist = -m_zoom;
|
|
|
|
float world_height = 2.0f * dist * tan(qDegreesToRadians(fov / 2.0f));
|
|
float pixels_to_world = world_height / viewportHeight;
|
|
|
|
setPanX(m_panX + dx * pixels_to_world);
|
|
setPanY(m_panY - dy * pixels_to_world);
|
|
} else {
|
|
// Rotate
|
|
setXRotation(m_xRot + 8 * dy);
|
|
setYRotation(m_yRot + 8 * dx);
|
|
}
|
|
}
|
|
m_lastPos = event->pos();
|
|
}
|
|
|
|
void Camera::wheelEvent(QWheelEvent* event, const QVector3D& worldPos)
|
|
{
|
|
QPoint numDegrees = event->angleDelta() / 8;
|
|
if (!numDegrees.isNull()) {
|
|
// Make zoom speed proportional to distance, with a minimum speed and a cap.
|
|
// The factors are chosen to match the original zoom speed at the default zoom level.
|
|
float dist = -m_zoom;
|
|
dist = qMin(dist, 200.0f); // Cap distance to avoid crazy fast zoom out.
|
|
float zoomFactor = dist * 0.009f + 0.02f;
|
|
float zoomAmount = numDegrees.y() * zoomFactor;
|
|
|
|
float oldZoom = m_zoom;
|
|
float newZoom = oldZoom + zoomAmount;
|
|
|
|
QMatrix4x4 rotation;
|
|
rotation.rotate(m_xRot / 16.0f, 1, 0, 0);
|
|
rotation.rotate(m_yRot / 16.0f, 0, 1, 0);
|
|
QVector3D p_camera = rotation.map(worldPos);
|
|
|
|
if (std::abs(p_camera.z() + oldZoom) < 1e-6) {
|
|
setZoom(newZoom);
|
|
return;
|
|
}
|
|
|
|
float ratio = (p_camera.z() + newZoom) / (p_camera.z() + oldZoom);
|
|
|
|
float newPanX = (p_camera.x() + m_panX) * ratio - p_camera.x();
|
|
float newPanY = (p_camera.y() + m_panY) * ratio - p_camera.y();
|
|
|
|
setZoom(newZoom);
|
|
setPanX(newPanX);
|
|
setPanY(newPanY);
|
|
}
|
|
}
|
|
|
|
QMatrix4x4 Camera::modelViewMatrix() const
|
|
{
|
|
QMatrix4x4 model;
|
|
model.translate(m_panX, m_panY, m_zoom);
|
|
|
|
if (m_isRotating) {
|
|
model.translate(m_rotationPivot);
|
|
}
|
|
model.rotate(m_xRot / 16.0f, 1, 0, 0);
|
|
model.rotate(m_yRot / 16.0f, 0, 1, 0);
|
|
if (m_isRotating) {
|
|
model.translate(-m_rotationPivot);
|
|
}
|
|
|
|
model.rotate(-90.0f, 1, 0, 0); // For Z-up system
|
|
return model;
|
|
}
|
|
|
|
float Camera::xRotation() const { return m_xRot; }
|
|
void Camera::setXRotation(float angle)
|
|
{
|
|
if (angle != m_xRot) {
|
|
m_xRot = angle;
|
|
emit cameraChanged();
|
|
}
|
|
}
|
|
|
|
float Camera::yRotation() const { return m_yRot; }
|
|
void Camera::setYRotation(float angle)
|
|
{
|
|
if (angle != m_yRot) {
|
|
m_yRot = angle;
|
|
emit cameraChanged();
|
|
}
|
|
}
|
|
|
|
float Camera::zoom() const { return m_zoom; }
|
|
void Camera::setZoom(float value)
|
|
{
|
|
const float max_dist = 5000.0f;
|
|
float remaining_dist_sq = max_dist * max_dist - m_panX * m_panX - m_panY * m_panY;
|
|
if (remaining_dist_sq < 0) {
|
|
remaining_dist_sq = 0;
|
|
}
|
|
float max_val = qSqrt(remaining_dist_sq);
|
|
value = qBound(-max_val, value, 0.0f); // Zoom is negative or zero
|
|
|
|
if (value != m_zoom) {
|
|
m_zoom = value;
|
|
emit cameraChanged();
|
|
}
|
|
}
|
|
|
|
float Camera::panX() const { return m_panX; }
|
|
void Camera::setPanX(float value)
|
|
{
|
|
const float max_dist = 5000.0f;
|
|
float remaining_dist_sq = max_dist * max_dist - m_panY * m_panY - m_zoom * m_zoom;
|
|
if (remaining_dist_sq < 0) {
|
|
remaining_dist_sq = 0;
|
|
}
|
|
float max_val = qSqrt(remaining_dist_sq);
|
|
value = qBound(-max_val, value, max_val);
|
|
|
|
if (value != m_panX) {
|
|
m_panX = value;
|
|
emit cameraChanged();
|
|
}
|
|
}
|
|
|
|
float Camera::panY() const { return m_panY; }
|
|
void Camera::setPanY(float value)
|
|
{
|
|
const float max_dist = 5000.0f;
|
|
float remaining_dist_sq = max_dist * max_dist - m_panX * m_panX - m_zoom * m_zoom;
|
|
if (remaining_dist_sq < 0) {
|
|
remaining_dist_sq = 0;
|
|
}
|
|
float max_val = qSqrt(remaining_dist_sq);
|
|
value = qBound(-max_val, value, max_val);
|
|
|
|
if (value != m_panY) {
|
|
m_panY = value;
|
|
emit cameraChanged();
|
|
}
|
|
}
|
|
|
|
void Camera::saveState()
|
|
{
|
|
m_savedXRot = m_xRot;
|
|
m_savedYRot = m_yRot;
|
|
m_savedPanX = m_panX;
|
|
m_savedPanY = m_panY;
|
|
m_savedZoom = m_zoom;
|
|
}
|
|
|
|
void Camera::restoreState()
|
|
{
|
|
setXRotation(m_savedXRot);
|
|
setYRotation(m_savedYRot);
|
|
setPanX(m_savedPanX);
|
|
setPanY(m_savedPanY);
|
|
setZoom(m_savedZoom);
|
|
}
|
|
|
|
void Camera::animateToPlaneView(int plane)
|
|
{
|
|
float targetXRot = xRotation();
|
|
float targetYRot = yRotation();
|
|
switch (static_cast<ViewportWidget::SketchPlane>(plane)) {
|
|
case ViewportWidget::SketchPlane::XY: // Top view
|
|
targetXRot = 90 * 16;
|
|
targetYRot = 0;
|
|
break;
|
|
case ViewportWidget::SketchPlane::XZ: // Front view
|
|
targetXRot = 0;
|
|
targetYRot = 0;
|
|
break;
|
|
case ViewportWidget::SketchPlane::YZ: // Right view
|
|
targetXRot = 0;
|
|
targetYRot = -90 * 16;
|
|
break;
|
|
case ViewportWidget::SketchPlane::NONE:
|
|
break;
|
|
}
|
|
|
|
auto* animGroup = new QParallelAnimationGroup(this);
|
|
|
|
const float full_circle = 360.0f * 16.0f;
|
|
|
|
float currentXRot = xRotation();
|
|
float diffX = targetXRot - currentXRot;
|
|
diffX = fmod(diffX, full_circle);
|
|
if (diffX > full_circle / 2.0f) {
|
|
diffX -= full_circle;
|
|
} else if (diffX < -full_circle / 2.0f) {
|
|
diffX += full_circle;
|
|
}
|
|
|
|
auto* xRotAnim = new QPropertyAnimation(this, "xRotation");
|
|
xRotAnim->setDuration(300);
|
|
xRotAnim->setStartValue(currentXRot);
|
|
xRotAnim->setEndValue(currentXRot + diffX);
|
|
xRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(xRotAnim);
|
|
|
|
float currentYRot = yRotation();
|
|
float diffY = targetYRot - currentYRot;
|
|
diffY = fmod(diffY, full_circle);
|
|
if (diffY > full_circle / 2.0f) {
|
|
diffY -= full_circle;
|
|
} else if (diffY < -full_circle / 2.0f) {
|
|
diffY += full_circle;
|
|
}
|
|
|
|
auto* yRotAnim = new QPropertyAnimation(this, "yRotation");
|
|
yRotAnim->setDuration(300);
|
|
yRotAnim->setStartValue(currentYRot);
|
|
yRotAnim->setEndValue(currentYRot + diffY);
|
|
yRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(yRotAnim);
|
|
|
|
auto* panXAnim = new QPropertyAnimation(this, "panX");
|
|
panXAnim->setDuration(300);
|
|
panXAnim->setStartValue(panX());
|
|
panXAnim->setEndValue(0.0f);
|
|
panXAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(panXAnim);
|
|
|
|
auto* panYAnim = new QPropertyAnimation(this, "panY");
|
|
panYAnim->setDuration(300);
|
|
panYAnim->setStartValue(panY());
|
|
panYAnim->setEndValue(0.0f);
|
|
panYAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(panYAnim);
|
|
|
|
auto* zoomAnim = new QPropertyAnimation(this, "zoom");
|
|
zoomAnim->setDuration(300);
|
|
zoomAnim->setStartValue(zoom());
|
|
zoomAnim->setEndValue(-20.0f);
|
|
zoomAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(zoomAnim);
|
|
|
|
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
|
|
}
|
|
|
|
void Camera::animateToHomeView()
|
|
{
|
|
auto* animGroup = new QParallelAnimationGroup(this);
|
|
|
|
const float full_circle = 360.0f * 16.0f;
|
|
|
|
float currentXRot = xRotation();
|
|
float targetXRot = 30 * 16;
|
|
float diffX = targetXRot - currentXRot;
|
|
diffX = fmod(diffX, full_circle);
|
|
if (diffX > full_circle / 2.0f) {
|
|
diffX -= full_circle;
|
|
} else if (diffX < -full_circle / 2.0f) {
|
|
diffX += full_circle;
|
|
}
|
|
|
|
auto* xRotAnim = new QPropertyAnimation(this, "xRotation");
|
|
xRotAnim->setDuration(300);
|
|
xRotAnim->setStartValue(currentXRot);
|
|
xRotAnim->setEndValue(currentXRot + diffX);
|
|
xRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(xRotAnim);
|
|
|
|
float currentYRot = yRotation();
|
|
float targetYRot = -45 * 16;
|
|
float diffY = targetYRot - currentYRot;
|
|
diffY = fmod(diffY, full_circle);
|
|
if (diffY > full_circle / 2.0f) {
|
|
diffY -= full_circle;
|
|
} else if (diffY < -full_circle / 2.0f) {
|
|
diffY += full_circle;
|
|
}
|
|
|
|
auto* yRotAnim = new QPropertyAnimation(this, "yRotation");
|
|
yRotAnim->setDuration(300);
|
|
yRotAnim->setStartValue(currentYRot);
|
|
yRotAnim->setEndValue(currentYRot + diffY);
|
|
yRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(yRotAnim);
|
|
|
|
auto* panXAnim = new QPropertyAnimation(this, "panX");
|
|
panXAnim->setDuration(300);
|
|
panXAnim->setStartValue(panX());
|
|
panXAnim->setEndValue(0.0f);
|
|
panXAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(panXAnim);
|
|
|
|
auto* panYAnim = new QPropertyAnimation(this, "panY");
|
|
panYAnim->setDuration(300);
|
|
panYAnim->setStartValue(panY());
|
|
panYAnim->setEndValue(0.0f);
|
|
panYAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(panYAnim);
|
|
|
|
auto* zoomAnim = new QPropertyAnimation(this, "zoom");
|
|
zoomAnim->setDuration(300);
|
|
zoomAnim->setStartValue(zoom());
|
|
zoomAnim->setEndValue(-20.0f);
|
|
zoomAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(zoomAnim);
|
|
|
|
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
|
|
}
|
|
|
|
void Camera::startRotation(const QVector3D& pivot)
|
|
{
|
|
m_rotationPivot = pivot;
|
|
if (m_rotationPivot.length() > 1000.0f) {
|
|
m_rotationPivot = m_rotationPivot.normalized() * 1000.0f;
|
|
}
|
|
m_stableZoom = m_zoom;
|
|
|
|
QMatrix4x4 rotation;
|
|
rotation.rotate(m_xRot / 16.0f, 1, 0, 0);
|
|
rotation.rotate(m_yRot / 16.0f, 0, 1, 0);
|
|
|
|
QVector3D p_rotated = rotation.map(m_rotationPivot);
|
|
QVector3D p_diff = p_rotated - m_rotationPivot;
|
|
|
|
setPanX(m_panX + p_diff.x());
|
|
setPanY(m_panY + p_diff.y());
|
|
setZoom(m_zoom + p_diff.z());
|
|
|
|
m_isRotating = true;
|
|
}
|
|
|
|
void Camera::stopRotation()
|
|
{
|
|
if (!m_isRotating) {
|
|
return;
|
|
}
|
|
|
|
QMatrix4x4 rotation;
|
|
rotation.rotate(m_xRot / 16.0f, 1, 0, 0);
|
|
rotation.rotate(m_yRot / 16.0f, 0, 1, 0);
|
|
|
|
QVector3D p_rotated = rotation.map(m_rotationPivot);
|
|
QVector3D p_diff = p_rotated - m_rotationPivot;
|
|
|
|
setPanX(m_panX - p_diff.x());
|
|
setPanY(m_panY - p_diff.y());
|
|
setZoom(m_zoom - p_diff.z());
|
|
|
|
m_isRotating = false;
|
|
}
|
|
|
|
void Camera::animateRestoreState()
|
|
{
|
|
auto* animGroup = new QParallelAnimationGroup(this);
|
|
|
|
const float full_circle = 360.0f * 16.0f;
|
|
|
|
float currentXRot = xRotation();
|
|
float targetXRot = savedXRot();
|
|
float diffX = targetXRot - currentXRot;
|
|
diffX = fmod(diffX, full_circle);
|
|
if (diffX > full_circle / 2.0f) {
|
|
diffX -= full_circle;
|
|
} else if (diffX < -full_circle / 2.0f) {
|
|
diffX += full_circle;
|
|
}
|
|
|
|
auto* xRotAnim = new QPropertyAnimation(this, "xRotation");
|
|
xRotAnim->setDuration(300);
|
|
xRotAnim->setStartValue(currentXRot);
|
|
xRotAnim->setEndValue(currentXRot + diffX);
|
|
xRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(xRotAnim);
|
|
|
|
float currentYRot = yRotation();
|
|
float targetYRot = savedYRot();
|
|
float diffY = targetYRot - currentYRot;
|
|
diffY = fmod(diffY, full_circle);
|
|
if (diffY > full_circle / 2.0f) {
|
|
diffY -= full_circle;
|
|
} else if (diffY < -full_circle / 2.0f) {
|
|
diffY += full_circle;
|
|
}
|
|
|
|
auto* yRotAnim = new QPropertyAnimation(this, "yRotation");
|
|
yRotAnim->setDuration(300);
|
|
yRotAnim->setStartValue(currentYRot);
|
|
yRotAnim->setEndValue(currentYRot + diffY);
|
|
yRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(yRotAnim);
|
|
|
|
auto* panXAnim = new QPropertyAnimation(this, "panX");
|
|
panXAnim->setDuration(300);
|
|
panXAnim->setStartValue(panX());
|
|
panXAnim->setEndValue(savedPanX());
|
|
panXAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(panXAnim);
|
|
|
|
auto* panYAnim = new QPropertyAnimation(this, "panY");
|
|
panYAnim->setDuration(300);
|
|
panYAnim->setStartValue(panY());
|
|
panYAnim->setEndValue(savedPanY());
|
|
panYAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(panYAnim);
|
|
|
|
auto* zoomAnim = new QPropertyAnimation(this, "zoom");
|
|
zoomAnim->setDuration(300);
|
|
zoomAnim->setStartValue(zoom());
|
|
zoomAnim->setEndValue(savedZoom());
|
|
zoomAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(zoomAnim);
|
|
|
|
connect(animGroup, &QParallelAnimationGroup::finished, this, &Camera::restoreStateAnimationFinished);
|
|
|
|
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
|
|
}
|
|
|
|
float Camera::uiCameraDistance() const
|
|
{
|
|
if (m_isRotating) {
|
|
return m_stableZoom;
|
|
}
|
|
return m_zoom;
|
|
}
|