From e3fbebe61111dfe670ffe19c96e313157df7331f Mon Sep 17 00:00:00 2001 From: Paul Lemire Date: Mon, 10 Dec 2018 10:17:25 +0100 Subject: QObjectPicker and QPickingSettings NearestPriorityPick picking mode Add a new priority property on QObjectPicker and a new QPickingSettings mode. This will select the result with the highest priority if there are several results on a given ray. If we have several results with identical properties, the result with the closest distance is selected. [ChangeLog][Qt3DRender] QObjectPicker: add a priority based result selection Change-Id: I7af12db6e163c3c2d9dad696e6d9f9bbbee064ed Reviewed-by: Mike Krus --- src/render/frontend/qpickingsettings.cpp | 8 ++ src/render/frontend/qpickingsettings.h | 3 +- src/render/jobs/pickboundingvolumejob.cpp | 16 ++- src/render/jobs/pickboundingvolumeutils.cpp | 132 ++++++++++++++--- src/render/jobs/pickboundingvolumeutils_p.h | 14 +- src/render/jobs/raycastingjob.cpp | 6 +- src/render/picking/objectpicker.cpp | 15 ++ src/render/picking/objectpicker_p.h | 5 + src/render/picking/qobjectpicker.cpp | 37 +++++ src/render/picking/qobjectpicker.h | 5 + src/render/picking/qobjectpicker_p.h | 3 + .../auto/render/objectpicker/tst_objectpicker.cpp | 36 +++++ .../pickboundingvolumejob.qrc | 1 + .../testscene_priorityoverlapping.qml | 135 +++++++++++++++++ .../tst_pickboundingvolumejob.cpp | 105 ++++++++++++++ .../render/qobjectpicker/tst_qobjectpicker.cpp | 160 ++++++++++++++++++++- 16 files changed, 648 insertions(+), 33 deletions(-) create mode 100644 tests/auto/render/pickboundingvolumejob/testscene_priorityoverlapping.qml diff --git a/src/render/frontend/qpickingsettings.cpp b/src/render/frontend/qpickingsettings.cpp index 66d3fc912..84e61e141 100644 --- a/src/render/frontend/qpickingsettings.cpp +++ b/src/render/frontend/qpickingsettings.cpp @@ -199,6 +199,9 @@ void QPickingSettings::setPickMethod(QPickingSettings::PickMethod pickMethod) * \value NearestPick Only the nearest entity to picking ray origin intersected by the picking ray * is picked (default). * \value AllPicks All entities that intersect the picking ray are picked. + * \value PriorityPick Selects the entity whose object picker has the highest + * value. If several object pickers have the same priority, the closest one on + * the ray is selected. * * \sa Qt3DRender::QPickEvent */ @@ -211,6 +214,7 @@ void QPickingSettings::setPickMethod(QPickingSettings::PickMethod pickMethod) \list \li PickingSettings.NearestPick \li PickingSettings.AllPicks + \li PickingSettings.NearestPriorityPick \endlist \sa Qt3DRender::QPickingSettings::PickResultMode @@ -225,6 +229,10 @@ void QPickingSettings::setPickMethod(QPickingSettings::PickMethod pickMethod) When setting the pick method to AllPicks, events will be triggered for all the entities with a QObjectPicker along the ray. + When setting the pick method to NearestPriorityPick, events will be + triggered for the nearest highest priority picker. This can be used when a + given element should always be selected even if others are in front of it. + If a QObjectPicker is assigned to an entity with multiple children, an event will be triggered for each child entity that intersects the ray. */ diff --git a/src/render/frontend/qpickingsettings.h b/src/render/frontend/qpickingsettings.h index 9c8a2c856..741f918b0 100644 --- a/src/render/frontend/qpickingsettings.h +++ b/src/render/frontend/qpickingsettings.h @@ -73,7 +73,8 @@ public: enum PickResultMode { NearestPick, - AllPicks + AllPicks, + NearestPriorityPick }; Q_ENUM(PickResultMode) // LCOV_EXCL_LINE diff --git a/src/render/jobs/pickboundingvolumejob.cpp b/src/render/jobs/pickboundingvolumejob.cpp index 2050b8772..96ec11b4e 100644 --- a/src/render/jobs/pickboundingvolumejob.cpp +++ b/src/render/jobs/pickboundingvolumejob.cpp @@ -211,7 +211,6 @@ bool PickBoundingVolumeJob::runHelper() const bool edgePickingRequested = (m_renderSettings->pickMethod() & QPickingSettings::LinePicking); const bool pointPickingRequested = (m_renderSettings->pickMethod() & QPickingSettings::PointPicking); const bool primitivePickingRequested = pointPickingRequested | edgePickingRequested | trianglePickingRequested; - const bool allHitsRequested = (m_renderSettings->pickResultMode() == QPickingSettings::AllPicks); const bool frontFaceRequested = m_renderSettings->faceOrientationPickingMode() != QPickingSettings::BackFace; const bool backFaceRequested = @@ -237,7 +236,7 @@ bool PickBoundingVolumeJob::runHelper() // has moved out of the viewport In case of a button released // outside of the viewport, we still want to notify the // lastCurrent entity about this. - dispatchPickEvents(event.second, PickingUtils::HitList(), eventButton, eventButtons, eventModifiers, allHitsRequested); + dispatchPickEvents(event.second, PickingUtils::HitList(), eventButton, eventButtons, eventModifiers, m_renderSettings->pickResultMode()); continue; } @@ -249,14 +248,16 @@ bool PickBoundingVolumeJob::runHelper() gathererFunctor.m_backFaceRequested = backFaceRequested; gathererFunctor.m_manager = m_manager; gathererFunctor.m_ray = ray; - sphereHits << gathererFunctor.computeHits(entityPicker.entities(), allHitsRequested); + gathererFunctor.m_entityToPriorityTable = entityPicker.entityToPriorityTable(); + sphereHits << gathererFunctor.computeHits(entityPicker.entities(), m_renderSettings->pickResultMode()); } if (edgePickingRequested) { PickingUtils::LineCollisionGathererFunctor gathererFunctor; gathererFunctor.m_manager = m_manager; gathererFunctor.m_ray = ray; gathererFunctor.m_pickWorldSpaceTolerance = pickWorldSpaceTolerance; - sphereHits << gathererFunctor.computeHits(entityPicker.entities(), allHitsRequested); + gathererFunctor.m_entityToPriorityTable = entityPicker.entityToPriorityTable(); + sphereHits << gathererFunctor.computeHits(entityPicker.entities(), m_renderSettings->pickResultMode()); PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits); } if (pointPickingRequested) { @@ -264,19 +265,20 @@ bool PickBoundingVolumeJob::runHelper() gathererFunctor.m_manager = m_manager; gathererFunctor.m_ray = ray; gathererFunctor.m_pickWorldSpaceTolerance = pickWorldSpaceTolerance; - sphereHits << gathererFunctor.computeHits(entityPicker.entities(), allHitsRequested); + gathererFunctor.m_entityToPriorityTable = entityPicker.entityToPriorityTable(); + sphereHits << gathererFunctor.computeHits(entityPicker.entities(), m_renderSettings->pickResultMode()); PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits); } if (!primitivePickingRequested) { sphereHits << entityPicker.hits(); PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits); - if (!allHitsRequested) + if (m_renderSettings->pickResultMode() != QPickingSettings::AllPicks) sphereHits = { sphereHits.front() }; } } // Dispatch events based on hit results - dispatchPickEvents(event.second, sphereHits, eventButton, eventButtons, eventModifiers, allHitsRequested); + dispatchPickEvents(event.second, sphereHits, eventButton, eventButtons, eventModifiers, m_renderSettings->pickResultMode()); } } diff --git a/src/render/jobs/pickboundingvolumeutils.cpp b/src/render/jobs/pickboundingvolumeutils.cpp index 5fed946d6..23e495ecb 100644 --- a/src/render/jobs/pickboundingvolumeutils.cpp +++ b/src/render/jobs/pickboundingvolumeutils.cpp @@ -55,6 +55,7 @@ #include #include +#include QT_BEGIN_NAMESPACE @@ -418,6 +419,46 @@ HitList reduceToFirstHit(HitList &result, const HitList &intermediate) return result; } + +struct HighestPriorityHitReducer +{ + // No need to protect this from concurrent access as the table + // is read only + const QHash entityToPriorityTable; + + HitList operator()(HitList &result, const HitList &intermediate) + { + // Sort by priority first + // If we have equal priorities, we then sort by distance + + if (!intermediate.empty()) { + if (result.empty()) + result.push_back(intermediate.front()); + int currentPriority = entityToPriorityTable.value(result.front().m_entityId, 0); + float closest = result.front().m_distance; + + for (const auto &v : intermediate) { + const int newEntryPriority = entityToPriorityTable.value(v.m_entityId, 0); + if (newEntryPriority > currentPriority) { + result.push_front(v); + currentPriority = newEntryPriority; + closest = v.m_distance; + } else if (newEntryPriority == currentPriority) { + if (v.m_distance < closest) { + result.push_front(v); + closest = v.m_distance; + currentPriority = newEntryPriority; + } + } + } + + while (result.size() > 1) + result.pop_back(); + } + return result; + } +}; + HitList reduceToAllHits(HitList &results, const HitList &intermediate) { if (!intermediate.empty()) @@ -492,9 +533,22 @@ struct MapFunctorHolder } // anonymous -HitList EntityCollisionGathererFunctor::computeHits(const QVector &entities, bool allHitsRequested) -{ - const auto reducerOp = allHitsRequested ? PickingUtils::reduceToAllHits : PickingUtils::reduceToFirstHit; +HitList EntityCollisionGathererFunctor::computeHits(const QVector &entities, + Qt3DRender::QPickingSettings::PickResultMode mode) +{ + std::function reducerOp; + switch (mode) { + case QPickingSettings::AllPicks: + reducerOp = PickingUtils::reduceToAllHits; + break; + case QPickingSettings::NearestPriorityPick: + reducerOp = HighestPriorityHitReducer{ m_entityToPriorityTable }; + break; + case QPickingSettings::NearestPick: + reducerOp = PickingUtils::reduceToFirstHit; + break; + } + const MapFunctorHolder holder(this); #if QT_CONFIG(concurrent) return QtConcurrent::blockingMappedReduced(entities, holder, reducerOp); @@ -519,9 +573,22 @@ HitList EntityCollisionGathererFunctor::pick(const Entity *entity) const return result; } -HitList TriangleCollisionGathererFunctor::computeHits(const QVector &entities, bool allHitsRequested) -{ - const auto reducerOp = allHitsRequested ? PickingUtils::reduceToAllHits : PickingUtils::reduceToFirstHit; +HitList TriangleCollisionGathererFunctor::computeHits(const QVector &entities, + Qt3DRender::QPickingSettings::PickResultMode mode) +{ + std::function reducerOp; + switch (mode) { + case QPickingSettings::AllPicks: + reducerOp = PickingUtils::reduceToAllHits; + break; + case QPickingSettings::NearestPriorityPick: + reducerOp = HighestPriorityHitReducer { m_entityToPriorityTable }; + break; + case QPickingSettings::NearestPick: + reducerOp = PickingUtils::reduceToFirstHit; + break; + } + const MapFunctorHolder holder(this); #if QT_CONFIG(concurrent) return QtConcurrent::blockingMappedReduced(entities, holder, reducerOp); @@ -553,9 +620,22 @@ HitList TriangleCollisionGathererFunctor::pick(const Entity *entity) const return result; } -HitList LineCollisionGathererFunctor::computeHits(const QVector &entities, bool allHitsRequested) -{ - const auto reducerOp = allHitsRequested ? PickingUtils::reduceToAllHits : PickingUtils::reduceToFirstHit; +HitList LineCollisionGathererFunctor::computeHits(const QVector &entities, + Qt3DRender::QPickingSettings::PickResultMode mode) +{ + std::function reducerOp; + switch (mode) { + case QPickingSettings::AllPicks: + reducerOp = PickingUtils::reduceToAllHits; + break; + case QPickingSettings::NearestPriorityPick: + reducerOp = HighestPriorityHitReducer { m_entityToPriorityTable }; + break; + case QPickingSettings::NearestPick: + reducerOp = PickingUtils::reduceToFirstHit; + break; + } + const MapFunctorHolder holder(this); #if QT_CONFIG(concurrent) return QtConcurrent::blockingMappedReduced(entities, holder, reducerOp); @@ -586,9 +666,22 @@ HitList LineCollisionGathererFunctor::pick(const Entity *entity) const return result; } -HitList PointCollisionGathererFunctor::computeHits(const QVector &entities, bool allHitsRequested) -{ - const auto reducerOp = allHitsRequested ? PickingUtils::reduceToAllHits : PickingUtils::reduceToFirstHit; +HitList PointCollisionGathererFunctor::computeHits(const QVector &entities, + Qt3DRender::QPickingSettings::PickResultMode mode) +{ + std::function reducerOp; + switch (mode) { + case QPickingSettings::AllPicks: + reducerOp = PickingUtils::reduceToAllHits; + break; + case QPickingSettings::NearestPriorityPick: + reducerOp = HighestPriorityHitReducer { m_entityToPriorityTable }; + break; + case QPickingSettings::NearestPick: + reducerOp = PickingUtils::reduceToFirstHit; + break; + } + const MapFunctorHolder holder(this); #if QT_CONFIG(concurrent) return QtConcurrent::blockingMappedReduced(entities, holder, reducerOp); @@ -641,15 +734,17 @@ bool HierarchicalEntityPicker::collectHits(NodeManagers *manager, Entity *root) { m_hits.clear(); m_entities.clear(); + m_entityToPriorityTable.clear(); QRayCastingService rayCasting; struct EntityData { Entity* entity; bool hasObjectPicker; Qt3DCore::QNodeIdVector recursiveLayers; + int priority; }; std::vector worklist; - worklist.push_back({root, !root->componentHandle().isNull(), {}}); + worklist.push_back({root, !root->componentHandle().isNull(), {}, 0}); LayerManager *layerManager = manager->layerManager(); @@ -710,6 +805,8 @@ bool HierarchicalEntityPicker::collectHits(NodeManagers *manager, Entity *root) if (accepted && queryResult.m_distance >= 0.f && (current.hasObjectPicker || !m_objectPickersRequired)) { m_entities.push_back(current.entity); m_hits.push_back(queryResult); + // Record entry for entity/priority + m_entityToPriorityTable.insert(current.entity->peerId(), current.priority); } Qt3DCore::QNodeIdVector recursiveLayers; @@ -722,9 +819,12 @@ bool HierarchicalEntityPicker::collectHits(NodeManagers *manager, Entity *root) // and pick children const auto children = current.entity->children(); - for (auto child: children) - worklist.push_back({child, current.hasObjectPicker || !child->componentHandle().isNull(), - current.recursiveLayers + recursiveLayers}); + for (Entity *child: children) { + ObjectPicker *childPicker = child->renderComponent(); + worklist.push_back({child, current.hasObjectPicker || childPicker, + current.recursiveLayers + recursiveLayers, + (childPicker ? childPicker->priority() : current.priority)}); + } } return !m_hits.empty(); diff --git a/src/render/jobs/pickboundingvolumeutils_p.h b/src/render/jobs/pickboundingvolumeutils_p.h index 780c16cc8..3fc4517c3 100644 --- a/src/render/jobs/pickboundingvolumeutils_p.h +++ b/src/render/jobs/pickboundingvolumeutils_p.h @@ -55,6 +55,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -124,6 +125,7 @@ public: bool collectHits(NodeManagers *manager, Entity *root); inline HitList hits() const { return m_hits; } inline QVector entities() const { return m_entities; } + inline QHash entityToPriorityTable() const { return m_entityToPriorityTable; } private: RayCasting::QRay3D m_ray; @@ -132,6 +134,7 @@ private: bool m_objectPickersRequired; Qt3DCore::QNodeIdVector m_layerIds; QAbstractRayCaster::FilterMode m_filterMode; + QHash m_entityToPriorityTable; }; struct Q_AUTOTEST_EXPORT AbstractCollisionGathererFunctor @@ -142,8 +145,9 @@ struct Q_AUTOTEST_EXPORT AbstractCollisionGathererFunctor bool m_objectPickersRequired = true; NodeManagers *m_manager = nullptr; RayCasting::QRay3D m_ray; + QHash m_entityToPriorityTable; - virtual HitList computeHits(const QVector &entities, bool allHitsRequested) = 0; + virtual HitList computeHits(const QVector &entities, Qt3DRender::QPickingSettings::PickResultMode mode) = 0; // This define is required to work with QtConcurrent typedef HitList result_type; @@ -156,7 +160,7 @@ struct Q_AUTOTEST_EXPORT AbstractCollisionGathererFunctor struct Q_AUTOTEST_EXPORT EntityCollisionGathererFunctor : public AbstractCollisionGathererFunctor { - HitList computeHits(const QVector &entities, bool allHitsRequested) override; + HitList computeHits(const QVector &entities, Qt3DRender::QPickingSettings::PickResultMode mode) override; HitList pick(const Entity *entity) const override; }; @@ -165,7 +169,7 @@ struct Q_AUTOTEST_EXPORT TriangleCollisionGathererFunctor : public AbstractColli bool m_frontFaceRequested; bool m_backFaceRequested; - HitList computeHits(const QVector &entities, bool allHitsRequested) override; + HitList computeHits(const QVector &entities, Qt3DRender::QPickingSettings::PickResultMode mode) override; HitList pick(const Entity *entity) const override; }; @@ -173,7 +177,7 @@ struct Q_AUTOTEST_EXPORT LineCollisionGathererFunctor : public AbstractCollision { float m_pickWorldSpaceTolerance; - HitList computeHits(const QVector &entities, bool allHitsRequested) override; + HitList computeHits(const QVector &entities, Qt3DRender::QPickingSettings::PickResultMode mode) override; HitList pick(const Entity *entity) const override; }; @@ -181,7 +185,7 @@ struct Q_AUTOTEST_EXPORT PointCollisionGathererFunctor : public AbstractCollisio { float m_pickWorldSpaceTolerance; - HitList computeHits(const QVector &entities, bool allHitsRequested) override; + HitList computeHits(const QVector &entities, Qt3DRender::QPickingSettings::PickResultMode mode) override; HitList pick(const Entity *entity) const override; }; diff --git a/src/render/jobs/raycastingjob.cpp b/src/render/jobs/raycastingjob.cpp index e76b9fe8d..70c7ac374 100644 --- a/src/render/jobs/raycastingjob.cpp +++ b/src/render/jobs/raycastingjob.cpp @@ -183,7 +183,7 @@ bool RayCastingJob::runHelper() gathererFunctor.m_manager = m_manager; gathererFunctor.m_ray = ray; gathererFunctor.m_objectPickersRequired = false; - sphereHits << gathererFunctor.computeHits(entityPicker.entities(), true); + sphereHits << gathererFunctor.computeHits(entityPicker.entities(), QPickingSettings::AllPicks); } if (edgePickingRequested) { PickingUtils::LineCollisionGathererFunctor gathererFunctor; @@ -191,7 +191,7 @@ bool RayCastingJob::runHelper() gathererFunctor.m_ray = ray; gathererFunctor.m_pickWorldSpaceTolerance = pickWorldSpaceTolerance; gathererFunctor.m_objectPickersRequired = false; - sphereHits << gathererFunctor.computeHits(entityPicker.entities(), true); + sphereHits << gathererFunctor.computeHits(entityPicker.entities(), QPickingSettings::AllPicks); PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits); } if (pointPickingRequested) { @@ -200,7 +200,7 @@ bool RayCastingJob::runHelper() gathererFunctor.m_ray = ray; gathererFunctor.m_pickWorldSpaceTolerance = pickWorldSpaceTolerance; gathererFunctor.m_objectPickersRequired = false; - sphereHits << gathererFunctor.computeHits(entityPicker.entities(), true); + sphereHits << gathererFunctor.computeHits(entityPicker.entities(), QPickingSettings::AllPicks); PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits); } if (!primitivePickingRequested) { diff --git a/src/render/picking/objectpicker.cpp b/src/render/picking/objectpicker.cpp index 76f00993c..43e308d20 100644 --- a/src/render/picking/objectpicker.cpp +++ b/src/render/picking/objectpicker.cpp @@ -53,6 +53,7 @@ namespace Render { ObjectPicker::ObjectPicker() : BackendNode(QBackendNode::ReadWrite) + , m_priority(0) , m_isPressed(false) , m_hoverEnabled(false) , m_dragEnabled(false) @@ -70,6 +71,7 @@ void ObjectPicker::cleanup() m_isPressed = false; m_hoverEnabled = false; m_dragEnabled = false; + m_priority = 0; notifyJob(); } @@ -79,6 +81,7 @@ void ObjectPicker::initializeFromPeer(const Qt3DCore::QNodeCreatedChangeBasePtr const auto &data = typedChange->data; m_hoverEnabled = data.hoverEnabled; m_dragEnabled = data.dragEnabled; + m_priority = data.priority; notifyJob(); } @@ -97,6 +100,8 @@ void ObjectPicker::sceneChangeEvent(const Qt3DCore::QSceneChangePtr &e) m_hoverEnabled = propertyChange->value().toBool(); } else if (propertyChange->propertyName() == QByteArrayLiteral("dragEnabled")) { m_dragEnabled = propertyChange->value().toBool(); + } else if (propertyChange->propertyName() == QByteArrayLiteral("priority")) { + m_priority = propertyChange->value().toInt(); } markDirty(AbstractRenderer::AllDirty); @@ -175,6 +180,16 @@ void ObjectPicker::onExited() notifyObservers(e); } +void ObjectPicker::setPriority(int priority) +{ + m_priority = priority; +} + +int ObjectPicker::priority() const +{ + return m_priority; +} + } // Render } // Qt3DRender diff --git a/src/render/picking/objectpicker_p.h b/src/render/picking/objectpicker_p.h index b9c308afb..7389a4b53 100644 --- a/src/render/picking/objectpicker_p.h +++ b/src/render/picking/objectpicker_p.h @@ -81,10 +81,15 @@ public: void onEntered(); void onExited(); + // Needed for unit tests + void setPriority(int priority); + int priority() const; + private: void initializeFromPeer(const Qt3DCore::QNodeCreatedChangeBasePtr &change) final; void notifyJob(); + int m_priority; bool m_isPressed; bool m_hoverEnabled; bool m_dragEnabled; diff --git a/src/render/picking/qobjectpicker.cpp b/src/render/picking/qobjectpicker.cpp index c3671d018..a0b6d8dcd 100644 --- a/src/render/picking/qobjectpicker.cpp +++ b/src/render/picking/qobjectpicker.cpp @@ -265,6 +265,23 @@ void QObjectPicker::setDragEnabled(bool dragEnabled) } } +/*! + * Sets the picker's priority to \a priority. This is used when the pick result + * mode on QPickingSettings is set to QPickingSettings::NearestPriorityPick. + * Picking results are sorted by highest priority and shortest picking + * distance. + * + * \since 5.13 + */ +void QObjectPicker::setPriority(int priority) +{ + Q_D(QObjectPicker); + if (priority != d->m_priority) { + d->m_priority = priority; + emit priorityChanged(priority); + } +} + /*! \qmlproperty bool Qt3D.Render::ObjectPicker::dragEnabled */ @@ -312,6 +329,25 @@ bool QObjectPicker::isPressed() const return d->m_pressed; } +/*! + \qmlproperty int Qt3D.Render::ObjectPicker::priority + + The priority to be used when filtering pick results by priority when + PickingSettings.pickResultMode is set to PickingSettings.PriorityPick. +*/ +/*! + \property Qt3DRender::QObjectPicker::priority + + The priority to be used when filtering pick results by priority when + QPickingSettings::pickResultMode is set to + QPickingSettings::NearestPriorityPick. +*/ +int QObjectPicker::priority() const +{ + Q_D(const QObjectPicker); + return d->m_priority; +} + /*! \internal */ void QObjectPicker::sceneChangeEvent(const Qt3DCore::QSceneChangePtr &change) { @@ -465,6 +501,7 @@ Qt3DCore::QNodeCreatedChangeBasePtr QObjectPicker::createNodeCreationChange() co Q_D(const QObjectPicker); data.hoverEnabled = d->m_hoverEnabled; data.dragEnabled = d->m_dragEnabled; + data.priority = d->m_priority; return creationChange; } diff --git a/src/render/picking/qobjectpicker.h b/src/render/picking/qobjectpicker.h index 9f3b138c3..1d15f6092 100644 --- a/src/render/picking/qobjectpicker.h +++ b/src/render/picking/qobjectpicker.h @@ -58,6 +58,7 @@ class QT3DRENDERSHARED_EXPORT QObjectPicker : public Qt3DCore::QComponent Q_PROPERTY(bool dragEnabled READ isDragEnabled WRITE setDragEnabled NOTIFY dragEnabledChanged) Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged) Q_PROPERTY(bool containsMouse READ containsMouse NOTIFY containsMouseChanged) + Q_PROPERTY(int priority READ priority WRITE setPriority NOTIFY priorityChanged REVISION 13) public: explicit QObjectPicker(QNode *parent = nullptr); @@ -69,9 +70,12 @@ public: bool containsMouse() const; bool isPressed() const; + int priority() const; + public Q_SLOTS: void setHoverEnabled(bool hoverEnabled); void setDragEnabled(bool dragEnabled); + void setPriority(int priority); Q_SIGNALS: void pressed(Qt3DRender::QPickEvent *pick); @@ -84,6 +88,7 @@ Q_SIGNALS: void dragEnabledChanged(bool dragEnabled); void pressedChanged(bool pressed); void containsMouseChanged(bool containsMouse); + void priorityChanged(int priority); protected: void sceneChangeEvent(const Qt3DCore::QSceneChangePtr &change) override; diff --git a/src/render/picking/qobjectpicker_p.h b/src/render/picking/qobjectpicker_p.h index 3c48b9419..384062bef 100644 --- a/src/render/picking/qobjectpicker_p.h +++ b/src/render/picking/qobjectpicker_p.h @@ -69,6 +69,7 @@ public: , m_pressed(false) , m_containsMouse(false) , m_acceptedLastPressedEvent(true) + , m_priority(0) { m_shareable = false; } @@ -79,6 +80,7 @@ public: bool m_pressed; bool m_containsMouse; bool m_acceptedLastPressedEvent; + int m_priority; enum EventType { Pressed, @@ -102,6 +104,7 @@ struct QObjectPickerData { bool hoverEnabled; bool dragEnabled; + int priority; }; } // namespace Qt3DRender diff --git a/tests/auto/render/objectpicker/tst_objectpicker.cpp b/tests/auto/render/objectpicker/tst_objectpicker.cpp index c1b06ccd8..644849102 100644 --- a/tests/auto/render/objectpicker/tst_objectpicker.cpp +++ b/tests/auto/render/objectpicker/tst_objectpicker.cpp @@ -47,6 +47,7 @@ private Q_SLOTS: Qt3DRender::Render::ObjectPicker objectPicker; Qt3DRender::QObjectPicker picker; picker.setHoverEnabled(true); + picker.setPriority(883); // WHEN simulateInitialization(&picker, &objectPicker); @@ -54,6 +55,7 @@ private Q_SLOTS: // THEN QVERIFY(!objectPicker.peerId().isNull()); QCOMPARE(objectPicker.isHoverEnabled(), true); + QCOMPARE(objectPicker.priority(), 883); } void checkInitialAndCleanedUpState() @@ -64,10 +66,14 @@ private Q_SLOTS: // THEN QVERIFY(objectPicker.peerId().isNull()); QCOMPARE(objectPicker.isHoverEnabled(), false); + QCOMPARE(objectPicker.isDragEnabled(), false); + QCOMPARE(objectPicker.priority(), 0); // GIVEN Qt3DRender::QObjectPicker picker; picker.setHoverEnabled(true); + picker.setDragEnabled(true); + picker.setPriority(1584); // WHEN simulateInitialization(&picker, &objectPicker); @@ -75,6 +81,8 @@ private Q_SLOTS: // THEN QCOMPARE(objectPicker.isHoverEnabled(), false); + QCOMPARE(objectPicker.isDragEnabled(), false); + QCOMPARE(objectPicker.priority(), 0); } void checkPropertyChanges() @@ -95,6 +103,34 @@ private Q_SLOTS: QCOMPARE(objectPicker.isHoverEnabled(), true); QVERIFY(renderer.dirtyBits() != 0); } + { + Qt3DRender::Render::ObjectPicker objectPicker; + objectPicker.setRenderer(&renderer); + + // WHEN + Qt3DCore::QPropertyUpdatedChangePtr updateChange(new Qt3DCore::QPropertyUpdatedChange(Qt3DCore::QNodeId())); + updateChange->setValue(true); + updateChange->setPropertyName("dragEnabled"); + objectPicker.sceneChangeEvent(updateChange); + + // THEN + QCOMPARE(objectPicker.isDragEnabled(), true); + QVERIFY(renderer.dirtyBits() != 0); + } + { + Qt3DRender::Render::ObjectPicker objectPicker; + objectPicker.setRenderer(&renderer); + + // WHEN + Qt3DCore::QPropertyUpdatedChangePtr updateChange(new Qt3DCore::QPropertyUpdatedChange(Qt3DCore::QNodeId())); + updateChange->setValue(15); + updateChange->setPropertyName("priority"); + objectPicker.sceneChangeEvent(updateChange); + + // THEN + QCOMPARE(objectPicker.priority(), 15); + QVERIFY(renderer.dirtyBits() != 0); + } } void checkBackendPropertyNotifications() diff --git a/tests/auto/render/pickboundingvolumejob/pickboundingvolumejob.qrc b/tests/auto/render/pickboundingvolumejob/pickboundingvolumejob.qrc index feef480e2..e1506de86 100644 --- a/tests/auto/render/pickboundingvolumejob/pickboundingvolumejob.qrc +++ b/tests/auto/render/pickboundingvolumejob/pickboundingvolumejob.qrc @@ -10,5 +10,6 @@ testscene_parententity.qml testscene_viewports.qml testscene_cameraposition.qml + testscene_priorityoverlapping.qml diff --git a/tests/auto/render/pickboundingvolumejob/testscene_priorityoverlapping.qml b/tests/auto/render/pickboundingvolumejob/testscene_priorityoverlapping.qml new file mode 100644 index 000000000..7cacb3d2d --- /dev/null +++ b/tests/auto/render/pickboundingvolumejob/testscene_priorityoverlapping.qml @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import Qt3D.Core 2.0 +import Qt3D.Render 2.13 +import Qt3D.Extras 2.0 +import QtQuick.Window 2.0 + +Entity { + id: sceneRoot + + Window { + id: win + width: 600 + height: 600 + visible: true + } + + Camera { + id: camera + projectionType: CameraLens.PerspectiveProjection + fieldOfView: 45 + nearPlane : 0.1 + farPlane : 1000.0 + position: Qt.vector3d( 0.0, 0.0, -40.0 ) + upVector: Qt.vector3d( 0.0, 1.0, 0.0 ) + viewCenter: Qt.vector3d( 0.0, 0.0, 0.0 ) + } + + components: [ + RenderSettings { + activeFrameGraph: Viewport { + normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0) + + RenderSurfaceSelector { + surface: win + + ClearBuffers { + buffers : ClearBuffers.ColorDepthBuffer + NoDraw {} + } + + CameraSelector { + camera: camera + } + } + } + pickingSettings { + pickResultMode: PickingSettings.NearestPriorityPick + pickMethod: PickingSettings.TrianglePicking + faceOrientationPickingMode: PickingSettings.FrontAndBackFace + } + } + ] + + CuboidMesh { id: cubeMesh } + PhongMaterial { id: material } + + // Entity 1 + Entity { + property ObjectPicker picker: ObjectPicker { + id: picker1 + objectName: "Picker1" + } + + property Transform transform: Transform { + translation: Qt.vector3d(0, 0, 0) + scale: 2.0 + } + + components: [cubeMesh, material, picker1, transform] + } + + // Entity 2 + Entity { + property ObjectPicker picker: ObjectPicker { + id: picker2 + objectName: "Picker2" + } + + property Transform transform: Transform { + translation: Qt.vector3d(0, 0, 10) + scale: 2.5 + } + + components: [cubeMesh, material, picker2, transform] + } +} diff --git a/tests/auto/render/pickboundingvolumejob/tst_pickboundingvolumejob.cpp b/tests/auto/render/pickboundingvolumejob/tst_pickboundingvolumejob.cpp index 60b60eb6e..5e51c8aa7 100644 --- a/tests/auto/render/pickboundingvolumejob/tst_pickboundingvolumejob.cpp +++ b/tests/auto/render/pickboundingvolumejob/tst_pickboundingvolumejob.cpp @@ -1473,6 +1473,111 @@ private Q_SLOTS: arbiter.events.clear(); } + void checkPriorityPicking() + { + // GIVEN + QmlSceneReader sceneReader(QUrl("qrc:/testscene_priorityoverlapping.qml")); + QScopedPointer root(qobject_cast(sceneReader.root())); + QVERIFY(root); + + QScopedPointer test(new Qt3DRender::TestAspect(root.data())); + TestArbiter arbiter1; + TestArbiter arbiter2; + + // Runs Required jobs + runRequiredJobs(test.data()); + + // THEN + QList pickers = root->findChildren(); + QCOMPARE(pickers.size(), 2); + + Qt3DRender::QObjectPicker *picker1 = nullptr; + Qt3DRender::QObjectPicker *picker2 = nullptr; + if (pickers.first()->objectName() == QLatin1String("Picker1")) { + picker1 = pickers.first(); + picker2 = pickers.last(); + } else { + picker1 = pickers.last(); + picker2 = pickers.first(); + } + + Qt3DRender::Render::ObjectPicker *backendPicker1 = test->nodeManagers()->objectPickerManager()->lookupResource(picker1->id()); + QVERIFY(backendPicker1); + Qt3DCore::QBackendNodePrivate::get(backendPicker1)->setArbiter(&arbiter1); + + Qt3DRender::Render::ObjectPicker *backendPicker2 = test->nodeManagers()->objectPickerManager()->lookupResource(picker2->id()); + QVERIFY(backendPicker2); + Qt3DCore::QBackendNodePrivate::get(backendPicker2)->setArbiter(&arbiter2); + + + // WHEN both have priority == 0, select closest + { + Qt3DRender::Render::PickBoundingVolumeJob pickBVJob; + initializePickBoundingVolumeJob(&pickBVJob, test.data()); + + // WHEN -> Pressed on object + QList> events; + events.push_back({nullptr, QMouseEvent(QMouseEvent::MouseButtonPress, QPointF(300.0f, 300.0f), + Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)}); + pickBVJob.setMouseEvents(events); + bool earlyReturn = !pickBVJob.runHelper(); + + // THEN -> Select picker with highest priority + QVERIFY(!earlyReturn); + QVERIFY(backendPicker1->isPressed()); + Qt3DCore::QPropertyUpdatedChangePtr change = arbiter1.events.first().staticCast(); + QCOMPARE(change->propertyName(), "pressed"); + + QVERIFY(!backendPicker2->isPressed()); + QVERIFY(arbiter2.events.isEmpty()); + + events.push_back({nullptr, QMouseEvent(QMouseEvent::MouseButtonRelease, QPointF(300.0f, 300.0f), + Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)}); + pickBVJob.setMouseEvents(events); + pickBVJob.runHelper(); + arbiter1.events.clear(); + arbiter2.events.clear(); + + QVERIFY(!backendPicker1->isPressed()); + QVERIFY(!backendPicker2->isPressed()); + } + + // WHEN furthest one has higher priority, select furthest one + { + backendPicker2->setPriority(1000); + QCOMPARE(backendPicker2->priority(), 1000); + + Qt3DRender::Render::PickBoundingVolumeJob pickBVJob; + initializePickBoundingVolumeJob(&pickBVJob, test.data()); + + // WHEN -> Pressed on object + QList> events; + events.push_back({nullptr, QMouseEvent(QMouseEvent::MouseButtonPress, QPointF(300.0f, 300.0f), + Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)}); + pickBVJob.setMouseEvents(events); + bool earlyReturn = !pickBVJob.runHelper(); + + // THEN -> Select picker with highest priority + QVERIFY(!earlyReturn); + QVERIFY(backendPicker2->isPressed()); + Qt3DCore::QPropertyUpdatedChangePtr change = arbiter2.events.first().staticCast(); + QCOMPARE(change->propertyName(), "pressed"); + + QVERIFY(!backendPicker1->isPressed()); + QVERIFY(arbiter1.events.isEmpty()); + + events.push_back({nullptr, QMouseEvent(QMouseEvent::MouseButtonRelease, QPointF(300.0f, 300.0f), + Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)}); + pickBVJob.setMouseEvents(events); + pickBVJob.runHelper(); + arbiter1.events.clear(); + arbiter2.events.clear(); + + QVERIFY(!backendPicker1->isPressed()); + QVERIFY(!backendPicker2->isPressed()); + } + } + }; QTEST_MAIN(tst_PickBoundingVolumeJob) diff --git a/tests/auto/render/qobjectpicker/tst_qobjectpicker.cpp b/tests/auto/render/qobjectpicker/tst_qobjectpicker.cpp index 6714d8a06..bd486774c 100644 --- a/tests/auto/render/qobjectpicker/tst_qobjectpicker.cpp +++ b/tests/auto/render/qobjectpicker/tst_qobjectpicker.cpp @@ -31,8 +31,10 @@ #include #include #include +#include #include - +#include +#include #include "testpostmanarbiter.h" class MyObjectPicker : public Qt3DRender::QObjectPicker @@ -71,6 +73,162 @@ public: private Q_SLOTS: + void checkInitialState() + { + // GIVEN + Qt3DRender::QObjectPicker picker; + + // THEN + QCOMPARE(picker.priority(), 0); + QCOMPARE(picker.isDragEnabled(), false); + QCOMPARE(picker.isHoverEnabled(), false); + } + + void checkCreationData() + { + // GIVEN + Qt3DRender::QObjectPicker picker; + + picker.setPriority(1584); + picker.setDragEnabled(true); + picker.setHoverEnabled(true); + + // WHEN + QVector creationChanges; + + { + Qt3DCore::QNodeCreatedChangeGenerator creationChangeGenerator(&picker); + creationChanges = creationChangeGenerator.creationChanges(); + } + + // THEN + { + QCOMPARE(creationChanges.size(), 1); + + const auto creationChangeData = qSharedPointerCast>(creationChanges.first()); + const Qt3DRender::QObjectPickerData cloneData = creationChangeData->data; + + QCOMPARE(cloneData.priority, 1584); + QCOMPARE(cloneData.hoverEnabled, true); + QCOMPARE(cloneData.dragEnabled, true); + QCOMPARE(picker.id(), creationChangeData->subjectId()); + QCOMPARE(picker.isEnabled(), true); + QCOMPARE(picker.isEnabled(), creationChangeData->isNodeEnabled()); + QCOMPARE(picker.metaObject(), creationChangeData->metaObject()); + } + + // WHEN + picker.setEnabled(false); + + { + Qt3DCore::QNodeCreatedChangeGenerator creationChangeGenerator(&picker); + creationChanges = creationChangeGenerator.creationChanges(); + } + + // THEN + { + QCOMPARE(creationChanges.size(), 1); + + const auto creationChangeData = qSharedPointerCast>(creationChanges.first()); + const Qt3DRender::QObjectPickerData cloneData = creationChangeData->data; + + QCOMPARE(cloneData.priority, 1584); + QCOMPARE(cloneData.hoverEnabled, true); + QCOMPARE(cloneData.dragEnabled, true); + QCOMPARE(picker.id(), creationChangeData->subjectId()); + QCOMPARE(picker.isEnabled(), false); + QCOMPARE(picker.isEnabled(), creationChangeData->isNodeEnabled()); + QCOMPARE(picker.metaObject(), creationChangeData->metaObject()); + } + } + + void checkPropertyUpdate() + { + // GIVEN + TestArbiter arbiter; + Qt3DRender::QObjectPicker picker; + arbiter.setArbiterOnNode(&picker); + + { + { + // WHEN + picker.setPriority(883); + QCoreApplication::processEvents(); + + // THEN + QCOMPARE(arbiter.events.size(), 1); + QCOMPARE(picker.priority(), 883); + auto change = arbiter.events.first().staticCast(); + QCOMPARE(change->propertyName(), "priority"); + QCOMPARE(change->value().value(), picker.priority()); + QCOMPARE(change->type(), Qt3DCore::PropertyUpdated); + + arbiter.events.clear(); + } + + { + // WHEN + picker.setPriority(883); + QCoreApplication::processEvents(); + + // THEN + QCOMPARE(arbiter.events.size(), 0); + } + } + { + { + // WHEN + picker.setDragEnabled(true); + QCoreApplication::processEvents(); + + // THEN + QCOMPARE(arbiter.events.size(), 1); + QCOMPARE(picker.isDragEnabled(), true); + auto change = arbiter.events.first().staticCast(); + QCOMPARE(change->propertyName(), "dragEnabled"); + QCOMPARE(change->value().value(), picker.isDragEnabled()); + QCOMPARE(change->type(), Qt3DCore::PropertyUpdated); + + arbiter.events.clear(); + } + + { + // WHEN + picker.setDragEnabled(true); + QCoreApplication::processEvents(); + + // THEN + QCOMPARE(arbiter.events.size(), 0); + } + } + { + { + // WHEN + picker.setHoverEnabled(true); + QCoreApplication::processEvents(); + + // THEN + QCOMPARE(arbiter.events.size(), 1); + QCOMPARE(picker.isHoverEnabled(), true); + auto change = arbiter.events.first().staticCast(); + QCOMPARE(change->propertyName(), "hoverEnabled"); + QCOMPARE(change->value().value(), picker.isHoverEnabled()); + QCOMPARE(change->type(), Qt3DCore::PropertyUpdated); + + arbiter.events.clear(); + } + + { + // WHEN + picker.setHoverEnabled(true); + QCoreApplication::processEvents(); + + // THEN + QCOMPARE(arbiter.events.size(), 0); + } + } + } + void checkCloning_data() { QTest::addColumn("objectPicker"); -- cgit v1.2.3