1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
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:GPL-EXCEPT$
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 General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QtTest/QTest>
31#include <QObject>
32#include <QSignalSpy>
33
34#include <qbackendnodetester.h>
35
36#include <Qt3DCore/QEntity>
37#include <Qt3DCore/private/qaspectjobmanager_p.h>
38#include <Qt3DCore/private/qnodevisitor_p.h>
39#include <Qt3DCore/qnodecreatedchange.h>
40#include <Qt3DCore/qtransform.h>
41
42#include <Qt3DRender/qcamera.h>
43#include <Qt3DRender/qcameralens.h>
44#include <Qt3DRender/qrenderaspect.h>
45#include <Qt3DRender/private/entity_p.h>
46#include <Qt3DRender/private/qcamera_p.h>
47#include <Qt3DRender/private/cameralens_p.h>
48#include <Qt3DRender/private/managers_p.h>
49#include <Qt3DRender/private/nodemanagers_p.h>
50#include <Qt3DRender/private/transform_p.h>
51#include <Qt3DRender/private/qrenderaspect_p.h>
52#include <Qt3DRender/private/updateworldtransformjob_p.h>
53
54QT_BEGIN_NAMESPACE
55
56namespace Qt3DRender {
57
58QVector<Qt3DCore::QNode *> getNodesForCreation(Qt3DCore::QNode *root)
59{
60 using namespace Qt3DCore;
61
62 QVector<QNode *> nodes;
63 Qt3DCore::QNodeVisitor visitor;
64 visitor.traverse(rootNode_: root, fN: [&nodes](QNode *node) {
65 nodes.append(t: node);
66
67 // Store the metaobject of the node in the QNode so that we have it available
68 // to us during destruction in the QNode destructor. This allows us to send
69 // the QNodeId and the metaobject as typeinfo to the backend aspects so they
70 // in turn can find the correct QBackendNodeMapper object to handle the destruction
71 // of the corresponding backend nodes.
72 QNodePrivate *d = QNodePrivate::get(q: node);
73 d->m_typeInfo = const_cast<QMetaObject*>(QNodePrivate::findStaticMetaObject(metaObject: node->metaObject()));
74
75 // Mark this node as having been handled for creation so that it is picked up
76 d->m_hasBackendNode = true;
77 });
78
79 return nodes;
80}
81
82QVector<Qt3DCore::NodeTreeChange> nodeTreeChangesForNodes(const QVector<Qt3DCore::QNode *> nodes)
83{
84 QVector<Qt3DCore::NodeTreeChange> nodeTreeChanges;
85 nodeTreeChanges.reserve(asize: nodes.size());
86
87 for (Qt3DCore::QNode *n : nodes) {
88 nodeTreeChanges.push_back(t: {
89 .id: n->id(),
90 .metaObj: Qt3DCore::QNodePrivate::get(q: n)->m_typeInfo,
91 .type: Qt3DCore::NodeTreeChange::Added,
92 .node: n
93 });
94 }
95
96 return nodeTreeChanges;
97}
98
99class TestAspect : public Qt3DRender::QRenderAspect
100{
101public:
102 TestAspect(Qt3DCore::QNode *root)
103 : Qt3DRender::QRenderAspect(Qt3DRender::QRenderAspect::Synchronous)
104 , m_sceneRoot(nullptr)
105 {
106 QRenderAspect::onRegistered();
107
108 const QVector<Qt3DCore::QNode *> nodes = getNodesForCreation(root);
109 d_func()->setRootAndCreateNodes(rootObject: qobject_cast<Qt3DCore::QEntity *>(object: root), nodesTreeChanges: nodeTreeChangesForNodes(nodes));
110
111 Render::Entity *rootEntity = nodeManagers()->lookupResource<Render::Entity, Render::EntityManager>(id: rootEntityId());
112 Q_ASSERT(rootEntity);
113 m_sceneRoot = rootEntity;
114 }
115
116 ~TestAspect();
117
118 void onRegistered() { QRenderAspect::onRegistered(); }
119 void onUnregistered() { QRenderAspect::onUnregistered(); }
120
121 Qt3DRender::Render::NodeManagers *nodeManagers() const { return d_func()->m_renderer->nodeManagers(); }
122 Qt3DRender::Render::FrameGraphNode *frameGraphRoot() const { return d_func()->m_renderer->frameGraphRoot(); }
123 Qt3DRender::Render::RenderSettings *renderSettings() const { return d_func()->m_renderer->settings(); }
124 Qt3DRender::Render::Entity *sceneRoot() const { return m_sceneRoot; }
125
126private:
127 Render::Entity *m_sceneRoot;
128};
129
130TestAspect::~TestAspect()
131{
132 QRenderAspect::onUnregistered();
133}
134
135} // namespace Qt3DRender
136
137QT_END_NAMESPACE
138
139namespace {
140
141void runRequiredJobs(Qt3DRender::TestAspect *test)
142{
143 Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform;
144 updateWorldTransform.setRoot(test->sceneRoot());
145 updateWorldTransform.setManagers(test->nodeManagers());
146 updateWorldTransform.run();
147}
148
149void fuzzyCompareMatrix(const QMatrix4x4 &a, const QMatrix4x4 &b)
150{
151 for (int i = 0; i < 4; i++) {
152 for (int j = 0; j < 4; j++) {
153 // Fuzzy comparison. qFuzzyCompare is not suitable because zero is compared to small numbers.
154 QVERIFY(qAbs(a(i, j) - b(i, j)) < 1.0e-6f);
155 }
156 }
157}
158
159} // anonymous
160
161class tst_QCamera : public Qt3DCore::QBackendNodeTester
162{
163 Q_OBJECT
164
165private Q_SLOTS:
166
167 void initTestCase()
168 {
169 }
170
171 void checkDefaultConstruction()
172 {
173 // GIVEN
174 Qt3DRender::QCamera camera;
175
176 // THEN
177 QCOMPARE(camera.projectionType(), Qt3DRender::QCameraLens::PerspectiveProjection);
178 QCOMPARE(camera.nearPlane(), 0.1f);
179 QCOMPARE(camera.farPlane(), 1024.0f);
180 QCOMPARE(camera.fieldOfView(), 25.0f);
181 QCOMPARE(camera.aspectRatio(), 1.0f);
182 QCOMPARE(camera.left(), -0.5f);
183 QCOMPARE(camera.right(), 0.5f);
184 QCOMPARE(camera.bottom(), -0.5f);
185 QCOMPARE(camera.top(), 0.5f);
186 QCOMPARE(camera.exposure(), 0.0f);
187 }
188
189 void checkTransformWithParent()
190 {
191 // GIVEN
192 QScopedPointer<Qt3DCore::QEntity> root(new Qt3DCore::QEntity);
193
194 QScopedPointer<Qt3DCore::QEntity> parent(new Qt3DCore::QEntity(root.data()));
195 Qt3DCore::QTransform *parentTransform = new Qt3DCore::QTransform();
196 parentTransform->setTranslation(QVector3D(10, 9, 8));
197 parentTransform->setRotationX(10.f);
198 parentTransform->setRotationY(20.f);
199 parentTransform->setRotationZ(30.f);
200 parent->addComponent(comp: parentTransform);
201
202 QScopedPointer<Qt3DRender::QCamera> camera(new Qt3DRender::QCamera(parent.data()));
203
204 QScopedPointer<Qt3DRender::TestAspect> test(new Qt3DRender::TestAspect(root.data()));
205 runRequiredJobs(test: test.data());
206
207 Qt3DRender::Render::Entity *cameraEntity = test->nodeManagers()->lookupResource<Qt3DRender::Render::Entity, Qt3DRender::Render::EntityManager>(id: camera->id());
208
209 // THEN
210 QVERIFY(qFuzzyCompare(convertToQMatrix4x4(*cameraEntity->worldTransform()), convertToQMatrix4x4(parentTransform->matrix())));
211 }
212
213 void checkTransformWithParentAndLookAt()
214 {
215 // GIVEN
216 QScopedPointer<Qt3DCore::QEntity> root(new Qt3DCore::QEntity);
217
218 QScopedPointer<Qt3DCore::QEntity> parent(new Qt3DCore::QEntity(root.data()));
219 Qt3DCore::QTransform *parentTransform = new Qt3DCore::QTransform();
220 parentTransform->setRotationZ(90.f);
221 parent->addComponent(comp: parentTransform);
222
223 QScopedPointer<Qt3DRender::QCamera> camera(new Qt3DRender::QCamera(parent.data()));
224 camera->setPosition(QVector3D(1.f, 0.f, 0.f));
225 camera->setViewCenter(QVector3D(1.f, 0.f, -1.f)); // look in -z
226 camera->setUpVector(QVector3D(0.f, 1.f, 0.f));
227
228 QScopedPointer<Qt3DRender::TestAspect> test(new Qt3DRender::TestAspect(root.data()));
229 runRequiredJobs(test: test.data());
230
231 Qt3DRender::Render::Entity *cameraEntity = test->nodeManagers()->lookupResource<Qt3DRender::Render::Entity, Qt3DRender::Render::EntityManager>(id: camera->id());
232
233 // THEN
234 QMatrix4x4 m;
235 m.translate(x: 0.f, y: 1.f, z: 0.f); // 90 deg z-rotation + x-translation = y-translation
236 m.rotate(angle: 90.f, vector: QVector3D(0.f, 0.f, 1.f));
237
238 const QMatrix4x4 worldTransform = convertToQMatrix4x4(v: *cameraEntity->worldTransform());
239 fuzzyCompareMatrix(a: worldTransform, b: m);
240
241 }
242
243 void checkTransformOfChild()
244 {
245 // GIVEN
246 QScopedPointer<Qt3DCore::QEntity> root(new Qt3DCore::QEntity);
247
248 QScopedPointer<Qt3DCore::QEntity> parent(new Qt3DCore::QEntity(root.data()));
249 Qt3DCore::QTransform *parentTransform = new Qt3DCore::QTransform();
250 parentTransform->setTranslation(QVector3D(10, 9, 8));
251 parent->addComponent(comp: parentTransform);
252
253 QScopedPointer<Qt3DRender::QCamera> camera(new Qt3DRender::QCamera(parent.data()));
254 camera->setPosition(QVector3D(2.f, 3.f, 4.f));
255 camera->setViewCenter(QVector3D(1.f, 3.f, 4.f)); // looking in -x = 90 deg y-rotation
256 camera->setUpVector(QVector3D(0.f, 1.f, 0.f));
257
258 // Child coordinate system:
259 // y = camera up vector = global y
260 // z = negative camera look direction = global x
261 // x = y cross z = global -z
262 QScopedPointer<Qt3DCore::QEntity> child(new Qt3DCore::QEntity(camera.data()));
263 Qt3DCore::QTransform *childTransform = new Qt3DCore::QTransform();
264 childTransform->setTranslation(QVector3D(1.f, 2.f, 3.f));
265 child->addComponent(comp: childTransform);
266
267 QScopedPointer<Qt3DRender::TestAspect> test(new Qt3DRender::TestAspect(root.data()));
268 runRequiredJobs(test: test.data());
269
270 Qt3DRender::Render::Entity *childEntity = test->nodeManagers()->lookupResource<Qt3DRender::Render::Entity, Qt3DRender::Render::EntityManager>(id: child->id());
271
272 // THEN
273 QMatrix4x4 m;
274 m.translate(x: 10.f, y: 9.f, z: 8.f); // parent's world translation
275 m.translate(x: 2.f, y: 3.f, z: 4.f); // camera's world translation
276 m.translate(x: 3.f, y: 2.f, z: -1.f); // child's world translation
277 m.rotate(angle: 90.f, vector: QVector3D(0.f, 1.f, 0.f)); // camera's rotation
278
279 fuzzyCompareMatrix(a: convertToQMatrix4x4(v: *childEntity->worldTransform()), b: m);
280 }
281};
282
283
284QTEST_MAIN(tst_QCamera)
285
286#include "tst_qcamera.moc"
287

source code of qt3d/tests/auto/render/qcamera/tst_qcamera.cpp