1// Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
2// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "calcboundingvolumejob_p.h"
6
7#include <Qt3DCore/qboundingvolume.h>
8#include <Qt3DCore/private/qabstractfrontendnodemanager_p.h>
9#include <Qt3DCore/private/qgeometry_p.h>
10#include <Qt3DCore/private/qaspectjobmanager_p.h>
11#include <Qt3DRender/private/nodemanagers_p.h>
12#include <Qt3DRender/private/entity_p.h>
13#include <Qt3DRender/private/renderlogging_p.h>
14#include <Qt3DRender/private/managers_p.h>
15#include <Qt3DRender/private/qgeometryrenderer_p.h>
16#include <Qt3DRender/private/geometryrenderer_p.h>
17#include <Qt3DRender/private/geometry_p.h>
18#include <Qt3DRender/private/buffermanager_p.h>
19#include <Qt3DRender/private/attribute_p.h>
20#include <Qt3DRender/private/buffer_p.h>
21#include <Qt3DRender/private/sphere_p.h>
22#include <Qt3DRender/private/buffervisitor_p.h>
23#include <Qt3DRender/private/entityvisitor_p.h>
24
25#include <QtCore/qmath.h>
26#if QT_CONFIG(concurrent)
27#include <QtConcurrent/QtConcurrent>
28#endif
29#include <Qt3DRender/private/job_common_p.h>
30
31QT_BEGIN_NAMESPACE
32
33using namespace Qt3DCore;
34
35namespace Qt3DRender {
36namespace Render {
37
38namespace {
39
40class BoundingVolumeCalculator
41{
42public:
43 explicit BoundingVolumeCalculator(NodeManagers *manager) : m_manager(manager) { }
44
45 const Sphere& result() { return m_volume; }
46 const QVector3D min() const { return m_min; }
47 const QVector3D max() const { return m_max; }
48
49 bool apply(Qt3DRender::Render::Attribute *positionAttribute,
50 Qt3DRender::Render::Attribute *indexAttribute,
51 int drawVertexCount,
52 bool primitiveRestartEnabled,
53 int primitiveRestartIndex)
54 {
55 FindExtremePoints findExtremePoints(m_manager);
56 if (!findExtremePoints.apply(attribute: positionAttribute, indexAttribute, drawVertexCount,
57 primitiveRestartEnabled, primitiveRestartIndex))
58 return false;
59
60 m_min = QVector3D(findExtremePoints.xMin, findExtremePoints.yMin, findExtremePoints.zMin);
61 m_max = QVector3D(findExtremePoints.xMax, findExtremePoints.yMax, findExtremePoints.zMax);
62
63 FindMaxDistantPoint maxDistantPointY(m_manager);
64 maxDistantPointY.setReferencePoint = true;
65 if (!maxDistantPointY.apply(attribute: positionAttribute, indexAttribute, drawVertexCount,
66 primitiveRestartEnabled, primitiveRestartIndex))
67 return false;
68 if (maxDistantPointY.hasNoPoints)
69 return false;
70
71 //const Vector3D x = maxDistantPointY.referencePt;
72 const Vector3D y = maxDistantPointY.maxDistPt;
73
74 FindMaxDistantPoint maxDistantPointZ(m_manager);
75 maxDistantPointZ.setReferencePoint = false;
76 maxDistantPointZ.referencePt = y;
77 if (!maxDistantPointZ.apply(attribute: positionAttribute, indexAttribute, drawVertexCount,
78 primitiveRestartEnabled, primitiveRestartIndex)) {
79 return false;
80 }
81 const Vector3D z = maxDistantPointZ.maxDistPt;
82
83 const Vector3D center = (y + z) * 0.5f;
84
85 FindMaxDistantPoint maxDistantPointCenter(m_manager);
86 maxDistantPointCenter.setReferencePoint = false;
87 maxDistantPointCenter.referencePt = center;
88 if (!maxDistantPointCenter.apply(attribute: positionAttribute, indexAttribute, drawVertexCount,
89 primitiveRestartEnabled, primitiveRestartIndex)) {
90 return false;
91 }
92
93 const float radius = (center - maxDistantPointCenter.maxDistPt).length();
94
95 m_volume = Qt3DRender::Render::Sphere(center, radius);
96
97 if (m_volume.isNull())
98 return false;
99
100 return true;
101 }
102
103private:
104 Sphere m_volume;
105 NodeManagers *m_manager;
106 QVector3D m_min;
107 QVector3D m_max;
108
109 class FindExtremePoints : public Buffer3fVisitor
110 {
111 public:
112 explicit FindExtremePoints(NodeManagers *manager)
113 : Buffer3fVisitor(manager)
114 , xMin(0.0f), xMax(0.0f), yMin(0.0f), yMax(0.0f), zMin(0.0f), zMax(0.0f)
115 { }
116
117 float xMin, xMax, yMin, yMax, zMin, zMax;
118 Vector3D xMinPt, xMaxPt, yMinPt, yMaxPt, zMinPt, zMaxPt;
119
120 void visit(uint ndx, float x, float y, float z) override
121 {
122 if (ndx) {
123 if (x < xMin) {
124 xMin = x;
125 xMinPt = Vector3D(x, y, z);
126 }
127 if (x > xMax) {
128 xMax = x;
129 xMaxPt = Vector3D(x, y, z);
130 }
131 if (y < yMin) {
132 yMin = y;
133 yMinPt = Vector3D(x, y, z);
134 }
135 if (y > yMax) {
136 yMax = y;
137 yMaxPt = Vector3D(x, y, z);
138 }
139 if (z < zMin) {
140 zMin = z;
141 zMinPt = Vector3D(x, y, z);
142 }
143 if (z > zMax) {
144 zMax = z;
145 zMaxPt = Vector3D(x, y, z);
146 }
147 } else {
148 xMin = xMax = x;
149 yMin = yMax = y;
150 zMin = zMax = z;
151 xMinPt = xMaxPt = yMinPt = yMaxPt = zMinPt = zMaxPt = Vector3D(x, y, z);
152 }
153 }
154 };
155
156 class FindMaxDistantPoint : public Buffer3fVisitor
157 {
158 public:
159 explicit FindMaxDistantPoint(NodeManagers *manager)
160 : Buffer3fVisitor(manager)
161 { }
162
163 float maxLengthSquared = 0.0f;
164 Vector3D maxDistPt;
165 Vector3D referencePt;
166 bool setReferencePoint = false;
167 bool hasNoPoints = true;
168
169 void visit(uint ndx, float x, float y, float z) override
170 {
171 Q_UNUSED(ndx);
172 const Vector3D p = Vector3D(x, y, z);
173
174 if (hasNoPoints && setReferencePoint) {
175 maxLengthSquared = 0.0f;
176 referencePt = p;
177 }
178 const float lengthSquared = (p - referencePt).lengthSquared();
179 if ( lengthSquared >= maxLengthSquared ) {
180 maxDistPt = p;
181 maxLengthSquared = lengthSquared;
182 }
183 hasNoPoints = false;
184 }
185 };
186};
187
188struct BoundingVolumeComputeData {
189 Entity *entity = nullptr;
190 GeometryRenderer *renderer = nullptr;
191 Geometry *geometry = nullptr;
192 Attribute *positionAttribute = nullptr;
193 Attribute *indexAttribute = nullptr;
194 int vertexCount = -1;
195
196 bool valid() const { return vertexCount >= 0; }
197};
198
199BoundingVolumeComputeData findBoundingVolumeComputeData(NodeManagers *manager, Entity *node)
200{
201 BoundingVolumeComputeData res;
202 res.entity = node;
203
204 res.renderer = node->renderComponent<GeometryRenderer>();
205 if (!res.renderer || res.renderer->primitiveType() == QGeometryRenderer::Patches)
206 return res;
207
208 GeometryManager *geometryManager = manager->geometryManager();
209 res.geometry = geometryManager->lookupResource(id: res.renderer->geometryId());
210 if (!res.geometry)
211 return res;
212
213 // if it has a view, the bounding volume will have been computed by the core aspect
214 if (res.renderer->hasView())
215 return res;
216
217 int drawVertexCount = res.renderer->vertexCount(); // may be 0, gets changed below if so
218
219 Qt3DRender::Render::Attribute *positionAttribute = manager->lookupResource<Attribute, AttributeManager>(id: res.geometry->boundingPositionAttribute());
220 bool hasBoundingVolumePositionAttribute = positionAttribute != nullptr;
221
222 // Use the default position attribute if attribute is null
223 if (!hasBoundingVolumePositionAttribute) {
224 const auto attrIds = res.geometry->attributes();
225 for (const Qt3DCore::QNodeId &attrId : attrIds) {
226 positionAttribute = manager->lookupResource<Attribute, AttributeManager>(id: attrId);
227 if (positionAttribute &&
228 positionAttribute->name() == QAttribute::defaultPositionAttributeName())
229 break;
230 }
231 }
232
233 if (!positionAttribute
234 || positionAttribute->attributeType() != QAttribute::VertexAttribute
235 || positionAttribute->vertexBaseType() != QAttribute::Float
236 || positionAttribute->vertexSize() < 3) {
237 qWarning(msg: "findBoundingVolumeComputeData: Position attribute not suited for bounding volume computation");
238 return res;
239 }
240
241 Buffer *buf = manager->lookupResource<Buffer, BufferManager>(id: positionAttribute->bufferId());
242 // No point in continuing if the positionAttribute doesn't have a suitable buffer
243 if (!buf) {
244 qWarning(msg: "findBoundingVolumeComputeData: Position attribute not referencing a valid buffer");
245 return res;
246 }
247
248 // Check if there is an index attribute.
249 Qt3DRender::Render::Attribute *indexAttribute = nullptr;
250 Buffer *indexBuf = nullptr;
251
252 if (!hasBoundingVolumePositionAttribute) {
253 const QList<Qt3DCore::QNodeId> attributes = res.geometry->attributes();
254
255 for (Qt3DCore::QNodeId attrNodeId : attributes) {
256 Qt3DRender::Render::Attribute *attr = manager->lookupResource<Attribute, AttributeManager>(id: attrNodeId);
257 if (attr && attr->attributeType() == QAttribute::IndexAttribute) {
258 indexBuf = manager->lookupResource<Buffer, BufferManager>(id: attr->bufferId());
259 if (indexBuf) {
260 indexAttribute = attr;
261
262 if (!drawVertexCount)
263 drawVertexCount = indexAttribute->count();
264
265 const QAttribute::VertexBaseType validIndexTypes[] = {
266 QAttribute::UnsignedShort,
267 QAttribute::UnsignedInt,
268 QAttribute::UnsignedByte
269 };
270
271 if (std::find(first: std::begin(arr: validIndexTypes),
272 last: std::end(arr: validIndexTypes),
273 val: indexAttribute->vertexBaseType()) == std::end(arr: validIndexTypes)) {
274 qWarning() << "findBoundingVolumeComputeData: Unsupported index attribute type" << indexAttribute->name() << indexAttribute->vertexBaseType();
275 return res;
276 }
277
278 break;
279 }
280 }
281 }
282 }
283
284 if (hasBoundingVolumePositionAttribute || (!indexAttribute && !drawVertexCount))
285 drawVertexCount = positionAttribute->count();
286
287 // Buf will be set to not dirty once it's loaded
288 // in a job executed after this one
289 // We need to recompute the bounding volume
290 // If anything in the GeometryRenderer has changed
291 if (buf->isDirty()
292 || node->isBoundingVolumeDirty()
293 || positionAttribute->isDirty()
294 || res.geometry->isDirty()
295 || res.renderer->isDirty()
296 || (indexAttribute && indexAttribute->isDirty())
297 || (indexBuf && indexBuf->isDirty())) {
298 res.vertexCount = drawVertexCount;
299 res.positionAttribute = positionAttribute;
300 res.indexAttribute = indexAttribute;
301 }
302
303 return res;
304}
305
306std::vector<Geometry *> calculateLocalBoundingVolume(NodeManagers *manager, const BoundingVolumeComputeData &data)
307{
308 // The Bounding volume will only be computed if the position Buffer
309 // isDirty
310
311 std::vector<Geometry *> updatedGeometries;
312
313 BoundingVolumeCalculator reader(manager);
314 if (reader.apply(positionAttribute: data.positionAttribute, indexAttribute: data.indexAttribute, drawVertexCount: data.vertexCount,
315 primitiveRestartEnabled: data.renderer->primitiveRestartEnabled(), primitiveRestartIndex: data.renderer->restartIndexValue())) {
316 data.entity->localBoundingVolume()->setCenter(reader.result().center());
317 data.entity->localBoundingVolume()->setRadius(reader.result().radius());
318 data.entity->unsetBoundingVolumeDirty();
319
320 // Record min/max vertex in Geometry
321 data.geometry->updateExtent(min: reader.min(), max: reader.max());
322 // Mark geometry as requiring a call to update its frontend
323 updatedGeometries.push_back(x: data.geometry);
324 }
325
326 return updatedGeometries;
327}
328
329struct UpdateBoundFunctor
330{
331 NodeManagers *manager;
332
333 // This define is required to work with QtConcurrent
334 typedef std::vector<Geometry *> result_type;
335 std::vector<Geometry *> operator ()(const BoundingVolumeComputeData &data)
336 {
337 return calculateLocalBoundingVolume(manager, data);
338 }
339};
340
341struct ReduceUpdateBoundFunctor
342{
343 void operator ()(std::vector<Geometry *> &result, const std::vector<Geometry *> &values)
344 {
345 result.insert(position: result.end(),
346 first: values.begin(),
347 last: values.end());
348 }
349};
350
351class DirtyEntityAccumulator : public EntityVisitor
352{
353public:
354 explicit DirtyEntityAccumulator(NodeManagers *manager)
355 : EntityVisitor(manager)
356 {
357 }
358
359 EntityVisitor::Operation visit(Entity *entity) override
360 {
361 if (!entity->isTreeEnabled())
362 return Prune;
363 auto data = findBoundingVolumeComputeData(manager: m_manager, node: entity);
364
365 if (data.valid()) {
366 // only valid if front end is a QGeometryRenderer without a view. All other cases handled by core aspect
367 m_entities.push_back(x: data);
368 }
369
370 return Continue;
371 }
372
373 Qt3DCore::QAbstractFrontEndNodeManager *m_frontEndNodeManager = nullptr;
374 std::vector<BoundingVolumeComputeData> m_entities;
375};
376
377
378} // anonymous
379
380
381CalculateBoundingVolumeJob::CalculateBoundingVolumeJob()
382 : Qt3DCore::QAspectJob()
383 , m_manager(nullptr)
384 , m_node(nullptr)
385 , m_frontEndNodeManager(nullptr)
386{
387 SET_JOB_RUN_STAT_TYPE(this, JobTypes::CalcBoundingVolume, 0)
388}
389
390void CalculateBoundingVolumeJob::run()
391{
392 // There's 2 bounding volume jobs, one in Core, the other here in Render.
393 // This one is setup to run after the other.
394 // (see more details in Qt3DCore::CalculateBoundingVolumeJob::run)
395 //
396 // TODO:
397 // - remove the one frame delay for propagating results of first job
398 // - avoid copying the computed BV at every frame
399
400 Q_ASSERT(m_frontEndNodeManager);
401
402 DirtyEntityAccumulator accumulator(m_manager);
403 accumulator.m_frontEndNodeManager = m_frontEndNodeManager;
404 accumulator.apply(root: m_node);
405
406 const std::vector<BoundingVolumeComputeData> entities = std::move(accumulator.m_entities);
407
408 std::vector<Geometry *> updatedGeometries;
409 updatedGeometries.reserve(n: entities.size());
410
411#if QT_CONFIG(concurrent)
412 if (entities.size() > 1 && QAspectJobManager::idealThreadCount() > 1) {
413 UpdateBoundFunctor functor;
414 functor.manager = m_manager;
415 ReduceUpdateBoundFunctor reduceFunctor;
416 const std::vector<Geometry *> &newGeometries = QtConcurrent::blockingMappedReduced<decltype(updatedGeometries)>(sequence: entities, map&: functor, reduce&: reduceFunctor);
417 updatedGeometries.insert(position: updatedGeometries.end(),
418 first: newGeometries.begin(),
419 last: newGeometries.end());
420 } else
421#endif
422 {
423 for (const auto &data: entities) {
424 const std::vector<Geometry *> &newGeometries = calculateLocalBoundingVolume(manager: m_manager, data);
425 updatedGeometries.insert(position: updatedGeometries.end(),
426 first: newGeometries.begin(),
427 last: newGeometries.end());
428 }
429 }
430
431 m_updatedGeometries = std::move(updatedGeometries);
432}
433
434void CalculateBoundingVolumeJob::postFrame(QAspectEngine *aspectEngine)
435{
436 Q_UNUSED(aspectEngine);
437 for (Geometry *backend : m_updatedGeometries) {
438 Qt3DCore::QGeometry *node = qobject_cast<Qt3DCore::QGeometry *>(object: m_frontEndNodeManager->lookupNode(id: backend->peerId()));
439 if (!node)
440 continue;
441 Qt3DCore::QGeometryPrivate *dNode = static_cast<Qt3DCore::QGeometryPrivate *>(Qt3DCore::QNodePrivate::get(q: node));
442 dNode->setExtent(minExtent: backend->min(), maxExtent: backend->max());
443 }
444
445 m_updatedGeometries.clear();
446}
447
448void CalculateBoundingVolumeJob::process(const Qt3DCore::BoundingVolumeComputeResult &result, bool computedResult)
449{
450 // This gets called from the thread of the CalculateBoundingVolumeJob in the core aspect.
451 // We receive the data calculated there and update our backend nodes
452
453 auto entity = m_manager->renderNodesManager()->lookupResource(id: result.entity->id());
454 if (!entity)
455 return;
456
457 // copy data to the entity
458 entity->localBoundingVolume()->setCenter(Vector3D(result.m_center));
459 entity->localBoundingVolume()->setRadius(std::max(a: result.m_radius, b: 0.0f));
460 entity->unsetBoundingVolumeDirty();
461 // copy the data to the geometry
462 if (computedResult) {
463 auto renderer = entity->renderComponent<GeometryRenderer>();
464 if (renderer) {
465 auto geometry = m_manager->geometryManager()->lookupResource(id: renderer->geometryId());
466
467 if (geometry)
468 geometry->updateExtent(min: result.m_min, max: result.m_max);
469 }
470 }
471}
472
473void CalculateBoundingVolumeJob::setRoot(Entity *node)
474{
475 m_node = node;
476}
477
478void CalculateBoundingVolumeJob::setManagers(NodeManagers *manager)
479{
480 m_manager = manager;
481}
482
483void CalculateBoundingVolumeJob::setFrontEndNodeManager(Qt3DCore::QAbstractFrontEndNodeManager *manager)
484{
485 m_frontEndNodeManager = manager;
486}
487
488} // namespace Render
489} // namespace Qt3DRender
490
491QT_END_NAMESPACE
492
493

source code of qt3d/src/render/jobs/calcboundingvolumejob.cpp