diff options
Diffstat (limited to 'src/tools/qml2puppet/qml2puppet/editor3d/selectionboxgeometry.cpp')
-rw-r--r-- | src/tools/qml2puppet/qml2puppet/editor3d/selectionboxgeometry.cpp | 427 |
1 files changed, 427 insertions, 0 deletions
diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/selectionboxgeometry.cpp b/src/tools/qml2puppet/qml2puppet/editor3d/selectionboxgeometry.cpp new file mode 100644 index 0000000000..1198047848 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/editor3d/selectionboxgeometry.cpp @@ -0,0 +1,427 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#ifdef QUICK3D_MODULE + +#include "selectionboxgeometry.h" + +#include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h> +#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h> +#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h> +#include <QtQuick3D/private/qquick3dmodel_p.h> +#include <QtQuick3D/private/qquick3dscenemanager_p.h> +#include <QtQuick3D/qquick3dobject.h> +#include <QtQuick/qquickwindow.h> +#include <QtCore/qvector.h> + +#include <limits> + +namespace QmlDesigner { +namespace Internal { + +static const float floatMin = std::numeric_limits<float>::lowest(); +static const float floatMax = std::numeric_limits<float>::max(); +static const QVector3D maxVec = QVector3D(floatMax, floatMax, floatMax); +static const QVector3D minVec = QVector3D(floatMin, floatMin, floatMin); + +SelectionBoxGeometry::SelectionBoxGeometry() + : GeometryBase() +{ +} + +SelectionBoxGeometry::~SelectionBoxGeometry() +{ + for (auto &connection : std::as_const(m_connections)) + QObject::disconnect(connection); + m_connections.clear(); +} + +QQuick3DNode *SelectionBoxGeometry::targetNode() const +{ + return m_targetNode; +} + +QQuick3DNode *SelectionBoxGeometry::rootNode() const +{ + return m_rootNode; +} + +QQuick3DViewport *SelectionBoxGeometry::view3D() const +{ + return m_view3D; +} + +bool QmlDesigner::Internal::SelectionBoxGeometry::isEmpty() const +{ + return m_isEmpty; +} + +void SelectionBoxGeometry::setEmpty(bool isEmpty) +{ + if (m_isEmpty != isEmpty) { + m_isEmpty = isEmpty; + emit isEmptyChanged(); + } +} + +QSSGBounds3 SelectionBoxGeometry::bounds() const +{ + return m_bounds; +} + +void SelectionBoxGeometry::clearGeometry() +{ + clear(); + setStride(12); // To avoid div by zero inside QtQuick3D + setEmpty(true); +} + +void SelectionBoxGeometry::setTargetNode(QQuick3DNode *targetNode) +{ + if (m_targetNode == targetNode) + return; + + if (m_targetNode) + m_targetNode->disconnect(this); + m_targetNode = targetNode; + + if (auto model = qobject_cast<QQuick3DModel *>(m_targetNode)) { + QObject::connect(model, &QQuick3DModel::sourceChanged, + this, &SelectionBoxGeometry::spatialNodeUpdateNeeded, Qt::QueuedConnection); + QObject::connect(model, &QQuick3DModel::geometryChanged, + this, &SelectionBoxGeometry::spatialNodeUpdateNeeded, Qt::QueuedConnection); + } + if (m_targetNode) { + QObject::connect(m_targetNode, &QQuick3DNode::parentChanged, + this, &SelectionBoxGeometry::spatialNodeUpdateNeeded, Qt::QueuedConnection); + } + + clearGeometry(); + emit targetNodeChanged(); + spatialNodeUpdateNeeded(); +} + +void SelectionBoxGeometry::setRootNode(QQuick3DNode *rootNode) +{ + if (m_rootNode == rootNode) + return; + + m_rootNode = rootNode; + + emit rootNodeChanged(); + spatialNodeUpdateNeeded(); +} + +void SelectionBoxGeometry::setView3D(QQuick3DViewport *view) +{ + if (m_view3D == view) + return; + + m_view3D = view; + + emit view3DChanged(); + spatialNodeUpdateNeeded(); +} + +QSSGRenderGraphObject *SelectionBoxGeometry::updateSpatialNode(QSSGRenderGraphObject *node) +{ + + if (m_spatialNodeUpdatePending) { + m_spatialNodeUpdatePending = false; + updateGeometry(); + } + + return QQuick3DGeometry::updateSpatialNode(node); +} + +void SelectionBoxGeometry::doUpdateGeometry() +{ + // Some changes require a frame to be rendered for us to be able to calculate geometry, + // so defer calculations until after next frame. + if (m_spatialNodeUpdatePending) { + update(); + return; + } + + GeometryBase::doUpdateGeometry(); + + for (auto &connection : std::as_const(m_connections)) + QObject::disconnect(connection); + m_connections.clear(); + + QByteArray vertexData; + QByteArray indexData; + + QVector3D minBounds = maxVec; + QVector3D maxBounds = minVec; + + if (m_targetNode) { + auto rootPriv = QQuick3DObjectPrivate::get(m_rootNode); + auto targetPriv = QQuick3DObjectPrivate::get(m_targetNode); + auto rootRN = static_cast<QSSGRenderNode *>(rootPriv->spatialNode); + auto targetRN = static_cast<QSSGRenderNode *>(targetPriv->spatialNode); + if (rootRN && targetRN) { + // Explicitly set local transform of root node to target node parent's global transform + // to avoid having to reparent the selection box. This has to be done directly on render + // nodes. + QMatrix4x4 m; + if (targetRN->parent) { + targetRN->parent->calculateGlobalVariables(); + m = targetRN->parent->globalTransform; + } + rootRN->localTransform = m; +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) + rootRN->markDirty(QSSGRenderNode::TransformDirtyFlag::TransformNotDirty); +#else + rootRN->markDirty(QSSGRenderNode::DirtyFlag::TransformDirty); +#endif + rootRN->calculateGlobalVariables(); + } else if (!m_spatialNodeUpdatePending) { + // Necessary spatial nodes do not yet exist. Defer selection box creation one frame. + m_spatialNodeUpdatePending = true; + update(); + } + getBounds(m_targetNode, vertexData, indexData, minBounds, maxBounds); + generateVertexData(vertexData, indexData, minBounds, maxBounds); + + // Track changes in ancestors, as they can move node without affecting node properties + auto parentNode = m_targetNode->parentNode(); + while (parentNode) { + trackNodeChanges(parentNode); + parentNode = parentNode->parentNode(); + } + } else { + // Fill some dummy data so geometry won't get rejected + minBounds = {}; + maxBounds = {}; + generateVertexData(vertexData, indexData, minBounds, maxBounds); + } + + addAttribute(QQuick3DGeometry::Attribute::IndexSemantic, 0, + QQuick3DGeometry::Attribute::U16Type); + setVertexData(vertexData); + setIndexData(indexData); + setBounds(minBounds, maxBounds); + + m_bounds = QSSGBounds3(minBounds, maxBounds); + + setEmpty(minBounds.isNull() && maxBounds.isNull()); +} + +void SelectionBoxGeometry::getBounds( + QQuick3DNode *node, QByteArray &vertexData, QByteArray &indexData, + QVector3D &minBounds, QVector3D &maxBounds) +{ + QMatrix4x4 localTransform; + auto nodePriv = QQuick3DObjectPrivate::get(node); + auto renderNode = static_cast<QSSGRenderNode *>(nodePriv->spatialNode); + + if (node != m_targetNode) { + if (renderNode) { +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) + if (renderNode->flags.testFlag(QSSGRenderNode::Flag::TransformDirty)) + renderNode->calculateLocalTransform(); +#else + if (renderNode->isDirty(QSSGRenderNode::DirtyFlag::TransformDirty)) { + renderNode->localTransform = QSSGRenderNode::calculateTransformMatrix( + node->position(), node->scale(), node->pivot(), node->rotation()); + } +#endif + localTransform = renderNode->localTransform; + } + trackNodeChanges(node); + } + + QVector3D localMinBounds = maxVec; + QVector3D localMaxBounds = minVec; + + // Find bounds for children + QVector<QVector3D> minBoundsVec; + QVector<QVector3D> maxBoundsVec; + const auto children = node->childItems(); + for (const auto child : children) { + if (auto childNode = qobject_cast<QQuick3DNode *>(child)) { + QVector3D newMinBounds = minBounds; + QVector3D newMaxBounds = maxBounds; + getBounds(childNode, vertexData, indexData, newMinBounds, newMaxBounds); + minBoundsVec << newMinBounds; + maxBoundsVec << newMaxBounds; + } + } + + auto combineMinBounds = [](QVector3D &target, const QVector3D &source) { + target.setX(qMin(source.x(), target.x())); + target.setY(qMin(source.y(), target.y())); + target.setZ(qMin(source.z(), target.z())); + }; + auto combineMaxBounds = [](QVector3D &target, const QVector3D &source) { + target.setX(qMax(source.x(), target.x())); + target.setY(qMax(source.y(), target.y())); + target.setZ(qMax(source.z(), target.z())); + }; + auto transformCorner = [&](const QMatrix4x4 &m, QVector3D &minTarget, QVector3D &maxTarget, + const QVector3D &corner) { + QVector3D mappedCorner = m.map(corner); + combineMinBounds(minTarget, mappedCorner); + combineMaxBounds(maxTarget, mappedCorner); + }; + auto transformCorners = [&](const QMatrix4x4 &m, QVector3D &minTarget, QVector3D &maxTarget, + const QVector3D &minCorner, const QVector3D &maxCorner) { + transformCorner(m, minTarget, maxTarget, minCorner); + transformCorner(m, minTarget, maxTarget, maxCorner); + transformCorner(m, minTarget, maxTarget, QVector3D(minCorner.x(), minCorner.y(), maxCorner.z())); + transformCorner(m, minTarget, maxTarget, QVector3D(minCorner.x(), maxCorner.y(), minCorner.z())); + transformCorner(m, minTarget, maxTarget, QVector3D(maxCorner.x(), minCorner.y(), minCorner.z())); + transformCorner(m, minTarget, maxTarget, QVector3D(minCorner.x(), maxCorner.y(), maxCorner.z())); + transformCorner(m, minTarget, maxTarget, QVector3D(maxCorner.x(), maxCorner.y(), minCorner.z())); + transformCorner(m, minTarget, maxTarget, QVector3D(maxCorner.x(), minCorner.y(), maxCorner.z())); + }; + + // Combine all child bounds + for (const auto &newBounds : std::as_const(minBoundsVec)) + combineMinBounds(localMinBounds, newBounds); + for (const auto &newBounds : std::as_const(maxBoundsVec)) + combineMaxBounds(localMaxBounds, newBounds); + + if (qobject_cast<QQuick3DModel *>(node)) { + if (auto renderModel = static_cast<QSSGRenderModel *>(renderNode)) { + QWindow *window = static_cast<QWindow *>(m_view3D->window()); + if (window) { + QSSGRef<QSSGRenderContextInterface> context; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + context = QSSGRenderContextInterface::getRenderContextInterface(quintptr(window)); +#else + context = QQuick3DObjectPrivate::get(this)->sceneManager->rci; +#endif + if (!context.isNull()) { + auto bufferManager = context->bufferManager(); +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) + QSSGBounds3 bounds = renderModel->getModelBounds(bufferManager); +#else + QSSGBounds3 bounds = bufferManager->getModelBounds(renderModel); +#endif + QVector3D center = bounds.center(); + QVector3D extents = bounds.extents(); + QVector3D localMin = center - extents; + QVector3D localMax = center + extents; + + combineMinBounds(localMinBounds, localMin); + combineMaxBounds(localMaxBounds, localMax); + } + } + } + } else { + combineMinBounds(localMinBounds, {}); + combineMaxBounds(localMaxBounds, {}); + } + + if (localMaxBounds == minVec) { + localMinBounds = {}; + localMaxBounds = {}; + } + + // Transform local space bounding box to parent space + transformCorners(localTransform, minBounds, maxBounds, localMinBounds, localMaxBounds); +} + +void SelectionBoxGeometry::generateVertexData(QByteArray &vertexData, QByteArray &indexData, + const QVector3D &minBounds, const QVector3D &maxBounds) +{ + // Adjust bounds to reduce targetNode pixels obscuring the selection box + QVector3D extents = (maxBounds - minBounds) / 1000.f; + QVector3D minAdjBounds = minBounds - extents; + QVector3D maxAdjBounds = maxBounds + extents; + + // Selection box has 8 corners with three short lines towards other corners on each corner + const int vertexSize = int(sizeof(float)) * 8 * 4 * 3; // 8 corners, 4 verts/corner, 3 floats/vert + vertexData.resize(vertexSize); + const int indexSize = int(sizeof(quint16)) * 8 * 3 * 2; // 8 * 3 lines, 2 vert/line + indexData.resize(indexSize); + + auto dataPtr = reinterpret_cast<float *>(vertexData.data()); + auto indexPtr = reinterpret_cast<quint16 *>(indexData.data()); + + QVector3D corners[8]; + corners[0] = QVector3D(maxAdjBounds.x(), maxAdjBounds.y(), maxAdjBounds.z()); + corners[1] = QVector3D(minAdjBounds.x(), maxAdjBounds.y(), maxAdjBounds.z()); + corners[2] = QVector3D(minAdjBounds.x(), minAdjBounds.y(), maxAdjBounds.z()); + corners[3] = QVector3D(maxAdjBounds.x(), minAdjBounds.y(), maxAdjBounds.z()); + corners[4] = QVector3D(maxAdjBounds.x(), maxAdjBounds.y(), minAdjBounds.z()); + corners[5] = QVector3D(minAdjBounds.x(), maxAdjBounds.y(), minAdjBounds.z()); + corners[6] = QVector3D(minAdjBounds.x(), minAdjBounds.y(), minAdjBounds.z()); + corners[7] = QVector3D(maxAdjBounds.x(), minAdjBounds.y(), minAdjBounds.z()); + + for (int i = 0; i < 8; ++i) { + *dataPtr++ = corners[i].x(); + *dataPtr++ = corners[i].y(); + *dataPtr++ = corners[i].z(); + } + + // Percentage of full box lines to show at each corner. Set to .5 for full box + static const float lineLen = .15f; + + int nextVertIdx = 8; + + // Add line from corner 'start', towards corner 'end' + auto addCornerLine = [&](int start, int end) { + QVector3D vert = corners[start] + lineLen * (corners[end] - corners[start]); + *dataPtr++ = vert.x(); *dataPtr++ = vert.y(); *dataPtr++ = vert.z(); + *indexPtr++ = start; *indexPtr++ = nextVertIdx++; + }; + + addCornerLine(0, 1); + addCornerLine(0, 3); + addCornerLine(0, 4); + + addCornerLine(1, 0); + addCornerLine(1, 2); + addCornerLine(1, 5); + + addCornerLine(2, 1); + addCornerLine(2, 3); + addCornerLine(2, 6); + + addCornerLine(3, 0); + addCornerLine(3, 2); + addCornerLine(3, 7); + + addCornerLine(4, 0); + addCornerLine(4, 5); + addCornerLine(4, 7); + + addCornerLine(5, 1); + addCornerLine(5, 4); + addCornerLine(5, 6); + + addCornerLine(6, 2); + addCornerLine(6, 5); + addCornerLine(6, 7); + + addCornerLine(7, 3); + addCornerLine(7, 4); + addCornerLine(7, 6); +} + +void SelectionBoxGeometry::trackNodeChanges(QQuick3DNode *node) +{ + m_connections << QObject::connect(node, &QQuick3DNode::sceneScaleChanged, + this, &SelectionBoxGeometry::updateGeometry, Qt::QueuedConnection); + m_connections << QObject::connect(node, &QQuick3DNode::sceneRotationChanged, + this, &SelectionBoxGeometry::updateGeometry, Qt::QueuedConnection); + m_connections << QObject::connect(node, &QQuick3DNode::scenePositionChanged, + this, &SelectionBoxGeometry::updateGeometry, Qt::QueuedConnection); + m_connections << QObject::connect(node, &QQuick3DNode::pivotChanged, + this, &SelectionBoxGeometry::updateGeometry, Qt::QueuedConnection); +} + +void SelectionBoxGeometry::spatialNodeUpdateNeeded() +{ + m_spatialNodeUpdatePending = true; + clearGeometry(); + update(); +} + +} +} + +#endif // QUICK3D_MODULE |