1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). |
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 <QtTest/qtest.h> |
41 | #include <QtCore/qtemporarydir.h> |
42 | #include <QtGui/qimage.h> |
43 | |
44 | #include <private/qsceneimporter_p.h> |
45 | #include <private/qsceneexportfactory_p.h> |
46 | #include <private/qsceneexporter_p.h> |
47 | #include <private/qsceneimportfactory_p.h> |
48 | #include <private/qsceneimporter_p.h> |
49 | |
50 | #include <Qt3DCore/qentity.h> |
51 | #include <Qt3DCore/qtransform.h> |
52 | |
53 | #include <Qt3DRender/qcamera.h> |
54 | #include <Qt3DRender/qcameralens.h> |
55 | #include <Qt3DRender/qtextureimage.h> |
56 | #include <Qt3DRender/qspotlight.h> |
57 | #include <Qt3DRender/qdirectionallight.h> |
58 | #include <Qt3DRender/qpointlight.h> |
59 | #include <Qt3DRender/qattribute.h> |
60 | #include <Qt3DRender/qbuffer.h> |
61 | #include <Qt3DRender/qeffect.h> |
62 | #include <Qt3DRender/qshaderprogram.h> |
63 | #include <Qt3DRender/qtechnique.h> |
64 | #include <Qt3DRender/qparameter.h> |
65 | #include <Qt3DRender/qgraphicsapifilter.h> |
66 | #include <Qt3DRender/qfilterkey.h> |
67 | #include <Qt3DRender/qtexture.h> |
68 | #include <Qt3DRender/qcolormask.h> |
69 | #include <Qt3DRender/qblendequation.h> |
70 | |
71 | #include <Qt3DExtras/qconemesh.h> |
72 | #include <Qt3DExtras/qcuboidmesh.h> |
73 | #include <Qt3DExtras/qcylindermesh.h> |
74 | #include <Qt3DExtras/qplanemesh.h> |
75 | #include <Qt3DExtras/qspheremesh.h> |
76 | #include <Qt3DExtras/qtorusmesh.h> |
77 | #include <Qt3DExtras/qt3dwindow.h> |
78 | #include <Qt3DExtras/qphongmaterial.h> |
79 | #include <Qt3DExtras/qphongalphamaterial.h> |
80 | #include <Qt3DExtras/qdiffusemapmaterial.h> |
81 | #include <Qt3DExtras/qdiffusespecularmapmaterial.h> |
82 | #include <Qt3DExtras/qnormaldiffusemapmaterial.h> |
83 | #include <Qt3DExtras/qnormaldiffusemapalphamaterial.h> |
84 | #include <Qt3DExtras/qnormaldiffusespecularmapmaterial.h> |
85 | #include <Qt3DExtras/qgoochmaterial.h> |
86 | #include <Qt3DExtras/qpervertexcolormaterial.h> |
87 | #include <Qt3DExtras/qforwardrenderer.h> |
88 | |
89 | //#define VISUAL_CHECK 5000 // The value indicates the time for visual check in ms |
90 | //#define PRESERVE_EXPORT // Uncomment to preserve export directory contents for analysis |
91 | |
92 | class tst_gltfPlugins : public QObject |
93 | { |
94 | Q_OBJECT |
95 | |
96 | private Q_SLOTS: |
97 | |
98 | void initTestCase(); |
99 | void init(); |
100 | void cleanup(); |
101 | void exportAndImport_data(); |
102 | void exportAndImport(); |
103 | |
104 | private: |
105 | void createTestScene(); |
106 | Qt3DCore::QEntity *findCameraChild(Qt3DCore::QEntity *entity, |
107 | Qt3DRender::QCameraLens::ProjectionType type); |
108 | void walkEntity(Qt3DCore::QEntity *entity, int depth); |
109 | void createAndAddEntity(const QString &name, |
110 | Qt3DCore::QComponent *comp1 = nullptr, |
111 | Qt3DCore::QComponent *comp2 = nullptr, |
112 | Qt3DCore::QComponent *comp3 = nullptr, |
113 | Qt3DCore::QEntity *parent = nullptr); |
114 | void addPositionAttributeToGeometry(Qt3DRender::QGeometry *geometry, |
115 | Qt3DRender::QBuffer *buffer, int count); |
116 | void addIndexAttributeToGeometry(Qt3DRender::QGeometry *geometry, |
117 | Qt3DRender::QBuffer *buffer, int count); |
118 | void addColorAttributeToGeometry(Qt3DRender::QGeometry *geometry, |
119 | Qt3DRender::QBuffer *buffer, int count); |
120 | Qt3DCore::QEntity *findChildEntity(Qt3DCore::QEntity *entity, const QString &name); |
121 | Qt3DCore::QTransform *transformComponent(Qt3DCore::QEntity *entity); |
122 | Qt3DRender::QAbstractLight *lightComponent(Qt3DCore::QEntity *entity); |
123 | Qt3DRender::QCameraLens *cameraComponent(Qt3DCore::QEntity *entity); |
124 | Qt3DRender::QGeometryRenderer *meshComponent(Qt3DCore::QEntity *entity); |
125 | Qt3DRender::QMaterial *materialComponent(Qt3DCore::QEntity *entity); |
126 | void compareComponents(Qt3DCore::QComponent *c1, Qt3DCore::QComponent *c2); |
127 | Qt3DRender::QAttribute *findAttribute(const QString &name, |
128 | Qt3DRender::QAttribute::AttributeType type, |
129 | Qt3DRender::QGeometry *geometry); |
130 | void compareAttributes(Qt3DRender::QAttribute *a1, Qt3DRender::QAttribute *a2); |
131 | void compareParameters(const QVector<Qt3DRender::QParameter *> ¶ms1, |
132 | const QVector<Qt3DRender::QParameter *> ¶ms2); |
133 | void compareRenderPasses(const QVector<Qt3DRender::QRenderPass *> &passes1, |
134 | const QVector<Qt3DRender::QRenderPass *> &passes2); |
135 | void compareFilterKeys(const QVector<Qt3DRender::QFilterKey *> &keys1, |
136 | const QVector<Qt3DRender::QFilterKey *> &keys2); |
137 | QUrl getTextureUrl(Qt3DRender::QAbstractTexture *tex); |
138 | Qt3DRender::QGeometryRenderer *createCustomCube(); |
139 | Qt3DRender::QEffect *createOnTopEffect(); |
140 | |
141 | QTemporaryDir *m_exportDir; |
142 | #ifdef VISUAL_CHECK |
143 | Qt3DExtras::Qt3DWindow *m_view1; |
144 | Qt3DExtras::Qt3DWindow *m_view2; |
145 | #endif |
146 | Qt3DCore::QEntity *m_sceneRoot1; |
147 | Qt3DCore::QEntity *m_sceneRoot2; |
148 | QHash<QString, Qt3DCore::QEntity *> m_entityMap; |
149 | }; |
150 | |
151 | void tst_gltfPlugins::initTestCase() |
152 | { |
153 | #ifndef VISUAL_CHECK |
154 | // QEffect doesn't get registered unless aspects are initialized, generating warnings in |
155 | // material comparisons. |
156 | qRegisterMetaType<Qt3DRender::QEffect *>(); |
157 | #endif |
158 | } |
159 | |
160 | void tst_gltfPlugins::init() |
161 | { |
162 | m_exportDir = new QTemporaryDir; |
163 | #ifdef VISUAL_CHECK |
164 | m_view1 = new Qt3DExtras::Qt3DWindow; |
165 | m_view1->setTitle(QStringLiteral("Original scene" )); |
166 | m_view2 = new Qt3DExtras::Qt3DWindow; |
167 | m_view2->setTitle(QStringLiteral("Imported scene" )); |
168 | #endif |
169 | } |
170 | |
171 | void tst_gltfPlugins::cleanup() |
172 | { |
173 | delete m_sceneRoot1; |
174 | delete m_sceneRoot2; |
175 | m_entityMap.clear(); |
176 | #ifdef VISUAL_CHECK |
177 | delete m_view1; |
178 | delete m_view2; |
179 | #endif |
180 | delete m_exportDir; |
181 | // Make sure the slate is clean for the next case |
182 | QCoreApplication::processEvents(); |
183 | QTest::qWait(ms: 0); |
184 | } |
185 | |
186 | void tst_gltfPlugins::walkEntity(Qt3DCore::QEntity *entity, int depth) |
187 | { |
188 | QString indent; |
189 | indent.fill(c: ' ', size: depth * 2); |
190 | qDebug().noquote() << indent << "Entity:" << entity << "Components:" << entity->components(); |
191 | for (auto child : entity->children()) { |
192 | if (auto childEntity = qobject_cast<Qt3DCore::QEntity *>(object: child)) |
193 | walkEntity(entity: childEntity, depth: depth + 1); |
194 | } |
195 | } |
196 | |
197 | void tst_gltfPlugins::createAndAddEntity(const QString &name, |
198 | Qt3DCore::QComponent *comp1, |
199 | Qt3DCore::QComponent *comp2, |
200 | Qt3DCore::QComponent *comp3, |
201 | Qt3DCore::QEntity *parent) |
202 | { |
203 | Qt3DCore::QEntity *parentEntity = parent ? parent : m_sceneRoot1; |
204 | Qt3DCore::QEntity *entity = new Qt3DCore::QEntity(parentEntity); |
205 | entity->setObjectName(name); |
206 | if (comp1) { |
207 | entity->addComponent(comp: comp1); |
208 | comp1->setObjectName(comp1->metaObject()->className()); |
209 | } |
210 | if (comp2) { |
211 | entity->addComponent(comp: comp2); |
212 | comp2->setObjectName(comp2->metaObject()->className()); |
213 | } |
214 | if (comp3) { |
215 | entity->addComponent(comp: comp3); |
216 | comp3->setObjectName(comp3->metaObject()->className()); |
217 | } |
218 | |
219 | m_entityMap.insert(akey: name, avalue: entity); |
220 | } |
221 | |
222 | void tst_gltfPlugins::createTestScene() |
223 | { |
224 | #ifdef VISUAL_CHECK |
225 | m_view1->defaultFrameGraph()->setClearColor(Qt::lightGray); |
226 | m_view2->defaultFrameGraph()->setClearColor(Qt::lightGray); |
227 | #endif |
228 | m_sceneRoot1 = new Qt3DCore::QEntity(); |
229 | m_sceneRoot1->setObjectName(QStringLiteral("Scene root" )); |
230 | m_sceneRoot2 = new Qt3DCore::QEntity(); |
231 | m_sceneRoot2->setObjectName(QStringLiteral("Imported scene parent" )); |
232 | |
233 | // Perspective camera |
234 | { |
235 | Qt3DRender::QCamera *camera = new Qt3DRender::QCamera(m_sceneRoot1); |
236 | camera->setProjectionType(Qt3DRender::QCameraLens::PerspectiveProjection); |
237 | camera->setViewCenter(QVector3D(0.0f, 1.5f, 0.0f)); |
238 | camera->setPosition(QVector3D(0.0f, 3.5f, 15.0f)); |
239 | camera->setNearPlane(0.001f); |
240 | camera->setFarPlane(10000.0f); |
241 | camera->setObjectName(QStringLiteral("Main camera" )); |
242 | camera->transform()->setObjectName(QStringLiteral("Main camera transform" )); |
243 | camera->lens()->setObjectName(QStringLiteral("Main camera lens" )); |
244 | camera->setFieldOfView(30.0f); |
245 | camera->setAspectRatio(1.0f); |
246 | m_entityMap.insert(akey: camera->objectName(), avalue: camera); |
247 | } |
248 | // Ortho camera |
249 | { |
250 | Qt3DCore::QEntity *camera = new Qt3DCore::QEntity(m_sceneRoot1); |
251 | camera->setObjectName(QStringLiteral("Ortho camera" )); |
252 | |
253 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
254 | transform->setTranslation(QVector3D(0.0f, 0.0f, -15.0f)); |
255 | transform->setObjectName(QStringLiteral("Ortho camera transform" )); |
256 | camera->addComponent(comp: transform); |
257 | |
258 | Qt3DRender::QCameraLens *lens = new Qt3DRender::QCameraLens; |
259 | lens->setProjectionType(Qt3DRender::QCameraLens::OrthographicProjection); |
260 | lens->setNearPlane(0.001f); |
261 | lens->setFarPlane(10000.0f); |
262 | lens->setRight(7.0f); |
263 | lens->setLeft(-7.0f); |
264 | lens->setTop(5.0f); |
265 | lens->setBottom(-5.0f); |
266 | lens->setObjectName(QStringLiteral("Ortho camera lens" )); |
267 | camera->addComponent(comp: lens); |
268 | |
269 | m_entityMap.insert(akey: camera->objectName(), avalue: camera); |
270 | #ifdef VISUAL_CHECK |
271 | m_view1->defaultFrameGraph()->setCamera(camera); |
272 | #endif |
273 | } |
274 | |
275 | // Point light |
276 | { |
277 | Qt3DRender::QPointLight *light = new Qt3DRender::QPointLight; |
278 | light->setColor(QColor("#FFDDAA" )); |
279 | light->setIntensity(0.9f); |
280 | light->setConstantAttenuation(0.03f); |
281 | light->setLinearAttenuation(0.04f); |
282 | light->setQuadraticAttenuation(0.01f); |
283 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
284 | transform->setTranslation(QVector3D(-6.0f, 2.0f, 3.0f)); |
285 | createAndAddEntity(QStringLiteral("Point Light" ), comp1: light, comp2: transform); |
286 | } |
287 | // Directional light |
288 | { |
289 | Qt3DRender::QDirectionalLight *light = new Qt3DRender::QDirectionalLight; |
290 | light->setColor(QColor("#BBCCEE" )); |
291 | light->setIntensity(0.75f); |
292 | light->setWorldDirection(QVector3D(-1.0f, -1.0f, -1.0f)); |
293 | createAndAddEntity(QStringLiteral("Directional Light" ), comp1: light); |
294 | } |
295 | // Spot light |
296 | { |
297 | Qt3DRender::QSpotLight *light = new Qt3DRender::QSpotLight; |
298 | light->setColor(QColor("#5599DD" )); |
299 | light->setIntensity(2.0f); |
300 | light->setConstantAttenuation(0.03f); |
301 | light->setLinearAttenuation(0.04f); |
302 | light->setQuadraticAttenuation(0.01f); |
303 | light->setLocalDirection(QVector3D(0.0f, -1.0f, -1.0f)); |
304 | light->setCutOffAngle(30.0f); |
305 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
306 | transform->setTranslation(QVector3D(0.0f, 5.0f, 0.0f)); |
307 | createAndAddEntity(QStringLiteral("Spot Light" ), comp1: light, comp2: transform); |
308 | } |
309 | // Cube with DiffuseMap |
310 | { |
311 | Qt3DExtras::QDiffuseMapMaterial *material = new Qt3DExtras::QDiffuseMapMaterial(); |
312 | Qt3DRender::QTextureImage *diffuseTextureImage = new Qt3DRender::QTextureImage(); |
313 | material->diffuse()->addTextureImage(textureImage: diffuseTextureImage); |
314 | material->setAmbient(QColor("#000088" )); |
315 | material->setSpecular(QColor("#FFFF00" )); |
316 | material->setShininess(30.0); |
317 | material->setTextureScale(2.0f); |
318 | diffuseTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo.png" ))); |
319 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
320 | transform->setScale(0.75f); |
321 | transform->setTranslation(QVector3D(2.0f, 1.0f, -1.0f)); |
322 | Qt3DExtras::QCuboidMesh *mesh = new Qt3DExtras::QCuboidMesh; |
323 | mesh->setXExtent(1.2f); |
324 | mesh->setYExtent(1.1f); |
325 | mesh->setZExtent(0.9f); |
326 | mesh->setYZMeshResolution(QSize(2, 2)); |
327 | mesh->setYZMeshResolution(QSize(2, 3)); |
328 | mesh->setYZMeshResolution(QSize(3, 2)); |
329 | createAndAddEntity(QStringLiteral("Cube with DiffuseMap" ), comp1: mesh, comp2: material, comp3: transform); |
330 | } |
331 | // Cone with PhongAlpha |
332 | { |
333 | Qt3DExtras::QPhongAlphaMaterial *material = new Qt3DExtras::QPhongAlphaMaterial(); |
334 | material->setAlpha(0.6f); |
335 | material->setAmbient(QColor("#550000" )); |
336 | material->setDiffuse(QColor("#00FFFF" )); |
337 | material->setSpecular(QColor("#FFFF00" )); |
338 | material->setShininess(20.0f); |
339 | material->setSourceRgbArg(Qt3DRender::QBlendEquationArguments::Source1Color); |
340 | material->setSourceAlphaArg(Qt3DRender::QBlendEquationArguments::Source1Alpha); |
341 | material->setDestinationRgbArg(Qt3DRender::QBlendEquationArguments::DestinationColor); |
342 | material->setDestinationAlphaArg(Qt3DRender::QBlendEquationArguments::DestinationAlpha); |
343 | material->setBlendFunctionArg(Qt3DRender::QBlendEquation::ReverseSubtract); |
344 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
345 | transform->setTranslation(QVector3D(2.0f, 1.0f, 1.0f)); |
346 | transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: -20.0f)); |
347 | Qt3DExtras::QConeMesh *mesh = new Qt3DExtras::QConeMesh; |
348 | mesh->setRings(2); |
349 | mesh->setSlices(16); |
350 | mesh->setTopRadius(0.5f); |
351 | mesh->setBottomRadius(1.5f); |
352 | mesh->setLength(0.9f); |
353 | createAndAddEntity(QStringLiteral("Cone with PhongAlpha" ), comp1: mesh, comp2: material, comp3: transform); |
354 | } |
355 | // Cylinder with Phong |
356 | { |
357 | Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial(); |
358 | material->setAmbient(QColor("#220022" )); |
359 | material->setDiffuse(QColor("#6633AA" )); |
360 | material->setSpecular(QColor("#66AA33" )); |
361 | material->setShininess(50.0f); |
362 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
363 | transform->setTranslation(QVector3D(0.0f, 1.0f, 1.0f)); |
364 | transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: -45.0f)); |
365 | Qt3DExtras::QCylinderMesh *mesh = new Qt3DExtras::QCylinderMesh; |
366 | mesh->setRadius(0.5f); |
367 | mesh->setRings(3); |
368 | mesh->setLength(1.2f); |
369 | mesh->setSlices(16); |
370 | createAndAddEntity(QStringLiteral("Cylinder with Phong" ), comp1: mesh, comp2: material, comp3: transform); |
371 | } |
372 | // Plane with DiffuseSpecularMap |
373 | { |
374 | Qt3DExtras::QDiffuseSpecularMapMaterial *material = |
375 | new Qt3DExtras::QDiffuseSpecularMapMaterial(); |
376 | Qt3DRender::QTextureImage *diffuseTextureImage = new Qt3DRender::QTextureImage(); |
377 | material->diffuse()->addTextureImage(textureImage: diffuseTextureImage); |
378 | diffuseTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo.png" ))); |
379 | Qt3DRender::QTextureImage *specularTextureImage = new Qt3DRender::QTextureImage(); |
380 | material->specular()->addTextureImage(textureImage: specularTextureImage); |
381 | specularTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_specular.png" ))); |
382 | |
383 | material->setAmbient(QColor("#0000FF" )); |
384 | material->setTextureScale(3.0f); |
385 | material->setShininess(15.0f); |
386 | |
387 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
388 | transform->setTranslation(QVector3D(-1.0f, 1.0f, 1.0f)); |
389 | transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: 45.0f)); |
390 | |
391 | Qt3DExtras::QPlaneMesh *mesh = new Qt3DExtras::QPlaneMesh; |
392 | mesh->setMeshResolution(QSize(3, 3)); |
393 | mesh->setHeight(1.5f); |
394 | mesh->setWidth(1.2f); |
395 | createAndAddEntity(QStringLiteral("Plane with DiffuseSpecularMap" ), |
396 | comp1: mesh, comp2: material, comp3: transform); |
397 | } |
398 | // Sphere with NormalDiffuseMap |
399 | { |
400 | Qt3DExtras::QNormalDiffuseMapMaterial *material = |
401 | new Qt3DExtras::QNormalDiffuseMapMaterial(); |
402 | |
403 | Qt3DRender::QTextureImage *normalTextureImage = new Qt3DRender::QTextureImage(); |
404 | material->normal()->addTextureImage(textureImage: normalTextureImage); |
405 | normalTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_normal.png" ))); |
406 | |
407 | Qt3DRender::QTextureImage *diffuseTextureImage = new Qt3DRender::QTextureImage(); |
408 | material->diffuse()->addTextureImage(textureImage: diffuseTextureImage); |
409 | diffuseTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo.png" ))); |
410 | |
411 | material->setAmbient(QColor("#000044" )); |
412 | material->setSpecular(QColor("#0000CC" )); |
413 | material->setShininess(9.0f); |
414 | material->setTextureScale(4.0f); |
415 | |
416 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
417 | transform->setTranslation(QVector3D(0.0f, 1.0f, -10.0f)); |
418 | |
419 | Qt3DExtras::QSphereMesh *mesh = new Qt3DExtras::QSphereMesh; |
420 | mesh->setRadius(2.0f); |
421 | mesh->setRings(16); |
422 | mesh->setSlices(16); |
423 | mesh->setGenerateTangents(true); |
424 | createAndAddEntity(QStringLiteral("Sphere with NormalDiffuseMap" ), |
425 | comp1: mesh, comp2: material, comp3: transform); |
426 | } |
427 | // Sphere with NormalDiffuseMapAlpha |
428 | { |
429 | Qt3DExtras::QNormalDiffuseMapAlphaMaterial *material = |
430 | new Qt3DExtras::QNormalDiffuseMapAlphaMaterial(); |
431 | |
432 | Qt3DRender::QTextureImage *normalTextureImage = new Qt3DRender::QTextureImage(); |
433 | material->normal()->addTextureImage(textureImage: normalTextureImage); |
434 | normalTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_normal.png" ))); |
435 | |
436 | Qt3DRender::QTextureImage *diffuseTextureImage = new Qt3DRender::QTextureImage(); |
437 | material->diffuse()->addTextureImage(textureImage: diffuseTextureImage); |
438 | diffuseTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_with_alpha.png" ))); |
439 | |
440 | material->setAmbient(QColor("#000044" )); |
441 | material->setSpecular(QColor("#0000CC" )); |
442 | material->setShininess(9.0f); |
443 | material->setTextureScale(4.0f); |
444 | |
445 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
446 | transform->setTranslation(QVector3D(4.0f, 1.0f, -10.0f)); |
447 | |
448 | Qt3DExtras::QSphereMesh *mesh = new Qt3DExtras::QSphereMesh; |
449 | mesh->setRadius(2.0f); |
450 | mesh->setRings(16); |
451 | mesh->setSlices(16); |
452 | mesh->setGenerateTangents(true); |
453 | createAndAddEntity(QStringLiteral("Sphere with NormalDiffuseMapAlpha" ), |
454 | comp1: mesh, comp2: material, comp3: transform); |
455 | } |
456 | // Sphere with NormalDiffuseSpecularMap |
457 | { |
458 | Qt3DExtras::QNormalDiffuseSpecularMapMaterial *material = |
459 | new Qt3DExtras::QNormalDiffuseSpecularMapMaterial(); |
460 | |
461 | Qt3DRender::QTextureImage *normalTextureImage = new Qt3DRender::QTextureImage(); |
462 | material->normal()->addTextureImage(textureImage: normalTextureImage); |
463 | normalTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_normal.png" ))); |
464 | |
465 | Qt3DRender::QTextureImage *diffuseTextureImage = new Qt3DRender::QTextureImage(); |
466 | material->diffuse()->addTextureImage(textureImage: diffuseTextureImage); |
467 | diffuseTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo.png" ))); |
468 | |
469 | Qt3DRender::QTextureImage *specularTextureImage = new Qt3DRender::QTextureImage(); |
470 | material->specular()->addTextureImage(textureImage: specularTextureImage); |
471 | specularTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_specular.png" ))); |
472 | |
473 | material->setAmbient(QColor("#000044" )); |
474 | material->setShininess(9.0f); |
475 | material->setTextureScale(4.0f); |
476 | |
477 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
478 | transform->setTranslation(QVector3D(-4.0f, 1.0f, -10.0f)); |
479 | |
480 | Qt3DExtras::QSphereMesh *mesh = new Qt3DExtras::QSphereMesh; |
481 | mesh->setRadius(2.0f); |
482 | mesh->setRings(16); |
483 | mesh->setSlices(16); |
484 | mesh->setGenerateTangents(true); |
485 | createAndAddEntity(QStringLiteral("Sphere with NormalDiffuseSpecularMap" ), |
486 | comp1: mesh, comp2: material, comp3: transform); |
487 | } |
488 | // Torus with Gooch |
489 | { |
490 | Qt3DExtras::QGoochMaterial *material = new Qt3DExtras::QGoochMaterial(); |
491 | |
492 | material->setDiffuse(QColor("#333333" )); |
493 | material->setSpecular(QColor("#550055" )); |
494 | material->setCool(QColor("#0055AA" )); |
495 | material->setWarm(QColor("#FF3300" )); |
496 | material->setAlpha(0.2f); |
497 | material->setBeta(0.4f); |
498 | material->setShininess(22.0f); |
499 | |
500 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
501 | transform->setTranslation(QVector3D(0.0f, 4.0f, -10.0f)); |
502 | |
503 | Qt3DExtras::QTorusMesh *mesh = new Qt3DExtras::QTorusMesh; |
504 | mesh->setRadius(1.0f); |
505 | mesh->setMinorRadius(0.5f); |
506 | mesh->setRings(16); |
507 | mesh->setSlices(16); |
508 | createAndAddEntity(QStringLiteral("Torus with Gooch" ), comp1: mesh, comp2: material, comp3: transform); |
509 | } |
510 | // Custom cube with per-vertex colors |
511 | { |
512 | Qt3DExtras::QPerVertexColorMaterial *material = new Qt3DExtras::QPerVertexColorMaterial(); |
513 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
514 | transform->setTranslation(QVector3D(4.0f, 3.0f, -15.0f)); |
515 | transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(x: 1.0f, y: 1.0f, z: 1.0f, angle: 270.0f)); |
516 | |
517 | Qt3DRender::QGeometryRenderer *boxMesh = createCustomCube(); |
518 | Qt3DRender::QBuffer *colorDataBuffer = |
519 | new Qt3DRender::QBuffer(boxMesh->geometry()); |
520 | QByteArray colorBufferData; |
521 | colorBufferData.resize(size: 8 * 4 * sizeof(float)); |
522 | |
523 | float *cPtr = reinterpret_cast<float *>(colorBufferData.data()); |
524 | for (int i = 0; i < 8; i++) { |
525 | cPtr[i * 4] = float(i) / 8.0f; |
526 | cPtr[i * 4 + 1] = float(8 - i) / 8.0f; |
527 | cPtr[i * 4 + 2] = float((i + 4) % 8) / 8.0f; |
528 | cPtr[i * 4 + 3] = 1.0f; |
529 | } |
530 | |
531 | colorDataBuffer->setData(colorBufferData); |
532 | |
533 | addColorAttributeToGeometry(geometry: boxMesh->geometry(), buffer: colorDataBuffer, count: 8); |
534 | |
535 | createAndAddEntity(QStringLiteral("Custom cube with per-vertex colors" ), |
536 | comp1: boxMesh, comp2: material, comp3: transform); |
537 | } |
538 | // Child cylinder with Phong |
539 | { |
540 | Qt3DCore::QEntity *parentEntity = findChildEntity(entity: m_sceneRoot1, |
541 | QStringLiteral("Cylinder with Phong" )); |
542 | Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial(); |
543 | material->setAmbient(QColor("#333333" )); |
544 | material->setDiffuse(QColor("#88FF00" )); |
545 | material->setSpecular(QColor("#000088" )); |
546 | material->setShininess(150.0f); |
547 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
548 | transform->setTranslation(QVector3D(0.0f, 4.0f, 0.0f)); |
549 | Qt3DExtras::QCylinderMesh *mesh = new Qt3DExtras::QCylinderMesh; |
550 | mesh->setRadius(0.25f); |
551 | mesh->setRings(3); |
552 | mesh->setLength(1.5f); |
553 | mesh->setSlices(16); |
554 | createAndAddEntity(QStringLiteral("Child with Phong" ), |
555 | comp1: mesh, comp2: material, comp3: transform, parent: parentEntity); |
556 | } |
557 | // Cube with custom material |
558 | { |
559 | Qt3DRender::QMaterial *material = new Qt3DRender::QMaterial; |
560 | material->setEffect(createOnTopEffect()); |
561 | material->addParameter(parameter: new Qt3DRender::QParameter(QStringLiteral("globalOffset" ), |
562 | QVector3D(-3.0f, 0.0f, 3.0f))); |
563 | material->addParameter(parameter: new Qt3DRender::QParameter(QStringLiteral("extraYOffset" ), 3)); |
564 | material->effect()->addParameter(parameter: new Qt3DRender::QParameter(QStringLiteral("handleColor" ), |
565 | QColor(Qt::magenta))); |
566 | material->effect()->addParameter(parameter: new Qt3DRender::QParameter(QStringLiteral("reverseOffset" ), |
567 | QVariant::fromValue(value: true))); |
568 | |
569 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; |
570 | transform->setTranslation(QVector3D(0.0f, 2.0f, -40.0f)); |
571 | transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(x: 1.0f, y: 2.0f, z: 3.0f, angle: 90.0f)); |
572 | Qt3DRender::QGeometryRenderer *boxMesh = createCustomCube(); |
573 | Qt3DRender::QBuffer *offsetBuffer = |
574 | new Qt3DRender::QBuffer(boxMesh->geometry()); |
575 | QByteArray offsetBufferData; |
576 | offsetBufferData.resize(size: 8 * 3 * sizeof(float)); |
577 | |
578 | float *oPtr = reinterpret_cast<float *>(offsetBufferData.data()); |
579 | for (int i = 0; i < 8; i++) { |
580 | oPtr[i * 3] = float(i) / 4.0f; |
581 | oPtr[i * 3 + 1] = float(8 - i) / 4.0f + 2.0f; |
582 | oPtr[i * 3 + 2] = float((i + 4) % 8) / 4.0f; |
583 | } |
584 | |
585 | offsetBuffer->setData(offsetBufferData); |
586 | |
587 | Qt3DRender::QAttribute *customAttribute = new Qt3DRender::QAttribute(); |
588 | customAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); |
589 | customAttribute->setBuffer(offsetBuffer); |
590 | customAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float); |
591 | customAttribute->setVertexSize(3); |
592 | customAttribute->setByteOffset(0); |
593 | customAttribute->setByteStride(0); |
594 | customAttribute->setCount(8); |
595 | customAttribute->setName(QStringLiteral("vertexOffset" )); |
596 | |
597 | boxMesh->geometry()->addAttribute(attribute: customAttribute); |
598 | |
599 | createAndAddEntity(QStringLiteral("Custom cube with on-top material" ), |
600 | comp1: boxMesh, comp2: material, comp3: transform); |
601 | } |
602 | |
603 | #ifdef VISUAL_CHECK |
604 | m_view1->setGeometry(30, 30, 400, 400); |
605 | m_view1->setRootEntity(m_sceneRoot1); |
606 | m_view1->show(); |
607 | |
608 | m_view2->setGeometry(450, 30, 400, 400); |
609 | m_view2->setRootEntity(m_sceneRoot2); |
610 | m_view2->show(); |
611 | |
612 | QTest::qWaitForWindowExposed(m_view1); |
613 | QTest::qWaitForWindowExposed(m_view2); |
614 | #endif |
615 | } |
616 | |
617 | void tst_gltfPlugins::addPositionAttributeToGeometry(Qt3DRender::QGeometry *geometry, |
618 | Qt3DRender::QBuffer *buffer, int count) |
619 | { |
620 | Qt3DRender::QAttribute *posAttribute = new Qt3DRender::QAttribute(); |
621 | posAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); |
622 | posAttribute->setBuffer(buffer); |
623 | posAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float); |
624 | posAttribute->setVertexSize(3); |
625 | posAttribute->setByteOffset(0); |
626 | posAttribute->setByteStride(0); |
627 | posAttribute->setCount(count); |
628 | posAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName()); |
629 | |
630 | geometry->addAttribute(attribute: posAttribute); |
631 | } |
632 | |
633 | void tst_gltfPlugins::addIndexAttributeToGeometry(Qt3DRender::QGeometry *geometry, |
634 | Qt3DRender::QBuffer *buffer, int count) |
635 | { |
636 | Qt3DRender::QAttribute *indexAttribute = new Qt3DRender::QAttribute(); |
637 | indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute); |
638 | indexAttribute->setBuffer(buffer); |
639 | indexAttribute->setVertexBaseType(Qt3DRender::QAttribute::UnsignedShort); |
640 | indexAttribute->setVertexSize(1); |
641 | indexAttribute->setByteOffset(0); |
642 | indexAttribute->setByteStride(0); |
643 | indexAttribute->setCount(count); |
644 | |
645 | geometry->addAttribute(attribute: indexAttribute); |
646 | } |
647 | |
648 | void tst_gltfPlugins::addColorAttributeToGeometry(Qt3DRender::QGeometry *geometry, |
649 | Qt3DRender::QBuffer *buffer, int count) |
650 | { |
651 | Qt3DRender::QAttribute *colorAttribute = new Qt3DRender::QAttribute(); |
652 | colorAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); |
653 | colorAttribute->setBuffer(buffer); |
654 | colorAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float); |
655 | colorAttribute->setVertexSize(4); |
656 | colorAttribute->setByteOffset(0); |
657 | colorAttribute->setByteStride(0); |
658 | colorAttribute->setCount(count); |
659 | colorAttribute->setName(Qt3DRender::QAttribute::defaultColorAttributeName()); |
660 | |
661 | geometry->addAttribute(attribute: colorAttribute); |
662 | } |
663 | |
664 | Qt3DCore::QEntity *tst_gltfPlugins::findChildEntity(Qt3DCore::QEntity *entity, const QString &name) |
665 | { |
666 | for (auto child : entity->children()) { |
667 | if (auto childEntity = qobject_cast<Qt3DCore::QEntity *>(object: child)) { |
668 | if (childEntity->objectName() == name) |
669 | return childEntity; |
670 | if (auto foundEntity = findChildEntity(entity: childEntity, name)) |
671 | return foundEntity; |
672 | } |
673 | } |
674 | return nullptr; |
675 | } |
676 | |
677 | Qt3DCore::QTransform *tst_gltfPlugins::transformComponent(Qt3DCore::QEntity *entity) |
678 | { |
679 | for (auto component : entity->components()) { |
680 | if (auto castedComponent = qobject_cast<Qt3DCore::QTransform *>(object: component)) |
681 | return castedComponent; |
682 | } |
683 | return nullptr; |
684 | } |
685 | |
686 | Qt3DRender::QAbstractLight *tst_gltfPlugins::lightComponent(Qt3DCore::QEntity *entity) |
687 | { |
688 | for (auto component : entity->components()) { |
689 | if (auto castedComponent = qobject_cast<Qt3DRender::QAbstractLight *>(object: component)) |
690 | return castedComponent; |
691 | } |
692 | return nullptr; |
693 | } |
694 | |
695 | Qt3DRender::QCameraLens *tst_gltfPlugins::cameraComponent(Qt3DCore::QEntity *entity) |
696 | { |
697 | for (auto component : entity->components()) { |
698 | if (auto castedComponent = qobject_cast<Qt3DRender::QCameraLens *>(object: component)) |
699 | return castedComponent; |
700 | } |
701 | return nullptr; |
702 | } |
703 | |
704 | Qt3DRender::QGeometryRenderer *tst_gltfPlugins::meshComponent(Qt3DCore::QEntity *entity) |
705 | { |
706 | for (auto component : entity->components()) { |
707 | if (auto castedComponent = qobject_cast<Qt3DRender::QGeometryRenderer *>(object: component)) |
708 | return castedComponent; |
709 | } |
710 | return nullptr; |
711 | } |
712 | |
713 | Qt3DRender::QMaterial *tst_gltfPlugins::materialComponent(Qt3DCore::QEntity *entity) |
714 | { |
715 | for (auto component : entity->components()) { |
716 | if (auto castedComponent = qobject_cast<Qt3DRender::QMaterial *>(object: component)) |
717 | return castedComponent; |
718 | } |
719 | return nullptr; |
720 | } |
721 | |
722 | void tst_gltfPlugins::compareComponents(Qt3DCore::QComponent *c1, Qt3DCore::QComponent *c2) |
723 | { |
724 | // Make sure component classes are the same and the non-pointer properties are the same |
725 | QCOMPARE((c1 == nullptr), (c2 == nullptr)); |
726 | if (c1) { |
727 | // Transform names are lost in export, as the transform is just part of the node item |
728 | if (!qobject_cast<Qt3DCore::QTransform *>(object: c1)) |
729 | QCOMPARE(c1->objectName(), c2->objectName()); |
730 | QCOMPARE(c1->metaObject()->className(), c2->metaObject()->className()); |
731 | // Meshes are all imported as generic meshes |
732 | if (auto mesh1 = qobject_cast<Qt3DRender::QGeometryRenderer *>(object: c1)) { |
733 | auto mesh2 = qobject_cast<Qt3DRender::QGeometryRenderer *>(object: c2); |
734 | QVERIFY(mesh2 != nullptr); |
735 | auto geometry1 = mesh1->geometry(); |
736 | auto geometry2 = mesh2->geometry(); |
737 | // Check that attributes match. |
738 | compareAttributes( |
739 | a1: findAttribute(name: Qt3DRender::QAttribute::defaultPositionAttributeName(), |
740 | type: Qt3DRender::QAttribute::VertexAttribute, |
741 | geometry: geometry1), |
742 | a2: findAttribute(name: Qt3DRender::QAttribute::defaultPositionAttributeName(), |
743 | type: Qt3DRender::QAttribute::VertexAttribute, |
744 | geometry: geometry2)); |
745 | compareAttributes( |
746 | a1: findAttribute(name: Qt3DRender::QAttribute::defaultNormalAttributeName(), |
747 | type: Qt3DRender::QAttribute::VertexAttribute, |
748 | geometry: geometry1), |
749 | a2: findAttribute(name: Qt3DRender::QAttribute::defaultNormalAttributeName(), |
750 | type: Qt3DRender::QAttribute::VertexAttribute, |
751 | geometry: geometry2)); |
752 | compareAttributes( |
753 | a1: findAttribute(name: Qt3DRender::QAttribute::defaultTangentAttributeName(), |
754 | type: Qt3DRender::QAttribute::VertexAttribute, |
755 | geometry: geometry1), |
756 | a2: findAttribute(name: Qt3DRender::QAttribute::defaultTangentAttributeName(), |
757 | type: Qt3DRender::QAttribute::VertexAttribute, |
758 | geometry: geometry2)); |
759 | compareAttributes( |
760 | a1: findAttribute(name: Qt3DRender::QAttribute::defaultTextureCoordinateAttributeName(), |
761 | type: Qt3DRender::QAttribute::VertexAttribute, |
762 | geometry: geometry1), |
763 | a2: findAttribute(name: Qt3DRender::QAttribute::defaultTextureCoordinateAttributeName(), |
764 | type: Qt3DRender::QAttribute::VertexAttribute, |
765 | geometry: geometry2)); |
766 | compareAttributes( |
767 | a1: findAttribute(name: Qt3DRender::QAttribute::defaultColorAttributeName(), |
768 | type: Qt3DRender::QAttribute::VertexAttribute, |
769 | geometry: geometry1), |
770 | a2: findAttribute(name: Qt3DRender::QAttribute::defaultColorAttributeName(), |
771 | type: Qt3DRender::QAttribute::VertexAttribute, |
772 | geometry: geometry2)); |
773 | compareAttributes( |
774 | a1: findAttribute(QStringLiteral("" ), |
775 | type: Qt3DRender::QAttribute::IndexAttribute, |
776 | geometry: geometry1), |
777 | a2: findAttribute(QStringLiteral("" ), |
778 | type: Qt3DRender::QAttribute::IndexAttribute, |
779 | geometry: geometry2)); |
780 | } else { |
781 | int count = c1->metaObject()->propertyCount(); |
782 | for (int i = 0; i < count; i++) { |
783 | auto property = c1->metaObject()->property(index: i); |
784 | auto v1 = c1->property(name: property.name()); |
785 | auto v2 = c2->property(name: property.name()); |
786 | if (v1.type() == QVariant::Bool) { |
787 | QCOMPARE(v1.toBool(), v2.toBool()); |
788 | } else if (v1.type() == QVariant::Color) { |
789 | QCOMPARE(v1.value<QColor>(), v2.value<QColor>()); |
790 | } else if (v1.type() == QVariant::Vector3D) { |
791 | QCOMPARE(v1.value<QVector3D>(), v2.value<QVector3D>()); |
792 | } else if (v1.type() == QVariant::Matrix4x4) { |
793 | QCOMPARE(v1.value<QMatrix4x4>(), v2.value<QMatrix4x4>()); |
794 | } else if (v1.canConvert(targetTypeId: QMetaType::Float)) { |
795 | QVERIFY(qFuzzyCompare(v1.toFloat(), v2.toFloat())); |
796 | } |
797 | } |
798 | if (QString::fromLatin1(str: c1->metaObject()->className()) |
799 | .endsWith(QStringLiteral("Qt3DRender::QMaterial" ))) { |
800 | auto m1 = qobject_cast<Qt3DRender::QMaterial *>(object: c1); |
801 | auto m2 = qobject_cast<Qt3DRender::QMaterial *>(object: c2); |
802 | QVERIFY(m1); |
803 | QVERIFY(m2); |
804 | auto e1 = m1->effect(); |
805 | auto e2 = m2->effect(); |
806 | QVERIFY(e1); |
807 | QVERIFY(e2); |
808 | QCOMPARE(e1->objectName(), e2->objectName()); |
809 | QCOMPARE(e1->techniques().size(), e2->techniques().size()); |
810 | |
811 | compareParameters(params1: m1->parameters(), params2: m2->parameters()); |
812 | compareParameters(params1: e1->parameters(), params2: e2->parameters()); |
813 | |
814 | for (auto t1 : e1->techniques()) { |
815 | bool techMatch = false; |
816 | for (auto t2 : e2->techniques()) { |
817 | if (t1->objectName() == t2->objectName()) { |
818 | techMatch = true; |
819 | compareParameters(params1: t1->parameters(), params2: t2->parameters()); |
820 | compareFilterKeys(keys1: t1->filterKeys(), keys2: t2->filterKeys()); |
821 | compareRenderPasses(passes1: t1->renderPasses(), passes2: t2->renderPasses()); |
822 | QCOMPARE(t1->graphicsApiFilter()->api(), |
823 | t2->graphicsApiFilter()->api()); |
824 | QCOMPARE(t1->graphicsApiFilter()->profile(), |
825 | t2->graphicsApiFilter()->profile()); |
826 | QCOMPARE(t1->graphicsApiFilter()->minorVersion(), |
827 | t2->graphicsApiFilter()->minorVersion()); |
828 | QCOMPARE(t1->graphicsApiFilter()->majorVersion(), |
829 | t2->graphicsApiFilter()->majorVersion()); |
830 | QCOMPARE(t1->graphicsApiFilter()->extensions(), |
831 | t2->graphicsApiFilter()->extensions()); |
832 | QCOMPARE(t1->graphicsApiFilter()->vendor(), |
833 | t2->graphicsApiFilter()->vendor()); |
834 | } |
835 | } |
836 | QVERIFY(techMatch); |
837 | } |
838 | } |
839 | } |
840 | } |
841 | } |
842 | |
843 | Qt3DRender::QAttribute *tst_gltfPlugins::findAttribute(const QString &name, |
844 | Qt3DRender::QAttribute::AttributeType type, |
845 | Qt3DRender::QGeometry *geometry) |
846 | { |
847 | for (auto att : geometry->attributes()) { |
848 | if ((type == Qt3DRender::QAttribute::IndexAttribute && type == att->attributeType()) |
849 | || name == att->name()) { |
850 | return att; |
851 | } |
852 | } |
853 | return nullptr; |
854 | } |
855 | |
856 | void tst_gltfPlugins::compareAttributes(Qt3DRender::QAttribute *a1, Qt3DRender::QAttribute *a2) |
857 | { |
858 | QCOMPARE(a1 == nullptr, a2 == nullptr); |
859 | if (a1) { |
860 | QCOMPARE(a1->attributeType(), a2->attributeType()); |
861 | QCOMPARE(a1->vertexBaseType(), a2->vertexBaseType()); |
862 | QCOMPARE(a1->vertexSize(), a2->vertexSize()); |
863 | QCOMPARE(a1->count(), a2->count()); |
864 | } |
865 | } |
866 | |
867 | void tst_gltfPlugins::compareParameters(const QVector<Qt3DRender::QParameter *> ¶ms1, |
868 | const QVector<Qt3DRender::QParameter *> ¶ms2) |
869 | { |
870 | QCOMPARE(params1.size(), params2.size()); |
871 | for (auto p1 : params1) { |
872 | bool pMatch = false; |
873 | for (auto p2 : params2) { |
874 | if (p1->name() == p2->name()) { |
875 | pMatch = true; |
876 | if (p1->value().type() == QVariant::Color) { |
877 | // Colors are imported as QVector4Ds |
878 | QColor color = p1->value().value<QColor>(); |
879 | QVector4D vec = p2->value().value<QVector4D>(); |
880 | QCOMPARE(color.redF(), vec.x()); |
881 | QCOMPARE(color.greenF(), vec.y()); |
882 | QCOMPARE(color.blueF(), vec.z()); |
883 | QCOMPARE(color.alphaF(), vec.w()); |
884 | } else if (p1->value().canConvert<Qt3DRender::QAbstractTexture *>()) { |
885 | QUrl u1 = getTextureUrl(tex: p1->value().value<Qt3DRender::QAbstractTexture *>()); |
886 | QUrl u2 = getTextureUrl(tex: p2->value().value<Qt3DRender::QAbstractTexture *>()); |
887 | QCOMPARE(u1.fileName(), u2.fileName()); |
888 | } else { |
889 | QCOMPARE(p1->value(), p2->value()); |
890 | } |
891 | } |
892 | } |
893 | QVERIFY(pMatch); |
894 | } |
895 | } |
896 | |
897 | void tst_gltfPlugins::compareRenderPasses(const QVector<Qt3DRender::QRenderPass *> &passes1, |
898 | const QVector<Qt3DRender::QRenderPass *> &passes2) |
899 | { |
900 | QCOMPARE(passes1.size(), passes2.size()); |
901 | for (auto pass1 : passes1) { |
902 | bool passMatch = false; |
903 | for (auto pass2 : passes2) { |
904 | if (pass1->objectName() == pass2->objectName()) { |
905 | passMatch = true; |
906 | compareFilterKeys(keys1: pass1->filterKeys(), keys2: pass2->filterKeys()); |
907 | compareParameters(params1: pass1->parameters(), params2: pass2->parameters()); |
908 | |
909 | QVector<Qt3DRender::QRenderState *> states1 = pass1->renderStates(); |
910 | QVector<Qt3DRender::QRenderState *> states2 = pass2->renderStates(); |
911 | QCOMPARE(states1.size(), states2.size()); |
912 | for (auto state1 : states1) { |
913 | bool stateMatch = false; |
914 | for (auto state2 : states2) { |
915 | if (state1->metaObject()->className() |
916 | == state2->metaObject()->className()) { |
917 | stateMatch = true; |
918 | } |
919 | } |
920 | QVERIFY(stateMatch); |
921 | } |
922 | |
923 | QCOMPARE(pass1->shaderProgram()->vertexShaderCode(), |
924 | pass2->shaderProgram()->vertexShaderCode()); |
925 | QCOMPARE(pass1->shaderProgram()->fragmentShaderCode(), |
926 | pass2->shaderProgram()->fragmentShaderCode()); |
927 | } |
928 | } |
929 | QVERIFY(passMatch); |
930 | } |
931 | } |
932 | |
933 | void tst_gltfPlugins::compareFilterKeys(const QVector<Qt3DRender::QFilterKey *> &keys1, |
934 | const QVector<Qt3DRender::QFilterKey *> &keys2) |
935 | { |
936 | QCOMPARE(keys1.size(), keys2.size()); |
937 | for (auto k1 : keys1) { |
938 | bool kMatch = false; |
939 | for (auto k2 : keys2) { |
940 | if (k1->name() == k2->name()) { |
941 | kMatch = true; |
942 | QCOMPARE(k1->value(), k2->value()); |
943 | } |
944 | } |
945 | QVERIFY(kMatch); |
946 | } |
947 | } |
948 | |
949 | QUrl tst_gltfPlugins::getTextureUrl(Qt3DRender::QAbstractTexture *tex) |
950 | { |
951 | QUrl url; |
952 | if (tex->textureImages().size()) { |
953 | Qt3DRender::QTextureImage *img = |
954 | qobject_cast<Qt3DRender::QTextureImage *>( |
955 | object: tex->textureImages().at(i: 0)); |
956 | if (img) |
957 | url = img->source(); |
958 | } |
959 | return url; |
960 | } |
961 | |
962 | Qt3DRender::QGeometryRenderer *tst_gltfPlugins::createCustomCube() |
963 | { |
964 | Qt3DRender::QGeometryRenderer *boxMesh = new Qt3DRender::QGeometryRenderer; |
965 | Qt3DRender::QGeometry *boxGeometry = new Qt3DRender::QGeometry(boxMesh); |
966 | Qt3DRender::QBuffer *boxDataBuffer = |
967 | new Qt3DRender::QBuffer(boxGeometry); |
968 | Qt3DRender::QBuffer *indexDataBuffer = |
969 | new Qt3DRender::QBuffer(boxGeometry); |
970 | QByteArray vertexBufferData; |
971 | QByteArray indexBufferData; |
972 | |
973 | vertexBufferData.resize(size: 8 * 3 * sizeof(float)); |
974 | indexBufferData.resize(size: 12 * 3 * sizeof(ushort)); |
975 | |
976 | float dimension = 1.0f; |
977 | |
978 | float *vPtr = reinterpret_cast<float *>(vertexBufferData.data()); |
979 | vPtr[0] = -dimension; vPtr[1] = -dimension; vPtr[2] = -dimension; |
980 | vPtr[3] = dimension; vPtr[4] = -dimension; vPtr[5] = -dimension; |
981 | vPtr[6] = dimension; vPtr[7] = -dimension; vPtr[8] = dimension; |
982 | vPtr[9] = -dimension; vPtr[10] = -dimension; vPtr[11] = dimension; |
983 | vPtr[12] = -dimension; vPtr[13] = dimension; vPtr[14] = -dimension; |
984 | vPtr[15] = dimension; vPtr[16] = dimension; vPtr[17] = -dimension; |
985 | vPtr[18] = dimension; vPtr[19] = dimension; vPtr[20] = dimension; |
986 | vPtr[21] = -dimension; vPtr[22] = dimension; vPtr[23] = dimension; |
987 | |
988 | ushort *iPtr = reinterpret_cast<ushort *>(indexBufferData.data()); |
989 | iPtr[0] = 2; iPtr[1] = 0; iPtr[2] = 1; |
990 | iPtr[3] = 2; iPtr[4] = 3; iPtr[5] = 0; |
991 | iPtr[6] = 1; iPtr[7] = 6; iPtr[8] = 2; |
992 | iPtr[9] = 1; iPtr[10] = 5; iPtr[11] = 6; |
993 | iPtr[12] = 2; iPtr[13] = 7; iPtr[14] = 3; |
994 | iPtr[15] = 2; iPtr[16] = 6; iPtr[17] = 7; |
995 | iPtr[18] = 6; iPtr[19] = 5; iPtr[20] = 4; |
996 | iPtr[21] = 6; iPtr[22] = 4; iPtr[23] = 7; |
997 | iPtr[24] = 7; iPtr[25] = 0; iPtr[26] = 3; |
998 | iPtr[27] = 7; iPtr[28] = 4; iPtr[29] = 0; |
999 | iPtr[30] = 4; iPtr[31] = 1; iPtr[32] = 0; |
1000 | iPtr[33] = 4; iPtr[34] = 5; iPtr[35] = 1; |
1001 | |
1002 | boxDataBuffer->setData(vertexBufferData); |
1003 | indexDataBuffer->setData(indexBufferData); |
1004 | |
1005 | addPositionAttributeToGeometry(geometry: boxGeometry, buffer: boxDataBuffer, count: 8); |
1006 | addIndexAttributeToGeometry(geometry: boxGeometry, buffer: indexDataBuffer, count: 36); |
1007 | |
1008 | boxMesh->setInstanceCount(1); |
1009 | boxMesh->setIndexOffset(0); |
1010 | boxMesh->setFirstInstance(0); |
1011 | boxMesh->setVertexCount(36); |
1012 | boxMesh->setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles); |
1013 | boxMesh->setGeometry(boxGeometry); |
1014 | |
1015 | return boxMesh; |
1016 | } |
1017 | |
1018 | Qt3DRender::QEffect *tst_gltfPlugins::createOnTopEffect() |
1019 | { |
1020 | Qt3DRender::QEffect *effect = new Qt3DRender::QEffect; |
1021 | |
1022 | Qt3DRender::QTechnique *technique = new Qt3DRender::QTechnique(); |
1023 | technique->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::NoProfile); |
1024 | technique->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGL); |
1025 | technique->graphicsApiFilter()->setMajorVersion(2); |
1026 | technique->graphicsApiFilter()->setMinorVersion(1); |
1027 | |
1028 | Qt3DRender::QTechnique *techniqueCore = new Qt3DRender::QTechnique(); |
1029 | techniqueCore->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::CoreProfile); |
1030 | techniqueCore->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGL); |
1031 | techniqueCore->graphicsApiFilter()->setMajorVersion(3); |
1032 | techniqueCore->graphicsApiFilter()->setMinorVersion(1); |
1033 | |
1034 | Qt3DRender::QTechnique *techniqueES2 = new Qt3DRender::QTechnique(); |
1035 | techniqueES2->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGLES); |
1036 | techniqueES2->graphicsApiFilter()->setMajorVersion(2); |
1037 | techniqueES2->graphicsApiFilter()->setMinorVersion(0); |
1038 | techniqueES2->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::NoProfile); |
1039 | |
1040 | Qt3DRender::QFilterKey *filterkey1 = new Qt3DRender::QFilterKey(effect); |
1041 | Qt3DRender::QFilterKey *filterkey2 = new Qt3DRender::QFilterKey(); |
1042 | filterkey1->setName(QStringLiteral("renderingStyle" )); |
1043 | filterkey1->setValue(QStringLiteral("forward" )); |
1044 | filterkey2->setName(QStringLiteral("dummyKey" )); |
1045 | filterkey2->setValue(QStringLiteral("dummyValue" )); |
1046 | |
1047 | Qt3DRender::QParameter *parameter1 = new Qt3DRender::QParameter(QStringLiteral("handleColor" ), |
1048 | QColor(Qt::yellow)); |
1049 | Qt3DRender::QParameter *parameter2 = new Qt3DRender::QParameter(QStringLiteral("customAlpha" ), |
1050 | 1.0f); |
1051 | Qt3DRender::QParameter *parameter3 = new Qt3DRender::QParameter(QStringLiteral("handleColor" ), |
1052 | QColor(Qt::blue)); |
1053 | Qt3DRender::QTexture2D *texture = new Qt3DRender::QTexture2D; |
1054 | Qt3DRender::QParameter *parameter4 = |
1055 | new Qt3DRender::QParameter(QStringLiteral("customTexture" ), texture); |
1056 | Qt3DRender::QTextureImage *ti = new Qt3DRender::QTextureImage(); |
1057 | parameter4->value().value<Qt3DRender::QAbstractTexture *>()->addTextureImage(textureImage: ti); |
1058 | ti->setSource(QUrl(QStringLiteral("qrc:/qtlogo.png" ))); |
1059 | |
1060 | technique->addFilterKey(filterKey: filterkey1); |
1061 | technique->addFilterKey(filterKey: filterkey2); |
1062 | techniqueES2->addFilterKey(filterKey: filterkey1); |
1063 | techniqueES2->addFilterKey(filterKey: filterkey2); |
1064 | techniqueCore->addFilterKey(filterKey: filterkey1); |
1065 | |
1066 | technique->addParameter(p: parameter1); |
1067 | technique->addParameter(p: parameter2); |
1068 | technique->addParameter(p: parameter4); |
1069 | techniqueES2->addParameter(p: parameter1); |
1070 | techniqueES2->addParameter(p: parameter2); |
1071 | |
1072 | Qt3DRender::QShaderProgram *shader = new Qt3DRender::QShaderProgram(); |
1073 | Qt3DRender::QShaderProgram *shaderES2 = new Qt3DRender::QShaderProgram(); |
1074 | shader->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource( |
1075 | sourceUrl: QUrl(QStringLiteral("qrc:/ontopmaterial.vert" )))); |
1076 | shader->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource( |
1077 | sourceUrl: QUrl(QStringLiteral("qrc:/ontopmaterial.frag" )))); |
1078 | shaderES2->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource( |
1079 | sourceUrl: QUrl(QStringLiteral("qrc:/ontopmaterialES2.vert" )))); |
1080 | shaderES2->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource( |
1081 | sourceUrl: QUrl(QStringLiteral("qrc:/ontopmaterialES2.frag" )))); |
1082 | shader->setObjectName(QStringLiteral("Basic shader" )); |
1083 | shaderES2->setObjectName(QStringLiteral("ES2 shader" )); |
1084 | |
1085 | Qt3DRender::QRenderPass *renderPass = new Qt3DRender::QRenderPass(); |
1086 | Qt3DRender::QRenderPass *renderPassES2 = new Qt3DRender::QRenderPass(); |
1087 | renderPass->setShaderProgram(shader); |
1088 | renderPassES2->setShaderProgram(shaderES2); |
1089 | renderPass->addFilterKey(filterKey: filterkey2); |
1090 | renderPass->addParameter(p: parameter3); |
1091 | Qt3DRender::QColorMask *cmask = new Qt3DRender::QColorMask; |
1092 | cmask->setRedMasked(false); |
1093 | renderPass->addRenderState(state: cmask); |
1094 | Qt3DRender::QBlendEquation *be = new Qt3DRender::QBlendEquation; |
1095 | be->setBlendFunction(Qt3DRender::QBlendEquation::Subtract); |
1096 | renderPass->addRenderState(state: be); |
1097 | technique->addRenderPass(pass: renderPassES2); |
1098 | techniqueES2->addRenderPass(pass: renderPassES2); |
1099 | techniqueCore->addRenderPass(pass: renderPass); |
1100 | technique->setObjectName(QStringLiteral("Basic technique" )); |
1101 | techniqueES2->setObjectName(QStringLiteral("ES2 technique" )); |
1102 | techniqueCore->setObjectName(QStringLiteral("Core technique" )); |
1103 | renderPass->setObjectName(QStringLiteral("Basic pass" )); |
1104 | renderPassES2->setObjectName(QStringLiteral("ES2 pass" )); |
1105 | |
1106 | effect->addTechnique(t: technique); |
1107 | effect->addTechnique(t: techniqueES2); |
1108 | effect->addTechnique(t: techniqueCore); |
1109 | effect->setObjectName(QStringLiteral("OnTopEffect" )); |
1110 | |
1111 | return effect; |
1112 | } |
1113 | |
1114 | Qt3DCore::QEntity *tst_gltfPlugins::findCameraChild(Qt3DCore::QEntity *entity, |
1115 | Qt3DRender::QCameraLens::ProjectionType type) |
1116 | { |
1117 | for (auto child : entity->children()) { |
1118 | if (auto childEntity = qobject_cast<Qt3DCore::QEntity *>(object: child)) { |
1119 | for (auto component : childEntity->components()) { |
1120 | if (auto cameraLens = qobject_cast<Qt3DRender::QCameraLens *>(object: component)) { |
1121 | if (cameraLens->projectionType() == type) |
1122 | return childEntity; |
1123 | } |
1124 | } |
1125 | if (auto cameraEntity = findCameraChild(entity: childEntity, type)) |
1126 | return cameraEntity; |
1127 | } |
1128 | } |
1129 | return nullptr; |
1130 | } |
1131 | |
1132 | void tst_gltfPlugins::exportAndImport_data() |
1133 | { |
1134 | QTest::addColumn<bool>(name: "binaryJson" ); |
1135 | QTest::addColumn<bool>(name: "compactJson" ); |
1136 | |
1137 | QTest::newRow(dataTag: "No options" ) << false << false; |
1138 | #ifndef VISUAL_CHECK |
1139 | QTest::newRow(dataTag: "Binary json" ) << true << false; |
1140 | QTest::newRow(dataTag: "Compact json" ) << false << true; |
1141 | QTest::newRow(dataTag: "Binary/Compact json" ) << true << true; // Compact is ignored in this case |
1142 | #endif |
1143 | } |
1144 | |
1145 | void tst_gltfPlugins::exportAndImport() |
1146 | { |
1147 | QFETCH(bool, binaryJson); |
1148 | QFETCH(bool, compactJson); |
1149 | |
1150 | createTestScene(); |
1151 | |
1152 | #ifdef PRESERVE_EXPORT |
1153 | m_exportDir->setAutoRemove(false); |
1154 | qDebug() << "Export Directory:" << m_exportDir->path(); |
1155 | #endif |
1156 | |
1157 | const QString sceneName = QStringLiteral("MyGLTFScene" ); |
1158 | const QString exportDir = m_exportDir->path(); |
1159 | |
1160 | // Export the created scene using GLTF export plugin |
1161 | QStringList keys = Qt3DRender::QSceneExportFactory::keys(); |
1162 | for (const QString &key : keys) { |
1163 | Qt3DRender::QSceneExporter *exporter = |
1164 | Qt3DRender::QSceneExportFactory::create(name: key, args: QStringList()); |
1165 | if (exporter != nullptr && key == QStringLiteral("gltfexport" )) { |
1166 | QVariantHash options; |
1167 | options.insert(QStringLiteral("binaryJson" ), avalue: QVariant(binaryJson)); |
1168 | options.insert(QStringLiteral("compactJson" ), avalue: QVariant(compactJson)); |
1169 | exporter->exportScene(sceneRoot: m_sceneRoot1, outDir: exportDir, exportName: sceneName, options); |
1170 | break; |
1171 | } |
1172 | } |
1173 | |
1174 | QCoreApplication::processEvents(); |
1175 | |
1176 | // Import the exported scene using GLTF import plugin |
1177 | Qt3DCore::QEntity *importedScene = nullptr; |
1178 | keys = Qt3DRender::QSceneImportFactory::keys(); |
1179 | for (auto key : keys) { |
1180 | Qt3DRender::QSceneImporter *importer = |
1181 | Qt3DRender::QSceneImportFactory::create(name: key, args: QStringList()); |
1182 | if (importer != nullptr && key == QStringLiteral("gltf" )) { |
1183 | QString sceneSource = exportDir; |
1184 | if (!sceneSource.endsWith(c: QLatin1Char('/'))) |
1185 | sceneSource.append(c: QLatin1Char('/')); |
1186 | sceneSource += sceneName; |
1187 | sceneSource += QLatin1Char('/'); |
1188 | sceneSource += sceneName; |
1189 | sceneSource += QStringLiteral(".qgltf" ); |
1190 | importer->setSource(QUrl::fromLocalFile(localfile: sceneSource)); |
1191 | importedScene = importer->scene(); |
1192 | break; |
1193 | } |
1194 | } |
1195 | |
1196 | importedScene->setParent(m_sceneRoot2); |
1197 | |
1198 | // Compare contents of the original scene and the exported one. |
1199 | for (auto it = m_entityMap.begin(), end = m_entityMap.end(); it != end; ++it) { |
1200 | QString name = it.key(); |
1201 | Qt3DCore::QEntity *exportedEntity = it.value(); |
1202 | Qt3DCore::QEntity *importedEntity = findChildEntity(entity: importedScene, name); |
1203 | QVERIFY(importedEntity != nullptr); |
1204 | if (importedEntity) { |
1205 | compareComponents(c1: transformComponent(entity: exportedEntity), |
1206 | c2: transformComponent(entity: importedEntity)); |
1207 | compareComponents(c1: lightComponent(entity: exportedEntity), |
1208 | c2: lightComponent(entity: importedEntity)); |
1209 | compareComponents(c1: cameraComponent(entity: exportedEntity), |
1210 | c2: cameraComponent(entity: importedEntity)); |
1211 | compareComponents(c1: meshComponent(entity: exportedEntity), |
1212 | c2: meshComponent(entity: importedEntity)); |
1213 | compareComponents(c1: materialComponent(entity: exportedEntity), |
1214 | c2: materialComponent(entity: importedEntity)); |
1215 | Qt3DRender::QCamera *exportedCamera = |
1216 | qobject_cast<Qt3DRender::QCamera *>(object: exportedEntity); |
1217 | if (exportedCamera) { |
1218 | Qt3DRender::QCamera *importedCamera = |
1219 | qobject_cast<Qt3DRender::QCamera *>(object: importedEntity); |
1220 | QVERIFY(importedCamera != nullptr); |
1221 | QCOMPARE(exportedCamera->position(), importedCamera->position()); |
1222 | QCOMPARE(exportedCamera->upVector(), importedCamera->upVector()); |
1223 | QCOMPARE(exportedCamera->viewCenter(), importedCamera->viewCenter()); |
1224 | } |
1225 | } |
1226 | } |
1227 | |
1228 | |
1229 | #ifdef VISUAL_CHECK |
1230 | qDebug() << "Dumping original entity tree:" ; |
1231 | walkEntity(m_sceneRoot1, 0); |
1232 | qDebug() << "Dumping imported entity tree:" ; |
1233 | walkEntity(importedScene, 0); |
1234 | |
1235 | // Find the camera to actually show the scene |
1236 | m_view2->defaultFrameGraph()->setCamera( |
1237 | findCameraChild(m_sceneRoot2, Qt3DRender::QCameraLens::OrthographicProjection)); |
1238 | QTest::qWait(VISUAL_CHECK); |
1239 | |
1240 | m_view1->defaultFrameGraph()->setCamera( |
1241 | findCameraChild(m_sceneRoot1, Qt3DRender::QCameraLens::PerspectiveProjection)); |
1242 | m_view2->defaultFrameGraph()->setCamera( |
1243 | findCameraChild(m_sceneRoot2, Qt3DRender::QCameraLens::PerspectiveProjection)); |
1244 | QTest::qWait(VISUAL_CHECK); |
1245 | #endif |
1246 | } |
1247 | |
1248 | QTEST_MAIN(tst_gltfPlugins) |
1249 | |
1250 | #include "tst_gltfplugins.moc" |
1251 | |