summaryrefslogtreecommitdiff
path: root/share
diff options
context:
space:
mode:
authorMiikka Heikkinen <miikka.heikkinen@qt.io>2019-11-04 16:03:07 +0200
committerMiikka Heikkinen <miikka.heikkinen@qt.io>2019-11-18 08:39:16 +0000
commit20257e1e4fbaa6530007d3d5ec8fb35f11df8284 (patch)
treec6b20d61c55424971a7ca0f0fd85cdc011431d0c /share
parenta87293d1c4d1c5e12dc8311cfa49283ab6d977ab (diff)
downloadqt-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.qml35
-rw-r--r--share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml221
-rw-r--r--share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml133
-rw-r--r--share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ring.meshbin0 -> 45528 bytes
-rw-r--r--share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ringselect.meshbin0 -> 50008 bytes
-rw-r--r--share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.cpp216
-rw-r--r--share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/mousearea3d.h45
-rw-r--r--share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc4
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
new file mode 100644
index 0000000000..56e1b82f29
--- /dev/null
+++ b/share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ring.mesh
Binary files differ
diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ringselect.mesh b/share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ringselect.mesh
new file mode 100644
index 0000000000..b110b308f0
--- /dev/null
+++ b/share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ringselect.mesh
Binary files differ
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 &currentPos,
+ 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 &currentPos)
+{
+ 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 &currentPos, 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 &currentPos);
+
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>