1/****************************************************************************
2**
3** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt3D module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "updatelevelofdetailjob_p.h"
41#include <Qt3DCore/private/qaspectmanager_p.h>
42#include <Qt3DRender/QLevelOfDetail>
43#include <Qt3DRender/private/entityvisitor_p.h>
44#include <Qt3DRender/private/job_common_p.h>
45#include <Qt3DRender/private/nodemanagers_p.h>
46#include <Qt3DRender/private/managers_p.h>
47#include <Qt3DRender/private/sphere_p.h>
48#include <Qt3DRender/private/pickboundingvolumeutils_p.h>
49
50QT_BEGIN_NAMESPACE
51
52namespace
53{
54
55template <unsigned N>
56double approxRollingAverage(double avg, double input) {
57 avg -= avg / N;
58 avg += input / N;
59 return avg;
60}
61
62class LODUpdateVisitor : public Qt3DRender::Render::EntityVisitor
63{
64public:
65 LODUpdateVisitor(double filterValue, Qt3DRender::Render::FrameGraphNode *frameGraphRoot, Qt3DRender::Render::NodeManagers *manager)
66 : Qt3DRender::Render::EntityVisitor(manager)
67 , m_filterValue(filterValue)
68 , m_frameGraphRoot(frameGraphRoot)
69 {
70 m_updatedIndices.reserve(asize: manager->levelOfDetailManager()->count());
71 }
72
73 double filterValue() const { return m_filterValue; }
74 const QVector<QPair<Qt3DCore::QNodeId, int>> &updatedIndices() const { return m_updatedIndices; }
75
76 Operation visit(Qt3DRender::Render::Entity *entity = nullptr) override {
77 using namespace Qt3DRender;
78 using namespace Qt3DRender::Render;
79
80 if (!entity->isEnabled())
81 return Prune; // skip disabled sub-trees, since their bounding box is probably not valid anyway
82
83 QVector<LevelOfDetail *> lods = entity->renderComponents<LevelOfDetail>();
84 if (!lods.empty()) {
85 LevelOfDetail* lod = lods.front(); // other lods are ignored
86
87 if (lod->isEnabled() && !lod->thresholds().isEmpty()) {
88 switch (lod->thresholdType()) {
89 case QLevelOfDetail::DistanceToCameraThreshold:
90 updateEntityLodByDistance(entity, lod);
91 break;
92 case QLevelOfDetail::ProjectedScreenPixelSizeThreshold:
93 updateEntityLodByScreenArea(entity, lod);
94 break;
95 default:
96 Q_ASSERT(false);
97 break;
98 }
99 }
100 }
101
102 return Continue;
103 }
104
105private:
106 double m_filterValue = 0.;
107 Qt3DRender::Render::FrameGraphNode *m_frameGraphRoot;
108 QVector<QPair<Qt3DCore::QNodeId, int>> m_updatedIndices;
109
110 void updateEntityLodByDistance(Qt3DRender::Render::Entity *entity, Qt3DRender::Render::LevelOfDetail *lod)
111 {
112 using namespace Qt3DRender;
113 using namespace Qt3DRender::Render;
114
115 Matrix4x4 viewMatrix;
116 Matrix4x4 projectionMatrix;
117 if (!Render::CameraLens::viewMatrixForCamera(manager: m_manager->renderNodesManager(), cameraId: lod->camera(), viewMatrix, projectionMatrix))
118 return;
119
120 const QVector<qreal> thresholds = lod->thresholds();
121 Vector3D center(lod->center());
122 if (lod->hasBoundingVolumeOverride() || entity->worldBoundingVolume() == nullptr) {
123 center = *entity->worldTransform() * center;
124 } else {
125 center = entity->worldBoundingVolume()->center();
126 }
127
128 const Vector3D tcenter = viewMatrix * center;
129 const float dist = tcenter.length();
130 const int n = thresholds.size();
131 for (int i=0; i<n; ++i) {
132 if (dist <= thresholds[i] || i == n -1) {
133 m_filterValue = approxRollingAverage<30>(avg: m_filterValue, input: i);
134 i = qBound(min: 0, val: static_cast<int>(qRound(d: m_filterValue)), max: n - 1);
135 if (lod->currentIndex() != i) {
136 lod->setCurrentIndex(i);
137 m_updatedIndices.push_back(t: {lod->peerId(), i});
138 }
139 break;
140 }
141 }
142 }
143
144 void updateEntityLodByScreenArea(Qt3DRender::Render::Entity *entity, Qt3DRender::Render::LevelOfDetail *lod)
145 {
146 using namespace Qt3DRender;
147 using namespace Qt3DRender::Render;
148
149 Matrix4x4 viewMatrix;
150 Matrix4x4 projectionMatrix;
151 if (!Render::CameraLens::viewMatrixForCamera(manager: m_manager->renderNodesManager(), cameraId: lod->camera(), viewMatrix, projectionMatrix))
152 return;
153
154 PickingUtils::ViewportCameraAreaGatherer vcaGatherer(lod->camera());
155 const QVector<PickingUtils::ViewportCameraAreaDetails> vcaTriplets = vcaGatherer.gather(root: m_frameGraphRoot);
156 if (vcaTriplets.isEmpty())
157 return;
158
159 const PickingUtils::ViewportCameraAreaDetails &vca = vcaTriplets.front();
160
161 const QVector<qreal> thresholds = lod->thresholds();
162 Sphere bv(Vector3D(lod->center()), lod->radius());
163 if (!lod->hasBoundingVolumeOverride() && entity->worldBoundingVolume() != nullptr) {
164 bv = *(entity->worldBoundingVolume());
165 } else {
166 bv.transform(mat: *entity->worldTransform());
167 }
168
169 bv.transform(mat: projectionMatrix * viewMatrix);
170 const float sideLength = bv.radius() * 2.f;
171 float area = vca.viewport.width() * sideLength * vca.viewport.height() * sideLength;
172
173 const QRect r = windowViewport(area: vca.area, relativeViewport: vca.viewport);
174 area = std::sqrt(x: area * r.width() * r.height());
175
176 const int n = thresholds.size();
177 for (int i = 0; i < n; ++i) {
178 if (thresholds[i] < area || i == n -1) {
179 m_filterValue = approxRollingAverage<30>(avg: m_filterValue, input: i);
180 i = qBound(min: 0, val: static_cast<int>(qRound(d: m_filterValue)), max: n - 1);
181 if (lod->currentIndex() != i) {
182 lod->setCurrentIndex(i);
183 m_updatedIndices.push_back(t: {lod->peerId(), i});
184 }
185 break;
186 }
187 }
188 }
189
190 QRect windowViewport(const QSize &area, const QRectF &relativeViewport) const
191 {
192 if (area.isValid()) {
193 const int areaWidth = area.width();
194 const int areaHeight = area.height();
195 return QRect(relativeViewport.x() * areaWidth,
196 (1.0 - relativeViewport.y() - relativeViewport.height()) * areaHeight,
197 relativeViewport.width() * areaWidth,
198 relativeViewport.height() * areaHeight);
199 }
200 return relativeViewport.toRect();
201 }
202};
203
204}
205
206
207namespace Qt3DRender {
208namespace Render {
209
210class UpdateLevelOfDetailJobPrivate : public Qt3DCore::QAspectJobPrivate
211{
212public:
213 UpdateLevelOfDetailJobPrivate(UpdateLevelOfDetailJob *q) : q_ptr(q) { }
214
215 bool isRequired() const override;
216 void postFrame(Qt3DCore::QAspectManager *manager) override;
217
218 QVector<QPair<Qt3DCore::QNodeId, int>> m_updatedIndices;
219
220 UpdateLevelOfDetailJob *q_ptr;
221 Q_DECLARE_PUBLIC(UpdateLevelOfDetailJob)
222};
223
224UpdateLevelOfDetailJob::UpdateLevelOfDetailJob()
225 : Qt3DCore::QAspectJob(*new UpdateLevelOfDetailJobPrivate(this))
226 , m_manager(nullptr)
227 , m_frameGraphRoot(nullptr)
228 , m_root(nullptr)
229 , m_filterValue(0.)
230{
231 SET_JOB_RUN_STAT_TYPE(this, JobTypes::UpdateLevelOfDetail, 0)
232}
233
234UpdateLevelOfDetailJob::~UpdateLevelOfDetailJob()
235{
236}
237
238void UpdateLevelOfDetailJob::setRoot(Entity *root)
239{
240 m_root = root;
241}
242
243void UpdateLevelOfDetailJob::setManagers(NodeManagers *manager)
244{
245 m_manager = manager;
246}
247
248void UpdateLevelOfDetailJob::setFrameGraphRoot(FrameGraphNode *frameGraphRoot)
249{
250 m_frameGraphRoot = frameGraphRoot;
251}
252
253void UpdateLevelOfDetailJob::run()
254{
255 Q_D(UpdateLevelOfDetailJob);
256
257 Q_ASSERT(m_frameGraphRoot && m_root && m_manager);
258
259 // short-circuit if no LoDs exist
260 if (m_manager->levelOfDetailManager()->count() == 0)
261 return;
262
263 LODUpdateVisitor visitor(m_filterValue, m_frameGraphRoot, m_manager);
264 visitor.apply(root: m_root);
265 m_filterValue = visitor.filterValue();
266 d->m_updatedIndices = visitor.updatedIndices();
267}
268
269bool UpdateLevelOfDetailJobPrivate::isRequired() const
270{
271 Q_Q(const UpdateLevelOfDetailJob);
272 return q->m_manager->levelOfDetailManager()->count() > 0;
273}
274
275void UpdateLevelOfDetailJobPrivate::postFrame(Qt3DCore::QAspectManager *manager)
276{
277 for (const auto &updatedNode: qAsConst(t&: m_updatedIndices)) {
278 QLevelOfDetail *node = qobject_cast<QLevelOfDetail *>(object: manager->lookupNode(id: updatedNode.first));
279 if (!node)
280 continue;
281
282 node->setCurrentIndex(updatedNode.second);
283 }
284}
285
286} // Render
287} // Qt3DRender
288
289QT_END_NAMESPACE
290

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