diff options
author | Miikka Heikkinen <miikka.heikkinen@qt.io> | 2019-11-04 16:03:07 +0200 |
---|---|---|
committer | Miikka Heikkinen <miikka.heikkinen@qt.io> | 2019-11-18 08:39:16 +0000 |
commit | 20257e1e4fbaa6530007d3d5ec8fb35f11df8284 (patch) | |
tree | c6b20d61c55424971a7ca0f0fd85cdc011431d0c /share | |
parent | a87293d1c4d1c5e12dc8311cfa49283ab6d977ab (diff) | |
download | qt-creator-20257e1e4fbaa6530007d3d5ec8fb35f11df8284.tar.gz |
QmlDesigner: Implement RotateGizmo for 3D edit view
Added a gizmo for rotating selected object either freely or locked
around X, Y, Z, or camera axis.
Change-Id: Ib43c7dd3fc0f49f384d5920fce21ea932c4fc90d
Task-number: QDS-1196
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
Diffstat (limited to 'share')
-rw-r--r-- | share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml | 35 | ||||
-rw-r--r-- | share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml | 221 | ||||
-rw-r--r-- | share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml | 133 | ||||
-rw-r--r-- | share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ring.mesh | bin | 0 -> 45528 bytes | |||
-rw-r--r-- | share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ringselect.mesh | bin | 0 -> 50008 bytes | |||
-rw-r--r-- | share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.cpp | 216 | ||||
-rw-r--r-- | share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.h | 45 | ||||
-rw-r--r-- | share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc | 4 |
8 files changed, 629 insertions, 25 deletions
diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml index bed8dd3f9a..2c90875522 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml @@ -100,12 +100,15 @@ Window { PerspectiveCamera { id: overlayPerspectiveCamera clipFar: editPerspectiveCamera.clipFar + clipNear: editPerspectiveCamera.clipNear position: editPerspectiveCamera.position rotation: editPerspectiveCamera.rotation } OrthographicCamera { id: overlayOrthoCamera + clipFar: editOrthoCamera.clipFar + clipNear: editOrthoCamera.clipNear position: editOrthoCamera.position rotation: editOrthoCamera.rotation } @@ -140,6 +143,21 @@ Window { onScaleChange: viewWindow.changeObjectProperty(selectedNode, "scale") } + RotateGizmo { + id: rotateGizmo + scale: autoScale.getScale(Qt.vector3d(7, 7, 7)) + highlightOnHover: true + targetNode: viewWindow.selectedNode + position: viewWindow.selectedNode ? viewWindow.selectedNode.scenePosition + : Qt.vector3d(0, 0, 0) + globalOrientation: globalControl.checked + visible: selectedNode && btnRotate.selected + view3D: overlayView + + onRotateCommit: viewWindow.commitObjectProperty(selectedNode, "rotation") + onRotateChange: viewWindow.changeObjectProperty(selectedNode, "rotation") + } + AutoScaleHelper { id: autoScale view3D: overlayView @@ -193,12 +211,15 @@ Window { y: 200 z: -300 clipFar: 100000 + clipNear: 1 } OrthographicCamera { id: editOrthoCamera y: 200 z: -300 + clipFar: 100000 + clipNear: 1 } } } @@ -346,7 +367,19 @@ Window { id: usePerspectiveCheckbox checked: true text: qsTr("Use Perspective Projection") - onCheckedChanged: cameraControl.forceActiveFocus() + onCheckedChanged: { + // Since WasdController always acts on active camera, we need to update pos/rot + // to the other camera when we change + if (checked) { + editPerspectiveCamera.position = editOrthoCamera.position; + editPerspectiveCamera.rotation = editOrthoCamera.rotation; + } else { + editOrthoCamera.position = editPerspectiveCamera.position; + editOrthoCamera.rotation = editPerspectiveCamera.rotation; + } + designStudioNativeCameraControlHelper.requestOverlayUpdate(); + cameraControl.forceActiveFocus(); + } } CheckBox { diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml new file mode 100644 index 0000000000..b2f42b39e2 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick3D 1.0 +import MouseArea3D 1.0 + +Node { + id: rotateGizmo + + property View3D view3D + property bool highlightOnHover: true + property Node targetNode: null + property bool globalOrientation: true + readonly property bool dragging: cameraRing.dragging + || rotRingX.dragging || rotRingY.dragging || rotRingZ.dragging + property real currentAngle + property point currentMousePos + + signal rotateCommit() + signal rotateChange() + + Rectangle { + id: angleLabel + color: "white" + x: rotateGizmo.currentMousePos.x - (10 + width) + y: rotateGizmo.currentMousePos.y - (10 + height) + width: gizmoLabelText.width + 4 + height: gizmoLabelText.height + 4 + border.width: 1 + visible: rotateGizmo.dragging + parent: rotateGizmo.view3D + + Text { + id: gizmoLabelText + text: { + var l = Qt.locale(); + if (rotateGizmo.targetNode) { + var degrees = currentAngle * (180 / Math.PI); + return qsTr(Number(degrees).toLocaleString(l, 'f', 1)); + } else { + return ""; + } + } + anchors.centerIn: parent + } + } + + Node { + rotation: globalOrientation || !targetNode ? Qt.vector3d(0, 0, 0) : targetNode.sceneRotation + + RotateRing { + id: rotRingX + objectName: "Rotate Ring X" + rotation: Qt.vector3d(0, 90, 0) + targetNode: rotateGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(1, 0, 0, 1)) + : Qt.rgba(1, 0, 0, 1) + priority: 40 + view3D: rotateGizmo.view3D + active: rotateGizmo.visible + + onRotateCommit: rotateGizmo.rotateCommit() + onRotateChange: rotateGizmo.rotateChange() + onCurrentAngleChanged: rotateGizmo.currentAngle = currentAngle + onCurrentMousePosChanged: rotateGizmo.currentMousePos = currentMousePos + } + + RotateRing { + id: rotRingY + objectName: "Rotate Ring Y" + rotation: Qt.vector3d(90, 0, 0) + targetNode: rotateGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) + : Qt.rgba(0, 0.6, 0, 1) + // Just a smidge smaller than higher priority rings so that it doesn't obscure them + scale: Qt.vector3d(0.998, 0.998, 0.998) + priority: 30 + view3D: rotateGizmo.view3D + active: rotateGizmo.visible + + onRotateCommit: rotateGizmo.rotateCommit() + onRotateChange: rotateGizmo.rotateChange() + onCurrentAngleChanged: rotateGizmo.currentAngle = currentAngle + onCurrentMousePosChanged: rotateGizmo.currentMousePos = currentMousePos + } + + RotateRing { + id: rotRingZ + objectName: "Rotate Ring Z" + rotation: Qt.vector3d(0, 0, 0) + targetNode: rotateGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1)) + : Qt.rgba(0, 0, 1, 1) + // Just a smidge smaller than higher priority rings so that it doesn't obscure them + scale: Qt.vector3d(0.996, 0.996, 0.996) + priority: 20 + view3D: rotateGizmo.view3D + active: rotateGizmo.visible + + onRotateCommit: rotateGizmo.rotateCommit() + onRotateChange: rotateGizmo.rotateChange() + onCurrentAngleChanged: rotateGizmo.currentAngle = currentAngle + onCurrentMousePosChanged: rotateGizmo.currentMousePos = currentMousePos + } + } + + RotateRing { + id: cameraRing + objectName: "cameraRing" + rotation: rotateGizmo.view3D.camera.rotation + targetNode: rotateGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0.5, 0.5, 0.5, 1)) + : Qt.rgba(0.5, 0.5, 0.5, 1) + // Just a smidge smaller than higher priority rings so that it doesn't obscure them + scale: Qt.vector3d(0.994, 0.994, 0.994) + priority: 10 + view3D: rotateGizmo.view3D + active: rotateGizmo.visible + + onRotateCommit: rotateGizmo.rotateCommit() + onRotateChange: rotateGizmo.rotateChange() + onCurrentAngleChanged: rotateGizmo.currentAngle = currentAngle + onCurrentMousePosChanged: rotateGizmo.currentMousePos = currentMousePos + } + + Model { + id: freeRotator + + source: "#Sphere" + materials: DefaultMaterial { + id: material + emissiveColor: "black" + opacity: mouseAreaFree.hovering ? 0.15 : 0 + lighting: DefaultMaterial.NoLighting + } + scale: Qt.vector3d(0.15, 0.15, 0.15) + + property vector3d _pointerPosPressed + property vector3d _targetPosOnScreen + property vector3d _startRotation + + function handlePressed(screenPos) + { + if (!rotateGizmo.targetNode) + return; + + _targetPosOnScreen = view3D.mapFrom3DScene(rotateGizmo.targetNode.scenePosition); + _targetPosOnScreen.z = 0; + _pointerPosPressed = Qt.vector3d(screenPos.x, screenPos.y, 0); + + // Recreate vector so we don't follow the changes in targetNode.rotation + _startRotation = Qt.vector3d(rotateGizmo.targetNode.rotation.x, + rotateGizmo.targetNode.rotation.y, + rotateGizmo.targetNode.rotation.z); + } + + function handleDragged(screenPos) + { + if (!rotateGizmo.targetNode) + return; + + mouseAreaFree.applyFreeRotation( + rotateGizmo.targetNode, _startRotation, _pointerPosPressed, + Qt.vector3d(screenPos.x, screenPos.y, 0), _targetPosOnScreen); + + rotateGizmo.rotateChange(); + } + + function handleReleased(screenPos) + { + if (!rotateGizmo.targetNode) + return; + + mouseAreaFree.applyFreeRotation( + rotateGizmo.targetNode, _startRotation, _pointerPosPressed, + Qt.vector3d(screenPos.x, screenPos.y, 0), _targetPosOnScreen); + + rotateGizmo.rotateCommit(); + } + + MouseArea3D { + id: mouseAreaFree + view3D: rotateGizmo.view3D + rotation: rotateGizmo.view3D.camera.rotation + objectName: "Free rotator plane" + x: -50 + y: -50 + width: 100 + height: 100 + circlePickArea: Qt.point(25, 50) + grabsMouse: rotateGizmo.targetNode + active: rotateGizmo.visible + onPressed: freeRotator.handlePressed(screenPos) + onDragged: freeRotator.handleDragged(screenPos) + onReleased: freeRotator.handleReleased(screenPos) + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml new file mode 100644 index 0000000000..634eb017c9 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick3D 1.0 +import MouseArea3D 1.0 + +Model { + id: rotateRing + + property View3D view3D + property alias color: material.emissiveColor + property Node targetNode: null + property bool dragging: false + property bool active: false + property alias hovering: mouseAreaMain.hovering + property alias priority: mouseAreaMain.priority + property real currentAngle + property point currentMousePos + + property vector3d _pointerPosPressed + property vector3d _targetPosOnScreen + property vector3d _startRotation + property bool _trackBall + + signal rotateCommit() + signal rotateChange() + + source: "meshes/ring.mesh" + + Model { + id: pickModel + objectName: "PickModel for " + rotateRing.objectName + source: "meshes/ringselect.mesh" + pickable: true + } + + materials: DefaultMaterial { + id: material + emissiveColor: "white" + lighting: DefaultMaterial.NoLighting + } + + function applyLocalRotation(screenPos) + { + currentAngle = mouseAreaMain.getNewRotationAngle(targetNode, _pointerPosPressed, + Qt.vector3d(screenPos.x, screenPos.y, 0), + _targetPosOnScreen, currentAngle, + _trackBall); + mouseAreaMain.applyRotationAngleToNode(targetNode, _startRotation, currentAngle); + } + + function handlePressed(screenPos, angle) + { + if (!targetNode) + return; + + _targetPosOnScreen = view3D.mapFrom3DScene(targetNode.scenePosition); + _targetPosOnScreen.z = 0; + _pointerPosPressed = Qt.vector3d(screenPos.x, screenPos.y, 0); + dragging = true; + _trackBall = angle < 0.1; + + // Recreate vector so we don't follow the changes in targetNode.rotation + _startRotation = Qt.vector3d(targetNode.rotation.x, + targetNode.rotation.y, + targetNode.rotation.z); + currentAngle = 0; + currentMousePos = screenPos; + } + + function handleDragged(screenPos) + { + if (!targetNode) + return; + + applyLocalRotation(screenPos); + currentMousePos = screenPos; + rotateChange(); + } + + function handleReleased(screenPos) + { + if (!targetNode) + return; + + applyLocalRotation(screenPos); + rotateCommit(); + dragging = false; + currentAngle = 0; + currentMousePos = screenPos; + } + + MouseArea3D { + id: mouseAreaMain + view3D: rotateRing.view3D + objectName: "Main plane of " + rotateRing.objectName + x: -30 + y: -30 + width: 60 + height: 60 + circlePickArea: Qt.point(9.2, 1.4) + grabsMouse: targetNode + active: rotateRing.active + pickNode: pickModel + minAngle: 0.05 + onPressed: rotateRing.handlePressed(screenPos, angle) + onDragged: rotateRing.handleDragged(screenPos) + onReleased: rotateRing.handleReleased(screenPos) + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ring.mesh b/share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ring.mesh Binary files differnew file mode 100644 index 0000000000..56e1b82f29 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ring.mesh diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ringselect.mesh b/share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ringselect.mesh Binary files differnew file mode 100644 index 0000000000..b110b308f0 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ringselect.mesh diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.cpp index d4135de973..31ed661125 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.cpp @@ -29,11 +29,15 @@ #include <QtGui/qguiapplication.h> #include <QtQml/qqmlinfo.h> +#include <QtQuick3D/private/qquick3dcamera_p.h> +#include <QtQuick3D/private/qquick3dorthographiccamera_p.h> +#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h> namespace QmlDesigner { namespace Internal { MouseArea3D *MouseArea3D::s_mouseGrab = nullptr; +static const qreal s_mouseDragMultiplier = .02; MouseArea3D::MouseArea3D(QQuick3DNode *parent) : QQuick3DNode(parent) @@ -65,6 +69,21 @@ bool MouseArea3D::active() const return m_active; } +QPointF MouseArea3D::circlePickArea() const +{ + return m_circlePickArea; +} + +qreal MouseArea3D::minAngle() const +{ + return m_minAngle; +} + +QQuick3DNode *MouseArea3D::pickNode() const +{ + return m_pickNode; +} + qreal MouseArea3D::x() const { return m_x; @@ -105,7 +124,7 @@ void MouseArea3D::setGrabsMouse(bool grabsMouse) return; m_grabsMouse = grabsMouse; - emit grabsMouseChanged(grabsMouse); + emit grabsMouseChanged(); } void MouseArea3D::setActive(bool active) @@ -114,7 +133,37 @@ void MouseArea3D::setActive(bool active) return; m_active = active; - emit activeChanged(active); + emit activeChanged(); +} + +void MouseArea3D::setCirclePickArea(const QPointF &pickArea) +{ + if (m_circlePickArea == pickArea) + return; + + m_circlePickArea = pickArea; + emit circlePickAreaChanged(); +} + +// This is the minimum angle for circle picking. At lower angles we fall back to picking on pickNode +void MouseArea3D::setMinAngle(qreal angle) +{ + if (qFuzzyCompare(m_minAngle, angle)) + return; + + m_minAngle = angle; + emit minAngleChanged(); +} + +// This is the fallback pick node when circle picking can't be done due to low angle +// Pick node can't be used except in low angles, as long as only bounding box picking is supported +void MouseArea3D::setPickNode(QQuick3DNode *node) +{ + if (m_pickNode == node) + return; + + m_pickNode = node; + emit pickNodeChanged(); } void MouseArea3D::setX(qreal x) @@ -123,7 +172,7 @@ void MouseArea3D::setX(qreal x) return; m_x = x; - emit xChanged(x); + emit xChanged(); } void MouseArea3D::setY(qreal y) @@ -132,7 +181,7 @@ void MouseArea3D::setY(qreal y) return; m_y = y; - emit yChanged(y); + emit yChanged(); } void MouseArea3D::setWidth(qreal width) @@ -141,7 +190,7 @@ void MouseArea3D::setWidth(qreal width) return; m_width = width; - emit widthChanged(width); + emit widthChanged(); } void MouseArea3D::setHeight(qreal height) @@ -150,7 +199,7 @@ void MouseArea3D::setHeight(qreal height) return; m_height = height; - emit heightChanged(height); + emit heightChanged(); } void MouseArea3D::setPriority(int level) @@ -159,7 +208,7 @@ void MouseArea3D::setPriority(int level) return; m_priority = level; - emit priorityChanged(level); + emit priorityChanged(); } void MouseArea3D::componentComplete() @@ -278,6 +327,84 @@ QVector3D MouseArea3D::getNewScale(QQuick3DNode *node, const QVector3D &startSca return startScale; } +qreal QmlDesigner::Internal::MouseArea3D::getNewRotationAngle( + QQuick3DNode *node, const QVector3D &pressPos, const QVector3D ¤tPos, + const QVector3D &nodePos, qreal prevAngle, bool trackBall) +{ + const QVector3D cameraToNodeDir = getCameraToNodeDir(node); + if (trackBall) { + // Only the distance in plane direction is relevant in trackball drag + QVector3D dragDir = QVector3D::crossProduct(getNormal(), cameraToNodeDir).normalized(); + QVector3D screenDragDir = m_view3D->mapFrom3DScene(node->scenePosition() + dragDir); + screenDragDir.setZ(0); + dragDir = (screenDragDir - nodePos).normalized(); + const QVector3D pressToCurrent = (currentPos - pressPos); + float magnitude = QVector3D::dotProduct(pressToCurrent, dragDir); + qreal angle = -s_mouseDragMultiplier * qreal(magnitude); + return angle; + } else { + const QVector3D nodeToPress = (pressPos - nodePos).normalized(); + const QVector3D nodeToCurrent = (currentPos - nodePos).normalized(); + qreal angle = qAcos(qreal(QVector3D::dotProduct(nodeToPress, nodeToCurrent))); + + // Determine drag direction left/right + const QVector3D dragNormal = QVector3D::crossProduct(nodeToPress, nodeToCurrent).normalized(); + angle *= QVector3D::dotProduct(QVector3D(0.f, 0.f, 1.f), dragNormal) < 0 ? -1.0 : 1.0; + + // Determine drag ring orientation relative to camera + angle *= QVector3D::dotProduct(getNormal(), cameraToNodeDir) < 0 ? 1.0 : -1.0; + + qreal adjustedPrevAngle = prevAngle; + const qreal PI_2 = M_PI * 2.0; + while (adjustedPrevAngle < -PI_2) + adjustedPrevAngle += PI_2; + while (adjustedPrevAngle > PI_2) + adjustedPrevAngle -= PI_2; + + // at M_PI rotation, the angle flips to negative + if (qAbs(angle - adjustedPrevAngle) > M_PI) { + if (angle > adjustedPrevAngle) + return prevAngle - (PI_2 - angle + adjustedPrevAngle); + else + return prevAngle + (PI_2 + angle - adjustedPrevAngle); + } else { + return prevAngle + angle - adjustedPrevAngle; + } + } + +} + +void QmlDesigner::Internal::MouseArea3D::applyRotationAngleToNode( + QQuick3DNode *node, const QVector3D &startRotation, qreal angle) +{ + if (!qFuzzyIsNull(angle)) { + node->setRotation(startRotation); + node->rotate(qRadiansToDegrees(angle), getNormal(), QQuick3DNode::SceneSpace); + } +} + +void MouseArea3D::applyFreeRotation(QQuick3DNode *node, const QVector3D &startRotation, + const QVector3D &pressPos, const QVector3D ¤tPos) +{ + QVector3D dragVector = currentPos - pressPos; + + if (dragVector.length() < 0.001f) + return; + + const float *dataPtr(sceneTransform().data()); + QVector3D xAxis = QVector3D(-dataPtr[0], -dataPtr[1], -dataPtr[2]).normalized(); + QVector3D yAxis = QVector3D(-dataPtr[4], -dataPtr[5], -dataPtr[6]).normalized(); + + QVector3D finalAxis = (dragVector.x() * yAxis + dragVector.y() * xAxis); + + qreal degrees = qRadiansToDegrees(qreal(finalAxis.length()) * s_mouseDragMultiplier); + + finalAxis.normalize(); + + node->setRotation(startRotation); + node->rotate(degrees, finalAxis, QQuick3DNode::SceneSpace); +} + QVector3D MouseArea3D::getMousePosInPlane(const QPointF &mousePosInView) const { const QVector3D mousePos1(float(mousePosInView.x()), float(mousePosInView.y()), 0); @@ -300,12 +427,50 @@ bool MouseArea3D::eventFilter(QObject *, QEvent *event) return false; } - auto mouseOnTopOfMouseArea = [this](const QVector3D &mousePosInPlane) -> bool { - return !qFuzzyCompare(mousePosInPlane.z(), -1) + qreal pickAngle = 0.; + + auto mouseOnTopOfMouseArea = [this, &pickAngle]( + const QVector3D &mousePosInPlane, const QPointF &mousePos) -> bool { + const bool onPlane = !qFuzzyCompare(mousePosInPlane.z(), -1) && mousePosInPlane.x() >= float(m_x) && mousePosInPlane.x() <= float(m_x + m_width) && mousePosInPlane.y() >= float(m_y) && mousePosInPlane.y() <= float(m_y + m_height); + + bool onCircle = true; + bool pickSuccess = false; + if (!qFuzzyIsNull(m_circlePickArea.y()) || !qFuzzyIsNull(m_minAngle)) { + + QVector3D cameraToMouseAreaDir = getCameraToNodeDir(this); + const QVector3D mouseAreaDir = getNormal(); + qreal angle = qreal(QVector3D::dotProduct(cameraToMouseAreaDir, mouseAreaDir)); + // Do not allow selecting ring that is nearly perpendicular to camera, as dragging along + // that plane would be difficult + pickAngle = qAcos(angle); + pickAngle = pickAngle > M_PI_2 ? pickAngle - M_PI_2 : M_PI_2 - pickAngle; + if (pickAngle > m_minAngle) { + if (!qFuzzyIsNull(m_circlePickArea.y())) { + qreal ringCenter = m_circlePickArea.x(); + // Thickness is increased according to the angle to camera to keep projected + // circle thickness constant at all angles. + qreal divisor = qSin(pickAngle) * 2.; // This is never zero + qreal thickness = ((m_circlePickArea.y() / divisor)); + qreal mousePosRadius = qSqrt(qreal(mousePosInPlane.x() * mousePosInPlane.x()) + + qreal(mousePosInPlane.y() * mousePosInPlane.y())); + onCircle = ringCenter - thickness <= mousePosRadius + && ringCenter + thickness >= mousePosRadius; + } + } else { + // Fall back to picking on the pickNode. At this angle, bounding box pick is not + // a problem + onCircle = false; + if (m_pickNode) { + QQuick3DPickResult pr = m_view3D->pick(float(mousePos.x()), float(mousePos.y())); + pickSuccess = pr.objectHit() == m_pickNode; + } + } + } + return (onCircle && onPlane) || pickSuccess; }; switch (event->type()) { @@ -313,9 +478,9 @@ bool MouseArea3D::eventFilter(QObject *, QEvent *event) auto const mouseEvent = static_cast<QMouseEvent *>(event); if (mouseEvent->button() == Qt::LeftButton) { m_mousePosInPlane = getMousePosInPlane(mouseEvent->pos()); - if (mouseOnTopOfMouseArea(m_mousePosInPlane)) { + if (mouseOnTopOfMouseArea(m_mousePosInPlane, mouseEvent->pos())) { setDragging(true); - emit pressed(m_mousePosInPlane, mouseEvent->globalPos()); + emit pressed(m_mousePosInPlane, mouseEvent->pos(), pickAngle); if (m_grabsMouse) { if (s_mouseGrab && s_mouseGrab != this) { s_mouseGrab->setDragging(false); @@ -338,13 +503,13 @@ bool MouseArea3D::eventFilter(QObject *, QEvent *event) if (qFuzzyCompare(mousePosInPlane.z(), -1)) mousePosInPlane = m_mousePosInPlane; setDragging(false); - emit released(mousePosInPlane, mouseEvent->globalPos()); + emit released(mousePosInPlane, mouseEvent->pos()); if (m_grabsMouse) { if (s_mouseGrab && s_mouseGrab != this) { s_mouseGrab->setDragging(false); s_mouseGrab->setHovering(false); } - if (mouseOnTopOfMouseArea(mousePosInPlane)) { + if (mouseOnTopOfMouseArea(mousePosInPlane, mouseEvent->pos())) { s_mouseGrab = this; setHovering(true); } else { @@ -362,7 +527,7 @@ bool MouseArea3D::eventFilter(QObject *, QEvent *event) case QEvent::HoverMove: { auto const mouseEvent = static_cast<QMouseEvent *>(event); const QVector3D mousePosInPlane = getMousePosInPlane(mouseEvent->pos()); - const bool hasMouse = mouseOnTopOfMouseArea(mousePosInPlane); + const bool hasMouse = mouseOnTopOfMouseArea(mousePosInPlane, mouseEvent->pos()); setHovering(hasMouse); @@ -376,9 +541,9 @@ bool MouseArea3D::eventFilter(QObject *, QEvent *event) s_mouseGrab = nullptr; } - if (m_dragging && !qFuzzyCompare(mousePosInPlane.z(), -1)) { + if (m_dragging && (m_circlePickArea.y() > 0. || !qFuzzyCompare(mousePosInPlane.z(), -1))) { m_mousePosInPlane = mousePosInPlane; - emit dragged(mousePosInPlane, mouseEvent->globalPos()); + emit dragged(mousePosInPlane, mouseEvent->pos()); } break; @@ -408,6 +573,25 @@ void MouseArea3D::setHovering(bool enable) emit hoveringChanged(); } +QVector3D MouseArea3D::getNormal() const +{ + const float *dataPtr(sceneTransform().data()); + return QVector3D(dataPtr[8], dataPtr[9], dataPtr[10]).normalized(); +} + +QVector3D MouseArea3D::getCameraToNodeDir(QQuick3DNode *node) const +{ + QVector3D dir; + if (qobject_cast<QQuick3DOrthographicCamera *>(m_view3D->camera())) { + dir = m_view3D->camera()->cameraNode()->getDirection(); + // Camera direction has x and y flipped + dir = QVector3D(-dir.x(), -dir.y(), dir.z()); + } else { + dir = (node->scenePosition() - m_view3D->camera()->scenePosition()).normalized(); + } + return dir; +} + } } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.h index af46557682..e227b3f9dd 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.h @@ -28,9 +28,11 @@ #ifdef QUICK3D_MODULE #include <QtGui/qvector3d.h> +#include <QtCore/qpoint.h> #include <QtCore/qpointer.h> #include <QtQuick3D/private/qquick3dnode_p.h> +#include <QtQuick3D/private/qquick3dmodel_p.h> #include <QtQuick3D/private/qquick3dviewport_p.h> #include <QtQuick3D/private/qtquick3dglobal_p.h> @@ -50,6 +52,9 @@ class MouseArea3D : public QQuick3DNode Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged) Q_PROPERTY(int priority READ priority WRITE setPriority NOTIFY priorityChanged) Q_PROPERTY(int active READ active WRITE setActive NOTIFY activeChanged) + Q_PROPERTY(QPointF circlePickArea READ circlePickArea WRITE setCirclePickArea NOTIFY circlePickAreaChanged) + Q_PROPERTY(qreal minAngle READ minAngle WRITE setMinAngle NOTIFY minAngleChanged) + Q_PROPERTY(QQuick3DNode *pickNode READ pickNode WRITE setPickNode NOTIFY pickNodeChanged) Q_INTERFACES(QQmlParserStatus) @@ -68,11 +73,17 @@ public: bool dragging() const; bool grabsMouse() const; bool active() const; + QPointF circlePickArea() const; + qreal minAngle() const; + QQuick3DNode *pickNode() const; public slots: void setView3D(QQuick3DViewport *view3D); void setGrabsMouse(bool grabsMouse); void setActive(bool active); + void setCirclePickArea(const QPointF &pickArea); + void setMinAngle(qreal angle); + void setPickNode(QQuick3DNode *node); void setX(qreal x); void setY(qreal y); @@ -89,22 +100,35 @@ public slots: const QVector3D &pressPos, const QVector3D &sceneRelativeDistance, bool global); + Q_INVOKABLE qreal getNewRotationAngle(QQuick3DNode *node, const QVector3D &pressPos, + const QVector3D ¤tPos, const QVector3D &nodePos, + qreal prevAngle, bool trackBall); + Q_INVOKABLE void applyRotationAngleToNode(QQuick3DNode *node, const QVector3D &startRotation, + qreal angle); + Q_INVOKABLE void applyFreeRotation(QQuick3DNode *node, const QVector3D &startRotation, + const QVector3D &pressPos, const QVector3D ¤tPos); + signals: void view3DChanged(); - void xChanged(qreal x); - void yChanged(qreal y); - void widthChanged(qreal width); - void heightChanged(qreal height); - void priorityChanged(int level); + void xChanged(); + void yChanged(); + void widthChanged(); + void heightChanged(); + void priorityChanged(); void hoveringChanged(); void draggingChanged(); - void activeChanged(bool active); - void pressed(const QVector3D &scenePos, const QPoint &screenPos); + void activeChanged(); + void grabsMouseChanged(); + void circlePickAreaChanged(); + void minAngleChanged(); + void pickNodeChanged(); + + // angle parameter is only set if circlePickArea is specified + void pressed(const QVector3D &scenePos, const QPoint &screenPos, qreal angle); void released(const QVector3D &scenePos, const QPoint &screenPos); void dragged(const QVector3D &scenePos, const QPoint &screenPos); - void grabsMouseChanged(bool grabsMouse); protected: void classBegin() override {} @@ -114,6 +138,8 @@ protected: private: void setDragging(bool enable); void setHovering(bool enable); + QVector3D getNormal() const; + QVector3D getCameraToNodeDir(QQuick3DNode *node) const; Q_DISABLE_COPY(MouseArea3D) QQuick3DViewport *m_view3D = nullptr; @@ -133,6 +159,9 @@ private: static MouseArea3D *s_mouseGrab; bool m_grabsMouse; QVector3D m_mousePosInPlane; + QPointF m_circlePickArea; + qreal m_minAngle = 0.; + QQuick3DNode *m_pickNode = nullptr; }; } diff --git a/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc b/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc index fbdec743e4..deb70ba145 100644 --- a/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc +++ b/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc @@ -22,8 +22,12 @@ <file>mockfiles/ScaleRod.qml</file> <file>mockfiles/ScaleGizmo.qml</file> <file>mockfiles/ToolBarButton.qml</file> + <file>mockfiles/RotateGizmo.qml</file> + <file>mockfiles/RotateRing.qml</file> <file>mockfiles/meshes/arrow.mesh</file> <file>mockfiles/meshes/scalerod.mesh</file> + <file>mockfiles/meshes/ring.mesh</file> + <file>mockfiles/meshes/ringselect.mesh</file> <file>mockfiles/images/camera-pick-icon.png</file> <file>mockfiles/images/camera-pick-icon@2x.png</file> <file>mockfiles/images/light-pick-icon.png</file> |