1// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "updatelevelofdetailjob_p.h"
5#include <Qt3DCore/private/qaspectmanager_p.h>
6#include <Qt3DRender/QLevelOfDetail>
7#include <Qt3DRender/private/entityvisitor_p.h>
8#include <Qt3DRender/private/job_common_p.h>
9#include <Qt3DRender/private/nodemanagers_p.h>
10#include <Qt3DRender/private/managers_p.h>
11#include <Qt3DRender/private/sphere_p.h>
12#include <Qt3DRender/private/pickboundingvolumeutils_p.h>
13
14QT_BEGIN_NAMESPACE
15
16namespace
17{
18
19template <unsigned N>
20double approxRollingAverage(double avg, double input) {
21 avg -= avg / N;
22 avg += input / N;
23 return avg;
24}
25
26class LODUpdateVisitor : public Qt3DRender::Render::EntityVisitor
27{
28public:
29 LODUpdateVisitor(double filterValue, Qt3DRender::Render::FrameGraphNode *frameGraphRoot, Qt3DRender::Render::NodeManagers *manager)
30 : Qt3DRender::Render::EntityVisitor(manager)
31 , m_filterValue(filterValue)
32 , m_frameGraphRoot(frameGraphRoot)
33 {
34 m_updatedIndices.reserve(asize: manager->levelOfDetailManager()->count());
35 }
36
37 double filterValue() const { return m_filterValue; }
38 const QList<QPair<Qt3DCore::QNodeId, int>> &updatedIndices() const { return m_updatedIndices; }
39
40 Operation visit(Qt3DRender::Render::Entity *entity = nullptr) override {
41 using namespace Qt3DRender;
42 using namespace Qt3DRender::Render;
43
44 if (!entity->isEnabled())
45 return Prune; // skip disabled sub-trees, since their bounding box is probably not valid anyway
46
47 const std::vector<LevelOfDetail *> &lods = entity->renderComponents<LevelOfDetail>();
48 if (!lods.empty()) {
49 LevelOfDetail* lod = lods.front(); // other lods are ignored
50
51 if (lod->isEnabled() && !lod->thresholds().isEmpty()) {
52 switch (lod->thresholdType()) {
53 case QLevelOfDetail::DistanceToCameraThreshold:
54 updateEntityLodByDistance(entity, lod);
55 break;
56 case QLevelOfDetail::ProjectedScreenPixelSizeThreshold:
57 updateEntityLodByScreenArea(entity, lod);
58 break;
59 default:
60 Q_ASSERT(false);
61 break;
62 }
63 }
64 }
65
66 return Continue;
67 }
68
69private:
70 double m_filterValue = 0.;
71 Qt3DRender::Render::FrameGraphNode *m_frameGraphRoot;
72 QList<QPair<Qt3DCore::QNodeId, int>> m_updatedIndices;
73
74 void updateEntityLodByDistance(Qt3DRender::Render::Entity *entity, Qt3DRender::Render::LevelOfDetail *lod)
75 {
76 using namespace Qt3DRender;
77 using namespace Qt3DRender::Render;
78
79 Matrix4x4 viewMatrix;
80 Matrix4x4 projectionMatrix;
81 if (!Render::CameraLens::viewMatrixForCamera(manager: m_manager->renderNodesManager(), cameraId: lod->camera(), viewMatrix, projectionMatrix))
82 return;
83
84 const QList<qreal> thresholds = lod->thresholds();
85 Vector3D center(lod->center());
86 if (lod->hasBoundingVolumeOverride() || entity->worldBoundingVolume() == nullptr) {
87 center = entity->worldTransform()->map(point: center);
88 } else {
89 center = entity->worldBoundingVolume()->center();
90 }
91
92 const Vector3D tcenter = viewMatrix.map(point: center);
93 const double dist = double(tcenter.length());
94 const qsizetype n = thresholds.size();
95 for (qsizetype i = 0; i < n; ++i) {
96 if (dist <= thresholds[i] || i == n -1) {
97 m_filterValue = approxRollingAverage<30>(avg: m_filterValue, input: i);
98 i = qBound(min: 0, val: static_cast<int>(qRound(d: m_filterValue)), max: n - 1);
99 if (lod->currentIndex() != i) {
100 lod->setCurrentIndex(i);
101 m_updatedIndices.push_back(t: {lod->peerId(), i});
102 }
103 break;
104 }
105 }
106 }
107
108 void updateEntityLodByScreenArea(Qt3DRender::Render::Entity *entity, Qt3DRender::Render::LevelOfDetail *lod)
109 {
110 using namespace Qt3DRender;
111 using namespace Qt3DRender::Render;
112
113 Matrix4x4 viewMatrix;
114 Matrix4x4 projectionMatrix;
115 if (!Render::CameraLens::viewMatrixForCamera(manager: m_manager->renderNodesManager(), cameraId: lod->camera(), viewMatrix, projectionMatrix))
116 return;
117
118 PickingUtils::ViewportCameraAreaGatherer vcaGatherer(lod->camera());
119 const std::vector<PickingUtils::ViewportCameraAreaDetails> &vcaTriplets = vcaGatherer.gather(root: m_frameGraphRoot);
120 if (vcaTriplets.empty())
121 return;
122
123 const PickingUtils::ViewportCameraAreaDetails &vca = vcaTriplets.front();
124
125 const QList<qreal> thresholds = lod->thresholds();
126 Sphere bv(Vector3D(lod->center()), lod->radius());
127 if (!lod->hasBoundingVolumeOverride() && entity->worldBoundingVolume() != nullptr) {
128 bv = *(entity->worldBoundingVolume());
129 } else {
130 bv.transform(mat: *entity->worldTransform());
131 }
132
133 bv.transform(mat: projectionMatrix * viewMatrix);
134 const float sideLength = bv.radius() * 2.f;
135 float area = vca.viewport.width() * sideLength * vca.viewport.height() * sideLength;
136
137 const QRect r = windowViewport(area: vca.area, relativeViewport: vca.viewport);
138 area = std::sqrt(x: area * float(r.width()) * float(r.height()));
139
140 const qsizetype n = thresholds.size();
141 for (qsizetype i = 0; i < n; ++i) {
142 if (thresholds[i] < area || i == n -1) {
143 m_filterValue = approxRollingAverage<30>(avg: m_filterValue, input: i);
144 i = qBound(min: 0, val: static_cast<int>(qRound(d: m_filterValue)), max: n - 1);
145 if (lod->currentIndex() != i) {
146 lod->setCurrentIndex(i);
147 m_updatedIndices.push_back(t: {lod->peerId(), i});
148 }
149 break;
150 }
151 }
152 }
153
154 QRect windowViewport(const QSize &area, const QRectF &relativeViewport) const
155 {
156 if (area.isValid()) {
157 const int areaWidth = area.width();
158 const int areaHeight = area.height();
159 return QRect(relativeViewport.x() * areaWidth,
160 (1.0 - relativeViewport.y() - relativeViewport.height()) * areaHeight,
161 relativeViewport.width() * areaWidth,
162 relativeViewport.height() * areaHeight);
163 }
164 return relativeViewport.toRect();
165 }
166};
167
168}
169
170
171namespace Qt3DRender {
172namespace Render {
173
174class UpdateLevelOfDetailJobPrivate : public Qt3DCore::QAspectJobPrivate
175{
176public:
177 UpdateLevelOfDetailJobPrivate(UpdateLevelOfDetailJob *q) : q_ptr(q) { }
178
179 bool isRequired() const override;
180 void postFrame(Qt3DCore::QAspectManager *manager) override;
181
182 QList<QPair<Qt3DCore::QNodeId, int>> m_updatedIndices;
183
184 UpdateLevelOfDetailJob *q_ptr;
185 Q_DECLARE_PUBLIC(UpdateLevelOfDetailJob)
186};
187
188UpdateLevelOfDetailJob::UpdateLevelOfDetailJob()
189 : Qt3DCore::QAspectJob(*new UpdateLevelOfDetailJobPrivate(this))
190 , m_manager(nullptr)
191 , m_frameGraphRoot(nullptr)
192 , m_root(nullptr)
193 , m_filterValue(0.)
194{
195 SET_JOB_RUN_STAT_TYPE(this, JobTypes::UpdateLevelOfDetail, 0)
196}
197
198UpdateLevelOfDetailJob::~UpdateLevelOfDetailJob()
199{
200}
201
202void UpdateLevelOfDetailJob::setRoot(Entity *root)
203{
204 m_root = root;
205}
206
207void UpdateLevelOfDetailJob::setManagers(NodeManagers *manager)
208{
209 m_manager = manager;
210}
211
212void UpdateLevelOfDetailJob::setFrameGraphRoot(FrameGraphNode *frameGraphRoot)
213{
214 m_frameGraphRoot = frameGraphRoot;
215}
216
217void UpdateLevelOfDetailJob::run()
218{
219 Q_D(UpdateLevelOfDetailJob);
220
221 Q_ASSERT(m_frameGraphRoot && m_root && m_manager);
222
223 // short-circuit if no LoDs exist
224 if (m_manager->levelOfDetailManager()->count() == 0)
225 return;
226
227 LODUpdateVisitor visitor(m_filterValue, m_frameGraphRoot, m_manager);
228 visitor.apply(root: m_root);
229 m_filterValue = visitor.filterValue();
230 d->m_updatedIndices = visitor.updatedIndices();
231}
232
233bool UpdateLevelOfDetailJobPrivate::isRequired() const
234{
235 Q_Q(const UpdateLevelOfDetailJob);
236 return q->m_manager->levelOfDetailManager()->count() > 0;
237}
238
239void UpdateLevelOfDetailJobPrivate::postFrame(Qt3DCore::QAspectManager *manager)
240{
241 for (const auto &updatedNode: std::as_const(t&: m_updatedIndices)) {
242 QLevelOfDetail *node = qobject_cast<QLevelOfDetail *>(object: manager->lookupNode(id: updatedNode.first));
243 if (!node)
244 continue;
245
246 node->setCurrentIndex(updatedNode.second);
247 }
248}
249
250} // Render
251} // Qt3DRender
252
253QT_END_NAMESPACE
254

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