#include "Camera.h" #include "ViewportWidget.h" #include #include #include #include #include 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(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; }