QmlDesigner: Implement axis helper on 3D edit view

Axis helper shows up on top right corner of 3D edit view.
Clicking on axis helper arms zooms camera on that side of the selected
object.

Change-Id: Ibd81a933036f7965f825e3dc97ad7156da62e14c
Fixes: QDS-1205
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Miikka Heikkinen
2019-11-20 18:01:34 +02:00
parent b619c60ff7
commit c7120bde92
11 changed files with 297 additions and 9 deletions

View File

@@ -81,6 +81,7 @@ Node {
MouseArea3D { MouseArea3D {
id: helper id: helper
active: false
view3D: overlayNode.view3D view3D: overlayNode.view3D
} }
} }

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
View3D {
id: axisHelperView
property var editCameraCtrl
property Node selectedNode
camera: axisHelperCamera
Node {
OrthographicCamera {
id: axisHelperCamera
rotation: editCameraCtrl.camera.rotation
position: editCameraCtrl.camera.position.minus(editCameraCtrl._lookAtPoint)
.normalized().times(600)
}
AutoScaleHelper {
id: autoScale
view3D: axisHelperView
position: axisHelperGizmo.scenePosition
}
Node {
id: axisHelperGizmo
scale: autoScale.getScale(Qt.vector3d(4, 4, 4))
AxisHelperArm {
id: armX
rotation: Qt.vector3d(0, 0, -90)
color: Qt.rgba(1, 0, 0, 1)
hoverColor: Qt.lighter(Qt.rgba(1, 0, 0, 1))
view3D: axisHelperView
camRotPos: Qt.vector3d(0, 90, 0)
camRotNeg: Qt.vector3d(0, -90, 0)
}
AxisHelperArm {
id: armY
rotation: Qt.vector3d(0, 0, 0)
color: Qt.rgba(0, 0.6, 0, 1)
hoverColor: Qt.lighter(Qt.rgba(0, 0.6, 0, 1))
view3D: axisHelperView
camRotPos: Qt.vector3d(-90, 0, 0)
camRotNeg: Qt.vector3d(90, 0, 0)
}
AxisHelperArm {
id: armZ
rotation: Qt.vector3d(90, 0, 0)
color: Qt.rgba(0, 0, 1, 1)
hoverColor: Qt.lighter(Qt.rgba(0, 0, 1, 1))
view3D: axisHelperView
camRotPos: Qt.vector3d(0, 0, 0)
camRotNeg: Qt.vector3d(0, 180, 0)
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton
property var pickObj: null
function cancelHover()
{
if (pickObj) {
pickObj.hovering = false;
pickObj = null;
}
}
function pick(mouse)
{
var result = axisHelperView.pick(mouse.x, mouse.y);
if (result.objectHit) {
if (result.objectHit !== pickObj) {
cancelHover();
pickObj = result.objectHit;
pickObj.hovering = true;
}
} else {
cancelHover();
}
}
onPositionChanged: {
pick(mouse);
}
onPressed: {
pick(mouse);
if (pickObj) {
axisHelperView.editCameraCtrl.fitObject(axisHelperView.selectedNode,
pickObj.cameraRotation);
} else {
mouse.accepted = false;
}
}
onExited: cancelHover()
onCanceled: cancelHover()
}
}

View File

@@ -0,0 +1,70 @@
/****************************************************************************
**
** 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
Node {
id: armRoot
property alias posModel: posModel
property alias negModel: negModel
property View3D view3D
property color hoverColor
property color color
property vector3d camRotPos
property vector3d camRotNeg
Model {
id: posModel
property bool hovering: false
property vector3d cameraRotation: armRoot.camRotPos
source: "meshes/axishelper.mesh"
materials: DefaultMaterial {
id: posMat
emissiveColor: posModel.hovering ? armRoot.hoverColor : armRoot.color
lighting: DefaultMaterial.NoLighting
}
pickable: true
}
Model {
id: negModel
property bool hovering: false
property vector3d cameraRotation: armRoot.camRotNeg
source: "#Sphere"
y: -6
scale: Qt.vector3d(0.025, 0.025, 0.025)
materials: DefaultMaterial {
id: negMat
emissiveColor: negModel.hovering ? armRoot.hoverColor : armRoot.color
lighting: DefaultMaterial.NoLighting
}
pickable: true
}
}

View File

@@ -30,6 +30,7 @@ Item {
id: cameraCtrl id: cameraCtrl
property Camera camera: null property Camera camera: null
property View3D view3d: null
property vector3d _lookAtPoint property vector3d _lookAtPoint
property vector3d _pressPoint property vector3d _pressPoint
@@ -44,6 +45,15 @@ Item {
property real _defaultCameraLookAtDistance: 0 property real _defaultCameraLookAtDistance: 0
property Camera _prevCamera: null property Camera _prevCamera: null
function fitObject(targetObject, rotation)
{
camera.rotation = rotation;
var newLookAtAndZoom = _generalHelper.fitObjectToCamera(
camera, _defaultCameraLookAtDistance, targetObject, view3d);
_lookAtPoint = newLookAtAndZoom.toVector3d();
_zoomFactor = newLookAtAndZoom.w;
}
function zoomRelative(distance) function zoomRelative(distance)
{ {
_zoomFactor = _generalHelper.zoomCamera(camera, distance, _defaultCameraLookAtDistance, _zoomFactor = _generalHelper.zoomCamera(camera, distance, _defaultCameraLookAtDistance,

View File

@@ -248,8 +248,7 @@ Window {
id: gizmoLabel id: gizmoLabel
targetNode: moveGizmo.visible ? moveGizmo : scaleGizmo targetNode: moveGizmo.visible ? moveGizmo : scaleGizmo
targetView: overlayView targetView: overlayView
offsetX: 0 offset: Qt.vector3d(0, 45, 0)
offsetY: 45
visible: targetNode.dragging visible: targetNode.dragging
Rectangle { Rectangle {
@@ -285,6 +284,7 @@ Window {
id: cameraControl id: cameraControl
camera: editView.camera camera: editView.camera
anchors.fill: parent anchors.fill: parent
view3d: editView
} }
} }
@@ -357,9 +357,18 @@ Window {
} }
} }
Column { AxisHelper {
y: 8
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top
width: 100
height: width
editCameraCtrl: cameraControl
selectedNode : viewWindow.selectedNode
}
Column {
anchors.left: parent.left
anchors.bottom: parent.bottom
CheckBox { CheckBox {
id: editLightCheckbox id: editLightCheckbox
checked: false checked: false

View File

@@ -49,8 +49,6 @@ Node {
id: iconOverlay id: iconOverlay
targetNode: iconGizmo targetNode: iconGizmo
targetView: view3D targetView: view3D
offsetX: 0
offsetY: 0
visible: iconGizmo.visible && !isBehindCamera visible: iconGizmo.visible && !isBehindCamera
parent: view3D parent: view3D

View File

@@ -31,8 +31,7 @@ Item {
property Node targetNode property Node targetNode
property View3D targetView property View3D targetView
property real offsetX: 0 property vector3d offset: Qt.vector3d(0, 0, 0)
property real offsetY: 0
property bool isBehindCamera property bool isBehindCamera
@@ -56,7 +55,9 @@ Item {
function updateOverlay() function updateOverlay()
{ {
var scenePos = targetNode ? targetNode.scenePosition : Qt.vector3d(0, 0, 0); var scenePos = targetNode ? targetNode.scenePosition : Qt.vector3d(0, 0, 0);
var scenePosWithOffset = Qt.vector3d(scenePos.x + offsetX, scenePos.y + offsetY, scenePos.z); var scenePosWithOffset = Qt.vector3d(scenePos.x + offset.x,
scenePos.y + offset.y,
scenePos.z + offset.z);
var viewPos = targetView ? targetView.mapFrom3DScene(scenePosWithOffset) var viewPos = targetView ? targetView.mapFrom3DScene(scenePosWithOffset)
: Qt.vector3d(0, 0, 0); : Qt.vector3d(0, 0, 0);
root.x = viewPos.x; root.x = viewPos.x;

View File

@@ -28,7 +28,15 @@
#include <QtQuick3D/private/qquick3dorthographiccamera_p.h> #include <QtQuick3D/private/qquick3dorthographiccamera_p.h>
#include <QtQuick3D/private/qquick3dperspectivecamera_p.h> #include <QtQuick3D/private/qquick3dperspectivecamera_p.h>
#include <QtQuick3D/private/qquick3dobject_p_p.h>
#include <QtQuick3D/private/qquick3dmodel_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h>
#include <QtQuick3DUtils/private/qssgbounds3_p.h>
#include <QtQuick/qquickwindow.h>
#include <QtCore/qhash.h> #include <QtCore/qhash.h>
#include <QtCore/qmath.h>
#include <QtGui/qmatrix4x4.h> #include <QtGui/qmatrix4x4.h>
namespace QmlDesigner { namespace QmlDesigner {
@@ -126,6 +134,57 @@ float GeneralHelper::zoomCamera(QQuick3DCamera *camera, float distance, float de
return newZoomFactor; return newZoomFactor;
} }
// Return value contains new lookAt point (xyz) and zoom factor (w)
QVector4D GeneralHelper::fitObjectToCamera(QQuick3DCamera *camera, float defaultLookAtDistance,
QQuick3DNode *targetObject, QQuick3DViewport *viewPort)
{
if (!camera)
return QVector4D(0.f, 0.f, 0.f, 1.f);
QVector3D lookAt = targetObject ? targetObject->scenePosition() : QVector3D();
// Get object bounds
qreal maxExtent = 200.;
if (auto modelNode = qobject_cast<QQuick3DModel *>(targetObject)) {
auto targetPriv = QQuick3DObjectPrivate::get(targetObject);
if (auto renderModel = static_cast<QSSGRenderModel *>(targetPriv->spatialNode)) {
QWindow *window = static_cast<QWindow *>(viewPort->window());
if (window) {
auto context = QSSGRenderContextInterface::getRenderContextInterface(quintptr(window));
if (!context.isNull()) {
auto bufferManager = context->bufferManager();
QSSGBounds3 bounds = renderModel->getModelBounds(bufferManager);
QVector3D center = bounds.center();
const QVector3D e = bounds.extents();
const QVector3D s = targetObject->sceneScale();
qreal maxScale = qSqrt(qreal(s.x() * s.x() + s.y() * s.y() + s.z() * s.z()));
maxExtent = qSqrt(qreal(e.x() * e.x() + e.y() * e.y() + e.z() * e.z()));
maxExtent *= maxScale;
// Adjust lookAt to look directly at the center of the object bounds
QMatrix4x4 m = targetObject->sceneTransform();
lookAt = m.map(center);
}
}
}
}
// Reset camera position to default zoom
QMatrix4x4 m = camera->sceneTransform();
const float *dataPtr(m.data());
QVector3D newLookVector(-dataPtr[8], -dataPtr[9], -dataPtr[10]);
newLookVector.normalize();
newLookVector *= defaultLookAtDistance;
camera->setPosition(lookAt + newLookVector);
// Emprically determined algorithm for nice zoom
float newZoomFactor = qBound(.0001f, float(maxExtent / 700.), 10000.f);
return QVector4D(lookAt,
zoomCamera(camera, 0, defaultLookAtDistance, lookAt, newZoomFactor, false));
}
} }
} }

View File

@@ -28,6 +28,8 @@
#ifdef QUICK3D_MODULE #ifdef QUICK3D_MODULE
#include <QtQuick3D/private/qquick3dcamera_p.h> #include <QtQuick3D/private/qquick3dcamera_p.h>
#include <QtQuick3D/private/qquick3dnode_p.h>
#include <QtQuick3D/private/qquick3dviewport_p.h>
#include <QtCore/qobject.h> #include <QtCore/qobject.h>
#include <QtCore/qtimer.h> #include <QtCore/qtimer.h>
@@ -53,6 +55,8 @@ public:
Q_INVOKABLE float zoomCamera(QQuick3DCamera *camera, float distance, Q_INVOKABLE float zoomCamera(QQuick3DCamera *camera, float distance,
float defaultLookAtDistance, const QVector3D &lookAt, float defaultLookAtDistance, const QVector3D &lookAt,
float zoomFactor, bool relative); float zoomFactor, bool relative);
Q_INVOKABLE QVector4D fitObjectToCamera(QQuick3DCamera *camera, float defaultLookAtDistance,
QQuick3DNode *targetObject, QQuick3DViewport *viewPort);
signals: signals:
void overlayUpdateNeeded(); void overlayUpdateNeeded();

View File

@@ -27,10 +27,13 @@
<file>mockfiles/RotateGizmo.qml</file> <file>mockfiles/RotateGizmo.qml</file>
<file>mockfiles/RotateRing.qml</file> <file>mockfiles/RotateRing.qml</file>
<file>mockfiles/SelectionBox.qml</file> <file>mockfiles/SelectionBox.qml</file>
<file>mockfiles/AxisHelper.qml</file>
<file>mockfiles/AxisHelperArm.qml</file>
<file>mockfiles/meshes/arrow.mesh</file> <file>mockfiles/meshes/arrow.mesh</file>
<file>mockfiles/meshes/scalerod.mesh</file> <file>mockfiles/meshes/scalerod.mesh</file>
<file>mockfiles/meshes/ring.mesh</file> <file>mockfiles/meshes/ring.mesh</file>
<file>mockfiles/meshes/ringselect.mesh</file> <file>mockfiles/meshes/ringselect.mesh</file>
<file>mockfiles/meshes/axishelper.mesh</file>
<file>mockfiles/images/editor_camera.png</file> <file>mockfiles/images/editor_camera.png</file>
<file>mockfiles/images/editor_camera@2x.png</file> <file>mockfiles/images/editor_camera@2x.png</file>
<file>mockfiles/images/light-pick-icon.png</file> <file>mockfiles/images/light-pick-icon.png</file>