forked from qt-creator/qt-creator
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>
This commit is contained in:
@@ -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 {
|
||||
|
221
share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml
Normal file
221
share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
133
share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml
Normal file
133
share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml
Normal file
@@ -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)
|
||||
}
|
||||
}
|
BIN
share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ring.mesh
Normal file
BIN
share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ring.mesh
Normal file
Binary file not shown.
BIN
share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ringselect.mesh
Normal file
BIN
share/qtcreator/qml/qmlpuppet/mockfiles/meshes/ringselect.mesh
Normal file
Binary file not shown.
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user