diff options
author | Paul Lemire <paul.lemire@kdab.com> | 2019-01-03 09:14:27 +0100 |
---|---|---|
committer | Mike Krus <mike.krus@kdab.com> | 2019-01-07 16:52:35 +0000 |
commit | 55b52c47030c759f8b38013eb873c0b161d0e426 (patch) | |
tree | 08d97fbd7e45a15dc1ffa7df18119094e8a62997 | |
parent | 6f59eb1990d2ad700a109c6407aaef28004e135b (diff) |
QGeometry: add minExtent/maxExtent properties
To allow computing a bounding box for a given QGeometry while waiting
for a proper bounding volume aspect.
Change-Id: If1ecf2f9236beaf569c650e5f8b05a6151ca6381
Reviewed-by: Mike Krus <mike.krus@kdab.com>
-rw-r--r-- | src/render/geometry/geometry.cpp | 39 | ||||
-rw-r--r-- | src/render/geometry/geometry_p.h | 10 | ||||
-rw-r--r-- | src/render/geometry/qgeometry.cpp | 56 | ||||
-rw-r--r-- | src/render/geometry/qgeometry.h | 8 | ||||
-rw-r--r-- | src/render/geometry/qgeometry_p.h | 3 | ||||
-rw-r--r-- | src/render/jobs/calcboundingvolumejob.cpp | 58 | ||||
-rw-r--r-- | tests/auto/render/geometry/tst_geometry.cpp | 28 | ||||
-rw-r--r-- | tests/auto/render/qgeometry/tst_qgeometry.cpp | 55 |
8 files changed, 242 insertions, 15 deletions
diff --git a/src/render/geometry/geometry.cpp b/src/render/geometry/geometry.cpp index d87b4d8eb..4ee02a74d 100644 --- a/src/render/geometry/geometry.cpp +++ b/src/render/geometry/geometry.cpp @@ -53,8 +53,10 @@ namespace Qt3DRender { namespace Render { Geometry::Geometry() - : BackendNode(ReadOnly) + : BackendNode(ReadWrite) , m_geometryDirty(false) + , m_shouldNotifyMinExtentChanged(false) + , m_shouldNotifyMaxExtentChanged(false) { } @@ -68,6 +70,10 @@ void Geometry::cleanup() m_attributes.clear(); m_geometryDirty = false; m_boundingPositionAttribute = Qt3DCore::QNodeId(); + m_min = QVector3D(); + m_max = QVector3D(); + m_shouldNotifyMinExtentChanged = false; + m_shouldNotifyMaxExtentChanged = false; } void Geometry::initializeFromPeer(const Qt3DCore::QNodeCreatedChangeBasePtr &change) @@ -122,6 +128,37 @@ void Geometry::unsetDirty() m_geometryDirty = false; } +// Called from calcboundingvolumejob (in a QtConcurrent thead (can't send +// update changes from such a thread)) +void Geometry::updateExtent(const QVector3D &min, const QVector3D &max) +{ + // Send notification to frontend + if (m_min != min) { + m_min = min; + m_shouldNotifyMinExtentChanged = true; + } + + if (m_max != max) { + m_max = max; + m_shouldNotifyMaxExtentChanged = true; + } +} + +// Called from calcboundingvolumejob after all bounding volumes have been +// updated (in an aspect thread) +void Geometry::notifyExtentChanged() +{ + if (m_shouldNotifyMinExtentChanged || m_shouldNotifyMaxExtentChanged) { + auto change = Qt3DCore::QPropertyUpdatedChangePtr::create(peerId()); + change->setDeliveryFlags(Qt3DCore::QSceneChange::Nodes); + change->setPropertyName("extent"); + change->setValue(QVariant::fromValue(QPair<QVector3D, QVector3D>(m_min, m_max))); + notifyObservers(change); + m_shouldNotifyMinExtentChanged = false; + m_shouldNotifyMaxExtentChanged = false; + } +} + } // namespace Render } // namespace Qt3DRender diff --git a/src/render/geometry/geometry_p.h b/src/render/geometry/geometry_p.h index b158648ad..e66524787 100644 --- a/src/render/geometry/geometry_p.h +++ b/src/render/geometry/geometry_p.h @@ -75,12 +75,22 @@ public: inline Qt3DCore::QNodeId boundingPositionAttribute() const { return m_boundingPositionAttribute; } void unsetDirty(); + inline QVector3D min() const { return m_min; } + inline QVector3D max() const { return m_max; } + + void updateExtent(const QVector3D &min, const QVector3D &max); + void notifyExtentChanged(); + private: void initializeFromPeer(const Qt3DCore::QNodeCreatedChangeBasePtr &change) final; QVector<Qt3DCore::QNodeId> m_attributes; bool m_geometryDirty; Qt3DCore::QNodeId m_boundingPositionAttribute; + QVector3D m_min; + QVector3D m_max; + bool m_shouldNotifyMinExtentChanged; + bool m_shouldNotifyMaxExtentChanged; }; } // namespace Render diff --git a/src/render/geometry/qgeometry.cpp b/src/render/geometry/qgeometry.cpp index c49dde822..ec80e2657 100644 --- a/src/render/geometry/qgeometry.cpp +++ b/src/render/geometry/qgeometry.cpp @@ -153,6 +153,28 @@ QGeometry::QGeometry(QGeometryPrivate &dd, QNode *parent) { } +void QGeometry::sceneChangeEvent(const QSceneChangePtr &change) +{ + Q_D(QGeometry); + QPropertyUpdatedChangePtr e = qSharedPointerCast<QPropertyUpdatedChange>(change); + if (e->type() == PropertyUpdated) { + const bool blocked = blockNotifications(true); + if (e->propertyName() == QByteArrayLiteral("extent")) { + const QPair<QVector3D, QVector3D> extent = e->value().value<QPair<QVector3D, QVector3D>>(); + + if (extent.first != d->m_minExtent) { + d->m_minExtent = extent.first; + emit minExtentChanged(extent.first); + } + if (extent.second != d->m_maxExtent) { + d->m_maxExtent = extent.second; + emit maxExtentChanged(d->m_maxExtent); + } + } + blockNotifications(blocked); + } +} + /*! \fn void Qt3DRender::QGeometry::addAttribute(Qt3DRender::QAttribute *attribute) Adds an \a attribute to this geometry. @@ -216,6 +238,40 @@ QAttribute *QGeometry::boundingVolumePositionAttribute() const } /*! + \qmlproperty vector3d Geometry::minExtent + + Holds the vertex with the lowest x, y, z position values. + */ + +/*! + \property QGeometry::minExtent + + Holds the vertex with the lowest x, y, z position values. + */ +QVector3D QGeometry::minExtent() const +{ + Q_D(const QGeometry); + return d->m_minExtent; +} + +/*! + \qmlproperty vector3d Geometry::maxExtent + + Holds the vertex with the highest x, y, z position values. + */ + +/*! + \property QGeometry::maxExtent + + Holds the vertex with the highest x, y, z position values. + */ +QVector3D QGeometry::maxExtent() const +{ + Q_D(const QGeometry); + return d->m_maxExtent; +} + +/*! Returns the list of attributes in this geometry. */ QVector<QAttribute *> QGeometry::attributes() const diff --git a/src/render/geometry/qgeometry.h b/src/render/geometry/qgeometry.h index 0e6f7d68e..61508c7d2 100644 --- a/src/render/geometry/qgeometry.h +++ b/src/render/geometry/qgeometry.h @@ -54,6 +54,8 @@ class QT3DRENDERSHARED_EXPORT QGeometry : public Qt3DCore::QNode { Q_OBJECT Q_PROPERTY(Qt3DRender::QAttribute *boundingVolumePositionAttribute READ boundingVolumePositionAttribute WRITE setBoundingVolumePositionAttribute NOTIFY boundingVolumePositionAttributeChanged) + Q_PROPERTY(QVector3D minExtent READ minExtent NOTIFY minExtentChanged REVISION 13) + Q_PROPERTY(QVector3D maxExtent READ maxExtent NOTIFY maxExtentChanged REVISION 13) public: explicit QGeometry(Qt3DCore::QNode *parent = nullptr); ~QGeometry(); @@ -63,15 +65,19 @@ public: Q_INVOKABLE void removeAttribute(Qt3DRender::QAttribute *attribute); QAttribute *boundingVolumePositionAttribute() const; + QVector3D minExtent() const; + QVector3D maxExtent() const; public Q_SLOTS: void setBoundingVolumePositionAttribute(QAttribute *boundingVolumePositionAttribute); Q_SIGNALS: void boundingVolumePositionAttributeChanged(QAttribute *boundingVolumePositionAttribute); - + void minExtentChanged(const QVector3D &minExtent); + void maxExtentChanged(const QVector3D &maxExtent); protected: explicit QGeometry(QGeometryPrivate &dd, Qt3DCore::QNode *parent = nullptr); + void sceneChangeEvent(const Qt3DCore::QSceneChangePtr &change) override; private: Q_DECLARE_PRIVATE(QGeometry) diff --git a/src/render/geometry/qgeometry_p.h b/src/render/geometry/qgeometry_p.h index e07b9ff0d..f53548e43 100644 --- a/src/render/geometry/qgeometry_p.h +++ b/src/render/geometry/qgeometry_p.h @@ -53,6 +53,7 @@ #include <Qt3DRender/private/qt3drender_global_p.h> #include <Qt3DCore/private/qnode_p.h> +#include <QVector3D> QT_BEGIN_NAMESPACE @@ -68,6 +69,8 @@ public: QVector<QAttribute *> m_attributes; QAttribute *m_boundingVolumePositionAttribute; + QVector3D m_minExtent; + QVector3D m_maxExtent; }; struct QGeometryData diff --git a/src/render/jobs/calcboundingvolumejob.cpp b/src/render/jobs/calcboundingvolumejob.cpp index 66d59f812..68eb308c5 100644 --- a/src/render/jobs/calcboundingvolumejob.cpp +++ b/src/render/jobs/calcboundingvolumejob.cpp @@ -65,14 +65,25 @@ namespace Render { namespace { -void calculateLocalBoundingVolume(NodeManagers *manager, Entity *node); +QVector<Geometry*> calculateLocalBoundingVolume(NodeManagers *manager, Entity *node); -struct UpdateBoundFunctor { +struct UpdateBoundFunctor +{ NodeManagers *manager; - void operator ()(Qt3DRender::Render::Entity *node) + // This define is required to work with QtConcurrent + typedef QVector<Geometry *> result_type; + QVector<Geometry *> operator ()(Qt3DRender::Render::Entity *node) + { + return calculateLocalBoundingVolume(manager, node); + } +}; + +struct ReduceUpdateBoundFunctor +{ + void operator ()(QVector<Geometry *> &result, const QVector<Geometry *> &values) { - calculateLocalBoundingVolume(manager, node); + result += values; } }; @@ -82,6 +93,8 @@ public: BoundingVolumeCalculator(NodeManagers *manager) : m_manager(manager) { } const Sphere& result() { return m_volume; } + const QVector3D min() const { return m_min; } + const QVector3D max() const { return m_max; } bool apply(Qt3DRender::Render::Attribute *positionAttribute, Qt3DRender::Render::Attribute *indexAttribute, @@ -95,6 +108,9 @@ public: return false; } + m_min = QVector3D(findExtremePoints.xMin, findExtremePoints.yMin, findExtremePoints.zMin); + m_max = QVector3D(findExtremePoints.xMax, findExtremePoints.yMax, findExtremePoints.zMax); + // Calculate squared distance for the pairs of points const float xDist2 = (findExtremePoints.xMaxPt - findExtremePoints.xMinPt).lengthSquared(); const float yDist2 = (findExtremePoints.yMaxPt - findExtremePoints.yMinPt).lengthSquared(); @@ -127,6 +143,8 @@ public: private: Sphere m_volume; NodeManagers *m_manager; + QVector3D m_min; + QVector3D m_max; class FindExtremePoints : public Buffer3fVisitor { @@ -191,17 +209,20 @@ private: }; }; -void calculateLocalBoundingVolume(NodeManagers *manager, Entity *node) +QVector<Geometry *> calculateLocalBoundingVolume(NodeManagers *manager, Entity *node) { // The Bounding volume will only be computed if the position Buffer // isDirty + QVector<Geometry *> updatedGeometries; + if (!node->isTreeEnabled()) - return; + return updatedGeometries; GeometryRenderer *gRenderer = node->renderComponent<GeometryRenderer>(); + GeometryManager *geometryManager = manager->geometryManager(); if (gRenderer && gRenderer->primitiveType() != QGeometryRenderer::Patches) { - Geometry *geom = manager->lookupResource<Geometry, GeometryManager>(gRenderer->geometryId()); + Geometry *geom = geometryManager->lookupResource(gRenderer->geometryId()); if (geom) { int drawVertexCount = gRenderer->vertexCount(); // may be 0, gets changed below if so @@ -224,14 +245,14 @@ void calculateLocalBoundingVolume(NodeManagers *manager, Entity *node) || positionAttribute->vertexBaseType() != QAttribute::Float || positionAttribute->vertexSize() < 3) { qWarning("calculateLocalBoundingVolume: Position attribute not suited for bounding volume computation"); - return; + return updatedGeometries; } Buffer *buf = manager->lookupResource<Buffer, BufferManager>(positionAttribute->bufferId()); // No point in continuing if the positionAttribute doesn't have a suitable buffer if (!buf) { qWarning("calculateLocalBoundingVolume: Position attribute not referencing a valid buffer"); - return; + return updatedGeometries; } // Check if there is an index attribute. @@ -259,7 +280,7 @@ void calculateLocalBoundingVolume(NodeManagers *manager, Entity *node) std::end(validIndexTypes), indexAttribute->vertexBaseType()) == std::end(validIndexTypes)) { qWarning() << "calculateLocalBoundingVolume: Unsupported index attribute type" << indexAttribute->name() << indexAttribute->vertexBaseType(); - return; + return updatedGeometries; } break; @@ -287,6 +308,11 @@ void calculateLocalBoundingVolume(NodeManagers *manager, Entity *node) node->localBoundingVolume()->setCenter(reader.result().center()); node->localBoundingVolume()->setRadius(reader.result().radius()); node->unsetBoundingVolumeDirty(); + + // Record min/max vertex in Geometry + geom->updateExtent(reader.min(), reader.max()); + // Mark geometry as requiring a call to update its frontend + updatedGeometries.push_back(geom); } } } @@ -297,14 +323,16 @@ void calculateLocalBoundingVolume(NodeManagers *manager, Entity *node) if (children.size() > 1) { UpdateBoundFunctor functor; functor.manager = manager; - QtConcurrent::blockingMap(children, functor); + ReduceUpdateBoundFunctor reduceFunctor; + updatedGeometries += QtConcurrent::blockingMappedReduced<decltype(updatedGeometries)>(children, functor, reduceFunctor); } else #endif { const auto children = node->children(); for (Entity *child : children) - calculateLocalBoundingVolume(manager, child); + updatedGeometries += calculateLocalBoundingVolume(manager, child); } + return updatedGeometries; } } // anonymous @@ -318,7 +346,11 @@ CalculateBoundingVolumeJob::CalculateBoundingVolumeJob() void CalculateBoundingVolumeJob::run() { - calculateLocalBoundingVolume(m_manager, m_node); + const QVector<Geometry *> updatedGeometries = calculateLocalBoundingVolume(m_manager, m_node); + + // Send extent updates to frontend + for (Geometry *geometry : updatedGeometries) + geometry->notifyExtentChanged(); } void CalculateBoundingVolumeJob::setRoot(Entity *node) diff --git a/tests/auto/render/geometry/tst_geometry.cpp b/tests/auto/render/geometry/tst_geometry.cpp index 958edfd09..7e65d27aa 100644 --- a/tests/auto/render/geometry/tst_geometry.cpp +++ b/tests/auto/render/geometry/tst_geometry.cpp @@ -34,7 +34,9 @@ #include <Qt3DCore/qpropertyupdatedchange.h> #include <Qt3DCore/qpropertynodeaddedchange.h> #include <Qt3DCore/qpropertynoderemovedchange.h> +#include <Qt3DCore/private/qbackendnode_p.h> #include "testrenderer.h" +#include "testpostmanarbiter.h" class DummyAttribute : public Qt3DRender::QAttribute { @@ -187,6 +189,32 @@ private Q_SLOTS: QVERIFY(renderer.dirtyBits() & Qt3DRender::Render::AbstractRenderer::GeometryDirty); renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty); } + + void checkExtentTransmission() + { + // GIVEN + TestRenderer renderer; + TestArbiter arbiter; + Qt3DRender::Render::Geometry renderGeometry; + + Qt3DCore::QBackendNodePrivate::get(&renderGeometry)->setArbiter(&arbiter); + renderGeometry.setRenderer(&renderer); + + // WHEN + renderGeometry.updateExtent(QVector3D(-1.0f, -1.0f, -1.0f), QVector3D(1.0f, 1.0f, 1.0f)); + renderGeometry.notifyExtentChanged(); + + // THEN + QCOMPARE(arbiter.events.count(), 1); + + Qt3DCore::QPropertyUpdatedChangePtr change = arbiter.events.first().staticCast<Qt3DCore::QPropertyUpdatedChange>(); + QCOMPARE(change->propertyName(), "extent"); + const QPair<QVector3D, QVector3D> v = change->value().value<QPair<QVector3D, QVector3D>>(); + QCOMPARE(v.first, QVector3D(-1.0f, -1.0f, -1.0f)); + QCOMPARE(v.second, QVector3D(1.0f, 1.0f, 1.0f)); + + arbiter.events.clear(); + } }; diff --git a/tests/auto/render/qgeometry/tst_qgeometry.cpp b/tests/auto/render/qgeometry/tst_qgeometry.cpp index b9271a8c0..55b7e752c 100644 --- a/tests/auto/render/qgeometry/tst_qgeometry.cpp +++ b/tests/auto/render/qgeometry/tst_qgeometry.cpp @@ -39,9 +39,21 @@ #include <Qt3DCore/QPropertyUpdatedChange> #include <Qt3DCore/QPropertyNodeAddedChange> #include <Qt3DCore/QPropertyNodeRemovedChange> +#include <QSignalSpy> #include "testpostmanarbiter.h" +class FakeGeometry : public Qt3DRender::QGeometry +{ + Q_OBJECT + +public: + void sceneChangeEvent(const Qt3DCore::QSceneChangePtr &change) override + { + Qt3DRender::QGeometry::sceneChangeEvent(change); + } +}; + class tst_QGeometry: public QObject { Q_OBJECT @@ -180,6 +192,49 @@ private Q_SLOTS: // THEN Should not crash when the attribute is destroyed (tests for failed removal of destruction helper) } } + + void checkExtentUpdates() + { + // GIVEN + TestArbiter arbiter; + QScopedPointer<FakeGeometry> geometry(new FakeGeometry()); + arbiter.setArbiterOnNode(geometry.data()); + QSignalSpy spyMinExtent(geometry.data(), SIGNAL(minExtentChanged(QVector3D))); + QSignalSpy spyMaxExtent(geometry.data(), SIGNAL(maxExtentChanged(QVector3D))); + + // THEN + QVERIFY(spyMinExtent.isValid()); + QVERIFY(spyMaxExtent.isValid()); + QCOMPARE(geometry->minExtent(), QVector3D()); + QCOMPARE(geometry->maxExtent(), QVector3D()); + + // WHEN + Qt3DCore::QPropertyUpdatedChangePtr valueChange(new Qt3DCore::QPropertyUpdatedChange(Qt3DCore::QNodeId())); + valueChange->setPropertyName("extent"); + valueChange->setValue(QVariant::fromValue(QPair<QVector3D, QVector3D>(QVector3D(10.0f, 10.f, 10.0f), + QVector3D()))); + geometry->sceneChangeEvent(valueChange); + + // THEN + QCOMPARE(spyMinExtent.count(), 1); + QCOMPARE(spyMaxExtent.count(), 0); + QCOMPARE(geometry->minExtent(), QVector3D(10.0f, 10.0f, 10.0f)); + + spyMinExtent.clear(); + + // WHEN + valueChange->setPropertyName("extent"); + valueChange->setValue(QVariant::fromValue(QPair<QVector3D, QVector3D>(QVector3D(10.0f, 10.f, 10.0f), + QVector3D(11.0f, 11.f, 11.0f)))); + geometry->sceneChangeEvent(valueChange); + + // THEN + QCOMPARE(spyMinExtent.count(), 0); + QCOMPARE(spyMaxExtent.count(), 1); + QCOMPARE(geometry->maxExtent(), QVector3D(11.0f, 11.0f, 11.0f)); + + spyMaxExtent.clear(); + } }; QTEST_MAIN(tst_QGeometry) |