From 389f96b0ee8b2485290aee5c3c1eac05403f04ac Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 21 Nov 2019 17:37:09 +0200 Subject: QmlDesigner: Implement group selection boxes in 3D edit view Object's selection box now includes the bounds of all of its descendants. Selection boxes of immediate children of a selected object are also drawn. Individual/group selection buttons also now work as expected. Change-Id: Ice7ef9a536e32c6bb6da70fe23bf0a38e72c14f8 Fixes: QDS-1210 Reviewed-by: Mahmoud Badri Reviewed-by: Thomas Hartmann --- .../qml/qmlpuppet/mockfiles/EditView3D.qml | 17 +- .../qml/qmlpuppet/mockfiles/SelectionBox.qml | 2 +- .../qml2puppet/editor3d/generalhelper.cpp | 6 +- .../qmlpuppet/qml2puppet/editor3d/generalhelper.h | 13 +- .../qml2puppet/editor3d/selectionboxgeometry.cpp | 206 ++++++++++++++++----- .../qml2puppet/editor3d/selectionboxgeometry.h | 11 +- .../instances/qt5informationnodeinstanceserver.cpp | 20 +- 7 files changed, 210 insertions(+), 65 deletions(-) (limited to 'share/qtcreator') diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml index 56faf67ec4..7af8cf0cba 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml @@ -55,9 +55,14 @@ Window { selectedNode = object; } - function emitObjectClicked(object) { - selectObject(object); - objectClicked(object); + function handleObjectClicked(object) { + var theObject = object; + if (btnSelectGroup.selected) { + while (theObject && theObject.parent !== scene) + theObject = theObject.parent; + } + selectObject(theObject); + objectClicked(theObject); } function addLightGizmo(obj) @@ -68,7 +73,7 @@ Window { {"view3D": overlayView, "targetNode": obj, "selectedNode": selectedNode}); lightGizmos[lightGizmos.length] = gizmo; - gizmo.clicked.connect(emitObjectClicked); + gizmo.clicked.connect(handleObjectClicked); gizmo.selectedNode = Qt.binding(function() {return selectedNode;}); } } @@ -83,7 +88,7 @@ Window { {"view3D": overlayView, "targetNode": obj, "geometryName": geometryName, "viewPortRect": viewPortRect, "selectedNode": selectedNode}); cameraGizmos[cameraGizmos.length] = gizmo; - gizmo.clicked.connect(emitObjectClicked); + gizmo.clicked.connect(handleObjectClicked); gizmo.viewPortRect = Qt.binding(function() {return viewPortRect;}); gizmo.selectedNode = Qt.binding(function() {return selectedNode;}); } @@ -178,7 +183,7 @@ Window { onTapped: { var pickResult = editView.pick(eventPoint.scenePosition.x, eventPoint.scenePosition.y); - emitObjectClicked(pickResult.objectHit); + handleObjectClicked(pickResult.objectHit); } } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/SelectionBox.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/SelectionBox.qml index 6465cab2b7..08de0a7ae1 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/SelectionBox.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/SelectionBox.qml @@ -52,7 +52,7 @@ Node { orientation: selectionBox.targetNode ? selectionBox.targetNode.orientation : Node.LeftHanded rotationOrder: selectionBox.targetNode ? selectionBox.targetNode.rotationOrder : Node.YXZ - visible: selectionBox.targetNode && selectionBox.targetNode instanceof Model + visible: selectionBox.targetNode && !selectionBoxGeometry.isEmpty materials: [ DefaultMaterial { diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp index 30be47d2dc..f5c7c6e656 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp @@ -29,15 +29,17 @@ #include #include #include +#include +#include #include +#include +#include #include #include #include #include #include -#include #include -#include namespace QmlDesigner { namespace Internal { diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h index 8a1cbe7001..16007e6798 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h @@ -27,14 +27,21 @@ #ifdef QUICK3D_MODULE -#include -#include -#include #include #include +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QQuick3DCamera; +class QQuick3DNode; +class QQuick3DViewport; +QT_END_NAMESPACE namespace QmlDesigner { namespace Internal { + class GeneralHelper : public QObject { Q_OBJECT diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp index 452d924427..db6218c331 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp @@ -35,6 +35,9 @@ #include #include #include +#include + +#include namespace QmlDesigner { namespace Internal { @@ -46,6 +49,9 @@ SelectionBoxGeometry::SelectionBoxGeometry() SelectionBoxGeometry::~SelectionBoxGeometry() { + for (auto &connection : qAsConst(m_connections)) + QObject::disconnect(connection); + m_connections.clear(); } QQuick3DNode *SelectionBoxGeometry::targetNode() const @@ -63,6 +69,11 @@ QQuick3DViewport *SelectionBoxGeometry::view3D() const return m_view3D; } +bool QmlDesigner::Internal::SelectionBoxGeometry::isEmpty() const +{ + return m_isEmpty; +} + void SelectionBoxGeometry::setTargetNode(QQuick3DNode *targetNode) { if (m_targetNode == targetNode) @@ -111,13 +122,18 @@ QSSGRenderGraphObject *SelectionBoxGeometry::updateSpatialNode(QSSGRenderGraphOb QSSGRenderGeometry *geometry = static_cast(node); geometry->clear(); + for (auto &connection : qAsConst(m_connections)) + QObject::disconnect(connection); + m_connections.clear(); QByteArray vertexData; QByteArray indexData; - QVector3D minBounds(-100.f, -100.f, -100.f); - QVector3D maxBounds(100.f, 100.f, 100.f); - QVector3D extents; + static const float floatMin = std::numeric_limits::lowest(); + static const float floatMax = std::numeric_limits::max(); + + QVector3D minBounds = QVector3D(floatMax, floatMax, floatMax); + QVector3D maxBounds = QVector3D(floatMin, floatMin, floatMin); if (m_targetNode) { auto rootPriv = QQuick3DObjectPrivate::get(m_rootNode); @@ -134,33 +150,12 @@ QSSGRenderGraphObject *SelectionBoxGeometry::updateSpatialNode(QSSGRenderGraphOb rootRN->markDirty(QSSGRenderNode::TransformDirtyFlag::TransformNotDirty); rootRN->calculateGlobalVariables(); } - if (auto modelNode = qobject_cast(m_targetNode)) { - auto nodePriv = QQuick3DObjectPrivate::get(m_targetNode); - if (auto renderModel = static_cast(nodePriv->spatialNode)) { - QWindow *window = static_cast(m_view3D->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(); - extents = bounds.extents(); - minBounds = center - extents; - maxBounds = center + extents; - } - } - } - } + getBounds(m_targetNode, vertexData, indexData, minBounds, maxBounds, QMatrix4x4()); + } else { + // Fill some dummy data so geometry won't get rejected + appendVertexData(vertexData, indexData, minBounds, maxBounds); } - // Adjust bounds to reduce targetNode pixels obscuring the selection box - extents /= 1000.f; - minBounds -= extents; - maxBounds += extents; - - fillVertexData(vertexData, indexData, minBounds, maxBounds); - geometry->addAttribute(QSSGRenderGeometry::Attribute::PositionSemantic, 0, QSSGRenderGeometry::Attribute::ComponentType::F32Type); geometry->addAttribute(QSSGRenderGeometry::Attribute::IndexSemantic, 0, @@ -171,19 +166,138 @@ QSSGRenderGraphObject *SelectionBoxGeometry::updateSpatialNode(QSSGRenderGraphOb geometry->setPrimitiveType(QSSGRenderGeometry::Lines); geometry->setBounds(minBounds, maxBounds); + bool empty = minBounds.isNull() && maxBounds.isNull(); + if (m_isEmpty != empty) { + m_isEmpty = empty; + // Delay notification until we're done with spatial node updates + QTimer::singleShot(0, this, &SelectionBoxGeometry::isEmptyChanged); + } + return node; } -void SelectionBoxGeometry::fillVertexData(QByteArray &vertexData, QByteArray &indexData, - const QVector3D &minBounds, const QVector3D &maxBounds) +void SelectionBoxGeometry::getBounds(QQuick3DNode *node, QByteArray &vertexData, + QByteArray &indexData, QVector3D &minBounds, + QVector3D &maxBounds, const QMatrix4x4 &transform) +{ + QMatrix4x4 fullTransform; + auto nodePriv = QQuick3DObjectPrivate::get(node); + auto renderNode = static_cast(nodePriv->spatialNode); + + // All transforms are relative to targetNode transform, so its local transform is ignored + if (node != m_targetNode) { + if (renderNode) { + if (renderNode->flags.testFlag(QSSGRenderNode::Flag::TransformDirty)) + renderNode->calculateLocalTransform(); + fullTransform = transform * renderNode->localTransform; + } + + m_connections << QObject::connect(node, &QQuick3DNode::scaleChanged, + this, &SelectionBoxGeometry::update, Qt::QueuedConnection); + m_connections << QObject::connect(node, &QQuick3DNode::rotationChanged, + this, &SelectionBoxGeometry::update, Qt::QueuedConnection); + m_connections << QObject::connect(node, &QQuick3DNode::positionChanged, + this, &SelectionBoxGeometry::update, Qt::QueuedConnection); + m_connections << QObject::connect(node, &QQuick3DNode::pivotChanged, + this, &SelectionBoxGeometry::update, Qt::QueuedConnection); + m_connections << QObject::connect(node, &QQuick3DNode::orientationChanged, + this, &SelectionBoxGeometry::update, Qt::QueuedConnection); + m_connections << QObject::connect(node, &QQuick3DNode::rotationOrderChanged, + this, &SelectionBoxGeometry::update, Qt::QueuedConnection); + } + + QVector minBoundsVec; + QVector maxBoundsVec; + + // Check for children + const auto children = node->childItems(); + for (const auto child : children) { + if (auto childNode = qobject_cast(child)) { + QVector3D newMinBounds = minBounds; + QVector3D newMaxBounds = maxBounds; + getBounds(childNode, vertexData, indexData, newMinBounds, newMaxBounds, fullTransform); + minBoundsVec << newMinBounds; + maxBoundsVec << newMaxBounds; + } + } + + // Combine all child bounds + for (const auto &newBounds : qAsConst(minBoundsVec)) { + minBounds.setX(qMin(newBounds.x(), minBounds.x())); + minBounds.setY(qMin(newBounds.y(), minBounds.y())); + minBounds.setZ(qMin(newBounds.z(), minBounds.z())); + } + for (const auto &newBounds : qAsConst(maxBoundsVec)) { + maxBounds.setX(qMax(newBounds.x(), maxBounds.x())); + maxBounds.setY(qMax(newBounds.y(), maxBounds.y())); + maxBounds.setZ(qMax(newBounds.z(), maxBounds.z())); + } + + if (auto modelNode = qobject_cast(node)) { + if (auto renderModel = static_cast(renderNode)) { + QWindow *window = static_cast(m_view3D->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(); + QVector3D extents = bounds.extents(); + QVector3D localMin = center - extents; + QVector3D localMax = center + extents; + + // Transform all corners of the local bounding box to find final extent in + // in parent space + + auto checkCorner = [&minBounds, &maxBounds, &fullTransform] + (const QVector3D &corner) { + QVector3D mappedCorner = fullTransform.map(corner); + minBounds.setX(qMin(mappedCorner.x(), minBounds.x())); + minBounds.setY(qMin(mappedCorner.y(), minBounds.y())); + minBounds.setZ(qMin(mappedCorner.z(), minBounds.z())); + maxBounds.setX(qMax(mappedCorner.x(), maxBounds.x())); + maxBounds.setY(qMax(mappedCorner.y(), maxBounds.y())); + maxBounds.setZ(qMax(mappedCorner.z(), maxBounds.z())); + }; + + checkCorner(localMin); + checkCorner(localMax); + checkCorner(QVector3D(localMin.x(), localMin.y(), localMax.z())); + checkCorner(QVector3D(localMin.x(), localMax.y(), localMin.z())); + checkCorner(QVector3D(localMax.x(), localMin.y(), localMin.z())); + checkCorner(QVector3D(localMin.x(), localMax.y(), localMax.z())); + checkCorner(QVector3D(localMax.x(), localMax.y(), localMin.z())); + checkCorner(QVector3D(localMax.x(), localMin.y(), localMax.z())); + } + } + } + } + + // Target node and immediate children get selection boxes + if (transform.isIdentity()) { + // Adjust bounds to reduce targetNode pixels obscuring the selection box + QVector3D extents = (maxBounds - minBounds) / 1000.f; + QVector3D minAdjBounds = minBounds - extents; + QVector3D maxAdjBounds = maxBounds + extents; + + appendVertexData(vertexData, indexData, minAdjBounds, maxAdjBounds); + } +} + +void SelectionBoxGeometry::appendVertexData(QByteArray &vertexData, QByteArray &indexData, + const QVector3D &minBounds, const QVector3D &maxBounds) { + int initialVertexSize = vertexData.size(); + int initialIndexSize = indexData.size(); const int vertexSize = int(sizeof(float)) * 8 * 3; // 8 vertices, 3 floats/vert - vertexData.resize(vertexSize); + quint16 indexAdd = quint16(initialVertexSize / 12); + vertexData.resize(initialVertexSize + vertexSize); const int indexSize = int(sizeof(quint16)) * 12 * 2; // 12 lines, 2 vert/line - indexData.resize(indexSize); + indexData.resize(initialIndexSize + indexSize); - auto dataPtr = reinterpret_cast(vertexData.data()); - auto indexPtr = reinterpret_cast(indexData.data()); + auto dataPtr = reinterpret_cast(vertexData.data() + initialVertexSize); + auto indexPtr = reinterpret_cast(indexData.data() + initialIndexSize); *dataPtr++ = maxBounds.x(); *dataPtr++ = maxBounds.y(); *dataPtr++ = maxBounds.z(); *dataPtr++ = minBounds.x(); *dataPtr++ = maxBounds.y(); *dataPtr++ = maxBounds.z(); @@ -194,20 +308,20 @@ void SelectionBoxGeometry::fillVertexData(QByteArray &vertexData, QByteArray &in *dataPtr++ = minBounds.x(); *dataPtr++ = minBounds.y(); *dataPtr++ = minBounds.z(); *dataPtr++ = maxBounds.x(); *dataPtr++ = minBounds.y(); *dataPtr++ = minBounds.z(); - *indexPtr++ = 0; *indexPtr++ = 1; - *indexPtr++ = 1; *indexPtr++ = 2; - *indexPtr++ = 2; *indexPtr++ = 3; - *indexPtr++ = 3; *indexPtr++ = 0; + *indexPtr++ = 0 + indexAdd; *indexPtr++ = 1 + indexAdd; + *indexPtr++ = 1 + indexAdd; *indexPtr++ = 2 + indexAdd; + *indexPtr++ = 2 + indexAdd; *indexPtr++ = 3 + indexAdd; + *indexPtr++ = 3 + indexAdd; *indexPtr++ = 0 + indexAdd; - *indexPtr++ = 0; *indexPtr++ = 4; - *indexPtr++ = 1; *indexPtr++ = 5; - *indexPtr++ = 2; *indexPtr++ = 6; - *indexPtr++ = 3; *indexPtr++ = 7; + *indexPtr++ = 0 + indexAdd; *indexPtr++ = 4 + indexAdd; + *indexPtr++ = 1 + indexAdd; *indexPtr++ = 5 + indexAdd; + *indexPtr++ = 2 + indexAdd; *indexPtr++ = 6 + indexAdd; + *indexPtr++ = 3 + indexAdd; *indexPtr++ = 7 + indexAdd; - *indexPtr++ = 4; *indexPtr++ = 5; - *indexPtr++ = 5; *indexPtr++ = 6; - *indexPtr++ = 6; *indexPtr++ = 7; - *indexPtr++ = 7; *indexPtr++ = 4; + *indexPtr++ = 4 + indexAdd; *indexPtr++ = 5 + indexAdd; + *indexPtr++ = 5 + indexAdd; *indexPtr++ = 6 + indexAdd; + *indexPtr++ = 6 + indexAdd; *indexPtr++ = 7 + indexAdd; + *indexPtr++ = 7 + indexAdd; *indexPtr++ = 4 + indexAdd; } } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.h index afe5efd8cf..ef472a5113 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.h @@ -40,6 +40,7 @@ class SelectionBoxGeometry : public QQuick3DGeometry Q_PROPERTY(QQuick3DNode *targetNode READ targetNode WRITE setTargetNode NOTIFY targetNodeChanged) Q_PROPERTY(QQuick3DNode *rootNode READ rootNode WRITE setRootNode NOTIFY rootNodeChanged) Q_PROPERTY(QQuick3DViewport *view3D READ view3D WRITE setView3D NOTIFY view3DChanged) + Q_PROPERTY(bool isEmpty READ isEmpty NOTIFY isEmptyChanged) public: SelectionBoxGeometry(); @@ -48,6 +49,7 @@ public: QQuick3DNode *targetNode() const; QQuick3DNode *rootNode() const; QQuick3DViewport *view3D() const; + bool isEmpty() const; public Q_SLOTS: void setTargetNode(QQuick3DNode *targetNode); @@ -58,17 +60,22 @@ Q_SIGNALS: void targetNodeChanged(); void rootNodeChanged(); void view3DChanged(); + void isEmptyChanged(); protected: QSSGRenderGraphObject *updateSpatialNode(QSSGRenderGraphObject *node) override; private: - void fillVertexData(QByteArray &vertexData, QByteArray &indexData, - const QVector3D &minBounds, const QVector3D &maxBounds); + void getBounds(QQuick3DNode *node, QByteArray &vertexData, QByteArray &indexData, + QVector3D &minBounds, QVector3D &maxBounds, const QMatrix4x4 &transform); + void appendVertexData(QByteArray &vertexData, QByteArray &indexData, + const QVector3D &minBounds, const QVector3D &maxBounds); QQuick3DNode *m_targetNode = nullptr; QQuick3DViewport *m_view3D = nullptr; QQuick3DNode *m_rootNode = nullptr; + bool m_isEmpty = true; + QVector m_connections; }; } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index d3747ab3b3..95360eaa4f 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -340,12 +340,24 @@ QObject *Qt5InformationNodeInstanceServer::findRootNodeOf3DViewport( { for (const ServerNodeInstance &instance : instanceList) { if (instance.isSubclassOf("QQuick3DViewport")) { + QObject *rootObj = nullptr; + int viewChildCount = 0; for (const ServerNodeInstance &child : instanceList) { /* Look for scene node */ /* The QQuick3DViewport always creates a root node. * This root node contains the complete scene. */ - if (child.isSubclassOf("QQuick3DNode") && child.parent() == instance) - return child.internalObject()->property("parent").value(); + if (child.isSubclassOf("QQuick3DNode") && child.parent() == instance) { + // Implicit root node is not visible in editor, so there is often another node + // added below it that serves as the actual scene root node. + // If the found root is the only node child of the view, assume that is the case. + ++viewChildCount; + if (!rootObj) + rootObj = child.internalObject(); + } } + if (viewChildCount == 1) + return rootObj; + else if (rootObj) + return rootObj->property("parent").value(); } } return nullptr; @@ -603,10 +615,8 @@ void Qt5InformationNodeInstanceServer::changeSelection(const ChangeSelectionComm if (hasInstanceForId(id)) { ServerNodeInstance instance = instanceForId(id); QObject *object = nullptr; - if (instance.isSubclassOf("QQuick3DModel") || instance.isSubclassOf("QQuick3DCamera") - || instance.isSubclassOf("QQuick3DAbstractLight")) { + if (instance.isSubclassOf("QQuick3DNode")) object = instance.internalObject(); - } QMetaObject::invokeMethod(m_editView3D, "selectObject", Q_ARG(QVariant, objectToVariant(object))); return; // TODO: support multi-selection -- cgit v1.2.1