blob: dc58eb6b9533f757b8a6cd74c0e05c70351b321c [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt3D module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "calcboundingvolumejob_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/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 <Qt3DCore/private/qaspectmanager_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 {
namespace {
class BoundingVolumeCalculator
{
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,
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:
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:
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;
Geometry *geometry = nullptr;
Attribute *positionAttribute = nullptr;
Attribute *indexAttribute = nullptr;
bool primitiveRestartEnabled = false;
int primitiveRestartIndex = -1;
int vertexCount = 0;
bool valid() const { return entity != nullptr; }
};
BoundingVolumeComputeData findBoundingVolumeComputeData(NodeManagers *manager, Entity *node)
{
GeometryRenderer *gRenderer = node->renderComponent<GeometryRenderer>();
GeometryManager *geometryManager = manager->geometryManager();
if (!gRenderer || gRenderer->primitiveType() == QGeometryRenderer::Patches)
return {};
Geometry *geom = geometryManager->lookupResource(gRenderer->geometryId());
if (!geom)
return {};
int drawVertexCount = gRenderer->vertexCount(); // may be 0, gets changed below if so
Qt3DRender::Render::Attribute *positionAttribute = manager->lookupResource<Attribute, AttributeManager>(geom->boundingPositionAttribute());
bool hasBoundingVolumePositionAttribute = positionAttribute != nullptr;
// Use the default position attribute if attribute is null
if (!hasBoundingVolumePositionAttribute) {
const auto attrIds = geom->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 {};
}
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 {};
}
// Check if there is an index attribute.
Qt3DRender::Render::Attribute *indexAttribute = nullptr;
Buffer *indexBuf = nullptr;
if (!hasBoundingVolumePositionAttribute) {
const QVector<Qt3DCore::QNodeId> attributes = geom->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 {};
}
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()
|| geom->isDirty()
|| gRenderer->isDirty()
|| (indexAttribute && indexAttribute->isDirty())
|| (indexBuf && indexBuf->isDirty())) {
BoundingVolumeComputeData res;
res.entity = node;
res.geometry = geom;
res.positionAttribute = positionAttribute;
res.indexAttribute = indexAttribute;
res.primitiveRestartEnabled = gRenderer->primitiveRestartEnabled();
res.primitiveRestartIndex = gRenderer->restartIndexValue();
res.vertexCount = drawVertexCount;
return res;
}
return {};
}
QVector<Geometry *> calculateLocalBoundingVolume(NodeManagers *manager, const BoundingVolumeComputeData &data)
{
// The Bounding volume will only be computed if the position Buffer
// isDirty
QVector<Geometry *> updatedGeometries;
BoundingVolumeCalculator reader(manager);
if (reader.apply(data.positionAttribute, data.indexAttribute, data.vertexCount,
data.primitiveRestartEnabled, data.primitiveRestartIndex)) {
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 QVector<Geometry *> result_type;
QVector<Geometry *> operator ()(const BoundingVolumeComputeData &data)
{
return calculateLocalBoundingVolume(manager, data);
}
};
struct ReduceUpdateBoundFunctor
{
void operator ()(QVector<Geometry *> &result, const QVector<Geometry *> &values)
{
result += values;
}
};
class DirtyEntityAccumulator : public EntityVisitor
{
public:
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())
m_entities.push_back(data);
return Continue;
}
std::vector<BoundingVolumeComputeData> m_entities;
};
} // anonymous
CalculateBoundingVolumeJob::CalculateBoundingVolumeJob()
: m_manager(nullptr)
, m_node(nullptr)
{
SET_JOB_RUN_STAT_TYPE(this, JobTypes::CalcBoundingVolume, 0)
}
void CalculateBoundingVolumeJob::run()
{
DirtyEntityAccumulator accumulator(m_manager);
accumulator.apply(m_node);
std::vector<BoundingVolumeComputeData> entities = std::move(accumulator.m_entities);
QVector<Geometry *> updatedGeometries;
updatedGeometries.reserve(entities.size());
#if QT_CONFIG(concurrent)
if (entities.size() > 1) {
UpdateBoundFunctor functor;
functor.manager = m_manager;
ReduceUpdateBoundFunctor reduceFunctor;
updatedGeometries += QtConcurrent::blockingMappedReduced<decltype(updatedGeometries)>(entities, functor, reduceFunctor);
} else
#endif
{
for (const auto &data: entities)
updatedGeometries += calculateLocalBoundingVolume(m_manager, data);
}
// Send extent updates to frontend
for (Geometry *geometry : updatedGeometries)
geometry->notifyExtentChanged();
}
void CalculateBoundingVolumeJob::setRoot(Entity *node)
{
m_node = node;
}
void CalculateBoundingVolumeJob::setManagers(NodeManagers *manager)
{
m_manager = manager;
}
} // namespace Render
} // namespace Qt3DRender
QT_END_NAMESPACE