Revision control

++ // Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
++ // Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "calcboundingvolumejob_p.h"
++
++ #include <Qt3DCore/qboundingvolume.h>
++ #include <Qt3DCore/private/qabstractfrontendnodemanager_p.h>
++ #include <Qt3DCore/private/qgeometry_p.h>
++ #include <Qt3DCore/private/qaspectjobmanager_p.h>
++ #include <Qt3DRender/private/nodemanagers_p.h>
++ #include <Qt3DRender/private/entity_p.h>
++ #include <Qt3DRender/private/renderlogging_p.h>
++ #include <Qt3DRender/private/managers_p.h>
++ #include <Qt3DRender/private/qgeometryrenderer_p.h>
++ #include <Qt3DRender/private/geometryrenderer_p.h>
++ #include <Qt3DRender/private/geometry_p.h>
++ #include <Qt3DRender/private/buffermanager_p.h>
++ #include <Qt3DRender/private/attribute_p.h>
++ #include <Qt3DRender/private/buffer_p.h>
++ #include <Qt3DRender/private/sphere_p.h>
++ #include <Qt3DRender/private/buffervisitor_p.h>
++ #include <Qt3DRender/private/entityvisitor_p.h>
++
++ #include <QtCore/qmath.h>
++ #if QT_CONFIG(concurrent)
++ #include <QtConcurrent/QtConcurrent>
++ #endif
++ #include <Qt3DRender/private/job_common_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace Qt3DRender {
++ namespace Render {
++
++ using namespace Qt3DCore;
++
++ namespace {
++
++ class BoundingVolumeCalculator
++ {
++ public:
++ explicit 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,
++ int drawVertexCount,
++ bool primitiveRestartEnabled,
++ int primitiveRestartIndex)
++ {
++ FindExtremePoints findExtremePoints(m_manager);
++ if (!findExtremePoints.apply(positionAttribute, indexAttribute, drawVertexCount,
++ primitiveRestartEnabled, primitiveRestartIndex))
++ return false;
++
++ m_min = QVector3D(findExtremePoints.xMin, findExtremePoints.yMin, findExtremePoints.zMin);
++ m_max = QVector3D(findExtremePoints.xMax, findExtremePoints.yMax, findExtremePoints.zMax);
++
++ FindMaxDistantPoint maxDistantPointY(m_manager);
++ maxDistantPointY.setReferencePoint = true;
++ if (!maxDistantPointY.apply(positionAttribute, indexAttribute, drawVertexCount,
++ primitiveRestartEnabled, primitiveRestartIndex))
++ return false;
++ if (maxDistantPointY.hasNoPoints)
++ return false;
++
++ //const Vector3D x = maxDistantPointY.referencePt;
++ const Vector3D y = maxDistantPointY.maxDistPt;
++
++ FindMaxDistantPoint maxDistantPointZ(m_manager);
++ maxDistantPointZ.setReferencePoint = false;
++ maxDistantPointZ.referencePt = y;
++ if (!maxDistantPointZ.apply(positionAttribute, indexAttribute, drawVertexCount,
++ primitiveRestartEnabled, primitiveRestartIndex)) {
++ return false;
++ }
++ const Vector3D z = maxDistantPointZ.maxDistPt;
++
++ const Vector3D center = (y + z) * 0.5f;
++
++ FindMaxDistantPoint maxDistantPointCenter(m_manager);
++ maxDistantPointCenter.setReferencePoint = false;
++ maxDistantPointCenter.referencePt = center;
++ if (!maxDistantPointCenter.apply(positionAttribute, indexAttribute, drawVertexCount,
++ primitiveRestartEnabled, primitiveRestartIndex)) {
++ return false;
++ }
++
++ const float radius = (center - maxDistantPointCenter.maxDistPt).length();
++
++ m_volume = Qt3DRender::Render::Sphere(center, radius);
++
++ if (m_volume.isNull())
++ return false;
++
++ return true;
++ }
++
++ private:
++ Sphere m_volume;
++ NodeManagers *m_manager;
++ QVector3D m_min;
++ QVector3D m_max;
++
++ class FindExtremePoints : public Buffer3fVisitor
++ {
++ public:
++ explicit FindExtremePoints(NodeManagers *manager)
++ : Buffer3fVisitor(manager)
++ , xMin(0.0f), xMax(0.0f), yMin(0.0f), yMax(0.0f), zMin(0.0f), zMax(0.0f)
++ { }
++
++ float xMin, xMax, yMin, yMax, zMin, zMax;
++ Vector3D xMinPt, xMaxPt, yMinPt, yMaxPt, zMinPt, zMaxPt;
++
++ void visit(uint ndx, float x, float y, float z) override
++ {
++ if (ndx) {
++ if (x < xMin) {
++ xMin = x;
++ xMinPt = Vector3D(x, y, z);
++ }
++ if (x > xMax) {
++ xMax = x;
++ xMaxPt = Vector3D(x, y, z);
++ }
++ if (y < yMin) {
++ yMin = y;
++ yMinPt = Vector3D(x, y, z);
++ }
++ if (y > yMax) {
++ yMax = y;
++ yMaxPt = Vector3D(x, y, z);
++ }
++ if (z < zMin) {
++ zMin = z;
++ zMinPt = Vector3D(x, y, z);
++ }
++ if (z > zMax) {
++ zMax = z;
++ zMaxPt = Vector3D(x, y, z);
++ }
++ } else {
++ xMin = xMax = x;
++ yMin = yMax = y;
++ zMin = zMax = z;
++ xMinPt = xMaxPt = yMinPt = yMaxPt = zMinPt = zMaxPt = Vector3D(x, y, z);
++ }
++ }
++ };
++
++ class FindMaxDistantPoint : public Buffer3fVisitor
++ {
++ public:
++ explicit FindMaxDistantPoint(NodeManagers *manager)
++ : Buffer3fVisitor(manager)
++ { }
++
++ float maxLengthSquared = 0.0f;
++ Vector3D maxDistPt;
++ Vector3D referencePt;
++ bool setReferencePoint = false;
++ bool hasNoPoints = true;
++
++ void visit(uint ndx, float x, float y, float z) override
++ {
++ Q_UNUSED(ndx);
++ const Vector3D p = Vector3D(x, y, z);
++
++ if (hasNoPoints && setReferencePoint) {
++ maxLengthSquared = 0.0f;
++ referencePt = p;
++ }
++ const float lengthSquared = (p - referencePt).lengthSquared();
++ if ( lengthSquared >= maxLengthSquared ) {
++ maxDistPt = p;
++ maxLengthSquared = lengthSquared;
++ }
++ hasNoPoints = false;
++ }
++ };
++ };
++
++ struct BoundingVolumeComputeData {
++ Entity *entity = nullptr;
++ GeometryRenderer *renderer = nullptr;
++ Geometry *geometry = nullptr;
++ Attribute *positionAttribute = nullptr;
++ Attribute *indexAttribute = nullptr;
++ int vertexCount = -1;
++
++ bool valid() const { return vertexCount >= 0; }
++ };
++
++ BoundingVolumeComputeData findBoundingVolumeComputeData(NodeManagers *manager, Entity *node)
++ {
++ BoundingVolumeComputeData res;
++ res.entity = node;
++
++ res.renderer = node->renderComponent<GeometryRenderer>();
++ if (!res.renderer || res.renderer->primitiveType() == QGeometryRenderer::Patches)
++ return res;
++
++ GeometryManager *geometryManager = manager->geometryManager();
++ res.geometry = geometryManager->lookupResource(res.renderer->geometryId());
++ if (!res.geometry)
++ return res;
++
++ // if it has a view, the bounding volume will have been computed by the core aspect
++ if (res.renderer->hasView())
++ return res;
++
++ int drawVertexCount = res.renderer->vertexCount(); // may be 0, gets changed below if so
++
++ Qt3DRender::Render::Attribute *positionAttribute = manager->lookupResource<Attribute, AttributeManager>(res.geometry->boundingPositionAttribute());
++ bool hasBoundingVolumePositionAttribute = positionAttribute != nullptr;
++
++ // Use the default position attribute if attribute is null
++ if (!hasBoundingVolumePositionAttribute) {
++ const auto attrIds = res.geometry->attributes();
++ for (const Qt3DCore::QNodeId &attrId : attrIds) {
++ positionAttribute = manager->lookupResource<Attribute, AttributeManager>(attrId);
++ if (positionAttribute &&
++ positionAttribute->name() == QAttribute::defaultPositionAttributeName())
++ break;
++ }
++ }
++
++ if (!positionAttribute
++ || positionAttribute->attributeType() != QAttribute::VertexAttribute
++ || positionAttribute->vertexBaseType() != QAttribute::Float
++ || positionAttribute->vertexSize() < 3) {
++ qWarning("findBoundingVolumeComputeData: Position attribute not suited for bounding volume computation");
++ return res;
++ }
++
++ Buffer *buf = manager->lookupResource<Buffer, BufferManager>(positionAttribute->bufferId());
++ // No point in continuing if the positionAttribute doesn't have a suitable buffer
++ if (!buf) {
++ qWarning("findBoundingVolumeComputeData: Position attribute not referencing a valid buffer");
++ return res;
++ }
++
++ // Check if there is an index attribute.
++ Qt3DRender::Render::Attribute *indexAttribute = nullptr;
++ Buffer *indexBuf = nullptr;
++
++ if (!hasBoundingVolumePositionAttribute) {
++ const QList<Qt3DCore::QNodeId> attributes = res.geometry->attributes();
++
++ for (Qt3DCore::QNodeId attrNodeId : attributes) {
++ Qt3DRender::Render::Attribute *attr = manager->lookupResource<Attribute, AttributeManager>(attrNodeId);
++ if (attr && attr->attributeType() == QAttribute::IndexAttribute) {
++ indexBuf = manager->lookupResource<Buffer, BufferManager>(attr->bufferId());
++ if (indexBuf) {
++ indexAttribute = attr;
++
++ if (!drawVertexCount)
++ drawVertexCount = indexAttribute->count();
++
++ const QAttribute::VertexBaseType validIndexTypes[] = {
++ QAttribute::UnsignedShort,
++ QAttribute::UnsignedInt,
++ QAttribute::UnsignedByte
++ };
++
++ if (std::find(std::begin(validIndexTypes),
++ std::end(validIndexTypes),
++ indexAttribute->vertexBaseType()) == std::end(validIndexTypes)) {
++ qWarning() << "findBoundingVolumeComputeData: Unsupported index attribute type" << indexAttribute->name() << indexAttribute->vertexBaseType();
++ return res;
++ }
++
++ break;
++ }
++ }
++ }
++ }
++
++ if (hasBoundingVolumePositionAttribute || (!indexAttribute && !drawVertexCount))
++ drawVertexCount = positionAttribute->count();
++
++ // Buf will be set to not dirty once it's loaded
++ // in a job executed after this one
++ // We need to recompute the bounding volume
++ // If anything in the GeometryRenderer has changed
++ if (buf->isDirty()
++ || node->isBoundingVolumeDirty()
++ || positionAttribute->isDirty()
++ || res.geometry->isDirty()
++ || res.renderer->isDirty()
++ || (indexAttribute && indexAttribute->isDirty())
++ || (indexBuf && indexBuf->isDirty())) {
++ res.vertexCount = drawVertexCount;
++ res.positionAttribute = positionAttribute;
++ res.indexAttribute = indexAttribute;
++ }
++
++ return res;
++ }
++
++ std::vector<Geometry *> calculateLocalBoundingVolume(NodeManagers *manager, const BoundingVolumeComputeData &data)
++ {
++ // The Bounding volume will only be computed if the position Buffer
++ // isDirty
++
++ std::vector<Geometry *> updatedGeometries;
++
++ BoundingVolumeCalculator reader(manager);
++ if (reader.apply(data.positionAttribute, data.indexAttribute, data.vertexCount,
++ data.renderer->primitiveRestartEnabled(), data.renderer->restartIndexValue())) {
++ data.entity->localBoundingVolume()->setCenter(reader.result().center());
++ data.entity->localBoundingVolume()->setRadius(reader.result().radius());
++ data.entity->unsetBoundingVolumeDirty();
++
++ // Record min/max vertex in Geometry
++ data.geometry->updateExtent(reader.min(), reader.max());
++ // Mark geometry as requiring a call to update its frontend
++ updatedGeometries.push_back(data.geometry);
++ }
++
++ return updatedGeometries;
++ }
++
++ struct UpdateBoundFunctor
++ {
++ NodeManagers *manager;
++
++ // This define is required to work with QtConcurrent
++ typedef std::vector<Geometry *> result_type;
++ std::vector<Geometry *> operator ()(const BoundingVolumeComputeData &data)
++ {
++ return calculateLocalBoundingVolume(manager, data);
++ }
++ };
++
++ struct ReduceUpdateBoundFunctor
++ {
++ void operator ()(std::vector<Geometry *> &result, const std::vector<Geometry *> &values)
++ {
++ result.insert(result.end(),
++ values.begin(),
++ values.end());
++ }
++ };
++
++ class DirtyEntityAccumulator : public EntityVisitor
++ {
++ public:
++ explicit DirtyEntityAccumulator(NodeManagers *manager)
++ : EntityVisitor(manager)
++ {
++ }
++
++ EntityVisitor::Operation visit(Entity *entity) override
++ {
++ if (!entity->isTreeEnabled())
++ return Prune;
++ auto data = findBoundingVolumeComputeData(m_manager, entity);
++
++ if (data.valid()) {
++ // only valid if front end is a QGeometryRenderer without a view. All other cases handled by core aspect
++ m_entities.push_back(data);
++ }
++
++ return Continue;
++ }
++
++ Qt3DCore::QAbstractFrontEndNodeManager *m_frontEndNodeManager = nullptr;
++ std::vector<BoundingVolumeComputeData> m_entities;
++ };
++
++
++ } // anonymous
++
++
++ CalculateBoundingVolumeJob::CalculateBoundingVolumeJob()
++ : Qt3DCore::QAspectJob()
++ , m_manager(nullptr)
++ , m_node(nullptr)
++ , m_frontEndNodeManager(nullptr)
++ {
++ SET_JOB_RUN_STAT_TYPE(this, JobTypes::CalcBoundingVolume, 0)
++ }
++
++ void CalculateBoundingVolumeJob::run()
++ {
++ // There's 2 bounding volume jobs, one in Core, the other here in Render.
++ // This one is setup to run after the other.
++ // (see more details in Qt3DCore::CalculateBoundingVolumeJob::run)
++ //
++ // TODO:
++ // - remove the one frame delay for propagating results of first job
++ // - avoid copying the computed BV at every frame
++
++ Q_ASSERT(m_frontEndNodeManager);
++
++ DirtyEntityAccumulator accumulator(m_manager);
++ accumulator.m_frontEndNodeManager = m_frontEndNodeManager;
++ accumulator.apply(m_node);
++
++ const std::vector<BoundingVolumeComputeData> entities = std::move(accumulator.m_entities);
++
++ std::vector<Geometry *> updatedGeometries;
++ updatedGeometries.reserve(entities.size());
++
++ #if QT_CONFIG(concurrent)
++ if (entities.size() > 1 && QAspectJobManager::idealThreadCount() > 1) {
++ UpdateBoundFunctor functor;
++ functor.manager = m_manager;
++ ReduceUpdateBoundFunctor reduceFunctor;
++ const std::vector<Geometry *> &newGeometries = QtConcurrent::blockingMappedReduced<decltype(updatedGeometries)>(entities, functor, reduceFunctor);
++ updatedGeometries.insert(updatedGeometries.end(),
++ newGeometries.begin(),
++ newGeometries.end());
++ } else
++ #endif
++ {
++ for (const auto &data: entities) {
++ const std::vector<Geometry *> &newGeometries = calculateLocalBoundingVolume(m_manager, data);
++ updatedGeometries.insert(updatedGeometries.end(),
++ newGeometries.begin(),
++ newGeometries.end());
++ }
++ }
++
++ m_updatedGeometries = std::move(updatedGeometries);
++ }
++
++ void CalculateBoundingVolumeJob::postFrame(QAspectEngine *aspectEngine)
++ {
++ Q_UNUSED(aspectEngine);
++ for (Geometry *backend : m_updatedGeometries) {
++ Qt3DCore::QGeometry *node = qobject_cast<Qt3DCore::QGeometry *>(m_frontEndNodeManager->lookupNode(backend->peerId()));
++ if (!node)
++ continue;
++ Qt3DCore::QGeometryPrivate *dNode = static_cast<Qt3DCore::QGeometryPrivate *>(Qt3DCore::QNodePrivate::get(node));
++ dNode->setExtent(backend->min(), backend->max());
++ }
++
++ m_updatedGeometries.clear();
++ }
++
++ void CalculateBoundingVolumeJob::process(const Qt3DCore::BoundingVolumeComputeResult &result, bool computedResult)
++ {
++ // This gets called from the thread of the CalculateBoundingVolumeJob in the core aspect.
++ // We receive the data calculated there and update our backend nodes
++
++ auto entity = m_manager->renderNodesManager()->lookupResource(result.entity->id());
++ if (!entity)
++ return;
++
++ // copy data to the entity
++ entity->localBoundingVolume()->setCenter(Vector3D(result.m_center));
++ entity->localBoundingVolume()->setRadius(std::max(result.m_radius, 0.0f));
++ entity->unsetBoundingVolumeDirty();
++ // copy the data to the geometry
++ if (computedResult) {
++ auto renderer = entity->renderComponent<GeometryRenderer>();
++ if (renderer) {
++ auto geometry = m_manager->geometryManager()->lookupResource(renderer->geometryId());
++
++ if (geometry)
++ geometry->updateExtent(result.m_min, result.m_max);
++ }
++ }
++ }
++
++ void CalculateBoundingVolumeJob::setRoot(Entity *node)
++ {
++ m_node = node;
++ }
++
++ void CalculateBoundingVolumeJob::setManagers(NodeManagers *manager)
++ {
++ m_manager = manager;
++ }
++
++ void CalculateBoundingVolumeJob::setFrontEndNodeManager(Qt3DCore::QAbstractFrontEndNodeManager *manager)
++ {
++ m_frontEndNodeManager = manager;
++ }
++
++ } // namespace Render
++ } // namespace Qt3DRender
++
++ QT_END_NAMESPACE
++