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:
Miikka Heikkinen
2019-11-04 16:03:07 +02:00
parent a87293d1c4
commit 20257e1e4f
8 changed files with 629 additions and 25 deletions

View File

@@ -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 {

View 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)
}
}
}

View 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)
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
};
}

View File

@@ -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>